From 2863c0726cfba1c9819df3cab3a5adb1b7ba35ac Mon Sep 17 00:00:00 2001 From: Wonchan Lee Date: Fri, 24 Sep 2021 22:04:42 -0700 Subject: [PATCH 001/462] Reserve numpy.h for the c++ interface of legate.numpy --- src/arg.cc | 2 +- src/binary/binary_op.h | 2 +- src/binary/binary_op_util.h | 2 +- src/binary/binary_red.h | 2 +- src/item/read.h | 2 +- src/item/write.h | 2 +- src/mapper.h | 2 +- src/matrix/diag.h | 2 +- src/matrix/dot.h | 2 +- src/matrix/matmul.h | 2 +- src/matrix/matvecmul.h | 2 +- src/matrix/tile.h | 2 +- src/matrix/transpose.h | 2 +- src/nullary/arange.h | 2 +- src/nullary/eye.h | 2 +- src/nullary/fill.h | 2 +- src/numpy.cc | 7 ++++--- src/numpy.cu | 4 ++-- src/{numpy.h => numpy_task.h} | 0 src/pitches.h | 2 +- src/random/rand.h | 2 +- src/random/rand_util.h | 2 +- src/search/nonzero.h | 2 +- src/stat/bincount.h | 2 +- src/ternary/where.h | 2 +- src/unary/convert.h | 2 +- src/unary/convert_util.h | 2 +- src/unary/scalar_unary_red.h | 2 +- src/unary/unary_op.h | 2 +- src/unary/unary_op_util.h | 2 +- src/unary/unary_red.h | 2 +- src/unary/unary_red_util.h | 2 +- 32 files changed, 35 insertions(+), 34 deletions(-) rename src/{numpy.h => numpy_task.h} (100%) diff --git a/src/arg.cc b/src/arg.cc index d94cd9dce4..cbc215e3b5 100644 --- a/src/arg.cc +++ b/src/arg.cc @@ -47,7 +47,7 @@ DECLARE_IDENTITIES(uint32_t) DECLARE_IDENTITIES(uint64_t) DECLARE_IDENTITIES(complex) -#define _REGISTER_REDOP(ID, TYPE) Runtime::register_reduction_op(ID); +#define _REGISTER_REDOP(ID, TYPE) Legion::Runtime::register_reduction_op(ID); #define REGISTER_REDOPS(OP) \ { \ diff --git a/src/binary/binary_op.h b/src/binary/binary_op.h index 93b748850c..2d08605149 100644 --- a/src/binary/binary_op.h +++ b/src/binary/binary_op.h @@ -16,7 +16,7 @@ #pragma once -#include "numpy.h" +#include "numpy_task.h" #include "binary/binary_op_util.h" namespace legate { diff --git a/src/binary/binary_op_util.h b/src/binary/binary_op_util.h index 70741d578c..82dc1c4223 100644 --- a/src/binary/binary_op_util.h +++ b/src/binary/binary_op_util.h @@ -16,7 +16,7 @@ #pragma once -#include "numpy.h" +#include "numpy_task.h" namespace legate { namespace numpy { diff --git a/src/binary/binary_red.h b/src/binary/binary_red.h index 96f549a0ea..23820c4686 100644 --- a/src/binary/binary_red.h +++ b/src/binary/binary_red.h @@ -16,7 +16,7 @@ #pragma once -#include "numpy.h" +#include "numpy_task.h" #include "binary/binary_op_util.h" namespace legate { diff --git a/src/item/read.h b/src/item/read.h index 6fa1740453..6db9321867 100644 --- a/src/item/read.h +++ b/src/item/read.h @@ -16,7 +16,7 @@ #pragma once -#include "numpy.h" +#include "numpy_task.h" namespace legate { namespace numpy { diff --git a/src/item/write.h b/src/item/write.h index 6fa6652ade..419e3f43ed 100644 --- a/src/item/write.h +++ b/src/item/write.h @@ -16,7 +16,7 @@ #pragma once -#include "numpy.h" +#include "numpy_task.h" namespace legate { namespace numpy { diff --git a/src/mapper.h b/src/mapper.h index 30c5efb1f9..28a4338601 100644 --- a/src/mapper.h +++ b/src/mapper.h @@ -16,7 +16,7 @@ #pragma once -#include "numpy.h" +#include "numpy_task.h" #include "mapping/base_mapper.h" diff --git a/src/matrix/diag.h b/src/matrix/diag.h index a7b227d074..f777af9c01 100644 --- a/src/matrix/diag.h +++ b/src/matrix/diag.h @@ -16,7 +16,7 @@ #pragma once -#include "numpy.h" +#include "numpy_task.h" namespace legate { namespace numpy { diff --git a/src/matrix/dot.h b/src/matrix/dot.h index ae3bbeccf4..00bc409569 100644 --- a/src/matrix/dot.h +++ b/src/matrix/dot.h @@ -16,7 +16,7 @@ #pragma once -#include "numpy.h" +#include "numpy_task.h" namespace legate { namespace numpy { diff --git a/src/matrix/matmul.h b/src/matrix/matmul.h index a45d18c5c6..af4ddf245c 100644 --- a/src/matrix/matmul.h +++ b/src/matrix/matmul.h @@ -16,7 +16,7 @@ #pragma once -#include "numpy.h" +#include "numpy_task.h" namespace legate { namespace numpy { diff --git a/src/matrix/matvecmul.h b/src/matrix/matvecmul.h index a5cf2465e7..6ea7de648f 100644 --- a/src/matrix/matvecmul.h +++ b/src/matrix/matvecmul.h @@ -16,7 +16,7 @@ #pragma once -#include "numpy.h" +#include "numpy_task.h" namespace legate { namespace numpy { diff --git a/src/matrix/tile.h b/src/matrix/tile.h index 60e3b69963..7557ebae35 100644 --- a/src/matrix/tile.h +++ b/src/matrix/tile.h @@ -16,7 +16,7 @@ #pragma once -#include "numpy.h" +#include "numpy_task.h" namespace legate { namespace numpy { diff --git a/src/matrix/transpose.h b/src/matrix/transpose.h index 956eb9de29..1017c4fdab 100644 --- a/src/matrix/transpose.h +++ b/src/matrix/transpose.h @@ -16,7 +16,7 @@ #pragma once -#include "numpy.h" +#include "numpy_task.h" namespace legate { namespace numpy { diff --git a/src/nullary/arange.h b/src/nullary/arange.h index d2c97fb5a9..623e64fa42 100644 --- a/src/nullary/arange.h +++ b/src/nullary/arange.h @@ -16,7 +16,7 @@ #pragma once -#include "numpy.h" +#include "numpy_task.h" namespace legate { namespace numpy { diff --git a/src/nullary/eye.h b/src/nullary/eye.h index 77867e6870..39eeccf716 100644 --- a/src/nullary/eye.h +++ b/src/nullary/eye.h @@ -16,7 +16,7 @@ #pragma once -#include "numpy.h" +#include "numpy_task.h" namespace legate { namespace numpy { diff --git a/src/nullary/fill.h b/src/nullary/fill.h index 4a3a157b67..3d678af95d 100644 --- a/src/nullary/fill.h +++ b/src/nullary/fill.h @@ -16,7 +16,7 @@ #pragma once -#include "numpy.h" +#include "numpy_task.h" namespace legate { namespace numpy { diff --git a/src/numpy.cc b/src/numpy.cc index 237731e8fd..6528b15298 100644 --- a/src/numpy.cc +++ b/src/numpy.cc @@ -14,7 +14,7 @@ * */ -#include "numpy.h" +#include "numpy_task.h" #include "mapper.h" #include "unary/unary_red_util.h" @@ -38,7 +38,7 @@ extern void register_cpu_reduction_operators(LibraryContext& context); #endif void registration_callback(Machine machine, - Runtime* runtime, + Legion::Runtime* runtime, const std::set& local_procs) { ResourceConfig config; @@ -73,6 +73,7 @@ void legate_numpy_perform_registration(void) // Tell the runtime about our registration callback so we hook it // in before the runtime starts and make it global so that we know // that this call back is invoked everywhere across all nodes - Runtime::perform_registration_callback(legate::numpy::registration_callback, true /*global*/); + Legion::Runtime::perform_registration_callback(legate::numpy::registration_callback, + true /*global*/); } } diff --git a/src/numpy.cu b/src/numpy.cu index 3e8886dd8e..a78655ddb6 100644 --- a/src/numpy.cu +++ b/src/numpy.cu @@ -14,7 +14,7 @@ * */ -#include "numpy.h" +#include "numpy_task.h" #include "arg.h" namespace legate { @@ -41,7 +41,7 @@ class CUDAReductionOpWrapper : public T { }; #define _REGISTER_REDOP(ID, TYPE) \ - Runtime::register_reduction_op( \ + Legion::Runtime::register_reduction_op( \ ID, \ Realm::ReductionOpUntyped::create_reduction_op>(), \ NULL, \ diff --git a/src/numpy.h b/src/numpy_task.h similarity index 100% rename from src/numpy.h rename to src/numpy_task.h diff --git a/src/pitches.h b/src/pitches.h index fd71d04d3d..14a0167679 100644 --- a/src/pitches.h +++ b/src/pitches.h @@ -16,7 +16,7 @@ #pragma once -#include "numpy.h" +#include "numpy_task.h" namespace legate { namespace numpy { diff --git a/src/random/rand.h b/src/random/rand.h index 7d7f917f56..eaf7328241 100644 --- a/src/random/rand.h +++ b/src/random/rand.h @@ -16,7 +16,7 @@ #pragma once -#include "numpy.h" +#include "numpy_task.h" #include "random/rand_util.h" namespace legate { diff --git a/src/random/rand_util.h b/src/random/rand_util.h index 1f990c07e8..da33748527 100644 --- a/src/random/rand_util.h +++ b/src/random/rand_util.h @@ -16,7 +16,7 @@ #pragma once -#include "numpy.h" +#include "numpy_task.h" #include "random/philox.h" #define HI_BITS(x) ((unsigned)((x) >> 32)) diff --git a/src/search/nonzero.h b/src/search/nonzero.h index 9e16dd5f9d..f891ad20ad 100644 --- a/src/search/nonzero.h +++ b/src/search/nonzero.h @@ -16,7 +16,7 @@ #pragma once -#include "numpy.h" +#include "numpy_task.h" namespace legate { namespace numpy { diff --git a/src/stat/bincount.h b/src/stat/bincount.h index 9ac2e79709..e264d375af 100644 --- a/src/stat/bincount.h +++ b/src/stat/bincount.h @@ -16,7 +16,7 @@ #pragma once -#include "numpy.h" +#include "numpy_task.h" namespace legate { namespace numpy { diff --git a/src/ternary/where.h b/src/ternary/where.h index ae13050e55..904806a3d3 100644 --- a/src/ternary/where.h +++ b/src/ternary/where.h @@ -16,7 +16,7 @@ #pragma once -#include "numpy.h" +#include "numpy_task.h" namespace legate { namespace numpy { diff --git a/src/unary/convert.h b/src/unary/convert.h index 15ab163887..a5cb383bd6 100644 --- a/src/unary/convert.h +++ b/src/unary/convert.h @@ -16,7 +16,7 @@ #pragma once -#include "numpy.h" +#include "numpy_task.h" namespace legate { namespace numpy { diff --git a/src/unary/convert_util.h b/src/unary/convert_util.h index e8cd1ebcc3..02a550a55c 100644 --- a/src/unary/convert_util.h +++ b/src/unary/convert_util.h @@ -16,7 +16,7 @@ #pragma once -#include "numpy.h" +#include "numpy_task.h" namespace legate { namespace numpy { diff --git a/src/unary/scalar_unary_red.h b/src/unary/scalar_unary_red.h index 4908e038e5..b87356e581 100644 --- a/src/unary/scalar_unary_red.h +++ b/src/unary/scalar_unary_red.h @@ -16,7 +16,7 @@ #pragma once -#include "numpy.h" +#include "numpy_task.h" #include "unary/unary_red_util.h" namespace legate { diff --git a/src/unary/unary_op.h b/src/unary/unary_op.h index 1250bbb6d1..713a2032ee 100644 --- a/src/unary/unary_op.h +++ b/src/unary/unary_op.h @@ -16,7 +16,7 @@ #pragma once -#include "numpy.h" +#include "numpy_task.h" #include "unary/unary_op_util.h" namespace legate { diff --git a/src/unary/unary_op_util.h b/src/unary/unary_op_util.h index fa004c9254..107fec22d8 100644 --- a/src/unary/unary_op_util.h +++ b/src/unary/unary_op_util.h @@ -16,7 +16,7 @@ #pragma once -#include "numpy.h" +#include "numpy_task.h" #include "arg.h" #include diff --git a/src/unary/unary_red.h b/src/unary/unary_red.h index ff94405296..de342f4f2b 100644 --- a/src/unary/unary_red.h +++ b/src/unary/unary_red.h @@ -16,7 +16,7 @@ #pragma once -#include "numpy.h" +#include "numpy_task.h" #include "unary/unary_red_util.h" namespace legate { diff --git a/src/unary/unary_red_util.h b/src/unary/unary_red_util.h index a0562e711c..d6c5126878 100644 --- a/src/unary/unary_red_util.h +++ b/src/unary/unary_red_util.h @@ -16,7 +16,7 @@ #pragma once -#include "numpy.h" +#include "numpy_task.h" #include "arg.h" namespace legate { From 47d77051d6fcf5fda80bd2a9472ad8741863b02c Mon Sep 17 00:00:00 2001 From: Wonchan Lee Date: Sat, 25 Sep 2021 01:08:04 -0700 Subject: [PATCH 002/462] Start building a c++ interface --- src/numpy.cc | 32 +++++++++++++++++------ src/numpy.h | 70 ++++++++++++++++++++++++++++++++++++++++++++++++++ src/numpy.mk | 3 +++ src/runtime.cc | 68 ++++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 165 insertions(+), 8 deletions(-) create mode 100644 src/numpy.h create mode 100644 src/runtime.cc diff --git a/src/numpy.cc b/src/numpy.cc index 6528b15298..f36f3507d5 100644 --- a/src/numpy.cc +++ b/src/numpy.cc @@ -14,6 +14,7 @@ * */ +#include "numpy.h" #include "numpy_task.h" #include "mapper.h" #include "unary/unary_red_util.h" @@ -38,29 +39,44 @@ extern void register_cpu_reduction_operators(LibraryContext& context); #endif void registration_callback(Machine machine, - Legion::Runtime* runtime, + Legion::Runtime* legion_runtime, const std::set& local_procs) { ResourceConfig config; config.max_mappers = NUMPY_MAX_MAPPERS; config.max_tasks = NUMPY_MAX_TASKS; config.max_reduction_ops = NUMPY_MAX_REDOPS; - LibraryContext context(runtime, numpy_library_name, config); - LegateNumPy::get_registrar().register_all_tasks(runtime, context); + auto runtime = legate::Runtime::get_runtime(); + + auto context = runtime->create_library(numpy_library_name, config); + + LegateNumPy::get_registrar().register_all_tasks(legion_runtime, *context); // Register our special reduction functions #ifdef LEGATE_USE_CUDA - register_gpu_reduction_operators(context); + register_gpu_reduction_operators(*context); #else - register_cpu_reduction_operators(context); + register_cpu_reduction_operators(*context); #endif // Now we can register our mapper with the runtime - auto numpy_mapper_id = context.get_mapper_id(0); - auto mapper = new NumPyMapper(runtime->get_mapper_runtime(), machine, context); + auto numpy_mapper_id = context->get_mapper_id(0); + auto mapper = new NumPyMapper(legion_runtime->get_mapper_runtime(), machine, *context); // This will register it with all the processors on the node - runtime->add_mapper(numpy_mapper_id, mapper); + legion_runtime->add_mapper(numpy_mapper_id, mapper); +} + +void bootstrapping_callback(Machine machine, + Legion::Runtime* legion_runtime, + const std::set& local_procs) +{ + registration_callback(machine, legion_runtime, local_procs); + + auto runtime = legate::Runtime::get_runtime(); + auto context = runtime->find_library(numpy_library_name); + + NumPyRuntime::initialize(runtime, context); } } // namespace numpy diff --git a/src/numpy.h b/src/numpy.h new file mode 100644 index 0000000000..b3bd0eca73 --- /dev/null +++ b/src/numpy.h @@ -0,0 +1,70 @@ +/* Copyright 2021 NVIDIA Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +#pragma once + +#include + +#include "legate.h" + +namespace legate { + +using LogicalStore = void; + +namespace numpy { + +// TODO: The following functions and classes will later be reorganized into separate header files + +void initialize(int32_t argc, char** argv); + +class NumPyArray; + +class NumPyRuntime { + private: + NumPyRuntime(Runtime* legate_runtime, LibraryContext* context); + + public: + std::shared_ptr create_array(std::vector shape, LegateTypeCode type); + + public: + static NumPyRuntime* get_runtime(); + static void initialize(Runtime* legate_runtime, LibraryContext* context); + + private: + static NumPyRuntime* runtime_; + + private: + Runtime* legate_runtime_; + LibraryContext* context_; +}; + +class NumPyArray { + friend class NumPyRuntime; + + private: + NumPyArray(NumPyRuntime* runtime, std::shared_ptr store); + + private: + NumPyRuntime* runtime_; + std::shared_ptr store_; +}; + +std::shared_ptr array(std::vector shape, LegateTypeCode type); + +std::shared_ptr arange(int64_t stop); + +} // namespace numpy +} // namespace legate diff --git a/src/numpy.mk b/src/numpy.mk index 07520ee77e..7f852e03a4 100644 --- a/src/numpy.mk +++ b/src/numpy.mk @@ -39,6 +39,7 @@ GEN_CPU_SRC += ternary/where.cc \ stat/bincount.cc \ arg.cc \ mapper.cc \ + runtime.cc \ numpy.cc # This must always be the last file! # It guarantees we do our registration callback # only after all task variants are recorded @@ -88,3 +89,5 @@ GEN_GPU_SRC += ternary/where.cu \ search/nonzero.cu \ stat/bincount.cu \ numpy.cu + +INSTALL_HEADERS = numpy.h diff --git a/src/runtime.cc b/src/runtime.cc new file mode 100644 index 0000000000..453057a5b5 --- /dev/null +++ b/src/runtime.cc @@ -0,0 +1,68 @@ +/* Copyright 2021 NVIDIA Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +#include "numpy.h" + +#include "legate_numpy_c.h" + +namespace legate { +namespace numpy { + +/*static*/ NumPyRuntime* NumPyRuntime::runtime_; + +extern void bootstrapping_callback(Legion::Machine machine, + Legion::Runtime* runtime, + const std::set& local_procs); + +void initialize(int32_t argc, char** argv) +{ + Legion::Runtime::perform_registration_callback(bootstrapping_callback, true /*global*/); +} + +NumPyRuntime::NumPyRuntime(Runtime* legate_runtime, LibraryContext* context) + : legate_runtime_(legate_runtime), context_(context) +{ +} + +std::shared_ptr NumPyRuntime::create_array(std::vector shape, + LegateTypeCode type) +{ + // auto store = legate_runtime_->create_store(shape, type); + auto array = new NumPyArray(this, nullptr); + return std::shared_ptr(array); +} + +/*static*/ NumPyRuntime* NumPyRuntime::get_runtime() { return runtime_; } + +/*static*/ void NumPyRuntime::initialize(Runtime* legate_runtime, LibraryContext* context) +{ + runtime_ = new NumPyRuntime(legate_runtime, context); +} + +NumPyArray::NumPyArray(NumPyRuntime* runtime, std::shared_ptr store) + : runtime_(runtime), store_(store) +{ +} + +std::shared_ptr array(std::vector shape, LegateTypeCode type) +{ + return NumPyRuntime::get_runtime()->create_array(std::move(shape), type); +} + +std::shared_ptr arange(int64_t stop) { return nullptr; } + +} // namespace numpy +} // namespace legate From 6ab41b3b188c56b0f72e20c25a1983db4e41c54b Mon Sep 17 00:00:00 2001 From: Wonchan Lee Date: Mon, 27 Sep 2021 20:49:08 -0700 Subject: [PATCH 003/462] Make the code ready to implement the first NumPy call in C++ --- src/numpy.h | 16 ++++++++++++---- src/runtime.cc | 34 +++++++++++++++++++++++++++++----- 2 files changed, 41 insertions(+), 9 deletions(-) diff --git a/src/numpy.h b/src/numpy.h index b3bd0eca73..4cf591683d 100644 --- a/src/numpy.h +++ b/src/numpy.h @@ -22,8 +22,6 @@ namespace legate { -using LogicalStore = void; - namespace numpy { // TODO: The following functions and classes will later be reorganized into separate header files @@ -39,6 +37,9 @@ class NumPyRuntime { public: std::shared_ptr create_array(std::vector shape, LegateTypeCode type); + public: + uint32_t get_next_random_epoch(); + public: static NumPyRuntime* get_runtime(); static void initialize(Runtime* legate_runtime, LibraryContext* context); @@ -49,22 +50,29 @@ class NumPyRuntime { private: Runtime* legate_runtime_; LibraryContext* context_; + uint32_t next_epoch_{0}; }; class NumPyArray { friend class NumPyRuntime; private: - NumPyArray(NumPyRuntime* runtime, std::shared_ptr store); + NumPyArray(NumPyRuntime* runtime, + std::vector shape, + std::shared_ptr store); + + public: + void random(int32_t gen_code); private: NumPyRuntime* runtime_; + std::vector shape_; std::shared_ptr store_; }; std::shared_ptr array(std::vector shape, LegateTypeCode type); -std::shared_ptr arange(int64_t stop); +std::shared_ptr random(std::vector shape); } // namespace numpy } // namespace legate diff --git a/src/runtime.cc b/src/runtime.cc index 453057a5b5..fe90d53966 100644 --- a/src/runtime.cc +++ b/src/runtime.cc @@ -17,6 +17,7 @@ #include "numpy.h" #include "legate_numpy_c.h" +#include "random/rand_util.h" namespace legate { namespace numpy { @@ -40,11 +41,14 @@ NumPyRuntime::NumPyRuntime(Runtime* legate_runtime, LibraryContext* context) std::shared_ptr NumPyRuntime::create_array(std::vector shape, LegateTypeCode type) { - // auto store = legate_runtime_->create_store(shape, type); - auto array = new NumPyArray(this, nullptr); + // TODO: We need a type system for NumPy and should not use the core types + auto store = legate_runtime_->create_store(shape, type); + auto array = new NumPyArray(this, std::move(shape), std::move(store)); return std::shared_ptr(array); } +uint32_t NumPyRuntime::get_next_random_epoch() { return next_epoch_++; } + /*static*/ NumPyRuntime* NumPyRuntime::get_runtime() { return runtime_; } /*static*/ void NumPyRuntime::initialize(Runtime* legate_runtime, LibraryContext* context) @@ -52,9 +56,23 @@ std::shared_ptr NumPyRuntime::create_array(std::vector shap runtime_ = new NumPyRuntime(legate_runtime, context); } -NumPyArray::NumPyArray(NumPyRuntime* runtime, std::shared_ptr store) - : runtime_(runtime), store_(store) +NumPyArray::NumPyArray(NumPyRuntime* runtime, + std::vector shape, + std::shared_ptr store) + : runtime_(runtime), shape_(std::move(shape)), store_(store) +{ +} + +void NumPyArray::random(int32_t gen_code) { + // auto task = runtime_->create_task(NumPyOpCode::NUMPY_RAND); + // task->add_output(out); + // task->add_scalar_arg(Scalar(static_cast(RandGenCode::UNIFORM))); + // task->add_scalar_arg(Scalar(runtime_->get_next_random_epoch())); + // auto strides = compute_strides(); + // task.add_scalar_arg(Scalar(static_cast(strides.size())); + // for (auto stride : strides) task.add_scalar_arg(Scalar(stride)); + // runtime_->submit(std::move(task)); } std::shared_ptr array(std::vector shape, LegateTypeCode type) @@ -62,7 +80,13 @@ std::shared_ptr array(std::vector shape, LegateTypeCode typ return NumPyRuntime::get_runtime()->create_array(std::move(shape), type); } -std::shared_ptr arange(int64_t stop) { return nullptr; } +std::shared_ptr random(std::vector shape) +{ + auto runtime = NumPyRuntime::get_runtime(); + auto out = runtime->create_array(std::move(shape), LegateTypeCode::DOUBLE_LT); + out->random(static_cast(RandGenCode::UNIFORM)); + return out; +} } // namespace numpy } // namespace legate From 0abfb5e2b197147b8cbdfeea9d353ea970bbc4a4 Mon Sep 17 00:00:00 2001 From: Wonchan Lee Date: Wed, 29 Sep 2021 00:54:05 -0700 Subject: [PATCH 004/462] Resolve naming collisions --- src/mapper.cc | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/mapper.cc b/src/mapper.cc index e3691e8828..3e1fa47204 100644 --- a/src/mapper.cc +++ b/src/mapper.cc @@ -34,7 +34,8 @@ NumPyMapper::NumPyMapper(MapperRuntime* rt, Machine m, const LibraryContext& ctx { } -TaskTarget NumPyMapper::task_target(const Task& task, const std::vector& options) +TaskTarget NumPyMapper::task_target(const mapping::Task& task, + const std::vector& options) { return *options.begin(); } From fd4d128de87c7215e44283f2c7456efa638d47d4 Mon Sep 17 00:00:00 2001 From: Wonchan Lee Date: Wed, 29 Sep 2021 00:54:34 -0700 Subject: [PATCH 005/462] Very first operator implementation using the C++ launch interface --- src/numpy.h | 6 ++++++ src/numpy.mk | 3 ++- src/runtime.cc | 43 ++++++++++++++++++++++++++++++++++--------- 3 files changed, 42 insertions(+), 10 deletions(-) diff --git a/src/numpy.h b/src/numpy.h index 4cf591683d..a91e1858b6 100644 --- a/src/numpy.h +++ b/src/numpy.h @@ -20,6 +20,8 @@ #include "legate.h" +#include "legate_numpy_c.h" + namespace legate { namespace numpy { @@ -37,6 +39,10 @@ class NumPyRuntime { public: std::shared_ptr create_array(std::vector shape, LegateTypeCode type); + public: + std::unique_ptr create_task(NumPyOpCode op_code); + void submit(std::unique_ptr task); + public: uint32_t get_next_random_epoch(); diff --git a/src/numpy.mk b/src/numpy.mk index 7f852e03a4..1848405834 100644 --- a/src/numpy.mk +++ b/src/numpy.mk @@ -90,4 +90,5 @@ GEN_GPU_SRC += ternary/where.cu \ stat/bincount.cu \ numpy.cu -INSTALL_HEADERS = numpy.h +INSTALL_HEADERS = legate_numpy_c.h \ + numpy.h diff --git a/src/runtime.cc b/src/runtime.cc index fe90d53966..27063616ec 100644 --- a/src/runtime.cc +++ b/src/runtime.cc @@ -16,7 +16,6 @@ #include "numpy.h" -#include "legate_numpy_c.h" #include "random/rand_util.h" namespace legate { @@ -47,6 +46,13 @@ std::shared_ptr NumPyRuntime::create_array(std::vector shap return std::shared_ptr(array); } +std::unique_ptr NumPyRuntime::create_task(NumPyOpCode op_code) +{ + return legate_runtime_->create_task(context_, op_code); +} + +void NumPyRuntime::submit(std::unique_ptr task) { legate_runtime_->submit(std::move(task)); } + uint32_t NumPyRuntime::get_next_random_epoch() { return next_epoch_++; } /*static*/ NumPyRuntime* NumPyRuntime::get_runtime() { return runtime_; } @@ -63,16 +69,35 @@ NumPyArray::NumPyArray(NumPyRuntime* runtime, { } +static std::vector compute_strides(const std::vector& shape) +{ + std::vector strides(shape.size()); + if (shape.size() > 0) { + int64_t stride = 1; + for (int32_t dim = shape.size() - 1; dim >= 0; --dim) { + strides[dim] = stride; + stride *= shape[dim]; + } + } + return std::move(strides); +} + void NumPyArray::random(int32_t gen_code) { - // auto task = runtime_->create_task(NumPyOpCode::NUMPY_RAND); - // task->add_output(out); - // task->add_scalar_arg(Scalar(static_cast(RandGenCode::UNIFORM))); - // task->add_scalar_arg(Scalar(runtime_->get_next_random_epoch())); - // auto strides = compute_strides(); - // task.add_scalar_arg(Scalar(static_cast(strides.size())); - // for (auto stride : strides) task.add_scalar_arg(Scalar(stride)); - // runtime_->submit(std::move(task)); + auto task = runtime_->create_task(NumPyOpCode::NUMPY_RAND); + + task->add_output(store_); + task->add_scalar_arg(Scalar(static_cast(RandGenCode::UNIFORM))); + task->add_scalar_arg(Scalar(runtime_->get_next_random_epoch())); + auto strides = compute_strides(shape_); + void* buffer = malloc(strides.size() * sizeof(int64_t) + sizeof(uint32_t)); + *static_cast(buffer) = strides.size(); + memcpy(static_cast(buffer) + sizeof(uint32_t), + strides.data(), + strides.size() * sizeof(int64_t)); + task->add_scalar_arg(Scalar(true, LegateTypeCode::INT64_LT, buffer)); + + runtime_->submit(std::move(task)); } std::shared_ptr array(std::vector shape, LegateTypeCode type) From 97dd058146d64446b0c0a378aeccc7675b3fed48 Mon Sep 17 00:00:00 2001 From: Wonchan Lee Date: Wed, 29 Sep 2021 20:20:02 -0700 Subject: [PATCH 006/462] Stop using the alias to Store --- src/numpy/binary/binary_op.h | 6 +++--- src/numpy/binary/binary_red.h | 6 +++--- src/numpy/item/read_template.inl | 2 +- src/numpy/item/write_template.inl | 2 +- src/numpy/matrix/diag.h | 4 ++-- src/numpy/matrix/dot.h | 6 +++--- src/numpy/matrix/matmul.h | 6 +++--- src/numpy/matrix/matvecmul.h | 6 +++--- src/numpy/matrix/tile.h | 4 ++-- src/numpy/matrix/transpose.h | 4 ++-- src/numpy/nullary/arange.h | 8 ++++---- src/numpy/nullary/eye.h | 2 +- src/numpy/nullary/fill.h | 4 ++-- src/numpy/numpy_task.h | 2 -- src/numpy/random/rand.h | 2 +- src/numpy/search/nonzero.h | 4 ++-- src/numpy/stat/bincount.h | 6 +++--- src/numpy/ternary/where.h | 8 ++++---- src/numpy/unary/convert.h | 4 ++-- src/numpy/unary/scalar_unary_red.h | 4 ++-- src/numpy/unary/unary_op.h | 4 ++-- src/numpy/unary/unary_red.h | 4 ++-- 22 files changed, 48 insertions(+), 50 deletions(-) diff --git a/src/numpy/binary/binary_op.h b/src/numpy/binary/binary_op.h index 08112550da..9546dfc4e9 100644 --- a/src/numpy/binary/binary_op.h +++ b/src/numpy/binary/binary_op.h @@ -23,9 +23,9 @@ namespace legate { namespace numpy { struct BinaryOpArgs { - const Array& in1; - const Array& in2; - const Array& out; + const Store& in1; + const Store& in2; + const Store& out; BinaryOpCode op_code; std::vector args; }; diff --git a/src/numpy/binary/binary_red.h b/src/numpy/binary/binary_red.h index ade687a9a2..534750b83b 100644 --- a/src/numpy/binary/binary_red.h +++ b/src/numpy/binary/binary_red.h @@ -23,9 +23,9 @@ namespace legate { namespace numpy { struct BinaryRedArgs { - const Array& out; - const Array& in1; - const Array& in2; + const Store& out; + const Store& in1; + const Store& in2; BinaryOpCode op_code; std::vector args; }; diff --git a/src/numpy/item/read_template.inl b/src/numpy/item/read_template.inl index 7e52a62135..65c545ca79 100644 --- a/src/numpy/item/read_template.inl +++ b/src/numpy/item/read_template.inl @@ -25,7 +25,7 @@ struct ReadImplBody; template struct ReadImpl { template - void operator()(const Array& out_arr, const Array& in_arr) const + void operator()(const Store& out_arr, const Store& in_arr) const { using VAL = legate_type_of; auto out = out_arr.write_accessor(); diff --git a/src/numpy/item/write_template.inl b/src/numpy/item/write_template.inl index 2303c367c6..7e3ff36090 100644 --- a/src/numpy/item/write_template.inl +++ b/src/numpy/item/write_template.inl @@ -25,7 +25,7 @@ struct WriteImplBody; template struct WriteImpl { template - void operator()(Array& out_arr, Array& in_arr) const + void operator()(Store& out_arr, Store& in_arr) const { using VAL = legate_type_of; auto out = out_arr.write_accessor(); diff --git a/src/numpy/matrix/diag.h b/src/numpy/matrix/diag.h index 9c74d5359d..9ab519278c 100644 --- a/src/numpy/matrix/diag.h +++ b/src/numpy/matrix/diag.h @@ -23,8 +23,8 @@ namespace numpy { struct DiagArgs { bool extract; - const Array& matrix; - const Array& diag; + const Store& matrix; + const Store& diag; }; class DiagTask : public NumPyTask { diff --git a/src/numpy/matrix/dot.h b/src/numpy/matrix/dot.h index fa0589c0e6..8626a63b8b 100644 --- a/src/numpy/matrix/dot.h +++ b/src/numpy/matrix/dot.h @@ -22,9 +22,9 @@ namespace legate { namespace numpy { struct DotArgs { - const Array& lhs; - const Array& rhs1; - const Array& rhs2; + const Store& lhs; + const Store& rhs1; + const Store& rhs2; }; class DotTask : public NumPyTask { diff --git a/src/numpy/matrix/matmul.h b/src/numpy/matrix/matmul.h index 4dfb7bd86c..a66a179a5e 100644 --- a/src/numpy/matrix/matmul.h +++ b/src/numpy/matrix/matmul.h @@ -22,9 +22,9 @@ namespace legate { namespace numpy { struct MatMulArgs { - const Array& lhs; - const Array& rhs1; - const Array& rhs2; + const Store& lhs; + const Store& rhs1; + const Store& rhs2; }; class MatMulTask : public NumPyTask { diff --git a/src/numpy/matrix/matvecmul.h b/src/numpy/matrix/matvecmul.h index bf98e1a49f..cbb036e661 100644 --- a/src/numpy/matrix/matvecmul.h +++ b/src/numpy/matrix/matvecmul.h @@ -23,9 +23,9 @@ namespace numpy { struct MatVecMulArgs { bool left_matrix; - const Array& lhs; - const Array& rhs1; - const Array& rhs2; + const Store& lhs; + const Store& rhs1; + const Store& rhs2; }; class MatVecMulTask : public NumPyTask { diff --git a/src/numpy/matrix/tile.h b/src/numpy/matrix/tile.h index 308605cea1..a617d6fe0e 100644 --- a/src/numpy/matrix/tile.h +++ b/src/numpy/matrix/tile.h @@ -22,8 +22,8 @@ namespace legate { namespace numpy { struct TileArgs { - const Array& in; - const Array& out; + const Store& in; + const Store& out; }; class TileTask : public NumPyTask { diff --git a/src/numpy/matrix/transpose.h b/src/numpy/matrix/transpose.h index 1b00e64fa9..f54abc33ed 100644 --- a/src/numpy/matrix/transpose.h +++ b/src/numpy/matrix/transpose.h @@ -22,8 +22,8 @@ namespace legate { namespace numpy { struct TransposeArgs { - const Array& out; - const Array& in; + const Store& out; + const Store& in; }; class TransposeTask : public NumPyTask { diff --git a/src/numpy/nullary/arange.h b/src/numpy/nullary/arange.h index 10a9d16a3d..e54c51b475 100644 --- a/src/numpy/nullary/arange.h +++ b/src/numpy/nullary/arange.h @@ -22,10 +22,10 @@ namespace legate { namespace numpy { struct ArangeArgs { - const Array& out; - const Array& start; - const Array& stop; - const Array& step; + const Store& out; + const Store& start; + const Store& stop; + const Store& step; }; class ArangeTask : public NumPyTask { diff --git a/src/numpy/nullary/eye.h b/src/numpy/nullary/eye.h index fda5a1aa0f..89f6ee88b7 100644 --- a/src/numpy/nullary/eye.h +++ b/src/numpy/nullary/eye.h @@ -22,7 +22,7 @@ namespace legate { namespace numpy { struct EyeArgs { - const Array& out; + const Store& out; int32_t k; }; diff --git a/src/numpy/nullary/fill.h b/src/numpy/nullary/fill.h index a555db7c84..d3947ca108 100644 --- a/src/numpy/nullary/fill.h +++ b/src/numpy/nullary/fill.h @@ -22,8 +22,8 @@ namespace legate { namespace numpy { struct FillArgs { - const Array& out; - const Array& fill_value; + const Store& out; + const Store& fill_value; bool is_argval; }; diff --git a/src/numpy/numpy_task.h b/src/numpy/numpy_task.h index 5dff1966f4..6026293933 100644 --- a/src/numpy/numpy_task.h +++ b/src/numpy/numpy_task.h @@ -22,8 +22,6 @@ namespace legate { namespace numpy { -using Array = Store; - enum class VariantKind : int { CPU = 0, OMP = 1, diff --git a/src/numpy/random/rand.h b/src/numpy/random/rand.h index e97fe2e230..d34c2db2d4 100644 --- a/src/numpy/random/rand.h +++ b/src/numpy/random/rand.h @@ -23,7 +23,7 @@ namespace legate { namespace numpy { struct RandArgs { - const Array& out; + const Store& out; RandGenCode gen_code; uint32_t epoch; Legion::DomainPoint strides; diff --git a/src/numpy/search/nonzero.h b/src/numpy/search/nonzero.h index a22bcf398a..8b9007a702 100644 --- a/src/numpy/search/nonzero.h +++ b/src/numpy/search/nonzero.h @@ -22,8 +22,8 @@ namespace legate { namespace numpy { struct NonzeroArgs { - const Array& input; - std::vector& results; + const Store& input; + std::vector& results; }; class NonzeroTask : public NumPyTask { diff --git a/src/numpy/stat/bincount.h b/src/numpy/stat/bincount.h index bcae0aac05..b11d3e9aaa 100644 --- a/src/numpy/stat/bincount.h +++ b/src/numpy/stat/bincount.h @@ -22,9 +22,9 @@ namespace legate { namespace numpy { struct BincountArgs { - const Array& lhs; - const Array& rhs; - const Array& weights; + const Store& lhs; + const Store& rhs; + const Store& weights; }; class BincountTask : public NumPyTask { diff --git a/src/numpy/ternary/where.h b/src/numpy/ternary/where.h index 5030131ec9..c73214333c 100644 --- a/src/numpy/ternary/where.h +++ b/src/numpy/ternary/where.h @@ -22,10 +22,10 @@ namespace legate { namespace numpy { struct WhereArgs { - const Array& out; - const Array& mask; - const Array& in1; - const Array& in2; + const Store& out; + const Store& mask; + const Store& in1; + const Store& in2; }; class WhereTask : public NumPyTask { diff --git a/src/numpy/unary/convert.h b/src/numpy/unary/convert.h index 94f5db4cdf..7f073af66c 100644 --- a/src/numpy/unary/convert.h +++ b/src/numpy/unary/convert.h @@ -22,8 +22,8 @@ namespace legate { namespace numpy { struct ConvertArgs { - const Array& out; - const Array& in; + const Store& out; + const Store& in; }; class ConvertTask : public NumPyTask { diff --git a/src/numpy/unary/scalar_unary_red.h b/src/numpy/unary/scalar_unary_red.h index 53922ba7b4..25a3371ee9 100644 --- a/src/numpy/unary/scalar_unary_red.h +++ b/src/numpy/unary/scalar_unary_red.h @@ -23,8 +23,8 @@ namespace legate { namespace numpy { struct ScalarUnaryRedArgs { - const Array& out; - const Array& in; + const Store& out; + const Store& in; UnaryRedCode op_code; std::vector args; }; diff --git a/src/numpy/unary/unary_op.h b/src/numpy/unary/unary_op.h index 32366d020c..8266bf74d9 100644 --- a/src/numpy/unary/unary_op.h +++ b/src/numpy/unary/unary_op.h @@ -23,8 +23,8 @@ namespace legate { namespace numpy { struct UnaryOpArgs { - const Array& in; - const Array& out; + const Store& in; + const Store& out; UnaryOpCode op_code; std::vector args; }; diff --git a/src/numpy/unary/unary_red.h b/src/numpy/unary/unary_red.h index 21b3d63123..5f3fc25c46 100644 --- a/src/numpy/unary/unary_red.h +++ b/src/numpy/unary/unary_red.h @@ -23,8 +23,8 @@ namespace legate { namespace numpy { struct UnaryRedArgs { - const Array& lhs; - const Array& rhs; + const Store& lhs; + const Store& rhs; int32_t collapsed_dim; UnaryRedCode op_code; }; From c83d68f324eac8d51f47d5e684cb2f112982ac4a Mon Sep 17 00:00:00 2001 From: Wonchan Lee Date: Wed, 29 Sep 2021 20:32:18 -0700 Subject: [PATCH 007/462] Split the top-level header into separate component-wise headers --- src/numpy.h | 71 ++---------------------------------------- src/numpy.mk | 5 +++ src/numpy/array.cc | 63 +++++++++++++++++++++++++++++++++++++ src/numpy/array.h | 46 +++++++++++++++++++++++++++ src/numpy/operators.cc | 39 +++++++++++++++++++++++ src/numpy/operators.h | 33 ++++++++++++++++++++ src/numpy/runtime.cc | 53 ------------------------------- src/numpy/runtime.h | 60 +++++++++++++++++++++++++++++++++++ 8 files changed, 249 insertions(+), 121 deletions(-) create mode 100644 src/numpy/array.cc create mode 100644 src/numpy/array.h create mode 100644 src/numpy/operators.cc create mode 100644 src/numpy/operators.h create mode 100644 src/numpy/runtime.h diff --git a/src/numpy.h b/src/numpy.h index c85516f03c..8d3f74d4b0 100644 --- a/src/numpy.h +++ b/src/numpy.h @@ -14,71 +14,6 @@ * */ -#pragma once - -#include - -#include "legate.h" - -#include "numpy/legate_numpy_c.h" - -namespace legate { - -namespace numpy { - -// TODO: The following functions and classes will later be reorganized into separate header files - -void initialize(int32_t argc, char** argv); - -class NumPyArray; - -class NumPyRuntime { - private: - NumPyRuntime(Runtime* legate_runtime, LibraryContext* context); - - public: - std::shared_ptr create_array(std::vector shape, LegateTypeCode type); - - public: - std::unique_ptr create_task(NumPyOpCode op_code); - void submit(std::unique_ptr task); - - public: - uint32_t get_next_random_epoch(); - - public: - static NumPyRuntime* get_runtime(); - static void initialize(Runtime* legate_runtime, LibraryContext* context); - - private: - static NumPyRuntime* runtime_; - - private: - Runtime* legate_runtime_; - LibraryContext* context_; - uint32_t next_epoch_{0}; -}; - -class NumPyArray { - friend class NumPyRuntime; - - private: - NumPyArray(NumPyRuntime* runtime, - std::vector shape, - std::shared_ptr store); - - public: - void random(int32_t gen_code); - - private: - NumPyRuntime* runtime_; - std::vector shape_; - std::shared_ptr store_; -}; - -std::shared_ptr array(std::vector shape, LegateTypeCode type); - -std::shared_ptr random(std::vector shape); - -} // namespace numpy -} // namespace legate +#include "numpy/array.h" +#include "numpy/runtime.h" +#include "numpy/operators.h" diff --git a/src/numpy.mk b/src/numpy.mk index 7ed54274ec..b062ae0e30 100644 --- a/src/numpy.mk +++ b/src/numpy.mk @@ -38,7 +38,9 @@ GEN_CPU_SRC += numpy/ternary/where.cc \ numpy/search/nonzero.cc \ numpy/stat/bincount.cc \ numpy/arg.cc \ + numpy/array.cc \ numpy/mapper.cc \ + numpy/operators.cc \ numpy/runtime.cc \ numpy/numpy.cc # This must always be the last file! # It guarantees we do our registration callback @@ -93,4 +95,7 @@ GEN_GPU_SRC += numpy/ternary/where.cu \ INSTALL_PATHS = numpy INSTALL_HEADERS = numpy/legate_numpy_c.h \ + numpy/array.h \ + numpy/operators.h \ + numpy/runtime.h \ numpy.h diff --git a/src/numpy/array.cc b/src/numpy/array.cc new file mode 100644 index 0000000000..0da8085c29 --- /dev/null +++ b/src/numpy/array.cc @@ -0,0 +1,63 @@ +/* Copyright 2021 NVIDIA Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +#include "numpy/array.h" +#include "numpy/runtime.h" +#include "numpy/random/rand_util.h" + +namespace legate { +namespace numpy { + +NumPyArray::NumPyArray(NumPyRuntime* runtime, + std::vector shape, + std::shared_ptr store) + : runtime_(runtime), shape_(std::move(shape)), store_(store) +{ +} + +static std::vector compute_strides(const std::vector& shape) +{ + std::vector strides(shape.size()); + if (shape.size() > 0) { + int64_t stride = 1; + for (int32_t dim = shape.size() - 1; dim >= 0; --dim) { + strides[dim] = stride; + stride *= shape[dim]; + } + } + return std::move(strides); +} + +void NumPyArray::random(int32_t gen_code) +{ + auto task = runtime_->create_task(NumPyOpCode::NUMPY_RAND); + + task->add_output(store_); + task->add_scalar_arg(Scalar(static_cast(RandGenCode::UNIFORM))); + task->add_scalar_arg(Scalar(runtime_->get_next_random_epoch())); + auto strides = compute_strides(shape_); + void* buffer = malloc(strides.size() * sizeof(int64_t) + sizeof(uint32_t)); + *static_cast(buffer) = strides.size(); + memcpy(static_cast(buffer) + sizeof(uint32_t), + strides.data(), + strides.size() * sizeof(int64_t)); + task->add_scalar_arg(Scalar(true, LegateTypeCode::INT64_LT, buffer)); + + runtime_->submit(std::move(task)); +} + +} // namespace numpy +} // namespace legate diff --git a/src/numpy/array.h b/src/numpy/array.h new file mode 100644 index 0000000000..9922aa8085 --- /dev/null +++ b/src/numpy/array.h @@ -0,0 +1,46 @@ +/* Copyright 2021 NVIDIA Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +#pragma once + +#include + +#include "legate.h" + +namespace legate { +namespace numpy { + +class NumPyRuntime; + +class NumPyArray { + friend class NumPyRuntime; + + private: + NumPyArray(NumPyRuntime* runtime, + std::vector shape, + std::shared_ptr store); + + public: + void random(int32_t gen_code); + + private: + NumPyRuntime* runtime_; + std::vector shape_; + std::shared_ptr store_; +}; + +} // namespace numpy +} // namespace legate diff --git a/src/numpy/operators.cc b/src/numpy/operators.cc new file mode 100644 index 0000000000..98c6d4c1a0 --- /dev/null +++ b/src/numpy/operators.cc @@ -0,0 +1,39 @@ +/* Copyright 2021 NVIDIA Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +#include "numpy/operators.h" +#include "numpy/array.h" +#include "numpy/runtime.h" +#include "numpy/random/rand_util.h" + +namespace legate { +namespace numpy { + +std::shared_ptr array(std::vector shape, LegateTypeCode type) +{ + return NumPyRuntime::get_runtime()->create_array(std::move(shape), type); +} + +std::shared_ptr random(std::vector shape) +{ + auto runtime = NumPyRuntime::get_runtime(); + auto out = runtime->create_array(std::move(shape), LegateTypeCode::DOUBLE_LT); + out->random(static_cast(RandGenCode::UNIFORM)); + return out; +} + +} // namespace numpy +} // namespace legate diff --git a/src/numpy/operators.h b/src/numpy/operators.h new file mode 100644 index 0000000000..fb351d094f --- /dev/null +++ b/src/numpy/operators.h @@ -0,0 +1,33 @@ +/* Copyright 2021 NVIDIA Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +#pragma once + +#include + +#include "legate.h" + +namespace legate { +namespace numpy { + +class NumPyArray; + +std::shared_ptr array(std::vector shape, LegateTypeCode type); + +std::shared_ptr random(std::vector shape); + +} // namespace numpy +} // namespace legate diff --git a/src/numpy/runtime.cc b/src/numpy/runtime.cc index 11744813b3..c7b813a474 100644 --- a/src/numpy/runtime.cc +++ b/src/numpy/runtime.cc @@ -16,8 +16,6 @@ #include "numpy.h" -#include "numpy/random/rand_util.h" - namespace legate { namespace numpy { @@ -62,56 +60,5 @@ uint32_t NumPyRuntime::get_next_random_epoch() { return next_epoch_++; } runtime_ = new NumPyRuntime(legate_runtime, context); } -NumPyArray::NumPyArray(NumPyRuntime* runtime, - std::vector shape, - std::shared_ptr store) - : runtime_(runtime), shape_(std::move(shape)), store_(store) -{ -} - -static std::vector compute_strides(const std::vector& shape) -{ - std::vector strides(shape.size()); - if (shape.size() > 0) { - int64_t stride = 1; - for (int32_t dim = shape.size() - 1; dim >= 0; --dim) { - strides[dim] = stride; - stride *= shape[dim]; - } - } - return std::move(strides); -} - -void NumPyArray::random(int32_t gen_code) -{ - auto task = runtime_->create_task(NumPyOpCode::NUMPY_RAND); - - task->add_output(store_); - task->add_scalar_arg(Scalar(static_cast(RandGenCode::UNIFORM))); - task->add_scalar_arg(Scalar(runtime_->get_next_random_epoch())); - auto strides = compute_strides(shape_); - void* buffer = malloc(strides.size() * sizeof(int64_t) + sizeof(uint32_t)); - *static_cast(buffer) = strides.size(); - memcpy(static_cast(buffer) + sizeof(uint32_t), - strides.data(), - strides.size() * sizeof(int64_t)); - task->add_scalar_arg(Scalar(true, LegateTypeCode::INT64_LT, buffer)); - - runtime_->submit(std::move(task)); -} - -std::shared_ptr array(std::vector shape, LegateTypeCode type) -{ - return NumPyRuntime::get_runtime()->create_array(std::move(shape), type); -} - -std::shared_ptr random(std::vector shape) -{ - auto runtime = NumPyRuntime::get_runtime(); - auto out = runtime->create_array(std::move(shape), LegateTypeCode::DOUBLE_LT); - out->random(static_cast(RandGenCode::UNIFORM)); - return out; -} - } // namespace numpy } // namespace legate diff --git a/src/numpy/runtime.h b/src/numpy/runtime.h new file mode 100644 index 0000000000..95074ef5bc --- /dev/null +++ b/src/numpy/runtime.h @@ -0,0 +1,60 @@ +/* Copyright 2021 NVIDIA Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +#pragma once + +#include + +#include "legate.h" + +#include "numpy/legate_numpy_c.h" + +namespace legate { +namespace numpy { + +void initialize(int32_t argc, char** argv); + +class NumPyArray; + +class NumPyRuntime { + private: + NumPyRuntime(Runtime* legate_runtime, LibraryContext* context); + + public: + std::shared_ptr create_array(std::vector shape, LegateTypeCode type); + + public: + std::unique_ptr create_task(NumPyOpCode op_code); + void submit(std::unique_ptr task); + + public: + uint32_t get_next_random_epoch(); + + public: + static NumPyRuntime* get_runtime(); + static void initialize(Runtime* legate_runtime, LibraryContext* context); + + private: + static NumPyRuntime* runtime_; + + private: + Runtime* legate_runtime_; + LibraryContext* context_; + uint32_t next_epoch_{0}; +}; + +} // namespace numpy +} // namespace legate From 73ec07109b4662d62069d5d0a6a7125cb35ff3b3 Mon Sep 17 00:00:00 2001 From: Wonchan Lee Date: Wed, 29 Sep 2021 20:38:06 -0700 Subject: [PATCH 008/462] Rename NumPyArray to Array and stop exposing the runtime to the API --- src/numpy.h | 1 - src/numpy.mk | 1 - src/numpy/array.cc | 6 ++---- src/numpy/array.h | 6 ++---- src/numpy/numpy.cc | 2 +- src/numpy/operators.cc | 4 ++-- src/numpy/operators.h | 6 +++--- src/numpy/runtime.cc | 10 +++++----- src/numpy/runtime.h | 4 ++-- 9 files changed, 17 insertions(+), 23 deletions(-) diff --git a/src/numpy.h b/src/numpy.h index 8d3f74d4b0..f6b2f6ed8b 100644 --- a/src/numpy.h +++ b/src/numpy.h @@ -15,5 +15,4 @@ */ #include "numpy/array.h" -#include "numpy/runtime.h" #include "numpy/operators.h" diff --git a/src/numpy.mk b/src/numpy.mk index b062ae0e30..c171470fee 100644 --- a/src/numpy.mk +++ b/src/numpy.mk @@ -97,5 +97,4 @@ INSTALL_PATHS = numpy INSTALL_HEADERS = numpy/legate_numpy_c.h \ numpy/array.h \ numpy/operators.h \ - numpy/runtime.h \ numpy.h diff --git a/src/numpy/array.cc b/src/numpy/array.cc index 0da8085c29..92b599a89a 100644 --- a/src/numpy/array.cc +++ b/src/numpy/array.cc @@ -21,9 +21,7 @@ namespace legate { namespace numpy { -NumPyArray::NumPyArray(NumPyRuntime* runtime, - std::vector shape, - std::shared_ptr store) +Array::Array(NumPyRuntime* runtime, std::vector shape, std::shared_ptr store) : runtime_(runtime), shape_(std::move(shape)), store_(store) { } @@ -41,7 +39,7 @@ static std::vector compute_strides(const std::vector& shape) return std::move(strides); } -void NumPyArray::random(int32_t gen_code) +void Array::random(int32_t gen_code) { auto task = runtime_->create_task(NumPyOpCode::NUMPY_RAND); diff --git a/src/numpy/array.h b/src/numpy/array.h index 9922aa8085..155395b678 100644 --- a/src/numpy/array.h +++ b/src/numpy/array.h @@ -25,13 +25,11 @@ namespace numpy { class NumPyRuntime; -class NumPyArray { +class Array { friend class NumPyRuntime; private: - NumPyArray(NumPyRuntime* runtime, - std::vector shape, - std::shared_ptr store); + Array(NumPyRuntime* runtime, std::vector shape, std::shared_ptr store); public: void random(int32_t gen_code); diff --git a/src/numpy/numpy.cc b/src/numpy/numpy.cc index 4f4c02b8f9..aca076a32b 100644 --- a/src/numpy/numpy.cc +++ b/src/numpy/numpy.cc @@ -14,7 +14,7 @@ * */ -#include "numpy.h" +#include "numpy/runtime.h" #include "numpy/numpy_task.h" #include "numpy/mapper.h" #include "numpy/unary/unary_red_util.h" diff --git a/src/numpy/operators.cc b/src/numpy/operators.cc index 98c6d4c1a0..b4b4e0fd76 100644 --- a/src/numpy/operators.cc +++ b/src/numpy/operators.cc @@ -22,12 +22,12 @@ namespace legate { namespace numpy { -std::shared_ptr array(std::vector shape, LegateTypeCode type) +std::shared_ptr array(std::vector shape, LegateTypeCode type) { return NumPyRuntime::get_runtime()->create_array(std::move(shape), type); } -std::shared_ptr random(std::vector shape) +std::shared_ptr random(std::vector shape) { auto runtime = NumPyRuntime::get_runtime(); auto out = runtime->create_array(std::move(shape), LegateTypeCode::DOUBLE_LT); diff --git a/src/numpy/operators.h b/src/numpy/operators.h index fb351d094f..91ba91dac5 100644 --- a/src/numpy/operators.h +++ b/src/numpy/operators.h @@ -23,11 +23,11 @@ namespace legate { namespace numpy { -class NumPyArray; +class Array; -std::shared_ptr array(std::vector shape, LegateTypeCode type); +std::shared_ptr array(std::vector shape, LegateTypeCode type); -std::shared_ptr random(std::vector shape); +std::shared_ptr random(std::vector shape); } // namespace numpy } // namespace legate diff --git a/src/numpy/runtime.cc b/src/numpy/runtime.cc index c7b813a474..e6f7f8a664 100644 --- a/src/numpy/runtime.cc +++ b/src/numpy/runtime.cc @@ -14,7 +14,8 @@ * */ -#include "numpy.h" +#include "numpy/runtime.h" +#include "numpy/array.h" namespace legate { namespace numpy { @@ -35,13 +36,12 @@ NumPyRuntime::NumPyRuntime(Runtime* legate_runtime, LibraryContext* context) { } -std::shared_ptr NumPyRuntime::create_array(std::vector shape, - LegateTypeCode type) +std::shared_ptr NumPyRuntime::create_array(std::vector shape, LegateTypeCode type) { // TODO: We need a type system for NumPy and should not use the core types auto store = legate_runtime_->create_store(shape, type); - auto array = new NumPyArray(this, std::move(shape), std::move(store)); - return std::shared_ptr(array); + auto array = new Array(this, std::move(shape), std::move(store)); + return std::shared_ptr(array); } std::unique_ptr NumPyRuntime::create_task(NumPyOpCode op_code) diff --git a/src/numpy/runtime.h b/src/numpy/runtime.h index 95074ef5bc..d1a8a88ba6 100644 --- a/src/numpy/runtime.h +++ b/src/numpy/runtime.h @@ -27,14 +27,14 @@ namespace numpy { void initialize(int32_t argc, char** argv); -class NumPyArray; +class Array; class NumPyRuntime { private: NumPyRuntime(Runtime* legate_runtime, LibraryContext* context); public: - std::shared_ptr create_array(std::vector shape, LegateTypeCode type); + std::shared_ptr create_array(std::vector shape, LegateTypeCode type); public: std::unique_ptr create_task(NumPyOpCode op_code); From a4a734291efd7fa21d6b31fcfaeb7fd9586c7154 Mon Sep 17 00:00:00 2001 From: Wonchan Lee Date: Wed, 29 Sep 2021 21:11:46 -0700 Subject: [PATCH 009/462] Interface to create accessors to numpy arrays --- src/numpy.mk | 1 + src/numpy/array.cc | 7 +++++-- src/numpy/array.h | 12 +++++++++++- src/numpy/operators.h | 2 ++ src/numpy/runtime.cc | 2 +- src/numpy/runtime.h | 2 -- 6 files changed, 20 insertions(+), 6 deletions(-) diff --git a/src/numpy.mk b/src/numpy.mk index c171470fee..6b9cf7c03c 100644 --- a/src/numpy.mk +++ b/src/numpy.mk @@ -96,5 +96,6 @@ INSTALL_PATHS = numpy INSTALL_HEADERS = numpy/legate_numpy_c.h \ numpy/array.h \ + numpy/array.inl \ numpy/operators.h \ numpy.h diff --git a/src/numpy/array.cc b/src/numpy/array.cc index 92b599a89a..d53241a3db 100644 --- a/src/numpy/array.cc +++ b/src/numpy/array.cc @@ -21,8 +21,11 @@ namespace legate { namespace numpy { -Array::Array(NumPyRuntime* runtime, std::vector shape, std::shared_ptr store) - : runtime_(runtime), shape_(std::move(shape)), store_(store) +Array::Array(NumPyRuntime* runtime, + LibraryContext* context, + std::vector shape, + std::shared_ptr store) + : runtime_(runtime), context_(context), shape_(std::move(shape)), store_(store) { } diff --git a/src/numpy/array.h b/src/numpy/array.h index 155395b678..b88da05e23 100644 --- a/src/numpy/array.h +++ b/src/numpy/array.h @@ -29,16 +29,26 @@ class Array { friend class NumPyRuntime; private: - Array(NumPyRuntime* runtime, std::vector shape, std::shared_ptr store); + Array(NumPyRuntime* runtime, + LibraryContext* context, + std::vector shape, + std::shared_ptr store); + + public: + template + AccessorRW get_accessor(); public: void random(int32_t gen_code); private: NumPyRuntime* runtime_; + LibraryContext* context_; std::vector shape_; std::shared_ptr store_; }; } // namespace numpy } // namespace legate + +#include "numpy/array.inl" diff --git a/src/numpy/operators.h b/src/numpy/operators.h index 91ba91dac5..3be6f5506f 100644 --- a/src/numpy/operators.h +++ b/src/numpy/operators.h @@ -25,6 +25,8 @@ namespace numpy { class Array; +void initialize(int32_t argc, char** argv); + std::shared_ptr array(std::vector shape, LegateTypeCode type); std::shared_ptr random(std::vector shape); diff --git a/src/numpy/runtime.cc b/src/numpy/runtime.cc index e6f7f8a664..559c3264d0 100644 --- a/src/numpy/runtime.cc +++ b/src/numpy/runtime.cc @@ -40,7 +40,7 @@ std::shared_ptr NumPyRuntime::create_array(std::vector shape, Le { // TODO: We need a type system for NumPy and should not use the core types auto store = legate_runtime_->create_store(shape, type); - auto array = new Array(this, std::move(shape), std::move(store)); + auto array = new Array(this, context_, std::move(shape), std::move(store)); return std::shared_ptr(array); } diff --git a/src/numpy/runtime.h b/src/numpy/runtime.h index d1a8a88ba6..52cd72f415 100644 --- a/src/numpy/runtime.h +++ b/src/numpy/runtime.h @@ -25,8 +25,6 @@ namespace legate { namespace numpy { -void initialize(int32_t argc, char** argv); - class Array; class NumPyRuntime { From 39f84893cd8e01d0376861097d73a4332c2e9e27 Mon Sep 17 00:00:00 2001 From: Wonchan Lee Date: Wed, 29 Sep 2021 21:28:57 -0700 Subject: [PATCH 010/462] Add more operators to make the interface more exciting --- src/numpy/array.cc | 29 +++++++++++++++++++++++++++++ src/numpy/array.h | 7 +++++++ src/numpy/operators.cc | 36 ++++++++++++++++++++++++++++++++++-- src/numpy/operators.h | 6 ++++++ 4 files changed, 76 insertions(+), 2 deletions(-) diff --git a/src/numpy/array.cc b/src/numpy/array.cc index d53241a3db..b75d2a9646 100644 --- a/src/numpy/array.cc +++ b/src/numpy/array.cc @@ -29,6 +29,12 @@ Array::Array(NumPyRuntime* runtime, { } +int32_t Array::dim() const { return static_cast(shape_.size()); } + +const std::vector& Array::shape() const { return shape_; } + +LegateTypeCode Array::code() const { return store_->code(); } + static std::vector compute_strides(const std::vector& shape) { std::vector strides(shape.size()); @@ -60,5 +66,28 @@ void Array::random(int32_t gen_code) runtime_->submit(std::move(task)); } +void Array::binary_op(int32_t op_code, std::shared_ptr rhs1, std::shared_ptr rhs2) +{ + auto task = runtime_->create_task(NumPyOpCode::NUMPY_BINARY_OP); + + task->add_output(store_); + task->add_input(rhs1->store_); + task->add_input(rhs2->store_); + task->add_scalar_arg(Scalar(op_code)); + + runtime_->submit(std::move(task)); +} + +void Array::unary_op(int32_t op_code, std::shared_ptr input) +{ + auto task = runtime_->create_task(NumPyOpCode::NUMPY_UNARY_OP); + + task->add_output(store_); + task->add_input(input->store_); + task->add_scalar_arg(Scalar(op_code)); + + runtime_->submit(std::move(task)); +} + } // namespace numpy } // namespace legate diff --git a/src/numpy/array.h b/src/numpy/array.h index b88da05e23..92cb95f804 100644 --- a/src/numpy/array.h +++ b/src/numpy/array.h @@ -34,12 +34,19 @@ class Array { std::vector shape, std::shared_ptr store); + public: + int32_t dim() const; + const std::vector& shape() const; + LegateTypeCode code() const; + public: template AccessorRW get_accessor(); public: void random(int32_t gen_code); + void binary_op(int32_t op_code, std::shared_ptr rhs1, std::shared_ptr rhs2); + void unary_op(int32_t op_code, std::shared_ptr input); private: NumPyRuntime* runtime_; diff --git a/src/numpy/operators.cc b/src/numpy/operators.cc index b4b4e0fd76..5dbbba58c1 100644 --- a/src/numpy/operators.cc +++ b/src/numpy/operators.cc @@ -17,17 +17,49 @@ #include "numpy/operators.h" #include "numpy/array.h" #include "numpy/runtime.h" +#include "numpy/binary/binary_op_util.h" +#include "numpy/unary/unary_op_util.h" #include "numpy/random/rand_util.h" namespace legate { namespace numpy { -std::shared_ptr array(std::vector shape, LegateTypeCode type) +using ArrayP = std::shared_ptr; + +ArrayP array(std::vector shape, LegateTypeCode type) { return NumPyRuntime::get_runtime()->create_array(std::move(shape), type); } -std::shared_ptr random(std::vector shape) +ArrayP unary_op(UnaryOpCode op_code, ArrayP input) +{ + auto runtime = NumPyRuntime::get_runtime(); + auto out = runtime->create_array(input->shape(), input->code()); + out->unary_op(static_cast(op_code), std::move(input)); + return std::move(out); +} + +ArrayP binary_op(BinaryOpCode op_code, ArrayP rhs1, ArrayP rhs2) +{ + assert(rhs1->shape() == rhs2->shape()); + assert(rhs1->code() == rhs2->code()); + + auto runtime = NumPyRuntime::get_runtime(); + auto out = runtime->create_array(rhs1->shape(), rhs1->code()); + out->binary_op(static_cast(op_code), std::move(rhs1), std::move(rhs2)); + return std::move(out); +} + +ArrayP abs(ArrayP input) { return unary_op(UnaryOpCode::ABSOLUTE, std::move(input)); } + +ArrayP add(ArrayP rhs1, ArrayP rhs2) +{ + return binary_op(BinaryOpCode::ADD, std::move(rhs1), std::move(rhs2)); +} + +ArrayP negative(ArrayP input) { return unary_op(UnaryOpCode::NEGATIVE, std::move(input)); } + +ArrayP random(std::vector shape) { auto runtime = NumPyRuntime::get_runtime(); auto out = runtime->create_array(std::move(shape), LegateTypeCode::DOUBLE_LT); diff --git a/src/numpy/operators.h b/src/numpy/operators.h index 3be6f5506f..506a3f8a9b 100644 --- a/src/numpy/operators.h +++ b/src/numpy/operators.h @@ -29,6 +29,12 @@ void initialize(int32_t argc, char** argv); std::shared_ptr array(std::vector shape, LegateTypeCode type); +std::shared_ptr abs(std::shared_ptr input); + +std::shared_ptr add(std::shared_ptr rhs1, std::shared_ptr rhs2); + +std::shared_ptr negative(std::shared_ptr input); + std::shared_ptr random(std::vector shape); } // namespace numpy From f2d011beda0c7dd718e8728256958d310f5164dc Mon Sep 17 00:00:00 2001 From: Wonchan Lee Date: Fri, 1 Oct 2021 23:06:06 -0700 Subject: [PATCH 011/462] Change the shape to a vector of size_t and remove the shape cached in each array --- src/numpy/array.cc | 15 ++++++--------- src/numpy/array.h | 8 ++------ src/numpy/operators.cc | 4 ++-- src/numpy/operators.h | 4 ++-- src/numpy/runtime.cc | 4 ++-- src/numpy/runtime.h | 2 +- 6 files changed, 15 insertions(+), 22 deletions(-) diff --git a/src/numpy/array.cc b/src/numpy/array.cc index b75d2a9646..58748dcc31 100644 --- a/src/numpy/array.cc +++ b/src/numpy/array.cc @@ -21,21 +21,18 @@ namespace legate { namespace numpy { -Array::Array(NumPyRuntime* runtime, - LibraryContext* context, - std::vector shape, - std::shared_ptr store) - : runtime_(runtime), context_(context), shape_(std::move(shape)), store_(store) +Array::Array(NumPyRuntime* runtime, LibraryContext* context, std::shared_ptr store) + : runtime_(runtime), context_(context), store_(store) { } -int32_t Array::dim() const { return static_cast(shape_.size()); } +int32_t Array::dim() const { return store_->dim(); } -const std::vector& Array::shape() const { return shape_; } +const std::vector& Array::shape() const { return store_->extents(); } LegateTypeCode Array::code() const { return store_->code(); } -static std::vector compute_strides(const std::vector& shape) +static std::vector compute_strides(const std::vector& shape) { std::vector strides(shape.size()); if (shape.size() > 0) { @@ -55,7 +52,7 @@ void Array::random(int32_t gen_code) task->add_output(store_); task->add_scalar_arg(Scalar(static_cast(RandGenCode::UNIFORM))); task->add_scalar_arg(Scalar(runtime_->get_next_random_epoch())); - auto strides = compute_strides(shape_); + auto strides = compute_strides(shape()); void* buffer = malloc(strides.size() * sizeof(int64_t) + sizeof(uint32_t)); *static_cast(buffer) = strides.size(); memcpy(static_cast(buffer) + sizeof(uint32_t), diff --git a/src/numpy/array.h b/src/numpy/array.h index 92cb95f804..8ab67cc380 100644 --- a/src/numpy/array.h +++ b/src/numpy/array.h @@ -29,14 +29,11 @@ class Array { friend class NumPyRuntime; private: - Array(NumPyRuntime* runtime, - LibraryContext* context, - std::vector shape, - std::shared_ptr store); + Array(NumPyRuntime* runtime, LibraryContext* context, std::shared_ptr store); public: int32_t dim() const; - const std::vector& shape() const; + const std::vector& shape() const; LegateTypeCode code() const; public: @@ -51,7 +48,6 @@ class Array { private: NumPyRuntime* runtime_; LibraryContext* context_; - std::vector shape_; std::shared_ptr store_; }; diff --git a/src/numpy/operators.cc b/src/numpy/operators.cc index 5dbbba58c1..62faf9e5f1 100644 --- a/src/numpy/operators.cc +++ b/src/numpy/operators.cc @@ -26,7 +26,7 @@ namespace numpy { using ArrayP = std::shared_ptr; -ArrayP array(std::vector shape, LegateTypeCode type) +ArrayP array(std::vector shape, LegateTypeCode type) { return NumPyRuntime::get_runtime()->create_array(std::move(shape), type); } @@ -59,7 +59,7 @@ ArrayP add(ArrayP rhs1, ArrayP rhs2) ArrayP negative(ArrayP input) { return unary_op(UnaryOpCode::NEGATIVE, std::move(input)); } -ArrayP random(std::vector shape) +ArrayP random(std::vector shape) { auto runtime = NumPyRuntime::get_runtime(); auto out = runtime->create_array(std::move(shape), LegateTypeCode::DOUBLE_LT); diff --git a/src/numpy/operators.h b/src/numpy/operators.h index 506a3f8a9b..8985cddba9 100644 --- a/src/numpy/operators.h +++ b/src/numpy/operators.h @@ -27,7 +27,7 @@ class Array; void initialize(int32_t argc, char** argv); -std::shared_ptr array(std::vector shape, LegateTypeCode type); +std::shared_ptr array(std::vector shape, LegateTypeCode type); std::shared_ptr abs(std::shared_ptr input); @@ -35,7 +35,7 @@ std::shared_ptr add(std::shared_ptr rhs1, std::shared_ptr r std::shared_ptr negative(std::shared_ptr input); -std::shared_ptr random(std::vector shape); +std::shared_ptr random(std::vector shape); } // namespace numpy } // namespace legate diff --git a/src/numpy/runtime.cc b/src/numpy/runtime.cc index 559c3264d0..ea2494a2d3 100644 --- a/src/numpy/runtime.cc +++ b/src/numpy/runtime.cc @@ -36,11 +36,11 @@ NumPyRuntime::NumPyRuntime(Runtime* legate_runtime, LibraryContext* context) { } -std::shared_ptr NumPyRuntime::create_array(std::vector shape, LegateTypeCode type) +std::shared_ptr NumPyRuntime::create_array(std::vector shape, LegateTypeCode type) { // TODO: We need a type system for NumPy and should not use the core types auto store = legate_runtime_->create_store(shape, type); - auto array = new Array(this, context_, std::move(shape), std::move(store)); + auto array = new Array(this, context_, std::move(store)); return std::shared_ptr(array); } diff --git a/src/numpy/runtime.h b/src/numpy/runtime.h index 52cd72f415..c6e45063f9 100644 --- a/src/numpy/runtime.h +++ b/src/numpy/runtime.h @@ -32,7 +32,7 @@ class NumPyRuntime { NumPyRuntime(Runtime* legate_runtime, LibraryContext* context); public: - std::shared_ptr create_array(std::vector shape, LegateTypeCode type); + std::shared_ptr create_array(std::vector shape, LegateTypeCode type); public: std::unique_ptr create_task(NumPyOpCode op_code); From 69b7734c2c423f67a24a4f4b4ec50a56e40afbf1 Mon Sep 17 00:00:00 2001 From: Wonchan Lee Date: Fri, 8 Oct 2021 16:24:48 -0700 Subject: [PATCH 012/462] Missing file --- src/numpy/array.inl | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) create mode 100644 src/numpy/array.inl diff --git a/src/numpy/array.inl b/src/numpy/array.inl new file mode 100644 index 0000000000..1836f80b00 --- /dev/null +++ b/src/numpy/array.inl @@ -0,0 +1,29 @@ +/* Copyright 2021 NVIDIA Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +namespace legate { +namespace numpy { + +template +AccessorRW Array::get_accessor() +{ + auto mapped = store_->get_physical_store(context_); + auto shape = mapped->shape(); + return mapped->read_write_accessor(shape); +} + +} // namespace numpy +} // namespace legate From 0b85ede67b9e7a29852ca837742f8e850e03a8cc Mon Sep 17 00:00:00 2001 From: Wonchan Lee Date: Fri, 15 Oct 2021 13:46:00 -0700 Subject: [PATCH 013/462] Update task launch code to catch up with the new interface --- src/numpy/array.cc | 26 ++++++++++++++++++++------ 1 file changed, 20 insertions(+), 6 deletions(-) diff --git a/src/numpy/array.cc b/src/numpy/array.cc index 58748dcc31..1953544712 100644 --- a/src/numpy/array.cc +++ b/src/numpy/array.cc @@ -49,7 +49,9 @@ void Array::random(int32_t gen_code) { auto task = runtime_->create_task(NumPyOpCode::NUMPY_RAND); - task->add_output(store_); + auto p_lhs = task->declare_partition(store_); + + task->add_output(store_, p_lhs); task->add_scalar_arg(Scalar(static_cast(RandGenCode::UNIFORM))); task->add_scalar_arg(Scalar(runtime_->get_next_random_epoch())); auto strides = compute_strides(shape()); @@ -67,11 +69,18 @@ void Array::binary_op(int32_t op_code, std::shared_ptr rhs1, std::shared_ { auto task = runtime_->create_task(NumPyOpCode::NUMPY_BINARY_OP); - task->add_output(store_); - task->add_input(rhs1->store_); - task->add_input(rhs2->store_); + auto p_lhs = task->declare_partition(store_); + auto p_rhs1 = task->declare_partition(rhs1->store_); + auto p_rhs2 = task->declare_partition(rhs2->store_); + + task->add_output(store_, p_lhs); + task->add_input(rhs1->store_, p_rhs1); + task->add_input(rhs2->store_, p_rhs2); task->add_scalar_arg(Scalar(op_code)); + task->add_constraint(align(p_lhs, p_rhs1)); + task->add_constraint(align(p_rhs1, p_rhs2)); + runtime_->submit(std::move(task)); } @@ -79,10 +88,15 @@ void Array::unary_op(int32_t op_code, std::shared_ptr input) { auto task = runtime_->create_task(NumPyOpCode::NUMPY_UNARY_OP); - task->add_output(store_); - task->add_input(input->store_); + auto p_out = task->declare_partition(store_); + auto p_in = task->declare_partition(input->store_); + + task->add_output(store_, p_out); + task->add_input(input->store_, p_in); task->add_scalar_arg(Scalar(op_code)); + task->add_constraint(align(p_out, p_in)); + runtime_->submit(std::move(task)); } From 3c9698b52e53a6332f296caf53ca204c5a9c0503 Mon Sep 17 00:00:00 2001 From: Marcin Zalewski Date: Wed, 27 Oct 2021 14:19:33 -0700 Subject: [PATCH 014/462] Update the package version for the release --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 85cee9bced..6bdcd4b8db 100755 --- a/setup.py +++ b/setup.py @@ -74,7 +74,7 @@ def run(self): sys.argv.remove("--recurse") setup( name="cunumeric", - version="0.1", + version="21.10.00", packages=[ "cunumeric", "cunumeric.linalg", From 02d8ce6a249260a07d261e331f2dcabc2bfc3d82 Mon Sep 17 00:00:00 2001 From: Wonchan Lee Date: Wed, 27 Oct 2021 15:16:41 -0700 Subject: [PATCH 015/462] Fix #111 --- cunumeric/module.py | 2 +- tests/array_creation.py | 5 +++++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/cunumeric/module.py b/cunumeric/module.py index 9fa29f1c92..81cb025642 100644 --- a/cunumeric/module.py +++ b/cunumeric/module.py @@ -182,7 +182,7 @@ def array(obj, dtype=None, copy=True, order="K", subok=False, ndmin=0): ) array = ndarray(shape=None, stacklevel=2, thunk=thunk) else: - array = array + array = obj if dtype is not None and array.dtype != dtype: array = array.astype(dtype) elif copy and obj is array: diff --git a/tests/array_creation.py b/tests/array_creation.py index 6d0f310d14..cefd7c1925 100644 --- a/tests/array_creation.py +++ b/tests/array_creation.py @@ -25,6 +25,11 @@ def test(): assert np.array_equal(x, z) assert x.dtype == z.dtype + x = num.array([1, 2, 3]) + y = num.array(x) + assert num.array_equal(x, y) + assert x.dtype == y.dtype + xe = num.empty((2, 3)) ye = np.empty((2, 3)) assert xe.shape == ye.shape From b698b3387a095d235b94226b6a1fcd58f06ed6fa Mon Sep 17 00:00:00 2001 From: Marcin Zalewski Date: Thu, 28 Oct 2021 18:56:22 -0700 Subject: [PATCH 016/462] Decrease relative tolerance in allclose for float16 values --- cunumeric/module.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/cunumeric/module.py b/cunumeric/module.py index 81cb025642..d2c7500659 100644 --- a/cunumeric/module.py +++ b/cunumeric/module.py @@ -627,7 +627,11 @@ def logical_not(a, out=None, where=True, dtype=None, **kwargs): @copy_docstring(np.allclose) -def allclose(a, b, rtol=1e-5, atol=1e-8, equal_nan=False): +def allclose(a, b, rtol=None, atol=1e-8, equal_nan=False): + if rtol is None and a.dtype == np.float16: + rtol = 1e-3 + elif rtol is None: + rtol = 1e-5 a_array = ndarray.convert_to_cunumeric_ndarray(a) b_array = ndarray.convert_to_cunumeric_ndarray(b) if equal_nan: @@ -644,6 +648,7 @@ def allclose(a, b, rtol=1e-5, atol=1e-8, equal_nan=False): ) + @copy_docstring(np.array_equal) def array_equal(a, b): a_array = ndarray.convert_to_cunumeric_ndarray(a) From ac314deeed09981c6c8d9860d65d84afb5249e75 Mon Sep 17 00:00:00 2001 From: Marcin Zalewski Date: Thu, 28 Oct 2021 21:00:23 -0700 Subject: [PATCH 017/462] Revert "Decrease relative tolerance in allclose for float16 values" This reverts commit b698b3387a095d235b94226b6a1fcd58f06ed6fa. --- cunumeric/module.py | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/cunumeric/module.py b/cunumeric/module.py index d2c7500659..81cb025642 100644 --- a/cunumeric/module.py +++ b/cunumeric/module.py @@ -627,11 +627,7 @@ def logical_not(a, out=None, where=True, dtype=None, **kwargs): @copy_docstring(np.allclose) -def allclose(a, b, rtol=None, atol=1e-8, equal_nan=False): - if rtol is None and a.dtype == np.float16: - rtol = 1e-3 - elif rtol is None: - rtol = 1e-5 +def allclose(a, b, rtol=1e-5, atol=1e-8, equal_nan=False): a_array = ndarray.convert_to_cunumeric_ndarray(a) b_array = ndarray.convert_to_cunumeric_ndarray(b) if equal_nan: @@ -648,7 +644,6 @@ def allclose(a, b, rtol=None, atol=1e-8, equal_nan=False): ) - @copy_docstring(np.array_equal) def array_equal(a, b): a_array = ndarray.convert_to_cunumeric_ndarray(a) From 4167c7ac24a54f896975205dfdf93559f9359394 Mon Sep 17 00:00:00 2001 From: Marcin Zalewski Date: Thu, 28 Oct 2021 21:05:39 -0700 Subject: [PATCH 018/462] Allow greater margin of error for tensordot with float16 --- tests/tensordot.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/tests/tensordot.py b/tests/tensordot.py index bfebfb51b4..a98f15fe98 100644 --- a/tests/tensordot.py +++ b/tests/tensordot.py @@ -19,13 +19,14 @@ def test(ty): + rtol=2e-03 if ty == np.float16 else 1e-05 a = num.random.rand(3, 5, 4).astype(ty) b = num.random.rand(4, 5, 3).astype(ty) cn = np.tensordot(a, b, axes=1) c = num.tensordot(a, b, axes=1) - assert np.allclose(cn, c) + assert np.allclose(cn, c, rtol=rtol) a = num.random.rand(3, 5, 4).astype(ty) b = num.random.rand(5, 4, 3).astype(ty) @@ -33,7 +34,7 @@ def test(ty): cn = np.tensordot(a, b) c = num.tensordot(a, b) - assert np.allclose(cn, c) + assert np.allclose(cn, c, rtol=rtol) a = num.arange(60.0).reshape((3, 4, 5)).astype(ty) b = num.arange(24.0).reshape((4, 3, 2)).astype(ty) @@ -41,7 +42,7 @@ def test(ty): cn = np.tensordot(a, b, axes=([1, 0], [0, 1])) c = num.tensordot(a, b, axes=([1, 0], [0, 1])) - assert np.allclose(cn, c) + assert np.allclose(cn, c, rtol=rtol) a = num.random.rand(5, 4).astype(ty) b = num.random.rand(4, 5).astype(ty) @@ -49,7 +50,7 @@ def test(ty): cn = np.tensordot(a, b, axes=1) c = num.tensordot(a, b, axes=1) - assert np.allclose(cn, c) + assert np.allclose(cn, c, rtol=rtol) a = num.random.rand(5, 4).astype(ty) b = num.random.rand(5, 4).astype(ty) @@ -57,7 +58,7 @@ def test(ty): cn = np.tensordot(a, b) c = num.tensordot(a, b) - assert np.allclose(cn, c) + assert np.allclose(cn, c, rtol=rtol) if __name__ == "__main__": From 05901bdba027e9a76f221b652dd94b7e8b128ccd Mon Sep 17 00:00:00 2001 From: Wonchan Lee Date: Wed, 3 Nov 2021 02:01:52 -0700 Subject: [PATCH 019/462] Add a bunch of missing files --- src/cunumeric.h | 18 ++++++ src/cunumeric/array.cc | 104 +++++++++++++++++++++++++++++++++ src/cunumeric/array.h | 57 ++++++++++++++++++ src/cunumeric/array.inl | 27 +++++++++ src/cunumeric/cunumeric_task.h | 44 ++++++++++++++ src/cunumeric/operators.cc | 69 ++++++++++++++++++++++ src/cunumeric/operators.h | 39 +++++++++++++ src/cunumeric/runtime.cc | 67 +++++++++++++++++++++ src/cunumeric/runtime.h | 56 ++++++++++++++++++ 9 files changed, 481 insertions(+) create mode 100644 src/cunumeric.h create mode 100644 src/cunumeric/array.cc create mode 100644 src/cunumeric/array.h create mode 100644 src/cunumeric/array.inl create mode 100644 src/cunumeric/cunumeric_task.h create mode 100644 src/cunumeric/operators.cc create mode 100644 src/cunumeric/operators.h create mode 100644 src/cunumeric/runtime.cc create mode 100644 src/cunumeric/runtime.h diff --git a/src/cunumeric.h b/src/cunumeric.h new file mode 100644 index 0000000000..bde5bd6b8b --- /dev/null +++ b/src/cunumeric.h @@ -0,0 +1,18 @@ +/* Copyright 2021 NVIDIA Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +#include "cunumeric/array.h" +#include "cunumeric/operators.h" diff --git a/src/cunumeric/array.cc b/src/cunumeric/array.cc new file mode 100644 index 0000000000..7014c514df --- /dev/null +++ b/src/cunumeric/array.cc @@ -0,0 +1,104 @@ +/* Copyright 2021 NVIDIA Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +#include "cunumeric/array.h" +#include "cunumeric/runtime.h" +#include "cunumeric/random/rand_util.h" + +namespace cunumeric { + +Array::Array(CuNumericRuntime* runtime, + legate::LibraryContext* context, + std::shared_ptr store) + : runtime_(runtime), context_(context), store_(store) +{ +} + +int32_t Array::dim() const { return store_->dim(); } + +const std::vector& Array::shape() const { return store_->extents(); } + +legate::LegateTypeCode Array::code() const { return store_->code(); } + +static std::vector compute_strides(const std::vector& shape) +{ + std::vector strides(shape.size()); + if (shape.size() > 0) { + int64_t stride = 1; + for (int32_t dim = shape.size() - 1; dim >= 0; --dim) { + strides[dim] = stride; + stride *= shape[dim]; + } + } + return std::move(strides); +} + +void Array::random(int32_t gen_code) +{ + auto task = runtime_->create_task(CuNumericOpCode::CUNUMERIC_RAND); + + auto p_lhs = task->declare_partition(store_); + + task->add_output(store_, p_lhs); + task->add_scalar_arg(legate::Scalar(static_cast(RandGenCode::UNIFORM))); + task->add_scalar_arg(legate::Scalar(runtime_->get_next_random_epoch())); + auto strides = compute_strides(shape()); + void* buffer = malloc(strides.size() * sizeof(int64_t) + sizeof(uint32_t)); + *static_cast(buffer) = strides.size(); + memcpy(static_cast(buffer) + sizeof(uint32_t), + strides.data(), + strides.size() * sizeof(int64_t)); + task->add_scalar_arg(legate::Scalar(true, legate::LegateTypeCode::INT64_LT, buffer)); + + runtime_->submit(std::move(task)); +} + +void Array::binary_op(int32_t op_code, std::shared_ptr rhs1, std::shared_ptr rhs2) +{ + auto task = runtime_->create_task(CuNumericOpCode::CUNUMERIC_BINARY_OP); + + auto p_lhs = task->declare_partition(store_); + auto p_rhs1 = task->declare_partition(rhs1->store_); + auto p_rhs2 = task->declare_partition(rhs2->store_); + + task->add_output(store_, p_lhs); + task->add_input(rhs1->store_, p_rhs1); + task->add_input(rhs2->store_, p_rhs2); + task->add_scalar_arg(legate::Scalar(op_code)); + + task->add_constraint(align(p_lhs, p_rhs1)); + task->add_constraint(align(p_rhs1, p_rhs2)); + + runtime_->submit(std::move(task)); +} + +void Array::unary_op(int32_t op_code, std::shared_ptr input) +{ + auto task = runtime_->create_task(CuNumericOpCode::CUNUMERIC_UNARY_OP); + + auto p_out = task->declare_partition(store_); + auto p_in = task->declare_partition(input->store_); + + task->add_output(store_, p_out); + task->add_input(input->store_, p_in); + task->add_scalar_arg(legate::Scalar(op_code)); + + task->add_constraint(align(p_out, p_in)); + + runtime_->submit(std::move(task)); +} + +} // namespace cunumeric diff --git a/src/cunumeric/array.h b/src/cunumeric/array.h new file mode 100644 index 0000000000..2f9c6868ef --- /dev/null +++ b/src/cunumeric/array.h @@ -0,0 +1,57 @@ +/* Copyright 2021 NVIDIA Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +#pragma once + +#include + +#include "legate.h" + +namespace cunumeric { + +class CuNumericRuntime; + +class Array { + friend class CuNumericRuntime; + + private: + Array(CuNumericRuntime* runtime, + legate::LibraryContext* context, + std::shared_ptr store); + + public: + int32_t dim() const; + const std::vector& shape() const; + legate::LegateTypeCode code() const; + + public: + template + legate::AccessorRW get_accessor(); + + public: + void random(int32_t gen_code); + void binary_op(int32_t op_code, std::shared_ptr rhs1, std::shared_ptr rhs2); + void unary_op(int32_t op_code, std::shared_ptr input); + + private: + CuNumericRuntime* runtime_; + legate::LibraryContext* context_; + std::shared_ptr store_; +}; + +} // namespace cunumeric + +#include "cunumeric/array.inl" diff --git a/src/cunumeric/array.inl b/src/cunumeric/array.inl new file mode 100644 index 0000000000..1bd676a4ac --- /dev/null +++ b/src/cunumeric/array.inl @@ -0,0 +1,27 @@ +/* Copyright 2021 NVIDIA Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +namespace cunumeric { + +template +legate::AccessorRW Array::get_accessor() +{ + auto mapped = store_->get_physical_store(context_); + auto shape = mapped->shape(); + return mapped->read_write_accessor(shape); +} + +} // namespace cunumeric diff --git a/src/cunumeric/cunumeric_task.h b/src/cunumeric/cunumeric_task.h new file mode 100644 index 0000000000..607fcecbee --- /dev/null +++ b/src/cunumeric/cunumeric_task.h @@ -0,0 +1,44 @@ +/* Copyright 2021 NVIDIA Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +#pragma once + +#include "legate.h" +#include "cunumeric/cunumeric_c.h" + +namespace cunumeric { + +enum class VariantKind : int { + CPU = 0, + OMP = 1, + GPU = 2, +}; + +struct CuNumeric { + template + static void record_variant(Args&&... args) + { + get_registrar().record_variant(std::forward(args)...); + } + static legate::LegateTaskRegistrar& get_registrar(); +}; + +template +struct CuNumericTask : public legate::LegateTask { + using Registrar = CuNumeric; +}; + +} // namespace cunumeric diff --git a/src/cunumeric/operators.cc b/src/cunumeric/operators.cc new file mode 100644 index 0000000000..8469a82ee4 --- /dev/null +++ b/src/cunumeric/operators.cc @@ -0,0 +1,69 @@ +/* Copyright 2021 NVIDIA Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +#include "cunumeric/operators.h" +#include "cunumeric/array.h" +#include "cunumeric/runtime.h" +#include "cunumeric/binary/binary_op_util.h" +#include "cunumeric/unary/unary_op_util.h" +#include "cunumeric/random/rand_util.h" + +namespace cunumeric { + +using ArrayP = std::shared_ptr; + +ArrayP array(std::vector shape, legate::LegateTypeCode type) +{ + return CuNumericRuntime::get_runtime()->create_array(std::move(shape), type); +} + +ArrayP unary_op(UnaryOpCode op_code, ArrayP input) +{ + auto runtime = CuNumericRuntime::get_runtime(); + auto out = runtime->create_array(input->shape(), input->code()); + out->unary_op(static_cast(op_code), std::move(input)); + return std::move(out); +} + +ArrayP binary_op(BinaryOpCode op_code, ArrayP rhs1, ArrayP rhs2) +{ + assert(rhs1->shape() == rhs2->shape()); + assert(rhs1->code() == rhs2->code()); + + auto runtime = CuNumericRuntime::get_runtime(); + auto out = runtime->create_array(rhs1->shape(), rhs1->code()); + out->binary_op(static_cast(op_code), std::move(rhs1), std::move(rhs2)); + return std::move(out); +} + +ArrayP abs(ArrayP input) { return unary_op(UnaryOpCode::ABSOLUTE, std::move(input)); } + +ArrayP add(ArrayP rhs1, ArrayP rhs2) +{ + return binary_op(BinaryOpCode::ADD, std::move(rhs1), std::move(rhs2)); +} + +ArrayP negative(ArrayP input) { return unary_op(UnaryOpCode::NEGATIVE, std::move(input)); } + +ArrayP random(std::vector shape) +{ + auto runtime = CuNumericRuntime::get_runtime(); + auto out = runtime->create_array(std::move(shape), legate::LegateTypeCode::DOUBLE_LT); + out->random(static_cast(RandGenCode::UNIFORM)); + return out; +} + +} // namespace cunumeric diff --git a/src/cunumeric/operators.h b/src/cunumeric/operators.h new file mode 100644 index 0000000000..084e0a7698 --- /dev/null +++ b/src/cunumeric/operators.h @@ -0,0 +1,39 @@ +/* Copyright 2021 NVIDIA Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +#pragma once + +#include + +#include "legate.h" + +namespace cunumeric { + +class Array; + +void initialize(int32_t argc, char** argv); + +std::shared_ptr array(std::vector shape, legate::LegateTypeCode type); + +std::shared_ptr abs(std::shared_ptr input); + +std::shared_ptr add(std::shared_ptr rhs1, std::shared_ptr rhs2); + +std::shared_ptr negative(std::shared_ptr input); + +std::shared_ptr random(std::vector shape); + +} // namespace cunumeric diff --git a/src/cunumeric/runtime.cc b/src/cunumeric/runtime.cc new file mode 100644 index 0000000000..7dd1e3c1c8 --- /dev/null +++ b/src/cunumeric/runtime.cc @@ -0,0 +1,67 @@ +/* Copyright 2021 NVIDIA Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +#include "cunumeric/runtime.h" +#include "cunumeric/array.h" + +namespace cunumeric { + +/*static*/ CuNumericRuntime* CuNumericRuntime::runtime_; + +extern void bootstrapping_callback(Legion::Machine machine, + Legion::Runtime* runtime, + const std::set& local_procs); + +void initialize(int32_t argc, char** argv) +{ + Legion::Runtime::perform_registration_callback(bootstrapping_callback, true /*global*/); +} + +CuNumericRuntime::CuNumericRuntime(legate::Runtime* legate_runtime, legate::LibraryContext* context) + : legate_runtime_(legate_runtime), context_(context) +{ +} + +std::shared_ptr CuNumericRuntime::create_array(std::vector shape, + legate::LegateTypeCode type) +{ + // TODO: We need a type system for cuNumeric and should not use the core types + auto store = legate_runtime_->create_store(shape, type); + auto array = new Array(this, context_, std::move(store)); + return std::shared_ptr(array); +} + +std::unique_ptr CuNumericRuntime::create_task(CuNumericOpCode op_code) +{ + return legate_runtime_->create_task(context_, op_code); +} + +void CuNumericRuntime::submit(std::unique_ptr task) +{ + legate_runtime_->submit(std::move(task)); +} + +uint32_t CuNumericRuntime::get_next_random_epoch() { return next_epoch_++; } + +/*static*/ CuNumericRuntime* CuNumericRuntime::get_runtime() { return runtime_; } + +/*static*/ void CuNumericRuntime::initialize(legate::Runtime* legate_runtime, + legate::LibraryContext* context) +{ + runtime_ = new CuNumericRuntime(legate_runtime, context); +} + +} // namespace cunumeric diff --git a/src/cunumeric/runtime.h b/src/cunumeric/runtime.h new file mode 100644 index 0000000000..20796b389c --- /dev/null +++ b/src/cunumeric/runtime.h @@ -0,0 +1,56 @@ +/* Copyright 2021 NVIDIA Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +#pragma once + +#include + +#include "legate.h" + +#include "cunumeric/cunumeric_c.h" + +namespace cunumeric { + +class Array; + +class CuNumericRuntime { + private: + CuNumericRuntime(legate::Runtime* legate_runtime, legate::LibraryContext* context); + + public: + std::shared_ptr create_array(std::vector shape, legate::LegateTypeCode type); + + public: + std::unique_ptr create_task(CuNumericOpCode op_code); + void submit(std::unique_ptr task); + + public: + uint32_t get_next_random_epoch(); + + public: + static CuNumericRuntime* get_runtime(); + static void initialize(legate::Runtime* legate_runtime, legate::LibraryContext* context); + + private: + static CuNumericRuntime* runtime_; + + private: + legate::Runtime* legate_runtime_; + legate::LibraryContext* context_; + uint32_t next_epoch_{0}; +}; + +} // namespace cunumeric From 4bf6bacc7be0f6dbfe40ec625aef862e99305f39 Mon Sep 17 00:00:00 2001 From: Wonchan Lee Date: Tue, 9 Nov 2021 16:54:36 -0800 Subject: [PATCH 020/462] Catch up the core changes for logical store --- src/cunumeric/array.cc | 10 ++++------ src/cunumeric/array.h | 6 ++---- src/cunumeric/array.inl | 2 +- 3 files changed, 7 insertions(+), 11 deletions(-) diff --git a/src/cunumeric/array.cc b/src/cunumeric/array.cc index 7014c514df..456d62f286 100644 --- a/src/cunumeric/array.cc +++ b/src/cunumeric/array.cc @@ -20,18 +20,16 @@ namespace cunumeric { -Array::Array(CuNumericRuntime* runtime, - legate::LibraryContext* context, - std::shared_ptr store) +Array::Array(CuNumericRuntime* runtime, legate::LibraryContext* context, legate::LogicalStore store) : runtime_(runtime), context_(context), store_(store) { } -int32_t Array::dim() const { return store_->dim(); } +int32_t Array::dim() const { return store_.dim(); } -const std::vector& Array::shape() const { return store_->extents(); } +const std::vector& Array::shape() const { return store_.extents(); } -legate::LegateTypeCode Array::code() const { return store_->code(); } +legate::LegateTypeCode Array::code() const { return store_.code(); } static std::vector compute_strides(const std::vector& shape) { diff --git a/src/cunumeric/array.h b/src/cunumeric/array.h index 2f9c6868ef..06747b2e8e 100644 --- a/src/cunumeric/array.h +++ b/src/cunumeric/array.h @@ -28,9 +28,7 @@ class Array { friend class CuNumericRuntime; private: - Array(CuNumericRuntime* runtime, - legate::LibraryContext* context, - std::shared_ptr store); + Array(CuNumericRuntime* runtime, legate::LibraryContext* context, legate::LogicalStore store); public: int32_t dim() const; @@ -49,7 +47,7 @@ class Array { private: CuNumericRuntime* runtime_; legate::LibraryContext* context_; - std::shared_ptr store_; + legate::LogicalStore store_; }; } // namespace cunumeric diff --git a/src/cunumeric/array.inl b/src/cunumeric/array.inl index 1bd676a4ac..9812966e30 100644 --- a/src/cunumeric/array.inl +++ b/src/cunumeric/array.inl @@ -19,7 +19,7 @@ namespace cunumeric { template legate::AccessorRW Array::get_accessor() { - auto mapped = store_->get_physical_store(context_); + auto mapped = store_.get_physical_store(context_); auto shape = mapped->shape(); return mapped->read_write_accessor(shape); } From a660f3f27ce736fbf0c72522cdb86c5bd99df8eb Mon Sep 17 00:00:00 2001 From: Wonchan Lee Date: Wed, 10 Nov 2021 13:53:36 -0800 Subject: [PATCH 021/462] Implement the full operator in C++ --- src/cunumeric.h | 1 + src/cunumeric.mk | 1 + src/cunumeric/array.cc | 15 +++++++++++++++ src/cunumeric/array.h | 2 ++ src/cunumeric/operators.cc | 10 +++++++++- src/cunumeric/operators.h | 3 +++ src/cunumeric/runtime.cc | 5 +++++ src/cunumeric/runtime.h | 2 ++ 8 files changed, 38 insertions(+), 1 deletion(-) diff --git a/src/cunumeric.h b/src/cunumeric.h index bde5bd6b8b..59c424f079 100644 --- a/src/cunumeric.h +++ b/src/cunumeric.h @@ -16,3 +16,4 @@ #include "cunumeric/array.h" #include "cunumeric/operators.h" +#include "cunumeric/typedefs.h" diff --git a/src/cunumeric.mk b/src/cunumeric.mk index 9b9444413f..b61b74e06b 100644 --- a/src/cunumeric.mk +++ b/src/cunumeric.mk @@ -104,4 +104,5 @@ INSTALL_HEADERS = cunumeric/cunumeric_c.h \ cunumeric/array.h \ cunumeric/array.inl \ cunumeric/operators.h \ + cunumeric/typedefs.h \ cunumeric.h diff --git a/src/cunumeric/array.cc b/src/cunumeric/array.cc index 456d62f286..6895a0eaae 100644 --- a/src/cunumeric/array.cc +++ b/src/cunumeric/array.cc @@ -64,6 +64,21 @@ void Array::random(int32_t gen_code) runtime_->submit(std::move(task)); } +void Array::fill(const Scalar& value, bool argval) +{ + auto fill_value = runtime_->create_scalar_store(value); + + auto task = runtime_->create_task(CuNumericOpCode::CUNUMERIC_FILL); + auto p_lhs = task->declare_partition(store_); + auto p_fill_value = task->declare_partition(fill_value); + + task->add_output(store_, p_lhs); + task->add_input(fill_value, p_fill_value); + task->add_scalar_arg(legate::Scalar(argval)); + + runtime_->submit(std::move(task)); +} + void Array::binary_op(int32_t op_code, std::shared_ptr rhs1, std::shared_ptr rhs2) { auto task = runtime_->create_task(CuNumericOpCode::CUNUMERIC_BINARY_OP); diff --git a/src/cunumeric/array.h b/src/cunumeric/array.h index 06747b2e8e..264c86c913 100644 --- a/src/cunumeric/array.h +++ b/src/cunumeric/array.h @@ -19,6 +19,7 @@ #include #include "legate.h" +#include "cunumeric/typedefs.h" namespace cunumeric { @@ -41,6 +42,7 @@ class Array { public: void random(int32_t gen_code); + void fill(const Scalar& value, bool argval); void binary_op(int32_t op_code, std::shared_ptr rhs1, std::shared_ptr rhs2); void unary_op(int32_t op_code, std::shared_ptr input); diff --git a/src/cunumeric/operators.cc b/src/cunumeric/operators.cc index 8469a82ee4..ec9ac9289d 100644 --- a/src/cunumeric/operators.cc +++ b/src/cunumeric/operators.cc @@ -63,7 +63,15 @@ ArrayP random(std::vector shape) auto runtime = CuNumericRuntime::get_runtime(); auto out = runtime->create_array(std::move(shape), legate::LegateTypeCode::DOUBLE_LT); out->random(static_cast(RandGenCode::UNIFORM)); - return out; + return std::move(out); +} + +ArrayP full(std::vector shape, const Scalar& value) +{ + auto runtime = CuNumericRuntime::get_runtime(); + auto out = runtime->create_array(std::move(shape), value.code()); + out->fill(value, false); + return std::move(out); } } // namespace cunumeric diff --git a/src/cunumeric/operators.h b/src/cunumeric/operators.h index 084e0a7698..6050f1c71c 100644 --- a/src/cunumeric/operators.h +++ b/src/cunumeric/operators.h @@ -19,6 +19,7 @@ #include #include "legate.h" +#include "cunumeric/typedefs.h" namespace cunumeric { @@ -36,4 +37,6 @@ std::shared_ptr negative(std::shared_ptr input); std::shared_ptr random(std::vector shape); +std::shared_ptr full(std::vector shape, const Scalar& value); + } // namespace cunumeric diff --git a/src/cunumeric/runtime.cc b/src/cunumeric/runtime.cc index 7dd1e3c1c8..30e4a3ac80 100644 --- a/src/cunumeric/runtime.cc +++ b/src/cunumeric/runtime.cc @@ -44,6 +44,11 @@ std::shared_ptr CuNumericRuntime::create_array(std::vector shape, return std::shared_ptr(array); } +legate::LogicalStore CuNumericRuntime::create_scalar_store(const Scalar& value) +{ + return legate_runtime_->create_store(value); +} + std::unique_ptr CuNumericRuntime::create_task(CuNumericOpCode op_code) { return legate_runtime_->create_task(context_, op_code); diff --git a/src/cunumeric/runtime.h b/src/cunumeric/runtime.h index 20796b389c..34affd18fd 100644 --- a/src/cunumeric/runtime.h +++ b/src/cunumeric/runtime.h @@ -21,6 +21,7 @@ #include "legate.h" #include "cunumeric/cunumeric_c.h" +#include "cunumeric/typedefs.h" namespace cunumeric { @@ -32,6 +33,7 @@ class CuNumericRuntime { public: std::shared_ptr create_array(std::vector shape, legate::LegateTypeCode type); + legate::LogicalStore create_scalar_store(const Scalar& value); public: std::unique_ptr create_task(CuNumericOpCode op_code); From 6caedb5cb6fe44e7b5128dc295253ec58fbf4fc6 Mon Sep 17 00:00:00 2001 From: Wonchan Lee Date: Wed, 10 Nov 2021 14:57:00 -0800 Subject: [PATCH 022/462] Missing file --- src/cunumeric/typedefs.h | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) create mode 100644 src/cunumeric/typedefs.h diff --git a/src/cunumeric/typedefs.h b/src/cunumeric/typedefs.h new file mode 100644 index 0000000000..7d062244f3 --- /dev/null +++ b/src/cunumeric/typedefs.h @@ -0,0 +1,27 @@ +/* Copyright 2021 NVIDIA Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +#pragma once + +#include + +#include "legate.h" + +namespace cunumeric { + +using Scalar = legate::Scalar; + +} // namespace cunumeric From 8903964b3a6656cfae32a85a93d3a4585e93192c Mon Sep 17 00:00:00 2001 From: Wonchan Lee Date: Thu, 11 Nov 2021 10:07:08 -0800 Subject: [PATCH 023/462] Dot operator in C++ --- src/cunumeric/array.cc | 34 +++++++++++++++++++++++++++++++++ src/cunumeric/array.h | 2 ++ src/cunumeric/operators.cc | 30 +++++++++++++++++++++++++++++ src/cunumeric/operators.h | 2 ++ src/cunumeric/runtime.cc | 39 ++++++++++++++++++++++++++++++++++++++ src/cunumeric/runtime.h | 4 ++++ 6 files changed, 111 insertions(+) diff --git a/src/cunumeric/array.cc b/src/cunumeric/array.cc index 6895a0eaae..8e765af9d2 100644 --- a/src/cunumeric/array.cc +++ b/src/cunumeric/array.cc @@ -17,6 +17,7 @@ #include "cunumeric/array.h" #include "cunumeric/runtime.h" #include "cunumeric/random/rand_util.h" +#include "cunumeric/unary/unary_red_util.h" namespace cunumeric { @@ -114,4 +115,37 @@ void Array::unary_op(int32_t op_code, std::shared_ptr input) runtime_->submit(std::move(task)); } +void Array::dot(std::shared_ptr rhs1, std::shared_ptr rhs2) +{ + auto identity = runtime_->get_reduction_identity(UnaryRedCode::SUM, code()); + fill(identity, false); + + assert(dim() == 2 && rhs1->dim() == 2 && rhs2->dim() == 2); + + auto m = rhs1->shape()[0]; + auto n = rhs2->shape()[1]; + auto k = rhs1->shape()[1]; + + auto lhs_s = store_.promote(1, k); + auto rhs1_s = rhs1->store_.promote(2, n); + auto rhs2_s = rhs2->store_.promote(0, m); + + auto task = runtime_->create_task(CuNumericOpCode::CUNUMERIC_MATMUL); + + auto p_lhs = task->declare_partition(lhs_s); + auto p_rhs1 = task->declare_partition(rhs1_s); + auto p_rhs2 = task->declare_partition(rhs2_s); + + auto redop = LEGION_REDOP_BASE + LEGION_TYPE_TOTAL * LEGION_REDOP_KIND_SUM + code(); + + task->add_reduction(lhs_s, redop, p_lhs); + task->add_input(rhs1_s, p_rhs1); + task->add_input(rhs2_s, p_rhs2); + + task->add_constraint(align(p_lhs, p_rhs1)); + task->add_constraint(align(p_rhs1, p_rhs2)); + + runtime_->submit(std::move(task)); +} + } // namespace cunumeric diff --git a/src/cunumeric/array.h b/src/cunumeric/array.h index 264c86c913..082c85a41f 100644 --- a/src/cunumeric/array.h +++ b/src/cunumeric/array.h @@ -45,6 +45,8 @@ class Array { void fill(const Scalar& value, bool argval); void binary_op(int32_t op_code, std::shared_ptr rhs1, std::shared_ptr rhs2); void unary_op(int32_t op_code, std::shared_ptr input); + void fill(std::shared_ptr fill_value); + void dot(std::shared_ptr rhs1, std::shared_ptr rhs2); private: CuNumericRuntime* runtime_; diff --git a/src/cunumeric/operators.cc b/src/cunumeric/operators.cc index ec9ac9289d..f9479372ea 100644 --- a/src/cunumeric/operators.cc +++ b/src/cunumeric/operators.cc @@ -74,4 +74,34 @@ ArrayP full(std::vector shape, const Scalar& value) return std::move(out); } +std::shared_ptr dot(std::shared_ptr rhs1, std::shared_ptr rhs2) +{ + if (rhs1->dim() != 2 || rhs2->dim() != 2) { + fprintf(stderr, "cunumeric::dot only supports matrices now"); + LEGATE_ABORT + } + + auto& rhs1_shape = rhs1->shape(); + auto& rhs2_shape = rhs2->shape(); + + if (rhs1_shape[1] != rhs2_shape[0]) { + fprintf(stderr, + "Incompatible matrices: (%zd, %zd) x (%zd, %zd)\n", + rhs1_shape[0], + rhs1_shape[1], + rhs2_shape[0], + rhs2_shape[1]); + LEGATE_ABORT + } + + auto runtime = CuNumericRuntime::get_runtime(); + std::vector shape; + shape.push_back(rhs1_shape[0]); + shape.push_back(rhs2_shape[1]); + + auto out = runtime->create_array(std::move(shape), rhs1->code()); + out->dot(std::move(rhs1), std::move(rhs2)); + return std::move(out); +} + } // namespace cunumeric diff --git a/src/cunumeric/operators.h b/src/cunumeric/operators.h index 6050f1c71c..b558966500 100644 --- a/src/cunumeric/operators.h +++ b/src/cunumeric/operators.h @@ -33,6 +33,8 @@ std::shared_ptr abs(std::shared_ptr input); std::shared_ptr add(std::shared_ptr rhs1, std::shared_ptr rhs2); +std::shared_ptr dot(std::shared_ptr rhs1, std::shared_ptr rhs2); + std::shared_ptr negative(std::shared_ptr input); std::shared_ptr random(std::vector shape); diff --git a/src/cunumeric/runtime.cc b/src/cunumeric/runtime.cc index 30e4a3ac80..68da99187f 100644 --- a/src/cunumeric/runtime.cc +++ b/src/cunumeric/runtime.cc @@ -21,6 +21,8 @@ namespace cunumeric { /*static*/ CuNumericRuntime* CuNumericRuntime::runtime_; +static std::map, Scalar> identities; + extern void bootstrapping_callback(Legion::Machine machine, Legion::Runtime* runtime, const std::set& local_procs); @@ -49,6 +51,43 @@ legate::LogicalStore CuNumericRuntime::create_scalar_store(const Scalar& value) return legate_runtime_->create_store(value); } +struct generate_identity_fn { + template + struct generator { + template ::valid>* = nullptr> + Scalar operator()() + { + auto value = UnaryRedOp::OP::identity; + return Scalar(value); + } + + template ::valid>* = nullptr> + Scalar operator()() + { + assert(false); + return Scalar(); + } + }; + + template + Scalar operator()(legate::LegateTypeCode type) + { + return legate::type_dispatch(type, generator{}); + } +}; + +Scalar CuNumericRuntime::get_reduction_identity(UnaryRedCode op, legate::LegateTypeCode type) +{ + auto key = std::make_pair(op, type); + auto finder = identities.find(key); + if (identities.end() != finder) return finder->second; + + auto identity = op_dispatch(op, generate_identity_fn{}, type); + identities[key] = identity; + return identity; +} + std::unique_ptr CuNumericRuntime::create_task(CuNumericOpCode op_code) { return legate_runtime_->create_task(context_, op_code); diff --git a/src/cunumeric/runtime.h b/src/cunumeric/runtime.h index 34affd18fd..7ab40ac7cc 100644 --- a/src/cunumeric/runtime.h +++ b/src/cunumeric/runtime.h @@ -22,6 +22,7 @@ #include "cunumeric/cunumeric_c.h" #include "cunumeric/typedefs.h" +#include "cunumeric/unary/unary_red_util.h" namespace cunumeric { @@ -35,6 +36,9 @@ class CuNumericRuntime { std::shared_ptr create_array(std::vector shape, legate::LegateTypeCode type); legate::LogicalStore create_scalar_store(const Scalar& value); + public: + Scalar get_reduction_identity(UnaryRedCode op, legate::LegateTypeCode type); + public: std::unique_ptr create_task(CuNumericOpCode op_code); void submit(std::unique_ptr task); From 84e20858fd1149c9867f912b4ccfc3edc094a79b Mon Sep 17 00:00:00 2001 From: Wonchan Lee Date: Wed, 17 Nov 2021 15:53:46 -0800 Subject: [PATCH 024/462] Unary reduction in C++ --- src/cunumeric/array.cc | 21 +++++++++++++++++++++ src/cunumeric/array.h | 1 + src/cunumeric/operators.cc | 12 +++++++++++- src/cunumeric/operators.h | 2 ++ src/cunumeric/runtime.cc | 31 +++++++++++++++++++++++++++++++ src/cunumeric/runtime.h | 1 + 6 files changed, 67 insertions(+), 1 deletion(-) diff --git a/src/cunumeric/array.cc b/src/cunumeric/array.cc index 8e765af9d2..b941b1f01a 100644 --- a/src/cunumeric/array.cc +++ b/src/cunumeric/array.cc @@ -115,6 +115,27 @@ void Array::unary_op(int32_t op_code, std::shared_ptr input) runtime_->submit(std::move(task)); } +void Array::unary_reduction(int32_t op_code_, std::shared_ptr input) +{ + auto op_code = static_cast(op_code_); + + auto identity = runtime_->get_reduction_identity(op_code, code()); + fill(identity, false); + + auto task = runtime_->create_task(CuNumericOpCode::CUNUMERIC_SCALAR_UNARY_RED); + + auto p_out = task->declare_partition(store_); + auto p_in = task->declare_partition(input->store_); + + auto redop = runtime_->get_reduction_op(op_code, code()); + + task->add_reduction(store_, redop, p_out); + task->add_input(input->store_, p_in); + task->add_scalar_arg(legate::Scalar(op_code_)); + + runtime_->submit(std::move(task)); +} + void Array::dot(std::shared_ptr rhs1, std::shared_ptr rhs2) { auto identity = runtime_->get_reduction_identity(UnaryRedCode::SUM, code()); diff --git a/src/cunumeric/array.h b/src/cunumeric/array.h index 082c85a41f..60c9ebfa3e 100644 --- a/src/cunumeric/array.h +++ b/src/cunumeric/array.h @@ -45,6 +45,7 @@ class Array { void fill(const Scalar& value, bool argval); void binary_op(int32_t op_code, std::shared_ptr rhs1, std::shared_ptr rhs2); void unary_op(int32_t op_code, std::shared_ptr input); + void unary_reduction(int32_t op_code, std::shared_ptr input); void fill(std::shared_ptr fill_value); void dot(std::shared_ptr rhs1, std::shared_ptr rhs2); diff --git a/src/cunumeric/operators.cc b/src/cunumeric/operators.cc index f9479372ea..61f13ea56b 100644 --- a/src/cunumeric/operators.cc +++ b/src/cunumeric/operators.cc @@ -38,6 +38,14 @@ ArrayP unary_op(UnaryOpCode op_code, ArrayP input) return std::move(out); } +ArrayP unary_reduction(UnaryRedCode op_code, ArrayP input) +{ + auto runtime = CuNumericRuntime::get_runtime(); + auto out = runtime->create_array({1}, input->code()); + out->unary_reduction(static_cast(op_code), std::move(input)); + return std::move(out); +} + ArrayP binary_op(BinaryOpCode op_code, ArrayP rhs1, ArrayP rhs2) { assert(rhs1->shape() == rhs2->shape()); @@ -74,7 +82,7 @@ ArrayP full(std::vector shape, const Scalar& value) return std::move(out); } -std::shared_ptr dot(std::shared_ptr rhs1, std::shared_ptr rhs2) +ArrayP dot(ArrayP rhs1, ArrayP rhs2) { if (rhs1->dim() != 2 || rhs2->dim() != 2) { fprintf(stderr, "cunumeric::dot only supports matrices now"); @@ -104,4 +112,6 @@ std::shared_ptr dot(std::shared_ptr rhs1, std::shared_ptr r return std::move(out); } +ArrayP sum(ArrayP input) { return unary_reduction(UnaryRedCode::SUM, std::move(input)); } + } // namespace cunumeric diff --git a/src/cunumeric/operators.h b/src/cunumeric/operators.h index b558966500..6ec285abf2 100644 --- a/src/cunumeric/operators.h +++ b/src/cunumeric/operators.h @@ -41,4 +41,6 @@ std::shared_ptr random(std::vector shape); std::shared_ptr full(std::vector shape, const Scalar& value); +std::shared_ptr sum(std::shared_ptr input); + } // namespace cunumeric diff --git a/src/cunumeric/runtime.cc b/src/cunumeric/runtime.cc index 68da99187f..5f21b3b5d1 100644 --- a/src/cunumeric/runtime.cc +++ b/src/cunumeric/runtime.cc @@ -77,6 +77,31 @@ struct generate_identity_fn { } }; +struct generate_redop_fn { + template + struct generator { + template ::valid>* = nullptr> + int32_t operator()() + { + return UnaryRedOp::OP::REDOP_ID; + } + + template ::valid>* = nullptr> + int32_t operator()() + { + assert(false); + return 0; + } + }; + + template + int32_t operator()(legate::LegateTypeCode type) + { + return legate::type_dispatch(type, generator{}); + } +}; + Scalar CuNumericRuntime::get_reduction_identity(UnaryRedCode op, legate::LegateTypeCode type) { auto key = std::make_pair(op, type); @@ -88,6 +113,12 @@ Scalar CuNumericRuntime::get_reduction_identity(UnaryRedCode op, legate::LegateT return identity; } +Legion::ReductionOpID CuNumericRuntime::get_reduction_op(UnaryRedCode op, + legate::LegateTypeCode type) +{ + return op_dispatch(op, generate_redop_fn{}, type); +} + std::unique_ptr CuNumericRuntime::create_task(CuNumericOpCode op_code) { return legate_runtime_->create_task(context_, op_code); diff --git a/src/cunumeric/runtime.h b/src/cunumeric/runtime.h index 7ab40ac7cc..5afb1509a1 100644 --- a/src/cunumeric/runtime.h +++ b/src/cunumeric/runtime.h @@ -38,6 +38,7 @@ class CuNumericRuntime { public: Scalar get_reduction_identity(UnaryRedCode op, legate::LegateTypeCode type); + Legion::ReductionOpID get_reduction_op(UnaryRedCode op, legate::LegateTypeCode type); public: std::unique_ptr create_task(CuNumericOpCode op_code); From 1af78aa85401901d610720577aea50f2631c592d Mon Sep 17 00:00:00 2001 From: Marcin Zalewski Date: Mon, 7 Feb 2022 18:44:50 -0800 Subject: [PATCH 025/462] Update version --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index fb643716ad..8cbab41a19 100755 --- a/setup.py +++ b/setup.py @@ -74,7 +74,7 @@ def run(self): sys.argv.remove("--recurse") setup( name="cunumeric", - version="0.1", + version="22.01", packages=[ "cunumeric", "cunumeric.linalg", From 3d8e15a8dcfb61a14dc99bf4ad22286e1ecc77f1 Mon Sep 17 00:00:00 2001 From: Marcin Zalewski Date: Tue, 8 Feb 2022 02:15:08 -0800 Subject: [PATCH 026/462] Version and package update. --- setup.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/setup.py b/setup.py index 8cbab41a19..2e09ddbdbb 100755 --- a/setup.py +++ b/setup.py @@ -74,11 +74,11 @@ def run(self): sys.argv.remove("--recurse") setup( name="cunumeric", - version="22.01", + version="22.01.00", packages=[ "cunumeric", - "cunumeric.linalg", - "cunumeric.random", + "cunumeric-linalg", + "cunumeric-random", ], cmdclass={"build_py": my_build_py}, ) From faca09b022d91e4b2cd1f506718e1ba67429338f Mon Sep 17 00:00:00 2001 From: Marcin Zalewski Date: Tue, 8 Feb 2022 11:10:01 -0800 Subject: [PATCH 027/462] Fix setup.py packages --- setup.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/setup.py b/setup.py index 2e09ddbdbb..95d4e80620 100755 --- a/setup.py +++ b/setup.py @@ -77,8 +77,8 @@ def run(self): version="22.01.00", packages=[ "cunumeric", - "cunumeric-linalg", - "cunumeric-random", + "cunumeric.linalg", + "cunumeric.random", ], cmdclass={"build_py": my_build_py}, ) From 3cf497d74515c88cb5ae41f6fc563974598da049 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 8 Feb 2022 20:51:04 +0000 Subject: [PATCH 028/462] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- tests/tensordot.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/tensordot.py b/tests/tensordot.py index 906771f935..386e4405c5 100644 --- a/tests/tensordot.py +++ b/tests/tensordot.py @@ -20,7 +20,7 @@ def test(ty): rtol = 2e-03 if ty == np.float16 else 1e-05 - + a = num.random.rand(3, 5, 4).astype(ty) b = num.random.rand(4, 5, 3).astype(ty) From dc16654e3d993e499a748c2031ac1f29b54bb2f4 Mon Sep 17 00:00:00 2001 From: Wonchan Lee Date: Thu, 24 Mar 2022 16:02:18 -0700 Subject: [PATCH 029/462] Rename ufunc to _ufunc and make cunumeric.ufunc a type, not a module --- cunumeric/__init__.py | 6 +----- cunumeric/{ufunc => _ufunc}/__init__.py | 7 +++++++ cunumeric/{ufunc => _ufunc}/bit_twiddling.py | 0 cunumeric/{ufunc => _ufunc}/comparison.py | 0 cunumeric/{ufunc => _ufunc}/floating.py | 0 cunumeric/{ufunc => _ufunc}/math.py | 0 cunumeric/{ufunc => _ufunc}/trigonometric.py | 0 cunumeric/{ufunc => _ufunc}/ufunc.py | 0 cunumeric/linalg/linalg.py | 2 +- cunumeric/module.py | 6 +++--- setup.py | 2 +- 11 files changed, 13 insertions(+), 10 deletions(-) rename cunumeric/{ufunc => _ufunc}/__init__.py (79%) rename cunumeric/{ufunc => _ufunc}/bit_twiddling.py (100%) rename cunumeric/{ufunc => _ufunc}/comparison.py (100%) rename cunumeric/{ufunc => _ufunc}/floating.py (100%) rename cunumeric/{ufunc => _ufunc}/math.py (100%) rename cunumeric/{ufunc => _ufunc}/trigonometric.py (100%) rename cunumeric/{ufunc => _ufunc}/ufunc.py (100%) diff --git a/cunumeric/__init__.py b/cunumeric/__init__.py index 228bf0a6b8..558507542e 100644 --- a/cunumeric/__init__.py +++ b/cunumeric/__init__.py @@ -28,11 +28,7 @@ from cunumeric import linalg, random from cunumeric.array import ndarray from cunumeric.module import * -from cunumeric.ufunc.bit_twiddling import * -from cunumeric.ufunc.comparison import * -from cunumeric.ufunc.math import * -from cunumeric.ufunc.floating import * -from cunumeric.ufunc.trigonometric import * +from cunumeric._ufunc import * from cunumeric.coverage import clone_module clone_module(_np, globals()) diff --git a/cunumeric/ufunc/__init__.py b/cunumeric/_ufunc/__init__.py similarity index 79% rename from cunumeric/ufunc/__init__.py rename to cunumeric/_ufunc/__init__.py index d148fe1cc1..62861c87d3 100644 --- a/cunumeric/ufunc/__init__.py +++ b/cunumeric/_ufunc/__init__.py @@ -12,3 +12,10 @@ # See the License for the specific language governing permissions and # limitations under the License. # + +from .bit_twiddling import * +from .comparison import * +from .math import * +from .floating import * +from .trigonometric import * +from .ufunc import ufunc diff --git a/cunumeric/ufunc/bit_twiddling.py b/cunumeric/_ufunc/bit_twiddling.py similarity index 100% rename from cunumeric/ufunc/bit_twiddling.py rename to cunumeric/_ufunc/bit_twiddling.py diff --git a/cunumeric/ufunc/comparison.py b/cunumeric/_ufunc/comparison.py similarity index 100% rename from cunumeric/ufunc/comparison.py rename to cunumeric/_ufunc/comparison.py diff --git a/cunumeric/ufunc/floating.py b/cunumeric/_ufunc/floating.py similarity index 100% rename from cunumeric/ufunc/floating.py rename to cunumeric/_ufunc/floating.py diff --git a/cunumeric/ufunc/math.py b/cunumeric/_ufunc/math.py similarity index 100% rename from cunumeric/ufunc/math.py rename to cunumeric/_ufunc/math.py diff --git a/cunumeric/ufunc/trigonometric.py b/cunumeric/_ufunc/trigonometric.py similarity index 100% rename from cunumeric/ufunc/trigonometric.py rename to cunumeric/_ufunc/trigonometric.py diff --git a/cunumeric/ufunc/ufunc.py b/cunumeric/_ufunc/ufunc.py similarity index 100% rename from cunumeric/ufunc/ufunc.py rename to cunumeric/_ufunc/ufunc.py diff --git a/cunumeric/linalg/linalg.py b/cunumeric/linalg/linalg.py index 3c251f29bf..41bbef0f6a 100644 --- a/cunumeric/linalg/linalg.py +++ b/cunumeric/linalg/linalg.py @@ -14,9 +14,9 @@ # import numpy as np +from cunumeric._ufunc.math import sqrt as _sqrt from cunumeric.array import convert_to_cunumeric_ndarray from cunumeric.module import ndarray -from cunumeric.ufunc.math import sqrt as _sqrt def cholesky(a): diff --git a/cunumeric/module.py b/cunumeric/module.py index c2388d0624..325de24ab4 100644 --- a/cunumeric/module.py +++ b/cunumeric/module.py @@ -23,9 +23,9 @@ import numpy as np import opt_einsum as oe -from cunumeric.ufunc.comparison import maximum, minimum -from cunumeric.ufunc.floating import floor -from cunumeric.ufunc.math import add, multiply +from cunumeric._ufunc.comparison import maximum, minimum +from cunumeric._ufunc.floating import floor +from cunumeric._ufunc.math import add, multiply from .array import ( convert_to_cunumeric_ndarray, diff --git a/setup.py b/setup.py index 5d85486a6e..b41776823f 100755 --- a/setup.py +++ b/setup.py @@ -79,7 +79,7 @@ def run(self): "cunumeric", "cunumeric.linalg", "cunumeric.random", - "cunumeric.ufunc", + "cunumeric._ufunc", ], cmdclass={"build_py": my_build_py}, ) From 397ad4d1249e99e5dc07c1033eb8e6c4da45b58d Mon Sep 17 00:00:00 2001 From: Bryan Van de Ven Date: Mon, 28 Mar 2022 16:39:07 -0700 Subject: [PATCH 030/462] Fix docs breakage --- cunumeric/coverage.py | 24 ++++-- cunumeric/module.py | 12 +-- docs/cunumeric/source/api/_ndarray.rst | 75 +++++++++++++++++++ docs/cunumeric/source/api/creation.rst | 1 + docs/cunumeric/source/api/ndarray.rst | 31 +++----- docs/cunumeric/source/api/sorting.rst | 11 +++ .../comparison/_comparison_generator.py | 2 +- 7 files changed, 121 insertions(+), 35 deletions(-) create mode 100644 docs/cunumeric/source/api/_ndarray.rst diff --git a/cunumeric/coverage.py b/cunumeric/coverage.py index 8f10e0ef4f..536c6b091e 100644 --- a/cunumeric/coverage.py +++ b/cunumeric/coverage.py @@ -69,6 +69,8 @@ def wrapper(*args: Any, **kwargs: Any) -> Any: runtime.record_api_call(name=name, location=location, implemented=True) return func(*args, **kwargs) + wrapper._cunumeric_implemented = True + return wrapper @@ -97,6 +99,8 @@ def wrapper(*args: Any, **kwargs: Any) -> Any: ) return func(*args, **kwargs) + wrapper._cunumeric_implemented = False + return wrapper @@ -129,10 +133,12 @@ def clone_module( omit_types=(ModuleType,), ) - if runtime.report_coverage: - for attr, value in new_globals.items(): - if isinstance(value, FunctionType): + for attr, value in new_globals.items(): + if isinstance(value, FunctionType): + if runtime.report_coverage: new_globals[attr] = implemented(value, module_name) + else: + value._cunumeric_implemented = True for attr, value in missing.items(): if isinstance(value, FunctionType): @@ -167,12 +173,14 @@ def decorator(cls: type) -> type: omit_names=set(cls.__dict__).union(NDARRAY_INTERNAL), ) - if runtime.report_coverage: - for attr, value in cls.__dict__.items(): - if isinstance( - value, (FunctionType, MethodType, MethodDescriptorType) - ): + for attr, value in cls.__dict__.items(): + if isinstance( + value, (FunctionType, MethodType, MethodDescriptorType) + ): + if runtime.report_coverage: setattr(cls, attr, implemented(value, class_name)) + else: + value._cunumeric_implemented = True for attr, value in missing.items(): if isinstance( diff --git a/cunumeric/module.py b/cunumeric/module.py index c2388d0624..f5b69c7aab 100644 --- a/cunumeric/module.py +++ b/cunumeric/module.py @@ -1918,7 +1918,7 @@ def repeat(a, repeats, axis=None): ---------- a : array_like Input array. - repeats : int or array of ints + repeats : int or ndarray[int] The number of repetitions for each element. repeats is broadcasted to fit the shape of the given axis. axis : int, optional @@ -3738,15 +3738,15 @@ def argsort(a, axis=-1, kind="quicksort", order=None): axis : int or None, optional Axis to sort. By default, the index -1 (the last axis) is used. If None, the flattened array is used. - kind : {'quicksort', 'mergesort', 'heapsort', 'stable'}, optional + kind : ``{'quicksort', 'mergesort', 'heapsort', 'stable'}``, optional Default is 'quicksort'. The underlying sort algorithm might vary. The code basically supports 'stable' or *not* 'stable'. - order : str or list of str, optional + order : str or list[str], optional Currently not supported Returns ------- - index_array : ndarray of ints + index_array : ndarray[int] Array of indices that sort a along the specified axis. It has the same shape as `a.shape` or is flattened in case of `axis` is None. @@ -3815,10 +3815,10 @@ def sort(a, axis=-1, kind="quicksort", order=None): axis : int or None, optional Axis to sort. By default, the index -1 (the last axis) is used. If None, the flattened array is used. - kind : {'quicksort', 'mergesort', 'heapsort', 'stable'}, optional + kind : ``{'quicksort', 'mergesort', 'heapsort', 'stable'}``, optional Default is 'quicksort'. The underlying sort algorithm might vary. The code basically supports 'stable' or *not* 'stable'. - order : str or list of str, optional + order : str or list[str], optional Currently not supported Returns diff --git a/docs/cunumeric/source/api/_ndarray.rst b/docs/cunumeric/source/api/_ndarray.rst new file mode 100644 index 0000000000..307f1a8ead --- /dev/null +++ b/docs/cunumeric/source/api/_ndarray.rst @@ -0,0 +1,75 @@ +cunumeric.ndarray +================= + +.. currentmodule:: cunumeric + +.. autoclass:: ndarray + + .. automethod:: __init__ + + .. rubric:: Methods + + .. autosummary:: + + ~ndarray.__init__ + ~ndarray.all + ~ndarray.any + ~ndarray.argmax + ~ndarray.argmin + ~ndarray.astype + ~ndarray.choose + ~ndarray.clip + ~ndarray.conj + ~ndarray.conjugate + ~ndarray.copy + ~ndarray.diagonal + ~ndarray.dot + ~ndarray.dump + ~ndarray.dumps + ~ndarray.fill + ~ndarray.find_common_type + ~ndarray.flatten + ~ndarray.flip + ~ndarray.getfield + ~ndarray.item + ~ndarray.itemset + ~ndarray.max + ~ndarray.mean + ~ndarray.min + ~ndarray.nonzero + ~ndarray.prod + ~ndarray.ravel + ~ndarray.reshape + ~ndarray.setfield + ~ndarray.setflags + ~ndarray.squeeze + ~ndarray.sum + ~ndarray.swapaxes + ~ndarray.tobytes + ~ndarray.tofile + ~ndarray.tolist + ~ndarray.tostring + ~ndarray.transpose + ~ndarray.unique + ~ndarray.view + + + .. rubric:: Attributes + + .. autosummary:: + + ~ndarray.T + ~ndarray.base + ~ndarray.ctypes + ~ndarray.data + ~ndarray.dtype + ~ndarray.flags + ~ndarray.flat + ~ndarray.imag + ~ndarray.itemsize + ~ndarray.nbytes + ~ndarray.ndim + ~ndarray.real + ~ndarray.shape + ~ndarray.size + ~ndarray.strides diff --git a/docs/cunumeric/source/api/creation.rst b/docs/cunumeric/source/api/creation.rst index a5f85fee16..bf9abb8499 100644 --- a/docs/cunumeric/source/api/creation.rst +++ b/docs/cunumeric/source/api/creation.rst @@ -30,6 +30,7 @@ From existing data array asarray copy + repeat Numerical ranges diff --git a/docs/cunumeric/source/api/ndarray.rst b/docs/cunumeric/source/api/ndarray.rst index 1920009186..7be813d46c 100644 --- a/docs/cunumeric/source/api/ndarray.rst +++ b/docs/cunumeric/source/api/ndarray.rst @@ -9,11 +9,14 @@ Constructing arrays New arrays can be constructed using the routines detailed in Array creation routines, and also by using the low-level ndarray constructor: -.. autosummary:: - :toctree: generated/ +.. toctree:: + :maxdepth: 2 + :hidden: + + _ndarray - ndarray +:meth:`ndarray` Calculation ----------- @@ -124,8 +127,8 @@ Item selection and manipulation .. ndarray.put .. ndarray.repeat ndarray.choose - .. ndarray.sort - .. ndarray.argsort + ndarray.sort + ndarray.argsort .. ndarray.partition .. ndarray.argpartition .. ndarray.searchsorted @@ -146,6 +149,9 @@ Calculation .. ndarray.ptp ndarray.clip ndarray.conj + ndarray.conjugate + ndarray.dot + ndarray.flip .. ndarray.round .. ndarray.trace ndarray.sum @@ -159,21 +165,6 @@ Calculation ndarray.any ndarray.unique - -EXTRA ------ - -Calculation -~~~~~~~~~~~ - -.. autosummary:: - :toctree: generated/ - - ndarray.conjugate - ndarray.dot - ndarray.flip - - Arithmetic, matrix multiplication, and comparison operations ------------------------------------------------------------ diff --git a/docs/cunumeric/source/api/sorting.rst b/docs/cunumeric/source/api/sorting.rst index 8393d83dca..a4a3ee32e6 100644 --- a/docs/cunumeric/source/api/sorting.rst +++ b/docs/cunumeric/source/api/sorting.rst @@ -3,6 +3,17 @@ Sorting, searching, and counting .. currentmodule:: cunumeric +Sorting +------- + +.. autosummary:: + :toctree: generated/ + + argsort + msort + sort + sort_complex + Searching --------- diff --git a/docs/cunumeric/source/comparison/_comparison_generator.py b/docs/cunumeric/source/comparison/_comparison_generator.py index 6e44736d4c..398d834f21 100644 --- a/docs/cunumeric/source/comparison/_comparison_generator.py +++ b/docs/cunumeric/source/comparison/_comparison_generator.py @@ -73,7 +73,7 @@ def _section(header, mod_ext, other_lib, klass=None, exclude_mod=None): lg_funcs = [] for f in _get_functions(lg_obj): obj = getattr(lg_obj, f) - if obj.__doc__ is None or "Unimplemented" not in obj.__doc__: + if getattr(obj, "_cunumeric_implemented", False): lg_funcs.append(f) lg_funcs = set(lg_funcs) From 71124f2154cc89f2f8092e652e245fd05dc6e2a4 Mon Sep 17 00:00:00 2001 From: Bryan Van de Ven Date: Tue, 29 Mar 2022 10:53:48 -0700 Subject: [PATCH 031/462] fix coverage for ufuncs --- cunumeric/coverage.py | 29 +++++++++++++++++++---------- 1 file changed, 19 insertions(+), 10 deletions(-) diff --git a/cunumeric/coverage.py b/cunumeric/coverage.py index 536c6b091e..c10f4e5275 100644 --- a/cunumeric/coverage.py +++ b/cunumeric/coverage.py @@ -60,8 +60,8 @@ def filter_namespace( # todo: (bev) use callback protocol type starting with 3.8 -def implemented(func: Any, prefix: str) -> Any: - name = f"{prefix}.{func.__name__}" +def implemented(func: Any, prefix: str, name: str) -> Any: + name = f"{prefix}.{name}" @wraps(func) def wrapper(*args: Any, **kwargs: Any) -> Any: @@ -75,8 +75,10 @@ def wrapper(*args: Any, **kwargs: Any) -> Any: # todo: (bev) use callback protocol type starting with 3.8 -def unimplemented(func: Any, prefix: str, *, reporting: bool = True) -> Any: - name = f"{prefix}.{func.__name__}" +def unimplemented( + func: Any, prefix: str, name: str, *, reporting: bool = True +) -> Any: + name = f"{prefix}.{name}" if reporting: @wraps(func) @@ -133,17 +135,21 @@ def clone_module( omit_types=(ModuleType,), ) + from numpy import ufunc as npufunc + + from .ufunc.ufunc import ufunc as lgufunc + for attr, value in new_globals.items(): - if isinstance(value, FunctionType): + if isinstance(value, (FunctionType, lgufunc)): if runtime.report_coverage: - new_globals[attr] = implemented(value, module_name) + new_globals[attr] = implemented(value, module_name, attr) else: value._cunumeric_implemented = True for attr, value in missing.items(): - if isinstance(value, FunctionType): + if isinstance(value, (FunctionType, npufunc)): new_globals[attr] = unimplemented( - value, module_name, reporting=runtime.report_coverage + value, module_name, attr, reporting=runtime.report_coverage ) else: new_globals[attr] = value @@ -178,7 +184,7 @@ def decorator(cls: type) -> type: value, (FunctionType, MethodType, MethodDescriptorType) ): if runtime.report_coverage: - setattr(cls, attr, implemented(value, class_name)) + setattr(cls, attr, implemented(value, class_name, attr)) else: value._cunumeric_implemented = True @@ -190,7 +196,10 @@ def decorator(cls: type) -> type: cls, attr, unimplemented( - value, class_name, reporting=runtime.report_coverage + value, + class_name, + attr, + reporting=runtime.report_coverage, ), ) else: From 1463d2b385c233cdc63a73c994747ca7eba47d04 Mon Sep 17 00:00:00 2001 From: Wonchan Lee Date: Tue, 29 Mar 2022 19:35:07 -0700 Subject: [PATCH 032/462] Catch up the ufunc renaming --- cunumeric/coverage.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cunumeric/coverage.py b/cunumeric/coverage.py index c10f4e5275..cd0eee1384 100644 --- a/cunumeric/coverage.py +++ b/cunumeric/coverage.py @@ -137,7 +137,7 @@ def clone_module( from numpy import ufunc as npufunc - from .ufunc.ufunc import ufunc as lgufunc + from ._ufunc.ufunc import ufunc as lgufunc for attr, value in new_globals.items(): if isinstance(value, (FunctionType, lgufunc)): From 89ac6f3574c60260343ce2efd0349df600b3d6f3 Mon Sep 17 00:00:00 2001 From: Wonchan Lee Date: Tue, 29 Mar 2022 21:15:48 -0700 Subject: [PATCH 033/462] Extract the cuBLAS at runtime for the workaround --- src/cunumeric/matrix/matvecmul.cu | 182 ++++++++++++++++-------------- 1 file changed, 100 insertions(+), 82 deletions(-) diff --git a/src/cunumeric/matrix/matvecmul.cu b/src/cunumeric/matrix/matvecmul.cu index 742d632cf1..a5ca6f9554 100644 --- a/src/cunumeric/matrix/matvecmul.cu +++ b/src/cunumeric/matrix/matvecmul.cu @@ -41,28 +41,34 @@ struct MatVecMulImplBody { const float beta = 0.0; auto trans = transpose_mat ? CUBLAS_OP_N : CUBLAS_OP_T; -#if CUDART_VERSION >= 11040 - CHECK_CUBLAS( - cublasSgemv(cublas_handle, trans, n, m, &alpha, mat, mat_stride, vec, 1, &beta, lhs, 1)); -#else - CHECK_CUBLAS(cublasSgemmEx(cublas_handle, - trans, - CUBLAS_OP_N, - transpose_mat ? n : m, - 1, - transpose_mat ? m : n, - &alpha, - mat, - CUDA_R_32F, - mat_stride, - vec, - CUDA_R_32F, - transpose_mat ? m : n, - &beta, - lhs, - CUDA_R_32F, - transpose_mat ? n : m)); -#endif + + // XXX: There is a bug in older versions of cuBLAS that are triggered + // by some degenerate matrix-vector multiplications. We simply use + // matrix-matrix multiplication all the time unless we're on a recent + // cuBLAS version + int32_t version; + CHECK_CUBLAS(cublasGetVersion(cublas_handle, &version)); + if (version >= 11700) + CHECK_CUBLAS( + cublasSgemv(cublas_handle, trans, n, m, &alpha, mat, mat_stride, vec, 1, &beta, lhs, 1)); + else + CHECK_CUBLAS(cublasSgemmEx(cublas_handle, + trans, + CUBLAS_OP_N, + transpose_mat ? n : m, + 1, + transpose_mat ? m : n, + &alpha, + mat, + CUDA_R_32F, + mat_stride, + vec, + CUDA_R_32F, + transpose_mat ? m : n, + &beta, + lhs, + CUDA_R_32F, + transpose_mat ? n : m)); } }; @@ -84,25 +90,29 @@ struct MatVecMulImplBody { const double beta = 0.0; auto trans = transpose_mat ? CUBLAS_OP_N : CUBLAS_OP_T; -#if CUDART_VERSION >= 11040 - CHECK_CUBLAS( - cublasDgemv(cublas_handle, trans, n, m, &alpha, mat, mat_stride, vec, 1, &beta, lhs, 1)); -#else - CHECK_CUBLAS(cublasDgemm(cublas_handle, - trans, - CUBLAS_OP_N, - transpose_mat ? n : m, - 1, - transpose_mat ? m : n, - &alpha, - mat, - mat_stride, - vec, - transpose_mat ? m : n, - &beta, - lhs, - transpose_mat ? n : m)); -#endif + + // FIXME: It's actually unknown that the cuBLAS bug for 32-bit floats reproduces for + // 64-bit flots as well. We're simply being conservative here. + int32_t version; + CHECK_CUBLAS(cublasGetVersion(cublas_handle, &version)); + if (version >= 11700) + CHECK_CUBLAS( + cublasDgemv(cublas_handle, trans, n, m, &alpha, mat, mat_stride, vec, 1, &beta, lhs, 1)); + else + CHECK_CUBLAS(cublasDgemm(cublas_handle, + trans, + CUBLAS_OP_N, + transpose_mat ? n : m, + 1, + transpose_mat ? m : n, + &alpha, + mat, + mat_stride, + vec, + transpose_mat ? m : n, + &beta, + lhs, + transpose_mat ? n : m)); } }; @@ -167,28 +177,32 @@ struct MatVecMulImplBody { const cuComplex beta = make_float2(0.0, 0.0); auto trans = transpose_mat ? CUBLAS_OP_N : CUBLAS_OP_T; -#if CUDART_VERSION >= 11040 - CHECK_CUBLAS( - cublasCgemv(cublas_handle, trans, n, m, &alpha, mat, mat_stride, vec, 1, &beta, lhs, 1)); -#else - CHECK_CUBLAS(cublasCgemmEx(cublas_handle, - trans, - CUBLAS_OP_N, - transpose_mat ? n : m, - 1, - transpose_mat ? m : n, - &alpha, - mat, - CUDA_C_32F, - mat_stride, - vec, - CUDA_C_32F, - transpose_mat ? m : n, - &beta, - lhs, - CUDA_C_32F, - transpose_mat ? n : m)); -#endif + + // FIXME: It's actually unknown that the cuBLAS bug for 32-bit floats reproduces for + // complex64 as well. We're simply being conservative here. + int32_t version; + CHECK_CUBLAS(cublasGetVersion(cublas_handle, &version)); + if (version >= 11700) + CHECK_CUBLAS( + cublasCgemv(cublas_handle, trans, n, m, &alpha, mat, mat_stride, vec, 1, &beta, lhs, 1)); + else + CHECK_CUBLAS(cublasCgemmEx(cublas_handle, + trans, + CUBLAS_OP_N, + transpose_mat ? n : m, + 1, + transpose_mat ? m : n, + &alpha, + mat, + CUDA_C_32F, + mat_stride, + vec, + CUDA_C_32F, + transpose_mat ? m : n, + &beta, + lhs, + CUDA_C_32F, + transpose_mat ? n : m)); } }; @@ -214,25 +228,29 @@ struct MatVecMulImplBody { const cuDoubleComplex beta = make_double2(0.0, 0.0); auto trans = transpose_mat ? CUBLAS_OP_N : CUBLAS_OP_T; -#if CUDART_VERSION >= 11040 - CHECK_CUBLAS( - cublasZgemv(cublas_handle, trans, n, m, &alpha, mat, mat_stride, vec, 1, &beta, lhs, 1)); -#else - CHECK_CUBLAS(cublasZgemm(cublas_handle, - trans, - CUBLAS_OP_N, - transpose_mat ? n : m, - 1, - transpose_mat ? m : n, - &alpha, - mat, - mat_stride, - vec, - transpose_mat ? m : n, - &beta, - lhs, - transpose_mat ? n : m)); -#endif + + // FIXME: It's actually unknown that the cuBLAS bug for 32-bit floats reproduces for + // complex128 as well. We're simply being conservative here. + int32_t version; + CHECK_CUBLAS(cublasGetVersion(cublas_handle, &version)); + if (version >= 11700) + CHECK_CUBLAS( + cublasZgemv(cublas_handle, trans, n, m, &alpha, mat, mat_stride, vec, 1, &beta, lhs, 1)); + else + CHECK_CUBLAS(cublasZgemm(cublas_handle, + trans, + CUBLAS_OP_N, + transpose_mat ? n : m, + 1, + transpose_mat ? m : n, + &alpha, + mat, + mat_stride, + vec, + transpose_mat ? m : n, + &beta, + lhs, + transpose_mat ? n : m)); } }; From bfc84eb625aa59a17b0049156c0e52aec6af17e3 Mon Sep 17 00:00:00 2001 From: Marcin Zalewski Date: Sat, 2 Apr 2022 03:13:40 -0700 Subject: [PATCH 034/462] Move up Thrust include (#257) Co-authored-by: Marcin Zalewski --- src/Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Makefile b/src/Makefile index d66b3d56bc..6b2dae92cd 100644 --- a/src/Makefile +++ b/src/Makefile @@ -55,7 +55,7 @@ LD_FLAGS += -L$(CUTENSOR_PATH)/lib -lcutensor -Wl,-rpath,$(CUTENSOR_PATH)/lib LD_FLAGS += -L$(NCCL_PATH)/lib -lnccl -Wl,-rpath,$(NCCL_PATH)/lib endif NVCC_FLAGS ?= -NVCC_FLAGS += -I. -I$(CUTENSOR_PATH)/include -I$(NCCL_PATH)/include -I$(THRUST_PATH) -Wno-deprecated-declarations +NVCC_FLAGS += -I. -I$(THRUST_PATH) -I$(CUTENSOR_PATH)/include -I$(NCCL_PATH)/include -Wno-deprecated-declarations ifeq ($(strip $(DEBUG)),1) CC_FLAGS += -DDEBUG_CUNUMERIC From e113d2882cd149c95bec5fdcccaae226e6d65d4a Mon Sep 17 00:00:00 2001 From: Marcin Zalewski Date: Sat, 2 Apr 2022 03:14:47 -0700 Subject: [PATCH 035/462] Move up Thrust include (#257) (#258) Co-authored-by: Marcin Zalewski Co-authored-by: Marcin Zalewski --- src/Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Makefile b/src/Makefile index d66b3d56bc..6b2dae92cd 100644 --- a/src/Makefile +++ b/src/Makefile @@ -55,7 +55,7 @@ LD_FLAGS += -L$(CUTENSOR_PATH)/lib -lcutensor -Wl,-rpath,$(CUTENSOR_PATH)/lib LD_FLAGS += -L$(NCCL_PATH)/lib -lnccl -Wl,-rpath,$(NCCL_PATH)/lib endif NVCC_FLAGS ?= -NVCC_FLAGS += -I. -I$(CUTENSOR_PATH)/include -I$(NCCL_PATH)/include -I$(THRUST_PATH) -Wno-deprecated-declarations +NVCC_FLAGS += -I. -I$(THRUST_PATH) -I$(CUTENSOR_PATH)/include -I$(NCCL_PATH)/include -Wno-deprecated-declarations ifeq ($(strip $(DEBUG)),1) CC_FLAGS += -DDEBUG_CUNUMERIC From cdfd5ba4d156f2986ae042465411a4ba8d1bb016 Mon Sep 17 00:00:00 2001 From: Bryan Van de Ven Date: Mon, 4 Apr 2022 13:24:09 -0700 Subject: [PATCH 036/462] Add user-facing docs for coverage reporting --- .../source/comparison/comparison.rst | 65 +++++++++++++++++++ 1 file changed, 65 insertions(+) diff --git a/docs/cunumeric/source/comparison/comparison.rst b/docs/cunumeric/source/comparison/comparison.rst index e18377c9e1..cc1de94232 100644 --- a/docs/cunumeric/source/comparison/comparison.rst +++ b/docs/cunumeric/source/comparison/comparison.rst @@ -7,3 +7,68 @@ Here is a list of NumPy APIs and corresponding cuNumeric implementations. We welcome contributions for these functions. .. include:: comparison_table.rst.inc + + +Measuring API coverage +---------------------- + +When running applications that use cunumeric, various command line options may +be used to generate coverage reports. + +Overall coverage report +~~~~~~~~~~~~~~~~~~~~~~~ + +The command line flag ``-cunumeric:report:coverage`` may be added to print an +overall percentage of cunumeric coverage: + +.. code-block:: sh + + legate test.py -cunumeric:report:coverage + +After execution completes, the percentage of NumPy API calls that were handled +by cunumeric is printed: + +.. code-block:: + + cuNumeric API coverage: 26/26 (100.0%) + +Detailed coverage report +~~~~~~~~~~~~~~~~~~~~~~~~ + +The command line flag ``-cunumeric:report:dump-csv`` may be added to save a +detailed coverage report: + +.. code-block:: sh + + legate test.py -cunumeric:report:dump-csv out.csv + +After execution completes, a CSV file will be saved to the specified location +(in this case ``out.csv``). The file shows exactly what NumPy API functions +were called, whether the are implemented by cunumeric, and the location of +the call site: + +.. code-block:: + + function_name,location,implemented + numpy.array,tests/dot.py:27,True + numpy.ndarray.__init__,tests/dot.py:27,True + numpy.array,tests/dot.py:28,True + numpy.ndarray.__init__,tests/dot.py:28,True + numpy.ndarray.dot,tests/dot.py:31,True + numpy.ndarray.__init__,tests/dot.py:31,True + numpy.allclose,tests/dot.py:33,True + numpy.ndarray.__init__,tests/dot.py:33,True + +Call stack reporting +~~~~~~~~~~~~~~~~~~~~ + +The command line flag ``-cunumeric:report:dump-callstack`` may be added to +include full call stack information in a CSV report: + +.. code-block:: sh + + legate test.py -cunumeric:report:dump-callstack -cunumeric:report:dump-csv out.csv + +After execution completes, the CSV output file have full call stack +information in the location column, with individual stack frames separated +by pipe (``|``) characters: From dd701af4e7061ff30975dd97fe6a61810389bc6c Mon Sep 17 00:00:00 2001 From: Marcin Zalewski Date: Thu, 26 May 2022 02:26:36 -0700 Subject: [PATCH 037/462] Fix the version in setup.py (#381) Co-authored-by: Marcin Zalewski --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 02c84c1ccd..0821012e71 100755 --- a/setup.py +++ b/setup.py @@ -76,7 +76,7 @@ def run(self): sys.argv.remove("--recurse") setup( name="cunumeric", - version="22.03", + version="22.05.00", packages=find_packages( where=".", include=["cunumeric*"], From 52e6224533b5e865835fbe9507cb76a9abbff234 Mon Sep 17 00:00:00 2001 From: Wonchan Lee Date: Wed, 1 Jun 2022 17:49:57 -0700 Subject: [PATCH 038/462] Post-merge clean-up --- src/cunumeric/cudalibs.cu | 2 +- src/cunumeric/cunumeric.cc | 16 ++++---- src/cunumeric/fft/fft.h | 6 +-- src/cunumeric/fft/fft_util.h | 2 +- src/cunumeric/index/advanced_indexing.h | 8 ++-- src/cunumeric/index/choose.h | 6 +-- src/cunumeric/index/repeat.cc | 6 +-- src/cunumeric/index/repeat.cu | 4 +- src/cunumeric/index/repeat.h | 8 ++-- src/cunumeric/index/repeat_omp.cc | 4 +- src/cunumeric/index/repeat_template.inl | 2 +- src/cunumeric/index/zip.h | 6 +-- src/cunumeric/matrix/contract.h | 8 ++-- src/cunumeric/matrix/diag_template.inl | 8 ++-- src/cunumeric/matrix/gemm.h | 2 +- src/cunumeric/matrix/gemm_template.inl | 8 +++- src/cunumeric/matrix/potrf.h | 2 +- src/cunumeric/matrix/potrf_template.inl | 4 +- src/cunumeric/matrix/syrk.h | 2 +- src/cunumeric/matrix/syrk_template.inl | 4 +- src/cunumeric/matrix/trilu.h | 6 +-- src/cunumeric/matrix/trsm.h | 2 +- src/cunumeric/matrix/trsm_template.inl | 4 +- src/cunumeric/nullary/window.h | 2 +- src/cunumeric/nullary/window_template.inl | 2 +- src/cunumeric/nullary/window_util.h | 2 +- src/cunumeric/operators.cc | 4 +- src/cunumeric/set/unique.h | 2 +- src/cunumeric/set/unique_reduce.h | 2 +- src/cunumeric/set/unique_reduce_template.inl | 2 +- src/cunumeric/set/unique_template.inl | 4 +- src/cunumeric/sort/sort.cc | 4 +- src/cunumeric/sort/sort.cu | 39 ++++++++++---------- src/cunumeric/sort/sort.h | 6 +-- src/cunumeric/sort/sort_omp.cc | 4 +- src/cunumeric/unary/unary_op.h | 6 +-- 36 files changed, 102 insertions(+), 97 deletions(-) diff --git a/src/cunumeric/cudalibs.cu b/src/cunumeric/cudalibs.cu index 0848461b33..ffad8e1ad2 100644 --- a/src/cunumeric/cudalibs.cu +++ b/src/cunumeric/cudalibs.cu @@ -14,7 +14,7 @@ * */ -#include "cunumeric.h" +#include "cunumeric/cunumeric_task.h" #include "cudalibs.h" diff --git a/src/cunumeric/cunumeric.cc b/src/cunumeric/cunumeric.cc index fa6dfde018..d60480d3af 100644 --- a/src/cunumeric/cunumeric.cc +++ b/src/cunumeric/cunumeric.cc @@ -25,8 +25,8 @@ namespace cunumeric { static const char* const cunumeric_library_name = "cunumeric"; -/*static*/ bool CuNumeric::has_numamem = false; -/*static*/ MapperID CuNumeric::mapper_id = -1; +/*static*/ bool CuNumeric::has_numamem = false; +/*static*/ Legion::MapperID CuNumeric::mapper_id = -1; /*static*/ LegateTaskRegistrar& CuNumeric::get_registrar() { @@ -64,10 +64,10 @@ void registration_callback(Legion::Machine machine, #endif // Now we can register our mapper with the runtime - auto cunumeric_mapper_id = context->get_mapper_id(0); - auto mapper = new CuNumericMapper(legion_runtime->get_mapper_runtime(), machine, *context); + CuNumeric::mapper_id = context->get_mapper_id(0); + auto mapper = new CuNumericMapper(legion_runtime, machine, *context); // This will register it with all the processors on the node - legion_runtime->add_mapper(cunumeric_mapper_id, mapper); + legion_runtime->add_mapper(CuNumeric::mapper_id, mapper); } void bootstrapping_callback(Legion::Machine machine, @@ -93,9 +93,9 @@ void cunumeric_perform_registration(void) // that this call back is invoked everywhere across all nodes Legion::Runtime::perform_registration_callback(cunumeric::registration_callback, true /*global*/); - Runtime* runtime = Runtime::get_runtime(); - Context ctx = Runtime::get_context(); - Future fut = runtime->select_tunable_value( + Legion::Runtime* runtime = Legion::Runtime::get_runtime(); + Legion::Context ctx = Legion::Runtime::get_context(); + Legion::Future fut = runtime->select_tunable_value( ctx, CUNUMERIC_TUNABLE_HAS_NUMAMEM, cunumeric::CuNumeric::mapper_id); if (fut.get_result() != 0) cunumeric::CuNumeric::has_numamem = true; } diff --git a/src/cunumeric/fft/fft.h b/src/cunumeric/fft/fft.h index 243b597b7b..607a6f8cd6 100644 --- a/src/cunumeric/fft/fft.h +++ b/src/cunumeric/fft/fft.h @@ -16,14 +16,14 @@ #pragma once -#include "cunumeric/cunumeric.h" +#include "cunumeric/cunumeric_task.h" #include "cunumeric/fft/fft_util.h" namespace cunumeric { struct FFTArgs { - Array output; - Array input; + legate::Store output; + legate::Store input; CuNumericFFTType type; CuNumericFFTDirection direction; bool operate_over_axes; diff --git a/src/cunumeric/fft/fft_util.h b/src/cunumeric/fft/fft_util.h index 04428a1abb..b27ef49d0d 100644 --- a/src/cunumeric/fft/fft_util.h +++ b/src/cunumeric/fft/fft_util.h @@ -16,7 +16,7 @@ #pragma once -#include "cunumeric/cunumeric.h" +#include "cunumeric/cunumeric_task.h" namespace cunumeric { diff --git a/src/cunumeric/index/advanced_indexing.h b/src/cunumeric/index/advanced_indexing.h index e375d2a725..b691f445aa 100644 --- a/src/cunumeric/index/advanced_indexing.h +++ b/src/cunumeric/index/advanced_indexing.h @@ -16,14 +16,14 @@ #pragma once -#include "cunumeric/cunumeric.h" +#include "cunumeric/cunumeric_task.h" namespace cunumeric { struct AdvancedIndexingArgs { - Array& output; - const Array& input_array; - const Array& indexing_array; + legate::Store& output; + const legate::Store& input_array; + const legate::Store& indexing_array; const bool is_set; }; diff --git a/src/cunumeric/index/choose.h b/src/cunumeric/index/choose.h index f1fc9ce870..c2aa9259ab 100644 --- a/src/cunumeric/index/choose.h +++ b/src/cunumeric/index/choose.h @@ -16,13 +16,13 @@ #pragma once -#include "cunumeric/cunumeric.h" +#include "cunumeric/cunumeric_task.h" namespace cunumeric { struct ChooseArgs { - const Array& out; - const std::vector& inputs; + const legate::Store& out; + const std::vector& inputs; }; class ChooseTask : public CuNumericTask { diff --git a/src/cunumeric/index/repeat.cc b/src/cunumeric/index/repeat.cc index bfd134cb4a..aab027a58e 100644 --- a/src/cunumeric/index/repeat.cc +++ b/src/cunumeric/index/repeat.cc @@ -26,7 +26,7 @@ template struct RepeatImplBody { using VAL = legate_type_of; - void operator()(Array& out_array, + void operator()(legate::Store& out_array, const AccessorRO& in, const int64_t repeats, const int32_t axis, @@ -50,7 +50,7 @@ struct RepeatImplBody { } } - void operator()(Array& out_array, + void operator()(legate::Store& out_array, const AccessorRO& in, const AccessorRO& repeats, const int32_t axis, @@ -74,7 +74,7 @@ struct RepeatImplBody { } template 1)>* = nullptr> - void operator()(Array& out_array, + void operator()(legate::Store& out_array, const AccessorRO& in, const AccessorRO& repeats, const int32_t axis, diff --git a/src/cunumeric/index/repeat.cu b/src/cunumeric/index/repeat.cu index 09d6c71978..bea4a33e97 100644 --- a/src/cunumeric/index/repeat.cu +++ b/src/cunumeric/index/repeat.cu @@ -99,7 +99,7 @@ template struct RepeatImplBody { using VAL = legate_type_of; - void operator()(Array& out_array, + void operator()(legate::Store& out_array, const AccessorRO& in, const int64_t repeats, const int32_t axis, @@ -122,7 +122,7 @@ struct RepeatImplBody { CHECK_CUDA_STREAM(stream); } - void operator()(Array& out_array, + void operator()(legate::Store& out_array, const AccessorRO& in, const AccessorRO& repeats, const int32_t axis, diff --git a/src/cunumeric/index/repeat.h b/src/cunumeric/index/repeat.h index 8faf4ed533..65633d1244 100644 --- a/src/cunumeric/index/repeat.h +++ b/src/cunumeric/index/repeat.h @@ -16,14 +16,14 @@ #pragma once -#include "cunumeric/cunumeric.h" +#include "cunumeric/cunumeric_task.h" namespace cunumeric { struct RepeatArgs { - Array& output; - const Array& input; - const Array& repeats_arr; + legate::Store& output; + const legate::Store& input; + const legate::Store& repeats_arr; int64_t repeats; int32_t axis; const bool scalar_repeats; diff --git a/src/cunumeric/index/repeat_omp.cc b/src/cunumeric/index/repeat_omp.cc index 823a1a16a4..0d9efe471d 100644 --- a/src/cunumeric/index/repeat_omp.cc +++ b/src/cunumeric/index/repeat_omp.cc @@ -31,7 +31,7 @@ template struct RepeatImplBody { using VAL = legate_type_of; - void operator()(Array& out_array, + void operator()(legate::Store& out_array, const AccessorRO& in, const int64_t repeats, const int32_t axis, @@ -56,7 +56,7 @@ struct RepeatImplBody { } } - void operator()(Array& out_array, + void operator()(legate::Store& out_array, const AccessorRO& in, const AccessorRO& repeats, const int32_t axis, diff --git a/src/cunumeric/index/repeat_template.inl b/src/cunumeric/index/repeat_template.inl index 922c95eb65..93b46d36cf 100644 --- a/src/cunumeric/index/repeat_template.inl +++ b/src/cunumeric/index/repeat_template.inl @@ -58,7 +58,7 @@ static void repeat_template(TaskContext& context) if (scalar_repeats) { auto repeats = context.scalars()[2].value(); RepeatArgs args{ - context.outputs()[0], context.inputs()[0], Array(), repeats, axis, scalar_repeats}; + context.outputs()[0], context.inputs()[0], legate::Store(), repeats, axis, scalar_repeats}; double_dispatch(args.input.dim(), args.input.code(), RepeatImpl{}, args); } else { auto& repeats = context.inputs()[1]; diff --git a/src/cunumeric/index/zip.h b/src/cunumeric/index/zip.h index 4cd5f1b4d8..3cbd4c62e1 100644 --- a/src/cunumeric/index/zip.h +++ b/src/cunumeric/index/zip.h @@ -16,13 +16,13 @@ #pragma once -#include "cunumeric/cunumeric.h" +#include "cunumeric/cunumeric_task.h" namespace cunumeric { struct ZipArgs { - const Array& out; - const std::vector& inputs; + const legate::Store& out; + const std::vector& inputs; const int64_t N; const int64_t key_dim; const int64_t start_index; diff --git a/src/cunumeric/matrix/contract.h b/src/cunumeric/matrix/contract.h index 740b304941..a2d72de0b3 100644 --- a/src/cunumeric/matrix/contract.h +++ b/src/cunumeric/matrix/contract.h @@ -16,14 +16,14 @@ #pragma once -#include "cunumeric/cunumeric.h" +#include "cunumeric/cunumeric_task.h" namespace cunumeric { struct ContractArgs { - const Array& lhs; - const Array& rhs1; - const Array& rhs2; + const legate::Store& lhs; + const legate::Store& rhs1; + const legate::Store& rhs2; legate::Span lhs_dim_mask; legate::Span rhs1_dim_mask; legate::Span rhs2_dim_mask; diff --git a/src/cunumeric/matrix/diag_template.inl b/src/cunumeric/matrix/diag_template.inl index 85ec8d71ba..a3dbb60f1d 100644 --- a/src/cunumeric/matrix/diag_template.inl +++ b/src/cunumeric/matrix/diag_template.inl @@ -98,10 +98,10 @@ struct DiagImpl { template static void diag_template(TaskContext& context) { - int naxes = context.scalars()[0].value(); - bool extract = context.scalars()[1].value(); - Array& matrix = extract ? context.inputs()[0] : context.outputs()[0]; - Array& diag = extract ? context.reductions()[0] : context.inputs()[0]; + int naxes = context.scalars()[0].value(); + bool extract = context.scalars()[1].value(); + legate::Store& matrix = extract ? context.inputs()[0] : context.outputs()[0]; + legate::Store& diag = extract ? context.reductions()[0] : context.inputs()[0]; DiagArgs args{naxes, extract, matrix, diag}; double_dispatch(matrix.dim(), matrix.code(), DiagImpl{}, args); } diff --git a/src/cunumeric/matrix/gemm.h b/src/cunumeric/matrix/gemm.h index 2bc541730a..5a460091c6 100644 --- a/src/cunumeric/matrix/gemm.h +++ b/src/cunumeric/matrix/gemm.h @@ -16,7 +16,7 @@ #pragma once -#include "cunumeric/cunumeric.h" +#include "cunumeric/cunumeric_task.h" namespace cunumeric { diff --git a/src/cunumeric/matrix/gemm_template.inl b/src/cunumeric/matrix/gemm_template.inl index 5148b1c78f..eebca39cfc 100644 --- a/src/cunumeric/matrix/gemm_template.inl +++ b/src/cunumeric/matrix/gemm_template.inl @@ -41,7 +41,9 @@ struct support_gemm : std::true_type { template struct GemmImpl { template ::value>* = nullptr> - void operator()(Array& lhs_array, Array& rhs1_array, Array& rhs2_array) const + void operator()(legate::Store& lhs_array, + legate::Store& rhs1_array, + legate::Store& rhs2_array) const { using VAL = legate_type_of; @@ -69,7 +71,9 @@ struct GemmImpl { } template ::value>* = nullptr> - void operator()(Array& lhs_array, Array& rhs1_array, Array& rhs2_array) const + void operator()(legate::Store& lhs_array, + legate::Store& rhs1_array, + legate::Store& rhs2_array) const { assert(false); } diff --git a/src/cunumeric/matrix/potrf.h b/src/cunumeric/matrix/potrf.h index 6b814561f5..c12108a529 100644 --- a/src/cunumeric/matrix/potrf.h +++ b/src/cunumeric/matrix/potrf.h @@ -16,7 +16,7 @@ #pragma once -#include "cunumeric/cunumeric.h" +#include "cunumeric/cunumeric_task.h" namespace cunumeric { diff --git a/src/cunumeric/matrix/potrf_template.inl b/src/cunumeric/matrix/potrf_template.inl index 83d5fd2a13..80bd15bf24 100644 --- a/src/cunumeric/matrix/potrf_template.inl +++ b/src/cunumeric/matrix/potrf_template.inl @@ -41,7 +41,7 @@ struct support_potrf : std::true_type { template struct PotrfImpl { template ::value>* = nullptr> - void operator()(Array& array) const + void operator()(legate::Store& array) const { using VAL = legate_type_of; @@ -60,7 +60,7 @@ struct PotrfImpl { } template ::value>* = nullptr> - void operator()(Array& array) const + void operator()(legate::Store& array) const { assert(false); } diff --git a/src/cunumeric/matrix/syrk.h b/src/cunumeric/matrix/syrk.h index b3c7bf0be0..52f7d193e3 100644 --- a/src/cunumeric/matrix/syrk.h +++ b/src/cunumeric/matrix/syrk.h @@ -16,7 +16,7 @@ #pragma once -#include "cunumeric/cunumeric.h" +#include "cunumeric/cunumeric_task.h" namespace cunumeric { diff --git a/src/cunumeric/matrix/syrk_template.inl b/src/cunumeric/matrix/syrk_template.inl index cfc633c6c2..c6e04fc7fe 100644 --- a/src/cunumeric/matrix/syrk_template.inl +++ b/src/cunumeric/matrix/syrk_template.inl @@ -41,7 +41,7 @@ struct support_syrk : std::true_type { template struct SyrkImpl { template ::value>* = nullptr> - void operator()(Array& lhs_array, Array& rhs_array) const + void operator()(legate::Store& lhs_array, legate::Store& rhs_array) const { using VAL = legate_type_of; @@ -64,7 +64,7 @@ struct SyrkImpl { } template ::value>* = nullptr> - void operator()(Array& lhs_array, Array& rhs_array) const + void operator()(legate::Store& lhs_array, legate::Store& rhs_array) const { assert(false); } diff --git a/src/cunumeric/matrix/trilu.h b/src/cunumeric/matrix/trilu.h index 0d9b80b6ac..a7e5ed6e57 100644 --- a/src/cunumeric/matrix/trilu.h +++ b/src/cunumeric/matrix/trilu.h @@ -16,15 +16,15 @@ #pragma once -#include "cunumeric/cunumeric.h" +#include "cunumeric/cunumeric_task.h" namespace cunumeric { struct TriluArgs { bool lower; int32_t k; - const Array& output; - const Array& input; + const legate::Store& output; + const legate::Store& input; }; class TriluTask : public CuNumericTask { diff --git a/src/cunumeric/matrix/trsm.h b/src/cunumeric/matrix/trsm.h index 82f7660bb3..bc54863c3a 100644 --- a/src/cunumeric/matrix/trsm.h +++ b/src/cunumeric/matrix/trsm.h @@ -16,7 +16,7 @@ #pragma once -#include "cunumeric/cunumeric.h" +#include "cunumeric/cunumeric_task.h" namespace cunumeric { diff --git a/src/cunumeric/matrix/trsm_template.inl b/src/cunumeric/matrix/trsm_template.inl index 8ce15affdc..f573ecaba6 100644 --- a/src/cunumeric/matrix/trsm_template.inl +++ b/src/cunumeric/matrix/trsm_template.inl @@ -41,7 +41,7 @@ struct support_trsm : std::true_type { template struct TrsmImpl { template ::value>* = nullptr> - void operator()(Array& lhs_array, Array& rhs_array) const + void operator()(legate::Store& lhs_array, legate::Store& rhs_array) const { using VAL = legate_type_of; @@ -64,7 +64,7 @@ struct TrsmImpl { } template ::value>* = nullptr> - void operator()(Array& lhs_array, Array& rhs_array) const + void operator()(legate::Store& lhs_array, legate::Store& rhs_array) const { assert(false); } diff --git a/src/cunumeric/nullary/window.h b/src/cunumeric/nullary/window.h index 5dc398bd33..8d585a98f7 100644 --- a/src/cunumeric/nullary/window.h +++ b/src/cunumeric/nullary/window.h @@ -16,7 +16,7 @@ #pragma once -#include "cunumeric/cunumeric.h" +#include "cunumeric/cunumeric_task.h" namespace cunumeric { diff --git a/src/cunumeric/nullary/window_template.inl b/src/cunumeric/nullary/window_template.inl index 2374bde009..034e57abe5 100644 --- a/src/cunumeric/nullary/window_template.inl +++ b/src/cunumeric/nullary/window_template.inl @@ -27,7 +27,7 @@ struct WindowImplBody; template struct WindowImpl { template - void operator()(Array& output, int64_t M, double beta) const + void operator()(legate::Store& output, int64_t M, double beta) const { auto rect = output.shape<1>(); diff --git a/src/cunumeric/nullary/window_util.h b/src/cunumeric/nullary/window_util.h index 8c46fd335a..c7632dcfb8 100644 --- a/src/cunumeric/nullary/window_util.h +++ b/src/cunumeric/nullary/window_util.h @@ -18,7 +18,7 @@ #define _USE_MATH_DEFINES -#include "cunumeric/cunumeric.h" +#include "cunumeric/cunumeric_task.h" #include extern double i0(double); diff --git a/src/cunumeric/operators.cc b/src/cunumeric/operators.cc index 61f13ea56b..9147d01455 100644 --- a/src/cunumeric/operators.cc +++ b/src/cunumeric/operators.cc @@ -86,7 +86,7 @@ ArrayP dot(ArrayP rhs1, ArrayP rhs2) { if (rhs1->dim() != 2 || rhs2->dim() != 2) { fprintf(stderr, "cunumeric::dot only supports matrices now"); - LEGATE_ABORT + LEGATE_ABORT; } auto& rhs1_shape = rhs1->shape(); @@ -99,7 +99,7 @@ ArrayP dot(ArrayP rhs1, ArrayP rhs2) rhs1_shape[1], rhs2_shape[0], rhs2_shape[1]); - LEGATE_ABORT + LEGATE_ABORT; } auto runtime = CuNumericRuntime::get_runtime(); diff --git a/src/cunumeric/set/unique.h b/src/cunumeric/set/unique.h index 5975a5b162..ebbe7a8096 100644 --- a/src/cunumeric/set/unique.h +++ b/src/cunumeric/set/unique.h @@ -16,7 +16,7 @@ #pragma once -#include "cunumeric/cunumeric.h" +#include "cunumeric/cunumeric_task.h" namespace cunumeric { diff --git a/src/cunumeric/set/unique_reduce.h b/src/cunumeric/set/unique_reduce.h index a878074461..e315f6d75a 100644 --- a/src/cunumeric/set/unique_reduce.h +++ b/src/cunumeric/set/unique_reduce.h @@ -16,7 +16,7 @@ #pragma once -#include "cunumeric/cunumeric.h" +#include "cunumeric/cunumeric_task.h" namespace cunumeric { diff --git a/src/cunumeric/set/unique_reduce_template.inl b/src/cunumeric/set/unique_reduce_template.inl index c33e833f33..1989a8caa2 100644 --- a/src/cunumeric/set/unique_reduce_template.inl +++ b/src/cunumeric/set/unique_reduce_template.inl @@ -27,7 +27,7 @@ struct UniqueReduceImplBody; template struct UniqueReduceImpl { template - void operator()(Array& output, std::vector& input_arrs) + void operator()(legate::Store& output, std::vector& input_arrs) { using VAL = legate_type_of; diff --git a/src/cunumeric/set/unique_template.inl b/src/cunumeric/set/unique_template.inl index 24ca4293d0..70c44fb9a6 100644 --- a/src/cunumeric/set/unique_template.inl +++ b/src/cunumeric/set/unique_template.inl @@ -27,8 +27,8 @@ struct UniqueImplBody; template struct UniqueImpl { template - void operator()(Array& output, - Array& input, + void operator()(legate::Store& output, + legate::Store& input, std::vector& comms, const DomainPoint& point, const Domain& launch_domain) const diff --git a/src/cunumeric/sort/sort.cc b/src/cunumeric/sort/sort.cc index 5178078afc..9a4014e7c1 100644 --- a/src/cunumeric/sort/sort.cc +++ b/src/cunumeric/sort/sort.cc @@ -60,8 +60,8 @@ struct SortImplBody { } } - void operator()(const Array& input_array, - Array& output_array, + void operator()(const legate::Store& input_array, + legate::Store& output_array, const Pitches& pitches, const Rect& rect, const size_t volume, diff --git a/src/cunumeric/sort/sort.cu b/src/cunumeric/sort/sort.cu index e666b989ff..1d79901340 100644 --- a/src/cunumeric/sort/sort.cu +++ b/src/cunumeric/sort/sort.cu @@ -1245,23 +1245,24 @@ void rebalance_data(SegmentMergePiece& merge_buffer, ///////////////////////////////////////////////////////////////////////////////////////////////// template -void sample_sort_nccl_nd(SortPiece> local_sorted, - Array& output_array_unbound, // only for unbound usage when !rebalance - void* output_ptr, - /* global domain information */ - size_t my_rank, // global NCCL rank - size_t num_ranks, - size_t segment_size_g, - /* domain information in sort dimension */ - size_t my_sort_rank, // local rank id in sort dimension - size_t num_sort_ranks, // #ranks that share a sort dimension - size_t* sort_ranks, // rank ids that share a sort dimension with us - size_t segment_size_l, // (local) segment size - /* other */ - bool rebalance, - bool argsort, - cudaStream_t stream, - ncclComm_t* comm) +void sample_sort_nccl_nd( + SortPiece> local_sorted, + legate::Store& output_array_unbound, // only for unbound usage when !rebalance + void* output_ptr, + /* global domain information */ + size_t my_rank, // global NCCL rank + size_t num_ranks, + size_t segment_size_g, + /* domain information in sort dimension */ + size_t my_sort_rank, // local rank id in sort dimension + size_t num_sort_ranks, // #ranks that share a sort dimension + size_t* sort_ranks, // rank ids that share a sort dimension with us + size_t segment_size_l, // (local) segment size + /* other */ + bool rebalance, + bool argsort, + cudaStream_t stream, + ncclComm_t* comm) { using VAL = legate_type_of; @@ -1712,8 +1713,8 @@ template struct SortImplBody { using VAL = legate_type_of; - void operator()(const Array& input_array, - Array& output_array, + void operator()(const legate::Store& input_array, + legate::Store& output_array, const Pitches& pitches, const Rect& rect, const size_t volume, diff --git a/src/cunumeric/sort/sort.h b/src/cunumeric/sort/sort.h index 83912ecfc1..5f8d294a2a 100644 --- a/src/cunumeric/sort/sort.h +++ b/src/cunumeric/sort/sort.h @@ -16,13 +16,13 @@ #pragma once -#include "cunumeric/cunumeric.h" +#include "cunumeric/cunumeric_task.h" namespace cunumeric { struct SortArgs { - const Array& input; - Array& output; + const legate::Store& input; + legate::Store& output; bool argsort; bool stable; size_t segment_size_g; diff --git a/src/cunumeric/sort/sort_omp.cc b/src/cunumeric/sort/sort_omp.cc index b772832c27..e24a245684 100644 --- a/src/cunumeric/sort/sort_omp.cc +++ b/src/cunumeric/sort/sort_omp.cc @@ -61,8 +61,8 @@ struct SortImplBody { } } - void operator()(const Array& input_array, - Array& output_array, + void operator()(const legate::Store& input_array, + legate::Store& output_array, const Pitches& pitches, const Rect& rect, const size_t volume, diff --git a/src/cunumeric/unary/unary_op.h b/src/cunumeric/unary/unary_op.h index 6217c57762..58b616f97d 100644 --- a/src/cunumeric/unary/unary_op.h +++ b/src/cunumeric/unary/unary_op.h @@ -29,9 +29,9 @@ struct UnaryOpArgs { }; struct MultiOutUnaryOpArgs { - const Array& in; - const Array& out1; - const Array& out2; + const legate::Store& in; + const legate::Store& out1; + const legate::Store& out2; UnaryOpCode op_code; }; From 3f8ec61b60b72b6c873a3652d6e2c70c019555d1 Mon Sep 17 00:00:00 2001 From: Wonchan Lee Date: Thu, 2 Jun 2022 20:26:24 -0700 Subject: [PATCH 039/462] Don't run the resolution logic if the arrays have the same dtype (#390) --- cunumeric/_ufunc/ufunc.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/cunumeric/_ufunc/ufunc.py b/cunumeric/_ufunc/ufunc.py index 64dec6b61b..fd8d6c3701 100644 --- a/cunumeric/_ufunc/ufunc.py +++ b/cunumeric/_ufunc/ufunc.py @@ -470,6 +470,13 @@ def __init__( @staticmethod def _find_common_type(arrs, orig_args): + all_ndarray = all(isinstance(arg, ndarray) for arg in orig_args) + unique_dtypes = set(arr.dtype for arr in arrs) + # If all operands are ndarrays and they all have the same dtype, + # we already know the common dtype + if len(unique_dtypes) == 1 and all_ndarray: + return arrs[0].dtype + # FIXME: The following is a miserable attempt to implement type # coercion rules that try to match NumPy's rules for a subset of cases; # for the others, cuNumeric computes a type different from what From e8de40d4843dd96a19eba7d3a960b699e97eed7c Mon Sep 17 00:00:00 2001 From: Marcin Zalewski Date: Wed, 15 Jun 2022 18:13:35 -0700 Subject: [PATCH 040/462] Update conda requirements (#383) (#406) * Add requirement for our minimum supported NumPy version * Add runtime dependency on minimum CTK version Co-authored-by: Manolis Papadakis --- conda/conda-build/meta.yaml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/conda/conda-build/meta.yaml b/conda/conda-build/meta.yaml index 412e5171fc..d6bfab9c77 100644 --- a/conda/conda-build/meta.yaml +++ b/conda/conda-build/meta.yaml @@ -90,9 +90,10 @@ requirements: {% endif %} run: - - numpy + - numpy >=1.22 - libopenblas =* =*openmp* {% if gpu_enabled_bool %} + - cuda-cudart >={{ cuda_version }} # - libcutensor >=1.3 - cutensor >=1.3 - libcublas @@ -102,7 +103,7 @@ requirements: - opt_einsum >=3.3 - scipy - typing_extensions - + run_constrained: {% if gpu_enabled_bool %} - __cuda >=11.4 From 9ec548d21dce1583665a37d4131403f1a2630442 Mon Sep 17 00:00:00 2001 From: Marcin Zalewski Date: Wed, 15 Jun 2022 18:14:36 -0700 Subject: [PATCH 041/462] Set cuda virtual package as hard run requirement for conda gpu package (#398) (#407) Co-authored-by: Mark Vaz --- conda/conda-build/meta.yaml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/conda/conda-build/meta.yaml b/conda/conda-build/meta.yaml index d6bfab9c77..5cad7d6fd5 100644 --- a/conda/conda-build/meta.yaml +++ b/conda/conda-build/meta.yaml @@ -104,10 +104,11 @@ requirements: - scipy - typing_extensions - run_constrained: {% if gpu_enabled_bool %} - __cuda >=11.4 {% endif %} + + run_constrained: - __glibc >=2.17 # [linux] about: From 3572dd35eb5b4b24bb02a543c43f7f95d9851b19 Mon Sep 17 00:00:00 2001 From: Marcin Zalewski Date: Wed, 15 Jun 2022 18:14:53 -0700 Subject: [PATCH 042/462] Fix nargs for report:dump-csv (#400) (#408) Co-authored-by: Bryan Van de Ven --- cunumeric/runtime.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cunumeric/runtime.py b/cunumeric/runtime.py index 62c3b0f4f7..81374bec08 100644 --- a/cunumeric/runtime.py +++ b/cunumeric/runtime.py @@ -107,7 +107,7 @@ ArgSpec( action="store", type=str, - nargs=1, + nargs="?", default=None, dest="report_dump_csv", help="Save a coverage report to a specified CSV file", From 7fcbf604fc2f70844de3cadd22e69d262b96d80b Mon Sep 17 00:00:00 2001 From: Marcin Zalewski Date: Thu, 16 Jun 2022 13:37:51 -0700 Subject: [PATCH 043/462] Release 22.05.01 --- conda/conda-build/meta.yaml | 8 +++++--- cunumeric/runtime.py | 2 +- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/conda/conda-build/meta.yaml b/conda/conda-build/meta.yaml index 412e5171fc..5cad7d6fd5 100644 --- a/conda/conda-build/meta.yaml +++ b/conda/conda-build/meta.yaml @@ -90,9 +90,10 @@ requirements: {% endif %} run: - - numpy + - numpy >=1.22 - libopenblas =* =*openmp* {% if gpu_enabled_bool %} + - cuda-cudart >={{ cuda_version }} # - libcutensor >=1.3 - cutensor >=1.3 - libcublas @@ -102,11 +103,12 @@ requirements: - opt_einsum >=3.3 - scipy - typing_extensions - - run_constrained: + {% if gpu_enabled_bool %} - __cuda >=11.4 {% endif %} + + run_constrained: - __glibc >=2.17 # [linux] about: diff --git a/cunumeric/runtime.py b/cunumeric/runtime.py index 62c3b0f4f7..81374bec08 100644 --- a/cunumeric/runtime.py +++ b/cunumeric/runtime.py @@ -107,7 +107,7 @@ ArgSpec( action="store", type=str, - nargs=1, + nargs="?", default=None, dest="report_dump_csv", help="Save a coverage report to a specified CSV file", From d46233ccae681711c1142fea9d8581c0b24060de Mon Sep 17 00:00:00 2001 From: Mark Vaz Date: Tue, 21 Jun 2022 03:39:10 -0700 Subject: [PATCH 044/462] Re-freezing conda compiler versions (#415) --- conda/conda-build/meta.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/conda/conda-build/meta.yaml b/conda/conda-build/meta.yaml index 5cad7d6fd5..5a7bbd0235 100644 --- a/conda/conda-build/meta.yaml +++ b/conda/conda-build/meta.yaml @@ -65,8 +65,8 @@ build: requirements: build: - - {{ compiler('c') }} - - {{ compiler('cxx') }} + - {{ compiler('c') }} =11.2 + - {{ compiler('cxx') }} =11.2 - make {% if gpu_enabled_bool %} - cuda-nvcc ={{ cuda_version }} From aba7fda4e6d8ae18ffed8ca98c9f6bd11e00087b Mon Sep 17 00:00:00 2001 From: Wonchan Lee Date: Tue, 21 Jun 2022 23:13:41 -0700 Subject: [PATCH 045/462] Post-merge clean-up --- src/cunumeric.mk | 3 +-- src/cunumeric/index/advanced_indexing.cc | 2 +- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/src/cunumeric.mk b/src/cunumeric.mk index 7925130884..0aa2df1e34 100644 --- a/src/cunumeric.mk +++ b/src/cunumeric.mk @@ -56,8 +56,7 @@ GEN_CPU_SRC += cunumeric/ternary/where.cc \ cunumeric/array.cc \ cunumeric/mapper.cc \ cunumeric/operators.cc \ - cunumeric/runtime.cc \ - cunumeric/mapper.cc + cunumeric/runtime.cc GEN_CPU_SRC += cunumeric/cephes/chbevl.cc \ cunumeric/cephes/i0.cc diff --git a/src/cunumeric/index/advanced_indexing.cc b/src/cunumeric/index/advanced_indexing.cc index 5bbbff3760..f60b457bea 100644 --- a/src/cunumeric/index/advanced_indexing.cc +++ b/src/cunumeric/index/advanced_indexing.cc @@ -57,7 +57,7 @@ struct AdvancedIndexingImplBody { } } - void operator()(Array& out_arr, + void operator()(legate::Store& out_arr, const AccessorRO& input, const AccessorRO& index, const Pitches& pitches, From 90c8285b809ca397d12c5e7ce47d07a968640844 Mon Sep 17 00:00:00 2001 From: Wonchan Lee Date: Tue, 21 Jun 2022 23:35:32 -0700 Subject: [PATCH 046/462] Catch up the ABI change and construct scalars directly from vectors --- src/cunumeric/array.cc | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/src/cunumeric/array.cc b/src/cunumeric/array.cc index b941b1f01a..49b29b7b4e 100644 --- a/src/cunumeric/array.cc +++ b/src/cunumeric/array.cc @@ -54,13 +54,8 @@ void Array::random(int32_t gen_code) task->add_output(store_, p_lhs); task->add_scalar_arg(legate::Scalar(static_cast(RandGenCode::UNIFORM))); task->add_scalar_arg(legate::Scalar(runtime_->get_next_random_epoch())); - auto strides = compute_strides(shape()); - void* buffer = malloc(strides.size() * sizeof(int64_t) + sizeof(uint32_t)); - *static_cast(buffer) = strides.size(); - memcpy(static_cast(buffer) + sizeof(uint32_t), - strides.data(), - strides.size() * sizeof(int64_t)); - task->add_scalar_arg(legate::Scalar(true, legate::LegateTypeCode::INT64_LT, buffer)); + auto strides = compute_strides(shape()); + task->add_scalar_arg(legate::Scalar(strides)); runtime_->submit(std::move(task)); } @@ -132,6 +127,7 @@ void Array::unary_reduction(int32_t op_code_, std::shared_ptr input) task->add_reduction(store_, redop, p_out); task->add_input(input->store_, p_in); task->add_scalar_arg(legate::Scalar(op_code_)); + task->add_scalar_arg(legate::Scalar(input->shape())); runtime_->submit(std::move(task)); } From c69ea1e87093bcfe5c38ae5d9e51a5c0aea4b184 Mon Sep 17 00:00:00 2001 From: Wonchan Lee Date: Wed, 27 Jul 2022 22:32:38 -0700 Subject: [PATCH 047/462] Post merge cleanup --- src/cunumeric/bits/packbits.h | 2 +- src/cunumeric/bits/packbits_template.inl | 4 ++-- src/cunumeric/bits/unpackbits.h | 2 +- src/cunumeric/bits/unpackbits_template.inl | 4 ++-- src/cunumeric/index/advanced_indexing.cu | 2 +- src/cunumeric/index/advanced_indexing_omp.cc | 2 +- src/cunumeric/random/bitgenerator.h | 4 ++-- src/cunumeric/random/bitgenerator_util.h | 2 +- src/cunumeric/sort/searchsorted.cc | 6 +++--- src/cunumeric/sort/searchsorted.cu | 6 +++--- src/cunumeric/sort/searchsorted.h | 8 ++++---- src/cunumeric/sort/searchsorted_omp.cc | 6 +++--- 12 files changed, 24 insertions(+), 24 deletions(-) diff --git a/src/cunumeric/bits/packbits.h b/src/cunumeric/bits/packbits.h index 03dece2b3e..0760724d28 100644 --- a/src/cunumeric/bits/packbits.h +++ b/src/cunumeric/bits/packbits.h @@ -16,7 +16,7 @@ #pragma once -#include "cunumeric/cunumeric.h" +#include "cunumeric/cunumeric_task.h" #include "cunumeric/bits/bits_util.h" namespace cunumeric { diff --git a/src/cunumeric/bits/packbits_template.inl b/src/cunumeric/bits/packbits_template.inl index 66e368b88d..4166698cb1 100644 --- a/src/cunumeric/bits/packbits_template.inl +++ b/src/cunumeric/bits/packbits_template.inl @@ -31,7 +31,7 @@ struct PackbitsImplBody; template struct PackbitsImpl { template ::value>* = nullptr> - void operator()(Array& output, Array& input, uint32_t axis) const + void operator()(Store& output, Store& input, uint32_t axis) const { using VAL = legate_type_of; @@ -78,7 +78,7 @@ struct PackbitsImpl { template ::value>* = nullptr> - void operator()(Array& output, Array& input, uint32_t axis) const + void operator()(Store& output, Store& input, uint32_t axis) const { // Unreachable assert(false); diff --git a/src/cunumeric/bits/unpackbits.h b/src/cunumeric/bits/unpackbits.h index eb3a45a32a..898390ee59 100644 --- a/src/cunumeric/bits/unpackbits.h +++ b/src/cunumeric/bits/unpackbits.h @@ -16,7 +16,7 @@ #pragma once -#include "cunumeric/cunumeric.h" +#include "cunumeric/cunumeric_task.h" #include "cunumeric/bits/bits_util.h" namespace cunumeric { diff --git a/src/cunumeric/bits/unpackbits_template.inl b/src/cunumeric/bits/unpackbits_template.inl index 53c27f0728..d6473fd3ae 100644 --- a/src/cunumeric/bits/unpackbits_template.inl +++ b/src/cunumeric/bits/unpackbits_template.inl @@ -31,7 +31,7 @@ struct UnpackbitsImplBody; template struct UnpackbitsImpl { template - void operator()(Array& output, Array& input, uint32_t axis) const + void operator()(Store& output, Store& input, uint32_t axis) const { auto out_rect = output.shape(); @@ -51,7 +51,7 @@ struct UnpackbitsImpl { template ::value>* = nullptr> - void operator()(Array& output, Array& input, uint32_t axis) const + void operator()(Store& output, Store& input, uint32_t axis) const { // Unreachable assert(false); diff --git a/src/cunumeric/index/advanced_indexing.cu b/src/cunumeric/index/advanced_indexing.cu index a5217b2123..bc17ed2b08 100644 --- a/src/cunumeric/index/advanced_indexing.cu +++ b/src/cunumeric/index/advanced_indexing.cu @@ -112,7 +112,7 @@ struct AdvancedIndexingImplBody { return size.read(); } - void operator()(Array& out_arr, + void operator()(Store& out_arr, const AccessorRO& input, const AccessorRO& index, const Pitches& pitches, diff --git a/src/cunumeric/index/advanced_indexing_omp.cc b/src/cunumeric/index/advanced_indexing_omp.cc index b78d3e8260..7ca21ceb44 100644 --- a/src/cunumeric/index/advanced_indexing_omp.cc +++ b/src/cunumeric/index/advanced_indexing_omp.cc @@ -83,7 +83,7 @@ struct AdvancedIndexingImplBody { return size; } - void operator()(Array& out_arr, + void operator()(Store& out_arr, const AccessorRO& input, const AccessorRO& index, const Pitches& pitches, diff --git a/src/cunumeric/random/bitgenerator.h b/src/cunumeric/random/bitgenerator.h index af729e3b42..b1b362ac68 100644 --- a/src/cunumeric/random/bitgenerator.h +++ b/src/cunumeric/random/bitgenerator.h @@ -16,7 +16,7 @@ #pragma once -#include "cunumeric/cunumeric.h" +#include "cunumeric/cunumeric_task.h" #include "cunumeric/random/bitgenerator_util.h" namespace cunumeric { @@ -89,4 +89,4 @@ class BitGeneratorTask : public CuNumericTask { #endif }; -} // namespace cunumeric \ No newline at end of file +} // namespace cunumeric diff --git a/src/cunumeric/random/bitgenerator_util.h b/src/cunumeric/random/bitgenerator_util.h index dadb980e59..09c652e310 100644 --- a/src/cunumeric/random/bitgenerator_util.h +++ b/src/cunumeric/random/bitgenerator_util.h @@ -16,7 +16,7 @@ #pragma once -#include "cunumeric/cunumeric.h" +#include "cunumeric/cunumeric_task.h" namespace cunumeric { diff --git a/src/cunumeric/sort/searchsorted.cc b/src/cunumeric/sort/searchsorted.cc index a0cbe094e5..65faffab46 100644 --- a/src/cunumeric/sort/searchsorted.cc +++ b/src/cunumeric/sort/searchsorted.cc @@ -26,9 +26,9 @@ template struct SearchSortedImplBody { using VAL = legate_type_of; - void operator()(const Array& input_array, - const Array& input_values, - const Array& output_positions, + void operator()(const Store& input_array, + const Store& input_values, + const Store& output_positions, const Rect<1>& rect_base, const Rect& rect_values, const Pitches pitches, diff --git a/src/cunumeric/sort/searchsorted.cu b/src/cunumeric/sort/searchsorted.cu index c62892e8cf..90d7b73d4a 100644 --- a/src/cunumeric/sort/searchsorted.cu +++ b/src/cunumeric/sort/searchsorted.cu @@ -68,9 +68,9 @@ template struct SearchSortedImplBody { using VAL = legate_type_of; - void operator()(const Array& input_array, - const Array& input_values, - const Array& output_positions, + void operator()(const Store& input_array, + const Store& input_values, + const Store& output_positions, const Rect<1>& rect_base, const Rect& rect_values, const Pitches pitches, diff --git a/src/cunumeric/sort/searchsorted.h b/src/cunumeric/sort/searchsorted.h index a9aae1d31e..df5e9cc661 100644 --- a/src/cunumeric/sort/searchsorted.h +++ b/src/cunumeric/sort/searchsorted.h @@ -16,14 +16,14 @@ #pragma once -#include "cunumeric/cunumeric.h" +#include "cunumeric/cunumeric_task.h" namespace cunumeric { struct SearchSortedArgs { - const Array& input_base; - const Array& input_values; - const Array& output_reduction; + const legate::Store& input_base; + const legate::Store& input_values; + const legate::Store& output_reduction; bool left; int64_t global_volume; bool is_index_space; diff --git a/src/cunumeric/sort/searchsorted_omp.cc b/src/cunumeric/sort/searchsorted_omp.cc index 705f4922d9..229b297373 100644 --- a/src/cunumeric/sort/searchsorted_omp.cc +++ b/src/cunumeric/sort/searchsorted_omp.cc @@ -28,9 +28,9 @@ template struct SearchSortedImplBody { using VAL = legate_type_of; - void operator()(const Array& input_array, - const Array& input_values, - const Array& output_positions, + void operator()(const Store& input_array, + const Store& input_values, + const Store& output_positions, const Rect<1>& rect_base, const Rect& rect_values, const Pitches pitches, From 8d122d8defa59ef97d3b39d585c919d9f597b607 Mon Sep 17 00:00:00 2001 From: Marcin Zalewski Date: Fri, 5 Aug 2022 15:24:56 -0700 Subject: [PATCH 048/462] Add support for curand conda package build (#510) (#512) Conda packages now build with support for curand both in the CPU and the GPU builds. Co-authored-by: Marcin Zalewski --- conda/conda-build/build.sh | 3 +++ conda/conda-build/meta.yaml | 9 ++++++++- 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/conda/conda-build/build.sh b/conda/conda-build/build.sh index 3ad75f4329..9899bd35c8 100644 --- a/conda/conda-build/build.sh +++ b/conda/conda-build/build.sh @@ -4,6 +4,9 @@ install_args=() if [ -z "$CPU_ONLY" ]; then # cutensor, relying on the conda cutensor package install_args+=("--with-cutensor" "$PREFIX") +else + # When we build without cuda, we need to provide the location of curand + install_args+=("--with-curand" "$PREFIX") fi # location of legate-core diff --git a/conda/conda-build/meta.yaml b/conda/conda-build/meta.yaml index a968e1c1ec..e63496de1f 100644 --- a/conda/conda-build/meta.yaml +++ b/conda/conda-build/meta.yaml @@ -72,6 +72,12 @@ requirements: - cuda-nvcc ={{ cuda_version }} {% endif %} host: + # the nvcc requirement is necessary because it contains crt/host_config.h used by cuda runtime. This is a packaging bug that has been reported. + - cuda-nvcc ={{ cuda_version }} + # libcurand is used both in CPU and GPU builds + - libcurand-dev + # cudart needed for CPU and GPU builds because of curand + - cuda-cudart-dev ={{ cuda_version }} - python - openblas =* =*openmp* {% if not gpu_enabled_bool %} @@ -79,7 +85,6 @@ requirements: {% else %} - legate-core >={{ core_version }} - cuda-driver-dev ={{ cuda_version }} - - cuda-cudart-dev ={{ cuda_version }} # - libcutensor-dev >=1.3 - cutensor >=1.3 - cuda-nvtx @@ -99,6 +104,8 @@ requirements: - libcublas - libcusolver - libcufft + # libcurand only enabled for a GPU package, include-only for CPU package + - libcurand {% endif %} - opt_einsum >=3.3 - scipy From c8e4826371a9193624638d2279f26266f227d21c Mon Sep 17 00:00:00 2001 From: Marcin Zalewski Date: Fri, 5 Aug 2022 15:34:59 -0700 Subject: [PATCH 049/462] Update version (#513) Co-authored-by: Marcin Zalewski --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 8d37a2de62..d92774afdc 100755 --- a/setup.py +++ b/setup.py @@ -76,7 +76,7 @@ def run(self): sys.argv.remove("--recurse") setup( name="cunumeric", - version="22.07.00", + version="22.08.00", packages=find_packages( where=".", include=["cunumeric*"], From b24a2de1b9dfa5fd5c801450bdd468596074abbd Mon Sep 17 00:00:00 2001 From: Wonchan Lee Date: Tue, 9 Aug 2022 23:06:14 -0700 Subject: [PATCH 050/462] Cache up an API change --- src/cunumeric/array.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/cunumeric/array.cc b/src/cunumeric/array.cc index 49b29b7b4e..4f8a248229 100644 --- a/src/cunumeric/array.cc +++ b/src/cunumeric/array.cc @@ -28,7 +28,7 @@ Array::Array(CuNumericRuntime* runtime, legate::LibraryContext* context, legate: int32_t Array::dim() const { return store_.dim(); } -const std::vector& Array::shape() const { return store_.extents(); } +const std::vector& Array::shape() const { return store_.extents().data(); } legate::LegateTypeCode Array::code() const { return store_.code(); } From ecf96e5acfcb9e56d13be2a2969ca6f44538e852 Mon Sep 17 00:00:00 2001 From: Wonchan Lee Date: Wed, 10 Aug 2022 15:49:30 -0700 Subject: [PATCH 051/462] Partition symbols are now created independently of stores --- src/cunumeric/array.cc | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/src/cunumeric/array.cc b/src/cunumeric/array.cc index 4f8a248229..70ee8e0b31 100644 --- a/src/cunumeric/array.cc +++ b/src/cunumeric/array.cc @@ -49,7 +49,7 @@ void Array::random(int32_t gen_code) { auto task = runtime_->create_task(CuNumericOpCode::CUNUMERIC_RAND); - auto p_lhs = task->declare_partition(store_); + auto p_lhs = task->declare_partition(); task->add_output(store_, p_lhs); task->add_scalar_arg(legate::Scalar(static_cast(RandGenCode::UNIFORM))); @@ -65,8 +65,8 @@ void Array::fill(const Scalar& value, bool argval) auto fill_value = runtime_->create_scalar_store(value); auto task = runtime_->create_task(CuNumericOpCode::CUNUMERIC_FILL); - auto p_lhs = task->declare_partition(store_); - auto p_fill_value = task->declare_partition(fill_value); + auto p_lhs = task->declare_partition(); + auto p_fill_value = task->declare_partition(); task->add_output(store_, p_lhs); task->add_input(fill_value, p_fill_value); @@ -79,9 +79,9 @@ void Array::binary_op(int32_t op_code, std::shared_ptr rhs1, std::shared_ { auto task = runtime_->create_task(CuNumericOpCode::CUNUMERIC_BINARY_OP); - auto p_lhs = task->declare_partition(store_); - auto p_rhs1 = task->declare_partition(rhs1->store_); - auto p_rhs2 = task->declare_partition(rhs2->store_); + auto p_lhs = task->declare_partition(); + auto p_rhs1 = task->declare_partition(); + auto p_rhs2 = task->declare_partition(); task->add_output(store_, p_lhs); task->add_input(rhs1->store_, p_rhs1); @@ -98,8 +98,8 @@ void Array::unary_op(int32_t op_code, std::shared_ptr input) { auto task = runtime_->create_task(CuNumericOpCode::CUNUMERIC_UNARY_OP); - auto p_out = task->declare_partition(store_); - auto p_in = task->declare_partition(input->store_); + auto p_out = task->declare_partition(); + auto p_in = task->declare_partition(); task->add_output(store_, p_out); task->add_input(input->store_, p_in); @@ -119,8 +119,8 @@ void Array::unary_reduction(int32_t op_code_, std::shared_ptr input) auto task = runtime_->create_task(CuNumericOpCode::CUNUMERIC_SCALAR_UNARY_RED); - auto p_out = task->declare_partition(store_); - auto p_in = task->declare_partition(input->store_); + auto p_out = task->declare_partition(); + auto p_in = task->declare_partition(); auto redop = runtime_->get_reduction_op(op_code, code()); @@ -149,9 +149,9 @@ void Array::dot(std::shared_ptr rhs1, std::shared_ptr rhs2) auto task = runtime_->create_task(CuNumericOpCode::CUNUMERIC_MATMUL); - auto p_lhs = task->declare_partition(lhs_s); - auto p_rhs1 = task->declare_partition(rhs1_s); - auto p_rhs2 = task->declare_partition(rhs2_s); + auto p_lhs = task->declare_partition(); + auto p_rhs1 = task->declare_partition(); + auto p_rhs2 = task->declare_partition(); auto redop = LEGION_REDOP_BASE + LEGION_TYPE_TOTAL * LEGION_REDOP_KIND_SUM + code(); From 1c4a2435137397119f7378f2ffa0c17ae5ae9443 Mon Sep 17 00:00:00 2001 From: Wonchan Lee Date: Thu, 11 Aug 2022 18:05:37 -0700 Subject: [PATCH 052/462] Remove unnecessary pointers from Array and use it in the API --- src/cunumeric/array.cc | 86 ++++++++++++++++++++++---------------- src/cunumeric/array.h | 27 +++++++----- src/cunumeric/array.inl | 5 ++- src/cunumeric/operators.cc | 54 ++++++++++++------------ src/cunumeric/operators.h | 16 +++---- src/cunumeric/runtime.cc | 6 +-- src/cunumeric/runtime.h | 5 ++- 7 files changed, 110 insertions(+), 89 deletions(-) diff --git a/src/cunumeric/array.cc b/src/cunumeric/array.cc index 70ee8e0b31..d63ecbc0a8 100644 --- a/src/cunumeric/array.cc +++ b/src/cunumeric/array.cc @@ -21,10 +21,7 @@ namespace cunumeric { -Array::Array(CuNumericRuntime* runtime, legate::LibraryContext* context, legate::LogicalStore store) - : runtime_(runtime), context_(context), store_(store) -{ -} +Array::Array(legate::LogicalStore&& store) : store_(std::forward(store)) {} int32_t Array::dim() const { return store_.dim(); } @@ -47,24 +44,28 @@ static std::vector compute_strides(const std::vector& shape) void Array::random(int32_t gen_code) { - auto task = runtime_->create_task(CuNumericOpCode::CUNUMERIC_RAND); + auto runtime = CuNumericRuntime::get_runtime(); + + auto task = runtime->create_task(CuNumericOpCode::CUNUMERIC_RAND); auto p_lhs = task->declare_partition(); task->add_output(store_, p_lhs); task->add_scalar_arg(legate::Scalar(static_cast(RandGenCode::UNIFORM))); - task->add_scalar_arg(legate::Scalar(runtime_->get_next_random_epoch())); + task->add_scalar_arg(legate::Scalar(runtime->get_next_random_epoch())); auto strides = compute_strides(shape()); task->add_scalar_arg(legate::Scalar(strides)); - runtime_->submit(std::move(task)); + runtime->submit(std::move(task)); } void Array::fill(const Scalar& value, bool argval) { - auto fill_value = runtime_->create_scalar_store(value); + auto runtime = CuNumericRuntime::get_runtime(); - auto task = runtime_->create_task(CuNumericOpCode::CUNUMERIC_FILL); + auto fill_value = runtime->create_scalar_store(value); + + auto task = runtime->create_task(CuNumericOpCode::CUNUMERIC_FILL); auto p_lhs = task->declare_partition(); auto p_fill_value = task->declare_partition(); @@ -72,82 +73,90 @@ void Array::fill(const Scalar& value, bool argval) task->add_input(fill_value, p_fill_value); task->add_scalar_arg(legate::Scalar(argval)); - runtime_->submit(std::move(task)); + runtime->submit(std::move(task)); } -void Array::binary_op(int32_t op_code, std::shared_ptr rhs1, std::shared_ptr rhs2) +void Array::binary_op(int32_t op_code, Array rhs1, Array rhs2) { - auto task = runtime_->create_task(CuNumericOpCode::CUNUMERIC_BINARY_OP); + auto runtime = CuNumericRuntime::get_runtime(); + + auto task = runtime->create_task(CuNumericOpCode::CUNUMERIC_BINARY_OP); auto p_lhs = task->declare_partition(); auto p_rhs1 = task->declare_partition(); auto p_rhs2 = task->declare_partition(); task->add_output(store_, p_lhs); - task->add_input(rhs1->store_, p_rhs1); - task->add_input(rhs2->store_, p_rhs2); + task->add_input(rhs1.store_, p_rhs1); + task->add_input(rhs2.store_, p_rhs2); task->add_scalar_arg(legate::Scalar(op_code)); task->add_constraint(align(p_lhs, p_rhs1)); task->add_constraint(align(p_rhs1, p_rhs2)); - runtime_->submit(std::move(task)); + runtime->submit(std::move(task)); } -void Array::unary_op(int32_t op_code, std::shared_ptr input) +void Array::unary_op(int32_t op_code, Array input) { - auto task = runtime_->create_task(CuNumericOpCode::CUNUMERIC_UNARY_OP); + auto runtime = CuNumericRuntime::get_runtime(); + + auto task = runtime->create_task(CuNumericOpCode::CUNUMERIC_UNARY_OP); auto p_out = task->declare_partition(); auto p_in = task->declare_partition(); task->add_output(store_, p_out); - task->add_input(input->store_, p_in); + task->add_input(input.store_, p_in); task->add_scalar_arg(legate::Scalar(op_code)); task->add_constraint(align(p_out, p_in)); - runtime_->submit(std::move(task)); + runtime->submit(std::move(task)); } -void Array::unary_reduction(int32_t op_code_, std::shared_ptr input) +void Array::unary_reduction(int32_t op_code_, Array input) { + auto runtime = CuNumericRuntime::get_runtime(); + auto op_code = static_cast(op_code_); - auto identity = runtime_->get_reduction_identity(op_code, code()); + auto identity = runtime->get_reduction_identity(op_code, code()); fill(identity, false); - auto task = runtime_->create_task(CuNumericOpCode::CUNUMERIC_SCALAR_UNARY_RED); + auto task = runtime->create_task(CuNumericOpCode::CUNUMERIC_SCALAR_UNARY_RED); auto p_out = task->declare_partition(); auto p_in = task->declare_partition(); - auto redop = runtime_->get_reduction_op(op_code, code()); + auto redop = runtime->get_reduction_op(op_code, code()); task->add_reduction(store_, redop, p_out); - task->add_input(input->store_, p_in); + task->add_input(input.store_, p_in); task->add_scalar_arg(legate::Scalar(op_code_)); - task->add_scalar_arg(legate::Scalar(input->shape())); + task->add_scalar_arg(legate::Scalar(input.shape())); - runtime_->submit(std::move(task)); + runtime->submit(std::move(task)); } -void Array::dot(std::shared_ptr rhs1, std::shared_ptr rhs2) +void Array::dot(Array rhs1, Array rhs2) { - auto identity = runtime_->get_reduction_identity(UnaryRedCode::SUM, code()); + auto runtime = CuNumericRuntime::get_runtime(); + + auto identity = runtime->get_reduction_identity(UnaryRedCode::SUM, code()); fill(identity, false); - assert(dim() == 2 && rhs1->dim() == 2 && rhs2->dim() == 2); + assert(dim() == 2 && rhs1.dim() == 2 && rhs2.dim() == 2); - auto m = rhs1->shape()[0]; - auto n = rhs2->shape()[1]; - auto k = rhs1->shape()[1]; + auto m = rhs1.shape()[0]; + auto n = rhs2.shape()[1]; + auto k = rhs1.shape()[1]; auto lhs_s = store_.promote(1, k); - auto rhs1_s = rhs1->store_.promote(2, n); - auto rhs2_s = rhs2->store_.promote(0, m); + auto rhs1_s = rhs1.store_.promote(2, n); + auto rhs2_s = rhs2.store_.promote(0, m); - auto task = runtime_->create_task(CuNumericOpCode::CUNUMERIC_MATMUL); + auto task = runtime->create_task(CuNumericOpCode::CUNUMERIC_MATMUL); auto p_lhs = task->declare_partition(); auto p_rhs1 = task->declare_partition(); @@ -162,7 +171,12 @@ void Array::dot(std::shared_ptr rhs1, std::shared_ptr rhs2) task->add_constraint(align(p_lhs, p_rhs1)); task->add_constraint(align(p_rhs1, p_rhs2)); - runtime_->submit(std::move(task)); + runtime->submit(std::move(task)); +} + +/*static*/ legate::LibraryContext* Array::get_context() +{ + return CuNumericRuntime::get_runtime()->get_context(); } } // namespace cunumeric diff --git a/src/cunumeric/array.h b/src/cunumeric/array.h index 60c9ebfa3e..27aedcb215 100644 --- a/src/cunumeric/array.h +++ b/src/cunumeric/array.h @@ -23,13 +23,19 @@ namespace cunumeric { -class CuNumericRuntime; - class Array { friend class CuNumericRuntime; private: - Array(CuNumericRuntime* runtime, legate::LibraryContext* context, legate::LogicalStore store); + Array(legate::LogicalStore&& store); + + public: + Array(const Array&) = default; + Array& operator=(const Array&) = default; + + public: + Array(Array&&) = default; + Array& operator=(Array&&) = default; public: int32_t dim() const; @@ -43,15 +49,16 @@ class Array { public: void random(int32_t gen_code); void fill(const Scalar& value, bool argval); - void binary_op(int32_t op_code, std::shared_ptr rhs1, std::shared_ptr rhs2); - void unary_op(int32_t op_code, std::shared_ptr input); - void unary_reduction(int32_t op_code, std::shared_ptr input); - void fill(std::shared_ptr fill_value); - void dot(std::shared_ptr rhs1, std::shared_ptr rhs2); + void binary_op(int32_t op_code, Array rhs1, Array rhs2); + void unary_op(int32_t op_code, Array input); + void unary_reduction(int32_t op_code, Array input); + void fill(Array fill_value); + void dot(Array rhs1, Array rhs2); + + public: + static legate::LibraryContext* get_context(); private: - CuNumericRuntime* runtime_; - legate::LibraryContext* context_; legate::LogicalStore store_; }; diff --git a/src/cunumeric/array.inl b/src/cunumeric/array.inl index 9812966e30..b1cd82e082 100644 --- a/src/cunumeric/array.inl +++ b/src/cunumeric/array.inl @@ -19,8 +19,9 @@ namespace cunumeric { template legate::AccessorRW Array::get_accessor() { - auto mapped = store_.get_physical_store(context_); - auto shape = mapped->shape(); + auto context = get_context(); + auto mapped = store_.get_physical_store(context); + auto shape = mapped->shape(); return mapped->read_write_accessor(shape); } diff --git a/src/cunumeric/operators.cc b/src/cunumeric/operators.cc index 9147d01455..ef3dc0b940 100644 --- a/src/cunumeric/operators.cc +++ b/src/cunumeric/operators.cc @@ -23,74 +23,72 @@ namespace cunumeric { -using ArrayP = std::shared_ptr; - -ArrayP array(std::vector shape, legate::LegateTypeCode type) +Array array(std::vector shape, legate::LegateTypeCode type) { return CuNumericRuntime::get_runtime()->create_array(std::move(shape), type); } -ArrayP unary_op(UnaryOpCode op_code, ArrayP input) +Array unary_op(UnaryOpCode op_code, Array input) { auto runtime = CuNumericRuntime::get_runtime(); - auto out = runtime->create_array(input->shape(), input->code()); - out->unary_op(static_cast(op_code), std::move(input)); + auto out = runtime->create_array(input.shape(), input.code()); + out.unary_op(static_cast(op_code), std::move(input)); return std::move(out); } -ArrayP unary_reduction(UnaryRedCode op_code, ArrayP input) +Array unary_reduction(UnaryRedCode op_code, Array input) { auto runtime = CuNumericRuntime::get_runtime(); - auto out = runtime->create_array({1}, input->code()); - out->unary_reduction(static_cast(op_code), std::move(input)); + auto out = runtime->create_array({1}, input.code()); + out.unary_reduction(static_cast(op_code), std::move(input)); return std::move(out); } -ArrayP binary_op(BinaryOpCode op_code, ArrayP rhs1, ArrayP rhs2) +Array binary_op(BinaryOpCode op_code, Array rhs1, Array rhs2) { - assert(rhs1->shape() == rhs2->shape()); - assert(rhs1->code() == rhs2->code()); + assert(rhs1.shape() == rhs2.shape()); + assert(rhs1.code() == rhs2.code()); auto runtime = CuNumericRuntime::get_runtime(); - auto out = runtime->create_array(rhs1->shape(), rhs1->code()); - out->binary_op(static_cast(op_code), std::move(rhs1), std::move(rhs2)); + auto out = runtime->create_array(rhs1.shape(), rhs1.code()); + out.binary_op(static_cast(op_code), std::move(rhs1), std::move(rhs2)); return std::move(out); } -ArrayP abs(ArrayP input) { return unary_op(UnaryOpCode::ABSOLUTE, std::move(input)); } +Array abs(Array input) { return unary_op(UnaryOpCode::ABSOLUTE, std::move(input)); } -ArrayP add(ArrayP rhs1, ArrayP rhs2) +Array add(Array rhs1, Array rhs2) { return binary_op(BinaryOpCode::ADD, std::move(rhs1), std::move(rhs2)); } -ArrayP negative(ArrayP input) { return unary_op(UnaryOpCode::NEGATIVE, std::move(input)); } +Array negative(Array input) { return unary_op(UnaryOpCode::NEGATIVE, std::move(input)); } -ArrayP random(std::vector shape) +Array random(std::vector shape) { auto runtime = CuNumericRuntime::get_runtime(); auto out = runtime->create_array(std::move(shape), legate::LegateTypeCode::DOUBLE_LT); - out->random(static_cast(RandGenCode::UNIFORM)); + out.random(static_cast(RandGenCode::UNIFORM)); return std::move(out); } -ArrayP full(std::vector shape, const Scalar& value) +Array full(std::vector shape, const Scalar& value) { auto runtime = CuNumericRuntime::get_runtime(); auto out = runtime->create_array(std::move(shape), value.code()); - out->fill(value, false); + out.fill(value, false); return std::move(out); } -ArrayP dot(ArrayP rhs1, ArrayP rhs2) +Array dot(Array rhs1, Array rhs2) { - if (rhs1->dim() != 2 || rhs2->dim() != 2) { + if (rhs1.dim() != 2 || rhs2.dim() != 2) { fprintf(stderr, "cunumeric::dot only supports matrices now"); LEGATE_ABORT; } - auto& rhs1_shape = rhs1->shape(); - auto& rhs2_shape = rhs2->shape(); + auto& rhs1_shape = rhs1.shape(); + auto& rhs2_shape = rhs2.shape(); if (rhs1_shape[1] != rhs2_shape[0]) { fprintf(stderr, @@ -107,11 +105,11 @@ ArrayP dot(ArrayP rhs1, ArrayP rhs2) shape.push_back(rhs1_shape[0]); shape.push_back(rhs2_shape[1]); - auto out = runtime->create_array(std::move(shape), rhs1->code()); - out->dot(std::move(rhs1), std::move(rhs2)); + auto out = runtime->create_array(std::move(shape), rhs1.code()); + out.dot(std::move(rhs1), std::move(rhs2)); return std::move(out); } -ArrayP sum(ArrayP input) { return unary_reduction(UnaryRedCode::SUM, std::move(input)); } +Array sum(Array input) { return unary_reduction(UnaryRedCode::SUM, std::move(input)); } } // namespace cunumeric diff --git a/src/cunumeric/operators.h b/src/cunumeric/operators.h index 6ec285abf2..b18dc94e84 100644 --- a/src/cunumeric/operators.h +++ b/src/cunumeric/operators.h @@ -27,20 +27,20 @@ class Array; void initialize(int32_t argc, char** argv); -std::shared_ptr array(std::vector shape, legate::LegateTypeCode type); +Array array(std::vector shape, legate::LegateTypeCode type); -std::shared_ptr abs(std::shared_ptr input); +Array abs(Array input); -std::shared_ptr add(std::shared_ptr rhs1, std::shared_ptr rhs2); +Array add(Array rhs1, Array rhs2); -std::shared_ptr dot(std::shared_ptr rhs1, std::shared_ptr rhs2); +Array dot(Array rhs1, Array rhs2); -std::shared_ptr negative(std::shared_ptr input); +Array negative(Array input); -std::shared_ptr random(std::vector shape); +Array random(std::vector shape); -std::shared_ptr full(std::vector shape, const Scalar& value); +Array full(std::vector shape, const Scalar& value); -std::shared_ptr sum(std::shared_ptr input); +Array sum(Array input); } // namespace cunumeric diff --git a/src/cunumeric/runtime.cc b/src/cunumeric/runtime.cc index 5f21b3b5d1..df79ba6ea5 100644 --- a/src/cunumeric/runtime.cc +++ b/src/cunumeric/runtime.cc @@ -37,13 +37,11 @@ CuNumericRuntime::CuNumericRuntime(legate::Runtime* legate_runtime, legate::Libr { } -std::shared_ptr CuNumericRuntime::create_array(std::vector shape, - legate::LegateTypeCode type) +Array CuNumericRuntime::create_array(std::vector shape, legate::LegateTypeCode type) { // TODO: We need a type system for cuNumeric and should not use the core types auto store = legate_runtime_->create_store(shape, type); - auto array = new Array(this, context_, std::move(store)); - return std::shared_ptr(array); + return Array(std::move(store)); } legate::LogicalStore CuNumericRuntime::create_scalar_store(const Scalar& value) diff --git a/src/cunumeric/runtime.h b/src/cunumeric/runtime.h index 5afb1509a1..4632a3adce 100644 --- a/src/cunumeric/runtime.h +++ b/src/cunumeric/runtime.h @@ -33,7 +33,7 @@ class CuNumericRuntime { CuNumericRuntime(legate::Runtime* legate_runtime, legate::LibraryContext* context); public: - std::shared_ptr create_array(std::vector shape, legate::LegateTypeCode type); + Array create_array(std::vector shape, legate::LegateTypeCode type); legate::LogicalStore create_scalar_store(const Scalar& value); public: @@ -47,6 +47,9 @@ class CuNumericRuntime { public: uint32_t get_next_random_epoch(); + public: + legate::LibraryContext* get_context() const { return context_; } + public: static CuNumericRuntime* get_runtime(); static void initialize(legate::Runtime* legate_runtime, legate::LibraryContext* context); From b0f11aef1831c325a522eb0dcb9103a4c1933e64 Mon Sep 17 00:00:00 2001 From: Wonchan Lee Date: Thu, 11 Aug 2022 18:13:53 -0700 Subject: [PATCH 053/462] Rename Array to NDArray --- src/cunumeric.h | 2 +- src/cunumeric.mk | 6 +++--- src/cunumeric/{array.cc => ndarray.cc} | 26 +++++++++++++----------- src/cunumeric/{array.h => ndarray.h} | 24 +++++++++++----------- src/cunumeric/{array.inl => ndarray.inl} | 2 +- src/cunumeric/operators.cc | 24 +++++++++++----------- src/cunumeric/operators.h | 18 ++++++++-------- src/cunumeric/runtime.cc | 7 ++++--- src/cunumeric/runtime.h | 4 ++-- 9 files changed, 58 insertions(+), 55 deletions(-) rename src/cunumeric/{array.cc => ndarray.cc} (86%) rename src/cunumeric/{array.h => ndarray.h} (70%) rename src/cunumeric/{array.inl => ndarray.inl} (94%) diff --git a/src/cunumeric.h b/src/cunumeric.h index 59c424f079..28a2c9d53a 100644 --- a/src/cunumeric.h +++ b/src/cunumeric.h @@ -14,6 +14,6 @@ * */ -#include "cunumeric/array.h" +#include "cunumeric/ndarray.h" #include "cunumeric/operators.h" #include "cunumeric/typedefs.h" diff --git a/src/cunumeric.mk b/src/cunumeric.mk index 95fd1feb02..8b95a36498 100644 --- a/src/cunumeric.mk +++ b/src/cunumeric.mk @@ -57,8 +57,8 @@ GEN_CPU_SRC += cunumeric/ternary/where.cc \ cunumeric/convolution/convolve.cc \ cunumeric/transform/flip.cc \ cunumeric/arg.cc \ - cunumeric/array.cc \ cunumeric/mapper.cc \ + cunumeric/ndarray.cc \ cunumeric/operators.cc \ cunumeric/runtime.cc @@ -162,8 +162,8 @@ GEN_CPU_SRC += cunumeric/cunumeric.cc # This must always be the last file! INSTALL_PATHS = cunumeric INSTALL_HEADERS = cunumeric/cunumeric_c.h \ - cunumeric/array.h \ - cunumeric/array.inl \ + cunumeric/ndarray.h \ + cunumeric/ndarray.inl \ cunumeric/operators.h \ cunumeric/typedefs.h \ cunumeric.h diff --git a/src/cunumeric/array.cc b/src/cunumeric/ndarray.cc similarity index 86% rename from src/cunumeric/array.cc rename to src/cunumeric/ndarray.cc index d63ecbc0a8..23daa7c0da 100644 --- a/src/cunumeric/array.cc +++ b/src/cunumeric/ndarray.cc @@ -14,20 +14,22 @@ * */ -#include "cunumeric/array.h" +#include "cunumeric/ndarray.h" #include "cunumeric/runtime.h" #include "cunumeric/random/rand_util.h" #include "cunumeric/unary/unary_red_util.h" namespace cunumeric { -Array::Array(legate::LogicalStore&& store) : store_(std::forward(store)) {} +NDArray::NDArray(legate::LogicalStore&& store) : store_(std::forward(store)) +{ +} -int32_t Array::dim() const { return store_.dim(); } +int32_t NDArray::dim() const { return store_.dim(); } -const std::vector& Array::shape() const { return store_.extents().data(); } +const std::vector& NDArray::shape() const { return store_.extents().data(); } -legate::LegateTypeCode Array::code() const { return store_.code(); } +legate::LegateTypeCode NDArray::code() const { return store_.code(); } static std::vector compute_strides(const std::vector& shape) { @@ -42,7 +44,7 @@ static std::vector compute_strides(const std::vector& shape) return std::move(strides); } -void Array::random(int32_t gen_code) +void NDArray::random(int32_t gen_code) { auto runtime = CuNumericRuntime::get_runtime(); @@ -59,7 +61,7 @@ void Array::random(int32_t gen_code) runtime->submit(std::move(task)); } -void Array::fill(const Scalar& value, bool argval) +void NDArray::fill(const Scalar& value, bool argval) { auto runtime = CuNumericRuntime::get_runtime(); @@ -76,7 +78,7 @@ void Array::fill(const Scalar& value, bool argval) runtime->submit(std::move(task)); } -void Array::binary_op(int32_t op_code, Array rhs1, Array rhs2) +void NDArray::binary_op(int32_t op_code, NDArray rhs1, NDArray rhs2) { auto runtime = CuNumericRuntime::get_runtime(); @@ -97,7 +99,7 @@ void Array::binary_op(int32_t op_code, Array rhs1, Array rhs2) runtime->submit(std::move(task)); } -void Array::unary_op(int32_t op_code, Array input) +void NDArray::unary_op(int32_t op_code, NDArray input) { auto runtime = CuNumericRuntime::get_runtime(); @@ -115,7 +117,7 @@ void Array::unary_op(int32_t op_code, Array input) runtime->submit(std::move(task)); } -void Array::unary_reduction(int32_t op_code_, Array input) +void NDArray::unary_reduction(int32_t op_code_, NDArray input) { auto runtime = CuNumericRuntime::get_runtime(); @@ -139,7 +141,7 @@ void Array::unary_reduction(int32_t op_code_, Array input) runtime->submit(std::move(task)); } -void Array::dot(Array rhs1, Array rhs2) +void NDArray::dot(NDArray rhs1, NDArray rhs2) { auto runtime = CuNumericRuntime::get_runtime(); @@ -174,7 +176,7 @@ void Array::dot(Array rhs1, Array rhs2) runtime->submit(std::move(task)); } -/*static*/ legate::LibraryContext* Array::get_context() +/*static*/ legate::LibraryContext* NDArray::get_context() { return CuNumericRuntime::get_runtime()->get_context(); } diff --git a/src/cunumeric/array.h b/src/cunumeric/ndarray.h similarity index 70% rename from src/cunumeric/array.h rename to src/cunumeric/ndarray.h index 27aedcb215..128af4ab5d 100644 --- a/src/cunumeric/array.h +++ b/src/cunumeric/ndarray.h @@ -23,19 +23,19 @@ namespace cunumeric { -class Array { +class NDArray { friend class CuNumericRuntime; private: - Array(legate::LogicalStore&& store); + NDArray(legate::LogicalStore&& store); public: - Array(const Array&) = default; - Array& operator=(const Array&) = default; + NDArray(const NDArray&) = default; + NDArray& operator=(const NDArray&) = default; public: - Array(Array&&) = default; - Array& operator=(Array&&) = default; + NDArray(NDArray&&) = default; + NDArray& operator=(NDArray&&) = default; public: int32_t dim() const; @@ -49,11 +49,11 @@ class Array { public: void random(int32_t gen_code); void fill(const Scalar& value, bool argval); - void binary_op(int32_t op_code, Array rhs1, Array rhs2); - void unary_op(int32_t op_code, Array input); - void unary_reduction(int32_t op_code, Array input); - void fill(Array fill_value); - void dot(Array rhs1, Array rhs2); + void binary_op(int32_t op_code, NDArray rhs1, NDArray rhs2); + void unary_op(int32_t op_code, NDArray input); + void unary_reduction(int32_t op_code, NDArray input); + void fill(NDArray fill_value); + void dot(NDArray rhs1, NDArray rhs2); public: static legate::LibraryContext* get_context(); @@ -64,4 +64,4 @@ class Array { } // namespace cunumeric -#include "cunumeric/array.inl" +#include "cunumeric/ndarray.inl" diff --git a/src/cunumeric/array.inl b/src/cunumeric/ndarray.inl similarity index 94% rename from src/cunumeric/array.inl rename to src/cunumeric/ndarray.inl index b1cd82e082..4063dc3fc1 100644 --- a/src/cunumeric/array.inl +++ b/src/cunumeric/ndarray.inl @@ -17,7 +17,7 @@ namespace cunumeric { template -legate::AccessorRW Array::get_accessor() +legate::AccessorRW NDArray::get_accessor() { auto context = get_context(); auto mapped = store_.get_physical_store(context); diff --git a/src/cunumeric/operators.cc b/src/cunumeric/operators.cc index ef3dc0b940..33357273b0 100644 --- a/src/cunumeric/operators.cc +++ b/src/cunumeric/operators.cc @@ -15,7 +15,7 @@ */ #include "cunumeric/operators.h" -#include "cunumeric/array.h" +#include "cunumeric/ndarray.h" #include "cunumeric/runtime.h" #include "cunumeric/binary/binary_op_util.h" #include "cunumeric/unary/unary_op_util.h" @@ -23,12 +23,12 @@ namespace cunumeric { -Array array(std::vector shape, legate::LegateTypeCode type) +NDArray array(std::vector shape, legate::LegateTypeCode type) { return CuNumericRuntime::get_runtime()->create_array(std::move(shape), type); } -Array unary_op(UnaryOpCode op_code, Array input) +NDArray unary_op(UnaryOpCode op_code, NDArray input) { auto runtime = CuNumericRuntime::get_runtime(); auto out = runtime->create_array(input.shape(), input.code()); @@ -36,7 +36,7 @@ Array unary_op(UnaryOpCode op_code, Array input) return std::move(out); } -Array unary_reduction(UnaryRedCode op_code, Array input) +NDArray unary_reduction(UnaryRedCode op_code, NDArray input) { auto runtime = CuNumericRuntime::get_runtime(); auto out = runtime->create_array({1}, input.code()); @@ -44,7 +44,7 @@ Array unary_reduction(UnaryRedCode op_code, Array input) return std::move(out); } -Array binary_op(BinaryOpCode op_code, Array rhs1, Array rhs2) +NDArray binary_op(BinaryOpCode op_code, NDArray rhs1, NDArray rhs2) { assert(rhs1.shape() == rhs2.shape()); assert(rhs1.code() == rhs2.code()); @@ -55,16 +55,16 @@ Array binary_op(BinaryOpCode op_code, Array rhs1, Array rhs2) return std::move(out); } -Array abs(Array input) { return unary_op(UnaryOpCode::ABSOLUTE, std::move(input)); } +NDArray abs(NDArray input) { return unary_op(UnaryOpCode::ABSOLUTE, std::move(input)); } -Array add(Array rhs1, Array rhs2) +NDArray add(NDArray rhs1, NDArray rhs2) { return binary_op(BinaryOpCode::ADD, std::move(rhs1), std::move(rhs2)); } -Array negative(Array input) { return unary_op(UnaryOpCode::NEGATIVE, std::move(input)); } +NDArray negative(NDArray input) { return unary_op(UnaryOpCode::NEGATIVE, std::move(input)); } -Array random(std::vector shape) +NDArray random(std::vector shape) { auto runtime = CuNumericRuntime::get_runtime(); auto out = runtime->create_array(std::move(shape), legate::LegateTypeCode::DOUBLE_LT); @@ -72,7 +72,7 @@ Array random(std::vector shape) return std::move(out); } -Array full(std::vector shape, const Scalar& value) +NDArray full(std::vector shape, const Scalar& value) { auto runtime = CuNumericRuntime::get_runtime(); auto out = runtime->create_array(std::move(shape), value.code()); @@ -80,7 +80,7 @@ Array full(std::vector shape, const Scalar& value) return std::move(out); } -Array dot(Array rhs1, Array rhs2) +NDArray dot(NDArray rhs1, NDArray rhs2) { if (rhs1.dim() != 2 || rhs2.dim() != 2) { fprintf(stderr, "cunumeric::dot only supports matrices now"); @@ -110,6 +110,6 @@ Array dot(Array rhs1, Array rhs2) return std::move(out); } -Array sum(Array input) { return unary_reduction(UnaryRedCode::SUM, std::move(input)); } +NDArray sum(NDArray input) { return unary_reduction(UnaryRedCode::SUM, std::move(input)); } } // namespace cunumeric diff --git a/src/cunumeric/operators.h b/src/cunumeric/operators.h index b18dc94e84..c63fe56af3 100644 --- a/src/cunumeric/operators.h +++ b/src/cunumeric/operators.h @@ -23,24 +23,24 @@ namespace cunumeric { -class Array; +class NDArray; void initialize(int32_t argc, char** argv); -Array array(std::vector shape, legate::LegateTypeCode type); +NDArray array(std::vector shape, legate::LegateTypeCode type); -Array abs(Array input); +NDArray abs(NDArray input); -Array add(Array rhs1, Array rhs2); +NDArray add(NDArray rhs1, NDArray rhs2); -Array dot(Array rhs1, Array rhs2); +NDArray dot(NDArray rhs1, NDArray rhs2); -Array negative(Array input); +NDArray negative(NDArray input); -Array random(std::vector shape); +NDArray random(std::vector shape); -Array full(std::vector shape, const Scalar& value); +NDArray full(std::vector shape, const Scalar& value); -Array sum(Array input); +NDArray sum(NDArray input); } // namespace cunumeric diff --git a/src/cunumeric/runtime.cc b/src/cunumeric/runtime.cc index df79ba6ea5..6c83ecdcf0 100644 --- a/src/cunumeric/runtime.cc +++ b/src/cunumeric/runtime.cc @@ -15,7 +15,8 @@ */ #include "cunumeric/runtime.h" -#include "cunumeric/array.h" + +#include "cunumeric/ndarray.h" namespace cunumeric { @@ -37,11 +38,11 @@ CuNumericRuntime::CuNumericRuntime(legate::Runtime* legate_runtime, legate::Libr { } -Array CuNumericRuntime::create_array(std::vector shape, legate::LegateTypeCode type) +NDArray CuNumericRuntime::create_array(std::vector shape, legate::LegateTypeCode type) { // TODO: We need a type system for cuNumeric and should not use the core types auto store = legate_runtime_->create_store(shape, type); - return Array(std::move(store)); + return NDArray(std::move(store)); } legate::LogicalStore CuNumericRuntime::create_scalar_store(const Scalar& value) diff --git a/src/cunumeric/runtime.h b/src/cunumeric/runtime.h index 4632a3adce..ef94d40e7f 100644 --- a/src/cunumeric/runtime.h +++ b/src/cunumeric/runtime.h @@ -26,14 +26,14 @@ namespace cunumeric { -class Array; +class NDArray; class CuNumericRuntime { private: CuNumericRuntime(legate::Runtime* legate_runtime, legate::LibraryContext* context); public: - Array create_array(std::vector shape, legate::LegateTypeCode type); + NDArray create_array(std::vector shape, legate::LegateTypeCode type); legate::LogicalStore create_scalar_store(const Scalar& value); public: From 318bc4fbdaf8a5b90ee0c6bc1f02a374e75948f3 Mon Sep 17 00:00:00 2001 From: Wonchan Lee Date: Fri, 12 Aug 2022 10:23:32 -0700 Subject: [PATCH 054/462] Implement NumPy broadcasting --- src/cunumeric/ndarray.cc | 36 ++++++++++++++++++++++++++++++++++-- src/cunumeric/ndarray.h | 3 +++ src/cunumeric/operators.cc | 35 ++++++++++++++++++++++++++++++++--- 3 files changed, 69 insertions(+), 5 deletions(-) diff --git a/src/cunumeric/ndarray.cc b/src/cunumeric/ndarray.cc index 23daa7c0da..715f723001 100644 --- a/src/cunumeric/ndarray.cc +++ b/src/cunumeric/ndarray.cc @@ -88,9 +88,13 @@ void NDArray::binary_op(int32_t op_code, NDArray rhs1, NDArray rhs2) auto p_rhs1 = task->declare_partition(); auto p_rhs2 = task->declare_partition(); + auto& out_shape = shape(); + auto rhs1_store = broadcast(out_shape, rhs1.store_); + auto rhs2_store = broadcast(out_shape, rhs2.store_); + task->add_output(store_, p_lhs); - task->add_input(rhs1.store_, p_rhs1); - task->add_input(rhs2.store_, p_rhs2); + task->add_input(rhs1_store, p_rhs1); + task->add_input(rhs2_store, p_rhs2); task->add_scalar_arg(legate::Scalar(op_code)); task->add_constraint(align(p_lhs, p_rhs1)); @@ -176,6 +180,34 @@ void NDArray::dot(NDArray rhs1, NDArray rhs2) runtime->submit(std::move(task)); } +legate::LogicalStore NDArray::broadcast(const std::vector& shape, + legate::LogicalStore& store) +{ + int32_t diff = static_cast(shape.size()) - store.dim(); + +#ifdef DEBUG_CUNUMERIC + assert(diff >= 0); +#endif + + auto result = store; + for (int32_t dim = 0; dim < diff; ++dim) result = result.promote(dim, shape[dim]); + + std::vector orig_shape = result.extents().data(); + for (uint32_t dim = 0; dim < shape.size(); ++dim) + if (orig_shape[dim] != shape[dim]) { +#ifdef DEBUG_CUNUMERIC + assert(orig_shape[dim] == 1); +#endif + result = result.project(dim, 0).promote(dim, shape[dim]); + } + +#ifdef DEBUG_CUNUMERIC + assert(result.dim() == shape.size()); +#endif + + return std::move(result); +} + /*static*/ legate::LibraryContext* NDArray::get_context() { return CuNumericRuntime::get_runtime()->get_context(); diff --git a/src/cunumeric/ndarray.h b/src/cunumeric/ndarray.h index 128af4ab5d..cf30a93973 100644 --- a/src/cunumeric/ndarray.h +++ b/src/cunumeric/ndarray.h @@ -55,6 +55,9 @@ class NDArray { void fill(NDArray fill_value); void dot(NDArray rhs1, NDArray rhs2); + private: + legate::LogicalStore broadcast(const std::vector& shape, legate::LogicalStore& store); + public: static legate::LibraryContext* get_context(); diff --git a/src/cunumeric/operators.cc b/src/cunumeric/operators.cc index 33357273b0..131c3f1a2f 100644 --- a/src/cunumeric/operators.cc +++ b/src/cunumeric/operators.cc @@ -23,6 +23,35 @@ namespace cunumeric { +namespace { + +std::vector broadcast_shapes(std::vector arrays) +{ +#ifdef DEBUG_CUNUMERIC + assert(!arrays.empty()); +#endif + int32_t dim = 0; + for (auto& array : arrays) dim = std::max(dim, array.dim()); + + std::vector result(dim, 1); + + for (auto& array : arrays) { + auto& shape = array.shape(); + + auto in_it = shape.rbegin(); + auto out_it = result.rbegin(); + for (; in_it != shape.rend() && out_it != result.rend(); ++in_it, ++out_it) { + if (1 == *out_it) + *out_it = *in_it; + else if (*in_it != 1 && *out_it != *in_it) + throw std::exception(); + } + } + return result; +} + +} // namespace + NDArray array(std::vector shape, legate::LegateTypeCode type) { return CuNumericRuntime::get_runtime()->create_array(std::move(shape), type); @@ -46,11 +75,11 @@ NDArray unary_reduction(UnaryRedCode op_code, NDArray input) NDArray binary_op(BinaryOpCode op_code, NDArray rhs1, NDArray rhs2) { - assert(rhs1.shape() == rhs2.shape()); assert(rhs1.code() == rhs2.code()); - auto runtime = CuNumericRuntime::get_runtime(); - auto out = runtime->create_array(rhs1.shape(), rhs1.code()); + auto runtime = CuNumericRuntime::get_runtime(); + auto out_shape = broadcast_shapes({rhs1, rhs2}); + auto out = runtime->create_array(out_shape, rhs1.code()); out.binary_op(static_cast(op_code), std::move(rhs1), std::move(rhs2)); return std::move(out); } From 1250e8474f9cabddf95a6f5e93508d1ab5cfd6a5 Mon Sep 17 00:00:00 2001 From: Wonchan Lee Date: Fri, 12 Aug 2022 10:51:21 -0700 Subject: [PATCH 055/462] Optional output arguments to binary operations --- src/cunumeric/operators.cc | 20 +++++++++++--------- src/cunumeric/operators.h | 6 +++--- 2 files changed, 14 insertions(+), 12 deletions(-) diff --git a/src/cunumeric/operators.cc b/src/cunumeric/operators.cc index 131c3f1a2f..8028323bf0 100644 --- a/src/cunumeric/operators.cc +++ b/src/cunumeric/operators.cc @@ -15,7 +15,7 @@ */ #include "cunumeric/operators.h" -#include "cunumeric/ndarray.h" + #include "cunumeric/runtime.h" #include "cunumeric/binary/binary_op_util.h" #include "cunumeric/unary/unary_op_util.h" @@ -73,22 +73,24 @@ NDArray unary_reduction(UnaryRedCode op_code, NDArray input) return std::move(out); } -NDArray binary_op(BinaryOpCode op_code, NDArray rhs1, NDArray rhs2) +NDArray binary_op(BinaryOpCode op_code, NDArray rhs1, NDArray rhs2, std::optional out) { assert(rhs1.code() == rhs2.code()); - auto runtime = CuNumericRuntime::get_runtime(); - auto out_shape = broadcast_shapes({rhs1, rhs2}); - auto out = runtime->create_array(out_shape, rhs1.code()); - out.binary_op(static_cast(op_code), std::move(rhs1), std::move(rhs2)); - return std::move(out); + auto runtime = CuNumericRuntime::get_runtime(); + if (!out.has_value()) { + auto out_shape = broadcast_shapes({rhs1, rhs2}); + out = runtime->create_array(out_shape, rhs1.code()); + } + out->binary_op(static_cast(op_code), std::move(rhs1), std::move(rhs2)); + return std::move(out.value()); } NDArray abs(NDArray input) { return unary_op(UnaryOpCode::ABSOLUTE, std::move(input)); } -NDArray add(NDArray rhs1, NDArray rhs2) +NDArray add(NDArray rhs1, NDArray rhs2, std::optional out) { - return binary_op(BinaryOpCode::ADD, std::move(rhs1), std::move(rhs2)); + return binary_op(BinaryOpCode::ADD, std::move(rhs1), std::move(rhs2), std::move(out)); } NDArray negative(NDArray input) { return unary_op(UnaryOpCode::NEGATIVE, std::move(input)); } diff --git a/src/cunumeric/operators.h b/src/cunumeric/operators.h index c63fe56af3..8fa437a5f5 100644 --- a/src/cunumeric/operators.h +++ b/src/cunumeric/operators.h @@ -17,21 +17,21 @@ #pragma once #include +#include #include "legate.h" +#include "cunumeric/ndarray.h" #include "cunumeric/typedefs.h" namespace cunumeric { -class NDArray; - void initialize(int32_t argc, char** argv); NDArray array(std::vector shape, legate::LegateTypeCode type); NDArray abs(NDArray input); -NDArray add(NDArray rhs1, NDArray rhs2); +NDArray add(NDArray rhs1, NDArray rhs2, std::optional out = std::nullopt); NDArray dot(NDArray rhs1, NDArray rhs2); From 6016d264f932e3434d907ea3bb3ca92d99ca9506 Mon Sep 17 00:00:00 2001 From: Wonchan Lee Date: Fri, 12 Aug 2022 11:09:21 -0700 Subject: [PATCH 056/462] Overload add operators in NDArray --- src/cunumeric/ndarray.cc | 10 ++++++++++ src/cunumeric/ndarray.h | 4 ++++ 2 files changed, 14 insertions(+) diff --git a/src/cunumeric/ndarray.cc b/src/cunumeric/ndarray.cc index 715f723001..5b106b0299 100644 --- a/src/cunumeric/ndarray.cc +++ b/src/cunumeric/ndarray.cc @@ -15,6 +15,8 @@ */ #include "cunumeric/ndarray.h" + +#include "cunumeric/operators.h" #include "cunumeric/runtime.h" #include "cunumeric/random/rand_util.h" #include "cunumeric/unary/unary_red_util.h" @@ -44,6 +46,14 @@ static std::vector compute_strides(const std::vector& shape) return std::move(strides); } +NDArray NDArray::operator+(const NDArray& other) const { return add(*this, other); } + +NDArray& NDArray::operator+=(const NDArray& other) +{ + add(*this, other, *this); + return *this; +} + void NDArray::random(int32_t gen_code) { auto runtime = CuNumericRuntime::get_runtime(); diff --git a/src/cunumeric/ndarray.h b/src/cunumeric/ndarray.h index cf30a93973..91c03b91bd 100644 --- a/src/cunumeric/ndarray.h +++ b/src/cunumeric/ndarray.h @@ -46,6 +46,10 @@ class NDArray { template legate::AccessorRW get_accessor(); + public: + NDArray operator+(const NDArray& other) const; + NDArray& operator+=(const NDArray& other); + public: void random(int32_t gen_code); void fill(const Scalar& value, bool argval); From cbc37eb1d904c76e57edf24cfc948d4a0b90afd7 Mon Sep 17 00:00:00 2001 From: Wonchan Lee Date: Fri, 2 Sep 2022 10:06:08 -0700 Subject: [PATCH 057/462] Nonzero implementation in C++ --- src/cunumeric/ndarray.cc | 27 +++++++++++++++++++++++++++ src/cunumeric/ndarray.h | 1 + src/cunumeric/operators.cc | 2 ++ src/cunumeric/operators.h | 2 ++ src/cunumeric/runtime.cc | 6 ++++++ src/cunumeric/runtime.h | 1 + 6 files changed, 39 insertions(+) diff --git a/src/cunumeric/ndarray.cc b/src/cunumeric/ndarray.cc index 5b106b0299..32f9400b83 100644 --- a/src/cunumeric/ndarray.cc +++ b/src/cunumeric/ndarray.cc @@ -190,6 +190,33 @@ void NDArray::dot(NDArray rhs1, NDArray rhs2) runtime->submit(std::move(task)); } +std::vector NDArray::nonzero() +{ + auto runtime = CuNumericRuntime::get_runtime(); + + std::vector outputs; + auto ndim = dim(); + for (int32_t i = 0; i < ndim; ++i) + outputs.emplace_back(runtime->create_array(legate::LegateTypeCode::INT64_LT)); + + auto task = runtime->create_task(CuNumericOpCode::CUNUMERIC_NONZERO); + + auto p_rhs = task->declare_partition(); + + for (auto& output : outputs) { + auto p_lhs = task->declare_partition(); + task->add_output(output.store_, p_lhs); + } + task->add_input(store_, p_rhs); + + // auto broadcast_dims = legate::from_range(1, ndim); + // task->add_constraints(broadcast(p_rhs, broadcast_dims)); + + runtime->submit(std::move(task)); + + return std::move(outputs); +} + legate::LogicalStore NDArray::broadcast(const std::vector& shape, legate::LogicalStore& store) { diff --git a/src/cunumeric/ndarray.h b/src/cunumeric/ndarray.h index 91c03b91bd..18bb2a8fd9 100644 --- a/src/cunumeric/ndarray.h +++ b/src/cunumeric/ndarray.h @@ -58,6 +58,7 @@ class NDArray { void unary_reduction(int32_t op_code, NDArray input); void fill(NDArray fill_value); void dot(NDArray rhs1, NDArray rhs2); + std::vector nonzero(); private: legate::LogicalStore broadcast(const std::vector& shape, legate::LogicalStore& store); diff --git a/src/cunumeric/operators.cc b/src/cunumeric/operators.cc index 8028323bf0..584965570a 100644 --- a/src/cunumeric/operators.cc +++ b/src/cunumeric/operators.cc @@ -143,4 +143,6 @@ NDArray dot(NDArray rhs1, NDArray rhs2) NDArray sum(NDArray input) { return unary_reduction(UnaryRedCode::SUM, std::move(input)); } +std::vector nonzero(NDArray input) { return input.nonzero(); } + } // namespace cunumeric diff --git a/src/cunumeric/operators.h b/src/cunumeric/operators.h index 8fa437a5f5..560e43d37f 100644 --- a/src/cunumeric/operators.h +++ b/src/cunumeric/operators.h @@ -43,4 +43,6 @@ NDArray full(std::vector shape, const Scalar& value); NDArray sum(NDArray input); +std::vector nonzero(NDArray input); + } // namespace cunumeric diff --git a/src/cunumeric/runtime.cc b/src/cunumeric/runtime.cc index 6c83ecdcf0..b746996d26 100644 --- a/src/cunumeric/runtime.cc +++ b/src/cunumeric/runtime.cc @@ -38,6 +38,12 @@ CuNumericRuntime::CuNumericRuntime(legate::Runtime* legate_runtime, legate::Libr { } +NDArray CuNumericRuntime::create_array(legate::LegateTypeCode type) +{ + auto store = legate_runtime_->create_store(type); + return NDArray(std::move(store)); +} + NDArray CuNumericRuntime::create_array(std::vector shape, legate::LegateTypeCode type) { // TODO: We need a type system for cuNumeric and should not use the core types diff --git a/src/cunumeric/runtime.h b/src/cunumeric/runtime.h index ef94d40e7f..6582aec6ba 100644 --- a/src/cunumeric/runtime.h +++ b/src/cunumeric/runtime.h @@ -33,6 +33,7 @@ class CuNumericRuntime { CuNumericRuntime(legate::Runtime* legate_runtime, legate::LibraryContext* context); public: + NDArray create_array(legate::LegateTypeCode type); NDArray create_array(std::vector shape, legate::LegateTypeCode type); legate::LogicalStore create_scalar_store(const Scalar& value); From 839c7d6d1fe48622e260b40568650f976992c544 Mon Sep 17 00:00:00 2001 From: Wonchan Lee Date: Thu, 29 Sep 2022 16:20:36 -0700 Subject: [PATCH 058/462] Post merge cleanup --- cunumeric_cpp.cmake | 14 +++++++++++++- src/cunumeric/cunumeric.cc | 2 +- src/cunumeric/cunumeric_task.h | 1 + .../execution_policy/reduction/scalar_reduction.h | 2 +- src/cunumeric/index/wrap.h | 2 +- src/cunumeric/matrix/solve.h | 2 +- src/cunumeric/search/argwhere.h | 2 +- src/cunumeric/typedefs.h | 1 + src/cunumeric/unary/scalar_unary_red.cu | 2 +- src/cunumeric/unary/scalar_unary_red_template.inl | 2 +- 10 files changed, 22 insertions(+), 8 deletions(-) diff --git a/cunumeric_cpp.cmake b/cunumeric_cpp.cmake index a47038a3bd..b1f8dd4d60 100644 --- a/cunumeric_cpp.cmake +++ b/cunumeric_cpp.cmake @@ -157,6 +157,9 @@ list(APPEND cunumeric_SOURCES src/cunumeric/transform/flip.cc src/cunumeric/arg.cc src/cunumeric/mapper.cc + src/cunumeric/ndarray.cc + src/cunumeric/operators.cc + src/cunumeric/runtime.cc src/cunumeric/cephes/chbevl.cc src/cunumeric/cephes/i0.cc ) @@ -426,9 +429,18 @@ install(TARGETS cunumeric EXPORT cunumeric-exports) install( - FILES ${CMAKE_CURRENT_BINARY_DIR}/include/cunumeric/version_config.hpp + FILES src/cunumeric.h + ${CMAKE_CURRENT_BINARY_DIR}/include/cunumeric/version_config.hpp DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/cunumeric) +install( + FILES src/cunumeric/cunumeric_c.h + src/cunumeric/ndarray.h + src/cunumeric/ndarray.inl + src/cunumeric/operators.h + src/cunumeric/typedefs.h + DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/cunumeric/cunumeric) + if(cunumeric_INSTALL_TBLIS) install(DIRECTORY ${tblis_BINARY_DIR}/lib/ DESTINATION ${lib_dir}) install(DIRECTORY ${tblis_BINARY_DIR}/include/ DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}) diff --git a/src/cunumeric/cunumeric.cc b/src/cunumeric/cunumeric.cc index 86fc1b48e4..a24d19fe8e 100644 --- a/src/cunumeric/cunumeric.cc +++ b/src/cunumeric/cunumeric.cc @@ -65,7 +65,7 @@ void registration_callback(Legion::Machine machine, // Now we can register our mapper with the runtime CuNumeric::mapper_id = context->get_mapper_id(0); - context.register_mapper(new CuNumericMapper(runtime, machine, context), 0); + context->register_mapper(new CuNumericMapper(legion_runtime, machine, *context), 0); } void bootstrapping_callback(Legion::Machine machine, diff --git a/src/cunumeric/cunumeric_task.h b/src/cunumeric/cunumeric_task.h index c35b71f8ab..5e6c85b825 100644 --- a/src/cunumeric/cunumeric_task.h +++ b/src/cunumeric/cunumeric_task.h @@ -17,6 +17,7 @@ #pragma once #include "legate.h" +#include "cunumeric.h" #include "cunumeric/cunumeric_c.h" namespace cunumeric { diff --git a/src/cunumeric/execution_policy/reduction/scalar_reduction.h b/src/cunumeric/execution_policy/reduction/scalar_reduction.h index 7779681bdb..496fa9e307 100644 --- a/src/cunumeric/execution_policy/reduction/scalar_reduction.h +++ b/src/cunumeric/execution_policy/reduction/scalar_reduction.h @@ -16,7 +16,7 @@ #pragma once -#include "cunumeric/cunumeric.h" +#include "cunumeric/cunumeric_task.h" namespace cunumeric { diff --git a/src/cunumeric/index/wrap.h b/src/cunumeric/index/wrap.h index 91c3f23263..2e16407918 100644 --- a/src/cunumeric/index/wrap.h +++ b/src/cunumeric/index/wrap.h @@ -16,7 +16,7 @@ #pragma once -#include "cunumeric/cunumeric.h" +#include "cunumeric/cunumeric_task.h" namespace cunumeric { diff --git a/src/cunumeric/matrix/solve.h b/src/cunumeric/matrix/solve.h index 8cb6835ad5..f41d98015a 100644 --- a/src/cunumeric/matrix/solve.h +++ b/src/cunumeric/matrix/solve.h @@ -16,7 +16,7 @@ #pragma once -#include "cunumeric/cunumeric.h" +#include "cunumeric/cunumeric_task.h" namespace cunumeric { diff --git a/src/cunumeric/search/argwhere.h b/src/cunumeric/search/argwhere.h index 658be6a096..493a3a7cf8 100644 --- a/src/cunumeric/search/argwhere.h +++ b/src/cunumeric/search/argwhere.h @@ -16,7 +16,7 @@ #pragma once -#include "cunumeric/cunumeric.h" +#include "cunumeric/cunumeric_task.h" namespace cunumeric { diff --git a/src/cunumeric/typedefs.h b/src/cunumeric/typedefs.h index 7d062244f3..628d208757 100644 --- a/src/cunumeric/typedefs.h +++ b/src/cunumeric/typedefs.h @@ -22,6 +22,7 @@ namespace cunumeric { +using Array = legate::Store; using Scalar = legate::Scalar; } // namespace cunumeric diff --git a/src/cunumeric/unary/scalar_unary_red.cu b/src/cunumeric/unary/scalar_unary_red.cu index e94d6bc30b..389e4966f2 100644 --- a/src/cunumeric/unary/scalar_unary_red.cu +++ b/src/cunumeric/unary/scalar_unary_red.cu @@ -14,7 +14,7 @@ * */ -#include "cunumeric/cunumeric.h" +#include "cunumeric/cunumeric_task.h" #include "cunumeric/unary/scalar_unary_red.h" #include "cunumeric/unary/scalar_unary_red_template.inl" #include "cunumeric/execution_policy/reduction/scalar_reduction.cuh" diff --git a/src/cunumeric/unary/scalar_unary_red_template.inl b/src/cunumeric/unary/scalar_unary_red_template.inl index 482d96187f..fe95ba9c7e 100644 --- a/src/cunumeric/unary/scalar_unary_red_template.inl +++ b/src/cunumeric/unary/scalar_unary_red_template.inl @@ -18,7 +18,7 @@ // Useful for IDEs #include -#include "cunumeric/cunumeric.h" +#include "cunumeric/cunumeric_task.h" #include "cunumeric/unary/scalar_unary_red.h" #include "cunumeric/unary/unary_red_util.h" #include "cunumeric/pitches.h" From da3319799e5676d6375db8a6b4dd774fc016a5ac Mon Sep 17 00:00:00 2001 From: Marcin Zalewski Date: Fri, 27 Jan 2023 10:32:30 -0800 Subject: [PATCH 059/462] Update the architectures built in conda package (#770) (#771) Co-authored-by: Marcin Zalewski --- conda/conda-build/build.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/conda/conda-build/build.sh b/conda/conda-build/build.sh index d0df680087..19b1f1f48d 100644 --- a/conda/conda-build/build.sh +++ b/conda/conda-build/build.sh @@ -13,7 +13,7 @@ if [ -z "$CPU_ONLY" ]; then # cutensor, relying on the conda cutensor package CMAKE_ARGS+=" -Dcutensor_DIR=$PREFIX --DCMAKE_CUDA_ARCHITECTURES:LIST=60-real;70-real;75-real;80-real;86 +-DCMAKE_CUDA_ARCHITECTURES:LIST=60-real;70-real;75-real;80-real;90 " else # When we build without cuda, we need to provide the location of curand From 1817dc773dcf95f7214416bab33d8aee3fcf2926 Mon Sep 17 00:00:00 2001 From: Marcin Zalewski Date: Mon, 30 Jan 2023 14:33:03 -0800 Subject: [PATCH 060/462] Revert "Update the architectures built in conda package (#770) (#771)" (#772) This reverts commit da3319799e5676d6375db8a6b4dd774fc016a5ac. Co-authored-by: Marcin Zalewski --- conda/conda-build/build.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/conda/conda-build/build.sh b/conda/conda-build/build.sh index 19b1f1f48d..d0df680087 100644 --- a/conda/conda-build/build.sh +++ b/conda/conda-build/build.sh @@ -13,7 +13,7 @@ if [ -z "$CPU_ONLY" ]; then # cutensor, relying on the conda cutensor package CMAKE_ARGS+=" -Dcutensor_DIR=$PREFIX --DCMAKE_CUDA_ARCHITECTURES:LIST=60-real;70-real;75-real;80-real;90 +-DCMAKE_CUDA_ARCHITECTURES:LIST=60-real;70-real;75-real;80-real;86 " else # When we build without cuda, we need to provide the location of curand From 88244f19fdf19755c46b8eeec9be01421e3aaee6 Mon Sep 17 00:00:00 2001 From: Wonchan Lee Date: Wed, 22 Feb 2023 22:41:07 -0800 Subject: [PATCH 061/462] Add cunumeric.h to the list of headers to install --- cunumeric_cpp.cmake | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/cunumeric_cpp.cmake b/cunumeric_cpp.cmake index 062a745913..364117976e 100644 --- a/cunumeric_cpp.cmake +++ b/cunumeric_cpp.cmake @@ -442,7 +442,8 @@ install( DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/cunumeric) install( - FILES src/cunumeric/cunumeric_c.h + FILES src/cunumeric.h + src/cunumeric/cunumeric_c.h src/cunumeric/ndarray.h src/cunumeric/ndarray.inl src/cunumeric/operators.h From 5a275d17d6d58331f29d25ccc0ffb2cc849cac47 Mon Sep 17 00:00:00 2001 From: Wonchan Lee Date: Wed, 22 Feb 2023 22:44:47 -0800 Subject: [PATCH 062/462] Rename shared memory allocations to avoid naming collisions --- src/cunumeric/stat/bincount.cu | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/cunumeric/stat/bincount.cu b/src/cunumeric/stat/bincount.cu index 921288fb16..a958bcf895 100644 --- a/src/cunumeric/stat/bincount.cu +++ b/src/cunumeric/stat/bincount.cu @@ -88,8 +88,8 @@ static __global__ void __launch_bounds__(THREADS_PER_BLOCK, MIN_CTAS_PER_SM) const size_t num_bins, Point<1> origin) { - extern __shared__ char array[]; - auto bins = reinterpret_cast(array); + extern __shared__ char __bins[]; + auto bins = reinterpret_cast(__bins); _bincount(bins, rhs, volume, num_bins, origin); // Now do the atomics out to global memory for (int32_t bin = threadIdx.x; bin < num_bins; bin += blockDim.x) { @@ -120,8 +120,8 @@ static __global__ void __launch_bounds__(THREADS_PER_BLOCK, MIN_CTAS_PER_SM) const size_t num_bins, Point<1> origin) { - extern __shared__ char array[]; - auto bins = reinterpret_cast(array); + extern __shared__ char __bins[]; + auto bins = reinterpret_cast(__bins); _weighted_bincount(bins, rhs, weights, volume, num_bins, origin); // Now do the atomics out to global memory for (int32_t bin = threadIdx.x; bin < num_bins; bin += blockDim.x) { From 1d11d1891b939f396df4d53545aa1eeda684fad8 Mon Sep 17 00:00:00 2001 From: Wonchan Lee Date: Wed, 22 Feb 2023 22:56:08 -0800 Subject: [PATCH 063/462] Revert "Add cunumeric.h to the list of headers to install" This reverts commit 88244f19fdf19755c46b8eeec9be01421e3aaee6. --- cunumeric_cpp.cmake | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/cunumeric_cpp.cmake b/cunumeric_cpp.cmake index 364117976e..062a745913 100644 --- a/cunumeric_cpp.cmake +++ b/cunumeric_cpp.cmake @@ -442,8 +442,7 @@ install( DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/cunumeric) install( - FILES src/cunumeric.h - src/cunumeric/cunumeric_c.h + FILES src/cunumeric/cunumeric_c.h src/cunumeric/ndarray.h src/cunumeric/ndarray.inl src/cunumeric/operators.h From ad93941f9c76c3b7b50c08df8b20580adeb48af7 Mon Sep 17 00:00:00 2001 From: Wonchan Lee Date: Wed, 22 Feb 2023 23:23:31 -0800 Subject: [PATCH 064/462] Make cuNumeric headers public --- cunumeric_cpp.cmake | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cunumeric_cpp.cmake b/cunumeric_cpp.cmake index 062a745913..ed5e055693 100644 --- a/cunumeric_cpp.cmake +++ b/cunumeric_cpp.cmake @@ -405,10 +405,10 @@ target_compile_definitions(cunumeric "$<$:${cunumeric_CUDA_DEFS}>") target_include_directories(cunumeric - PRIVATE + PUBLIC $ INTERFACE - $ + $ ) if(Legion_USE_CUDA) From c9e3977f036e7e3fe43e8aaf7673763a4ecb0646 Mon Sep 17 00:00:00 2001 From: Marcin Zalewski Date: Tue, 7 Mar 2023 13:54:09 -0800 Subject: [PATCH 065/462] Update BUILD.md (#831) (#832) Backport #831 Co-authored-by: Bryan Van de Ven --- BUILD.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/BUILD.md b/BUILD.md index 6adcc49166..9f4547e000 100644 --- a/BUILD.md +++ b/BUILD.md @@ -77,4 +77,4 @@ The Python source tree and CMake build tree are now available with the environme for running cuNumeric programs. The diagram below illustrates the complete workflow for building both Legate core and cuNumeric. -drawing +drawing From 9cc2afe9269816ea73ed83059c169ca97456720b Mon Sep 17 00:00:00 2001 From: Mark Vaz Date: Tue, 7 Mar 2023 16:20:29 -0800 Subject: [PATCH 066/462] Add support for Python 3.11 (#830) * updating build config for py311 and clearing an unused comment * Document support for python 3.11 --------- Co-authored-by: Manolis Papadakis --- conda/conda-build/conda_build_config.yaml | 1 + conda/conda-build/meta.yaml | 1 - setup.py | 1 + 3 files changed, 2 insertions(+), 1 deletion(-) diff --git a/conda/conda-build/conda_build_config.yaml b/conda/conda-build/conda_build_config.yaml index 3b3c5fa231..272e3b2781 100644 --- a/conda/conda-build/conda_build_config.yaml +++ b/conda/conda-build/conda_build_config.yaml @@ -5,6 +5,7 @@ gpu_enabled: python: - "3.9,!=3.9.7" - 3.10 + - 3.11 numpy_version: - ">=1.22" diff --git a/conda/conda-build/meta.yaml b/conda/conda-build/meta.yaml index d16d5fd71d..555336ee4e 100644 --- a/conda/conda-build/meta.yaml +++ b/conda/conda-build/meta.yaml @@ -137,7 +137,6 @@ requirements: {% else %} - legate-core ={{ core_version }} - cuda-cudart >={{ cuda_version }} - # - libcutensor >=1.3 - cutensor >=1.3 =*_* - libcublas - libcusolver =11.4.1.48-0 diff --git a/setup.py b/setup.py index 95bb8c88b2..1ec5180968 100644 --- a/setup.py +++ b/setup.py @@ -35,6 +35,7 @@ "Programming Language :: Python", "Programming Language :: Python :: 3.9", "Programming Language :: Python :: 3.10", + "Programming Language :: Python :: 3.11", ], packages=find_packages( where=".", From d0275574cf840045eb310d73916458c037046722 Mon Sep 17 00:00:00 2001 From: Wonchan Lee Date: Mon, 3 Apr 2023 13:10:55 -0700 Subject: [PATCH 067/462] Catch up the accessor API change * Catch up the access API change * Start using scalar optimization * Use more specific return types See merge request legate/cunumeric.internal!3 --- src/cunumeric/ndarray.h | 2 +- src/cunumeric/ndarray.inl | 4 ++-- src/cunumeric/runtime.cc | 4 ++-- src/cunumeric/runtime.h | 2 +- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/cunumeric/ndarray.h b/src/cunumeric/ndarray.h index 18bb2a8fd9..7fc863e90b 100644 --- a/src/cunumeric/ndarray.h +++ b/src/cunumeric/ndarray.h @@ -44,7 +44,7 @@ class NDArray { public: template - legate::AccessorRW get_accessor(); + legate::AccessorRO get_read_accessor(); public: NDArray operator+(const NDArray& other) const; diff --git a/src/cunumeric/ndarray.inl b/src/cunumeric/ndarray.inl index 4063dc3fc1..7340b2d189 100644 --- a/src/cunumeric/ndarray.inl +++ b/src/cunumeric/ndarray.inl @@ -17,12 +17,12 @@ namespace cunumeric { template -legate::AccessorRW NDArray::get_accessor() +legate::AccessorRO NDArray::get_read_accessor() { auto context = get_context(); auto mapped = store_.get_physical_store(context); auto shape = mapped->shape(); - return mapped->read_write_accessor(shape); + return mapped->read_accessor(shape); } } // namespace cunumeric diff --git a/src/cunumeric/runtime.cc b/src/cunumeric/runtime.cc index b746996d26..39242744d5 100644 --- a/src/cunumeric/runtime.cc +++ b/src/cunumeric/runtime.cc @@ -47,7 +47,7 @@ NDArray CuNumericRuntime::create_array(legate::LegateTypeCode type) NDArray CuNumericRuntime::create_array(std::vector shape, legate::LegateTypeCode type) { // TODO: We need a type system for cuNumeric and should not use the core types - auto store = legate_runtime_->create_store(shape, type); + auto store = legate_runtime_->create_store(shape, type, true /*optimize_scalar*/); return NDArray(std::move(store)); } @@ -124,7 +124,7 @@ Legion::ReductionOpID CuNumericRuntime::get_reduction_op(UnaryRedCode op, return op_dispatch(op, generate_redop_fn{}, type); } -std::unique_ptr CuNumericRuntime::create_task(CuNumericOpCode op_code) +std::unique_ptr CuNumericRuntime::create_task(CuNumericOpCode op_code) { return legate_runtime_->create_task(context_, op_code); } diff --git a/src/cunumeric/runtime.h b/src/cunumeric/runtime.h index 6582aec6ba..ae53d4c3ac 100644 --- a/src/cunumeric/runtime.h +++ b/src/cunumeric/runtime.h @@ -42,7 +42,7 @@ class CuNumericRuntime { Legion::ReductionOpID get_reduction_op(UnaryRedCode op, legate::LegateTypeCode type); public: - std::unique_ptr create_task(CuNumericOpCode op_code); + std::unique_ptr create_task(CuNumericOpCode op_code); void submit(std::unique_ptr task); public: From 6e505333f04e585a1db743b0dbfdab5685ab2c0f Mon Sep 17 00:00:00 2001 From: Wonchan Lee Date: Thu, 18 May 2023 09:02:11 -0700 Subject: [PATCH 068/462] Use broadcast constraints in nonzero * Use broadcast constraints in nonzero See merge request legate/cunumeric.internal!5 --- src/cunumeric/ndarray.cc | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/cunumeric/ndarray.cc b/src/cunumeric/ndarray.cc index 7706d50e38..ef5ba253ac 100644 --- a/src/cunumeric/ndarray.cc +++ b/src/cunumeric/ndarray.cc @@ -208,8 +208,7 @@ std::vector NDArray::nonzero() } task->add_input(store_, p_rhs); - // auto broadcast_dims = legate::from_range(1, ndim); - // task->add_constraints(broadcast(p_rhs, broadcast_dims)); + task->add_constraint(legate::broadcast(p_rhs, legate::from_range(1, ndim))); runtime->submit(std::move(task)); From ff5240ea307a85de31eafdfea55b868a7cf9673d Mon Sep 17 00:00:00 2001 From: Wonchan Lee Date: Thu, 25 May 2023 17:18:52 -0700 Subject: [PATCH 069/462] Slicing and 2D stencil example * Missing file * Use legate::Slice * 2D stencil example in C++ * Implement slicing, assignment, and multiplication to port 2D stencil * WIP slicing implementation See merge request legate/cunumeric.internal!8 --- examples/cpp/stencil/CMakeLists.txt | 31 +++++++ examples/cpp/stencil/editable-install.sh | 20 +++++ examples/cpp/stencil/stencil.cc | 102 +++++++++++++++++++++++ src/cunumeric.h | 2 +- src/cunumeric/cunumeric_task.h | 2 +- src/cunumeric/ndarray.cc | 59 ++++++++++++- src/cunumeric/ndarray.h | 12 +++ src/cunumeric/operators.cc | 29 ++++++- src/cunumeric/operators.h | 4 + src/cunumeric/runtime.cc | 36 ++++++-- src/cunumeric/runtime.h | 4 + src/cunumeric/slice.h | 27 ++++++ 12 files changed, 317 insertions(+), 11 deletions(-) create mode 100644 examples/cpp/stencil/CMakeLists.txt create mode 100755 examples/cpp/stencil/editable-install.sh create mode 100644 examples/cpp/stencil/stencil.cc create mode 100644 src/cunumeric/slice.h diff --git a/examples/cpp/stencil/CMakeLists.txt b/examples/cpp/stencil/CMakeLists.txt new file mode 100644 index 0000000000..f547ff4a1e --- /dev/null +++ b/examples/cpp/stencil/CMakeLists.txt @@ -0,0 +1,31 @@ +#============================================================================= +# Copyright 2023 NVIDIA Corporation +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +#============================================================================= + +cmake_minimum_required(VERSION 3.22.1 FATAL_ERROR) + +project(stencil VERSION 0.1 LANGUAGES C CXX) + +if (NOT CMAKE_CXX_STANDARD) + set(CMAKE_CXX_STANDARD 17) +endif() + +find_package(cunumeric REQUIRED) + +add_executable(stencil stencil.cc) + +target_link_libraries(stencil PRIVATE cunumeric::cunumeric) + +install(TARGETS stencil DESTINATION "${CMAKE_CURRENT_BINARY_DIR}/cmake-install") diff --git a/examples/cpp/stencil/editable-install.sh b/examples/cpp/stencil/editable-install.sh new file mode 100755 index 0000000000..3b8abed26c --- /dev/null +++ b/examples/cpp/stencil/editable-install.sh @@ -0,0 +1,20 @@ +#!/bin/bash + +# Copyright 2023 NVIDIA Corporation +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +cunumeric_root=`python -c 'import cunumeric.install_info as i; from pathlib import Path; print(Path(i.libpath).parent.resolve())'` +echo "Using cuNumeric at $cunumeric_root" +cmake -S . -B build -D cunumeric_ROOT="$cunumeric_root" -D CMAKE_BUILD_TYPE=Debug +cmake --build build --parallel 8 diff --git a/examples/cpp/stencil/stencil.cc b/examples/cpp/stencil/stencil.cc new file mode 100644 index 0000000000..179a28f796 --- /dev/null +++ b/examples/cpp/stencil/stencil.cc @@ -0,0 +1,102 @@ +/* Copyright 2023 NVIDIA Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +#include "legate.h" +#include "cunumeric.h" +#include "realm/cmdline.h" + +#include + +namespace stencil { + +using cunumeric::open; +using cunumeric::slice; + +struct Config { + bool timing{false}; + int32_t iter{100}; + int32_t warmup{5}; + uint64_t N{100}; +}; + +void print_array(cunumeric::NDArray array) +{ + auto acc = array.get_read_accessor(); + auto& shape = array.shape(); + std::stringstream ss; + for (uint32_t i = 0; i < shape[0]; ++i) { + for (uint32_t j = 0; j < shape[0]; ++j) { + if (j > 0) ss << " "; + ss << std::setw(8) << std::setprecision(5) << acc[i][j]; + } + ss << std::endl; + } + std::cerr << std::move(ss).str(); +} + +cunumeric::NDArray initialize(uint64_t N) +{ + auto grid = cunumeric::zeros({N + 2, N + 2}); + grid[{slice(), slice(0, 1)}].assign(-273.15); + grid[{slice(), slice(-1, open)}].assign(-273.15); + grid[{slice(-1, open), slice()}].assign(-273.15); + grid[{slice(0, 1), slice()}].assign(40.0); + return std::move(grid); +} + +void stencil(const Config& config) +{ + auto grid = initialize(config.N); + + auto center = grid[{slice(1, -1), slice(1, -1)}]; + auto north = grid[{slice(0, -2), slice(1, -1)}]; + auto east = grid[{slice(1, -1), slice(2, open)}]; + auto west = grid[{slice(1, -1), slice(0, -2)}]; + auto south = grid[{slice(2, open), slice{1, -1}}]; + + auto max_iter = config.iter + config.warmup; + for (int32_t iter = 0; iter < max_iter; ++iter) { + auto average = center + north + east + west + south; + auto work = average * 0.2; + center.assign(work); + // print_array(average); + // print_array(center); + }; +} + +} // namespace stencil + +int main(int argc, char** argv) +{ + legate::initialize(argc, argv); + + cunumeric::initialize(argc, argv); + + legate::start(argc, argv); + + stencil::Config config{}; + + Realm::CommandLineParser cp; + cp.add_option_int("--iter", config.iter) + .add_option_int("--warmup", config.warmup) + .add_option_int("--num", config.N) + .add_option_bool("--time", config.timing) + .parse_command_line(argc, argv); + + stencil::stencil(config); + + return legate::wait_for_shutdown(); +} diff --git a/src/cunumeric.h b/src/cunumeric.h index 28a2c9d53a..be10e37614 100644 --- a/src/cunumeric.h +++ b/src/cunumeric.h @@ -16,4 +16,4 @@ #include "cunumeric/ndarray.h" #include "cunumeric/operators.h" -#include "cunumeric/typedefs.h" +#include "cunumeric/slice.h" diff --git a/src/cunumeric/cunumeric_task.h b/src/cunumeric/cunumeric_task.h index 769d624a47..7e77eb0059 100644 --- a/src/cunumeric/cunumeric_task.h +++ b/src/cunumeric/cunumeric_task.h @@ -17,7 +17,7 @@ #pragma once #include "legate.h" -#include "cunumeric.h" +#include "cunumeric/typedefs.h" #include "cunumeric/cunumeric_c.h" namespace cunumeric { diff --git a/src/cunumeric/ndarray.cc b/src/cunumeric/ndarray.cc index ef5ba253ac..baf053bdb7 100644 --- a/src/cunumeric/ndarray.cc +++ b/src/cunumeric/ndarray.cc @@ -19,6 +19,7 @@ #include "cunumeric/operators.h" #include "cunumeric/runtime.h" #include "cunumeric/random/rand_util.h" +#include "cunumeric/unary/unary_op_util.h" #include "cunumeric/unary/unary_red_util.h" namespace cunumeric { @@ -48,12 +49,64 @@ static std::vector compute_strides(const std::vector& shape) NDArray NDArray::operator+(const NDArray& other) const { return add(*this, other); } +NDArray NDArray::operator+(const legate::Scalar& other) const +{ + auto runtime = CuNumericRuntime::get_runtime(); + auto scalar = runtime->create_scalar_store(other); + return operator+(NDArray(std::move(scalar))); +} + NDArray& NDArray::operator+=(const NDArray& other) { add(*this, other, *this); return *this; } +NDArray NDArray::operator*(const NDArray& other) const { return multiply(*this, other); } + +NDArray NDArray::operator*(const legate::Scalar& other) const +{ + auto runtime = CuNumericRuntime::get_runtime(); + auto scalar = runtime->create_scalar_store(other); + return operator*(NDArray(std::move(scalar))); +} + +NDArray& NDArray::operator*=(const NDArray& other) +{ + multiply(*this, other, *this); + return *this; +} + +NDArray NDArray::operator[](std::initializer_list slices) const +{ + if (slices.size() > static_cast(dim())) { + std::string err_msg = "Can't slice a " + std::to_string(dim()) + "-D ndarray with " + + std::to_string(slices.size()) + " slices"; + throw std::invalid_argument(std::move(err_msg)); + } + + uint32_t dim = 0; + auto sliced = store_; + for (const auto& sl : slices) { + sliced = sliced.slice(0, sl); + ++dim; + } + + return NDArray(std::move(sliced)); +} + +void NDArray::assign(const NDArray& other) +{ + unary_op(static_cast(UnaryOpCode::COPY), other); +} + +void NDArray::assign(const legate::Scalar& other) +{ + auto runtime = CuNumericRuntime::get_runtime(); + auto scalar = runtime->create_scalar_store(other); + assign(NDArray(std::move(scalar))); +} + void NDArray::random(int32_t gen_code) { auto runtime = CuNumericRuntime::get_runtime(); @@ -90,6 +143,8 @@ void NDArray::fill(const Scalar& value, bool argval) void NDArray::binary_op(int32_t op_code, NDArray rhs1, NDArray rhs2) { + if (rhs1.type() != rhs2.type()) throw std::invalid_argument("Operands must have the same type"); + auto runtime = CuNumericRuntime::get_runtime(); auto task = runtime->create_task(CuNumericOpCode::CUNUMERIC_BINARY_OP); @@ -122,8 +177,10 @@ void NDArray::unary_op(int32_t op_code, NDArray input) auto p_out = task->declare_partition(); auto p_in = task->declare_partition(); + auto rhs = broadcast(shape(), input.store_); + task->add_output(store_, p_out); - task->add_input(input.store_, p_in); + task->add_input(rhs, p_in); task->add_scalar_arg(legate::Scalar(op_code)); task->add_constraint(align(p_out, p_in)); diff --git a/src/cunumeric/ndarray.h b/src/cunumeric/ndarray.h index a2192cf92f..729ff0f70a 100644 --- a/src/cunumeric/ndarray.h +++ b/src/cunumeric/ndarray.h @@ -17,8 +17,10 @@ #pragma once #include +#include #include "legate.h" +#include "cunumeric/slice.h" #include "cunumeric/typedefs.h" namespace cunumeric { @@ -48,7 +50,17 @@ class NDArray { public: NDArray operator+(const NDArray& other) const; + NDArray operator+(const legate::Scalar& other) const; NDArray& operator+=(const NDArray& other); + NDArray operator*(const NDArray& other) const; + NDArray operator*(const legate::Scalar& other) const; + NDArray& operator*=(const NDArray& other); + NDArray operator[](std::initializer_list slices) const; + + public: + // Copy the contents of the other ndarray to this one + void assign(const NDArray& other); + void assign(const legate::Scalar& other); public: void random(int32_t gen_code); diff --git a/src/cunumeric/operators.cc b/src/cunumeric/operators.cc index 942b8752a4..c402e7c2cc 100644 --- a/src/cunumeric/operators.cc +++ b/src/cunumeric/operators.cc @@ -75,8 +75,6 @@ NDArray unary_reduction(UnaryRedCode op_code, NDArray input) NDArray binary_op(BinaryOpCode op_code, NDArray rhs1, NDArray rhs2, std::optional out) { - assert(rhs1.type() == rhs2.type()); - auto runtime = CuNumericRuntime::get_runtime(); if (!out.has_value()) { auto out_shape = broadcast_shapes({rhs1, rhs2}); @@ -93,6 +91,11 @@ NDArray add(NDArray rhs1, NDArray rhs2, std::optional out) return binary_op(BinaryOpCode::ADD, std::move(rhs1), std::move(rhs2), std::move(out)); } +NDArray multiply(NDArray rhs1, NDArray rhs2, std::optional out) +{ + return binary_op(BinaryOpCode::MULTIPLY, std::move(rhs1), std::move(rhs2), std::move(out)); +} + NDArray negative(NDArray input) { return unary_op(UnaryOpCode::NEGATIVE, std::move(input)); } NDArray random(std::vector shape) @@ -103,6 +106,28 @@ NDArray random(std::vector shape) return std::move(out); } +namespace { + +struct generate_zero_fn { + template + legate::Scalar operator()() + { + using VAL = legate::legate_type_of; + return legate::Scalar(VAL(0)); + } +}; + +} // namespace + +NDArray zeros(std::vector shape, std::unique_ptr type) +{ + if (nullptr == type) type = legate::float64(); + if (static_cast(type->code) >= static_cast(legate::Type::Code::FIXED_ARRAY)) + throw std::invalid_argument("Type must be a primitive type"); + auto zero = legate::type_dispatch(type->code, generate_zero_fn{}); + return full(shape, zero); +} + NDArray full(std::vector shape, const Scalar& value) { auto runtime = CuNumericRuntime::get_runtime(); diff --git a/src/cunumeric/operators.h b/src/cunumeric/operators.h index fd58d9cc10..1d8c4086fc 100644 --- a/src/cunumeric/operators.h +++ b/src/cunumeric/operators.h @@ -33,12 +33,16 @@ NDArray abs(NDArray input); NDArray add(NDArray rhs1, NDArray rhs2, std::optional out = std::nullopt); +NDArray multiply(NDArray rhs1, NDArray rhs2, std::optional out = std::nullopt); + NDArray dot(NDArray rhs1, NDArray rhs2); NDArray negative(NDArray input); NDArray random(std::vector shape); +NDArray zeros(std::vector shape, std::unique_ptr type = nullptr); + NDArray full(std::vector shape, const Scalar& value); NDArray sum(NDArray input); diff --git a/src/cunumeric/runtime.cc b/src/cunumeric/runtime.cc index d0d9c504bb..9474bba25e 100644 --- a/src/cunumeric/runtime.cc +++ b/src/cunumeric/runtime.cc @@ -69,15 +69,25 @@ legate::LogicalStore CuNumericRuntime::create_scalar_store(const Scalar& value) struct generate_identity_fn { template struct generator { - template ::valid>* = nullptr> - Scalar operator()() + template ::valid && !is_arg_reduce::value>* = nullptr> + Scalar operator()(const legate::Type&) { auto value = UnaryRedOp::OP::identity; return Scalar(value); } + template ::valid && is_arg_reduce::value>* = nullptr> + Scalar operator()(const legate::Type& type) + { + auto value = UnaryRedOp::OP::identity; + auto& argred_type = CuNumericRuntime::get_runtime()->get_argred_type(type); + return Scalar(value, argred_type.clone()); + } + template ::valid>* = nullptr> - Scalar operator()() + Scalar operator()(const legate::Type&) { assert(false); return Scalar(); @@ -85,9 +95,9 @@ struct generate_identity_fn { }; template - Scalar operator()(legate::Type::Code code) + Scalar operator()(const legate::Type& type) { - return legate::type_dispatch(code, generator{}); + return legate::type_dispatch(type.code, generator{}, type); } }; @@ -97,11 +107,25 @@ Scalar CuNumericRuntime::get_reduction_identity(UnaryRedCode op, const legate::T auto finder = identities.find(key); if (identities.end() != finder) return finder->second; - auto identity = op_dispatch(op, generate_identity_fn{}, type.code); + auto identity = op_dispatch(op, generate_identity_fn{}, type); identities[key] = identity; return identity; } +const legate::Type& CuNumericRuntime::get_argred_type(const legate::Type& value_type) +{ + auto finder = argred_types_.find(value_type.code); + if (finder != argred_types_.end()) return *finder->second; + + std::vector> field_types{}; + field_types.push_back(legate::int64()); + field_types.push_back(value_type.clone()); + auto argred_type = legate::struct_type(std::move(field_types), true /*align*/); + const auto& result = *argred_type; + argred_types_.insert({value_type.code, std::move(argred_type)}); + return result; +} + namespace { const std::unordered_map TO_CORE_REDOP = { diff --git a/src/cunumeric/runtime.h b/src/cunumeric/runtime.h index 9a1d8c22c2..4803fe00e6 100644 --- a/src/cunumeric/runtime.h +++ b/src/cunumeric/runtime.h @@ -43,6 +43,9 @@ class CuNumericRuntime { Scalar get_reduction_identity(UnaryRedCode op, const legate::Type& type); Legion::ReductionOpID get_reduction_op(UnaryRedCode op, const legate::Type& type); + public: + const legate::Type& get_argred_type(const legate::Type& value_type); + public: std::unique_ptr create_task(CuNumericOpCode op_code); void submit(std::unique_ptr task); @@ -64,6 +67,7 @@ class CuNumericRuntime { legate::Runtime* legate_runtime_; legate::LibraryContext* context_; uint32_t next_epoch_{0}; + std::unordered_map> argred_types_; }; } // namespace cunumeric diff --git a/src/cunumeric/slice.h b/src/cunumeric/slice.h new file mode 100644 index 0000000000..3e816ae3b7 --- /dev/null +++ b/src/cunumeric/slice.h @@ -0,0 +1,27 @@ +/* Copyright 2023 NVIDIA Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +#pragma once + +#include + +namespace cunumeric { + +using slice = legate::Slice; + +constexpr auto open = legate::Slice::OPEN; + +} // namespace cunumeric From ee88c9fe3633adf3944d500661f62abce7a87e8f Mon Sep 17 00:00:00 2001 From: Wonchan Lee Date: Thu, 1 Jun 2023 17:30:07 -0700 Subject: [PATCH 070/462] Implementation for np.unique in C++ * Implementation for np.unique in C++ See merge request legate/cunumeric.internal!9 --- src/cunumeric/ndarray.cc | 20 ++++++++++++++++++++ src/cunumeric/ndarray.h | 1 + src/cunumeric/operators.cc | 2 ++ src/cunumeric/operators.h | 2 ++ 4 files changed, 25 insertions(+) diff --git a/src/cunumeric/ndarray.cc b/src/cunumeric/ndarray.cc index baf053bdb7..0d5124c237 100644 --- a/src/cunumeric/ndarray.cc +++ b/src/cunumeric/ndarray.cc @@ -272,6 +272,26 @@ std::vector NDArray::nonzero() return std::move(outputs); } +NDArray NDArray::unique() +{ + auto& machine = legate::Runtime::get_runtime()->get_machine(); + bool has_gpus = machine.count(legate::mapping::TaskTarget::GPU) > 0; + + auto runtime = CuNumericRuntime::get_runtime(); + auto result = runtime->create_array(type()); + + auto task = runtime->create_task(CuNumericOpCode::CUNUMERIC_UNIQUE); + auto part_out = task->declare_partition(); + auto part_in = task->declare_partition(); + task->add_output(result.store_, part_out); + task->add_input(store_, part_in); + task->add_communicator("nccl"); + if (!has_gpus) + task->add_constraint(legate::broadcast(part_in, legate::from_range(0, dim()))); + runtime->submit(std::move(task)); + return result; +} + legate::LogicalStore NDArray::broadcast(const std::vector& shape, legate::LogicalStore& store) { diff --git a/src/cunumeric/ndarray.h b/src/cunumeric/ndarray.h index 729ff0f70a..54bdf91c7b 100644 --- a/src/cunumeric/ndarray.h +++ b/src/cunumeric/ndarray.h @@ -71,6 +71,7 @@ class NDArray { void fill(NDArray fill_value); void dot(NDArray rhs1, NDArray rhs2); std::vector nonzero(); + NDArray unique(); private: legate::LogicalStore broadcast(const std::vector& shape, legate::LogicalStore& store); diff --git a/src/cunumeric/operators.cc b/src/cunumeric/operators.cc index c402e7c2cc..27df42cc1d 100644 --- a/src/cunumeric/operators.cc +++ b/src/cunumeric/operators.cc @@ -168,6 +168,8 @@ NDArray dot(NDArray rhs1, NDArray rhs2) NDArray sum(NDArray input) { return unary_reduction(UnaryRedCode::SUM, std::move(input)); } +NDArray unique(NDArray input) { return input.unique(); } + std::vector nonzero(NDArray input) { return input.nonzero(); } } // namespace cunumeric diff --git a/src/cunumeric/operators.h b/src/cunumeric/operators.h index 1d8c4086fc..65975f2062 100644 --- a/src/cunumeric/operators.h +++ b/src/cunumeric/operators.h @@ -47,6 +47,8 @@ NDArray full(std::vector shape, const Scalar& value); NDArray sum(NDArray input); +NDArray unique(NDArray input); + std::vector nonzero(NDArray input); } // namespace cunumeric From 93bcce05d97fa6b171f20bb6b0aec4c936919947 Mon Sep 17 00:00:00 2001 From: Robin Wang Date: Thu, 1 Jun 2023 22:38:45 -0700 Subject: [PATCH 071/462] Implement eye and trilu in C++ * fix typo * rebase from cpp-branch-23.07 and fix typo. * Add output type for eye API. * Implement eye and trilu in C++ * Address comments. * Add output type for eye API. * Implement eye and trilu in C++ * Implement eye and trilu in C++ See merge request legate/cunumeric.internal!7 --- src/cunumeric/ndarray.cc | 54 ++++++++++++++++++++++++++++++++++++++ src/cunumeric/ndarray.h | 2 ++ src/cunumeric/operators.cc | 38 +++++++++++++++++++++++++++ src/cunumeric/operators.h | 6 +++++ 4 files changed, 100 insertions(+) diff --git a/src/cunumeric/ndarray.cc b/src/cunumeric/ndarray.cc index 0d5124c237..a158e70751 100644 --- a/src/cunumeric/ndarray.cc +++ b/src/cunumeric/ndarray.cc @@ -24,6 +24,19 @@ namespace cunumeric { +namespace { + +struct generate_zero_fn { + template + legate::Scalar operator()() + { + using VAL = legate::legate_type_of; + return legate::Scalar(VAL(0)); + } +}; + +} // namespace + NDArray::NDArray(legate::LogicalStore&& store) : store_(std::forward(store)) { } @@ -141,6 +154,47 @@ void NDArray::fill(const Scalar& value, bool argval) runtime->submit(std::move(task)); } +void NDArray::eye(int32_t k) +{ + assert(dim() == 2); + + auto zero = legate::type_dispatch(type().code, generate_zero_fn{}); + fill(zero, false); + + auto runtime = CuNumericRuntime::get_runtime(); + + auto task = runtime->create_task(CuNumericOpCode::CUNUMERIC_EYE); + auto p_lhs = task->declare_partition(); + + task->add_input(store_, p_lhs); + task->add_output(store_, p_lhs); + task->add_scalar_arg(legate::Scalar(k)); + + runtime->submit(std::move(task)); +} + +void NDArray::trilu(NDArray rhs, int32_t k, bool lower) +{ + auto runtime = CuNumericRuntime::get_runtime(); + + auto task = runtime->create_task(CuNumericOpCode::CUNUMERIC_TRILU); + auto p_lhs = task->declare_partition(); + auto p_rhs = task->declare_partition(); + + auto& out_shape = shape(); + rhs = rhs.broadcast(out_shape, rhs.store_); + + task->add_scalar_arg(legate::Scalar(lower)); + task->add_scalar_arg(legate::Scalar(k)); + + task->add_output(store_, p_lhs); + task->add_input(rhs.store_, p_rhs); + + task->add_constraint(align(p_lhs, p_rhs)); + + runtime->submit(std::move(task)); +} + void NDArray::binary_op(int32_t op_code, NDArray rhs1, NDArray rhs2) { if (rhs1.type() != rhs2.type()) throw std::invalid_argument("Operands must have the same type"); diff --git a/src/cunumeric/ndarray.h b/src/cunumeric/ndarray.h index 54bdf91c7b..f19bf937a2 100644 --- a/src/cunumeric/ndarray.h +++ b/src/cunumeric/ndarray.h @@ -69,6 +69,8 @@ class NDArray { void unary_op(int32_t op_code, NDArray input); void unary_reduction(int32_t op_code, NDArray input); void fill(NDArray fill_value); + void eye(int32_t k); + void trilu(NDArray rhs, int32_t k, bool lower); void dot(NDArray rhs1, NDArray rhs2); std::vector nonzero(); NDArray unique(); diff --git a/src/cunumeric/operators.cc b/src/cunumeric/operators.cc index 27df42cc1d..1ab6ce28b7 100644 --- a/src/cunumeric/operators.cc +++ b/src/cunumeric/operators.cc @@ -136,6 +136,44 @@ NDArray full(std::vector shape, const Scalar& value) return std::move(out); } +NDArray eye(size_t n, std::optional m, int32_t k, std::unique_ptr type) +{ + if (static_cast(type->code) >= static_cast(legate::Type::Code::FIXED_ARRAY)) + throw std::invalid_argument("Type must be a primitive type"); + + auto runtime = CuNumericRuntime::get_runtime(); + auto out = runtime->create_array({n, m.value_or(n)}, std::move(type)); + out.eye(k); + return std::move(out); +} + +NDArray trilu(NDArray rhs, int32_t k, bool lower) +{ + auto dim = rhs.dim(); + auto& shape = rhs.shape(); + std::vector out_shape(shape); + if (dim == 0) + throw std::invalid_argument("Dim of input array must be > 0"); + if (dim == 1) + out_shape.emplace_back(shape[0]); + + auto runtime = CuNumericRuntime::get_runtime(); + auto out = runtime->create_array(std::move(out_shape), rhs.type()); + out.trilu(std::move(rhs), k, lower); + return std::move(out); +} + + +NDArray tril(NDArray rhs, int32_t k) +{ + return trilu(rhs, k, true); +} + +NDArray triu(NDArray rhs, int32_t k) +{ + return trilu(rhs, k, false); +} + NDArray dot(NDArray rhs1, NDArray rhs2) { if (rhs1.dim() != 2 || rhs2.dim() != 2) { diff --git a/src/cunumeric/operators.h b/src/cunumeric/operators.h index 65975f2062..4cc17857e5 100644 --- a/src/cunumeric/operators.h +++ b/src/cunumeric/operators.h @@ -51,4 +51,10 @@ NDArray unique(NDArray input); std::vector nonzero(NDArray input); +NDArray eye(size_t n, std::optional m, int32_t k = 0, std::unique_ptr type = legate::float64()); + +NDArray tril(NDArray rhs, int32_t k = 0); + +NDArray triu(NDArray rhs, int32_t k = 0); + } // namespace cunumeric From 152a635294787e4ae44e113bf6708c964ded97bd Mon Sep 17 00:00:00 2001 From: Vladislav Zhurba Date: Tue, 6 Jun 2023 10:28:37 -0700 Subject: [PATCH 072/462] Implement misc operators * Minor review fixes * Add simple operator bool to NDArray * Apply review * Implement misc operators - binary reduction - arrange - as_type - array_equal - create array using Legate store See merge request legate/cunumeric.internal!6 --- src/cunumeric.mk | 1 + src/cunumeric/binary/binary_op.cc | 25 ++++++ src/cunumeric/binary/binary_op_util.cc | 48 ++++++++++++ src/cunumeric/binary/binary_op_util.h | 3 + src/cunumeric/ndarray.cc | 103 ++++++++++++++++++++++++- src/cunumeric/ndarray.h | 8 ++ src/cunumeric/operators.cc | 62 ++++++++------- src/cunumeric/operators.h | 9 +++ src/cunumeric/runtime.cc | 5 ++ src/cunumeric/runtime.h | 1 + 10 files changed, 235 insertions(+), 30 deletions(-) create mode 100644 src/cunumeric/binary/binary_op_util.cc diff --git a/src/cunumeric.mk b/src/cunumeric.mk index 28b2c913b7..1a3b5a4c98 100644 --- a/src/cunumeric.mk +++ b/src/cunumeric.mk @@ -19,6 +19,7 @@ GEN_CPU_SRC += cunumeric/ternary/where.cc \ cunumeric/scan/scan_global.cc \ cunumeric/scan/scan_local.cc \ cunumeric/binary/binary_op.cc \ + cunumeric/binary/binary_op_util.cc \ cunumeric/binary/binary_red.cc \ cunumeric/bits/packbits.cc \ cunumeric/bits/unpackbits.cc \ diff --git a/src/cunumeric/binary/binary_op.cc b/src/cunumeric/binary/binary_op.cc index a718443faa..ed309f0e9e 100644 --- a/src/cunumeric/binary/binary_op.cc +++ b/src/cunumeric/binary/binary_op.cc @@ -56,6 +56,31 @@ struct BinaryOpImplBody { binary_op_template(context); } +std::vector broadcast_shapes(std::vector arrays) +{ +#ifdef DEBUG_CUNUMERIC + assert(!arrays.empty()); +#endif + int32_t dim = 0; + for (auto& array : arrays) dim = std::max(dim, array.dim()); + + std::vector result(dim, 1); + + for (auto& array : arrays) { + auto& shape = array.shape(); + + auto in_it = shape.rbegin(); + auto out_it = result.rbegin(); + for (; in_it != shape.rend() && out_it != result.rend(); ++in_it, ++out_it) { + if (1 == *out_it) + *out_it = *in_it; + else if (*in_it != 1 && *out_it != *in_it) + throw std::exception(); + } + } + return result; +} + namespace // unnamed { static void __attribute__((constructor)) register_tasks(void) { BinaryOpTask::register_variants(); } diff --git a/src/cunumeric/binary/binary_op_util.cc b/src/cunumeric/binary/binary_op_util.cc new file mode 100644 index 0000000000..24fdd8e96e --- /dev/null +++ b/src/cunumeric/binary/binary_op_util.cc @@ -0,0 +1,48 @@ +/* Copyright 2023 NVIDIA Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +namespace cunumeric { + +using namespace legate; + +#include "cunumeric/binary/binary_op_util.h" + +std::vector broadcast_shapes(std::vector arrays) +{ +#ifdef DEBUG_CUNUMERIC + assert(!arrays.empty()); +#endif + int32_t dim = 0; + for (auto& array : arrays) dim = std::max(dim, array.dim()); + + std::vector result(dim, 1); + + for (auto& array : arrays) { + auto& shape = array.shape(); + + auto in_it = shape.rbegin(); + auto out_it = result.rbegin(); + for (; in_it != shape.rend() && out_it != result.rend(); ++in_it, ++out_it) { + if (1 == *out_it) + *out_it = *in_it; + else if (*in_it != 1 && *out_it != *in_it) + throw std::exception(); + } + } + return result; +} + +} // namespace cunumeric diff --git a/src/cunumeric/binary/binary_op_util.h b/src/cunumeric/binary/binary_op_util.h index 8c127acf0f..0e2e6def68 100644 --- a/src/cunumeric/binary/binary_op_util.h +++ b/src/cunumeric/binary/binary_op_util.h @@ -17,6 +17,7 @@ #pragma once #include "cunumeric/cunumeric_task.h" +#include "cunumeric/ndarray.h" namespace cunumeric { @@ -889,4 +890,6 @@ struct RHS2OfBinaryOp { template using rhs2_of_binary_op = typename RHS2OfBinaryOp::type; +std::vector broadcast_shapes(std::vector arrays); + } // namespace cunumeric diff --git a/src/cunumeric/ndarray.cc b/src/cunumeric/ndarray.cc index a158e70751..48959abbf2 100644 --- a/src/cunumeric/ndarray.cc +++ b/src/cunumeric/ndarray.cc @@ -16,9 +16,11 @@ #include "cunumeric/ndarray.h" +#include "cunumeric/binary/binary_op_util.h" #include "cunumeric/operators.h" -#include "cunumeric/runtime.h" #include "cunumeric/random/rand_util.h" +#include "cunumeric/runtime.h" +#include "cunumeric/unary/convert_util.h" #include "cunumeric/unary/unary_op_util.h" #include "cunumeric/unary/unary_red_util.h" @@ -108,6 +110,11 @@ NDArray NDArray::operator[](std::initializer_list slices) const return NDArray(std::move(sliced)); } +NDArray::operator bool() const +{ + return ((NDArray *)this)->get_read_accessor()[0]; +} + void NDArray::assign(const NDArray& other) { unary_op(static_cast(UnaryOpCode::COPY), other); @@ -222,6 +229,37 @@ void NDArray::binary_op(int32_t op_code, NDArray rhs1, NDArray rhs2) runtime->submit(std::move(task)); } +void NDArray::binary_reduction(int32_t op_code, NDArray rhs1, NDArray rhs2) +{ + auto runtime = CuNumericRuntime::get_runtime(); + + auto rhs1_store = broadcast(rhs1, rhs2); + auto rhs2_store = broadcast(rhs2, rhs1); + + Legion::ReductionOpID redop; + if (op_code == static_cast(BinaryOpCode::NOT_EQUAL)) { + redop = runtime->get_reduction_op(UnaryRedCode::SUM, type()); + fill(legate::Scalar(false), false); + } else { + redop = runtime->get_reduction_op(UnaryRedCode::PROD, type()); + fill(legate::Scalar(true), false); + } + auto task = runtime->create_task(CuNumericOpCode::CUNUMERIC_BINARY_RED); + + auto p_lhs = task->declare_partition(); + auto p_rhs1 = task->declare_partition(); + auto p_rhs2 = task->declare_partition(); + + task->add_reduction(store_, redop, p_lhs); + task->add_input(rhs1_store, p_rhs1); + task->add_input(rhs2_store, p_rhs2); + task->add_scalar_arg(legate::Scalar(op_code)); + + task->add_constraint(align(p_rhs1, p_rhs2)); + + runtime->submit(std::move(task)); +} + void NDArray::unary_op(int32_t op_code, NDArray input) { auto runtime = CuNumericRuntime::get_runtime(); @@ -301,6 +339,34 @@ void NDArray::dot(NDArray rhs1, NDArray rhs2) runtime->submit(std::move(task)); } +void NDArray::arange(double start, double stop, double step) +{ + auto runtime = CuNumericRuntime::get_runtime(); + + assert(dim() == 1); + + // TODO: Optimization when value is a scalar + + auto task = runtime->create_task(CuNumericOpCode::CUNUMERIC_ARANGE); + + auto p_lhs = task->declare_partition(); + auto p_start = task->declare_partition(); + auto p_stop = task->declare_partition(); + auto p_step = task->declare_partition(); + + task->add_output(store_, p_lhs); + + auto start_value = runtime->create_scalar_store(Scalar(start)); + auto stop_value = runtime->create_scalar_store(Scalar(stop)); + auto step_value = runtime->create_scalar_store(Scalar(step)); + + task->add_input(start_value, p_start); + task->add_input(stop_value, p_stop); + task->add_input(step_value, p_step); + + runtime->submit(std::move(task)); +} + std::vector NDArray::nonzero() { auto runtime = CuNumericRuntime::get_runtime(); @@ -346,6 +412,34 @@ NDArray NDArray::unique() return result; } +NDArray NDArray::as_type(std::unique_ptr type) +{ + auto runtime = CuNumericRuntime::get_runtime(); + + // TODO: Check if conversion is valid + + auto out = runtime->create_array(shape(), std::move(type)); + + assert(store_.type() != out.store_.type()); + + auto task = runtime->create_task(CuNumericOpCode::CUNUMERIC_CONVERT); + + auto p_lhs = task->declare_partition(); + auto p_rhs = task->declare_partition(); + + task->add_output(out.store_, p_lhs); + task->add_input(store_, p_rhs); + task->add_scalar_arg(legate::Scalar((int32_t)ConvertCode::NOOP)); + + task->add_constraint(align(p_lhs, p_rhs)); + + runtime->submit(std::move(task)); + + return std::move(out); +} + +legate::LogicalStore NDArray::get_store() { return store_; } + legate::LogicalStore NDArray::broadcast(const std::vector& shape, legate::LogicalStore& store) { @@ -374,6 +468,13 @@ legate::LogicalStore NDArray::broadcast(const std::vector& shape, return std::move(result); } +legate::LogicalStore NDArray::broadcast(NDArray rhs1, NDArray rhs2) +{ + if (rhs1.shape() == rhs2.shape()) { return rhs1.store_; } + auto out_shape = broadcast_shapes({rhs1, rhs2}); + return broadcast(out_shape, rhs1.store_); +} + /*static*/ legate::LibraryContext* NDArray::get_context() { return CuNumericRuntime::get_runtime()->get_context(); diff --git a/src/cunumeric/ndarray.h b/src/cunumeric/ndarray.h index f19bf937a2..70ec4b0c11 100644 --- a/src/cunumeric/ndarray.h +++ b/src/cunumeric/ndarray.h @@ -56,6 +56,7 @@ class NDArray { NDArray operator*(const legate::Scalar& other) const; NDArray& operator*=(const NDArray& other); NDArray operator[](std::initializer_list slices) const; + operator bool() const; public: // Copy the contents of the other ndarray to this one @@ -66,17 +67,24 @@ class NDArray { void random(int32_t gen_code); void fill(const Scalar& value, bool argval); void binary_op(int32_t op_code, NDArray rhs1, NDArray rhs2); + void binary_reduction(int32_t op_code, NDArray rhs1, NDArray rhs2); void unary_op(int32_t op_code, NDArray input); void unary_reduction(int32_t op_code, NDArray input); void fill(NDArray fill_value); void eye(int32_t k); void trilu(NDArray rhs, int32_t k, bool lower); void dot(NDArray rhs1, NDArray rhs2); + void arange(double start, double stop, double step); std::vector nonzero(); NDArray unique(); + public: + NDArray as_type(std::unique_ptr type); + legate::LogicalStore get_store(); + private: legate::LogicalStore broadcast(const std::vector& shape, legate::LogicalStore& store); + legate::LogicalStore broadcast(NDArray rhs1, NDArray rhs2); public: static legate::LibraryContext* get_context(); diff --git a/src/cunumeric/operators.cc b/src/cunumeric/operators.cc index 1ab6ce28b7..4dd3b5358c 100644 --- a/src/cunumeric/operators.cc +++ b/src/cunumeric/operators.cc @@ -23,35 +23,6 @@ namespace cunumeric { -namespace { - -std::vector broadcast_shapes(std::vector arrays) -{ -#ifdef DEBUG_CUNUMERIC - assert(!arrays.empty()); -#endif - int32_t dim = 0; - for (auto& array : arrays) dim = std::max(dim, array.dim()); - - std::vector result(dim, 1); - - for (auto& array : arrays) { - auto& shape = array.shape(); - - auto in_it = shape.rbegin(); - auto out_it = result.rbegin(); - for (; in_it != shape.rend() && out_it != result.rend(); ++in_it, ++out_it) { - if (1 == *out_it) - *out_it = *in_it; - else if (*in_it != 1 && *out_it != *in_it) - throw std::exception(); - } - } - return result; -} - -} // namespace - NDArray array(std::vector shape, std::unique_ptr type) { return CuNumericRuntime::get_runtime()->create_array(std::move(shape), std::move(type)); @@ -208,6 +179,39 @@ NDArray sum(NDArray input) { return unary_reduction(UnaryRedCode::SUM, std::move NDArray unique(NDArray input) { return input.unique(); } +NDArray arange(std::optional start, + std::optional stop, + std::optional step, + std::optional> type) +{ + if (!stop.has_value()) { + stop = start; + start = 0; + } + + size_t N = ceil((stop.value() - start.value()) / step.value()); + auto out = CuNumericRuntime::get_runtime()->create_array({N}, std::move(type.value())); + out.arange(start.value(), stop.value(), step.value()); + return std::move(out); +} + +NDArray as_array(legate::LogicalStore store) +{ + return CuNumericRuntime::get_runtime()->create_array(std::move(store)); +} + +NDArray array_equal(NDArray input0, NDArray input1) +{ + auto dst = CuNumericRuntime::get_runtime()->create_array({1}, legate::bool_()); + + if (input0.shape() != input1.shape()) { + dst.fill(legate::Scalar(false), false); + } else { + dst.binary_reduction(static_cast(BinaryOpCode::EQUAL), input0, input1); + } + return dst; +} + std::vector nonzero(NDArray input) { return input.nonzero(); } } // namespace cunumeric diff --git a/src/cunumeric/operators.h b/src/cunumeric/operators.h index 4cc17857e5..af249e8dc1 100644 --- a/src/cunumeric/operators.h +++ b/src/cunumeric/operators.h @@ -49,6 +49,15 @@ NDArray sum(NDArray input); NDArray unique(NDArray input); +NDArray arange(std::optional start = 0, + std::optional stop = std::nullopt, + std::optional step = 1, + std::optional> type = legate::float64()); + +NDArray as_array(legate::LogicalStore store); + +NDArray array_equal(NDArray input0, NDArray input1); + std::vector nonzero(NDArray input); NDArray eye(size_t n, std::optional m, int32_t k = 0, std::unique_ptr type = legate::float64()); diff --git a/src/cunumeric/runtime.cc b/src/cunumeric/runtime.cc index 9474bba25e..ac6bbdafe5 100644 --- a/src/cunumeric/runtime.cc +++ b/src/cunumeric/runtime.cc @@ -61,6 +61,11 @@ NDArray CuNumericRuntime::create_array(std::vector shape, const legate:: return create_array(std::move(shape), type.clone()); } +NDArray CuNumericRuntime::create_array(legate::LogicalStore&& store) +{ + return NDArray(std::move(store)); +} + legate::LogicalStore CuNumericRuntime::create_scalar_store(const Scalar& value) { return legate_runtime_->create_store(value); diff --git a/src/cunumeric/runtime.h b/src/cunumeric/runtime.h index 4803fe00e6..c556513bb9 100644 --- a/src/cunumeric/runtime.h +++ b/src/cunumeric/runtime.h @@ -37,6 +37,7 @@ class CuNumericRuntime { NDArray create_array(const legate::Type& type); NDArray create_array(std::vector shape, std::unique_ptr type); NDArray create_array(std::vector shape, const legate::Type& type); + NDArray create_array(legate::LogicalStore&& store); legate::LogicalStore create_scalar_store(const Scalar& value); public: From 3470aea11066b28d771f2c4173654bad6a491e88 Mon Sep 17 00:00:00 2001 From: Wonchan Lee Date: Thu, 8 Jun 2023 20:02:46 -0700 Subject: [PATCH 073/462] Catch up changes in the Scalar API * Catch up changes in the Scalar API See merge request legate/cunumeric.internal!11 --- src/cunumeric/runtime.cc | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/cunumeric/runtime.cc b/src/cunumeric/runtime.cc index ac6bbdafe5..cc7444f924 100644 --- a/src/cunumeric/runtime.cc +++ b/src/cunumeric/runtime.cc @@ -95,7 +95,7 @@ struct generate_identity_fn { Scalar operator()(const legate::Type&) { assert(false); - return Scalar(); + return Scalar(0); } }; @@ -112,8 +112,8 @@ Scalar CuNumericRuntime::get_reduction_identity(UnaryRedCode op, const legate::T auto finder = identities.find(key); if (identities.end() != finder) return finder->second; - auto identity = op_dispatch(op, generate_identity_fn{}, type); - identities[key] = identity; + auto identity = op_dispatch(op, generate_identity_fn{}, type); + identities.insert({key, identity}); return identity; } From 8d359697e5e42926574f471a9da9b3f7e0148132 Mon Sep 17 00:00:00 2001 From: Robin Wang Date: Tue, 20 Jun 2023 20:01:34 -0700 Subject: [PATCH 074/462] Implement windows APIs in C++ * rebase and address comments * Implement windows APIs in C++ See merge request legate/cunumeric.internal!10 --- src/cunumeric/ndarray.cc | 33 ++++++++++++++++++-------- src/cunumeric/ndarray.h | 2 ++ src/cunumeric/operators.cc | 48 +++++++++++++++++++++++++++----------- src/cunumeric/operators.h | 15 +++++++++++- 4 files changed, 73 insertions(+), 25 deletions(-) diff --git a/src/cunumeric/ndarray.cc b/src/cunumeric/ndarray.cc index 48959abbf2..474a680328 100644 --- a/src/cunumeric/ndarray.cc +++ b/src/cunumeric/ndarray.cc @@ -110,10 +110,7 @@ NDArray NDArray::operator[](std::initializer_list slices) const return NDArray(std::move(sliced)); } -NDArray::operator bool() const -{ - return ((NDArray *)this)->get_read_accessor()[0]; -} +NDArray::operator bool() const { return ((NDArray*)this)->get_read_accessor()[0]; } void NDArray::assign(const NDArray& other) { @@ -170,8 +167,8 @@ void NDArray::eye(int32_t k) auto runtime = CuNumericRuntime::get_runtime(); - auto task = runtime->create_task(CuNumericOpCode::CUNUMERIC_EYE); - auto p_lhs = task->declare_partition(); + auto task = runtime->create_task(CuNumericOpCode::CUNUMERIC_EYE); + auto p_lhs = task->declare_partition(); task->add_input(store_, p_lhs); task->add_output(store_, p_lhs); @@ -184,12 +181,12 @@ void NDArray::trilu(NDArray rhs, int32_t k, bool lower) { auto runtime = CuNumericRuntime::get_runtime(); - auto task = runtime->create_task(CuNumericOpCode::CUNUMERIC_TRILU); - auto p_lhs = task->declare_partition(); - auto p_rhs = task->declare_partition(); + auto task = runtime->create_task(CuNumericOpCode::CUNUMERIC_TRILU); + auto p_lhs = task->declare_partition(); + auto p_rhs = task->declare_partition(); auto& out_shape = shape(); - rhs = rhs.broadcast(out_shape, rhs.store_); + rhs = rhs.broadcast(out_shape, rhs.store_); task->add_scalar_arg(legate::Scalar(lower)); task->add_scalar_arg(legate::Scalar(k)); @@ -438,6 +435,22 @@ NDArray NDArray::as_type(std::unique_ptr type) return std::move(out); } +void NDArray::create_window(WindowOpCode op_code, int64_t M, std::vector args) +{ + auto runtime = CuNumericRuntime::get_runtime(); + + auto task = runtime->create_task(CuNumericOpCode::CUNUMERIC_WINDOW); + auto p_lhs = task->declare_partition(); + + task->add_output(store_, p_lhs); + task->add_scalar_arg(legate::Scalar(static_cast(op_code))); + task->add_scalar_arg(legate::Scalar(M)); + + for (double arg : args) { task->add_scalar_arg(legate::Scalar(arg)); } + + runtime->submit(std::move(task)); +} + legate::LogicalStore NDArray::get_store() { return store_; } legate::LogicalStore NDArray::broadcast(const std::vector& shape, diff --git a/src/cunumeric/ndarray.h b/src/cunumeric/ndarray.h index 70ec4b0c11..7404df212f 100644 --- a/src/cunumeric/ndarray.h +++ b/src/cunumeric/ndarray.h @@ -22,6 +22,7 @@ #include "legate.h" #include "cunumeric/slice.h" #include "cunumeric/typedefs.h" +#include "cunumeric/nullary/window_util.h" namespace cunumeric { @@ -77,6 +78,7 @@ class NDArray { void arange(double start, double stop, double step); std::vector nonzero(); NDArray unique(); + void create_window(WindowOpCode op_code, int64_t M, std::vector args); public: NDArray as_type(std::unique_ptr type); diff --git a/src/cunumeric/operators.cc b/src/cunumeric/operators.cc index 4dd3b5358c..0e155226c3 100644 --- a/src/cunumeric/operators.cc +++ b/src/cunumeric/operators.cc @@ -20,6 +20,7 @@ #include "cunumeric/binary/binary_op_util.h" #include "cunumeric/unary/unary_op_util.h" #include "cunumeric/random/rand_util.h" +#include "cunumeric/nullary/window_util.h" namespace cunumeric { @@ -120,13 +121,11 @@ NDArray eye(size_t n, std::optional m, int32_t k, std::unique_ptr out_shape(shape); - if (dim == 0) - throw std::invalid_argument("Dim of input array must be > 0"); - if (dim == 1) - out_shape.emplace_back(shape[0]); + if (dim == 0) throw std::invalid_argument("Dim of input array must be > 0"); + if (dim == 1) out_shape.emplace_back(shape[0]); auto runtime = CuNumericRuntime::get_runtime(); auto out = runtime->create_array(std::move(out_shape), rhs.type()); @@ -134,16 +133,9 @@ NDArray trilu(NDArray rhs, int32_t k, bool lower) return std::move(out); } +NDArray tril(NDArray rhs, int32_t k) { return trilu(rhs, k, true); } -NDArray tril(NDArray rhs, int32_t k) -{ - return trilu(rhs, k, true); -} - -NDArray triu(NDArray rhs, int32_t k) -{ - return trilu(rhs, k, false); -} +NDArray triu(NDArray rhs, int32_t k) { return trilu(rhs, k, false); } NDArray dot(NDArray rhs1, NDArray rhs2) { @@ -214,4 +206,32 @@ NDArray array_equal(NDArray input0, NDArray input1) std::vector nonzero(NDArray input) { return input.nonzero(); } +// window functions +NDArray create_window(int64_t M, WindowOpCode op_code, std::vector args) +{ + auto type = legate::float64(); + auto runtime = CuNumericRuntime::get_runtime(); + if (M <= 0) { + return runtime->create_array({0}, std::move(type)); + } else if (M == 1) { + auto out = runtime->create_array({1}, std::move(type)); + auto one = legate::Scalar(static_cast(1)); + out.fill(one, false); + return out; + } + auto out = runtime->create_array({static_cast(M)}, std::move(type)); + out.create_window(op_code, M, args); + return out; +} + +NDArray bartlett(int64_t M) { return create_window(M, WindowOpCode::BARLETT, {}); } + +NDArray blackman(int64_t M) { return create_window(M, WindowOpCode::BLACKMAN, {}); } + +NDArray hamming(int64_t M) { return create_window(M, WindowOpCode::HAMMING, {}); } + +NDArray hanning(int64_t M) { return create_window(M, WindowOpCode::HANNING, {}); } + +NDArray kaiser(int64_t M, double beta) { return create_window(M, WindowOpCode::KAISER, {beta}); } + } // namespace cunumeric diff --git a/src/cunumeric/operators.h b/src/cunumeric/operators.h index af249e8dc1..700dd90a84 100644 --- a/src/cunumeric/operators.h +++ b/src/cunumeric/operators.h @@ -60,10 +60,23 @@ NDArray array_equal(NDArray input0, NDArray input1); std::vector nonzero(NDArray input); -NDArray eye(size_t n, std::optional m, int32_t k = 0, std::unique_ptr type = legate::float64()); +NDArray eye(size_t n, + std::optional m, + int32_t k = 0, + std::unique_ptr type = legate::float64()); NDArray tril(NDArray rhs, int32_t k = 0); NDArray triu(NDArray rhs, int32_t k = 0); +NDArray bartlett(int64_t M); + +NDArray blackman(int64_t M); + +NDArray hamming(int64_t M); + +NDArray hanning(int64_t M); + +NDArray kaiser(int64_t M, double beta); + } // namespace cunumeric From 942f1d1ebfd9141bd549aa0216cdf82961cd545d Mon Sep 17 00:00:00 2001 From: Wonchan Lee Date: Thu, 22 Jun 2023 13:37:27 -0700 Subject: [PATCH 075/462] Fix the c++ stencil example * Fix the c++ stencil example See merge request legate/cunumeric.internal!12 --- examples/cpp/stencil/stencil.cc | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/examples/cpp/stencil/stencil.cc b/examples/cpp/stencil/stencil.cc index 179a28f796..242b3e993b 100644 --- a/examples/cpp/stencil/stencil.cc +++ b/examples/cpp/stencil/stencil.cc @@ -70,7 +70,7 @@ void stencil(const Config& config) auto max_iter = config.iter + config.warmup; for (int32_t iter = 0; iter < max_iter; ++iter) { auto average = center + north + east + west + south; - auto work = average * 0.2; + auto work = average * legate::Scalar(double(0.2)); center.assign(work); // print_array(average); // print_array(center); @@ -81,12 +81,10 @@ void stencil(const Config& config) int main(int argc, char** argv) { - legate::initialize(argc, argv); + legate::start(argc, argv); cunumeric::initialize(argc, argv); - legate::start(argc, argv); - stencil::Config config{}; Realm::CommandLineParser cp; @@ -98,5 +96,5 @@ int main(int argc, char** argv) stencil::stencil(config); - return legate::wait_for_shutdown(); + return legate::finish(); } From 3ccb1d77ecf58428493492b80b85351e238669b3 Mon Sep 17 00:00:00 2001 From: Wonchan Lee Date: Sat, 24 Jun 2023 00:30:45 -0700 Subject: [PATCH 076/462] Fix inline mapping for ndarray * get_physical_store no longer needs a context argument See merge request legate/cunumeric.internal!14 --- src/cunumeric/ndarray.inl | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/cunumeric/ndarray.inl b/src/cunumeric/ndarray.inl index 7340b2d189..f349803fe7 100644 --- a/src/cunumeric/ndarray.inl +++ b/src/cunumeric/ndarray.inl @@ -19,9 +19,8 @@ namespace cunumeric { template legate::AccessorRO NDArray::get_read_accessor() { - auto context = get_context(); - auto mapped = store_.get_physical_store(context); - auto shape = mapped->shape(); + auto mapped = store_.get_physical_store(); + auto shape = mapped->shape(); return mapped->read_accessor(shape); } From fdf621bc606ee24d82954cb44c309f5857005c65 Mon Sep 17 00:00:00 2001 From: Wonchan Lee Date: Wed, 28 Jun 2023 20:41:10 -0700 Subject: [PATCH 077/462] Catch up changes in the Legate API * Catch up changes in the Legate APi See merge request legate/cunumeric.internal!15 --- src/cunumeric/ndarray.cc | 176 +++++++++++++++++++-------------------- src/cunumeric/runtime.cc | 7 +- src/cunumeric/runtime.h | 4 +- 3 files changed, 92 insertions(+), 95 deletions(-) diff --git a/src/cunumeric/ndarray.cc b/src/cunumeric/ndarray.cc index 474a680328..825097ab95 100644 --- a/src/cunumeric/ndarray.cc +++ b/src/cunumeric/ndarray.cc @@ -130,13 +130,13 @@ void NDArray::random(int32_t gen_code) auto task = runtime->create_task(CuNumericOpCode::CUNUMERIC_RAND); - auto p_lhs = task->declare_partition(); + auto p_lhs = task.declare_partition(); - task->add_output(store_, p_lhs); - task->add_scalar_arg(legate::Scalar(static_cast(RandGenCode::UNIFORM))); - task->add_scalar_arg(legate::Scalar(runtime->get_next_random_epoch())); + task.add_output(store_, p_lhs); + task.add_scalar_arg(legate::Scalar(static_cast(RandGenCode::UNIFORM))); + task.add_scalar_arg(legate::Scalar(runtime->get_next_random_epoch())); auto strides = compute_strides(shape()); - task->add_scalar_arg(legate::Scalar(strides)); + task.add_scalar_arg(legate::Scalar(strides)); runtime->submit(std::move(task)); } @@ -148,12 +148,12 @@ void NDArray::fill(const Scalar& value, bool argval) auto fill_value = runtime->create_scalar_store(value); auto task = runtime->create_task(CuNumericOpCode::CUNUMERIC_FILL); - auto p_lhs = task->declare_partition(); - auto p_fill_value = task->declare_partition(); + auto p_lhs = task.declare_partition(); + auto p_fill_value = task.declare_partition(); - task->add_output(store_, p_lhs); - task->add_input(fill_value, p_fill_value); - task->add_scalar_arg(legate::Scalar(argval)); + task.add_output(store_, p_lhs); + task.add_input(fill_value, p_fill_value); + task.add_scalar_arg(legate::Scalar(argval)); runtime->submit(std::move(task)); } @@ -168,11 +168,11 @@ void NDArray::eye(int32_t k) auto runtime = CuNumericRuntime::get_runtime(); auto task = runtime->create_task(CuNumericOpCode::CUNUMERIC_EYE); - auto p_lhs = task->declare_partition(); + auto p_lhs = task.declare_partition(); - task->add_input(store_, p_lhs); - task->add_output(store_, p_lhs); - task->add_scalar_arg(legate::Scalar(k)); + task.add_input(store_, p_lhs); + task.add_output(store_, p_lhs); + task.add_scalar_arg(legate::Scalar(k)); runtime->submit(std::move(task)); } @@ -182,19 +182,19 @@ void NDArray::trilu(NDArray rhs, int32_t k, bool lower) auto runtime = CuNumericRuntime::get_runtime(); auto task = runtime->create_task(CuNumericOpCode::CUNUMERIC_TRILU); - auto p_lhs = task->declare_partition(); - auto p_rhs = task->declare_partition(); + auto p_lhs = task.declare_partition(); + auto p_rhs = task.declare_partition(); auto& out_shape = shape(); rhs = rhs.broadcast(out_shape, rhs.store_); - task->add_scalar_arg(legate::Scalar(lower)); - task->add_scalar_arg(legate::Scalar(k)); + task.add_scalar_arg(legate::Scalar(lower)); + task.add_scalar_arg(legate::Scalar(k)); - task->add_output(store_, p_lhs); - task->add_input(rhs.store_, p_rhs); + task.add_output(store_, p_lhs); + task.add_input(rhs.store_, p_rhs); - task->add_constraint(align(p_lhs, p_rhs)); + task.add_constraint(align(p_lhs, p_rhs)); runtime->submit(std::move(task)); } @@ -207,21 +207,21 @@ void NDArray::binary_op(int32_t op_code, NDArray rhs1, NDArray rhs2) auto task = runtime->create_task(CuNumericOpCode::CUNUMERIC_BINARY_OP); - auto p_lhs = task->declare_partition(); - auto p_rhs1 = task->declare_partition(); - auto p_rhs2 = task->declare_partition(); + auto p_lhs = task.declare_partition(); + auto p_rhs1 = task.declare_partition(); + auto p_rhs2 = task.declare_partition(); auto& out_shape = shape(); auto rhs1_store = broadcast(out_shape, rhs1.store_); auto rhs2_store = broadcast(out_shape, rhs2.store_); - task->add_output(store_, p_lhs); - task->add_input(rhs1_store, p_rhs1); - task->add_input(rhs2_store, p_rhs2); - task->add_scalar_arg(legate::Scalar(op_code)); + task.add_output(store_, p_lhs); + task.add_input(rhs1_store, p_rhs1); + task.add_input(rhs2_store, p_rhs2); + task.add_scalar_arg(legate::Scalar(op_code)); - task->add_constraint(align(p_lhs, p_rhs1)); - task->add_constraint(align(p_rhs1, p_rhs2)); + task.add_constraint(align(p_lhs, p_rhs1)); + task.add_constraint(align(p_rhs1, p_rhs2)); runtime->submit(std::move(task)); } @@ -243,16 +243,16 @@ void NDArray::binary_reduction(int32_t op_code, NDArray rhs1, NDArray rhs2) } auto task = runtime->create_task(CuNumericOpCode::CUNUMERIC_BINARY_RED); - auto p_lhs = task->declare_partition(); - auto p_rhs1 = task->declare_partition(); - auto p_rhs2 = task->declare_partition(); + auto p_lhs = task.declare_partition(); + auto p_rhs1 = task.declare_partition(); + auto p_rhs2 = task.declare_partition(); - task->add_reduction(store_, redop, p_lhs); - task->add_input(rhs1_store, p_rhs1); - task->add_input(rhs2_store, p_rhs2); - task->add_scalar_arg(legate::Scalar(op_code)); + task.add_reduction(store_, redop, p_lhs); + task.add_input(rhs1_store, p_rhs1); + task.add_input(rhs2_store, p_rhs2); + task.add_scalar_arg(legate::Scalar(op_code)); - task->add_constraint(align(p_rhs1, p_rhs2)); + task.add_constraint(align(p_rhs1, p_rhs2)); runtime->submit(std::move(task)); } @@ -263,16 +263,16 @@ void NDArray::unary_op(int32_t op_code, NDArray input) auto task = runtime->create_task(CuNumericOpCode::CUNUMERIC_UNARY_OP); - auto p_out = task->declare_partition(); - auto p_in = task->declare_partition(); + auto p_out = task.declare_partition(); + auto p_in = task.declare_partition(); auto rhs = broadcast(shape(), input.store_); - task->add_output(store_, p_out); - task->add_input(rhs, p_in); - task->add_scalar_arg(legate::Scalar(op_code)); + task.add_output(store_, p_out); + task.add_input(rhs, p_in); + task.add_scalar_arg(legate::Scalar(op_code)); - task->add_constraint(align(p_out, p_in)); + task.add_constraint(align(p_out, p_in)); runtime->submit(std::move(task)); } @@ -288,15 +288,15 @@ void NDArray::unary_reduction(int32_t op_code_, NDArray input) auto task = runtime->create_task(CuNumericOpCode::CUNUMERIC_SCALAR_UNARY_RED); - auto p_out = task->declare_partition(); - auto p_in = task->declare_partition(); + auto p_out = task.declare_partition(); + auto p_in = task.declare_partition(); auto redop = runtime->get_reduction_op(op_code, type()); - task->add_reduction(store_, redop, p_out); - task->add_input(input.store_, p_in); - task->add_scalar_arg(legate::Scalar(op_code_)); - task->add_scalar_arg(legate::Scalar(input.shape())); + task.add_reduction(store_, redop, p_out); + task.add_input(input.store_, p_in); + task.add_scalar_arg(legate::Scalar(op_code_)); + task.add_scalar_arg(legate::Scalar(input.shape())); runtime->submit(std::move(task)); } @@ -320,18 +320,18 @@ void NDArray::dot(NDArray rhs1, NDArray rhs2) auto task = runtime->create_task(CuNumericOpCode::CUNUMERIC_MATMUL); - auto p_lhs = task->declare_partition(); - auto p_rhs1 = task->declare_partition(); - auto p_rhs2 = task->declare_partition(); + auto p_lhs = task.declare_partition(); + auto p_rhs1 = task.declare_partition(); + auto p_rhs2 = task.declare_partition(); auto redop = runtime->get_reduction_op(UnaryRedCode::SUM, type()); - task->add_reduction(lhs_s, redop, p_lhs); - task->add_input(rhs1_s, p_rhs1); - task->add_input(rhs2_s, p_rhs2); + task.add_reduction(lhs_s, redop, p_lhs); + task.add_input(rhs1_s, p_rhs1); + task.add_input(rhs2_s, p_rhs2); - task->add_constraint(align(p_lhs, p_rhs1)); - task->add_constraint(align(p_rhs1, p_rhs2)); + task.add_constraint(align(p_lhs, p_rhs1)); + task.add_constraint(align(p_rhs1, p_rhs2)); runtime->submit(std::move(task)); } @@ -346,20 +346,20 @@ void NDArray::arange(double start, double stop, double step) auto task = runtime->create_task(CuNumericOpCode::CUNUMERIC_ARANGE); - auto p_lhs = task->declare_partition(); - auto p_start = task->declare_partition(); - auto p_stop = task->declare_partition(); - auto p_step = task->declare_partition(); + auto p_lhs = task.declare_partition(); + auto p_start = task.declare_partition(); + auto p_stop = task.declare_partition(); + auto p_step = task.declare_partition(); - task->add_output(store_, p_lhs); + task.add_output(store_, p_lhs); auto start_value = runtime->create_scalar_store(Scalar(start)); auto stop_value = runtime->create_scalar_store(Scalar(stop)); auto step_value = runtime->create_scalar_store(Scalar(step)); - task->add_input(start_value, p_start); - task->add_input(stop_value, p_stop); - task->add_input(step_value, p_step); + task.add_input(start_value, p_start); + task.add_input(stop_value, p_stop); + task.add_input(step_value, p_step); runtime->submit(std::move(task)); } @@ -374,15 +374,15 @@ std::vector NDArray::nonzero() auto task = runtime->create_task(CuNumericOpCode::CUNUMERIC_NONZERO); - auto p_rhs = task->declare_partition(); + auto p_rhs = task.declare_partition(); for (auto& output : outputs) { - auto p_lhs = task->declare_partition(); - task->add_output(output.store_, p_lhs); + auto p_lhs = task.declare_partition(); + task.add_output(output.store_, p_lhs); } - task->add_input(store_, p_rhs); + task.add_input(store_, p_rhs); - task->add_constraint(legate::broadcast(p_rhs, legate::from_range(1, ndim))); + task.add_constraint(legate::broadcast(p_rhs, legate::from_range(1, ndim))); runtime->submit(std::move(task)); @@ -398,13 +398,13 @@ NDArray NDArray::unique() auto result = runtime->create_array(type()); auto task = runtime->create_task(CuNumericOpCode::CUNUMERIC_UNIQUE); - auto part_out = task->declare_partition(); - auto part_in = task->declare_partition(); - task->add_output(result.store_, part_out); - task->add_input(store_, part_in); - task->add_communicator("nccl"); + auto part_out = task.declare_partition(); + auto part_in = task.declare_partition(); + task.add_output(result.store_, part_out); + task.add_input(store_, part_in); + task.add_communicator("nccl"); if (!has_gpus) - task->add_constraint(legate::broadcast(part_in, legate::from_range(0, dim()))); + task.add_constraint(legate::broadcast(part_in, legate::from_range(0, dim()))); runtime->submit(std::move(task)); return result; } @@ -421,14 +421,14 @@ NDArray NDArray::as_type(std::unique_ptr type) auto task = runtime->create_task(CuNumericOpCode::CUNUMERIC_CONVERT); - auto p_lhs = task->declare_partition(); - auto p_rhs = task->declare_partition(); + auto p_lhs = task.declare_partition(); + auto p_rhs = task.declare_partition(); - task->add_output(out.store_, p_lhs); - task->add_input(store_, p_rhs); - task->add_scalar_arg(legate::Scalar((int32_t)ConvertCode::NOOP)); + task.add_output(out.store_, p_lhs); + task.add_input(store_, p_rhs); + task.add_scalar_arg(legate::Scalar((int32_t)ConvertCode::NOOP)); - task->add_constraint(align(p_lhs, p_rhs)); + task.add_constraint(align(p_lhs, p_rhs)); runtime->submit(std::move(task)); @@ -440,13 +440,13 @@ void NDArray::create_window(WindowOpCode op_code, int64_t M, std::vector auto runtime = CuNumericRuntime::get_runtime(); auto task = runtime->create_task(CuNumericOpCode::CUNUMERIC_WINDOW); - auto p_lhs = task->declare_partition(); + auto p_lhs = task.declare_partition(); - task->add_output(store_, p_lhs); - task->add_scalar_arg(legate::Scalar(static_cast(op_code))); - task->add_scalar_arg(legate::Scalar(M)); + task.add_output(store_, p_lhs); + task.add_scalar_arg(legate::Scalar(static_cast(op_code))); + task.add_scalar_arg(legate::Scalar(M)); - for (double arg : args) { task->add_scalar_arg(legate::Scalar(arg)); } + for (double arg : args) { task.add_scalar_arg(legate::Scalar(arg)); } runtime->submit(std::move(task)); } diff --git a/src/cunumeric/runtime.cc b/src/cunumeric/runtime.cc index cc7444f924..ea9eb86d17 100644 --- a/src/cunumeric/runtime.cc +++ b/src/cunumeric/runtime.cc @@ -153,15 +153,12 @@ Legion::ReductionOpID CuNumericRuntime::get_reduction_op(UnaryRedCode op, const return type.find_reduction_operator(TO_CORE_REDOP.at(op)); } -std::unique_ptr CuNumericRuntime::create_task(CuNumericOpCode op_code) +legate::AutoTask CuNumericRuntime::create_task(CuNumericOpCode op_code) { return legate_runtime_->create_task(context_, op_code); } -void CuNumericRuntime::submit(std::unique_ptr task) -{ - legate_runtime_->submit(std::move(task)); -} +void CuNumericRuntime::submit(legate::AutoTask&& task) { legate_runtime_->submit(std::move(task)); } uint32_t CuNumericRuntime::get_next_random_epoch() { return next_epoch_++; } diff --git a/src/cunumeric/runtime.h b/src/cunumeric/runtime.h index c556513bb9..44701383ee 100644 --- a/src/cunumeric/runtime.h +++ b/src/cunumeric/runtime.h @@ -48,8 +48,8 @@ class CuNumericRuntime { const legate::Type& get_argred_type(const legate::Type& value_type); public: - std::unique_ptr create_task(CuNumericOpCode op_code); - void submit(std::unique_ptr task); + legate::AutoTask create_task(CuNumericOpCode op_code); + void submit(legate::AutoTask&& task); public: uint32_t get_next_random_epoch(); From 9a3231facd437308fcc7d75274c9ff881796a5df Mon Sep 17 00:00:00 2001 From: Wonchan Lee Date: Mon, 3 Jul 2023 14:15:52 -0700 Subject: [PATCH 078/462] Use type objects to register reduction operators * Use type objects to register reduction operators See merge request legate/cunumeric.internal!16 --- cunumeric/config.py | 4 +--- cunumeric/runtime.py | 2 +- src/cunumeric/arg_redop_register.cc | 6 +++--- src/cunumeric/arg_redop_register.cu | 6 +++--- src/cunumeric/arg_redop_register.h | 8 ++++---- src/cunumeric/cunumeric_c.h | 2 +- 6 files changed, 13 insertions(+), 15 deletions(-) diff --git a/cunumeric/config.py b/cunumeric/config.py index 21a7a68e58..1d54fa07bd 100644 --- a/cunumeric/config.py +++ b/cunumeric/config.py @@ -266,9 +266,7 @@ def cunumeric_has_curand(self) -> int: ... @abstractmethod - def cunumeric_register_reduction_op( - self, type_uid: int, elem_type_code: int - ) -> None: + def cunumeric_register_reduction_op(self, raw_ptr: int) -> None: ... diff --git a/cunumeric/runtime.py b/cunumeric/runtime.py index 689a384230..e3941ba601 100644 --- a/cunumeric/runtime.py +++ b/cunumeric/runtime.py @@ -129,7 +129,7 @@ def get_argred_type(self, value_dtype: ty.Dtype) -> ty.Dtype: argred_dtype = ty.struct_type([ty.int64, value_dtype], True) self._cached_argred_types[value_dtype] = argred_dtype self.cunumeric_lib.cunumeric_register_reduction_op( - argred_dtype.uid, value_dtype.code + argred_dtype.raw_ptr ) return argred_dtype diff --git a/src/cunumeric/arg_redop_register.cc b/src/cunumeric/arg_redop_register.cc index 5068abaee1..ead38a2202 100644 --- a/src/cunumeric/arg_redop_register.cc +++ b/src/cunumeric/arg_redop_register.cc @@ -61,10 +61,10 @@ DEFINE_IDENTITIES(uint64_t) extern "C" { -void cunumeric_register_reduction_op(int32_t type_uid, int32_t _elem_type_code) +void cunumeric_register_reduction_op(uintptr_t raw_type) { - auto elem_type_code = static_cast(_elem_type_code); - legate::type_dispatch(elem_type_code, cunumeric::register_reduction_op_fn{}, type_uid); + const auto* type = reinterpret_cast(raw_type); + legate::type_dispatch(type->field_type(1).code, cunumeric::register_reduction_op_fn{}, type); } } diff --git a/src/cunumeric/arg_redop_register.cu b/src/cunumeric/arg_redop_register.cu index 5a14b0b718..428a3bef45 100644 --- a/src/cunumeric/arg_redop_register.cu +++ b/src/cunumeric/arg_redop_register.cu @@ -18,9 +18,9 @@ extern "C" { -void cunumeric_register_reduction_op(int32_t type_uid, int32_t _elem_type_code) +void cunumeric_register_reduction_op(uintptr_t raw_type) { - auto elem_type_code = static_cast(_elem_type_code); - legate::type_dispatch(elem_type_code, cunumeric::register_reduction_op_fn{}, type_uid); + const auto* type = reinterpret_cast(raw_type); + legate::type_dispatch(type->field_type(1).code, cunumeric::register_reduction_op_fn{}, type); } } diff --git a/src/cunumeric/arg_redop_register.h b/src/cunumeric/arg_redop_register.h index 02433da625..7444b4358c 100644 --- a/src/cunumeric/arg_redop_register.h +++ b/src/cunumeric/arg_redop_register.h @@ -24,7 +24,7 @@ namespace cunumeric { struct register_reduction_op_fn { template ::value>* = nullptr> - void operator()(int32_t type_uid) + void operator()(const legate::StructType* type) { using VAL = legate::legate_type_of; @@ -34,18 +34,18 @@ struct register_reduction_op_fn { auto redop_id = context->register_reduction_operator>(next_reduction_operator_id()); auto op_kind = static_cast(legate::ReductionOpKind::MAX); - runtime->record_reduction_operator(type_uid, op_kind, redop_id); + type->record_reduction_operator(op_kind, redop_id); } { auto redop_id = context->register_reduction_operator>(next_reduction_operator_id()); auto op_kind = static_cast(legate::ReductionOpKind::MIN); - runtime->record_reduction_operator(type_uid, op_kind, redop_id); + type->record_reduction_operator(op_kind, redop_id); } } template ::value>* = nullptr> - void operator()(int32_t type_uid) + void operator()(const legate::StructType* type) { LEGATE_ABORT; } diff --git a/src/cunumeric/cunumeric_c.h b/src/cunumeric/cunumeric_c.h index c3145939ea..bcf4e0f692 100644 --- a/src/cunumeric/cunumeric_c.h +++ b/src/cunumeric/cunumeric_c.h @@ -325,7 +325,7 @@ extern "C" { void cunumeric_perform_registration(); bool cunumeric_has_curand(); -void cunumeric_register_reduction_op(int32_t type_uid, int32_t elem_type_code); +void cunumeric_register_reduction_op(uintptr_t raw_type); #ifdef __cplusplus } From 4568b74f1b6a8b46a4519ae552c37a0235844075 Mon Sep 17 00:00:00 2001 From: Parag Kulkarni Date: Wed, 12 Jul 2023 22:00:15 -0700 Subject: [PATCH 079/462] Add CI for cunumeric repository. * - Add CI for cunumeric. - Use github caching mechanism to speed up the builds. - Add a CI/CD variable Personal_Access_Token for AWS authentication. - Add gitlab specific package for legate.core.internal - The legate change against which the legate is built breaks the cunumeric build. - The required change and the files are specific to gitlab, hence it is difficult to converge on a common change that works both for github and gitlab. - Hence we add a separate gitlab specific package. See merge request legate/cunumeric.internal!18 --- .gitlab-ci.yml | 95 +++++++++++++++++++++++++++++++++++++++++++++ cmake/versions.json | 7 ++++ 2 files changed, 102 insertions(+) create mode 100644 .gitlab-ci.yml diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml new file mode 100644 index 0000000000..6513f99b19 --- /dev/null +++ b/.gitlab-ci.yml @@ -0,0 +1,95 @@ +image: + name: "rapidsai/devcontainers:23.06-cpp-cuda11.8-mambaforge-ubuntu22.04" + +stages: + - build + +Build:LegateCore: + stage: build + + rules: + - if: ($CI_PIPELINE_SOURCE == "merge_request_event" || $CI_PIPELINE_SOURCE == "push") && + ($CI_PROJECT_PATH == "legate/cunumeric.internal") + + tags: + - cpu_only + - gpu/disabled + + variables: + # Set the following two vars to `nthreads - 1` + JOBS: 1 + CMAKE_BUILD_PARALLEL_LEVEL: 1 + PYTHONDONTWRITEBYTECODE: 1 + DEFAULT_CONDA_ENV: legate + GIT_DEPTH: 0 + GIT_STRATEGY: clone + # Add these if we want to speed up builds with sccache + SCCACHE_REGION: us-east-2 + SCCACHE_BUCKET: rapids-sccache-devs + SCCACHE_S3_KEY_PREFIX: legate-cunumeric-int-dev + GH_TOKEN: '' + GITHUB_TOKEN: '' + VAULT_HOST: '' + VAULT_S3_TTL: "28800s" # 8 hours + + before_script: + # Set variables for AWS authentication + - | + if [ $PERSONAL_ACCESS_TOKEN ]; then + VAULT_HOST='https://vault.ops.k8s.rapids.ai'; + GH_TOKEN=$PERSONAL_ACCESS_TOKEN; + GITHUB_TOKEN=$PERSONAL_ACCESS_TOKEN; + fi + # Clone legate.core and checkout to the expected version. + - cd $CI_PROJECT_DIR/.. + - rm -rf legate.core.internal + - git clone https://gitlab-ci-token:${CI_JOB_TOKEN}@gitlab-master.nvidia.com/legate/legate.core.internal.git + - export LEGATE_SHA=$(cat $CI_PROJECT_DIR/cmake/versions.json | jq -r '.packages.legate_core_internal.git_tag') + - echo "LEGATE_SHA- ${LEGATE_SHA}" + - git -C legate.core.internal checkout $LEGATE_SHA + # Copy relevant scripts + - cp -ar legate.core.internal/continuous_integration/home/coder/.gitconfig /home/coder/; + - cp -ar legate.core.internal/continuous_integration/home/coder/.local ~coder/; + - mv legate.core.internal ~coder/legate; + - cp -ar $CI_PROJECT_DIR/continuous_integration/home/coder/.local/bin/* ~coder/.local/bin/; + - cp -ar $CI_PROJECT_DIR ~coder/cunumeric; + - mkdir -p ~coder/.local + - mkdir -p /tmp/out; + - chown -R coder:coder /tmp/out + - chown -R coder:coder /home/coder/ + + script: + # Create conda env + - su coder -c 'cd ~/; exec entrypoint make-conda-env' + + # Build legate.core C++ library + - su coder -c 'cd ~/; exec entrypoint build-legate-cpp' + + # Build legate.core Python Wheel + - su coder -c 'cd ~/; exec entrypoint build-legate-wheel' + + # Build legate.core Conda Package + - su coder -c 'cd ~/; exec entrypoint build-legate-conda' + + # Build cunumeric C++ library + - su coder -c 'cd ~/; exec entrypoint build-cunumeric-cpp' + + # Build cunumeric Python Wheel + - su coder -c 'cd ~/; exec entrypoint build-cunumeric-wheel' + + # Build cunumeric Python Conda Package + - su coder -c 'cd ~/; exec entrypoint build-cunumeric-conda' + + after_script: + # Copy the artifacts + - mkdir -p $CI_PROJECT_DIR/artifacts + - cp /tmp/out/* $CI_PROJECT_DIR/artifacts/ + + artifacts: + name: "$CI_COMMIT_REF_NAME" + paths: + - artifacts + exclude: + - artifacts/env*.yaml + + diff --git a/cmake/versions.json b/cmake/versions.json index ae8c48ed18..47b001343c 100644 --- a/cmake/versions.json +++ b/cmake/versions.json @@ -6,6 +6,13 @@ "git_shallow": false, "always_download": false, "git_tag" : "92cf12157fc186ae368cb40fd937ad4e1c68bb6e" + }, + "legate_core_internal" : { + "version": "23.07.00", + "git_url" : "https://gitlab-master.nvidia.com/legate/legate.core.internal.git", + "git_shallow": false, + "always_download": false, + "git_tag" : "ce437ffbe659836eda5296bd45c180eb812996ea" } } } From 9ab916fb49afdbcd814c33885c153a9337d64e00 Mon Sep 17 00:00:00 2001 From: Parag Kulkarni Date: Thu, 13 Jul 2023 08:42:55 -0700 Subject: [PATCH 080/462] Bump up the cmake version to address the unsupported GNU version error in CI. * - The corresponding SHA change addresses error -- unsupported GNU version. See merge request legate/cunumeric.internal!19 --- cmake/versions.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmake/versions.json b/cmake/versions.json index 47b001343c..fa920179c2 100644 --- a/cmake/versions.json +++ b/cmake/versions.json @@ -12,7 +12,7 @@ "git_url" : "https://gitlab-master.nvidia.com/legate/legate.core.internal.git", "git_shallow": false, "always_download": false, - "git_tag" : "ce437ffbe659836eda5296bd45c180eb812996ea" + "git_tag" : "407b15b0d78e43b8bdecab59e70d8d11c4bc70d4" } } } From 457fe841f230477736c87b9211d0e3f5a1e3e0e0 Mon Sep 17 00:00:00 2001 From: Wonchan Lee Date: Tue, 18 Jul 2023 20:01:21 -0700 Subject: [PATCH 081/462] Catch up core changes * Update the commit hash * Catch up core changes for reduction ops and alignments See merge request legate/cunumeric.internal!20 --- cmake/versions.json | 2 +- src/cunumeric/ndarray.cc | 41 ++++++++++++++++++++-------------------- src/cunumeric/runtime.cc | 4 ++-- src/cunumeric/runtime.h | 2 +- 4 files changed, 25 insertions(+), 24 deletions(-) diff --git a/cmake/versions.json b/cmake/versions.json index fa920179c2..37156bba98 100644 --- a/cmake/versions.json +++ b/cmake/versions.json @@ -12,7 +12,7 @@ "git_url" : "https://gitlab-master.nvidia.com/legate/legate.core.internal.git", "git_shallow": false, "always_download": false, - "git_tag" : "407b15b0d78e43b8bdecab59e70d8d11c4bc70d4" + "git_tag" : "b98430bc4f7dd512fd7679b80bc22c91b2e9a3a7" } } } diff --git a/src/cunumeric/ndarray.cc b/src/cunumeric/ndarray.cc index 825097ab95..152a29e2f6 100644 --- a/src/cunumeric/ndarray.cc +++ b/src/cunumeric/ndarray.cc @@ -182,8 +182,8 @@ void NDArray::trilu(NDArray rhs, int32_t k, bool lower) auto runtime = CuNumericRuntime::get_runtime(); auto task = runtime->create_task(CuNumericOpCode::CUNUMERIC_TRILU); - auto p_lhs = task.declare_partition(); - auto p_rhs = task.declare_partition(); + auto p_lhs = task.find_or_declare_partition(store_); + auto p_rhs = task.find_or_declare_partition(rhs.store_); auto& out_shape = shape(); rhs = rhs.broadcast(out_shape, rhs.store_); @@ -207,14 +207,14 @@ void NDArray::binary_op(int32_t op_code, NDArray rhs1, NDArray rhs2) auto task = runtime->create_task(CuNumericOpCode::CUNUMERIC_BINARY_OP); - auto p_lhs = task.declare_partition(); - auto p_rhs1 = task.declare_partition(); - auto p_rhs2 = task.declare_partition(); - auto& out_shape = shape(); auto rhs1_store = broadcast(out_shape, rhs1.store_); auto rhs2_store = broadcast(out_shape, rhs2.store_); + auto p_lhs = task.find_or_declare_partition(store_); + auto p_rhs1 = task.find_or_declare_partition(rhs1_store); + auto p_rhs2 = task.find_or_declare_partition(rhs2_store); + task.add_output(store_, p_lhs); task.add_input(rhs1_store, p_rhs1); task.add_input(rhs2_store, p_rhs2); @@ -233,19 +233,19 @@ void NDArray::binary_reduction(int32_t op_code, NDArray rhs1, NDArray rhs2) auto rhs1_store = broadcast(rhs1, rhs2); auto rhs2_store = broadcast(rhs2, rhs1); - Legion::ReductionOpID redop; + legate::ReductionOpKind redop; if (op_code == static_cast(BinaryOpCode::NOT_EQUAL)) { - redop = runtime->get_reduction_op(UnaryRedCode::SUM, type()); + redop = runtime->get_reduction_op(UnaryRedCode::SUM); fill(legate::Scalar(false), false); } else { - redop = runtime->get_reduction_op(UnaryRedCode::PROD, type()); + redop = runtime->get_reduction_op(UnaryRedCode::PROD); fill(legate::Scalar(true), false); } auto task = runtime->create_task(CuNumericOpCode::CUNUMERIC_BINARY_RED); - auto p_lhs = task.declare_partition(); - auto p_rhs1 = task.declare_partition(); - auto p_rhs2 = task.declare_partition(); + auto p_lhs = task.find_or_declare_partition(store_); + auto p_rhs1 = task.find_or_declare_partition(rhs1_store); + auto p_rhs2 = task.find_or_declare_partition(rhs2_store); task.add_reduction(store_, redop, p_lhs); task.add_input(rhs1_store, p_rhs1); @@ -263,11 +263,11 @@ void NDArray::unary_op(int32_t op_code, NDArray input) auto task = runtime->create_task(CuNumericOpCode::CUNUMERIC_UNARY_OP); - auto p_out = task.declare_partition(); - auto p_in = task.declare_partition(); - auto rhs = broadcast(shape(), input.store_); + auto p_out = task.find_or_declare_partition(store_); + auto p_in = task.find_or_declare_partition(rhs); + task.add_output(store_, p_out); task.add_input(rhs, p_in); task.add_scalar_arg(legate::Scalar(op_code)); @@ -291,7 +291,7 @@ void NDArray::unary_reduction(int32_t op_code_, NDArray input) auto p_out = task.declare_partition(); auto p_in = task.declare_partition(); - auto redop = runtime->get_reduction_op(op_code, type()); + auto redop = runtime->get_reduction_op(op_code); task.add_reduction(store_, redop, p_out); task.add_input(input.store_, p_in); @@ -320,11 +320,12 @@ void NDArray::dot(NDArray rhs1, NDArray rhs2) auto task = runtime->create_task(CuNumericOpCode::CUNUMERIC_MATMUL); - auto p_lhs = task.declare_partition(); - auto p_rhs1 = task.declare_partition(); - auto p_rhs2 = task.declare_partition(); + // TODO: aliased partitions will be created if the LHS and one of the RHSes are the same store + auto p_lhs = task.find_or_declare_partition(lhs_s); + auto p_rhs1 = task.find_or_declare_partition(rhs1_s); + auto p_rhs2 = task.find_or_declare_partition(rhs2_s); - auto redop = runtime->get_reduction_op(UnaryRedCode::SUM, type()); + auto redop = runtime->get_reduction_op(UnaryRedCode::SUM); task.add_reduction(lhs_s, redop, p_lhs); task.add_input(rhs1_s, p_rhs1); diff --git a/src/cunumeric/runtime.cc b/src/cunumeric/runtime.cc index ea9eb86d17..64e55fbd12 100644 --- a/src/cunumeric/runtime.cc +++ b/src/cunumeric/runtime.cc @@ -148,9 +148,9 @@ const std::unordered_map TO_CORE_REDOP = } // namespace -Legion::ReductionOpID CuNumericRuntime::get_reduction_op(UnaryRedCode op, const legate::Type& type) +legate::ReductionOpKind CuNumericRuntime::get_reduction_op(UnaryRedCode op) { - return type.find_reduction_operator(TO_CORE_REDOP.at(op)); + return TO_CORE_REDOP.at(op); } legate::AutoTask CuNumericRuntime::create_task(CuNumericOpCode op_code) diff --git a/src/cunumeric/runtime.h b/src/cunumeric/runtime.h index 44701383ee..e39d22cf9c 100644 --- a/src/cunumeric/runtime.h +++ b/src/cunumeric/runtime.h @@ -42,7 +42,7 @@ class CuNumericRuntime { public: Scalar get_reduction_identity(UnaryRedCode op, const legate::Type& type); - Legion::ReductionOpID get_reduction_op(UnaryRedCode op, const legate::Type& type); + legate::ReductionOpKind get_reduction_op(UnaryRedCode op); public: const legate::Type& get_argred_type(const legate::Type& value_type); From 1cb6e112f07ba9854a5fae9126898c3e386dda13 Mon Sep 17 00:00:00 2001 From: Parag Kulkarni Date: Thu, 20 Jul 2023 09:19:13 -0700 Subject: [PATCH 082/462] Rename Build name to Cunumeric. * - Rename Build name to Cunumeric. See merge request legate/cunumeric.internal!21 --- .gitlab-ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 6513f99b19..e5206fb7ef 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -4,7 +4,7 @@ image: stages: - build -Build:LegateCore: +Build:CuNumeric: stage: build rules: From 55a132cb3bf3a37c5b42e886af265f787f253442 Mon Sep 17 00:00:00 2001 From: Manolis Papadakis Date: Fri, 21 Jul 2023 12:02:02 -0700 Subject: [PATCH 083/462] Missing alignment on histogram call (#1000) --- cunumeric/deferred.py | 1 + 1 file changed, 1 insertion(+) diff --git a/cunumeric/deferred.py b/cunumeric/deferred.py index 6e8b6fe2ce..b927c69fb7 100644 --- a/cunumeric/deferred.py +++ b/cunumeric/deferred.py @@ -3626,5 +3626,6 @@ def histogram(self, src: Any, bins: Any, weights: Any) -> None: task.add_broadcast(bins_array.base) task.add_broadcast(dst_array.base) + task.add_alignment(src_array.base, weight_array.base) task.execute() From 2d000049b269b05daff0443a1a5161e1666e68c6 Mon Sep 17 00:00:00 2001 From: Mark Vaz Date: Fri, 21 Jul 2023 12:56:35 -0700 Subject: [PATCH 084/462] pin cuda-version (#998) --- conda/conda-build/meta.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/conda/conda-build/meta.yaml b/conda/conda-build/meta.yaml index f2035260a1..81569e3a24 100644 --- a/conda/conda-build/meta.yaml +++ b/conda/conda-build/meta.yaml @@ -135,6 +135,7 @@ requirements: {% else %} - legate-core ={{ core_version }} - cuda-cudart >={{ cuda_version }},<{{ cuda_major+1 }} + - cuda-version >={{ cuda_version }},<{{ cuda_major+1 }} - cutensor >=1.3 =*_* - libcublas - libcusolver =11.4.1.48-0 From 7f8051be065d2759ee2d686a8ed3c3db42a2de03 Mon Sep 17 00:00:00 2001 From: Wonchan Lee Date: Tue, 25 Jul 2023 04:03:49 -0700 Subject: [PATCH 085/462] Catch up pimpl changes in Legate Core * Update versions.json * Set Legate commit hash for testing * Catch up the machine API change * Type erasure for window functions * Catch up the mapping API change * Missing header to install * Catch up more changes * Catch up more changes * Catch up the core changes See merge request legate/cunumeric.internal!22 --- cmake/versions.json | 2 +- cunumeric/config.py | 7 +- cunumeric/runtime.py | 10 +- cunumeric_cpp.cmake | 1 + .../stencil/{editable-install.sh => build.sh} | 0 src/cunumeric/arg_redop_register.cc | 6 +- src/cunumeric/arg_redop_register.cu | 6 +- src/cunumeric/arg_redop_register.h | 24 ++-- src/cunumeric/cunumeric.cc | 8 +- src/cunumeric/cunumeric_c.h | 7 +- src/cunumeric/mapper.cc | 105 +++++++----------- src/cunumeric/ndarray.cc | 18 +-- src/cunumeric/ndarray.h | 9 +- src/cunumeric/ndarray.inl | 3 +- src/cunumeric/nullary/fill_template.inl | 4 +- src/cunumeric/operators.cc | 26 ++--- src/cunumeric/operators.h | 16 +-- src/cunumeric/runtime.cc | 55 ++++----- src/cunumeric/runtime.h | 14 +-- src/cunumeric/unary/unary_op_template.inl | 2 +- 20 files changed, 147 insertions(+), 176 deletions(-) rename examples/cpp/stencil/{editable-install.sh => build.sh} (100%) diff --git a/cmake/versions.json b/cmake/versions.json index 37156bba98..e5d1a32a7a 100644 --- a/cmake/versions.json +++ b/cmake/versions.json @@ -12,7 +12,7 @@ "git_url" : "https://gitlab-master.nvidia.com/legate/legate.core.internal.git", "git_shallow": false, "always_download": false, - "git_tag" : "b98430bc4f7dd512fd7679b80bc22c91b2e9a3a7" + "git_tag" : "f1419c41eda8fd77f94a58e3493145a2589f665d" } } } diff --git a/cunumeric/config.py b/cunumeric/config.py index 1d54fa07bd..b3dac5b4b4 100644 --- a/cunumeric/config.py +++ b/cunumeric/config.py @@ -28,6 +28,11 @@ from .runtime import Runtime +class _ReductionOpIds: + argmax_redop_id: int + argmin_redop_id: int + + class _CunumericSharedLib: CUNUMERIC_ADVANCED_INDEXING: int CUNUMERIC_ARANGE: int @@ -266,7 +271,7 @@ def cunumeric_has_curand(self) -> int: ... @abstractmethod - def cunumeric_register_reduction_op(self, raw_ptr: int) -> None: + def cunumeric_register_reduction_ops(self, code: int) -> _ReductionOpIds: ... diff --git a/cunumeric/runtime.py b/cunumeric/runtime.py index e3941ba601..57cadf9b5c 100644 --- a/cunumeric/runtime.py +++ b/cunumeric/runtime.py @@ -128,8 +128,14 @@ def get_argred_type(self, value_dtype: ty.Dtype) -> ty.Dtype: return cached argred_dtype = ty.struct_type([ty.int64, value_dtype], True) self._cached_argred_types[value_dtype] = argred_dtype - self.cunumeric_lib.cunumeric_register_reduction_op( - argred_dtype.raw_ptr + ids = self.cunumeric_lib.cunumeric_register_reduction_ops( + value_dtype.code + ) + argred_dtype.record_reduction_op( + ty.ReductionOp.MAX, ids.argmax_redop_id + ) + argred_dtype.record_reduction_op( + ty.ReductionOp.MIN, ids.argmin_redop_id ) return argred_dtype diff --git a/cunumeric_cpp.cmake b/cunumeric_cpp.cmake index 245ddccb0c..3499eeafc7 100644 --- a/cunumeric_cpp.cmake +++ b/cunumeric_cpp.cmake @@ -455,6 +455,7 @@ install( src/cunumeric/ndarray.h src/cunumeric/ndarray.inl src/cunumeric/operators.h + src/cunumeric/slice.h src/cunumeric/typedefs.h DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/cunumeric/cunumeric) diff --git a/examples/cpp/stencil/editable-install.sh b/examples/cpp/stencil/build.sh similarity index 100% rename from examples/cpp/stencil/editable-install.sh rename to examples/cpp/stencil/build.sh diff --git a/src/cunumeric/arg_redop_register.cc b/src/cunumeric/arg_redop_register.cc index ead38a2202..454d369959 100644 --- a/src/cunumeric/arg_redop_register.cc +++ b/src/cunumeric/arg_redop_register.cc @@ -61,10 +61,10 @@ DEFINE_IDENTITIES(uint64_t) extern "C" { -void cunumeric_register_reduction_op(uintptr_t raw_type) +ReductionOpIds cunumeric_register_reduction_ops(int32_t code) { - const auto* type = reinterpret_cast(raw_type); - legate::type_dispatch(type->field_type(1).code, cunumeric::register_reduction_op_fn{}, type); + return legate::type_dispatch(static_cast(code), + cunumeric::register_reduction_op_fn{}); } } diff --git a/src/cunumeric/arg_redop_register.cu b/src/cunumeric/arg_redop_register.cu index 428a3bef45..6969be0f40 100644 --- a/src/cunumeric/arg_redop_register.cu +++ b/src/cunumeric/arg_redop_register.cu @@ -18,9 +18,9 @@ extern "C" { -void cunumeric_register_reduction_op(uintptr_t raw_type) +ReductionOpIds cunumeric_register_reduction_ops(int32_t code) { - const auto* type = reinterpret_cast(raw_type); - legate::type_dispatch(type->field_type(1).code, cunumeric::register_reduction_op_fn{}, type); + return legate::type_dispatch(static_cast(code), + cunumeric::register_reduction_op_fn{}); } } diff --git a/src/cunumeric/arg_redop_register.h b/src/cunumeric/arg_redop_register.h index 7444b4358c..a06e245d8e 100644 --- a/src/cunumeric/arg_redop_register.h +++ b/src/cunumeric/arg_redop_register.h @@ -24,30 +24,24 @@ namespace cunumeric { struct register_reduction_op_fn { template ::value>* = nullptr> - void operator()(const legate::StructType* type) + ReductionOpIds operator()() { using VAL = legate::legate_type_of; - + ReductionOpIds result; auto runtime = legate::Runtime::get_runtime(); auto context = runtime->find_library("cunumeric"); - { - auto redop_id = - context->register_reduction_operator>(next_reduction_operator_id()); - auto op_kind = static_cast(legate::ReductionOpKind::MAX); - type->record_reduction_operator(op_kind, redop_id); - } - { - auto redop_id = - context->register_reduction_operator>(next_reduction_operator_id()); - auto op_kind = static_cast(legate::ReductionOpKind::MIN); - type->record_reduction_operator(op_kind, redop_id); - } + result.argmax_redop_id = + context.register_reduction_operator>(next_reduction_operator_id()); + result.argmin_redop_id = + context.register_reduction_operator>(next_reduction_operator_id()); + return result; } template ::value>* = nullptr> - void operator()(const legate::StructType* type) + ReductionOpIds operator()() { LEGATE_ABORT; + return ReductionOpIds{}; } static int32_t next_reduction_operator_id(); diff --git a/src/cunumeric/cunumeric.cc b/src/cunumeric/cunumeric.cc index 638c2ba602..b048e592fc 100644 --- a/src/cunumeric/cunumeric.cc +++ b/src/cunumeric/cunumeric.cc @@ -37,10 +37,10 @@ void registration_callback() config.max_tasks = CUNUMERIC_MAX_TASKS; config.max_reduction_ops = CUNUMERIC_MAX_REDOPS; - auto context = Runtime::get_runtime()->create_library( + auto library = Runtime::get_runtime()->create_library( cunumeric_library_name, config, std::make_unique()); - CuNumericRegistrar::get_registrar().register_all_tasks(context); + CuNumericRegistrar::get_registrar().register_all_tasks(library); } void bootstrapping_callback(Legion::Machine machine, @@ -50,9 +50,9 @@ void bootstrapping_callback(Legion::Machine machine, registration_callback(); auto runtime = legate::Runtime::get_runtime(); - auto context = runtime->find_library(cunumeric_library_name); + auto library = runtime->find_library(cunumeric_library_name); - CuNumericRuntime::initialize(runtime, context); + CuNumericRuntime::initialize(runtime, library); } } // namespace cunumeric diff --git a/src/cunumeric/cunumeric_c.h b/src/cunumeric/cunumeric_c.h index bcf4e0f692..1175eb4687 100644 --- a/src/cunumeric/cunumeric_c.h +++ b/src/cunumeric/cunumeric_c.h @@ -323,9 +323,14 @@ enum CuNumericBitorder { CUNUMERIC_BITORDER_BIG = 0, CUNUMERIC_BITORDER_LITTLE = extern "C" { #endif +typedef struct ReductionOpIds { + int argmax_redop_id; + int argmin_redop_id; +} ReductionOpIds; + void cunumeric_perform_registration(); bool cunumeric_has_curand(); -void cunumeric_register_reduction_op(uintptr_t raw_type); +struct ReductionOpIds cunumeric_register_reduction_ops(int32_t code); #ifdef __cplusplus } diff --git a/src/cunumeric/mapper.cc b/src/cunumeric/mapper.cc index da5fcb1cae..2c78f7f411 100644 --- a/src/cunumeric/mapper.cc +++ b/src/cunumeric/mapper.cc @@ -78,32 +78,30 @@ std::vector CuNumericMapper::store_mappings( switch (task.task_id()) { case CUNUMERIC_CONVOLVE: { std::vector mappings; - auto& inputs = task.inputs(); + auto inputs = task.inputs(); mappings.push_back(StoreMapping::default_mapping(inputs[0], options.front())); mappings.push_back(StoreMapping::default_mapping(inputs[1], options.front())); auto& input_mapping = mappings.back(); - for (uint32_t idx = 2; idx < inputs.size(); ++idx) - input_mapping.stores.push_back(inputs[idx]); + for (uint32_t idx = 2; idx < inputs.size(); ++idx) input_mapping.add_store(inputs[idx]); return std::move(mappings); } case CUNUMERIC_FFT: { std::vector mappings; - auto& inputs = task.inputs(); - auto& outputs = task.outputs(); + auto inputs = task.inputs(); + auto outputs = task.outputs(); mappings.push_back(StoreMapping::default_mapping(inputs[0], options.front())); - mappings.push_back(StoreMapping::default_mapping(outputs[0], options.front())); - mappings.back().policy.exact = true; - mappings.back().policy.ordering.c_order(); + mappings.push_back( + StoreMapping::default_mapping(outputs[0], options.front(), true /*exact*/)); return std::move(mappings); } case CUNUMERIC_TRANSPOSE_COPY_2D: { auto logical = task.scalars()[0].value(); if (!logical) { std::vector mappings; - auto& outputs = task.outputs(); - mappings.push_back(StoreMapping::default_mapping(outputs[0], options.front())); - mappings.back().policy.ordering.set_fortran_order(); - mappings.back().policy.exact = true; + auto outputs = task.outputs(); + mappings.push_back( + StoreMapping::default_mapping(outputs[0], options.front(), true /*exact*/)); + mappings.back().policy().ordering.set_fortran_order(); return std::move(mappings); } else return {}; @@ -113,15 +111,14 @@ std::vector CuNumericMapper::store_mappings( // TODO: Our actual requirements are a little less strict than this; we require each array or // vector to have a stride of 1 on at least one dimension. std::vector mappings; - auto& inputs = task.inputs(); - auto& reductions = task.reductions(); + auto inputs = task.inputs(); + auto reductions = task.reductions(); for (auto& input : inputs) { - mappings.push_back(StoreMapping::default_mapping(input, options.front())); - mappings.back().policy.exact = true; + mappings.push_back(StoreMapping::default_mapping(input, options.front(), true /*exact*/)); } for (auto& reduction : reductions) { - mappings.push_back(StoreMapping::default_mapping(reduction, options.front())); - mappings.back().policy.exact = true; + mappings.push_back( + StoreMapping::default_mapping(reduction, options.front(), true /*exact*/)); } return std::move(mappings); } @@ -131,17 +128,15 @@ std::vector CuNumericMapper::store_mappings( case CUNUMERIC_SYRK: case CUNUMERIC_GEMM: { std::vector mappings; - auto& inputs = task.inputs(); - auto& outputs = task.outputs(); + auto inputs = task.inputs(); + auto outputs = task.outputs(); for (auto& input : inputs) { - mappings.push_back(StoreMapping::default_mapping(input, options.front())); - mappings.back().policy.ordering.set_fortran_order(); - mappings.back().policy.exact = true; + mappings.push_back(StoreMapping::default_mapping(input, options.front(), true /*exact*/)); + mappings.back().policy().ordering.set_fortran_order(); } for (auto& output : outputs) { - mappings.push_back(StoreMapping::default_mapping(output, options.front())); - mappings.back().policy.ordering.set_fortran_order(); - mappings.back().policy.exact = true; + mappings.push_back(StoreMapping::default_mapping(output, options.front(), true /*exact*/)); + mappings.back().policy().ordering.set_fortran_order(); } return std::move(mappings); } @@ -150,78 +145,62 @@ std::vector CuNumericMapper::store_mappings( // If we're here, this task was the post-processing for Cholesky. // So we will request fortran ordering std::vector mappings; - auto& input = task.inputs().front(); - mappings.push_back(StoreMapping::default_mapping(input, options.front())); - mappings.back().policy.ordering.set_fortran_order(); - mappings.back().policy.exact = true; + auto input = task.input(0); + mappings.push_back(StoreMapping::default_mapping(input, options.front(), true /*exact*/)); + mappings.back().policy().ordering.set_fortran_order(); return std::move(mappings); } case CUNUMERIC_SEARCHSORTED: { std::vector mappings; - auto& inputs = task.inputs(); - mappings.push_back(StoreMapping::default_mapping(inputs[0], options.front())); - mappings.back().policy.exact = true; + auto inputs = task.inputs(); + mappings.push_back(StoreMapping::default_mapping(inputs[0], options.front(), true /*exact*/)); return std::move(mappings); } case CUNUMERIC_SORT: { std::vector mappings; - auto& inputs = task.inputs(); - auto& outputs = task.outputs(); + auto inputs = task.inputs(); + auto outputs = task.outputs(); for (auto& input : inputs) { - mappings.push_back(StoreMapping::default_mapping(input, options.front())); - mappings.back().policy.ordering.set_c_order(); - mappings.back().policy.exact = true; + mappings.push_back(StoreMapping::default_mapping(input, options.front(), true /*exact*/)); } for (auto& output : outputs) { - mappings.push_back(StoreMapping::default_mapping(output, options.front())); - mappings.back().policy.ordering.set_c_order(); - mappings.back().policy.exact = true; + mappings.push_back(StoreMapping::default_mapping(output, options.front(), true /*exact*/)); } return std::move(mappings); } case CUNUMERIC_SCAN_LOCAL: { std::vector mappings; - auto& inputs = task.inputs(); - auto& outputs = task.outputs(); + auto inputs = task.inputs(); + auto outputs = task.outputs(); for (auto& input : inputs) { - mappings.push_back(StoreMapping::default_mapping(input, options.front())); - mappings.back().policy.ordering.set_c_order(); - mappings.back().policy.exact = true; + mappings.push_back(StoreMapping::default_mapping(input, options.front(), true /*exact*/)); } for (auto& output : outputs) { - mappings.push_back(StoreMapping::default_mapping(output, options.front())); - mappings.back().policy.ordering.set_c_order(); - mappings.back().policy.exact = true; + mappings.push_back(StoreMapping::default_mapping(output, options.front(), true /*exact*/)); } return std::move(mappings); } case CUNUMERIC_SCAN_GLOBAL: { std::vector mappings; - auto& inputs = task.inputs(); - auto& outputs = task.outputs(); + auto inputs = task.inputs(); + auto outputs = task.outputs(); for (auto& input : inputs) { - mappings.push_back(StoreMapping::default_mapping(input, options.front())); - mappings.back().policy.ordering.set_c_order(); - mappings.back().policy.exact = true; + mappings.push_back(StoreMapping::default_mapping(input, options.front(), true /*exact*/)); } for (auto& output : outputs) { - mappings.push_back(StoreMapping::default_mapping(output, options.front())); - mappings.back().policy.ordering.set_c_order(); - mappings.back().policy.exact = true; + mappings.push_back(StoreMapping::default_mapping(output, options.front(), true /*exact*/)); } return std::move(mappings); } case CUNUMERIC_BITGENERATOR: { std::vector mappings; - auto& inputs = task.inputs(); - auto& outputs = task.outputs(); + auto inputs = task.inputs(); + auto outputs = task.outputs(); for (auto& input : inputs) { - mappings.push_back(StoreMapping::default_mapping(input, options.front())); - mappings.back().policy.exact = true; + mappings.push_back(StoreMapping::default_mapping(input, options.front(), true /*exact*/)); } for (auto& output : outputs) { - mappings.push_back(StoreMapping::default_mapping(output, options.front())); - mappings.back().policy.exact = true; + mappings.push_back(StoreMapping::default_mapping(output, options.front(), true /*exact*/)); } return std::move(mappings); } diff --git a/src/cunumeric/ndarray.cc b/src/cunumeric/ndarray.cc index 152a29e2f6..efa474e324 100644 --- a/src/cunumeric/ndarray.cc +++ b/src/cunumeric/ndarray.cc @@ -47,7 +47,7 @@ int32_t NDArray::dim() const { return store_.dim(); } const std::vector& NDArray::shape() const { return store_.extents().data(); } -const legate::Type& NDArray::type() const { return store_.type(); } +legate::Type NDArray::type() const { return store_.type(); } static std::vector compute_strides(const std::vector& shape) { @@ -162,7 +162,7 @@ void NDArray::eye(int32_t k) { assert(dim() == 2); - auto zero = legate::type_dispatch(type().code, generate_zero_fn{}); + auto zero = legate::type_dispatch(type().code(), generate_zero_fn{}); fill(zero, false); auto runtime = CuNumericRuntime::get_runtime(); @@ -392,7 +392,7 @@ std::vector NDArray::nonzero() NDArray NDArray::unique() { - auto& machine = legate::Runtime::get_runtime()->get_machine(); + auto machine = legate::Runtime::get_runtime()->get_machine(); bool has_gpus = machine.count(legate::mapping::TaskTarget::GPU) > 0; auto runtime = CuNumericRuntime::get_runtime(); @@ -410,13 +410,13 @@ NDArray NDArray::unique() return result; } -NDArray NDArray::as_type(std::unique_ptr type) +NDArray NDArray::as_type(const legate::Type& type) { auto runtime = CuNumericRuntime::get_runtime(); // TODO: Check if conversion is valid - auto out = runtime->create_array(shape(), std::move(type)); + auto out = runtime->create_array(shape(), type); assert(store_.type() != out.store_.type()); @@ -436,7 +436,7 @@ NDArray NDArray::as_type(std::unique_ptr type) return std::move(out); } -void NDArray::create_window(WindowOpCode op_code, int64_t M, std::vector args) +void NDArray::create_window(int32_t op_code, int64_t M, std::vector args) { auto runtime = CuNumericRuntime::get_runtime(); @@ -444,7 +444,7 @@ void NDArray::create_window(WindowOpCode op_code, int64_t M, std::vector auto p_lhs = task.declare_partition(); task.add_output(store_, p_lhs); - task.add_scalar_arg(legate::Scalar(static_cast(op_code))); + task.add_scalar_arg(legate::Scalar(op_code)); task.add_scalar_arg(legate::Scalar(M)); for (double arg : args) { task.add_scalar_arg(legate::Scalar(arg)); } @@ -489,9 +489,9 @@ legate::LogicalStore NDArray::broadcast(NDArray rhs1, NDArray rhs2) return broadcast(out_shape, rhs1.store_); } -/*static*/ legate::LibraryContext* NDArray::get_context() +/*static*/ legate::Library NDArray::get_library() { - return CuNumericRuntime::get_runtime()->get_context(); + return CuNumericRuntime::get_runtime()->get_library(); } } // namespace cunumeric diff --git a/src/cunumeric/ndarray.h b/src/cunumeric/ndarray.h index 7404df212f..01a78dd03b 100644 --- a/src/cunumeric/ndarray.h +++ b/src/cunumeric/ndarray.h @@ -22,7 +22,6 @@ #include "legate.h" #include "cunumeric/slice.h" #include "cunumeric/typedefs.h" -#include "cunumeric/nullary/window_util.h" namespace cunumeric { @@ -43,7 +42,7 @@ class NDArray { public: int32_t dim() const; const std::vector& shape() const; - const legate::Type& type() const; + legate::Type type() const; public: template @@ -78,10 +77,10 @@ class NDArray { void arange(double start, double stop, double step); std::vector nonzero(); NDArray unique(); - void create_window(WindowOpCode op_code, int64_t M, std::vector args); + void create_window(int32_t op_code, int64_t M, std::vector args); public: - NDArray as_type(std::unique_ptr type); + NDArray as_type(const legate::Type& type); legate::LogicalStore get_store(); private: @@ -89,7 +88,7 @@ class NDArray { legate::LogicalStore broadcast(NDArray rhs1, NDArray rhs2); public: - static legate::LibraryContext* get_context(); + static legate::Library get_library(); private: legate::LogicalStore store_; diff --git a/src/cunumeric/ndarray.inl b/src/cunumeric/ndarray.inl index f349803fe7..f1ff44c866 100644 --- a/src/cunumeric/ndarray.inl +++ b/src/cunumeric/ndarray.inl @@ -20,8 +20,7 @@ template legate::AccessorRO NDArray::get_read_accessor() { auto mapped = store_.get_physical_store(); - auto shape = mapped->shape(); - return mapped->read_accessor(shape); + return mapped.read_accessor(); } } // namespace cunumeric diff --git a/src/cunumeric/nullary/fill_template.inl b/src/cunumeric/nullary/fill_template.inl index dc9c2f609b..4b671be962 100644 --- a/src/cunumeric/nullary/fill_template.inl +++ b/src/cunumeric/nullary/fill_template.inl @@ -76,8 +76,8 @@ static void fill_template(TaskContext& context) #ifdef DEBUG_CUNUMERIC assert(args.is_argval); #endif - auto& field_type = static_cast(args.out.type()).field_type(1); - code = field_type.code; + auto field_type = args.out.type().as_struct_type().field_type(1); + code = field_type.code(); } double_dispatch(args.out.dim(), code, FillImpl{}, args); } diff --git a/src/cunumeric/operators.cc b/src/cunumeric/operators.cc index 0e155226c3..f4f1497258 100644 --- a/src/cunumeric/operators.cc +++ b/src/cunumeric/operators.cc @@ -24,9 +24,9 @@ namespace cunumeric { -NDArray array(std::vector shape, std::unique_ptr type) +NDArray array(std::vector shape, const legate::Type& type) { - return CuNumericRuntime::get_runtime()->create_array(std::move(shape), std::move(type)); + return CuNumericRuntime::get_runtime()->create_array(std::move(shape), type); } NDArray unary_op(UnaryOpCode op_code, NDArray input) @@ -91,30 +91,30 @@ struct generate_zero_fn { } // namespace -NDArray zeros(std::vector shape, std::unique_ptr type) +NDArray zeros(std::vector shape, std::optional type) { - if (nullptr == type) type = legate::float64(); - if (static_cast(type->code) >= static_cast(legate::Type::Code::FIXED_ARRAY)) + auto code = type.has_value() ? type.value().code() : legate::Type::Code::FLOAT64; + if (static_cast(code) >= static_cast(legate::Type::Code::FIXED_ARRAY)) throw std::invalid_argument("Type must be a primitive type"); - auto zero = legate::type_dispatch(type->code, generate_zero_fn{}); + auto zero = legate::type_dispatch(code, generate_zero_fn{}); return full(shape, zero); } NDArray full(std::vector shape, const Scalar& value) { auto runtime = CuNumericRuntime::get_runtime(); - auto out = runtime->create_array(std::move(shape), value.type().clone()); + auto out = runtime->create_array(std::move(shape), value.type()); out.fill(value, false); return std::move(out); } -NDArray eye(size_t n, std::optional m, int32_t k, std::unique_ptr type) +NDArray eye(size_t n, std::optional m, int32_t k, const legate::Type& type) { - if (static_cast(type->code) >= static_cast(legate::Type::Code::FIXED_ARRAY)) + if (static_cast(type.code()) >= static_cast(legate::Type::Code::FIXED_ARRAY)) throw std::invalid_argument("Type must be a primitive type"); auto runtime = CuNumericRuntime::get_runtime(); - auto out = runtime->create_array({n, m.value_or(n)}, std::move(type)); + auto out = runtime->create_array({n, m.value_or(n)}, type); out.eye(k); return std::move(out); } @@ -174,7 +174,7 @@ NDArray unique(NDArray input) { return input.unique(); } NDArray arange(std::optional start, std::optional stop, std::optional step, - std::optional> type) + const legate::Type& type) { if (!stop.has_value()) { stop = start; @@ -182,7 +182,7 @@ NDArray arange(std::optional start, } size_t N = ceil((stop.value() - start.value()) / step.value()); - auto out = CuNumericRuntime::get_runtime()->create_array({N}, std::move(type.value())); + auto out = CuNumericRuntime::get_runtime()->create_array({N}, type); out.arange(start.value(), stop.value(), step.value()); return std::move(out); } @@ -220,7 +220,7 @@ NDArray create_window(int64_t M, WindowOpCode op_code, std::vector args) return out; } auto out = runtime->create_array({static_cast(M)}, std::move(type)); - out.create_window(op_code, M, args); + out.create_window(static_cast(op_code), M, args); return out; } diff --git a/src/cunumeric/operators.h b/src/cunumeric/operators.h index 700dd90a84..b78da7b049 100644 --- a/src/cunumeric/operators.h +++ b/src/cunumeric/operators.h @@ -27,7 +27,7 @@ namespace cunumeric { void initialize(int32_t argc, char** argv); -NDArray array(std::vector shape, std::unique_ptr type); +NDArray array(std::vector shape, const legate::Type& type); NDArray abs(NDArray input); @@ -41,7 +41,7 @@ NDArray negative(NDArray input); NDArray random(std::vector shape); -NDArray zeros(std::vector shape, std::unique_ptr type = nullptr); +NDArray zeros(std::vector shape, std::optional type = std::nullopt); NDArray full(std::vector shape, const Scalar& value); @@ -49,10 +49,10 @@ NDArray sum(NDArray input); NDArray unique(NDArray input); -NDArray arange(std::optional start = 0, - std::optional stop = std::nullopt, - std::optional step = 1, - std::optional> type = legate::float64()); +NDArray arange(std::optional start = 0, + std::optional stop = std::nullopt, + std::optional step = 1, + const legate::Type& type = legate::float64()); NDArray as_array(legate::LogicalStore store); @@ -62,8 +62,8 @@ std::vector nonzero(NDArray input); NDArray eye(size_t n, std::optional m, - int32_t k = 0, - std::unique_ptr type = legate::float64()); + int32_t k = 0, + const legate::Type& type = legate::float64()); NDArray tril(NDArray rhs, int32_t k = 0); diff --git a/src/cunumeric/runtime.cc b/src/cunumeric/runtime.cc index 64e55fbd12..8a4da330e4 100644 --- a/src/cunumeric/runtime.cc +++ b/src/cunumeric/runtime.cc @@ -33,32 +33,21 @@ void initialize(int32_t argc, char** argv) Legion::Runtime::perform_registration_callback(bootstrapping_callback, true /*global*/); } -CuNumericRuntime::CuNumericRuntime(legate::Runtime* legate_runtime, legate::LibraryContext* context) - : legate_runtime_(legate_runtime), context_(context) +CuNumericRuntime::CuNumericRuntime(legate::Runtime* legate_runtime, legate::Library library) + : legate_runtime_(legate_runtime), library_(library) { } -NDArray CuNumericRuntime::create_array(std::unique_ptr type) -{ - auto store = legate_runtime_->create_store(std::move(type)); - return NDArray(std::move(store)); -} - NDArray CuNumericRuntime::create_array(const legate::Type& type) { - return create_array(type.clone()); -} - -NDArray CuNumericRuntime::create_array(std::vector shape, - std::unique_ptr type) -{ - auto store = legate_runtime_->create_store(shape, std::move(type), true /*optimize_scalar*/); + auto store = legate_runtime_->create_store(type); return NDArray(std::move(store)); } NDArray CuNumericRuntime::create_array(std::vector shape, const legate::Type& type) { - return create_array(std::move(shape), type.clone()); + auto store = legate_runtime_->create_store(shape, type, true /*optimize_scalar*/); + return NDArray(std::move(store)); } NDArray CuNumericRuntime::create_array(legate::LogicalStore&& store) @@ -86,9 +75,9 @@ struct generate_identity_fn { std::enable_if_t::valid && is_arg_reduce::value>* = nullptr> Scalar operator()(const legate::Type& type) { - auto value = UnaryRedOp::OP::identity; - auto& argred_type = CuNumericRuntime::get_runtime()->get_argred_type(type); - return Scalar(value, argred_type.clone()); + auto value = UnaryRedOp::OP::identity; + auto argred_type = CuNumericRuntime::get_runtime()->get_argred_type(type); + return Scalar(value, argred_type); } template ::valid>* = nullptr> @@ -102,13 +91,13 @@ struct generate_identity_fn { template Scalar operator()(const legate::Type& type) { - return legate::type_dispatch(type.code, generator{}, type); + return legate::type_dispatch(type.code(), generator{}, type); } }; Scalar CuNumericRuntime::get_reduction_identity(UnaryRedCode op, const legate::Type& type) { - auto key = std::make_pair(op, type.code); + auto key = std::make_pair(op, type.code()); auto finder = identities.find(key); if (identities.end() != finder) return finder->second; @@ -117,18 +106,14 @@ Scalar CuNumericRuntime::get_reduction_identity(UnaryRedCode op, const legate::T return identity; } -const legate::Type& CuNumericRuntime::get_argred_type(const legate::Type& value_type) +legate::Type CuNumericRuntime::get_argred_type(const legate::Type& value_type) { - auto finder = argred_types_.find(value_type.code); - if (finder != argred_types_.end()) return *finder->second; - - std::vector> field_types{}; - field_types.push_back(legate::int64()); - field_types.push_back(value_type.clone()); - auto argred_type = legate::struct_type(std::move(field_types), true /*align*/); - const auto& result = *argred_type; - argred_types_.insert({value_type.code, std::move(argred_type)}); - return result; + auto finder = argred_types_.find(value_type.code()); + if (finder != argred_types_.end()) return finder->second; + + auto argred_type = legate::struct_type({legate::int64(), value_type}, true /*align*/); + argred_types_.insert({value_type.code(), argred_type}); + return std::move(argred_type); } namespace { @@ -155,7 +140,7 @@ legate::ReductionOpKind CuNumericRuntime::get_reduction_op(UnaryRedCode op) legate::AutoTask CuNumericRuntime::create_task(CuNumericOpCode op_code) { - return legate_runtime_->create_task(context_, op_code); + return legate_runtime_->create_task(library_, op_code); } void CuNumericRuntime::submit(legate::AutoTask&& task) { legate_runtime_->submit(std::move(task)); } @@ -165,9 +150,9 @@ uint32_t CuNumericRuntime::get_next_random_epoch() { return next_epoch_++; } /*static*/ CuNumericRuntime* CuNumericRuntime::get_runtime() { return runtime_; } /*static*/ void CuNumericRuntime::initialize(legate::Runtime* legate_runtime, - legate::LibraryContext* context) + legate::Library library) { - runtime_ = new CuNumericRuntime(legate_runtime, context); + runtime_ = new CuNumericRuntime(legate_runtime, library); } } // namespace cunumeric diff --git a/src/cunumeric/runtime.h b/src/cunumeric/runtime.h index e39d22cf9c..9a1efde3da 100644 --- a/src/cunumeric/runtime.h +++ b/src/cunumeric/runtime.h @@ -30,12 +30,10 @@ class NDArray; class CuNumericRuntime { private: - CuNumericRuntime(legate::Runtime* legate_runtime, legate::LibraryContext* context); + CuNumericRuntime(legate::Runtime* legate_runtime, legate::Library library); public: - NDArray create_array(std::unique_ptr type); NDArray create_array(const legate::Type& type); - NDArray create_array(std::vector shape, std::unique_ptr type); NDArray create_array(std::vector shape, const legate::Type& type); NDArray create_array(legate::LogicalStore&& store); legate::LogicalStore create_scalar_store(const Scalar& value); @@ -45,7 +43,7 @@ class CuNumericRuntime { legate::ReductionOpKind get_reduction_op(UnaryRedCode op); public: - const legate::Type& get_argred_type(const legate::Type& value_type); + legate::Type get_argred_type(const legate::Type& value_type); public: legate::AutoTask create_task(CuNumericOpCode op_code); @@ -55,20 +53,20 @@ class CuNumericRuntime { uint32_t get_next_random_epoch(); public: - legate::LibraryContext* get_context() const { return context_; } + legate::Library get_library() const { return library_; } public: static CuNumericRuntime* get_runtime(); - static void initialize(legate::Runtime* legate_runtime, legate::LibraryContext* context); + static void initialize(legate::Runtime* legate_runtime, legate::Library library); private: static CuNumericRuntime* runtime_; private: legate::Runtime* legate_runtime_; - legate::LibraryContext* context_; + legate::Library library_; uint32_t next_epoch_{0}; - std::unordered_map> argred_types_; + std::unordered_map argred_types_; }; } // namespace cunumeric diff --git a/src/cunumeric/unary/unary_op_template.inl b/src/cunumeric/unary/unary_op_template.inl index 548cba9bfd..a9109bb1ad 100644 --- a/src/cunumeric/unary/unary_op_template.inl +++ b/src/cunumeric/unary/unary_op_template.inl @@ -165,7 +165,7 @@ struct UnaryOpDispatch { { auto dim = std::max(args.in.dim(), 1); if ((OP_CODE == UnaryOpCode::COPY) && (args.in.code() == Type::Code::FIXED_ARRAY)) { - auto& type = static_cast(args.in.type()); + auto type = args.in.type().as_fixed_array_type(); cunumeric::double_dispatch(dim, type.num_elements(), UnaryCopyImpl{}, args); } else { auto code = OP_CODE == UnaryOpCode::GETARG ? args.out.code() : args.in.code(); From 18f4a8f98515e1e32eea7d071d26d39fb6b215c6 Mon Sep 17 00:00:00 2001 From: Parag Kulkarni Date: Thu, 3 Aug 2023 20:09:28 +0530 Subject: [PATCH 086/462] - Retarget CI for cunumeric.internal. --- .github/workflows/ci-gh.yml | 19 ++++++++++--------- cmake/versions.json | 2 +- 2 files changed, 11 insertions(+), 10 deletions(-) diff --git a/.github/workflows/ci-gh.yml b/.github/workflows/ci-gh.yml index eeb38e29d1..9a129a8d8e 100644 --- a/.github/workflows/ci-gh.yml +++ b/.github/workflows/ci-gh.yml @@ -1,4 +1,4 @@ -name: Build cunumeric on GH +name: Build cunumeric.internal on GH concurrency: group: ci-gpu-on-${{ github.event_name }}-from-${{ github.ref_name }} @@ -8,7 +8,7 @@ on: push: branches: - "pull-request/[0-9]+" - - "branch-*" + - "cpp-branch-*" jobs: build: @@ -17,7 +17,7 @@ jobs: contents: read # This is required for actions/checkout # Ref: https://docs.rapids.ai/resources/github-actions/#cpu-labels for `linux-amd64-cpu4` - runs-on: ${{ github.repository == 'nv-legate/cunumeric' && 'linux-amd64-cpu4' || 'ubuntu-latest' }} + runs-on: ${{ github.repository == 'nv-legate/cunumeric.internal' && 'linux-amd64-cpu4' || 'ubuntu-latest' }} container: options: -u root image: rapidsai/devcontainers:23.06-cpp-cuda11.8-mambaforge-ubuntu22.04 @@ -35,14 +35,15 @@ jobs: VAULT_S3_TTL: "28800s" # 8 hours steps: - - name: Checkout legate.core + - name: Checkout legate.core.internal uses: actions/checkout@v3 with: - repository: nv-legate/legate.core + repository: nv-legate/legate.core.internal fetch-depth: 0 path: legate + token: ${{ secrets.PERSONAL_ACCESS_TOKEN }} - - name: Checkout cunumeric (= this repo) + - name: Checkout cunumeric.internal (= this repo) uses: actions/checkout@v3 with: fetch-depth: 0 @@ -51,7 +52,7 @@ jobs: - name: Setup shell: bash -eo pipefail {0} run: | - export LEGATE_SHA=$(cat cunumeric/cmake/versions.json | jq -r '.packages.legate_core.git_tag') + export LEGATE_SHA=$(cat cunumeric/cmake/versions.json | jq -r '.packages.legate_core_internal.git_tag') echo "Checking out LEGATE_SHA: ${LEGATE_SHA}" git -C legate checkout $LEGATE_SHA @@ -66,7 +67,7 @@ jobs: chown -R coder:coder /home/coder/; chown -R coder:coder /tmp/out; - - if: github.repository == 'nv-legate/cunumeric' + - if: github.repository == 'nv-legate/cunumeric.internal' name: Get AWS credentials for sccache bucket uses: aws-actions/configure-aws-credentials@v2 with: @@ -105,5 +106,5 @@ jobs: - name: Upload build output uses: actions/upload-artifact@v3 with: - name: "cunumeric-${{ github.sha }}" + name: "cunumeric.internal-${{ github.sha }}" path: ./out/* diff --git a/cmake/versions.json b/cmake/versions.json index e5d1a32a7a..b1b5c03511 100644 --- a/cmake/versions.json +++ b/cmake/versions.json @@ -9,7 +9,7 @@ }, "legate_core_internal" : { "version": "23.07.00", - "git_url" : "https://gitlab-master.nvidia.com/legate/legate.core.internal.git", + "git_url" : "https://github.com/nv-legate/legate.core.internal.git", "git_shallow": false, "always_download": false, "git_tag" : "f1419c41eda8fd77f94a58e3493145a2589f665d" From 8ac935ca43cb5b390e01b37ae4eda50a4996f5d3 Mon Sep 17 00:00:00 2001 From: Wonchan Lee Date: Tue, 15 Aug 2023 23:51:18 -0700 Subject: [PATCH 087/462] Catch up Legate array changes (#3) * Catch up changes in the calling convention * Catch up the task signature change * Update the commit hash for legate.core.internal --- cmake/versions.json | 2 +- src/cunumeric/binary/binary_op.cc | 2 +- src/cunumeric/binary/binary_op.cu | 2 +- src/cunumeric/binary/binary_op.h | 12 ++--- src/cunumeric/binary/binary_op_omp.cc | 2 +- src/cunumeric/binary/binary_op_template.inl | 4 +- src/cunumeric/binary/binary_red.cc | 2 +- src/cunumeric/binary/binary_red.cu | 2 +- src/cunumeric/binary/binary_red.h | 12 ++--- src/cunumeric/binary/binary_red_omp.cc | 2 +- src/cunumeric/binary/binary_red_template.inl | 4 +- src/cunumeric/bits/packbits.cc | 2 +- src/cunumeric/bits/packbits.cu | 2 +- src/cunumeric/bits/packbits.h | 6 +-- src/cunumeric/bits/packbits_omp.cc | 2 +- src/cunumeric/bits/packbits_template.inl | 14 ++--- src/cunumeric/bits/unpackbits.cc | 2 +- src/cunumeric/bits/unpackbits.cu | 2 +- src/cunumeric/bits/unpackbits.h | 6 +-- src/cunumeric/bits/unpackbits_omp.cc | 2 +- src/cunumeric/bits/unpackbits_template.inl | 14 ++--- src/cunumeric/convolution/convolve.cc | 2 +- src/cunumeric/convolution/convolve.cu | 2 +- src/cunumeric/convolution/convolve.h | 6 +-- src/cunumeric/convolution/convolve_omp.cc | 2 +- .../convolution/convolve_template.inl | 6 +-- src/cunumeric/cudalibs.cu | 4 +- src/cunumeric/fft/fft.cu | 2 +- src/cunumeric/fft/fft.h | 2 +- src/cunumeric/fft/fft_template.inl | 4 +- src/cunumeric/index/advanced_indexing.cc | 2 +- src/cunumeric/index/advanced_indexing.cu | 2 +- src/cunumeric/index/advanced_indexing.h | 12 ++--- src/cunumeric/index/advanced_indexing_omp.cc | 2 +- .../index/advanced_indexing_template.inl | 7 ++- src/cunumeric/index/choose.cc | 2 +- src/cunumeric/index/choose.cu | 2 +- src/cunumeric/index/choose.h | 10 ++-- src/cunumeric/index/choose_omp.cc | 2 +- src/cunumeric/index/choose_template.inl | 4 +- src/cunumeric/index/putmask.cc | 2 +- src/cunumeric/index/putmask.cu | 2 +- src/cunumeric/index/putmask.h | 12 ++--- src/cunumeric/index/putmask_omp.cc | 2 +- src/cunumeric/index/putmask_template.inl | 4 +- src/cunumeric/index/repeat.cc | 2 +- src/cunumeric/index/repeat.cu | 2 +- src/cunumeric/index/repeat.h | 12 ++--- src/cunumeric/index/repeat_omp.cc | 2 +- src/cunumeric/index/repeat_template.inl | 12 ++--- src/cunumeric/index/wrap.cc | 2 +- src/cunumeric/index/wrap.cu | 2 +- src/cunumeric/index/wrap.h | 10 ++-- src/cunumeric/index/wrap_omp.cc | 2 +- src/cunumeric/index/wrap_template.inl | 15 +++--- src/cunumeric/index/zip.cc | 2 +- src/cunumeric/index/zip.cu | 2 +- src/cunumeric/index/zip.h | 10 ++-- src/cunumeric/index/zip_omp.cc | 2 +- src/cunumeric/index/zip_template.inl | 12 +++-- src/cunumeric/item/read.cc | 2 +- src/cunumeric/item/read.cu | 2 +- src/cunumeric/item/read.h | 6 +-- src/cunumeric/item/read_template.inl | 8 +-- src/cunumeric/item/write.cc | 2 +- src/cunumeric/item/write.cu | 2 +- src/cunumeric/item/write.h | 6 +-- src/cunumeric/item/write_template.inl | 8 +-- src/cunumeric/mapper.cc | 54 ++++++++++++------- src/cunumeric/matrix/contract.cc | 2 +- src/cunumeric/matrix/contract.cu | 2 +- src/cunumeric/matrix/contract.h | 12 ++--- src/cunumeric/matrix/contract_omp.cc | 2 +- src/cunumeric/matrix/contract_template.inl | 6 +-- src/cunumeric/matrix/diag.cc | 2 +- src/cunumeric/matrix/diag.cu | 2 +- src/cunumeric/matrix/diag.h | 10 ++-- src/cunumeric/matrix/diag_omp.cc | 2 +- src/cunumeric/matrix/diag_template.inl | 8 +-- src/cunumeric/matrix/dot.cc | 2 +- src/cunumeric/matrix/dot.cu | 2 +- src/cunumeric/matrix/dot.h | 12 ++--- src/cunumeric/matrix/dot_omp.cc | 2 +- src/cunumeric/matrix/dot_template.inl | 4 +- src/cunumeric/matrix/gemm.cc | 2 +- src/cunumeric/matrix/gemm.cu | 2 +- src/cunumeric/matrix/gemm.h | 6 +-- src/cunumeric/matrix/gemm_omp.cc | 2 +- src/cunumeric/matrix/gemm_template.inl | 10 ++-- src/cunumeric/matrix/matmul.cc | 2 +- src/cunumeric/matrix/matmul.cu | 2 +- src/cunumeric/matrix/matmul.h | 12 ++--- src/cunumeric/matrix/matmul_omp.cc | 2 +- src/cunumeric/matrix/matmul_template.inl | 4 +- src/cunumeric/matrix/matvecmul.cc | 2 +- src/cunumeric/matrix/matvecmul.cu | 2 +- src/cunumeric/matrix/matvecmul.h | 12 ++--- src/cunumeric/matrix/matvecmul_omp.cc | 2 +- src/cunumeric/matrix/matvecmul_template.inl | 4 +- src/cunumeric/matrix/potrf.cc | 2 +- src/cunumeric/matrix/potrf.cu | 2 +- src/cunumeric/matrix/potrf.h | 6 +-- src/cunumeric/matrix/potrf_omp.cc | 2 +- src/cunumeric/matrix/potrf_template.inl | 8 +-- src/cunumeric/matrix/solve.cc | 2 +- src/cunumeric/matrix/solve.cu | 2 +- src/cunumeric/matrix/solve.h | 6 +-- src/cunumeric/matrix/solve_omp.cc | 2 +- src/cunumeric/matrix/solve_template.inl | 10 ++-- src/cunumeric/matrix/syrk.cc | 2 +- src/cunumeric/matrix/syrk.cu | 2 +- src/cunumeric/matrix/syrk.h | 6 +-- src/cunumeric/matrix/syrk_omp.cc | 2 +- src/cunumeric/matrix/syrk_template.inl | 10 ++-- src/cunumeric/matrix/tile.cc | 2 +- src/cunumeric/matrix/tile.cu | 2 +- src/cunumeric/matrix/tile.h | 10 ++-- src/cunumeric/matrix/tile_omp.cc | 2 +- src/cunumeric/matrix/tile_template.inl | 2 +- src/cunumeric/matrix/transpose.cc | 2 +- src/cunumeric/matrix/transpose.cu | 2 +- src/cunumeric/matrix/transpose.h | 10 ++-- src/cunumeric/matrix/transpose_omp.cc | 2 +- src/cunumeric/matrix/transpose_template.inl | 8 +-- src/cunumeric/matrix/trilu.cc | 2 +- src/cunumeric/matrix/trilu.cu | 2 +- src/cunumeric/matrix/trilu.h | 10 ++-- src/cunumeric/matrix/trilu_omp.cc | 2 +- src/cunumeric/matrix/trilu_template.inl | 6 +-- src/cunumeric/matrix/trsm.cc | 2 +- src/cunumeric/matrix/trsm.cu | 2 +- src/cunumeric/matrix/trsm.h | 6 +-- src/cunumeric/matrix/trsm_omp.cc | 2 +- src/cunumeric/matrix/trsm_template.inl | 10 ++-- src/cunumeric/nullary/arange.cc | 2 +- src/cunumeric/nullary/arange.cu | 2 +- src/cunumeric/nullary/arange.h | 14 ++--- src/cunumeric/nullary/arange_omp.cc | 2 +- src/cunumeric/nullary/arange_template.inl | 4 +- src/cunumeric/nullary/eye.cc | 2 +- src/cunumeric/nullary/eye.cu | 2 +- src/cunumeric/nullary/eye.h | 8 +-- src/cunumeric/nullary/eye_omp.cc | 2 +- src/cunumeric/nullary/eye_template.inl | 2 +- src/cunumeric/nullary/fill.cc | 2 +- src/cunumeric/nullary/fill.cu | 2 +- src/cunumeric/nullary/fill.h | 10 ++-- src/cunumeric/nullary/fill_omp.cc | 2 +- src/cunumeric/nullary/fill_template.inl | 2 +- src/cunumeric/nullary/window.cc | 2 +- src/cunumeric/nullary/window.cu | 2 +- src/cunumeric/nullary/window.h | 6 +-- src/cunumeric/nullary/window_omp.cc | 2 +- src/cunumeric/nullary/window_template.inl | 4 +- src/cunumeric/random/bitgenerator.cc | 2 +- src/cunumeric/random/bitgenerator.cu | 2 +- src/cunumeric/random/bitgenerator.h | 6 +-- src/cunumeric/random/bitgenerator_curand.inl | 6 +-- .../random/bitgenerator_template.inl | 4 +- src/cunumeric/random/rand.cc | 2 +- src/cunumeric/random/rand.cu | 2 +- src/cunumeric/random/rand.h | 8 +-- src/cunumeric/random/rand_omp.cc | 2 +- src/cunumeric/random/rand_template.inl | 4 +- src/cunumeric/scan/scan_global.cc | 2 +- src/cunumeric/scan/scan_global.cu | 2 +- src/cunumeric/scan/scan_global.h | 10 ++-- src/cunumeric/scan/scan_global_omp.cc | 2 +- src/cunumeric/scan/scan_global_template.inl | 2 +- src/cunumeric/scan/scan_local.cc | 2 +- src/cunumeric/scan/scan_local.cu | 2 +- src/cunumeric/scan/scan_local.h | 12 ++--- src/cunumeric/scan/scan_local_omp.cc | 2 +- src/cunumeric/scan/scan_local_template.inl | 10 ++-- src/cunumeric/search/argwhere.cc | 4 +- src/cunumeric/search/argwhere.cu | 4 +- src/cunumeric/search/argwhere.h | 10 ++-- src/cunumeric/search/argwhere_omp.cc | 4 +- src/cunumeric/search/argwhere_template.inl | 2 +- src/cunumeric/search/nonzero.cc | 2 +- src/cunumeric/search/nonzero.cu | 2 +- src/cunumeric/search/nonzero.h | 10 ++-- src/cunumeric/search/nonzero_omp.cc | 2 +- src/cunumeric/search/nonzero_template.inl | 4 +- src/cunumeric/set/unique.cc | 4 +- src/cunumeric/set/unique.cu | 6 +-- src/cunumeric/set/unique.h | 6 +-- src/cunumeric/set/unique_omp.cc | 4 +- src/cunumeric/set/unique_reduce.cc | 2 +- src/cunumeric/set/unique_reduce.h | 4 +- src/cunumeric/set/unique_reduce_omp.cc | 2 +- src/cunumeric/set/unique_reduce_template.inl | 12 +++-- src/cunumeric/set/unique_template.inl | 12 ++--- src/cunumeric/sort/searchsorted.cc | 2 +- src/cunumeric/sort/searchsorted.cu | 2 +- src/cunumeric/sort/searchsorted.h | 12 ++--- src/cunumeric/sort/searchsorted_omp.cc | 2 +- src/cunumeric/sort/searchsorted_template.inl | 10 ++-- src/cunumeric/sort/sort.cc | 2 +- src/cunumeric/sort/sort.cu | 37 ++++++------- src/cunumeric/sort/sort.h | 10 ++-- src/cunumeric/sort/sort_cpu.inl | 6 +-- src/cunumeric/sort/sort_omp.cc | 2 +- src/cunumeric/sort/sort_template.inl | 12 ++--- src/cunumeric/stat/bincount.cc | 2 +- src/cunumeric/stat/bincount.cu | 2 +- src/cunumeric/stat/bincount.h | 12 ++--- src/cunumeric/stat/bincount_omp.cc | 2 +- src/cunumeric/stat/bincount_template.inl | 4 +- src/cunumeric/stat/histogram.cc | 2 +- src/cunumeric/stat/histogram.cu | 2 +- src/cunumeric/stat/histogram.h | 16 +++--- src/cunumeric/stat/histogram_omp.cc | 2 +- src/cunumeric/stat/histogram_template.inl | 4 +- src/cunumeric/ternary/where.cc | 2 +- src/cunumeric/ternary/where.cu | 2 +- src/cunumeric/ternary/where.h | 14 ++--- src/cunumeric/ternary/where_omp.cc | 2 +- src/cunumeric/ternary/where_template.inl | 4 +- src/cunumeric/transform/flip.cc | 2 +- src/cunumeric/transform/flip.cu | 2 +- src/cunumeric/transform/flip.h | 10 ++-- src/cunumeric/transform/flip_omp.cc | 2 +- src/cunumeric/transform/flip_template.inl | 4 +- src/cunumeric/unary/convert.cc | 2 +- src/cunumeric/unary/convert.cu | 2 +- src/cunumeric/unary/convert.h | 10 ++-- src/cunumeric/unary/convert_omp.cc | 2 +- src/cunumeric/unary/convert_template.inl | 3 +- src/cunumeric/unary/scalar_unary_red.cc | 2 +- src/cunumeric/unary/scalar_unary_red.cu | 2 +- src/cunumeric/unary/scalar_unary_red.h | 10 ++-- src/cunumeric/unary/scalar_unary_red_omp.cc | 2 +- .../unary/scalar_unary_red_template.inl | 5 +- src/cunumeric/unary/unary_op.cc | 2 +- src/cunumeric/unary/unary_op.cu | 2 +- src/cunumeric/unary/unary_op.h | 16 +++--- src/cunumeric/unary/unary_op_omp.cc | 2 +- src/cunumeric/unary/unary_op_template.inl | 4 +- src/cunumeric/unary/unary_red.cc | 2 +- src/cunumeric/unary/unary_red.cu | 2 +- src/cunumeric/unary/unary_red.h | 10 ++-- src/cunumeric/unary/unary_red_omp.cc | 2 +- src/cunumeric/unary/unary_red_template.inl | 6 +-- 244 files changed, 604 insertions(+), 587 deletions(-) diff --git a/cmake/versions.json b/cmake/versions.json index 60faa371af..b10d08615c 100644 --- a/cmake/versions.json +++ b/cmake/versions.json @@ -12,7 +12,7 @@ "git_url" : "https://github.com/nv-legate/legate.core.internal.git", "git_shallow": false, "always_download": false, - "git_tag" : "d77bb736da79a843f9a3b8bdfa955a4f2ce12297" + "git_tag" : "ef3a2942a4b6e93124656421c02ce9a36dc6fd0d" } } } diff --git a/src/cunumeric/binary/binary_op.cc b/src/cunumeric/binary/binary_op.cc index ed309f0e9e..90adf9e033 100644 --- a/src/cunumeric/binary/binary_op.cc +++ b/src/cunumeric/binary/binary_op.cc @@ -51,7 +51,7 @@ struct BinaryOpImplBody { } }; -/*static*/ void BinaryOpTask::cpu_variant(TaskContext& context) +/*static*/ void BinaryOpTask::cpu_variant(TaskContext context) { binary_op_template(context); } diff --git a/src/cunumeric/binary/binary_op.cu b/src/cunumeric/binary/binary_op.cu index 76177b154c..6ef70e448a 100644 --- a/src/cunumeric/binary/binary_op.cu +++ b/src/cunumeric/binary/binary_op.cu @@ -82,7 +82,7 @@ struct BinaryOpImplBody { } }; -/*static*/ void BinaryOpTask::gpu_variant(TaskContext& context) +/*static*/ void BinaryOpTask::gpu_variant(TaskContext context) { binary_op_template(context); } diff --git a/src/cunumeric/binary/binary_op.h b/src/cunumeric/binary/binary_op.h index e0977838b3..7e5334257a 100644 --- a/src/cunumeric/binary/binary_op.h +++ b/src/cunumeric/binary/binary_op.h @@ -22,9 +22,9 @@ namespace cunumeric { struct BinaryOpArgs { - const legate::Store& in1; - const legate::Store& in2; - const legate::Store& out; + legate::Store in1; + legate::Store in2; + legate::Store out; BinaryOpCode op_code; std::vector args; }; @@ -34,12 +34,12 @@ class BinaryOpTask : public CuNumericTask { static const int TASK_ID = CUNUMERIC_BINARY_OP; public: - static void cpu_variant(legate::TaskContext& context); + static void cpu_variant(legate::TaskContext context); #ifdef LEGATE_USE_OPENMP - static void omp_variant(legate::TaskContext& context); + static void omp_variant(legate::TaskContext context); #endif #ifdef LEGATE_USE_CUDA - static void gpu_variant(legate::TaskContext& context); + static void gpu_variant(legate::TaskContext context); #endif }; diff --git a/src/cunumeric/binary/binary_op_omp.cc b/src/cunumeric/binary/binary_op_omp.cc index 53ec582a72..e6985925a2 100644 --- a/src/cunumeric/binary/binary_op_omp.cc +++ b/src/cunumeric/binary/binary_op_omp.cc @@ -53,7 +53,7 @@ struct BinaryOpImplBody { } }; -/*static*/ void BinaryOpTask::omp_variant(TaskContext& context) +/*static*/ void BinaryOpTask::omp_variant(TaskContext context) { binary_op_template(context); } diff --git a/src/cunumeric/binary/binary_op_template.inl b/src/cunumeric/binary/binary_op_template.inl index d324848359..22253d036b 100644 --- a/src/cunumeric/binary/binary_op_template.inl +++ b/src/cunumeric/binary/binary_op_template.inl @@ -82,8 +82,8 @@ struct BinaryOpDispatch { template static void binary_op_template(TaskContext& context) { - auto& inputs = context.inputs(); - auto& outputs = context.outputs(); + auto inputs = context.inputs(); + auto outputs = context.outputs(); auto& scalars = context.scalars(); std::vector extra_args; diff --git a/src/cunumeric/binary/binary_red.cc b/src/cunumeric/binary/binary_red.cc index 5340e53342..47adecbf2c 100644 --- a/src/cunumeric/binary/binary_red.cc +++ b/src/cunumeric/binary/binary_red.cc @@ -58,7 +58,7 @@ struct BinaryRedImplBody { } }; -/*static*/ void BinaryRedTask::cpu_variant(TaskContext& context) +/*static*/ void BinaryRedTask::cpu_variant(TaskContext context) { binary_red_template(context); } diff --git a/src/cunumeric/binary/binary_red.cu b/src/cunumeric/binary/binary_red.cu index 98435abd8f..a0007cad9d 100644 --- a/src/cunumeric/binary/binary_red.cu +++ b/src/cunumeric/binary/binary_red.cu @@ -78,7 +78,7 @@ struct BinaryRedImplBody { } }; -/*static*/ void BinaryRedTask::gpu_variant(TaskContext& context) +/*static*/ void BinaryRedTask::gpu_variant(TaskContext context) { binary_red_template(context); } diff --git a/src/cunumeric/binary/binary_red.h b/src/cunumeric/binary/binary_red.h index b07c7999cf..ca11bb62fb 100644 --- a/src/cunumeric/binary/binary_red.h +++ b/src/cunumeric/binary/binary_red.h @@ -22,9 +22,9 @@ namespace cunumeric { struct BinaryRedArgs { - const legate::Store& out; - const legate::Store& in1; - const legate::Store& in2; + legate::Store out; + legate::Store in1; + legate::Store in2; BinaryOpCode op_code; std::vector args; }; @@ -34,12 +34,12 @@ class BinaryRedTask : public CuNumericTask { static const int TASK_ID = CUNUMERIC_BINARY_RED; public: - static void cpu_variant(legate::TaskContext& context); + static void cpu_variant(legate::TaskContext context); #ifdef LEGATE_USE_OPENMP - static void omp_variant(legate::TaskContext& context); + static void omp_variant(legate::TaskContext context); #endif #ifdef LEGATE_USE_CUDA - static void gpu_variant(legate::TaskContext& context); + static void gpu_variant(legate::TaskContext context); #endif }; diff --git a/src/cunumeric/binary/binary_red_omp.cc b/src/cunumeric/binary/binary_red_omp.cc index 891aa7abde..9237431f2f 100644 --- a/src/cunumeric/binary/binary_red_omp.cc +++ b/src/cunumeric/binary/binary_red_omp.cc @@ -55,7 +55,7 @@ struct BinaryRedImplBody { } }; -/*static*/ void BinaryRedTask::omp_variant(TaskContext& context) +/*static*/ void BinaryRedTask::omp_variant(TaskContext context) { binary_red_template(context); } diff --git a/src/cunumeric/binary/binary_red_template.inl b/src/cunumeric/binary/binary_red_template.inl index 4bff8e454f..d77983e2f0 100644 --- a/src/cunumeric/binary/binary_red_template.inl +++ b/src/cunumeric/binary/binary_red_template.inl @@ -86,13 +86,13 @@ struct BinaryRedDispatch { template static void binary_red_template(TaskContext& context) { - auto& inputs = context.inputs(); + auto inputs = context.inputs(); auto& scalars = context.scalars(); std::vector extra_args; for (size_t idx = 2; idx < inputs.size(); ++idx) extra_args.push_back(std::move(inputs[idx])); - BinaryRedArgs args{context.reductions()[0], + BinaryRedArgs args{context.reduction(0), inputs[0], inputs[1], scalars[0].value(), diff --git a/src/cunumeric/bits/packbits.cc b/src/cunumeric/bits/packbits.cc index 99eac967ca..98f4c1ad3d 100644 --- a/src/cunumeric/bits/packbits.cc +++ b/src/cunumeric/bits/packbits.cc @@ -50,7 +50,7 @@ struct PackbitsImplBody { } }; -/*static*/ void PackbitsTask::cpu_variant(TaskContext& context) +/*static*/ void PackbitsTask::cpu_variant(TaskContext context) { packbits_template(context); } diff --git a/src/cunumeric/bits/packbits.cu b/src/cunumeric/bits/packbits.cu index 81edb82b46..0f219b2380 100644 --- a/src/cunumeric/bits/packbits.cu +++ b/src/cunumeric/bits/packbits.cu @@ -78,7 +78,7 @@ struct PackbitsImplBody { } }; -/*static*/ void PackbitsTask::gpu_variant(TaskContext& context) +/*static*/ void PackbitsTask::gpu_variant(TaskContext context) { packbits_template(context); } diff --git a/src/cunumeric/bits/packbits.h b/src/cunumeric/bits/packbits.h index 8718de0de5..1af0b45489 100644 --- a/src/cunumeric/bits/packbits.h +++ b/src/cunumeric/bits/packbits.h @@ -106,12 +106,12 @@ class PackbitsTask : public CuNumericTask { static const int TASK_ID = CUNUMERIC_PACKBITS; public: - static void cpu_variant(legate::TaskContext& context); + static void cpu_variant(legate::TaskContext context); #ifdef LEGATE_USE_OPENMP - static void omp_variant(legate::TaskContext& context); + static void omp_variant(legate::TaskContext context); #endif #ifdef LEGATE_USE_CUDA - static void gpu_variant(legate::TaskContext& context); + static void gpu_variant(legate::TaskContext context); #endif }; diff --git a/src/cunumeric/bits/packbits_omp.cc b/src/cunumeric/bits/packbits_omp.cc index 7e8e05c557..59182c14f2 100644 --- a/src/cunumeric/bits/packbits_omp.cc +++ b/src/cunumeric/bits/packbits_omp.cc @@ -52,7 +52,7 @@ struct PackbitsImplBody { } }; -/*static*/ void PackbitsTask::omp_variant(TaskContext& context) +/*static*/ void PackbitsTask::omp_variant(TaskContext context) { packbits_template(context); } diff --git a/src/cunumeric/bits/packbits_template.inl b/src/cunumeric/bits/packbits_template.inl index 7e1e68a890..d70d8a7ccf 100644 --- a/src/cunumeric/bits/packbits_template.inl +++ b/src/cunumeric/bits/packbits_template.inl @@ -30,7 +30,7 @@ struct PackbitsImplBody; template struct PackbitsImpl { template ::value>* = nullptr> - void operator()(Array& output, Array& input, uint32_t axis) const + void operator()(legate::Store output, legate::Store input, uint32_t axis) const { using VAL = legate_type_of; @@ -75,7 +75,7 @@ struct PackbitsImpl { } template ::value>* = nullptr> - void operator()(Array& output, Array& input, uint32_t axis) const + void operator()(legate::Store output, legate::Store input, uint32_t axis) const { // Unreachable assert(false); @@ -85,11 +85,11 @@ struct PackbitsImpl { template static void packbits_template(TaskContext& context) { - auto& output = context.outputs().front(); - auto& input = context.inputs().front(); - auto& scalars = context.scalars(); - auto axis = scalars[0].value(); - auto bitorder = scalars[1].value(); + legate::Store output = context.output(0); + legate::Store input = context.input(0); + auto& scalars = context.scalars(); + auto axis = scalars[0].value(); + auto bitorder = scalars[1].value(); auto code = input.code(); switch (bitorder) { diff --git a/src/cunumeric/bits/unpackbits.cc b/src/cunumeric/bits/unpackbits.cc index 263f73714e..2302b4afc8 100644 --- a/src/cunumeric/bits/unpackbits.cc +++ b/src/cunumeric/bits/unpackbits.cc @@ -38,7 +38,7 @@ struct UnpackbitsImplBody { } }; -/*static*/ void UnpackbitsTask::cpu_variant(TaskContext& context) +/*static*/ void UnpackbitsTask::cpu_variant(TaskContext context) { unpackbits_template(context); } diff --git a/src/cunumeric/bits/unpackbits.cu b/src/cunumeric/bits/unpackbits.cu index 7b7f558042..c48edde560 100644 --- a/src/cunumeric/bits/unpackbits.cu +++ b/src/cunumeric/bits/unpackbits.cu @@ -57,7 +57,7 @@ struct UnpackbitsImplBody { } }; -/*static*/ void UnpackbitsTask::gpu_variant(TaskContext& context) +/*static*/ void UnpackbitsTask::gpu_variant(TaskContext context) { unpackbits_template(context); } diff --git a/src/cunumeric/bits/unpackbits.h b/src/cunumeric/bits/unpackbits.h index 52c4b7fefe..eb7ed47c94 100644 --- a/src/cunumeric/bits/unpackbits.h +++ b/src/cunumeric/bits/unpackbits.h @@ -63,12 +63,12 @@ class UnpackbitsTask : public CuNumericTask { static const int TASK_ID = CUNUMERIC_UNPACKBITS; public: - static void cpu_variant(legate::TaskContext& context); + static void cpu_variant(legate::TaskContext context); #ifdef LEGATE_USE_OPENMP - static void omp_variant(legate::TaskContext& context); + static void omp_variant(legate::TaskContext context); #endif #ifdef LEGATE_USE_CUDA - static void gpu_variant(legate::TaskContext& context); + static void gpu_variant(legate::TaskContext context); #endif }; diff --git a/src/cunumeric/bits/unpackbits_omp.cc b/src/cunumeric/bits/unpackbits_omp.cc index b101d0395d..be48aafd6f 100644 --- a/src/cunumeric/bits/unpackbits_omp.cc +++ b/src/cunumeric/bits/unpackbits_omp.cc @@ -39,7 +39,7 @@ struct UnpackbitsImplBody { } }; -/*static*/ void UnpackbitsTask::omp_variant(TaskContext& context) +/*static*/ void UnpackbitsTask::omp_variant(TaskContext context) { unpackbits_template(context); } diff --git a/src/cunumeric/bits/unpackbits_template.inl b/src/cunumeric/bits/unpackbits_template.inl index 4ac5708e90..11bed73bb2 100644 --- a/src/cunumeric/bits/unpackbits_template.inl +++ b/src/cunumeric/bits/unpackbits_template.inl @@ -30,7 +30,7 @@ struct UnpackbitsImplBody; template struct UnpackbitsImpl { template - void operator()(Store& output, Store& input, uint32_t axis) const + void operator()(Store output, Store input, uint32_t axis) const { auto out_rect = output.shape(); @@ -48,7 +48,7 @@ struct UnpackbitsImpl { } template ::value>* = nullptr> - void operator()(Array& output, Array& input, uint32_t axis) const + void operator()(legate::Store output, legate::Store input, uint32_t axis) const { // Unreachable assert(false); @@ -58,11 +58,11 @@ struct UnpackbitsImpl { template static void unpackbits_template(TaskContext& context) { - auto& output = context.outputs().front(); - auto& input = context.inputs().front(); - auto& scalars = context.scalars(); - auto axis = scalars[0].value(); - auto bitorder = scalars[1].value(); + legate::Store output = context.output(0); + legate::Store input = context.input(0); + auto& scalars = context.scalars(); + auto axis = scalars[0].value(); + auto bitorder = scalars[1].value(); auto code = input.code(); switch (bitorder) { diff --git a/src/cunumeric/convolution/convolve.cc b/src/cunumeric/convolution/convolve.cc index 3827be7181..d20788a3e7 100644 --- a/src/cunumeric/convolution/convolve.cc +++ b/src/cunumeric/convolution/convolve.cc @@ -250,7 +250,7 @@ struct ConvolveImplBody { } }; -/*static*/ void ConvolveTask::cpu_variant(TaskContext& context) +/*static*/ void ConvolveTask::cpu_variant(TaskContext context) { convolve_template(context); } diff --git a/src/cunumeric/convolution/convolve.cu b/src/cunumeric/convolution/convolve.cu index 7e4ad66e62..6fdc6443e7 100644 --- a/src/cunumeric/convolution/convolve.cu +++ b/src/cunumeric/convolution/convolve.cu @@ -1443,7 +1443,7 @@ struct ConvolveImplBody { } }; -/*static*/ void ConvolveTask::gpu_variant(TaskContext& context) +/*static*/ void ConvolveTask::gpu_variant(TaskContext context) { convolve_template(context); } diff --git a/src/cunumeric/convolution/convolve.h b/src/cunumeric/convolution/convolve.h index 096ec977eb..32d4439ce7 100644 --- a/src/cunumeric/convolution/convolve.h +++ b/src/cunumeric/convolution/convolve.h @@ -41,12 +41,12 @@ class ConvolveTask : public CuNumericTask { static const int TASK_ID = CUNUMERIC_CONVOLVE; public: - static void cpu_variant(legate::TaskContext& context); + static void cpu_variant(legate::TaskContext context); #ifdef LEGATE_USE_OPENMP - static void omp_variant(legate::TaskContext& context); + static void omp_variant(legate::TaskContext context); #endif #ifdef LEGATE_USE_CUDA - static void gpu_variant(legate::TaskContext& context); + static void gpu_variant(legate::TaskContext context); #endif }; diff --git a/src/cunumeric/convolution/convolve_omp.cc b/src/cunumeric/convolution/convolve_omp.cc index 283f4b7b92..a3531627b4 100644 --- a/src/cunumeric/convolution/convolve_omp.cc +++ b/src/cunumeric/convolution/convolve_omp.cc @@ -214,7 +214,7 @@ struct ConvolveImplBody { } }; -/*static*/ void ConvolveTask::omp_variant(TaskContext& context) +/*static*/ void ConvolveTask::omp_variant(TaskContext context) { convolve_template(context); } diff --git a/src/cunumeric/convolution/convolve_template.inl b/src/cunumeric/convolution/convolve_template.inl index f698ebfd49..3b0fb28805 100644 --- a/src/cunumeric/convolution/convolve_template.inl +++ b/src/cunumeric/convolution/convolve_template.inl @@ -67,14 +67,14 @@ static void convolve_template(TaskContext& context) { ConvolveArgs args; - auto& inputs = context.inputs(); - auto& outputs = context.outputs(); + auto inputs = context.inputs(); + auto outputs = context.outputs(); args.out = std::move(outputs[0]); args.filter = std::move(inputs[0]); for (uint32_t idx = 1; idx < inputs.size(); ++idx) args.inputs.push_back(std::move(inputs[idx])); - auto shape = context.scalars()[0].value(); + auto shape = context.scalar(0).value(); args.root_domain.dim = shape.dim; for (int32_t dim = 0; dim < shape.dim; ++dim) { args.root_domain.rect_data[dim] = 0; diff --git a/src/cunumeric/cudalibs.cu b/src/cunumeric/cudalibs.cu index c702c07a44..cc7462b26f 100644 --- a/src/cunumeric/cudalibs.cu +++ b/src/cunumeric/cudalibs.cu @@ -370,7 +370,7 @@ class LoadCUDALibsTask : public CuNumericTask { static const int TASK_ID = CUNUMERIC_LOAD_CUDALIBS; public: - static void gpu_variant(legate::TaskContext& context) + static void gpu_variant(legate::TaskContext context) { const auto proc = legate::Processor::get_executing_processor(); auto& lib = get_cuda_libraries(proc); @@ -385,7 +385,7 @@ class UnloadCUDALibsTask : public CuNumericTask { static const int TASK_ID = CUNUMERIC_UNLOAD_CUDALIBS; public: - static void gpu_variant(legate::TaskContext& context) + static void gpu_variant(legate::TaskContext context) { const auto proc = legate::Processor::get_executing_processor(); auto& lib = get_cuda_libraries(proc); diff --git a/src/cunumeric/fft/fft.cu b/src/cunumeric/fft/fft.cu index c7dbd0778d..4d679a59cf 100644 --- a/src/cunumeric/fft/fft.cu +++ b/src/cunumeric/fft/fft.cu @@ -360,7 +360,7 @@ struct FFTImplBody { } }; -/*static*/ void FFTTask::gpu_variant(TaskContext& context) +/*static*/ void FFTTask::gpu_variant(TaskContext context) { fft_template(context); }; diff --git a/src/cunumeric/fft/fft.h b/src/cunumeric/fft/fft.h index 607a6f8cd6..0e32dcb68f 100644 --- a/src/cunumeric/fft/fft.h +++ b/src/cunumeric/fft/fft.h @@ -36,7 +36,7 @@ class FFTTask : public CuNumericTask { public: #ifdef LEGATE_USE_CUDA - static void gpu_variant(legate::TaskContext& context); + static void gpu_variant(legate::TaskContext context); #endif }; diff --git a/src/cunumeric/fft/fft_template.inl b/src/cunumeric/fft/fft_template.inl index 7baae2d87c..430d0b1acc 100644 --- a/src/cunumeric/fft/fft_template.inl +++ b/src/cunumeric/fft/fft_template.inl @@ -80,8 +80,8 @@ static void fft_template(TaskContext& context) { FFTArgs args; - auto& inputs = context.inputs(); - auto& outputs = context.outputs(); + auto inputs = context.inputs(); + auto outputs = context.outputs(); auto& scalars = context.scalars(); args.output = std::move(outputs[0]); diff --git a/src/cunumeric/index/advanced_indexing.cc b/src/cunumeric/index/advanced_indexing.cc index b99c447f00..4cf96196a9 100644 --- a/src/cunumeric/index/advanced_indexing.cc +++ b/src/cunumeric/index/advanced_indexing.cc @@ -92,7 +92,7 @@ struct AdvancedIndexingImplBody { } }; -/*static*/ void AdvancedIndexingTask::cpu_variant(TaskContext& context) +/*static*/ void AdvancedIndexingTask::cpu_variant(TaskContext context) { advanced_indexing_template(context); } diff --git a/src/cunumeric/index/advanced_indexing.cu b/src/cunumeric/index/advanced_indexing.cu index 3201f892b0..7edb49bd9d 100644 --- a/src/cunumeric/index/advanced_indexing.cu +++ b/src/cunumeric/index/advanced_indexing.cu @@ -151,7 +151,7 @@ struct AdvancedIndexingImplBody { } }; -/*static*/ void AdvancedIndexingTask::gpu_variant(TaskContext& context) +/*static*/ void AdvancedIndexingTask::gpu_variant(TaskContext context) { advanced_indexing_template(context); } diff --git a/src/cunumeric/index/advanced_indexing.h b/src/cunumeric/index/advanced_indexing.h index 09319ad3bc..68f37152fc 100644 --- a/src/cunumeric/index/advanced_indexing.h +++ b/src/cunumeric/index/advanced_indexing.h @@ -21,9 +21,9 @@ namespace cunumeric { struct AdvancedIndexingArgs { - legate::Store& output; - const legate::Store& input_array; - const legate::Store& indexing_array; + legate::Store output; + legate::Store input_array; + legate::Store indexing_array; const bool is_set; const int64_t key_dim; }; @@ -33,12 +33,12 @@ class AdvancedIndexingTask : public CuNumericTask { static const int TASK_ID = CUNUMERIC_ADVANCED_INDEXING; public: - static void cpu_variant(legate::TaskContext& context); + static void cpu_variant(legate::TaskContext context); #ifdef LEGATE_USE_OPENMP - static void omp_variant(legate::TaskContext& context); + static void omp_variant(legate::TaskContext context); #endif #ifdef LEGATE_USE_CUDA - static void gpu_variant(legate::TaskContext& context); + static void gpu_variant(legate::TaskContext context); #endif }; diff --git a/src/cunumeric/index/advanced_indexing_omp.cc b/src/cunumeric/index/advanced_indexing_omp.cc index a5a815029e..767bb7f322 100644 --- a/src/cunumeric/index/advanced_indexing_omp.cc +++ b/src/cunumeric/index/advanced_indexing_omp.cc @@ -112,7 +112,7 @@ struct AdvancedIndexingImplBody { } }; -/*static*/ void AdvancedIndexingTask::omp_variant(TaskContext& context) +/*static*/ void AdvancedIndexingTask::omp_variant(TaskContext context) { advanced_indexing_template(context); } diff --git a/src/cunumeric/index/advanced_indexing_template.inl b/src/cunumeric/index/advanced_indexing_template.inl index fb160adfff..22ee033e24 100644 --- a/src/cunumeric/index/advanced_indexing_template.inl +++ b/src/cunumeric/index/advanced_indexing_template.inl @@ -68,10 +68,9 @@ template static void advanced_indexing_template(TaskContext& context) { // is_set flag is used to fill Point field for in-place assignment operation - bool is_set = context.scalars()[0].value(); - int64_t key_dim = context.scalars()[1].value(); - AdvancedIndexingArgs args{ - context.outputs()[0], context.inputs()[0], context.inputs()[1], is_set, key_dim}; + bool is_set = context.scalar(0).value(); + int64_t key_dim = context.scalar(1).value(); + AdvancedIndexingArgs args{context.output(0), context.input(0), context.input(1), is_set, key_dim}; double_dispatch( args.input_array.dim(), args.input_array.code(), AdvancedIndexingImpl{}, args); } diff --git a/src/cunumeric/index/choose.cc b/src/cunumeric/index/choose.cc index ed4b0a0cf7..47d38a156d 100644 --- a/src/cunumeric/index/choose.cc +++ b/src/cunumeric/index/choose.cc @@ -55,7 +55,7 @@ struct ChooseImplBody { } }; -/*static*/ void ChooseTask::cpu_variant(TaskContext& context) +/*static*/ void ChooseTask::cpu_variant(TaskContext context) { choose_template(context); } diff --git a/src/cunumeric/index/choose.cu b/src/cunumeric/index/choose.cu index 5deab68bbe..9920007ff6 100644 --- a/src/cunumeric/index/choose.cu +++ b/src/cunumeric/index/choose.cu @@ -78,7 +78,7 @@ struct ChooseImplBody { } }; -/*static*/ void ChooseTask::gpu_variant(TaskContext& context) +/*static*/ void ChooseTask::gpu_variant(TaskContext context) { choose_template(context); } diff --git a/src/cunumeric/index/choose.h b/src/cunumeric/index/choose.h index c2aa9259ab..5b7d788dfd 100644 --- a/src/cunumeric/index/choose.h +++ b/src/cunumeric/index/choose.h @@ -21,8 +21,8 @@ namespace cunumeric { struct ChooseArgs { - const legate::Store& out; - const std::vector& inputs; + legate::Store out; + std::vector inputs; }; class ChooseTask : public CuNumericTask { @@ -30,12 +30,12 @@ class ChooseTask : public CuNumericTask { static const int TASK_ID = CUNUMERIC_CHOOSE; public: - static void cpu_variant(legate::TaskContext& context); + static void cpu_variant(legate::TaskContext context); #ifdef LEGATE_USE_OPENMP - static void omp_variant(legate::TaskContext& context); + static void omp_variant(legate::TaskContext context); #endif #ifdef LEGATE_USE_CUDA - static void gpu_variant(legate::TaskContext& context); + static void gpu_variant(legate::TaskContext context); #endif }; diff --git a/src/cunumeric/index/choose_omp.cc b/src/cunumeric/index/choose_omp.cc index 19bf12ee22..be23f467be 100644 --- a/src/cunumeric/index/choose_omp.cc +++ b/src/cunumeric/index/choose_omp.cc @@ -54,7 +54,7 @@ struct ChooseImplBody { } }; -/*static*/ void ChooseTask::omp_variant(TaskContext& context) +/*static*/ void ChooseTask::omp_variant(TaskContext context) { choose_template(context); } diff --git a/src/cunumeric/index/choose_template.inl b/src/cunumeric/index/choose_template.inl index 9399f736a0..e9bb8c95e3 100644 --- a/src/cunumeric/index/choose_template.inl +++ b/src/cunumeric/index/choose_template.inl @@ -64,7 +64,9 @@ struct ChooseImpl { template static void choose_template(TaskContext& context) { - ChooseArgs args{context.outputs()[0], context.inputs()}; + std::vector inputs; + for (auto& input : context.inputs()) { inputs.emplace_back(input); } + ChooseArgs args{context.output(0), std::move(inputs)}; double_dispatch(args.inputs[0].dim(), args.inputs[0].code(), ChooseImpl{}, args); } diff --git a/src/cunumeric/index/putmask.cc b/src/cunumeric/index/putmask.cc index 595329f136..0aeba2c2ff 100644 --- a/src/cunumeric/index/putmask.cc +++ b/src/cunumeric/index/putmask.cc @@ -19,7 +19,7 @@ namespace cunumeric { -/*static*/ void PutmaskTask::cpu_variant(TaskContext& context) +/*static*/ void PutmaskTask::cpu_variant(TaskContext context) { putmask_template(context); } diff --git a/src/cunumeric/index/putmask.cu b/src/cunumeric/index/putmask.cu index abe94d82fd..ebb0b60cbc 100644 --- a/src/cunumeric/index/putmask.cu +++ b/src/cunumeric/index/putmask.cu @@ -20,7 +20,7 @@ namespace cunumeric { -/*static*/ void PutmaskTask::gpu_variant(TaskContext& context) +/*static*/ void PutmaskTask::gpu_variant(TaskContext context) { putmask_template(context); } diff --git a/src/cunumeric/index/putmask.h b/src/cunumeric/index/putmask.h index 6a9c1a0164..01e51fc8ac 100644 --- a/src/cunumeric/index/putmask.h +++ b/src/cunumeric/index/putmask.h @@ -21,9 +21,9 @@ namespace cunumeric { struct PutmaskArgs { - const Array& input; - const Array& mask; - const Array& values; + legate::Store input; + legate::Store mask; + legate::Store values; }; class PutmaskTask : public CuNumericTask { @@ -31,12 +31,12 @@ class PutmaskTask : public CuNumericTask { static const int TASK_ID = CUNUMERIC_PUTMASK; public: - static void cpu_variant(legate::TaskContext& context); + static void cpu_variant(legate::TaskContext context); #ifdef LEGATE_USE_OPENMP - static void omp_variant(legate::TaskContext& context); + static void omp_variant(legate::TaskContext context); #endif #ifdef LEGATE_USE_CUDA - static void gpu_variant(legate::TaskContext& context); + static void gpu_variant(legate::TaskContext context); #endif }; diff --git a/src/cunumeric/index/putmask_omp.cc b/src/cunumeric/index/putmask_omp.cc index 8550b41cd9..a4ebf9645b 100644 --- a/src/cunumeric/index/putmask_omp.cc +++ b/src/cunumeric/index/putmask_omp.cc @@ -20,7 +20,7 @@ namespace cunumeric { -/*static*/ void PutmaskTask::omp_variant(TaskContext& context) +/*static*/ void PutmaskTask::omp_variant(TaskContext context) { putmask_template(context); } diff --git a/src/cunumeric/index/putmask_template.inl b/src/cunumeric/index/putmask_template.inl index 60ce6af0e4..fbff142c86 100644 --- a/src/cunumeric/index/putmask_template.inl +++ b/src/cunumeric/index/putmask_template.inl @@ -103,8 +103,8 @@ struct PutmaskImpl { template static void putmask_template(TaskContext& context) { - auto& inputs = context.inputs(); - PutmaskArgs args{context.outputs()[0], inputs[1], inputs[2]}; + auto inputs = context.inputs(); + PutmaskArgs args{context.output(0), inputs[1], inputs[2]}; int dim = std::max(1, args.input.dim()); double_dispatch(dim, args.input.code(), PutmaskImpl{}, args); } diff --git a/src/cunumeric/index/repeat.cc b/src/cunumeric/index/repeat.cc index 535ce73c42..7abb0a7791 100644 --- a/src/cunumeric/index/repeat.cc +++ b/src/cunumeric/index/repeat.cc @@ -115,7 +115,7 @@ struct RepeatImplBody { } }; -/*static*/ void RepeatTask::cpu_variant(TaskContext& context) +/*static*/ void RepeatTask::cpu_variant(TaskContext context) { repeat_template(context); } diff --git a/src/cunumeric/index/repeat.cu b/src/cunumeric/index/repeat.cu index 5a4fa895ee..6368947450 100644 --- a/src/cunumeric/index/repeat.cu +++ b/src/cunumeric/index/repeat.cu @@ -164,7 +164,7 @@ struct RepeatImplBody { } }; -/*static*/ void RepeatTask::gpu_variant(TaskContext& context) +/*static*/ void RepeatTask::gpu_variant(TaskContext context) { repeat_template(context); } diff --git a/src/cunumeric/index/repeat.h b/src/cunumeric/index/repeat.h index 65633d1244..697ffe12cc 100644 --- a/src/cunumeric/index/repeat.h +++ b/src/cunumeric/index/repeat.h @@ -21,9 +21,9 @@ namespace cunumeric { struct RepeatArgs { - legate::Store& output; - const legate::Store& input; - const legate::Store& repeats_arr; + legate::Store output; + legate::Store input; + legate::Store repeats_arr; int64_t repeats; int32_t axis; const bool scalar_repeats; @@ -34,12 +34,12 @@ class RepeatTask : public CuNumericTask { static const int TASK_ID = CUNUMERIC_REPEAT; public: - static void cpu_variant(legate::TaskContext& context); + static void cpu_variant(legate::TaskContext context); #ifdef LEGATE_USE_OPENMP - static void omp_variant(legate::TaskContext& context); + static void omp_variant(legate::TaskContext context); #endif #ifdef LEGATE_USE_CUDA - static void gpu_variant(legate::TaskContext& context); + static void gpu_variant(legate::TaskContext context); #endif }; diff --git a/src/cunumeric/index/repeat_omp.cc b/src/cunumeric/index/repeat_omp.cc index 9561ebcdcc..424a632aa9 100644 --- a/src/cunumeric/index/repeat_omp.cc +++ b/src/cunumeric/index/repeat_omp.cc @@ -114,7 +114,7 @@ struct RepeatImplBody { } }; -/*static*/ void RepeatTask::omp_variant(TaskContext& context) +/*static*/ void RepeatTask::omp_variant(TaskContext context) { repeat_template(context); } diff --git a/src/cunumeric/index/repeat_template.inl b/src/cunumeric/index/repeat_template.inl index a02ca2074e..9e0980ec1b 100644 --- a/src/cunumeric/index/repeat_template.inl +++ b/src/cunumeric/index/repeat_template.inl @@ -54,16 +54,16 @@ struct RepeatImpl { template static void repeat_template(TaskContext& context) { - bool scalar_repeats = context.scalars()[1].value(); - auto axis = context.scalars()[0].value(); + bool scalar_repeats = context.scalar(1).value(); + auto axis = context.scalar(0).value(); if (scalar_repeats) { - auto repeats = context.scalars()[2].value(); + auto repeats = context.scalar(2).value(); RepeatArgs args{ - context.outputs()[0], context.inputs()[0], legate::Store(), repeats, axis, scalar_repeats}; + context.output(0), context.input(0), legate::Store(), repeats, axis, scalar_repeats}; double_dispatch(args.input.dim(), args.input.code(), RepeatImpl{}, args); } else { - auto& repeats = context.inputs()[1]; - RepeatArgs args{context.outputs()[0], context.inputs()[0], repeats, 0, axis, scalar_repeats}; + auto repeats = context.input(1); + RepeatArgs args{context.output(0), context.input(0), repeats, 0, axis, scalar_repeats}; double_dispatch(args.input.dim(), args.input.code(), RepeatImpl{}, args); } } diff --git a/src/cunumeric/index/wrap.cc b/src/cunumeric/index/wrap.cc index 83651ac44c..ca46ec6e20 100644 --- a/src/cunumeric/index/wrap.cc +++ b/src/cunumeric/index/wrap.cc @@ -55,7 +55,7 @@ struct WrapImplBody { } }; -/*static*/ void WrapTask::cpu_variant(TaskContext& context) +/*static*/ void WrapTask::cpu_variant(TaskContext context) { wrap_template(context); } diff --git a/src/cunumeric/index/wrap.cu b/src/cunumeric/index/wrap.cu index 701ca33008..71998e0fc7 100644 --- a/src/cunumeric/index/wrap.cu +++ b/src/cunumeric/index/wrap.cu @@ -152,7 +152,7 @@ struct WrapImplBody { } }; -/*static*/ void WrapTask::gpu_variant(TaskContext& context) +/*static*/ void WrapTask::gpu_variant(TaskContext context) { wrap_template(context); } diff --git a/src/cunumeric/index/wrap.h b/src/cunumeric/index/wrap.h index 7d81e0cf79..64e372a694 100644 --- a/src/cunumeric/index/wrap.h +++ b/src/cunumeric/index/wrap.h @@ -21,13 +21,13 @@ namespace cunumeric { struct WrapArgs { - const Array& out; // Array with Point type that is used to + legate::Store out; // Array with Point type that is used to // copy information from original array to the // `wrapped` one const legate::DomainPoint shape; // shape of the original array const bool has_input; const bool check_bounds; - const Array& in = Array(); + legate::Store in = legate::Store(); }; class WrapTask : public CuNumericTask { @@ -35,12 +35,12 @@ class WrapTask : public CuNumericTask { static const int TASK_ID = CUNUMERIC_WRAP; public: - static void cpu_variant(legate::TaskContext& context); + static void cpu_variant(legate::TaskContext context); #ifdef LEGATE_USE_OPENMP - static void omp_variant(legate::TaskContext& context); + static void omp_variant(legate::TaskContext context); #endif #ifdef LEGATE_USE_CUDA - static void gpu_variant(legate::TaskContext& context); + static void gpu_variant(legate::TaskContext context); #endif }; diff --git a/src/cunumeric/index/wrap_omp.cc b/src/cunumeric/index/wrap_omp.cc index ae97d67b4d..b69d4ae5cb 100644 --- a/src/cunumeric/index/wrap_omp.cc +++ b/src/cunumeric/index/wrap_omp.cc @@ -62,7 +62,7 @@ struct WrapImplBody { } }; -/*static*/ void WrapTask::omp_variant(TaskContext& context) +/*static*/ void WrapTask::omp_variant(TaskContext context) { wrap_template(context); } diff --git a/src/cunumeric/index/wrap_template.inl b/src/cunumeric/index/wrap_template.inl index 339bd37982..b65d0d611d 100644 --- a/src/cunumeric/index/wrap_template.inl +++ b/src/cunumeric/index/wrap_template.inl @@ -79,16 +79,13 @@ struct WrapImpl { template static void wrap_template(TaskContext& context) { - auto shape = context.scalars()[0].value(); + auto shape = context.scalar(0).value(); int dim = shape.dim; - bool has_input = context.scalars()[1].value(); - bool check_bounds = context.scalars()[2].value(); - Array tmp_array = Array(); - WrapArgs args{context.outputs()[0], - shape, - has_input, - check_bounds, - has_input ? context.inputs()[0] : tmp_array}; + bool has_input = context.scalar(1).value(); + bool check_bounds = context.scalar(2).value(); + legate::Store tmp_array{}; + WrapArgs args{ + context.output(0), shape, has_input, check_bounds, has_input ? context.input(0) : tmp_array}; dim_dispatch(dim, WrapImpl{}, args); } diff --git a/src/cunumeric/index/zip.cc b/src/cunumeric/index/zip.cc index 9bc68b6c4a..b00209cd6e 100644 --- a/src/cunumeric/index/zip.cc +++ b/src/cunumeric/index/zip.cc @@ -80,7 +80,7 @@ struct ZipImplBody { } }; -/*static*/ void ZipTask::cpu_variant(TaskContext& context) +/*static*/ void ZipTask::cpu_variant(TaskContext context) { zip_template(context); } diff --git a/src/cunumeric/index/zip.cu b/src/cunumeric/index/zip.cu index 0bb88be1c9..c66c59f4fe 100644 --- a/src/cunumeric/index/zip.cu +++ b/src/cunumeric/index/zip.cu @@ -186,7 +186,7 @@ struct ZipImplBody { } }; -/*static*/ void ZipTask::gpu_variant(TaskContext& context) +/*static*/ void ZipTask::gpu_variant(TaskContext context) { zip_template(context); } diff --git a/src/cunumeric/index/zip.h b/src/cunumeric/index/zip.h index 52b212ba68..e9be934b55 100644 --- a/src/cunumeric/index/zip.h +++ b/src/cunumeric/index/zip.h @@ -21,8 +21,8 @@ namespace cunumeric { struct ZipArgs { - const legate::Store& out; - const std::vector& inputs; + legate::Store out; + std::vector inputs; const int64_t N; const int64_t key_dim; const int64_t start_index; @@ -34,12 +34,12 @@ class ZipTask : public CuNumericTask { static const int TASK_ID = CUNUMERIC_ZIP; public: - static void cpu_variant(legate::TaskContext& context); + static void cpu_variant(legate::TaskContext context); #ifdef LEGATE_USE_OPENMP - static void omp_variant(legate::TaskContext& context); + static void omp_variant(legate::TaskContext context); #endif #ifdef LEGATE_USE_CUDA - static void gpu_variant(legate::TaskContext& context); + static void gpu_variant(legate::TaskContext context); #endif }; diff --git a/src/cunumeric/index/zip_omp.cc b/src/cunumeric/index/zip_omp.cc index ff7f71d333..643a202ed0 100644 --- a/src/cunumeric/index/zip_omp.cc +++ b/src/cunumeric/index/zip_omp.cc @@ -90,7 +90,7 @@ struct ZipImplBody { } }; -/*static*/ void ZipTask::omp_variant(TaskContext& context) +/*static*/ void ZipTask::omp_variant(TaskContext context) { zip_template(context); } diff --git a/src/cunumeric/index/zip_template.inl b/src/cunumeric/index/zip_template.inl index 63b85d5d24..b160e612be 100644 --- a/src/cunumeric/index/zip_template.inl +++ b/src/cunumeric/index/zip_template.inl @@ -89,11 +89,13 @@ static void zip_template(TaskContext& context) // key_dim = 3 // start_index = 1 - int64_t N = context.scalars()[0].value(); - int64_t key_dim = context.scalars()[1].value(); - int64_t start_index = context.scalars()[2].value(); - auto shape = context.scalars()[3].value(); - ZipArgs args{context.outputs()[0], context.inputs(), N, key_dim, start_index, shape}; + int64_t N = context.scalar(0).value(); + int64_t key_dim = context.scalar(1).value(); + int64_t start_index = context.scalar(2).value(); + auto shape = context.scalar(3).value(); + std::vector inputs; + for (auto& input : context.inputs()) { inputs.emplace_back(input); } + ZipArgs args{context.output(0), std::move(inputs), N, key_dim, start_index, shape}; int dim = args.inputs[0].dim(); // if scalar passed as an input, convert it to the array size 1 if (dim == 0) { dim = 1; } diff --git a/src/cunumeric/item/read.cc b/src/cunumeric/item/read.cc index cf431b69cd..1875da52f1 100644 --- a/src/cunumeric/item/read.cc +++ b/src/cunumeric/item/read.cc @@ -26,7 +26,7 @@ struct ReadImplBody { void operator()(AccessorWO out, AccessorRO in) const { out[0] = in[0]; } }; -/*static*/ void ReadTask::cpu_variant(TaskContext& context) +/*static*/ void ReadTask::cpu_variant(TaskContext context) { read_template(context); } diff --git a/src/cunumeric/item/read.cu b/src/cunumeric/item/read.cu index e8806aa1a0..f4d677d674 100644 --- a/src/cunumeric/item/read.cu +++ b/src/cunumeric/item/read.cu @@ -37,7 +37,7 @@ struct ReadImplBody { } }; -/*static*/ void ReadTask::gpu_variant(TaskContext& context) +/*static*/ void ReadTask::gpu_variant(TaskContext context) { read_template(context); } diff --git a/src/cunumeric/item/read.h b/src/cunumeric/item/read.h index 2d52a1456d..5b748972ac 100644 --- a/src/cunumeric/item/read.h +++ b/src/cunumeric/item/read.h @@ -25,12 +25,12 @@ class ReadTask : public CuNumericTask { static const int TASK_ID = CUNUMERIC_READ; public: - static void cpu_variant(legate::TaskContext& context); + static void cpu_variant(legate::TaskContext context); #ifdef LEGATE_USE_OPENMP - static void omp_variant(legate::TaskContext& context) { ReadTask::cpu_variant(context); } + static void omp_variant(legate::TaskContext context) { ReadTask::cpu_variant(context); } #endif #ifdef LEGATE_USE_CUDA - static void gpu_variant(legate::TaskContext& context); + static void gpu_variant(legate::TaskContext context); #endif }; diff --git a/src/cunumeric/item/read_template.inl b/src/cunumeric/item/read_template.inl index b13a003459..64d1241be4 100644 --- a/src/cunumeric/item/read_template.inl +++ b/src/cunumeric/item/read_template.inl @@ -29,7 +29,7 @@ struct ReadImplBody; template struct ReadImpl { template - void operator()(const Array& out_arr, const Array& in_arr) const + void operator()(legate::Store out_arr, legate::Store in_arr) const { using VAL = legate_type_of; auto out = out_arr.write_accessor(); @@ -41,9 +41,9 @@ struct ReadImpl { template static void read_template(TaskContext& context) { - auto& out = context.outputs()[0]; - auto& in = context.inputs()[0]; - type_dispatch(in.code(), ReadImpl{}, out, in); + auto out = context.output(0); + auto in = context.input(0); + type_dispatch(in.type().code(), ReadImpl{}, out, in); } } // namespace cunumeric diff --git a/src/cunumeric/item/write.cc b/src/cunumeric/item/write.cc index 891291a284..02ba5a03b9 100644 --- a/src/cunumeric/item/write.cc +++ b/src/cunumeric/item/write.cc @@ -29,7 +29,7 @@ struct WriteImplBody { } }; -/*static*/ void WriteTask::cpu_variant(TaskContext& context) +/*static*/ void WriteTask::cpu_variant(TaskContext context) { write_template(context); } diff --git a/src/cunumeric/item/write.cu b/src/cunumeric/item/write.cu index 574ee36380..1a9e8e9981 100644 --- a/src/cunumeric/item/write.cu +++ b/src/cunumeric/item/write.cu @@ -37,7 +37,7 @@ struct WriteImplBody { } }; -/*static*/ void WriteTask::gpu_variant(TaskContext& context) +/*static*/ void WriteTask::gpu_variant(TaskContext context) { write_template(context); } diff --git a/src/cunumeric/item/write.h b/src/cunumeric/item/write.h index 3648ef5d75..d86050fabd 100644 --- a/src/cunumeric/item/write.h +++ b/src/cunumeric/item/write.h @@ -25,12 +25,12 @@ class WriteTask : public CuNumericTask { static const int TASK_ID = CUNUMERIC_WRITE; public: - static void cpu_variant(legate::TaskContext& context); + static void cpu_variant(legate::TaskContext context); #ifdef LEGATE_USE_OPENMP - static void omp_variant(legate::TaskContext& context) { WriteTask::cpu_variant(context); } + static void omp_variant(legate::TaskContext context) { WriteTask::cpu_variant(context); } #endif #ifdef LEGATE_USE_CUDA - static void gpu_variant(legate::TaskContext& context); + static void gpu_variant(legate::TaskContext context); #endif }; diff --git a/src/cunumeric/item/write_template.inl b/src/cunumeric/item/write_template.inl index 41b18e01c8..2a6c766fed 100644 --- a/src/cunumeric/item/write_template.inl +++ b/src/cunumeric/item/write_template.inl @@ -29,7 +29,7 @@ struct WriteImplBody; template struct WriteImpl { template - void operator()(Array& out_arr, Array& in_arr) const + void operator()(legate::Store out_arr, legate::Store in_arr) const { using VAL = legate_type_of; auto out = out_arr.write_accessor(); @@ -41,9 +41,9 @@ struct WriteImpl { template static void write_template(TaskContext& context) { - auto& in = context.inputs()[0]; - auto& out = context.outputs()[0]; - type_dispatch(out.code(), WriteImpl{}, out, in); + auto in = context.input(0); + auto out = context.output(0); + type_dispatch(out.type().code(), WriteImpl{}, out, in); } } // namespace cunumeric diff --git a/src/cunumeric/mapper.cc b/src/cunumeric/mapper.cc index 1315939568..f36289110d 100644 --- a/src/cunumeric/mapper.cc +++ b/src/cunumeric/mapper.cc @@ -79,19 +79,20 @@ std::vector CuNumericMapper::store_mappings( case CUNUMERIC_CONVOLVE: { std::vector mappings; auto inputs = task.inputs(); - mappings.push_back(StoreMapping::default_mapping(inputs[0], options.front())); - mappings.push_back(StoreMapping::default_mapping(inputs[1], options.front())); + mappings.push_back(StoreMapping::default_mapping(inputs[0].data(), options.front())); + mappings.push_back(StoreMapping::default_mapping(inputs[1].data(), options.front())); auto& input_mapping = mappings.back(); - for (uint32_t idx = 2; idx < inputs.size(); ++idx) input_mapping.add_store(inputs[idx]); + for (uint32_t idx = 2; idx < inputs.size(); ++idx) + input_mapping.add_store(inputs[idx].data()); return std::move(mappings); } case CUNUMERIC_FFT: { std::vector mappings; auto inputs = task.inputs(); auto outputs = task.outputs(); - mappings.push_back(StoreMapping::default_mapping(inputs[0], options.front())); + mappings.push_back(StoreMapping::default_mapping(inputs[0].data(), options.front())); mappings.push_back( - StoreMapping::default_mapping(outputs[0], options.front(), true /*exact*/)); + StoreMapping::default_mapping(outputs[0].data(), options.front(), true /*exact*/)); return std::move(mappings); } case CUNUMERIC_TRANSPOSE_COPY_2D: { @@ -100,7 +101,7 @@ std::vector CuNumericMapper::store_mappings( std::vector mappings; auto outputs = task.outputs(); mappings.push_back( - StoreMapping::default_mapping(outputs[0], options.front(), true /*exact*/)); + StoreMapping::default_mapping(outputs[0].data(), options.front(), true /*exact*/)); mappings.back().policy().ordering.set_fortran_order(); return std::move(mappings); } else @@ -115,11 +116,12 @@ std::vector CuNumericMapper::store_mappings( auto inputs = task.inputs(); auto reductions = task.reductions(); for (auto& input : inputs) { - mappings.push_back(StoreMapping::default_mapping(input, options.front(), true /*exact*/)); + mappings.push_back( + StoreMapping::default_mapping(input.data(), options.front(), true /*exact*/)); } for (auto& reduction : reductions) { mappings.push_back( - StoreMapping::default_mapping(reduction, options.front(), true /*exact*/)); + StoreMapping::default_mapping(reduction.data(), options.front(), true /*exact*/)); } return std::move(mappings); } @@ -132,11 +134,13 @@ std::vector CuNumericMapper::store_mappings( auto inputs = task.inputs(); auto outputs = task.outputs(); for (auto& input : inputs) { - mappings.push_back(StoreMapping::default_mapping(input, options.front(), true /*exact*/)); + mappings.push_back( + StoreMapping::default_mapping(input.data(), options.front(), true /*exact*/)); mappings.back().policy().ordering.set_fortran_order(); } for (auto& output : outputs) { - mappings.push_back(StoreMapping::default_mapping(output, options.front(), true /*exact*/)); + mappings.push_back( + StoreMapping::default_mapping(output.data(), options.front(), true /*exact*/)); mappings.back().policy().ordering.set_fortran_order(); } return std::move(mappings); @@ -147,14 +151,16 @@ std::vector CuNumericMapper::store_mappings( // So we will request fortran ordering std::vector mappings; auto input = task.input(0); - mappings.push_back(StoreMapping::default_mapping(input, options.front(), true /*exact*/)); + mappings.push_back( + StoreMapping::default_mapping(input.data(), options.front(), true /*exact*/)); mappings.back().policy().ordering.set_fortran_order(); return std::move(mappings); } case CUNUMERIC_SEARCHSORTED: { std::vector mappings; auto inputs = task.inputs(); - mappings.push_back(StoreMapping::default_mapping(inputs[0], options.front(), true /*exact*/)); + mappings.push_back( + StoreMapping::default_mapping(inputs[0].data(), options.front(), true /*exact*/)); return std::move(mappings); } case CUNUMERIC_SORT: { @@ -162,10 +168,12 @@ std::vector CuNumericMapper::store_mappings( auto inputs = task.inputs(); auto outputs = task.outputs(); for (auto& input : inputs) { - mappings.push_back(StoreMapping::default_mapping(input, options.front(), true /*exact*/)); + mappings.push_back( + StoreMapping::default_mapping(input.data(), options.front(), true /*exact*/)); } for (auto& output : outputs) { - mappings.push_back(StoreMapping::default_mapping(output, options.front(), true /*exact*/)); + mappings.push_back( + StoreMapping::default_mapping(output.data(), options.front(), true /*exact*/)); } return std::move(mappings); } @@ -174,10 +182,12 @@ std::vector CuNumericMapper::store_mappings( auto inputs = task.inputs(); auto outputs = task.outputs(); for (auto& input : inputs) { - mappings.push_back(StoreMapping::default_mapping(input, options.front(), true /*exact*/)); + mappings.push_back( + StoreMapping::default_mapping(input.data(), options.front(), true /*exact*/)); } for (auto& output : outputs) { - mappings.push_back(StoreMapping::default_mapping(output, options.front(), true /*exact*/)); + mappings.push_back( + StoreMapping::default_mapping(output.data(), options.front(), true /*exact*/)); } return std::move(mappings); } @@ -186,10 +196,12 @@ std::vector CuNumericMapper::store_mappings( auto inputs = task.inputs(); auto outputs = task.outputs(); for (auto& input : inputs) { - mappings.push_back(StoreMapping::default_mapping(input, options.front(), true /*exact*/)); + mappings.push_back( + StoreMapping::default_mapping(input.data(), options.front(), true /*exact*/)); } for (auto& output : outputs) { - mappings.push_back(StoreMapping::default_mapping(output, options.front(), true /*exact*/)); + mappings.push_back( + StoreMapping::default_mapping(output.data(), options.front(), true /*exact*/)); } return std::move(mappings); } @@ -198,10 +210,12 @@ std::vector CuNumericMapper::store_mappings( auto inputs = task.inputs(); auto outputs = task.outputs(); for (auto& input : inputs) { - mappings.push_back(StoreMapping::default_mapping(input, options.front(), true /*exact*/)); + mappings.push_back( + StoreMapping::default_mapping(input.data(), options.front(), true /*exact*/)); } for (auto& output : outputs) { - mappings.push_back(StoreMapping::default_mapping(output, options.front(), true /*exact*/)); + mappings.push_back( + StoreMapping::default_mapping(output.data(), options.front(), true /*exact*/)); } return std::move(mappings); } diff --git a/src/cunumeric/matrix/contract.cc b/src/cunumeric/matrix/contract.cc index eab84e70ed..1081c8b457 100644 --- a/src/cunumeric/matrix/contract.cc +++ b/src/cunumeric/matrix/contract.cc @@ -240,7 +240,7 @@ struct ContractImplBody { } }; -/*static*/ void ContractTask::cpu_variant(legate::TaskContext& context) +/*static*/ void ContractTask::cpu_variant(legate::TaskContext context) { contract_template(context); } diff --git a/src/cunumeric/matrix/contract.cu b/src/cunumeric/matrix/contract.cu index 3d41551065..8118ab9361 100644 --- a/src/cunumeric/matrix/contract.cu +++ b/src/cunumeric/matrix/contract.cu @@ -341,7 +341,7 @@ struct ContractImplBody { } }; -/*static*/ void ContractTask::gpu_variant(TaskContext& context) +/*static*/ void ContractTask::gpu_variant(TaskContext context) { contract_template(context); } diff --git a/src/cunumeric/matrix/contract.h b/src/cunumeric/matrix/contract.h index a2d72de0b3..195b5c857e 100644 --- a/src/cunumeric/matrix/contract.h +++ b/src/cunumeric/matrix/contract.h @@ -21,9 +21,9 @@ namespace cunumeric { struct ContractArgs { - const legate::Store& lhs; - const legate::Store& rhs1; - const legate::Store& rhs2; + legate::Store lhs; + legate::Store rhs1; + legate::Store rhs2; legate::Span lhs_dim_mask; legate::Span rhs1_dim_mask; legate::Span rhs2_dim_mask; @@ -34,12 +34,12 @@ class ContractTask : public CuNumericTask { static const int TASK_ID = CUNUMERIC_CONTRACT; public: - static void cpu_variant(legate::TaskContext& context); + static void cpu_variant(legate::TaskContext context); #ifdef LEGATE_USE_OPENMP - static void omp_variant(legate::TaskContext& context); + static void omp_variant(legate::TaskContext context); #endif #ifdef LEGATE_USE_CUDA - static void gpu_variant(legate::TaskContext& context); + static void gpu_variant(legate::TaskContext context); #endif }; diff --git a/src/cunumeric/matrix/contract_omp.cc b/src/cunumeric/matrix/contract_omp.cc index 698690cfee..13d83e169a 100644 --- a/src/cunumeric/matrix/contract_omp.cc +++ b/src/cunumeric/matrix/contract_omp.cc @@ -233,7 +233,7 @@ struct ContractImplBody { } }; -/*static*/ void ContractTask::omp_variant(legate::TaskContext& context) +/*static*/ void ContractTask::omp_variant(legate::TaskContext context) { std::stringstream ss; ss << omp_get_max_threads(); diff --git a/src/cunumeric/matrix/contract_template.inl b/src/cunumeric/matrix/contract_template.inl index a7fa69fa1a..feff5ffed8 100644 --- a/src/cunumeric/matrix/contract_template.inl +++ b/src/cunumeric/matrix/contract_template.inl @@ -203,9 +203,9 @@ struct ContractImpl { template static void contract_template(legate::TaskContext& context) { - auto& reductions = context.reductions(); - auto& inputs = context.inputs(); - auto& scalars = context.scalars(); + auto reductions = context.reductions(); + auto inputs = context.inputs(); + auto& scalars = context.scalars(); ContractArgs args{reductions[0], inputs[0], diff --git a/src/cunumeric/matrix/diag.cc b/src/cunumeric/matrix/diag.cc index 84140b1afb..da398e99d2 100644 --- a/src/cunumeric/matrix/diag.cc +++ b/src/cunumeric/matrix/diag.cc @@ -68,7 +68,7 @@ struct DiagImplBody { } }; -/*static*/ void DiagTask::cpu_variant(TaskContext& context) +/*static*/ void DiagTask::cpu_variant(TaskContext context) { diag_template(context); } diff --git a/src/cunumeric/matrix/diag.cu b/src/cunumeric/matrix/diag.cu index 17b6a25648..eabbb4cc06 100644 --- a/src/cunumeric/matrix/diag.cu +++ b/src/cunumeric/matrix/diag.cu @@ -108,7 +108,7 @@ struct DiagImplBody { } }; -/*static*/ void DiagTask::gpu_variant(TaskContext& context) +/*static*/ void DiagTask::gpu_variant(TaskContext context) { diag_template(context); } diff --git a/src/cunumeric/matrix/diag.h b/src/cunumeric/matrix/diag.h index 5c0296d4ec..6d13aa6b35 100644 --- a/src/cunumeric/matrix/diag.h +++ b/src/cunumeric/matrix/diag.h @@ -23,8 +23,8 @@ namespace cunumeric { struct DiagArgs { int naxes; bool extract; - const legate::Store& matrix; - const legate::Store& diag; + legate::Store matrix; + legate::Store diag; }; class DiagTask : public CuNumericTask { @@ -32,12 +32,12 @@ class DiagTask : public CuNumericTask { static const int TASK_ID = CUNUMERIC_DIAG; public: - static void cpu_variant(legate::TaskContext& context); + static void cpu_variant(legate::TaskContext context); #ifdef LEGATE_USE_OPENMP - static void omp_variant(legate::TaskContext& context); + static void omp_variant(legate::TaskContext context); #endif #ifdef LEGATE_USE_CUDA - static void gpu_variant(legate::TaskContext& context); + static void gpu_variant(legate::TaskContext context); #endif }; diff --git a/src/cunumeric/matrix/diag_omp.cc b/src/cunumeric/matrix/diag_omp.cc index 5d2224d0cc..14a4842f7b 100644 --- a/src/cunumeric/matrix/diag_omp.cc +++ b/src/cunumeric/matrix/diag_omp.cc @@ -75,7 +75,7 @@ struct DiagImplBody { } }; -/*static*/ void DiagTask::omp_variant(TaskContext& context) +/*static*/ void DiagTask::omp_variant(TaskContext context) { diag_template(context); } diff --git a/src/cunumeric/matrix/diag_template.inl b/src/cunumeric/matrix/diag_template.inl index bb1cb0896d..ea493d4353 100644 --- a/src/cunumeric/matrix/diag_template.inl +++ b/src/cunumeric/matrix/diag_template.inl @@ -101,10 +101,10 @@ struct DiagImpl { template static void diag_template(TaskContext& context) { - int naxes = context.scalars()[0].value(); - bool extract = context.scalars()[1].value(); - legate::Store& matrix = extract ? context.inputs()[0] : context.outputs()[0]; - legate::Store& diag = extract ? context.reductions()[0] : context.inputs()[0]; + int naxes = context.scalar(0).value(); + bool extract = context.scalar(1).value(); + legate::Store matrix = extract ? context.input(0) : context.output(0); + legate::Store diag = extract ? context.reduction(0) : context.input(0); DiagArgs args{naxes, extract, matrix, diag}; double_dispatch(matrix.dim(), matrix.code(), DiagImpl{}, args); } diff --git a/src/cunumeric/matrix/dot.cc b/src/cunumeric/matrix/dot.cc index 637ab6a4e0..18eaf3a87d 100644 --- a/src/cunumeric/matrix/dot.cc +++ b/src/cunumeric/matrix/dot.cc @@ -50,7 +50,7 @@ struct DotImplBody { } }; -/*static*/ void DotTask::cpu_variant(TaskContext& context) +/*static*/ void DotTask::cpu_variant(TaskContext context) { dot_template(context); } diff --git a/src/cunumeric/matrix/dot.cu b/src/cunumeric/matrix/dot.cu index f990479320..9f22d447eb 100644 --- a/src/cunumeric/matrix/dot.cu +++ b/src/cunumeric/matrix/dot.cu @@ -75,7 +75,7 @@ struct DotImplBody { } }; -/*static*/ void DotTask::gpu_variant(TaskContext& context) +/*static*/ void DotTask::gpu_variant(TaskContext context) { dot_template(context); } diff --git a/src/cunumeric/matrix/dot.h b/src/cunumeric/matrix/dot.h index 4a18ff072e..fe0981207a 100644 --- a/src/cunumeric/matrix/dot.h +++ b/src/cunumeric/matrix/dot.h @@ -21,9 +21,9 @@ namespace cunumeric { struct DotArgs { - const legate::Store& lhs; - const legate::Store& rhs1; - const legate::Store& rhs2; + legate::Store lhs; + legate::Store rhs1; + legate::Store rhs2; }; class DotTask : public CuNumericTask { @@ -31,12 +31,12 @@ class DotTask : public CuNumericTask { static const int TASK_ID = CUNUMERIC_DOT; public: - static void cpu_variant(legate::TaskContext& context); + static void cpu_variant(legate::TaskContext context); #ifdef LEGATE_USE_OPENMP - static void omp_variant(legate::TaskContext& context); + static void omp_variant(legate::TaskContext context); #endif #ifdef LEGATE_USE_CUDA - static void gpu_variant(legate::TaskContext& context); + static void gpu_variant(legate::TaskContext context); #endif }; diff --git a/src/cunumeric/matrix/dot_omp.cc b/src/cunumeric/matrix/dot_omp.cc index 857ab8f265..33d0235c66 100644 --- a/src/cunumeric/matrix/dot_omp.cc +++ b/src/cunumeric/matrix/dot_omp.cc @@ -69,7 +69,7 @@ struct DotImplBody { } }; -/*static*/ void DotTask::omp_variant(TaskContext& context) +/*static*/ void DotTask::omp_variant(TaskContext context) { dot_template(context); } diff --git a/src/cunumeric/matrix/dot_template.inl b/src/cunumeric/matrix/dot_template.inl index fae14df13a..973bc39812 100644 --- a/src/cunumeric/matrix/dot_template.inl +++ b/src/cunumeric/matrix/dot_template.inl @@ -72,8 +72,8 @@ struct DotImpl { template static void dot_template(TaskContext& context) { - auto& inputs = context.inputs(); - DotArgs args{context.reductions()[0], inputs[0], inputs[1]}; + auto inputs = context.inputs(); + DotArgs args{context.reduction(0), inputs[0], inputs[1]}; type_dispatch(args.rhs1.code(), DotImpl{}, args); } diff --git a/src/cunumeric/matrix/gemm.cc b/src/cunumeric/matrix/gemm.cc index 4160bb03eb..459ba263e6 100644 --- a/src/cunumeric/matrix/gemm.cc +++ b/src/cunumeric/matrix/gemm.cc @@ -97,7 +97,7 @@ struct GemmImplBody { } }; -/*static*/ void GemmTask::cpu_variant(TaskContext& context) +/*static*/ void GemmTask::cpu_variant(TaskContext context) { #ifdef LEGATE_USE_OPENMP openblas_set_num_threads(1); // make sure this isn't overzealous diff --git a/src/cunumeric/matrix/gemm.cu b/src/cunumeric/matrix/gemm.cu index 8fff167fff..b2fe26783a 100644 --- a/src/cunumeric/matrix/gemm.cu +++ b/src/cunumeric/matrix/gemm.cu @@ -112,7 +112,7 @@ struct GemmImplBody { } }; -/*static*/ void GemmTask::gpu_variant(TaskContext& context) +/*static*/ void GemmTask::gpu_variant(TaskContext context) { gemm_template(context); } diff --git a/src/cunumeric/matrix/gemm.h b/src/cunumeric/matrix/gemm.h index 5a460091c6..3aa47c2dc4 100644 --- a/src/cunumeric/matrix/gemm.h +++ b/src/cunumeric/matrix/gemm.h @@ -25,12 +25,12 @@ class GemmTask : public CuNumericTask { static const int TASK_ID = CUNUMERIC_GEMM; public: - static void cpu_variant(legate::TaskContext& context); + static void cpu_variant(legate::TaskContext context); #ifdef LEGATE_USE_OPENMP - static void omp_variant(legate::TaskContext& context); + static void omp_variant(legate::TaskContext context); #endif #ifdef LEGATE_USE_CUDA - static void gpu_variant(legate::TaskContext& context); + static void gpu_variant(legate::TaskContext context); #endif }; diff --git a/src/cunumeric/matrix/gemm_omp.cc b/src/cunumeric/matrix/gemm_omp.cc index 69b20c6733..226f69cc20 100644 --- a/src/cunumeric/matrix/gemm_omp.cc +++ b/src/cunumeric/matrix/gemm_omp.cc @@ -98,7 +98,7 @@ struct GemmImplBody { } }; -/*static*/ void GemmTask::omp_variant(TaskContext& context) +/*static*/ void GemmTask::omp_variant(TaskContext context) { openblas_set_num_threads(omp_get_max_threads()); gemm_template(context); diff --git a/src/cunumeric/matrix/gemm_template.inl b/src/cunumeric/matrix/gemm_template.inl index 09178be1b5..fbf6215f07 100644 --- a/src/cunumeric/matrix/gemm_template.inl +++ b/src/cunumeric/matrix/gemm_template.inl @@ -40,7 +40,7 @@ struct support_gemm : std::true_type {}; template struct GemmImpl { template ::value>* = nullptr> - void operator()(Array& lhs_array, Array& rhs1_array, Array& rhs2_array) const + void operator()(legate::Store lhs_array, legate::Store rhs1_array, legate::Store rhs2_array) const { using VAL = legate_type_of; @@ -68,7 +68,7 @@ struct GemmImpl { } template ::value>* = nullptr> - void operator()(Array& lhs_array, Array& rhs1_array, Array& rhs2_array) const + void operator()(legate::Store lhs_array, legate::Store rhs1_array, legate::Store rhs2_array) const { assert(false); } @@ -77,14 +77,14 @@ struct GemmImpl { template static void gemm_template(TaskContext& context) { - auto& inputs = context.inputs(); - auto& outputs = context.outputs(); + auto inputs = context.inputs(); + auto outputs = context.outputs(); auto& lhs = outputs[0]; auto& rhs1 = inputs[0]; auto& rhs2 = inputs[1]; - type_dispatch(lhs.code(), GemmImpl{}, lhs, rhs1, rhs2); + type_dispatch(lhs.type().code(), GemmImpl{}, lhs, rhs1, rhs2); } } // namespace cunumeric diff --git a/src/cunumeric/matrix/matmul.cc b/src/cunumeric/matrix/matmul.cc index 82e6aa651f..b29adbeb5a 100644 --- a/src/cunumeric/matrix/matmul.cc +++ b/src/cunumeric/matrix/matmul.cc @@ -27,7 +27,7 @@ namespace cunumeric { using namespace legate; -/*static*/ void MatMulTask::cpu_variant(TaskContext& context) +/*static*/ void MatMulTask::cpu_variant(TaskContext context) { #ifdef LEGATE_USE_OPENMP openblas_set_num_threads(1); // make sure this isn't overzealous diff --git a/src/cunumeric/matrix/matmul.cu b/src/cunumeric/matrix/matmul.cu index 934deb9659..8433ae1b87 100644 --- a/src/cunumeric/matrix/matmul.cu +++ b/src/cunumeric/matrix/matmul.cu @@ -250,7 +250,7 @@ struct MatMulImplBody { } }; -/*static*/ void MatMulTask::gpu_variant(TaskContext& context) +/*static*/ void MatMulTask::gpu_variant(TaskContext context) { matmul_template(context); } diff --git a/src/cunumeric/matrix/matmul.h b/src/cunumeric/matrix/matmul.h index bf7a5648e9..8f262396de 100644 --- a/src/cunumeric/matrix/matmul.h +++ b/src/cunumeric/matrix/matmul.h @@ -21,9 +21,9 @@ namespace cunumeric { struct MatMulArgs { - const legate::Store& lhs; - const legate::Store& rhs1; - const legate::Store& rhs2; + legate::Store lhs; + legate::Store rhs1; + legate::Store rhs2; }; class MatMulTask : public CuNumericTask { @@ -31,12 +31,12 @@ class MatMulTask : public CuNumericTask { static const int TASK_ID = CUNUMERIC_MATMUL; public: - static void cpu_variant(legate::TaskContext& context); + static void cpu_variant(legate::TaskContext context); #ifdef LEGATE_USE_OPENMP - static void omp_variant(legate::TaskContext& context); + static void omp_variant(legate::TaskContext context); #endif #ifdef LEGATE_USE_CUDA - static void gpu_variant(legate::TaskContext& context); + static void gpu_variant(legate::TaskContext context); #endif }; diff --git a/src/cunumeric/matrix/matmul_omp.cc b/src/cunumeric/matrix/matmul_omp.cc index 73406709ac..f4500c4be3 100644 --- a/src/cunumeric/matrix/matmul_omp.cc +++ b/src/cunumeric/matrix/matmul_omp.cc @@ -24,7 +24,7 @@ namespace cunumeric { using namespace legate; -/*static*/ void MatMulTask::omp_variant(TaskContext& context) +/*static*/ void MatMulTask::omp_variant(TaskContext context) { openblas_set_num_threads(omp_get_max_threads()); matmul_template(context); diff --git a/src/cunumeric/matrix/matmul_template.inl b/src/cunumeric/matrix/matmul_template.inl index 967860f535..8c72614aae 100644 --- a/src/cunumeric/matrix/matmul_template.inl +++ b/src/cunumeric/matrix/matmul_template.inl @@ -115,8 +115,8 @@ struct MatMulImpl { template static void matmul_template(TaskContext& context) { - auto& reductions = context.reductions(); - auto& inputs = context.inputs(); + auto reductions = context.reductions(); + auto inputs = context.inputs(); MatMulArgs args{reductions[0], inputs[0], inputs[1]}; // Note that we can't dispatch on the lhs's type, diff --git a/src/cunumeric/matrix/matvecmul.cc b/src/cunumeric/matrix/matvecmul.cc index 9a31b896f0..115d004420 100644 --- a/src/cunumeric/matrix/matvecmul.cc +++ b/src/cunumeric/matrix/matvecmul.cc @@ -27,7 +27,7 @@ namespace cunumeric { using namespace legate; -/*static*/ void MatVecMulTask::cpu_variant(TaskContext& context) +/*static*/ void MatVecMulTask::cpu_variant(TaskContext context) { #ifdef LEGATE_USE_OPENMP openblas_set_num_threads(1); // make sure this isn't overzealous diff --git a/src/cunumeric/matrix/matvecmul.cu b/src/cunumeric/matrix/matvecmul.cu index d54e28b488..b0e1992314 100644 --- a/src/cunumeric/matrix/matvecmul.cu +++ b/src/cunumeric/matrix/matvecmul.cu @@ -268,7 +268,7 @@ struct MatVecMulImplBody { } }; -/*static*/ void MatVecMulTask::gpu_variant(TaskContext& context) +/*static*/ void MatVecMulTask::gpu_variant(TaskContext context) { matvecmul_template(context); } diff --git a/src/cunumeric/matrix/matvecmul.h b/src/cunumeric/matrix/matvecmul.h index 42a0e50b9f..09fdd3aa55 100644 --- a/src/cunumeric/matrix/matvecmul.h +++ b/src/cunumeric/matrix/matvecmul.h @@ -21,9 +21,9 @@ namespace cunumeric { struct MatVecMulArgs { - const legate::Store& lhs; - const legate::Store& rhs1; - const legate::Store& rhs2; + legate::Store lhs; + legate::Store rhs1; + legate::Store rhs2; }; class MatVecMulTask : public CuNumericTask { @@ -31,12 +31,12 @@ class MatVecMulTask : public CuNumericTask { static const int TASK_ID = CUNUMERIC_MATVECMUL; public: - static void cpu_variant(legate::TaskContext& context); + static void cpu_variant(legate::TaskContext context); #ifdef LEGATE_USE_OPENMP - static void omp_variant(legate::TaskContext& context); + static void omp_variant(legate::TaskContext context); #endif #ifdef LEGATE_USE_CUDA - static void gpu_variant(legate::TaskContext& context); + static void gpu_variant(legate::TaskContext context); #endif }; diff --git a/src/cunumeric/matrix/matvecmul_omp.cc b/src/cunumeric/matrix/matvecmul_omp.cc index 454a14e7e9..8f8d1faa7a 100644 --- a/src/cunumeric/matrix/matvecmul_omp.cc +++ b/src/cunumeric/matrix/matvecmul_omp.cc @@ -25,7 +25,7 @@ namespace cunumeric { using namespace legate; -/*static*/ void MatVecMulTask::omp_variant(TaskContext& context) +/*static*/ void MatVecMulTask::omp_variant(TaskContext context) { openblas_set_num_threads(omp_get_max_threads()); matvecmul_template(context); diff --git a/src/cunumeric/matrix/matvecmul_template.inl b/src/cunumeric/matrix/matvecmul_template.inl index 547d376d10..e6868d2aec 100644 --- a/src/cunumeric/matrix/matvecmul_template.inl +++ b/src/cunumeric/matrix/matvecmul_template.inl @@ -96,8 +96,8 @@ struct MatVecMulImpl { template static void matvecmul_template(TaskContext& context) { - auto& reductions = context.reductions(); - auto& inputs = context.inputs(); + auto reductions = context.reductions(); + auto inputs = context.inputs(); MatVecMulArgs args{reductions[0], inputs[0], inputs[1]}; // Note that we can't dispatch on the lhs's type, diff --git a/src/cunumeric/matrix/potrf.cc b/src/cunumeric/matrix/potrf.cc index 02ae062461..ac59afb3df 100644 --- a/src/cunumeric/matrix/potrf.cc +++ b/src/cunumeric/matrix/potrf.cc @@ -68,7 +68,7 @@ struct PotrfImplBody { } }; -/*static*/ void PotrfTask::cpu_variant(TaskContext& context) +/*static*/ void PotrfTask::cpu_variant(TaskContext context) { #ifdef LEGATE_USE_OPENMP openblas_set_num_threads(1); // make sure this isn't overzealous diff --git a/src/cunumeric/matrix/potrf.cu b/src/cunumeric/matrix/potrf.cu index 68616525f5..9d027f0e80 100644 --- a/src/cunumeric/matrix/potrf.cu +++ b/src/cunumeric/matrix/potrf.cu @@ -85,7 +85,7 @@ struct PotrfImplBody { } }; -/*static*/ void PotrfTask::gpu_variant(TaskContext& context) +/*static*/ void PotrfTask::gpu_variant(TaskContext context) { potrf_template(context); } diff --git a/src/cunumeric/matrix/potrf.h b/src/cunumeric/matrix/potrf.h index c12108a529..b7abf930a5 100644 --- a/src/cunumeric/matrix/potrf.h +++ b/src/cunumeric/matrix/potrf.h @@ -25,12 +25,12 @@ class PotrfTask : public CuNumericTask { static const int TASK_ID = CUNUMERIC_POTRF; public: - static void cpu_variant(legate::TaskContext& context); + static void cpu_variant(legate::TaskContext context); #ifdef LEGATE_USE_OPENMP - static void omp_variant(legate::TaskContext& context); + static void omp_variant(legate::TaskContext context); #endif #ifdef LEGATE_USE_CUDA - static void gpu_variant(legate::TaskContext& context); + static void gpu_variant(legate::TaskContext context); #endif }; diff --git a/src/cunumeric/matrix/potrf_omp.cc b/src/cunumeric/matrix/potrf_omp.cc index d26143a6f2..b495baf9e8 100644 --- a/src/cunumeric/matrix/potrf_omp.cc +++ b/src/cunumeric/matrix/potrf_omp.cc @@ -69,7 +69,7 @@ struct PotrfImplBody { } }; -/*static*/ void PotrfTask::omp_variant(TaskContext& context) +/*static*/ void PotrfTask::omp_variant(TaskContext context) { openblas_set_num_threads(omp_get_max_threads()); potrf_template(context); diff --git a/src/cunumeric/matrix/potrf_template.inl b/src/cunumeric/matrix/potrf_template.inl index 55c782ad05..b4280d824d 100644 --- a/src/cunumeric/matrix/potrf_template.inl +++ b/src/cunumeric/matrix/potrf_template.inl @@ -40,7 +40,7 @@ struct support_potrf : std::true_type {}; template struct PotrfImpl { template ::value>* = nullptr> - void operator()(Array& array) const + void operator()(legate::Store array) const { using VAL = legate_type_of; @@ -59,7 +59,7 @@ struct PotrfImpl { } template ::value>* = nullptr> - void operator()(Array& array) const + void operator()(legate::Store array) const { assert(false); } @@ -68,8 +68,8 @@ struct PotrfImpl { template static void potrf_template(TaskContext& context) { - auto& array = context.outputs()[0]; - type_dispatch(array.code(), PotrfImpl{}, array); + auto array = context.output(0); + type_dispatch(array.type().code(), PotrfImpl{}, array); } } // namespace cunumeric diff --git a/src/cunumeric/matrix/solve.cc b/src/cunumeric/matrix/solve.cc index 7949c7f440..53c4d5f56c 100644 --- a/src/cunumeric/matrix/solve.cc +++ b/src/cunumeric/matrix/solve.cc @@ -24,7 +24,7 @@ using namespace legate; /*static*/ const char* SolveTask::ERROR_MESSAGE = "Singular matrix"; -/*static*/ void SolveTask::cpu_variant(TaskContext& context) +/*static*/ void SolveTask::cpu_variant(TaskContext context) { #ifdef LEGATE_USE_OPENMP openblas_set_num_threads(1); // make sure this isn't overzealous diff --git a/src/cunumeric/matrix/solve.cu b/src/cunumeric/matrix/solve.cu index 3f3262b15f..576c91cef1 100644 --- a/src/cunumeric/matrix/solve.cu +++ b/src/cunumeric/matrix/solve.cu @@ -108,7 +108,7 @@ struct SolveImplBody { } }; -/*static*/ void SolveTask::gpu_variant(TaskContext& context) +/*static*/ void SolveTask::gpu_variant(TaskContext context) { solve_template(context); } diff --git a/src/cunumeric/matrix/solve.h b/src/cunumeric/matrix/solve.h index f41d98015a..58d3237177 100644 --- a/src/cunumeric/matrix/solve.h +++ b/src/cunumeric/matrix/solve.h @@ -26,12 +26,12 @@ class SolveTask : public CuNumericTask { static const char* ERROR_MESSAGE; public: - static void cpu_variant(legate::TaskContext& context); + static void cpu_variant(legate::TaskContext context); #ifdef LEGATE_USE_OPENMP - static void omp_variant(legate::TaskContext& context); + static void omp_variant(legate::TaskContext context); #endif #ifdef LEGATE_USE_CUDA - static void gpu_variant(legate::TaskContext& context); + static void gpu_variant(legate::TaskContext context); #endif }; diff --git a/src/cunumeric/matrix/solve_omp.cc b/src/cunumeric/matrix/solve_omp.cc index 57e14fdb4a..baa9d8c7e0 100644 --- a/src/cunumeric/matrix/solve_omp.cc +++ b/src/cunumeric/matrix/solve_omp.cc @@ -22,7 +22,7 @@ namespace cunumeric { -/*static*/ void SolveTask::omp_variant(TaskContext& context) +/*static*/ void SolveTask::omp_variant(TaskContext context) { openblas_set_num_threads(omp_get_max_threads()); solve_template(context); diff --git a/src/cunumeric/matrix/solve_template.inl b/src/cunumeric/matrix/solve_template.inl index e338b8326e..6c9202135d 100644 --- a/src/cunumeric/matrix/solve_template.inl +++ b/src/cunumeric/matrix/solve_template.inl @@ -42,7 +42,7 @@ struct support_solve : std::true_type {}; template struct SolveImpl { template ::value>* = nullptr> - void operator()(Array& a_array, Array& b_array) const + void operator()(legate::Store a_array, legate::Store b_array) const { using VAL = legate_type_of; @@ -96,7 +96,7 @@ struct SolveImpl { } template ::value>* = nullptr> - void operator()(Array& a_array, Array& b_array) const + void operator()(legate::Store a_array, legate::Store b_array) const { assert(false); } @@ -105,9 +105,9 @@ struct SolveImpl { template static void solve_template(TaskContext& context) { - auto& a_array = context.outputs()[0]; - auto& b_array = context.outputs()[1]; - type_dispatch(a_array.code(), SolveImpl{}, a_array, b_array); + auto a_array = context.output(0); + auto b_array = context.output(1); + type_dispatch(a_array.type().code(), SolveImpl{}, a_array, b_array); } } // namespace cunumeric diff --git a/src/cunumeric/matrix/syrk.cc b/src/cunumeric/matrix/syrk.cc index 2fa5bc64cc..0d90b6146a 100644 --- a/src/cunumeric/matrix/syrk.cc +++ b/src/cunumeric/matrix/syrk.cc @@ -75,7 +75,7 @@ struct SyrkImplBody { } }; -/*static*/ void SyrkTask::cpu_variant(TaskContext& context) +/*static*/ void SyrkTask::cpu_variant(TaskContext context) { #ifdef LEGATE_USE_OPENMP openblas_set_num_threads(1); // make sure this isn't overzealous diff --git a/src/cunumeric/matrix/syrk.cu b/src/cunumeric/matrix/syrk.cu index 1fdbd2ca66..08c3916f51 100644 --- a/src/cunumeric/matrix/syrk.cu +++ b/src/cunumeric/matrix/syrk.cu @@ -81,7 +81,7 @@ struct SyrkImplBody { } }; -/*static*/ void SyrkTask::gpu_variant(TaskContext& context) +/*static*/ void SyrkTask::gpu_variant(TaskContext context) { syrk_template(context); } diff --git a/src/cunumeric/matrix/syrk.h b/src/cunumeric/matrix/syrk.h index 52f7d193e3..61917851cb 100644 --- a/src/cunumeric/matrix/syrk.h +++ b/src/cunumeric/matrix/syrk.h @@ -25,12 +25,12 @@ class SyrkTask : public CuNumericTask { static const int TASK_ID = CUNUMERIC_SYRK; public: - static void cpu_variant(legate::TaskContext& context); + static void cpu_variant(legate::TaskContext context); #ifdef LEGATE_USE_OPENMP - static void omp_variant(legate::TaskContext& context); + static void omp_variant(legate::TaskContext context); #endif #ifdef LEGATE_USE_CUDA - static void gpu_variant(legate::TaskContext& context); + static void gpu_variant(legate::TaskContext context); #endif }; diff --git a/src/cunumeric/matrix/syrk_omp.cc b/src/cunumeric/matrix/syrk_omp.cc index b276d71a2f..408854058e 100644 --- a/src/cunumeric/matrix/syrk_omp.cc +++ b/src/cunumeric/matrix/syrk_omp.cc @@ -76,7 +76,7 @@ struct SyrkImplBody { } }; -/*static*/ void SyrkTask::omp_variant(TaskContext& context) +/*static*/ void SyrkTask::omp_variant(TaskContext context) { openblas_set_num_threads(omp_get_max_threads()); syrk_template(context); diff --git a/src/cunumeric/matrix/syrk_template.inl b/src/cunumeric/matrix/syrk_template.inl index 58ea4abae4..6b043d57c4 100644 --- a/src/cunumeric/matrix/syrk_template.inl +++ b/src/cunumeric/matrix/syrk_template.inl @@ -40,7 +40,7 @@ struct support_syrk : std::true_type {}; template struct SyrkImpl { template ::value>* = nullptr> - void operator()(Array& lhs_array, Array& rhs_array) const + void operator()(legate::Store lhs_array, legate::Store rhs_array) const { using VAL = legate_type_of; @@ -63,7 +63,7 @@ struct SyrkImpl { } template ::value>* = nullptr> - void operator()(Array& lhs_array, Array& rhs_array) const + void operator()(legate::Store lhs_array, legate::Store rhs_array) const { assert(false); } @@ -72,10 +72,10 @@ struct SyrkImpl { template static void syrk_template(TaskContext& context) { - auto& lhs = context.outputs()[0]; - auto& rhs = context.inputs()[0]; + auto lhs = context.output(0); + auto rhs = context.input(0); - type_dispatch(lhs.code(), SyrkImpl{}, lhs, rhs); + type_dispatch(lhs.type().code(), SyrkImpl{}, lhs, rhs); } } // namespace cunumeric diff --git a/src/cunumeric/matrix/tile.cc b/src/cunumeric/matrix/tile.cc index d40d576986..2b9456a259 100644 --- a/src/cunumeric/matrix/tile.cc +++ b/src/cunumeric/matrix/tile.cc @@ -38,7 +38,7 @@ struct TileImplBody { } }; -/*static*/ void TileTask::cpu_variant(TaskContext& context) +/*static*/ void TileTask::cpu_variant(TaskContext context) { tile_template(context); } diff --git a/src/cunumeric/matrix/tile.cu b/src/cunumeric/matrix/tile.cu index 377c0c2889..b06d7a4d76 100644 --- a/src/cunumeric/matrix/tile.cu +++ b/src/cunumeric/matrix/tile.cu @@ -55,7 +55,7 @@ struct TileImplBody { } }; -/*static*/ void TileTask::gpu_variant(TaskContext& context) +/*static*/ void TileTask::gpu_variant(TaskContext context) { tile_template(context); } diff --git a/src/cunumeric/matrix/tile.h b/src/cunumeric/matrix/tile.h index 5d49c79a3c..1d63be12ae 100644 --- a/src/cunumeric/matrix/tile.h +++ b/src/cunumeric/matrix/tile.h @@ -21,8 +21,8 @@ namespace cunumeric { struct TileArgs { - const legate::Store& in; - const legate::Store& out; + legate::Store in; + legate::Store out; }; class TileTask : public CuNumericTask { @@ -30,12 +30,12 @@ class TileTask : public CuNumericTask { static const int TASK_ID = CUNUMERIC_TILE; public: - static void cpu_variant(legate::TaskContext& context); + static void cpu_variant(legate::TaskContext context); #ifdef LEGATE_USE_OPENMP - static void omp_variant(legate::TaskContext& context); + static void omp_variant(legate::TaskContext context); #endif #ifdef LEGATE_USE_CUDA - static void gpu_variant(legate::TaskContext& context); + static void gpu_variant(legate::TaskContext context); #endif }; diff --git a/src/cunumeric/matrix/tile_omp.cc b/src/cunumeric/matrix/tile_omp.cc index 992fbe1726..41fbfd2ec0 100644 --- a/src/cunumeric/matrix/tile_omp.cc +++ b/src/cunumeric/matrix/tile_omp.cc @@ -39,7 +39,7 @@ struct TileImplBody { } }; -/*static*/ void TileTask::omp_variant(TaskContext& context) +/*static*/ void TileTask::omp_variant(TaskContext context) { tile_template(context); } diff --git a/src/cunumeric/matrix/tile_template.inl b/src/cunumeric/matrix/tile_template.inl index 35f8dc967b..1987c2799c 100644 --- a/src/cunumeric/matrix/tile_template.inl +++ b/src/cunumeric/matrix/tile_template.inl @@ -78,7 +78,7 @@ struct TileDispatch { template static void tile_template(TaskContext& context) { - TileArgs args{context.inputs()[0], context.outputs()[0]}; + TileArgs args{context.input(0), context.output(0)}; type_dispatch(args.in.code(), TileDispatch{}, args); } diff --git a/src/cunumeric/matrix/transpose.cc b/src/cunumeric/matrix/transpose.cc index 224a36ab26..4509ccfba2 100644 --- a/src/cunumeric/matrix/transpose.cc +++ b/src/cunumeric/matrix/transpose.cc @@ -58,7 +58,7 @@ struct TransposeImplBody { } }; -/*static*/ void TransposeTask::cpu_variant(TaskContext& context) +/*static*/ void TransposeTask::cpu_variant(TaskContext context) { #ifdef LEGATE_USE_OPENMP openblas_set_num_threads(1); // make sure this isn't overzealous diff --git a/src/cunumeric/matrix/transpose.cu b/src/cunumeric/matrix/transpose.cu index 5ccd3ef7ad..ff7f340e4c 100644 --- a/src/cunumeric/matrix/transpose.cu +++ b/src/cunumeric/matrix/transpose.cu @@ -162,7 +162,7 @@ struct TransposeImplBody { } }; -/*static*/ void TransposeTask::gpu_variant(TaskContext& context) +/*static*/ void TransposeTask::gpu_variant(TaskContext context) { transpose_template(context); } diff --git a/src/cunumeric/matrix/transpose.h b/src/cunumeric/matrix/transpose.h index d7b9b05351..1c15970197 100644 --- a/src/cunumeric/matrix/transpose.h +++ b/src/cunumeric/matrix/transpose.h @@ -21,8 +21,8 @@ namespace cunumeric { struct TransposeArgs { - const legate::Store& out; - const legate::Store& in; + legate::Store out; + legate::Store in; bool logical; }; @@ -31,12 +31,12 @@ class TransposeTask : public CuNumericTask { static const int TASK_ID = CUNUMERIC_TRANSPOSE_COPY_2D; public: - static void cpu_variant(legate::TaskContext& context); + static void cpu_variant(legate::TaskContext context); #ifdef LEGATE_USE_OPENMP - static void omp_variant(legate::TaskContext& context); + static void omp_variant(legate::TaskContext context); #endif #ifdef LEGATE_USE_CUDA - static void gpu_variant(legate::TaskContext& context); + static void gpu_variant(legate::TaskContext context); #endif }; diff --git a/src/cunumeric/matrix/transpose_omp.cc b/src/cunumeric/matrix/transpose_omp.cc index 729719242b..0716632dbf 100644 --- a/src/cunumeric/matrix/transpose_omp.cc +++ b/src/cunumeric/matrix/transpose_omp.cc @@ -58,7 +58,7 @@ struct TransposeImplBody { } }; -/*static*/ void TransposeTask::omp_variant(TaskContext& context) +/*static*/ void TransposeTask::omp_variant(TaskContext context) { openblas_set_num_threads(omp_get_max_threads()); transpose_template(context); diff --git a/src/cunumeric/matrix/transpose_template.inl b/src/cunumeric/matrix/transpose_template.inl index 4d695c3cd1..0fe3a703f0 100644 --- a/src/cunumeric/matrix/transpose_template.inl +++ b/src/cunumeric/matrix/transpose_template.inl @@ -53,12 +53,12 @@ struct TransposeImpl { template static void transpose_template(TaskContext& context) { - auto& output = context.outputs()[0]; - auto& input = context.inputs()[0]; - auto logical = context.scalars()[0].value(); + auto output = context.output(0); + auto input = context.input(0); + auto logical = context.scalar(0).value(); TransposeArgs args{output, input, logical}; - type_dispatch(input.code(), TransposeImpl{}, args); + type_dispatch(input.type().code(), TransposeImpl{}, args); } } // namespace cunumeric diff --git a/src/cunumeric/matrix/trilu.cc b/src/cunumeric/matrix/trilu.cc index 7d0e55e4f8..8eb48c51bc 100644 --- a/src/cunumeric/matrix/trilu.cc +++ b/src/cunumeric/matrix/trilu.cc @@ -52,7 +52,7 @@ struct TriluImplBody { } }; -/*static*/ void TriluTask::cpu_variant(TaskContext& context) +/*static*/ void TriluTask::cpu_variant(TaskContext context) { trilu_template(context); } diff --git a/src/cunumeric/matrix/trilu.cu b/src/cunumeric/matrix/trilu.cu index 6a8c7a02b9..e643876674 100644 --- a/src/cunumeric/matrix/trilu.cu +++ b/src/cunumeric/matrix/trilu.cu @@ -70,7 +70,7 @@ struct TriluImplBody { } }; -/*static*/ void TriluTask::gpu_variant(TaskContext& context) +/*static*/ void TriluTask::gpu_variant(TaskContext context) { trilu_template(context); } diff --git a/src/cunumeric/matrix/trilu.h b/src/cunumeric/matrix/trilu.h index a7e5ed6e57..29e18daea3 100644 --- a/src/cunumeric/matrix/trilu.h +++ b/src/cunumeric/matrix/trilu.h @@ -23,8 +23,8 @@ namespace cunumeric { struct TriluArgs { bool lower; int32_t k; - const legate::Store& output; - const legate::Store& input; + legate::Store output; + legate::Store input; }; class TriluTask : public CuNumericTask { @@ -32,12 +32,12 @@ class TriluTask : public CuNumericTask { static const int TASK_ID = CUNUMERIC_TRILU; public: - static void cpu_variant(legate::TaskContext& context); + static void cpu_variant(legate::TaskContext context); #ifdef LEGATE_USE_OPENMP - static void omp_variant(legate::TaskContext& context); + static void omp_variant(legate::TaskContext context); #endif #ifdef LEGATE_USE_CUDA - static void gpu_variant(legate::TaskContext& context); + static void gpu_variant(legate::TaskContext context); #endif }; diff --git a/src/cunumeric/matrix/trilu_omp.cc b/src/cunumeric/matrix/trilu_omp.cc index b4e2482daa..7932e4e43f 100644 --- a/src/cunumeric/matrix/trilu_omp.cc +++ b/src/cunumeric/matrix/trilu_omp.cc @@ -54,7 +54,7 @@ struct TriluImplBody { } }; -/*static*/ void TriluTask::omp_variant(TaskContext& context) +/*static*/ void TriluTask::omp_variant(TaskContext context) { trilu_template(context); } diff --git a/src/cunumeric/matrix/trilu_template.inl b/src/cunumeric/matrix/trilu_template.inl index ca417f5af7..4c2d7fcc94 100644 --- a/src/cunumeric/matrix/trilu_template.inl +++ b/src/cunumeric/matrix/trilu_template.inl @@ -72,10 +72,10 @@ static void trilu_template(TaskContext& context) auto& scalars = context.scalars(); auto lower = scalars[0].value(); auto k = scalars[1].value(); - auto& input = context.inputs()[0]; - auto& output = context.outputs()[0]; + auto input = context.input(0); + auto output = context.output(0); TriluArgs args{lower, k, output, input}; - double_dispatch(args.output.dim(), args.output.code(), TriluImpl{}, args); + double_dispatch(args.output.dim(), args.output.type().code(), TriluImpl{}, args); } } // namespace cunumeric diff --git a/src/cunumeric/matrix/trsm.cc b/src/cunumeric/matrix/trsm.cc index e61c869816..678ae48aa2 100644 --- a/src/cunumeric/matrix/trsm.cc +++ b/src/cunumeric/matrix/trsm.cc @@ -86,7 +86,7 @@ struct TrsmImplBody { } }; -/*static*/ void TrsmTask::cpu_variant(TaskContext& context) +/*static*/ void TrsmTask::cpu_variant(TaskContext context) { #ifdef LEGATE_USE_OPENMP openblas_set_num_threads(1); // make sure this isn't overzealous diff --git a/src/cunumeric/matrix/trsm.cu b/src/cunumeric/matrix/trsm.cu index 8bd5d66c76..30dbeba439 100644 --- a/src/cunumeric/matrix/trsm.cu +++ b/src/cunumeric/matrix/trsm.cu @@ -80,7 +80,7 @@ struct TrsmImplBody { } }; -/*static*/ void TrsmTask::gpu_variant(TaskContext& context) +/*static*/ void TrsmTask::gpu_variant(TaskContext context) { trsm_template(context); } diff --git a/src/cunumeric/matrix/trsm.h b/src/cunumeric/matrix/trsm.h index bc54863c3a..5bb65f8c6d 100644 --- a/src/cunumeric/matrix/trsm.h +++ b/src/cunumeric/matrix/trsm.h @@ -25,12 +25,12 @@ class TrsmTask : public CuNumericTask { static const int TASK_ID = CUNUMERIC_TRSM; public: - static void cpu_variant(legate::TaskContext& context); + static void cpu_variant(legate::TaskContext context); #ifdef LEGATE_USE_OPENMP - static void omp_variant(legate::TaskContext& context); + static void omp_variant(legate::TaskContext context); #endif #ifdef LEGATE_USE_CUDA - static void gpu_variant(legate::TaskContext& context); + static void gpu_variant(legate::TaskContext context); #endif }; diff --git a/src/cunumeric/matrix/trsm_omp.cc b/src/cunumeric/matrix/trsm_omp.cc index 2041ec17a6..8ed09f7d85 100644 --- a/src/cunumeric/matrix/trsm_omp.cc +++ b/src/cunumeric/matrix/trsm_omp.cc @@ -87,7 +87,7 @@ struct TrsmImplBody { } }; -/*static*/ void TrsmTask::omp_variant(TaskContext& context) +/*static*/ void TrsmTask::omp_variant(TaskContext context) { openblas_set_num_threads(omp_get_max_threads()); trsm_template(context); diff --git a/src/cunumeric/matrix/trsm_template.inl b/src/cunumeric/matrix/trsm_template.inl index 28f37ba1b3..3cd93288cb 100644 --- a/src/cunumeric/matrix/trsm_template.inl +++ b/src/cunumeric/matrix/trsm_template.inl @@ -40,7 +40,7 @@ struct support_trsm : std::true_type {}; template struct TrsmImpl { template ::value>* = nullptr> - void operator()(Array& lhs_array, Array& rhs_array) const + void operator()(legate::Store lhs_array, legate::Store rhs_array) const { using VAL = legate_type_of; @@ -63,7 +63,7 @@ struct TrsmImpl { } template ::value>* = nullptr> - void operator()(Array& lhs_array, Array& rhs_array) const + void operator()(legate::Store lhs_array, legate::Store rhs_array) const { assert(false); } @@ -72,10 +72,10 @@ struct TrsmImpl { template static void trsm_template(TaskContext& context) { - auto& lhs = context.outputs()[0]; - auto& rhs = context.inputs()[0]; + auto lhs = context.output(0); + auto rhs = context.input(0); - type_dispatch(lhs.code(), TrsmImpl{}, lhs, rhs); + type_dispatch(lhs.type().code(), TrsmImpl{}, lhs, rhs); } } // namespace cunumeric diff --git a/src/cunumeric/nullary/arange.cc b/src/cunumeric/nullary/arange.cc index dc3266a04c..552f24a6bb 100644 --- a/src/cunumeric/nullary/arange.cc +++ b/src/cunumeric/nullary/arange.cc @@ -33,7 +33,7 @@ struct ArangeImplBody { } }; -/*static*/ void ArangeTask::cpu_variant(TaskContext& context) +/*static*/ void ArangeTask::cpu_variant(TaskContext context) { arange_template(context); } diff --git a/src/cunumeric/nullary/arange.cu b/src/cunumeric/nullary/arange.cu index d93ce538dd..dafdae0776 100644 --- a/src/cunumeric/nullary/arange.cu +++ b/src/cunumeric/nullary/arange.cu @@ -47,7 +47,7 @@ struct ArangeImplBody { } }; -/*static*/ void ArangeTask::gpu_variant(TaskContext& context) +/*static*/ void ArangeTask::gpu_variant(TaskContext context) { arange_template(context); } diff --git a/src/cunumeric/nullary/arange.h b/src/cunumeric/nullary/arange.h index 303138a7bd..1d70922a3a 100644 --- a/src/cunumeric/nullary/arange.h +++ b/src/cunumeric/nullary/arange.h @@ -21,10 +21,10 @@ namespace cunumeric { struct ArangeArgs { - const legate::Store& out; - const legate::Store& start; - const legate::Store& stop; - const legate::Store& step; + legate::Store out; + legate::Store start; + legate::Store stop; + legate::Store step; }; class ArangeTask : public CuNumericTask { @@ -32,12 +32,12 @@ class ArangeTask : public CuNumericTask { static const int TASK_ID = CUNUMERIC_ARANGE; public: - static void cpu_variant(legate::TaskContext& context); + static void cpu_variant(legate::TaskContext context); #ifdef LEGATE_USE_OPENMP - static void omp_variant(legate::TaskContext& context); + static void omp_variant(legate::TaskContext context); #endif #ifdef LEGATE_USE_CUDA - static void gpu_variant(legate::TaskContext& context); + static void gpu_variant(legate::TaskContext context); #endif }; diff --git a/src/cunumeric/nullary/arange_omp.cc b/src/cunumeric/nullary/arange_omp.cc index 47cd32f2cd..e61d0ee375 100644 --- a/src/cunumeric/nullary/arange_omp.cc +++ b/src/cunumeric/nullary/arange_omp.cc @@ -34,7 +34,7 @@ struct ArangeImplBody { } }; -/*static*/ void ArangeTask::omp_variant(TaskContext& context) +/*static*/ void ArangeTask::omp_variant(TaskContext context) { arange_template(context); } diff --git a/src/cunumeric/nullary/arange_template.inl b/src/cunumeric/nullary/arange_template.inl index c71b9c44e6..703d733b30 100644 --- a/src/cunumeric/nullary/arange_template.inl +++ b/src/cunumeric/nullary/arange_template.inl @@ -52,8 +52,8 @@ struct ArangeImpl { template static void arange_template(TaskContext& context) { - auto& inputs = context.inputs(); - ArangeArgs args{context.outputs()[0], inputs[0], inputs[1], inputs[2]}; + auto inputs = context.inputs(); + ArangeArgs args{context.output(0), inputs[0], inputs[1], inputs[2]}; type_dispatch(args.out.code(), ArangeImpl{}, args); } diff --git a/src/cunumeric/nullary/eye.cc b/src/cunumeric/nullary/eye.cc index 2b91d023b3..bedd76953f 100644 --- a/src/cunumeric/nullary/eye.cc +++ b/src/cunumeric/nullary/eye.cc @@ -31,7 +31,7 @@ struct EyeImplBody { } }; -/*static*/ void EyeTask::cpu_variant(TaskContext& context) +/*static*/ void EyeTask::cpu_variant(TaskContext context) { eye_template(context); } diff --git a/src/cunumeric/nullary/eye.cu b/src/cunumeric/nullary/eye.cu index 7b337d2e6a..9a2b363963 100644 --- a/src/cunumeric/nullary/eye.cu +++ b/src/cunumeric/nullary/eye.cu @@ -42,7 +42,7 @@ struct EyeImplBody { } }; -/*static*/ void EyeTask::gpu_variant(TaskContext& context) +/*static*/ void EyeTask::gpu_variant(TaskContext context) { eye_template(context); } diff --git a/src/cunumeric/nullary/eye.h b/src/cunumeric/nullary/eye.h index 6dac663702..c014538f08 100644 --- a/src/cunumeric/nullary/eye.h +++ b/src/cunumeric/nullary/eye.h @@ -21,7 +21,7 @@ namespace cunumeric { struct EyeArgs { - const legate::Store& out; + legate::Store out; int32_t k; }; @@ -30,12 +30,12 @@ class EyeTask : public CuNumericTask { static const int TASK_ID = CUNUMERIC_EYE; public: - static void cpu_variant(legate::TaskContext& context); + static void cpu_variant(legate::TaskContext context); #ifdef LEGATE_USE_OPENMP - static void omp_variant(legate::TaskContext& context); + static void omp_variant(legate::TaskContext context); #endif #ifdef LEGATE_USE_CUDA - static void gpu_variant(legate::TaskContext& context); + static void gpu_variant(legate::TaskContext context); #endif }; diff --git a/src/cunumeric/nullary/eye_omp.cc b/src/cunumeric/nullary/eye_omp.cc index 8cdc3795d0..aa90027d8d 100644 --- a/src/cunumeric/nullary/eye_omp.cc +++ b/src/cunumeric/nullary/eye_omp.cc @@ -32,7 +32,7 @@ struct EyeImplBody { } }; -/*static*/ void EyeTask::omp_variant(TaskContext& context) +/*static*/ void EyeTask::omp_variant(TaskContext context) { eye_template(context); } diff --git a/src/cunumeric/nullary/eye_template.inl b/src/cunumeric/nullary/eye_template.inl index 33cbe6054c..664b40efa5 100644 --- a/src/cunumeric/nullary/eye_template.inl +++ b/src/cunumeric/nullary/eye_template.inl @@ -69,7 +69,7 @@ struct EyeImpl { template static void eye_template(TaskContext& context) { - EyeArgs args{context.outputs()[0], context.scalars()[0].value()}; + EyeArgs args{context.output(0), context.scalar(0).value()}; type_dispatch(args.out.code(), EyeImpl{}, args); } diff --git a/src/cunumeric/nullary/fill.cc b/src/cunumeric/nullary/fill.cc index 2cf026e85a..807aeaf8c3 100644 --- a/src/cunumeric/nullary/fill.cc +++ b/src/cunumeric/nullary/fill.cc @@ -43,7 +43,7 @@ struct FillImplBody { } }; -/*static*/ void FillTask::cpu_variant(TaskContext& context) +/*static*/ void FillTask::cpu_variant(TaskContext context) { fill_template(context); } diff --git a/src/cunumeric/nullary/fill.cu b/src/cunumeric/nullary/fill.cu index f4299fad55..9f2d2ff45b 100644 --- a/src/cunumeric/nullary/fill.cu +++ b/src/cunumeric/nullary/fill.cu @@ -61,7 +61,7 @@ struct FillImplBody { } }; -/*static*/ void FillTask::gpu_variant(TaskContext& context) +/*static*/ void FillTask::gpu_variant(TaskContext context) { fill_template(context); } diff --git a/src/cunumeric/nullary/fill.h b/src/cunumeric/nullary/fill.h index 6b53b355ca..0444c95c12 100644 --- a/src/cunumeric/nullary/fill.h +++ b/src/cunumeric/nullary/fill.h @@ -21,8 +21,8 @@ namespace cunumeric { struct FillArgs { - const legate::Store& out; - const legate::Store& fill_value; + legate::Store out; + legate::Store fill_value; bool is_argval; }; @@ -31,12 +31,12 @@ class FillTask : public CuNumericTask { static const int TASK_ID = CUNUMERIC_FILL; public: - static void cpu_variant(legate::TaskContext& context); + static void cpu_variant(legate::TaskContext context); #ifdef LEGATE_USE_OPENMP - static void omp_variant(legate::TaskContext& context); + static void omp_variant(legate::TaskContext context); #endif #ifdef LEGATE_USE_CUDA - static void gpu_variant(legate::TaskContext& context); + static void gpu_variant(legate::TaskContext context); #endif }; diff --git a/src/cunumeric/nullary/fill_omp.cc b/src/cunumeric/nullary/fill_omp.cc index 8200525404..64b8ac41e7 100644 --- a/src/cunumeric/nullary/fill_omp.cc +++ b/src/cunumeric/nullary/fill_omp.cc @@ -50,7 +50,7 @@ struct FillImplBody { } }; -/*static*/ void FillTask::omp_variant(TaskContext& context) +/*static*/ void FillTask::omp_variant(TaskContext context) { fill_template(context); } diff --git a/src/cunumeric/nullary/fill_template.inl b/src/cunumeric/nullary/fill_template.inl index 4b671be962..871622d14f 100644 --- a/src/cunumeric/nullary/fill_template.inl +++ b/src/cunumeric/nullary/fill_template.inl @@ -70,7 +70,7 @@ struct FillImpl { template static void fill_template(TaskContext& context) { - FillArgs args{context.outputs()[0], context.inputs()[0], context.scalars()[0].value()}; + FillArgs args{context.output(0), context.input(0), context.scalar(0).value()}; Type::Code code{args.out.code()}; if (Type::Code::STRUCT == code) { #ifdef DEBUG_CUNUMERIC diff --git a/src/cunumeric/nullary/window.cc b/src/cunumeric/nullary/window.cc index 7fce1b9598..9813747f19 100644 --- a/src/cunumeric/nullary/window.cc +++ b/src/cunumeric/nullary/window.cc @@ -37,7 +37,7 @@ struct WindowImplBody { } }; -/*static*/ void WindowTask::cpu_variant(TaskContext& context) +/*static*/ void WindowTask::cpu_variant(TaskContext context) { window_template(context); } diff --git a/src/cunumeric/nullary/window.cu b/src/cunumeric/nullary/window.cu index f58dcba174..ff3124bc39 100644 --- a/src/cunumeric/nullary/window.cu +++ b/src/cunumeric/nullary/window.cu @@ -64,7 +64,7 @@ struct WindowImplBody { } }; -/*static*/ void WindowTask::gpu_variant(TaskContext& context) +/*static*/ void WindowTask::gpu_variant(TaskContext context) { window_template(context); } diff --git a/src/cunumeric/nullary/window.h b/src/cunumeric/nullary/window.h index 8d585a98f7..9f767d75fd 100644 --- a/src/cunumeric/nullary/window.h +++ b/src/cunumeric/nullary/window.h @@ -25,12 +25,12 @@ class WindowTask : public CuNumericTask { static const int TASK_ID = CUNUMERIC_WINDOW; public: - static void cpu_variant(legate::TaskContext& context); + static void cpu_variant(legate::TaskContext context); #ifdef LEGATE_USE_OPENMP - static void omp_variant(legate::TaskContext& context); + static void omp_variant(legate::TaskContext context); #endif #ifdef LEGATE_USE_CUDA - static void gpu_variant(legate::TaskContext& context); + static void gpu_variant(legate::TaskContext context); #endif }; diff --git a/src/cunumeric/nullary/window_omp.cc b/src/cunumeric/nullary/window_omp.cc index 1057e2333b..7af11e6b64 100644 --- a/src/cunumeric/nullary/window_omp.cc +++ b/src/cunumeric/nullary/window_omp.cc @@ -38,7 +38,7 @@ struct WindowImplBody { } }; -/*static*/ void WindowTask::omp_variant(TaskContext& context) +/*static*/ void WindowTask::omp_variant(TaskContext context) { window_template(context); } diff --git a/src/cunumeric/nullary/window_template.inl b/src/cunumeric/nullary/window_template.inl index 4289a0269b..17285d319d 100644 --- a/src/cunumeric/nullary/window_template.inl +++ b/src/cunumeric/nullary/window_template.inl @@ -30,7 +30,7 @@ struct WindowImplBody; template struct WindowImpl { template - void operator()(legate::Store& output, int64_t M, double beta) const + void operator()(legate::Store output, int64_t M, double beta) const { auto rect = output.shape<1>(); @@ -53,7 +53,7 @@ struct WindowImpl { template static void window_template(TaskContext& context) { - auto& output = context.outputs().front(); + auto output = context.outputs().front(); auto& scalars = context.scalars(); auto op_code = scalars[0].value(); auto M = scalars[1].value(); diff --git a/src/cunumeric/random/bitgenerator.cc b/src/cunumeric/random/bitgenerator.cc index 1038a5309a..fcbe009ce2 100644 --- a/src/cunumeric/random/bitgenerator.cc +++ b/src/cunumeric/random/bitgenerator.cc @@ -70,7 +70,7 @@ std::map>> template <> std::mutex BitGeneratorImplBody::lock_generators = {}; -/*static*/ void BitGeneratorTask::cpu_variant(TaskContext& context) +/*static*/ void BitGeneratorTask::cpu_variant(TaskContext context) { bitgenerator_template(context); } diff --git a/src/cunumeric/random/bitgenerator.cu b/src/cunumeric/random/bitgenerator.cu index f63ed3ef11..a98153e20f 100644 --- a/src/cunumeric/random/bitgenerator.cu +++ b/src/cunumeric/random/bitgenerator.cu @@ -63,7 +63,7 @@ std::map>> template <> std::mutex BitGeneratorImplBody::lock_generators = {}; -/*static*/ void BitGeneratorTask::gpu_variant(legate::TaskContext& context) +/*static*/ void BitGeneratorTask::gpu_variant(legate::TaskContext context) { bitgenerator_template(context); } diff --git a/src/cunumeric/random/bitgenerator.h b/src/cunumeric/random/bitgenerator.h index c0661f9809..46292d9fd4 100644 --- a/src/cunumeric/random/bitgenerator.h +++ b/src/cunumeric/random/bitgenerator.h @@ -83,14 +83,14 @@ class BitGeneratorTask : public CuNumericTask { static const int TASK_ID = CUNUMERIC_BITGENERATOR; public: - static void cpu_variant(legate::TaskContext& context); + static void cpu_variant(legate::TaskContext context); #ifdef LEGATE_USE_OPENMP // TODO: Fully parallelized OpenMP implementation for BitGenerator // Doing it this way is safe, but only one thread is being used out of the OpenMP pool. - static void omp_variant(legate::TaskContext& context) { BitGeneratorTask::cpu_variant(context); } + static void omp_variant(legate::TaskContext context) { BitGeneratorTask::cpu_variant(context); } #endif #ifdef LEGATE_USE_CUDA - static void gpu_variant(legate::TaskContext& context); + static void gpu_variant(legate::TaskContext context); #endif }; diff --git a/src/cunumeric/random/bitgenerator_curand.inl b/src/cunumeric/random/bitgenerator_curand.inl index f74c87ad03..adb4539add 100644 --- a/src/cunumeric/random/bitgenerator_curand.inl +++ b/src/cunumeric/random/bitgenerator_curand.inl @@ -275,7 +275,7 @@ struct CURANDGenerator { struct generate_fn { template - size_t operator()(CURANDGenerator& gen, legate::Store& output) + size_t operator()(CURANDGenerator& gen, legate::Store output) { auto rect = output.shape(); uint64_t volume = rect.volume(); @@ -1364,7 +1364,7 @@ struct generate_distribution { generate_distribution(const generator_t& generator) : generator_(generator) {} template - size_t operator()(CURANDGenerator& gen, legate::Store& output) + size_t operator()(CURANDGenerator& gen, legate::Store output) { auto rect = output.shape(); uint64_t volume = rect.volume(); @@ -1383,7 +1383,7 @@ struct generate_distribution { return volume; } - static void generate(legate::Store& res, + static void generate(legate::Store res, CURANDGenerator& cugen, const std::vector& intparams, const std::vector& floatparams, diff --git a/src/cunumeric/random/bitgenerator_template.inl b/src/cunumeric/random/bitgenerator_template.inl index 83a47d4b3b..e447f69104 100644 --- a/src/cunumeric/random/bitgenerator_template.inl +++ b/src/cunumeric/random/bitgenerator_template.inl @@ -50,8 +50,8 @@ struct BitGeneratorImpl { template static void bitgenerator_template(TaskContext& context) { - auto& inputs = context.inputs(); - auto& outputs = context.outputs(); + auto inputs = context.inputs(); + auto outputs = context.outputs(); auto& scalars = context.scalars(); auto bitgen_op = scalars[0].value(); auto generatorID = scalars[1].value(); diff --git a/src/cunumeric/random/rand.cc b/src/cunumeric/random/rand.cc index 011fe64c87..4beff339fc 100644 --- a/src/cunumeric/random/rand.cc +++ b/src/cunumeric/random/rand.cc @@ -39,7 +39,7 @@ struct RandImplBody { } }; -/*static*/ void RandTask::cpu_variant(TaskContext& context) +/*static*/ void RandTask::cpu_variant(TaskContext context) { rand_template(context); } diff --git a/src/cunumeric/random/rand.cu b/src/cunumeric/random/rand.cu index 30e25aea81..7b3d7d9897 100644 --- a/src/cunumeric/random/rand.cu +++ b/src/cunumeric/random/rand.cu @@ -50,7 +50,7 @@ struct RandImplBody { } }; -/*static*/ void RandTask::gpu_variant(TaskContext& context) +/*static*/ void RandTask::gpu_variant(TaskContext context) { rand_template(context); } diff --git a/src/cunumeric/random/rand.h b/src/cunumeric/random/rand.h index 99baf0e6bf..297e51627a 100644 --- a/src/cunumeric/random/rand.h +++ b/src/cunumeric/random/rand.h @@ -22,7 +22,7 @@ namespace cunumeric { struct RandArgs { - const legate::Store& out; + legate::Store out; RandGenCode gen_code; uint32_t epoch; legate::DomainPoint strides; @@ -34,12 +34,12 @@ class RandTask : public CuNumericTask { static const int TASK_ID = CUNUMERIC_RAND; public: - static void cpu_variant(legate::TaskContext& context); + static void cpu_variant(legate::TaskContext context); #ifdef LEGATE_USE_OPENMP - static void omp_variant(legate::TaskContext& context); + static void omp_variant(legate::TaskContext context); #endif #ifdef LEGATE_USE_CUDA - static void gpu_variant(legate::TaskContext& context); + static void gpu_variant(legate::TaskContext context); #endif }; diff --git a/src/cunumeric/random/rand_omp.cc b/src/cunumeric/random/rand_omp.cc index 3966b989db..1031b50437 100644 --- a/src/cunumeric/random/rand_omp.cc +++ b/src/cunumeric/random/rand_omp.cc @@ -40,7 +40,7 @@ struct RandImplBody { } }; -/*static*/ void RandTask::omp_variant(TaskContext& context) +/*static*/ void RandTask::omp_variant(TaskContext context) { rand_template(context); } diff --git a/src/cunumeric/random/rand_template.inl b/src/cunumeric/random/rand_template.inl index 3b689a7289..b0cf83813a 100644 --- a/src/cunumeric/random/rand_template.inl +++ b/src/cunumeric/random/rand_template.inl @@ -74,8 +74,8 @@ struct RandDispatch { template static void rand_template(TaskContext& context) { - auto& inputs = context.inputs(); - auto& outputs = context.outputs(); + auto inputs = context.inputs(); + auto outputs = context.outputs(); auto& scalars = context.scalars(); auto gen_code = scalars[0].value(); diff --git a/src/cunumeric/scan/scan_global.cc b/src/cunumeric/scan/scan_global.cc index 2df4ae14d1..7215bbdd71 100644 --- a/src/cunumeric/scan/scan_global.cc +++ b/src/cunumeric/scan/scan_global.cc @@ -67,7 +67,7 @@ struct ScanGlobalImplBody { } }; -/*static*/ void ScanGlobalTask::cpu_variant(TaskContext& context) +/*static*/ void ScanGlobalTask::cpu_variant(TaskContext context) { scan_global_template(context); } diff --git a/src/cunumeric/scan/scan_global.cu b/src/cunumeric/scan/scan_global.cu index ba1c5da9d8..58afee4a08 100644 --- a/src/cunumeric/scan/scan_global.cu +++ b/src/cunumeric/scan/scan_global.cu @@ -81,7 +81,7 @@ struct ScanGlobalImplBody { } }; -/*static*/ void ScanGlobalTask::gpu_variant(TaskContext& context) +/*static*/ void ScanGlobalTask::gpu_variant(TaskContext context) { scan_global_template(context); } diff --git a/src/cunumeric/scan/scan_global.h b/src/cunumeric/scan/scan_global.h index 95904a2fcc..2443550926 100644 --- a/src/cunumeric/scan/scan_global.h +++ b/src/cunumeric/scan/scan_global.h @@ -22,8 +22,8 @@ namespace cunumeric { struct ScanGlobalArgs { - const legate::Store& sum_vals; - const legate::Store& out; + legate::Store sum_vals; + legate::Store out; ScanCode op_code; const legate::DomainPoint& partition_index; }; @@ -33,12 +33,12 @@ class ScanGlobalTask : public CuNumericTask { static const int TASK_ID = CUNUMERIC_SCAN_GLOBAL; public: - static void cpu_variant(legate::TaskContext& context); + static void cpu_variant(legate::TaskContext context); #ifdef LEGATE_USE_OPENMP - static void omp_variant(legate::TaskContext& context); + static void omp_variant(legate::TaskContext context); #endif #ifdef LEGATE_USE_CUDA - static void gpu_variant(legate::TaskContext& context); + static void gpu_variant(legate::TaskContext context); #endif }; diff --git a/src/cunumeric/scan/scan_global_omp.cc b/src/cunumeric/scan/scan_global_omp.cc index 3ad989acaf..bb4f64273d 100644 --- a/src/cunumeric/scan/scan_global_omp.cc +++ b/src/cunumeric/scan/scan_global_omp.cc @@ -70,7 +70,7 @@ struct ScanGlobalImplBody { } }; -/*static*/ void ScanGlobalTask::omp_variant(TaskContext& context) +/*static*/ void ScanGlobalTask::omp_variant(TaskContext context) { scan_global_template(context); } diff --git a/src/cunumeric/scan/scan_global_template.inl b/src/cunumeric/scan/scan_global_template.inl index b96007dc25..2134900a52 100644 --- a/src/cunumeric/scan/scan_global_template.inl +++ b/src/cunumeric/scan/scan_global_template.inl @@ -71,7 +71,7 @@ static void scan_global_template(TaskContext& context) { auto task_index = context.get_task_index(); ScanGlobalArgs args{ - context.inputs()[1], context.outputs()[0], context.scalars()[0].value(), task_index}; + context.input(1), context.output(0), context.scalar(0).value(), task_index}; op_dispatch(args.op_code, ScanGlobalDispatch{}, args); } diff --git a/src/cunumeric/scan/scan_local.cc b/src/cunumeric/scan/scan_local.cc index 37cc02ac66..5dabe3486f 100644 --- a/src/cunumeric/scan/scan_local.cc +++ b/src/cunumeric/scan/scan_local.cc @@ -109,7 +109,7 @@ struct ScanLocalNanImplBody { } }; -/*static*/ void ScanLocalTask::cpu_variant(TaskContext& context) +/*static*/ void ScanLocalTask::cpu_variant(TaskContext context) { scan_local_template(context); } diff --git a/src/cunumeric/scan/scan_local.cu b/src/cunumeric/scan/scan_local.cu index e13218b37f..1de5582d7c 100644 --- a/src/cunumeric/scan/scan_local.cu +++ b/src/cunumeric/scan/scan_local.cu @@ -128,7 +128,7 @@ struct ScanLocalNanImplBody { } }; -/*static*/ void ScanLocalTask::gpu_variant(TaskContext& context) +/*static*/ void ScanLocalTask::gpu_variant(TaskContext context) { scan_local_template(context); } diff --git a/src/cunumeric/scan/scan_local.h b/src/cunumeric/scan/scan_local.h index ffe7a5d935..71a6a3dc8c 100644 --- a/src/cunumeric/scan/scan_local.h +++ b/src/cunumeric/scan/scan_local.h @@ -22,9 +22,9 @@ namespace cunumeric { struct ScanLocalArgs { - const legate::Store& out; - const legate::Store& in; - legate::Store& sum_vals; + legate::Store out; + legate::Store in; + legate::Store sum_vals; ScanCode op_code; bool nan_to_identity; }; @@ -34,12 +34,12 @@ class ScanLocalTask : public CuNumericTask { static const int TASK_ID = CUNUMERIC_SCAN_LOCAL; public: - static void cpu_variant(legate::TaskContext& context); + static void cpu_variant(legate::TaskContext context); #ifdef LEGATE_USE_OPENMP - static void omp_variant(legate::TaskContext& context); + static void omp_variant(legate::TaskContext context); #endif #ifdef LEGATE_USE_CUDA - static void gpu_variant(legate::TaskContext& context); + static void gpu_variant(legate::TaskContext context); #endif }; diff --git a/src/cunumeric/scan/scan_local_omp.cc b/src/cunumeric/scan/scan_local_omp.cc index 6b695f9e42..9aba5b9e35 100644 --- a/src/cunumeric/scan/scan_local_omp.cc +++ b/src/cunumeric/scan/scan_local_omp.cc @@ -111,7 +111,7 @@ struct ScanLocalNanImplBody { } }; -/*static*/ void ScanLocalTask::omp_variant(TaskContext& context) +/*static*/ void ScanLocalTask::omp_variant(TaskContext context) { scan_local_template(context); } diff --git a/src/cunumeric/scan/scan_local_template.inl b/src/cunumeric/scan/scan_local_template.inl index 154a86b355..566d8e4c32 100644 --- a/src/cunumeric/scan/scan_local_template.inl +++ b/src/cunumeric/scan/scan_local_template.inl @@ -96,11 +96,11 @@ struct ScanLocalDispatch { template static void scan_local_template(TaskContext& context) { - ScanLocalArgs args{context.outputs()[0], - context.inputs()[0], - context.outputs()[1], - context.scalars()[0].value(), - context.scalars()[1].value()}; + ScanLocalArgs args{context.output(0), + context.input(0), + context.output(1), + context.scalar(0).value(), + context.scalar(1).value()}; op_dispatch(args.op_code, args.nan_to_identity, ScanLocalDispatch{}, args); } diff --git a/src/cunumeric/search/argwhere.cc b/src/cunumeric/search/argwhere.cc index a787c2f4c5..f4ed83cb6f 100644 --- a/src/cunumeric/search/argwhere.cc +++ b/src/cunumeric/search/argwhere.cc @@ -25,7 +25,7 @@ template struct ArgWhereImplBody { using VAL = legate_type_of; - void operator()(Array& out_array, + void operator()(legate::Store& out_array, AccessorRO input, const Pitches& pitches, const Rect& rect, @@ -53,7 +53,7 @@ struct ArgWhereImplBody { } }; -/*static*/ void ArgWhereTask::cpu_variant(TaskContext& context) +/*static*/ void ArgWhereTask::cpu_variant(TaskContext context) { argwhere_template(context); } diff --git a/src/cunumeric/search/argwhere.cu b/src/cunumeric/search/argwhere.cu index 09819aca76..e411dd0579 100644 --- a/src/cunumeric/search/argwhere.cu +++ b/src/cunumeric/search/argwhere.cu @@ -45,7 +45,7 @@ template struct ArgWhereImplBody { using VAL = legate_type_of; - void operator()(Array& out_array, + void operator()(legate::Store& out_array, AccessorRO input, const Pitches& pitches, const Rect& rect, @@ -68,7 +68,7 @@ struct ArgWhereImplBody { } }; -/*static*/ void ArgWhereTask::gpu_variant(TaskContext& context) +/*static*/ void ArgWhereTask::gpu_variant(TaskContext context) { argwhere_template(context); } diff --git a/src/cunumeric/search/argwhere.h b/src/cunumeric/search/argwhere.h index 493a3a7cf8..f62ba2cddc 100644 --- a/src/cunumeric/search/argwhere.h +++ b/src/cunumeric/search/argwhere.h @@ -21,8 +21,8 @@ namespace cunumeric { struct ArgWhereArgs { - Array& out; - const Array& in; + legate::Store out; + legate::Store in; }; class ArgWhereTask : public CuNumericTask { @@ -30,12 +30,12 @@ class ArgWhereTask : public CuNumericTask { static const int TASK_ID = CUNUMERIC_ARGWHERE; public: - static void cpu_variant(legate::TaskContext& context); + static void cpu_variant(legate::TaskContext context); #ifdef LEGATE_USE_OPENMP - static void omp_variant(legate::TaskContext& context); + static void omp_variant(legate::TaskContext context); #endif #ifdef LEGATE_USE_CUDA - static void gpu_variant(legate::TaskContext& context); + static void gpu_variant(legate::TaskContext context); #endif }; diff --git a/src/cunumeric/search/argwhere_omp.cc b/src/cunumeric/search/argwhere_omp.cc index 51555b6848..990cb804a9 100644 --- a/src/cunumeric/search/argwhere_omp.cc +++ b/src/cunumeric/search/argwhere_omp.cc @@ -27,7 +27,7 @@ template struct ArgWhereImplBody { using VAL = legate_type_of; - void operator()(Array& out_array, + void operator()(legate::Store& out_array, AccessorRO input, const Pitches& pitches, const Rect& rect, @@ -74,7 +74,7 @@ struct ArgWhereImplBody { } }; -/*static*/ void ArgWhereTask::omp_variant(TaskContext& context) +/*static*/ void ArgWhereTask::omp_variant(TaskContext context) { argwhere_template(context); } diff --git a/src/cunumeric/search/argwhere_template.inl b/src/cunumeric/search/argwhere_template.inl index 5c1a91a853..7d8c84b099 100644 --- a/src/cunumeric/search/argwhere_template.inl +++ b/src/cunumeric/search/argwhere_template.inl @@ -52,7 +52,7 @@ struct ArgWhereImpl { template static void argwhere_template(TaskContext& context) { - ArgWhereArgs args{context.outputs()[0], context.inputs()[0]}; + ArgWhereArgs args{context.output(0), context.input(0)}; double_dispatch(args.in.dim(), args.in.code(), ArgWhereImpl{}, args); } diff --git a/src/cunumeric/search/nonzero.cc b/src/cunumeric/search/nonzero.cc index 5e2da51133..71f58d7be5 100644 --- a/src/cunumeric/search/nonzero.cc +++ b/src/cunumeric/search/nonzero.cc @@ -53,7 +53,7 @@ struct NonzeroImplBody { } }; -/*static*/ void NonzeroTask::cpu_variant(TaskContext& context) +/*static*/ void NonzeroTask::cpu_variant(TaskContext context) { nonzero_template(context); } diff --git a/src/cunumeric/search/nonzero.cu b/src/cunumeric/search/nonzero.cu index 38cfa8480f..4a674b3d93 100644 --- a/src/cunumeric/search/nonzero.cu +++ b/src/cunumeric/search/nonzero.cu @@ -80,7 +80,7 @@ struct NonzeroImplBody { } }; -/*static*/ void NonzeroTask::gpu_variant(TaskContext& context) +/*static*/ void NonzeroTask::gpu_variant(TaskContext context) { nonzero_template(context); } diff --git a/src/cunumeric/search/nonzero.h b/src/cunumeric/search/nonzero.h index c9f148d8ba..c756e96217 100644 --- a/src/cunumeric/search/nonzero.h +++ b/src/cunumeric/search/nonzero.h @@ -21,8 +21,8 @@ namespace cunumeric { struct NonzeroArgs { - const legate::Store& input; - std::vector& results; + legate::Store input; + std::vector results; }; class NonzeroTask : public CuNumericTask { @@ -30,12 +30,12 @@ class NonzeroTask : public CuNumericTask { static const int TASK_ID = CUNUMERIC_NONZERO; public: - static void cpu_variant(legate::TaskContext& context); + static void cpu_variant(legate::TaskContext context); #ifdef LEGATE_USE_OPENMP - static void omp_variant(legate::TaskContext& context); + static void omp_variant(legate::TaskContext context); #endif #ifdef LEGATE_USE_CUDA - static void gpu_variant(legate::TaskContext& context); + static void gpu_variant(legate::TaskContext context); #endif }; diff --git a/src/cunumeric/search/nonzero_omp.cc b/src/cunumeric/search/nonzero_omp.cc index e07fb51705..b38a274628 100644 --- a/src/cunumeric/search/nonzero_omp.cc +++ b/src/cunumeric/search/nonzero_omp.cc @@ -77,7 +77,7 @@ struct NonzeroImplBody { } }; -/*static*/ void NonzeroTask::omp_variant(TaskContext& context) +/*static*/ void NonzeroTask::omp_variant(TaskContext context) { nonzero_template(context); } diff --git a/src/cunumeric/search/nonzero_template.inl b/src/cunumeric/search/nonzero_template.inl index fb99355359..ab6332016b 100644 --- a/src/cunumeric/search/nonzero_template.inl +++ b/src/cunumeric/search/nonzero_template.inl @@ -52,7 +52,9 @@ struct NonzeroImpl { template static void nonzero_template(TaskContext& context) { - NonzeroArgs args{context.inputs()[0], context.outputs()}; + std::vector outputs; + for (auto& output : context.outputs()) { outputs.emplace_back(output); } + NonzeroArgs args{context.input(0), std::move(outputs)}; double_dispatch(args.input.dim(), args.input.code(), NonzeroImpl{}, args); } diff --git a/src/cunumeric/set/unique.cc b/src/cunumeric/set/unique.cc index 7aa09d0e51..482686d683 100644 --- a/src/cunumeric/set/unique.cc +++ b/src/cunumeric/set/unique.cc @@ -25,7 +25,7 @@ template struct UniqueImplBody { using VAL = legate_type_of; - void operator()(Array& output, + void operator()(legate::Store& output, const AccessorRO& in, const Pitches& pitches, const Rect& rect, @@ -47,7 +47,7 @@ struct UniqueImplBody { } }; -/*static*/ void UniqueTask::cpu_variant(TaskContext& context) +/*static*/ void UniqueTask::cpu_variant(TaskContext context) { unique_template(context); } diff --git a/src/cunumeric/set/unique.cu b/src/cunumeric/set/unique.cu index 302077c5f9..0ee43d9240 100644 --- a/src/cunumeric/set/unique.cu +++ b/src/cunumeric/set/unique.cu @@ -48,7 +48,7 @@ using Piece = std::pair, size_t>; auto get_aligned_size = [](auto size) { return std::max(16, (size + 15) / 16 * 16); }; template -static Piece tree_reduce(Array& output, +static Piece tree_reduce(legate::Store& output, Piece my_piece, size_t my_id, size_t num_ranks, @@ -143,7 +143,7 @@ template struct UniqueImplBody { using VAL = legate_type_of; - void operator()(Array& output, + void operator()(legate::Store& output, const AccessorRO& in, const Pitches& pitches, const Rect& rect, @@ -197,7 +197,7 @@ struct UniqueImplBody { } }; -/*static*/ void UniqueTask::gpu_variant(TaskContext& context) +/*static*/ void UniqueTask::gpu_variant(TaskContext context) { unique_template(context); } diff --git a/src/cunumeric/set/unique.h b/src/cunumeric/set/unique.h index ebbe7a8096..b63d69c623 100644 --- a/src/cunumeric/set/unique.h +++ b/src/cunumeric/set/unique.h @@ -25,12 +25,12 @@ class UniqueTask : public CuNumericTask { static const int TASK_ID = CUNUMERIC_UNIQUE; public: - static void cpu_variant(legate::TaskContext& context); + static void cpu_variant(legate::TaskContext context); #ifdef LEGATE_USE_OPENMP - static void omp_variant(legate::TaskContext& context); + static void omp_variant(legate::TaskContext context); #endif #ifdef LEGATE_USE_CUDA - static void gpu_variant(legate::TaskContext& context); + static void gpu_variant(legate::TaskContext context); #endif }; diff --git a/src/cunumeric/set/unique_omp.cc b/src/cunumeric/set/unique_omp.cc index 37a86582b7..810ff1b6fc 100644 --- a/src/cunumeric/set/unique_omp.cc +++ b/src/cunumeric/set/unique_omp.cc @@ -27,7 +27,7 @@ template struct UniqueImplBody { using VAL = legate_type_of; - void operator()(Array& output, + void operator()(legate::Store& output, const AccessorRO& in, const Pitches& pitches, const Rect& rect, @@ -72,7 +72,7 @@ struct UniqueImplBody { } }; -/*static*/ void UniqueTask::omp_variant(TaskContext& context) +/*static*/ void UniqueTask::omp_variant(TaskContext context) { unique_template(context); } diff --git a/src/cunumeric/set/unique_reduce.cc b/src/cunumeric/set/unique_reduce.cc index 5be7f7160f..6f126c2258 100644 --- a/src/cunumeric/set/unique_reduce.cc +++ b/src/cunumeric/set/unique_reduce.cc @@ -19,7 +19,7 @@ namespace cunumeric { -/*static*/ void UniqueReduceTask::cpu_variant(TaskContext& context) +/*static*/ void UniqueReduceTask::cpu_variant(TaskContext context) { unique_reduce_template(context, thrust::host); } diff --git a/src/cunumeric/set/unique_reduce.h b/src/cunumeric/set/unique_reduce.h index dacef61eec..f26c2c9349 100644 --- a/src/cunumeric/set/unique_reduce.h +++ b/src/cunumeric/set/unique_reduce.h @@ -25,9 +25,9 @@ class UniqueReduceTask : public CuNumericTask { static const int TASK_ID = CUNUMERIC_UNIQUE_REDUCE; public: - static void cpu_variant(legate::TaskContext& context); + static void cpu_variant(legate::TaskContext context); #ifdef LEGATE_USE_OPENMP - static void omp_variant(legate::TaskContext& context); + static void omp_variant(legate::TaskContext context); #endif }; diff --git a/src/cunumeric/set/unique_reduce_omp.cc b/src/cunumeric/set/unique_reduce_omp.cc index 825b93379a..63f3c135d9 100644 --- a/src/cunumeric/set/unique_reduce_omp.cc +++ b/src/cunumeric/set/unique_reduce_omp.cc @@ -21,7 +21,7 @@ namespace cunumeric { -/*static*/ void UniqueReduceTask::omp_variant(TaskContext& context) +/*static*/ void UniqueReduceTask::omp_variant(TaskContext context) { unique_reduce_template(context, thrust::omp::par); } diff --git a/src/cunumeric/set/unique_reduce_template.inl b/src/cunumeric/set/unique_reduce_template.inl index c38d289ebd..2db43c0044 100644 --- a/src/cunumeric/set/unique_reduce_template.inl +++ b/src/cunumeric/set/unique_reduce_template.inl @@ -32,7 +32,9 @@ using namespace legate; template struct UniqueReduceImpl { template - void operator()(Array& output, std::vector& input_arrs, const exe_pol_t& exe_pol) + void operator()(legate::Store output, + const std::vector& input_arrs, + exe_pol_t exe_pol) { using VAL = legate_type_of; @@ -49,7 +51,7 @@ struct UniqueReduceImpl { size_t strides[1]; Rect<1> shape = input_arr.shape<1>(); size_t volume = shape.volume(); - const VAL* in_ptr = input_arr.read_accessor(shape).ptr(shape, strides); + const VAL* in_ptr = input_arr.data().read_accessor(shape).ptr(shape, strides); assert(shape.volume() <= 1 || strides[0] == 1); thrust::copy(exe_pol, in_ptr, in_ptr + volume, res_ptr + offset); offset += volume; @@ -65,9 +67,9 @@ struct UniqueReduceImpl { template static void unique_reduce_template(TaskContext& context, const exe_pol_t& exe_pol) { - auto& inputs = context.inputs(); - auto& output = context.outputs()[0]; - type_dispatch(output.code(), UniqueReduceImpl{}, output, inputs, exe_pol); + auto inputs = context.inputs(); + auto output = context.output(0); + type_dispatch(output.type().code(), UniqueReduceImpl{}, output, inputs, exe_pol); } } // namespace cunumeric diff --git a/src/cunumeric/set/unique_template.inl b/src/cunumeric/set/unique_template.inl index 1ab1a7e1f4..a1530a3a2c 100644 --- a/src/cunumeric/set/unique_template.inl +++ b/src/cunumeric/set/unique_template.inl @@ -30,8 +30,8 @@ struct UniqueImplBody; template struct UniqueImpl { template - void operator()(Array& output, - Array& input, + void operator()(legate::Store output, + legate::Store input, std::vector& comms, const DomainPoint& point, const Domain& launch_domain) const @@ -51,11 +51,11 @@ struct UniqueImpl { template static void unique_template(TaskContext& context) { - auto& input = context.inputs()[0]; - auto& output = context.outputs()[0]; - auto& comms = context.communicators(); + auto input = context.input(0); + auto output = context.output(0); + auto comms = context.communicators(); double_dispatch(input.dim(), - input.code(), + input.type().code(), UniqueImpl{}, output, input, diff --git a/src/cunumeric/sort/searchsorted.cc b/src/cunumeric/sort/searchsorted.cc index 65486b3d96..4e541c4900 100644 --- a/src/cunumeric/sort/searchsorted.cc +++ b/src/cunumeric/sort/searchsorted.cc @@ -68,7 +68,7 @@ struct SearchSortedImplBody { } }; -/*static*/ void SearchSortedTask::cpu_variant(TaskContext& context) +/*static*/ void SearchSortedTask::cpu_variant(TaskContext context) { searchsorted_template(context); } diff --git a/src/cunumeric/sort/searchsorted.cu b/src/cunumeric/sort/searchsorted.cu index 05cc46483f..e69eded75a 100644 --- a/src/cunumeric/sort/searchsorted.cu +++ b/src/cunumeric/sort/searchsorted.cu @@ -102,7 +102,7 @@ struct SearchSortedImplBody { } }; -/*static*/ void SearchSortedTask::gpu_variant(TaskContext& context) +/*static*/ void SearchSortedTask::gpu_variant(TaskContext context) { searchsorted_template(context); } diff --git a/src/cunumeric/sort/searchsorted.h b/src/cunumeric/sort/searchsorted.h index df5e9cc661..2719ec62b6 100644 --- a/src/cunumeric/sort/searchsorted.h +++ b/src/cunumeric/sort/searchsorted.h @@ -21,9 +21,9 @@ namespace cunumeric { struct SearchSortedArgs { - const legate::Store& input_base; - const legate::Store& input_values; - const legate::Store& output_reduction; + legate::Store input_base; + legate::Store input_values; + legate::Store output_reduction; bool left; int64_t global_volume; bool is_index_space; @@ -34,12 +34,12 @@ class SearchSortedTask : public CuNumericTask { static const int TASK_ID = CUNUMERIC_SEARCHSORTED; public: - static void cpu_variant(legate::TaskContext& context); + static void cpu_variant(legate::TaskContext context); #ifdef LEGATE_USE_OPENMP - static void omp_variant(legate::TaskContext& context); + static void omp_variant(legate::TaskContext context); #endif #ifdef LEGATE_USE_CUDA - static void gpu_variant(legate::TaskContext& context); + static void gpu_variant(legate::TaskContext context); #endif }; diff --git a/src/cunumeric/sort/searchsorted_omp.cc b/src/cunumeric/sort/searchsorted_omp.cc index 76d978349d..408082948e 100644 --- a/src/cunumeric/sort/searchsorted_omp.cc +++ b/src/cunumeric/sort/searchsorted_omp.cc @@ -72,7 +72,7 @@ struct SearchSortedImplBody { } }; -/*static*/ void SearchSortedTask::omp_variant(TaskContext& context) +/*static*/ void SearchSortedTask::omp_variant(TaskContext context) { searchsorted_template(context); } diff --git a/src/cunumeric/sort/searchsorted_template.inl b/src/cunumeric/sort/searchsorted_template.inl index 8ccd0661f2..118cd4d833 100644 --- a/src/cunumeric/sort/searchsorted_template.inl +++ b/src/cunumeric/sort/searchsorted_template.inl @@ -65,11 +65,11 @@ struct SearchSortedImpl { template static void searchsorted_template(TaskContext& context) { - SearchSortedArgs args{context.inputs()[0], - context.inputs()[1], - context.reductions()[0], - context.scalars()[0].value(), - context.scalars()[1].value(), + SearchSortedArgs args{context.input(0), + context.input(1), + context.reduction(0), + context.scalar(0).value(), + context.scalar(1).value(), !context.is_single_task()}; assert(args.input_base.dim() == 1); diff --git a/src/cunumeric/sort/sort.cc b/src/cunumeric/sort/sort.cc index 222460b162..c8b3085e3f 100644 --- a/src/cunumeric/sort/sort.cc +++ b/src/cunumeric/sort/sort.cc @@ -65,7 +65,7 @@ struct SortImplBody { } }; -/*static*/ void SortTask::cpu_variant(TaskContext& context) +/*static*/ void SortTask::cpu_variant(TaskContext context) { sort_template(context); } diff --git a/src/cunumeric/sort/sort.cu b/src/cunumeric/sort/sort.cu index eada9ede36..3f75edeb33 100644 --- a/src/cunumeric/sort/sort.cu +++ b/src/cunumeric/sort/sort.cu @@ -1188,23 +1188,24 @@ void rebalance_data(SegmentMergePiece& merge_buffer, ///////////////////////////////////////////////////////////////////////////////////////////////// template -void sample_sort_nccl_nd(SortPiece> local_sorted, - Array& output_array_unbound, // only for unbound usage when !rebalance - void* output_ptr, - /* global domain information */ - size_t my_rank, // global NCCL rank - size_t num_ranks, - size_t segment_size_g, - /* domain information in sort dimension */ - size_t my_sort_rank, // local rank id in sort dimension - size_t num_sort_ranks, // #ranks that share a sort dimension - size_t* sort_ranks, // rank ids that share a sort dimension with us - size_t segment_size_l, // (local) segment size - /* other */ - bool rebalance, - bool argsort, - cudaStream_t stream, - ncclComm_t* comm) +void sample_sort_nccl_nd( + SortPiece> local_sorted, + legate::Store& output_array_unbound, // only for unbound usage when !rebalance + void* output_ptr, + /* global domain information */ + size_t my_rank, // global NCCL rank + size_t num_ranks, + size_t segment_size_g, + /* domain information in sort dimension */ + size_t my_sort_rank, // local rank id in sort dimension + size_t num_sort_ranks, // #ranks that share a sort dimension + size_t* sort_ranks, // rank ids that share a sort dimension with us + size_t segment_size_l, // (local) segment size + /* other */ + bool rebalance, + bool argsort, + cudaStream_t stream, + ncclComm_t* comm) { using VAL = legate_type_of; @@ -1806,7 +1807,7 @@ struct SortImplBody { } }; -/*static*/ void SortTask::gpu_variant(TaskContext& context) +/*static*/ void SortTask::gpu_variant(TaskContext context) { sort_template(context); } diff --git a/src/cunumeric/sort/sort.h b/src/cunumeric/sort/sort.h index 59a7fdf695..32cbb4417f 100644 --- a/src/cunumeric/sort/sort.h +++ b/src/cunumeric/sort/sort.h @@ -23,8 +23,8 @@ namespace cunumeric { struct SortArgs { - const legate::Store& input; - legate::Store& output; + legate::Store input; + legate::Store output; bool argsort; bool stable; size_t segment_size_g; @@ -95,12 +95,12 @@ class SortTask : public CuNumericTask { static const int TASK_ID = CUNUMERIC_SORT; public: - static void cpu_variant(legate::TaskContext& context); + static void cpu_variant(legate::TaskContext context); #ifdef LEGATE_USE_OPENMP - static void omp_variant(legate::TaskContext& context); + static void omp_variant(legate::TaskContext context); #endif #ifdef LEGATE_USE_CUDA - static void gpu_variant(legate::TaskContext& context); + static void gpu_variant(legate::TaskContext context); #endif }; diff --git a/src/cunumeric/sort/sort_cpu.inl b/src/cunumeric/sort/sort_cpu.inl index f4889eee4f..31dd848463 100644 --- a/src/cunumeric/sort/sort_cpu.inl +++ b/src/cunumeric/sort/sort_cpu.inl @@ -443,7 +443,7 @@ void rebalance_data(SegmentMergePiece& merge_buffer, template void sample_sort_nd(SortPiece> local_sorted, - legate::Store& output_array_unbound, // only for unbound usage when !rebalance + legate::Store output_array_unbound, // only for unbound usage when !rebalance void* output_ptr, /* global domain information */ size_t my_rank, // global rank @@ -899,8 +899,8 @@ struct SortImplBodyCpu { using VAL = legate_type_of; template - void operator()(const legate::Store& input_array, - legate::Store& output_array, + void operator()(legate::Store input_array, + legate::Store output_array, const Pitches& pitches, const Rect& rect, const size_t volume, diff --git a/src/cunumeric/sort/sort_omp.cc b/src/cunumeric/sort/sort_omp.cc index d0c20210b4..0d06bdc165 100644 --- a/src/cunumeric/sort/sort_omp.cc +++ b/src/cunumeric/sort/sort_omp.cc @@ -66,7 +66,7 @@ struct SortImplBody { } }; -/*static*/ void SortTask::omp_variant(TaskContext& context) +/*static*/ void SortTask::omp_variant(TaskContext context) { sort_template(context); } diff --git a/src/cunumeric/sort/sort_template.inl b/src/cunumeric/sort/sort_template.inl index 0a4d1c16b3..abcec8621b 100644 --- a/src/cunumeric/sort/sort_template.inl +++ b/src/cunumeric/sort/sort_template.inl @@ -42,7 +42,7 @@ static int get_rank(Domain domain, DomainPoint index_point) template struct SortImpl { template - void operator()(SortArgs& args, std::vector& comms) const + void operator()(SortArgs& args, std::vector comms) const { using VAL = legate_type_of; @@ -86,17 +86,17 @@ struct SortImpl { template static void sort_template(TaskContext& context) { - auto shape_span = context.scalars()[1].values(); + auto shape_span = context.scalar(1).values(); size_t segment_size_g = shape_span[shape_span.size() - 1]; auto domain = context.get_launch_domain(); size_t local_rank = get_rank(domain, context.get_task_index()); size_t num_ranks = domain.get_volume(); size_t num_sort_ranks = domain.hi()[domain.get_dim() - 1] - domain.lo()[domain.get_dim() - 1] + 1; - SortArgs args{context.inputs()[0], - context.outputs()[0], - context.scalars()[0].value(), // argsort - context.scalars()[2].value(), // stable + SortArgs args{context.input(0), + context.output(0), + context.scalar(0).value(), // argsort + context.scalar(2).value(), // stable segment_size_g, !context.is_single_task(), local_rank, diff --git a/src/cunumeric/stat/bincount.cc b/src/cunumeric/stat/bincount.cc index d4806cbabc..ba7ba80229 100644 --- a/src/cunumeric/stat/bincount.cc +++ b/src/cunumeric/stat/bincount.cc @@ -51,7 +51,7 @@ struct BincountImplBody { } }; -/*static*/ void BincountTask::cpu_variant(TaskContext& context) +/*static*/ void BincountTask::cpu_variant(TaskContext context) { bincount_template(context); } diff --git a/src/cunumeric/stat/bincount.cu b/src/cunumeric/stat/bincount.cu index 27099536b3..e0700652c2 100644 --- a/src/cunumeric/stat/bincount.cu +++ b/src/cunumeric/stat/bincount.cu @@ -206,7 +206,7 @@ struct BincountImplBody { } }; -/*static*/ void BincountTask::gpu_variant(TaskContext& context) +/*static*/ void BincountTask::gpu_variant(TaskContext context) { bincount_template(context); } diff --git a/src/cunumeric/stat/bincount.h b/src/cunumeric/stat/bincount.h index 2df34ea956..dad6baa6f0 100644 --- a/src/cunumeric/stat/bincount.h +++ b/src/cunumeric/stat/bincount.h @@ -21,9 +21,9 @@ namespace cunumeric { struct BincountArgs { - const legate::Store& lhs; - const legate::Store& rhs; - const legate::Store& weights; + legate::Store lhs; + legate::Store rhs; + legate::Store weights; }; class BincountTask : public CuNumericTask { @@ -31,12 +31,12 @@ class BincountTask : public CuNumericTask { static const int TASK_ID = CUNUMERIC_BINCOUNT; public: - static void cpu_variant(legate::TaskContext& context); + static void cpu_variant(legate::TaskContext context); #ifdef LEGATE_USE_OPENMP - static void omp_variant(legate::TaskContext& context); + static void omp_variant(legate::TaskContext context); #endif #ifdef LEGATE_USE_CUDA - static void gpu_variant(legate::TaskContext& context); + static void gpu_variant(legate::TaskContext context); #endif }; diff --git a/src/cunumeric/stat/bincount_omp.cc b/src/cunumeric/stat/bincount_omp.cc index 4f21e95a87..651b6f0e41 100644 --- a/src/cunumeric/stat/bincount_omp.cc +++ b/src/cunumeric/stat/bincount_omp.cc @@ -102,7 +102,7 @@ struct BincountImplBody { } }; -/*static*/ void BincountTask::omp_variant(TaskContext& context) +/*static*/ void BincountTask::omp_variant(TaskContext context) { bincount_template(context); } diff --git a/src/cunumeric/stat/bincount_template.inl b/src/cunumeric/stat/bincount_template.inl index 83ae638e15..aead962cea 100644 --- a/src/cunumeric/stat/bincount_template.inl +++ b/src/cunumeric/stat/bincount_template.inl @@ -60,8 +60,8 @@ struct BincountImpl { template static void bincount_template(TaskContext& context) { - auto& inputs = context.inputs(); - auto& reductions = context.reductions(); + auto inputs = context.inputs(); + auto reductions = context.reductions(); BincountArgs args{reductions[0], inputs[0], inputs[1]}; type_dispatch(args.rhs.code(), BincountImpl{}, args); } diff --git a/src/cunumeric/stat/histogram.cc b/src/cunumeric/stat/histogram.cc index 7c0caa6b7e..e07e9844b8 100644 --- a/src/cunumeric/stat/histogram.cc +++ b/src/cunumeric/stat/histogram.cc @@ -62,7 +62,7 @@ struct HistogramImplBody { } }; -/*static*/ void HistogramTask::cpu_variant(TaskContext& context) +/*static*/ void HistogramTask::cpu_variant(TaskContext context) { histogram_template(context); } diff --git a/src/cunumeric/stat/histogram.cu b/src/cunumeric/stat/histogram.cu index f43fe84d64..3c8c5d0818 100644 --- a/src/cunumeric/stat/histogram.cu +++ b/src/cunumeric/stat/histogram.cu @@ -69,7 +69,7 @@ struct HistogramImplBody { } }; -/*static*/ void HistogramTask::gpu_variant(TaskContext& context) +/*static*/ void HistogramTask::gpu_variant(TaskContext context) { histogram_template(context); } diff --git a/src/cunumeric/stat/histogram.h b/src/cunumeric/stat/histogram.h index 217a79e8c4..45d3cd9557 100644 --- a/src/cunumeric/stat/histogram.h +++ b/src/cunumeric/stat/histogram.h @@ -16,15 +16,15 @@ #pragma once -#include "cunumeric/cunumeric.h" +#include "cunumeric/cunumeric_task.h" namespace cunumeric { struct HistogramArgs { - const Array& result; - const Array& src; - const Array& bins; - const Array& weights; + legate::Store result; + legate::Store src; + legate::Store bins; + legate::Store weights; }; class HistogramTask : public CuNumericTask { @@ -32,12 +32,12 @@ class HistogramTask : public CuNumericTask { static const int TASK_ID = CUNUMERIC_HISTOGRAM; public: - static void cpu_variant(legate::TaskContext& context); + static void cpu_variant(legate::TaskContext context); #ifdef LEGATE_USE_OPENMP - static void omp_variant(legate::TaskContext& context); + static void omp_variant(legate::TaskContext context); #endif #ifdef LEGATE_USE_CUDA - static void gpu_variant(legate::TaskContext& context); + static void gpu_variant(legate::TaskContext context); #endif }; diff --git a/src/cunumeric/stat/histogram_omp.cc b/src/cunumeric/stat/histogram_omp.cc index b68d7f81ea..96b92b28f1 100644 --- a/src/cunumeric/stat/histogram_omp.cc +++ b/src/cunumeric/stat/histogram_omp.cc @@ -113,7 +113,7 @@ struct HistogramImplBody { } }; -/*static*/ void HistogramTask::omp_variant(TaskContext& context) +/*static*/ void HistogramTask::omp_variant(TaskContext context) { histogram_template(context); } diff --git a/src/cunumeric/stat/histogram_template.inl b/src/cunumeric/stat/histogram_template.inl index d7ed940bc5..298be82f24 100644 --- a/src/cunumeric/stat/histogram_template.inl +++ b/src/cunumeric/stat/histogram_template.inl @@ -67,8 +67,8 @@ struct HistogramImpl { template static void histogram_template(TaskContext& context) { - auto& inputs = context.inputs(); - auto& reductions = context.reductions(); + auto inputs = context.inputs(); + auto reductions = context.reductions(); HistogramArgs args{reductions[0], inputs[0], inputs[1], inputs[2]}; type_dispatch(args.src.code(), HistogramImpl{}, args); } diff --git a/src/cunumeric/ternary/where.cc b/src/cunumeric/ternary/where.cc index 85c602522d..9705233015 100644 --- a/src/cunumeric/ternary/where.cc +++ b/src/cunumeric/ternary/where.cc @@ -51,7 +51,7 @@ struct WhereImplBody { } }; -/*static*/ void WhereTask::cpu_variant(TaskContext& context) +/*static*/ void WhereTask::cpu_variant(TaskContext context) { where_template(context); } diff --git a/src/cunumeric/ternary/where.cu b/src/cunumeric/ternary/where.cu index a9dfdb1a3c..359b61b937 100644 --- a/src/cunumeric/ternary/where.cu +++ b/src/cunumeric/ternary/where.cu @@ -71,7 +71,7 @@ struct WhereImplBody { } }; -/*static*/ void WhereTask::gpu_variant(TaskContext& context) +/*static*/ void WhereTask::gpu_variant(TaskContext context) { where_template(context); } diff --git a/src/cunumeric/ternary/where.h b/src/cunumeric/ternary/where.h index 97b6f6e184..3b307bd91e 100644 --- a/src/cunumeric/ternary/where.h +++ b/src/cunumeric/ternary/where.h @@ -21,10 +21,10 @@ namespace cunumeric { struct WhereArgs { - const legate::Store& out; - const legate::Store& mask; - const legate::Store& in1; - const legate::Store& in2; + legate::Store out; + legate::Store mask; + legate::Store in1; + legate::Store in2; }; class WhereTask : public CuNumericTask { @@ -32,12 +32,12 @@ class WhereTask : public CuNumericTask { static const int TASK_ID = CUNUMERIC_WHERE; public: - static void cpu_variant(legate::TaskContext& context); + static void cpu_variant(legate::TaskContext context); #ifdef LEGATE_USE_OPENMP - static void omp_variant(legate::TaskContext& context); + static void omp_variant(legate::TaskContext context); #endif #ifdef LEGATE_USE_CUDA - static void gpu_variant(legate::TaskContext& context); + static void gpu_variant(legate::TaskContext context); #endif }; diff --git a/src/cunumeric/ternary/where_omp.cc b/src/cunumeric/ternary/where_omp.cc index dd0ed7e55c..a8cc7003f0 100644 --- a/src/cunumeric/ternary/where_omp.cc +++ b/src/cunumeric/ternary/where_omp.cc @@ -53,7 +53,7 @@ struct WhereImplBody { } }; -/*static*/ void WhereTask::omp_variant(TaskContext& context) +/*static*/ void WhereTask::omp_variant(TaskContext context) { where_template(context); } diff --git a/src/cunumeric/ternary/where_template.inl b/src/cunumeric/ternary/where_template.inl index ccdc78b5aa..6735a1f56d 100644 --- a/src/cunumeric/ternary/where_template.inl +++ b/src/cunumeric/ternary/where_template.inl @@ -62,8 +62,8 @@ struct WhereImpl { template static void where_template(TaskContext& context) { - auto& inputs = context.inputs(); - WhereArgs args{context.outputs()[0], inputs[0], inputs[1], inputs[2]}; + auto inputs = context.inputs(); + WhereArgs args{context.output(0), inputs[0], inputs[1], inputs[2]}; auto dim = std::max(1, args.out.dim()); double_dispatch(dim, args.out.code(), WhereImpl{}, args); } diff --git a/src/cunumeric/transform/flip.cc b/src/cunumeric/transform/flip.cc index 3aa332d57e..57ffcd4362 100644 --- a/src/cunumeric/transform/flip.cc +++ b/src/cunumeric/transform/flip.cc @@ -41,7 +41,7 @@ struct FlipImplBody { } }; -/*static*/ void FlipTask::cpu_variant(TaskContext& context) +/*static*/ void FlipTask::cpu_variant(TaskContext context) { flip_template(context); } diff --git a/src/cunumeric/transform/flip.cu b/src/cunumeric/transform/flip.cu index 8c6dc166b6..18fce618ff 100644 --- a/src/cunumeric/transform/flip.cu +++ b/src/cunumeric/transform/flip.cu @@ -64,7 +64,7 @@ struct FlipImplBody { } }; -/*static*/ void FlipTask::gpu_variant(TaskContext& context) +/*static*/ void FlipTask::gpu_variant(TaskContext context) { flip_template(context); } diff --git a/src/cunumeric/transform/flip.h b/src/cunumeric/transform/flip.h index 360ea48567..6e0ccdc12a 100644 --- a/src/cunumeric/transform/flip.h +++ b/src/cunumeric/transform/flip.h @@ -21,8 +21,8 @@ namespace cunumeric { struct FlipArgs { - const legate::Store& in; - const legate::Store& out; + legate::Store in; + legate::Store out; legate::Span axes; }; @@ -31,12 +31,12 @@ class FlipTask : public CuNumericTask { static const int TASK_ID = CUNUMERIC_FLIP; public: - static void cpu_variant(legate::TaskContext& context); + static void cpu_variant(legate::TaskContext context); #ifdef LEGATE_USE_OPENMP - static void omp_variant(legate::TaskContext& context); + static void omp_variant(legate::TaskContext context); #endif #ifdef LEGATE_USE_CUDA - static void gpu_variant(legate::TaskContext& context); + static void gpu_variant(legate::TaskContext context); #endif }; diff --git a/src/cunumeric/transform/flip_omp.cc b/src/cunumeric/transform/flip_omp.cc index 775fd6802e..f475666f39 100644 --- a/src/cunumeric/transform/flip_omp.cc +++ b/src/cunumeric/transform/flip_omp.cc @@ -44,7 +44,7 @@ struct FlipImplBody { } }; -/*static*/ void FlipTask::omp_variant(TaskContext& context) +/*static*/ void FlipTask::omp_variant(TaskContext context) { flip_template(context); } diff --git a/src/cunumeric/transform/flip_template.inl b/src/cunumeric/transform/flip_template.inl index 6af541fc64..eca2782a4c 100644 --- a/src/cunumeric/transform/flip_template.inl +++ b/src/cunumeric/transform/flip_template.inl @@ -51,8 +51,8 @@ struct FlipImpl { template static void flip_template(TaskContext& context) { - auto& inputs = context.inputs(); - auto& outputs = context.outputs(); + auto inputs = context.inputs(); + auto outputs = context.outputs(); auto& scalars = context.scalars(); FlipArgs args{inputs[0], outputs[0], scalars[0].values()}; diff --git a/src/cunumeric/unary/convert.cc b/src/cunumeric/unary/convert.cc index a3fae7fbb2..7165116267 100644 --- a/src/cunumeric/unary/convert.cc +++ b/src/cunumeric/unary/convert.cc @@ -48,7 +48,7 @@ struct ConvertImplBody { } }; -/*static*/ void ConvertTask::cpu_variant(TaskContext& context) +/*static*/ void ConvertTask::cpu_variant(TaskContext context) { convert_template(context); } diff --git a/src/cunumeric/unary/convert.cu b/src/cunumeric/unary/convert.cu index ea1d7cfb1a..6810c0cdc5 100644 --- a/src/cunumeric/unary/convert.cu +++ b/src/cunumeric/unary/convert.cu @@ -68,7 +68,7 @@ struct ConvertImplBody { } }; -/*static*/ void ConvertTask::gpu_variant(TaskContext& context) +/*static*/ void ConvertTask::gpu_variant(TaskContext context) { convert_template(context); } diff --git a/src/cunumeric/unary/convert.h b/src/cunumeric/unary/convert.h index 4d3472008b..891a130826 100644 --- a/src/cunumeric/unary/convert.h +++ b/src/cunumeric/unary/convert.h @@ -22,8 +22,8 @@ namespace cunumeric { struct ConvertArgs { - const legate::Store& out; - const legate::Store& in; + legate::Store out; + legate::Store in; ConvertCode nan_op; }; @@ -32,12 +32,12 @@ class ConvertTask : public CuNumericTask { static const int TASK_ID = CUNUMERIC_CONVERT; public: - static void cpu_variant(legate::TaskContext& context); + static void cpu_variant(legate::TaskContext context); #ifdef LEGATE_USE_OPENMP - static void omp_variant(legate::TaskContext& context); + static void omp_variant(legate::TaskContext context); #endif #ifdef LEGATE_USE_CUDA - static void gpu_variant(legate::TaskContext& context); + static void gpu_variant(legate::TaskContext context); #endif }; diff --git a/src/cunumeric/unary/convert_omp.cc b/src/cunumeric/unary/convert_omp.cc index de2f204788..911c7721a2 100644 --- a/src/cunumeric/unary/convert_omp.cc +++ b/src/cunumeric/unary/convert_omp.cc @@ -50,7 +50,7 @@ struct ConvertImplBody { } }; -/*static*/ void ConvertTask::omp_variant(TaskContext& context) +/*static*/ void ConvertTask::omp_variant(TaskContext context) { convert_template(context); } diff --git a/src/cunumeric/unary/convert_template.inl b/src/cunumeric/unary/convert_template.inl index 8d507d35f3..be4e4c4783 100644 --- a/src/cunumeric/unary/convert_template.inl +++ b/src/cunumeric/unary/convert_template.inl @@ -100,8 +100,7 @@ struct SourceTypeDispatch { template static void convert_template(TaskContext& context) { - ConvertArgs args{ - context.outputs()[0], context.inputs()[0], context.scalars()[0].value()}; + ConvertArgs args{context.output(0), context.input(0), context.scalar(0).value()}; type_dispatch(args.in.code(), SourceTypeDispatch{}, args); } diff --git a/src/cunumeric/unary/scalar_unary_red.cc b/src/cunumeric/unary/scalar_unary_red.cc index 77f746eb44..44979a9641 100644 --- a/src/cunumeric/unary/scalar_unary_red.cc +++ b/src/cunumeric/unary/scalar_unary_red.cc @@ -19,7 +19,7 @@ namespace cunumeric { -/*static*/ void ScalarUnaryRedTask::cpu_variant(TaskContext& context) +/*static*/ void ScalarUnaryRedTask::cpu_variant(TaskContext context) { scalar_unary_red_template(context); } diff --git a/src/cunumeric/unary/scalar_unary_red.cu b/src/cunumeric/unary/scalar_unary_red.cu index d25b63bb12..9e0280b341 100644 --- a/src/cunumeric/unary/scalar_unary_red.cu +++ b/src/cunumeric/unary/scalar_unary_red.cu @@ -23,7 +23,7 @@ namespace cunumeric { using namespace legate; -/*static*/ void ScalarUnaryRedTask::gpu_variant(TaskContext& context) +/*static*/ void ScalarUnaryRedTask::gpu_variant(TaskContext context) { scalar_unary_red_template(context); } diff --git a/src/cunumeric/unary/scalar_unary_red.h b/src/cunumeric/unary/scalar_unary_red.h index 6b6980671f..495473a6cd 100644 --- a/src/cunumeric/unary/scalar_unary_red.h +++ b/src/cunumeric/unary/scalar_unary_red.h @@ -22,8 +22,8 @@ namespace cunumeric { struct ScalarUnaryRedArgs { - const legate::Store& out; - const legate::Store& in; + legate::Store out; + legate::Store in; UnaryRedCode op_code; legate::DomainPoint shape; std::vector args; @@ -35,12 +35,12 @@ class ScalarUnaryRedTask : public CuNumericTask { static const int TASK_ID = CUNUMERIC_SCALAR_UNARY_RED; public: - static void cpu_variant(legate::TaskContext& context); + static void cpu_variant(legate::TaskContext context); #ifdef LEGATE_USE_OPENMP - static void omp_variant(legate::TaskContext& context); + static void omp_variant(legate::TaskContext context); #endif #ifdef LEGATE_USE_CUDA - static void gpu_variant(legate::TaskContext& context); + static void gpu_variant(legate::TaskContext context); #endif }; diff --git a/src/cunumeric/unary/scalar_unary_red_omp.cc b/src/cunumeric/unary/scalar_unary_red_omp.cc index c335763552..3ddfaa7cab 100644 --- a/src/cunumeric/unary/scalar_unary_red_omp.cc +++ b/src/cunumeric/unary/scalar_unary_red_omp.cc @@ -20,7 +20,7 @@ namespace cunumeric { -/*static*/ void ScalarUnaryRedTask::omp_variant(TaskContext& context) +/*static*/ void ScalarUnaryRedTask::omp_variant(TaskContext context) { scalar_unary_red_template(context); } diff --git a/src/cunumeric/unary/scalar_unary_red_template.inl b/src/cunumeric/unary/scalar_unary_red_template.inl index 3c0044cf86..ac1634e8e0 100644 --- a/src/cunumeric/unary/scalar_unary_red_template.inl +++ b/src/cunumeric/unary/scalar_unary_red_template.inl @@ -142,7 +142,7 @@ struct ScalarUnaryRedDispatch { template static void scalar_unary_red_template(TaskContext& context) { - auto& inputs = context.inputs(); + auto inputs = context.inputs(); auto& scalars = context.scalars(); std::vector extra_args; @@ -155,8 +155,7 @@ static void scalar_unary_red_template(TaskContext& context) shape.dim = 1; shape[0] = 1; } - ScalarUnaryRedArgs args{ - context.reductions()[0], inputs[0], op_code, shape, std::move(extra_args)}; + ScalarUnaryRedArgs args{context.reduction(0), inputs[0], op_code, shape, std::move(extra_args)}; op_dispatch(args.op_code, ScalarUnaryRedDispatch{}, args); } diff --git a/src/cunumeric/unary/unary_op.cc b/src/cunumeric/unary/unary_op.cc index 53c085113e..da57b142b7 100644 --- a/src/cunumeric/unary/unary_op.cc +++ b/src/cunumeric/unary/unary_op.cc @@ -100,7 +100,7 @@ struct MultiOutUnaryOpImplBody { } }; -/*static*/ void UnaryOpTask::cpu_variant(TaskContext& context) +/*static*/ void UnaryOpTask::cpu_variant(TaskContext context) { unary_op_template(context); } diff --git a/src/cunumeric/unary/unary_op.cu b/src/cunumeric/unary/unary_op.cu index 41de2e20b4..81cc41bbfd 100644 --- a/src/cunumeric/unary/unary_op.cu +++ b/src/cunumeric/unary/unary_op.cu @@ -175,7 +175,7 @@ struct MultiOutUnaryOpImplBody { } }; -/*static*/ void UnaryOpTask::gpu_variant(TaskContext& context) +/*static*/ void UnaryOpTask::gpu_variant(TaskContext context) { unary_op_template(context); } diff --git a/src/cunumeric/unary/unary_op.h b/src/cunumeric/unary/unary_op.h index dababcaf64..57919a3175 100644 --- a/src/cunumeric/unary/unary_op.h +++ b/src/cunumeric/unary/unary_op.h @@ -22,16 +22,16 @@ namespace cunumeric { struct UnaryOpArgs { - const legate::Store& in; - const legate::Store& out; + legate::Store in; + legate::Store out; UnaryOpCode op_code; std::vector args; }; struct MultiOutUnaryOpArgs { - const legate::Store& in; - const legate::Store& out1; - const legate::Store& out2; + legate::Store in; + legate::Store out1; + legate::Store out2; UnaryOpCode op_code; }; @@ -40,12 +40,12 @@ class UnaryOpTask : public CuNumericTask { static const int TASK_ID = CUNUMERIC_UNARY_OP; public: - static void cpu_variant(legate::TaskContext& context); + static void cpu_variant(legate::TaskContext context); #ifdef LEGATE_USE_OPENMP - static void omp_variant(legate::TaskContext& context); + static void omp_variant(legate::TaskContext context); #endif #ifdef LEGATE_USE_CUDA - static void gpu_variant(legate::TaskContext& context); + static void gpu_variant(legate::TaskContext context); #endif }; diff --git a/src/cunumeric/unary/unary_op_omp.cc b/src/cunumeric/unary/unary_op_omp.cc index 1badb93a8e..225adaeb58 100644 --- a/src/cunumeric/unary/unary_op_omp.cc +++ b/src/cunumeric/unary/unary_op_omp.cc @@ -106,7 +106,7 @@ struct MultiOutUnaryOpImplBody { } }; -/*static*/ void UnaryOpTask::omp_variant(TaskContext& context) +/*static*/ void UnaryOpTask::omp_variant(TaskContext context) { unary_op_template(context); } diff --git a/src/cunumeric/unary/unary_op_template.inl b/src/cunumeric/unary/unary_op_template.inl index a9109bb1ad..fb8cd65cf2 100644 --- a/src/cunumeric/unary/unary_op_template.inl +++ b/src/cunumeric/unary/unary_op_template.inl @@ -177,8 +177,8 @@ struct UnaryOpDispatch { template static void unary_op_template(TaskContext& context) { - auto& inputs = context.inputs(); - auto& outputs = context.outputs(); + auto inputs = context.inputs(); + auto outputs = context.outputs(); auto& scalars = context.scalars(); auto op_code = scalars[0].value(); diff --git a/src/cunumeric/unary/unary_red.cc b/src/cunumeric/unary/unary_red.cc index bec85fc6f8..9040565831 100644 --- a/src/cunumeric/unary/unary_red.cc +++ b/src/cunumeric/unary/unary_red.cc @@ -42,7 +42,7 @@ struct UnaryRedImplBody { } }; -/*static*/ void UnaryRedTask::cpu_variant(TaskContext& context) +/*static*/ void UnaryRedTask::cpu_variant(TaskContext context) { unary_red_template(context); } diff --git a/src/cunumeric/unary/unary_red.cu b/src/cunumeric/unary/unary_red.cu index b417b3638c..f2e03de912 100644 --- a/src/cunumeric/unary/unary_red.cu +++ b/src/cunumeric/unary/unary_red.cu @@ -321,7 +321,7 @@ struct UnaryRedImplBody { } }; -/*static*/ void UnaryRedTask::gpu_variant(TaskContext& context) +/*static*/ void UnaryRedTask::gpu_variant(TaskContext context) { unary_red_template(context); } diff --git a/src/cunumeric/unary/unary_red.h b/src/cunumeric/unary/unary_red.h index e1a004d10f..bb2ca561c4 100644 --- a/src/cunumeric/unary/unary_red.h +++ b/src/cunumeric/unary/unary_red.h @@ -22,8 +22,8 @@ namespace cunumeric { struct UnaryRedArgs { - const legate::Store& lhs; - const legate::Store& rhs; + legate::Store lhs; + legate::Store rhs; int32_t collapsed_dim; UnaryRedCode op_code; }; @@ -33,12 +33,12 @@ class UnaryRedTask : public CuNumericTask { static const int TASK_ID = CUNUMERIC_UNARY_RED; public: - static void cpu_variant(legate::TaskContext& context); + static void cpu_variant(legate::TaskContext context); #ifdef LEGATE_USE_OPENMP - static void omp_variant(legate::TaskContext& context); + static void omp_variant(legate::TaskContext context); #endif #ifdef LEGATE_USE_CUDA - static void gpu_variant(legate::TaskContext& context); + static void gpu_variant(legate::TaskContext context); #endif }; diff --git a/src/cunumeric/unary/unary_red_omp.cc b/src/cunumeric/unary/unary_red_omp.cc index 21ba49d4a2..719bb9049c 100644 --- a/src/cunumeric/unary/unary_red_omp.cc +++ b/src/cunumeric/unary/unary_red_omp.cc @@ -98,7 +98,7 @@ struct UnaryRedImplBody { } }; -/*static*/ void UnaryRedTask::omp_variant(TaskContext& context) +/*static*/ void UnaryRedTask::omp_variant(TaskContext context) { unary_red_template(context); } diff --git a/src/cunumeric/unary/unary_red_template.inl b/src/cunumeric/unary/unary_red_template.inl index 1e3b298d31..ab780d7074 100644 --- a/src/cunumeric/unary/unary_red_template.inl +++ b/src/cunumeric/unary/unary_red_template.inl @@ -75,9 +75,9 @@ struct UnaryRedDispatch { template static void unary_red_template(TaskContext& context) { - auto& inputs = context.inputs(); - auto& reductions = context.reductions(); - auto& scalars = context.scalars(); + auto inputs = context.inputs(); + auto reductions = context.reductions(); + auto& scalars = context.scalars(); UnaryRedArgs args{ reductions[0], inputs[0], scalars[0].value(), scalars[1].value()}; From 04352904feacb179e4fc6f8c45aa63507fde82bd Mon Sep 17 00:00:00 2001 From: Robin Wang <104830875+robinwnv@users.noreply.github.com> Date: Mon, 14 Aug 2023 16:09:39 +0800 Subject: [PATCH 088/462] Port bincount and add bincount test. (#1) * Port bincount and add bincount test. * Update bincount in deferred.py. * Address comments. * Address comments - part2 --- cunumeric/deferred.py | 11 +- src/cunumeric/ndarray.cc | 30 +++ src/cunumeric/ndarray.h | 4 + src/cunumeric/ndarray.inl | 7 + src/cunumeric/operators.cc | 62 ++++++ src/cunumeric/operators.h | 6 + src/cunumeric/stat/bincount.h | 1 + src/cunumeric/stat/bincount_template.inl | 14 +- tests/cpp/.gitignore | 1 + tests/cpp/CMakeLists.txt | 76 +++++++ tests/cpp/build.sh | 11 + tests/cpp/cmake/thirdparty/get_nccl.cmake | 34 +++ tests/cpp/integration/test_bincount.cc | 106 +++++++++ tests/cpp/integration/util.inl | 260 ++++++++++++++++++++++ tests/cpp/main.cc | 39 ++++ tests/cpp/run.py | 162 ++++++++++++++ tests/cpp/run.sh | 13 ++ 17 files changed, 827 insertions(+), 10 deletions(-) create mode 100644 tests/cpp/.gitignore create mode 100644 tests/cpp/CMakeLists.txt create mode 100755 tests/cpp/build.sh create mode 100644 tests/cpp/cmake/thirdparty/get_nccl.cmake create mode 100644 tests/cpp/integration/test_bincount.cc create mode 100644 tests/cpp/integration/util.inl create mode 100644 tests/cpp/main.cc create mode 100755 tests/cpp/run.py create mode 100755 tests/cpp/run.sh diff --git a/cunumeric/deferred.py b/cunumeric/deferred.py index b927c69fb7..381353e5a8 100644 --- a/cunumeric/deferred.py +++ b/cunumeric/deferred.py @@ -2036,22 +2036,17 @@ def bincount(self, rhs: Any, weights: Optional[NumPyThunk] = None) -> None: assert src_array.shape == weight_array.shape or ( src_array.size == 1 and weight_array.size == 1 ) - else: - weight_array = self.runtime.create_wrapped_scalar( - np.array(1, dtype=np.int64), - np.dtype(np.int64), - shape=(), - ) dst_array.fill(np.array(0, dst_array.dtype)) task = self.context.create_auto_task(CuNumericOpCode.BINCOUNT) task.add_reduction(dst_array.base, ReductionOp.ADD) task.add_input(src_array.base) - task.add_input(weight_array.base) # type: ignore + if weight_array is not None: + task.add_input(weight_array.base) # type: ignore task.add_broadcast(dst_array.base) - if not weight_array.scalar: + if not (weight_array is None or weight_array.scalar): task.add_alignment(src_array.base, weight_array.base) # type: ignore # noqa task.execute() diff --git a/src/cunumeric/ndarray.cc b/src/cunumeric/ndarray.cc index efa474e324..bbe9e71847 100644 --- a/src/cunumeric/ndarray.cc +++ b/src/cunumeric/ndarray.cc @@ -47,6 +47,8 @@ int32_t NDArray::dim() const { return store_.dim(); } const std::vector& NDArray::shape() const { return store_.extents().data(); } +size_t NDArray::size() const { return store_.volume(); } + legate::Type NDArray::type() const { return store_.type(); } static std::vector compute_strides(const std::vector& shape) @@ -177,6 +179,34 @@ void NDArray::eye(int32_t k) runtime->submit(std::move(task)); } +void NDArray::bincount(NDArray rhs, std::optional weights /*=std::nullopt*/) +{ + assert(dim() == 1); + + auto runtime = CuNumericRuntime::get_runtime(); + + if (weights.has_value()) { assert(rhs.shape() == weights.value().shape()); } + + auto zero = legate::type_dispatch(type().code(), generate_zero_fn{}); + fill(zero, false); + + auto task = runtime->create_task(CuNumericOpCode::CUNUMERIC_BINCOUNT); + legate::ReductionOpKind redop = legate::ReductionOpKind::ADD; + auto p_lhs = task.find_or_declare_partition(store_); + auto p_rhs = task.find_or_declare_partition(rhs.store_); + + task.add_reduction(store_, redop, p_lhs); + task.add_input(rhs.store_, p_rhs); + task.add_constraint(legate::broadcast(p_lhs, {0})); + if (weights.has_value()) { + auto p_weight = task.find_or_declare_partition(weights.value().store_); + task.add_input(weights.value().store_, p_weight); + task.add_constraint(legate::align(p_rhs, p_weight)); + } + + runtime->submit(std::move(task)); +} + void NDArray::trilu(NDArray rhs, int32_t k, bool lower) { auto runtime = CuNumericRuntime::get_runtime(); diff --git a/src/cunumeric/ndarray.h b/src/cunumeric/ndarray.h index 01a78dd03b..f367416c9b 100644 --- a/src/cunumeric/ndarray.h +++ b/src/cunumeric/ndarray.h @@ -42,11 +42,14 @@ class NDArray { public: int32_t dim() const; const std::vector& shape() const; + size_t size() const; legate::Type type() const; public: template legate::AccessorRO get_read_accessor(); + template + legate::AccessorWO get_write_accessor(); public: NDArray operator+(const NDArray& other) const; @@ -78,6 +81,7 @@ class NDArray { std::vector nonzero(); NDArray unique(); void create_window(int32_t op_code, int64_t M, std::vector args); + void bincount(NDArray rhs, std::optional weights = std::nullopt); public: NDArray as_type(const legate::Type& type); diff --git a/src/cunumeric/ndarray.inl b/src/cunumeric/ndarray.inl index f1ff44c866..67b2950e8b 100644 --- a/src/cunumeric/ndarray.inl +++ b/src/cunumeric/ndarray.inl @@ -23,4 +23,11 @@ legate::AccessorRO NDArray::get_read_accessor() return mapped.read_accessor(); } +template +legate::AccessorWO NDArray::get_write_accessor() +{ + auto mapped = store_.get_physical_store(); + return mapped.write_accessor(); +} + } // namespace cunumeric diff --git a/src/cunumeric/operators.cc b/src/cunumeric/operators.cc index f4f1497258..fe57e68cb0 100644 --- a/src/cunumeric/operators.cc +++ b/src/cunumeric/operators.cc @@ -89,6 +89,22 @@ struct generate_zero_fn { } }; +struct generate_int_value_fn { + template ::value>* = nullptr> + int operator()(NDArray& array) + { + using VAL = legate::legate_type_of; + return static_cast(array.get_read_accessor()[0]); + } + + template ::value>* = nullptr> + int operator()(NDArray& array) + { + assert(false); + return -1; + } +}; + } // namespace NDArray zeros(std::vector shape, std::optional type) @@ -119,6 +135,48 @@ NDArray eye(size_t n, std::optional m, int32_t k, const legate::Type& ty return std::move(out); } +NDArray bincount(NDArray x, + std::optional weights /*=std::nullopt*/, + uint32_t min_length /*=0*/) +{ + if (x.dim() != 1) throw std::invalid_argument("The input array must be 1-dimensional"); + if (x.size() == 0) throw std::invalid_argument("The input array must be non-empty"); + + int32_t x_type_code = static_cast(x.type().code()); + if (x_type_code < static_cast(legate::Type::Code::INT8) || + x_type_code > static_cast(legate::Type::Code::UINT64)) + throw std::invalid_argument("input array for bincount must be integer type"); + + auto max_val_arr = amax(x); + auto max_val = + legate::type_dispatch(max_val_arr.type().code(), generate_int_value_fn{}, max_val_arr); + auto min_val_arr = amin(x); + auto min_val = + legate::type_dispatch(min_val_arr.type().code(), generate_int_value_fn{}, min_val_arr); + if (min_val < 0) throw std::invalid_argument("the input array must have no negative elements"); + if (min_length < max_val + 1) min_length = max_val + 1; + + auto runtime = CuNumericRuntime::get_runtime(); + if (!weights.has_value()) { + auto out = runtime->create_array({min_length}, legate::int64()); + out.bincount(x); + return out; + } else { + auto weight_array = weights.value(); + if (weight_array.shape() != x.shape()) + throw std::invalid_argument("weights array must have the same shape as the input array"); + auto weight_code = weight_array.type().code(); + if (static_cast(weight_code) >= static_cast(legate::Type::Code::COMPLEX64)) + throw std::invalid_argument("weights must be convertible to float64"); + if (weight_code != legate::Type::Code::FLOAT64) + weight_array = weight_array.as_type(legate::float64()); + + auto out = runtime->create_array({min_length}, weight_array.type()); + out.bincount(x, weight_array); + return out; + } +} + NDArray trilu(NDArray rhs, int32_t k, bool lower) { auto dim = rhs.dim(); @@ -169,6 +227,10 @@ NDArray dot(NDArray rhs1, NDArray rhs2) NDArray sum(NDArray input) { return unary_reduction(UnaryRedCode::SUM, std::move(input)); } +NDArray amax(NDArray input) { return unary_reduction(UnaryRedCode::MAX, std::move(input)); } + +NDArray amin(NDArray input) { return unary_reduction(UnaryRedCode::MIN, std::move(input)); } + NDArray unique(NDArray input) { return input.unique(); } NDArray arange(std::optional start, diff --git a/src/cunumeric/operators.h b/src/cunumeric/operators.h index b78da7b049..4c97502f15 100644 --- a/src/cunumeric/operators.h +++ b/src/cunumeric/operators.h @@ -47,6 +47,10 @@ NDArray full(std::vector shape, const Scalar& value); NDArray sum(NDArray input); +NDArray amax(NDArray input); + +NDArray amin(NDArray input); + NDArray unique(NDArray input); NDArray arange(std::optional start = 0, @@ -79,4 +83,6 @@ NDArray hanning(int64_t M); NDArray kaiser(int64_t M, double beta); +NDArray bincount(NDArray x, std::optional weights = std::nullopt, uint32_t min_length = 0); + } // namespace cunumeric diff --git a/src/cunumeric/stat/bincount.h b/src/cunumeric/stat/bincount.h index dad6baa6f0..07e45de030 100644 --- a/src/cunumeric/stat/bincount.h +++ b/src/cunumeric/stat/bincount.h @@ -24,6 +24,7 @@ struct BincountArgs { legate::Store lhs; legate::Store rhs; legate::Store weights; + bool has_weights; }; class BincountTask : public CuNumericTask { diff --git a/src/cunumeric/stat/bincount_template.inl b/src/cunumeric/stat/bincount_template.inl index aead962cea..4dc693d9b9 100644 --- a/src/cunumeric/stat/bincount_template.inl +++ b/src/cunumeric/stat/bincount_template.inl @@ -38,7 +38,7 @@ struct BincountImpl { if (rect.empty()) return; auto rhs = args.rhs.read_accessor(rect); - if (args.weights.dim() == 1) { + if (args.has_weights) { auto weights = args.weights.read_accessor(rect); auto lhs = args.lhs.reduce_accessor, KIND != VariantKind::GPU, 1>(lhs_rect); @@ -62,7 +62,17 @@ static void bincount_template(TaskContext& context) { auto inputs = context.inputs(); auto reductions = context.reductions(); - BincountArgs args{reductions[0], inputs[0], inputs[1]}; + + BincountArgs args; + args.lhs = std::move(reductions[0]); + args.rhs = std::move(inputs[0]); + if (inputs.size() >= 2) { + args.has_weights = true; + args.weights = std::move(inputs[1]); + } else { + args.has_weights = false; + } + type_dispatch(args.rhs.code(), BincountImpl{}, args); } diff --git a/tests/cpp/.gitignore b/tests/cpp/.gitignore new file mode 100644 index 0000000000..567609b123 --- /dev/null +++ b/tests/cpp/.gitignore @@ -0,0 +1 @@ +build/ diff --git a/tests/cpp/CMakeLists.txt b/tests/cpp/CMakeLists.txt new file mode 100644 index 0000000000..8138054f1b --- /dev/null +++ b/tests/cpp/CMakeLists.txt @@ -0,0 +1,76 @@ +#============================================================================= +# Copyright 2023 NVIDIA Corporation +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +#============================================================================= + +cmake_minimum_required(VERSION 3.22.1 FATAL_ERROR) + +project(cpp_tests VERSION 0.1 LANGUAGES C CXX) + +if (NOT CMAKE_CXX_STANDARD) + set(CMAKE_CXX_STANDARD 17) +endif() + +include(FetchContent) +FetchContent_Declare( + googletest + URL https://github.com/google/googletest/archive/refs/tags/v1.13.0.zip +) + +set(gtest_force_shared_crt ON CACHE BOOL "" FORCE) +FetchContent_MakeAvailable(googletest) + +find_package(legate_core REQUIRED) +find_package(cunumeric REQUIRED) + +enable_testing() + +file(GLOB main_SRC ${PROJECT_SOURCE_DIR}/main.cc) +file(GLOB integration_SRC ${PROJECT_SOURCE_DIR}/integration/*.cc) +file(GLOB tasks_SRC ${PROJECT_SOURCE_DIR}/integration/tasks/*.cc) +file(GLOB unit_SRC ${PROJECT_SOURCE_DIR}/unit/*.cc) + +if(Legion_USE_CUDA) + +find_package(CUDAToolkit REQUIRED) +enable_language(CUDA) + +file(GLOB integration_GPU_SRC ${PROJECT_SOURCE_DIR}/integration/*.cu) +list(APPEND integration_SRC ${integration_GPU_SRC}) + +if(NOT EXISTS ${CMAKE_BINARY_DIR}/RAPIDS.cmake) + file(DOWNLOAD https://raw.githubusercontent.com/rapidsai/rapids-cmake/branch-23.04/RAPIDS.cmake + ${CMAKE_BINARY_DIR}/RAPIDS.cmake) +endif() +include(${CMAKE_BINARY_DIR}/RAPIDS.cmake) +include(rapids-find) +include(cmake/thirdparty/get_nccl.cmake) + +endif() + +add_executable(cpp_tests ${main_SRC} ${tasks_SRC} ${integration_SRC} ${unit_SRC}) + +target_link_libraries(cpp_tests PRIVATE legate::core PRIVATE cunumeric::cunumeric PRIVATE GTest::gtest) +if(Legion_USE_CUDA) +target_link_libraries(cpp_tests PRIVATE NCCL::NCCL) +endif() + +include(GoogleTest) +gtest_discover_tests(cpp_tests DISCOVERY_MODE PRE_TEST WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}) + +include(CPack) +include(GNUInstallDirs) + +install(TARGETS cpp_tests DESTINATION "${CMAKE_CURRENT_BINARY_DIR}/cmake-install") +install( TARGETS cpp_tests DESTINATION ${CMAKE_INSTALL_BINDIR}) diff --git a/tests/cpp/build.sh b/tests/cpp/build.sh new file mode 100755 index 0000000000..2c27371890 --- /dev/null +++ b/tests/cpp/build.sh @@ -0,0 +1,11 @@ +#!/bin/bash + +legate_root=`python -c 'import legate.install_info as i; from pathlib import Path; print(Path(i.libpath).parent.resolve())'` +echo "Using Legate at $legate_root" + +`python -c 'import cunumeric.install_info as i; from pathlib import Path; print(f"export cunumeric_root={Path(i.libpath).parent.resolve()}")'` +echo "Using cunumeric at $cunumeric_root" + +rm -rf build +cmake -B build -S . -D legate_core_ROOT="$legate_root" -D cunumeric_ROOT="$cunumeric_root" -D CMAKE_BUILD_TYPE=Debug +cmake --build build -j 8 diff --git a/tests/cpp/cmake/thirdparty/get_nccl.cmake b/tests/cpp/cmake/thirdparty/get_nccl.cmake new file mode 100644 index 0000000000..86695e0485 --- /dev/null +++ b/tests/cpp/cmake/thirdparty/get_nccl.cmake @@ -0,0 +1,34 @@ +#============================================================================= +# Copyright 2023 NVIDIA Corporation +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +#============================================================================= + +function(find_or_configure_nccl) + + if(TARGET NCCL::NCCL) + return() + endif() + + rapids_find_generate_module(NCCL + HEADER_NAMES nccl.h + LIBRARY_NAMES nccl + ) + + # Currently NCCL has no CMake build-system so we require + # it built and installed on the machine already + rapids_find_package(NCCL REQUIRED) + +endfunction() + +find_or_configure_nccl() diff --git a/tests/cpp/integration/test_bincount.cc b/tests/cpp/integration/test_bincount.cc new file mode 100644 index 0000000000..6fb71182e8 --- /dev/null +++ b/tests/cpp/integration/test_bincount.cc @@ -0,0 +1,106 @@ +/* Copyright 2023 NVIDIA Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +#include +#include +#include + +#include +#include "legate.h" +#include "cunumeric.h" +#include "util.inl" + +void bincount_test() +{ + // case: x, no w, min_length=0. out NDArray type is int64_t if no weights + std::array exp1 = {0, 1, 1, 2, 0, 1, 1}; + std::array in_x1 = {1, 2, 3, 3, 5, 6}; + auto A1 = cunumeric::zeros({6}, legate::int32()); + assign_values_to_array(A1, in_x1.data(), in_x1.size()); + auto B1 = cunumeric::bincount(A1); + check_array_eq(B1, exp1.data(), exp1.size()); + + // case: x, w, min_length=0. + std::array exp2 = {0, 1, 1.2, 2, 0, 1, 0.1}; + std::array in_w2 = {1, 1.2, 1, 1, 1, 0.1}; + auto w2 = cunumeric::zeros({6}, legate::float64()); + assign_values_to_array(w2, in_w2.data(), in_w2.size()); + auto B2 = cunumeric::bincount(A1, w2); + check_array_eq(B2, exp2.data(), exp2.size()); + + // case: x, no w, min_length=8. out NDArray type is int64_t if no weights + std::array exp3 = {0, 1, 1, 2, 0, 1, 1, 0}; + auto B3 = cunumeric::bincount(A1, std::nullopt, 8); + check_array_eq(B3, exp3.data(), exp3.size()); + + // case: x of length 1, no w, min_length=0 + std::array exp4 = {0, 0, 0, 0, 0, 1}; + auto A4 = cunumeric::full({1}, cunumeric::Scalar(5)); + // If we use another way to initialize A4 of length 1 as below, it would rasie error. Seems a lock + // issue. In this way, if A4 is not of length 1, it pass. int64_t in_x4[1] = {5}; auto A4 = + // cunumeric::zeros({1}, legate::int64()); assign_values_to_array(A4, (void *)in_x4, + // sizeof(in_x4)/sizeof(int64_t)); cpp_tests: legion/runtime/realm/runtime_impl.cc:2755: + // Realm::RegionInstanceImpl* Realm::RuntimeImpl::get_instance_impl(Realm::ID): Assertion `0 && + // "invalid instance handle"' failed. + auto B4 = cunumeric::bincount(A4); + check_array_eq(B4, exp4.data(), exp4.size()); + + // case: x of length 1, w of length 1, min_length=0 + std::array exp5 = {0, 0, 0, 0, 0, 1.3}; + auto w5 = cunumeric::full({1}, cunumeric::Scalar(1.3)); + auto B5 = cunumeric::bincount(A4, w5); + check_array_eq(B5, exp5.data(), exp5.size()); + + // case: x of length 1, w of length 1, min_length=8 + std::array exp6 = {0, 0, 0, 0, 0, 1.3, 0, 0}; + auto B6 = cunumeric::bincount(A4, w5, 8); + check_array_eq(B6, exp6.data(), exp6.size()); +} + +void bincount_negative_test() +{ + // case: x.size() == 0 + auto A1 = cunumeric::full({0}, cunumeric::Scalar(5)); + EXPECT_THROW(cunumeric::bincount(A1), std::invalid_argument); + + // case: x.dim() != 1 + auto A2 = cunumeric::full({1, 1}, cunumeric::Scalar(5)); + EXPECT_THROW(cunumeric::bincount(A2), std::invalid_argument); + + // case: x.type() is not int + auto A3 = cunumeric::full({3}, cunumeric::Scalar(1.3)); + EXPECT_THROW(cunumeric::bincount(A3), std::invalid_argument); + + // case: x.shape() != w.shape() + auto A4 = cunumeric::zeros({6}, legate::int32()); + auto w4 = cunumeric::zeros({4}, legate::int32()); + EXPECT_THROW(cunumeric::bincount(A4, w4), std::invalid_argument); + + // case: w.type() is not convertible to float64 + auto w5 = cunumeric::zeros({6}, legate::complex64()); + EXPECT_THROW(cunumeric::bincount(A4, w5), std::invalid_argument); + + // case: x is negative + std::array in_x = {1, 2, -3, 4, 5, 6}; + auto A7 = cunumeric::zeros({6}, legate::int32()); + assign_values_to_array(A7, in_x.data(), in_x.size()); + EXPECT_THROW(cunumeric::bincount(A7), std::invalid_argument); +} + +// void cpp_test() +TEST(Bincount, Normal) { bincount_test(); } + +TEST(Bincount, Negative) { bincount_negative_test(); } diff --git a/tests/cpp/integration/util.inl b/tests/cpp/integration/util.inl new file mode 100644 index 0000000000..7cdca692c9 --- /dev/null +++ b/tests/cpp/integration/util.inl @@ -0,0 +1,260 @@ +/* Copyright 2023 NVIDIA Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +namespace { +template +std::string to_string_1d(legate::AccessorRO acc, const std::vector& shape) +{ + std::stringstream ss; + + ss << "["; + for (auto i = 0; i < shape[0]; ++i) { + if (i > 0) ss << ", "; + ss << std::setw(9) << std::setprecision(6) << acc[i]; + } + ss << "]"; + + return ss.str(); +} + +template +std::string to_string_2d(legate::AccessorRO acc, const std::vector& shape) +{ + std::stringstream ss; + + ss << "["; + for (auto i = 0; i < shape[0]; ++i) { + if (i > 0) ss << ",\n "; + ss << "["; + for (auto j = 0; j < shape[1]; ++j) { + if (j > 0) ss << ", "; + ss << std::setw(9) << std::setprecision(3) << acc[i][j]; + } + ss << "]"; + } + + return ss.str(); +} + +template +std::string to_string_3d(legate::AccessorRO acc, const std::vector& shape) +{ + std::stringstream ss; + + ss << "["; + for (auto k = 0; k < shape[0]; ++k) { + if (k > 0) ss << ",\n "; + ss << "["; + for (auto i = 0; i < shape[1]; ++i) { + if (i > 0) ss << ",\n "; + ss << "["; + for (auto j = 0; j < shape[2]; ++j) { + if (j > 0) ss << ", "; + ss << std::setw(9) << std::setprecision(3) << acc[k][i][j]; + } + ss << "]"; + } + ss << "]"; + } + ss << "]"; + + return ss.str(); +} + +template +std::string check_array_eq_1d(legate::AccessorRO acc, + T* values_ptr, + const std::vector& shape) +{ + std::stringstream ss; + + ss << "["; + for (auto i = 0; i < shape[0]; ++i) { + if (i > 0) ss << ", "; + ss << std::setw(9) << std::setprecision(6) << acc[i]; + EXPECT_EQ(acc[i], values_ptr[i]); + } + ss << "]"; + + return ss.str(); +} + +template +std::string check_array_eq_2d(legate::AccessorRO acc, + T* values_ptr, + const std::vector& shape) +{ + std::stringstream ss; + + ss << "["; + for (auto i = 0; i < shape[0]; ++i) { + if (i > 0) ss << ",\n "; + ss << "["; + for (auto j = 0; j < shape[1]; ++j) { + if (j > 0) ss << ", "; + ss << std::setw(9) << std::setprecision(3) << acc[i][j]; + EXPECT_EQ(acc[i][j], values_ptr[i * shape[1] + j]); + } + ss << "]"; + } + ss << "]"; + + return ss.str(); +} + +template +std::string check_array_eq_3d(legate::AccessorRO acc, + T* values_ptr, + const std::vector& shape) +{ + std::stringstream ss; + + ss << "["; + for (auto k = 0; k < shape[0]; ++k) { + if (k > 0) ss << ",\n "; + ss << "["; + for (auto i = 0; i < shape[1]; ++i) { + if (i > 0) ss << ",\n "; + ss << "["; + for (auto j = 0; j < shape[2]; ++j) { + if (j > 0) ss << ", "; + ss << std::setw(9) << std::setprecision(3) << acc[k][i][j]; + EXPECT_EQ(acc[k][i][j], values_ptr[k * shape[1] * shape[2] + i * shape[2] + j]); + } + ss << "]"; + } + ss << "]"; + } + ss << "]"; + + return ss.str(); +} + +template +struct print_fn; + +template +struct print_fn { + void operator()(legate::AccessorRO acc, const std::vector& shape) + { + std::cerr << to_string_1d(acc, shape) << std::endl; + } +}; + +template +struct print_fn { + void operator()(legate::AccessorRO acc, const std::vector& shape) + { + std::cerr << to_string_2d(acc, shape) << std::endl; + } +}; + +template +struct print_fn { + void operator()(legate::AccessorRO acc, const std::vector& shape) + { + std::cerr << to_string_3d(acc, shape) << std::endl; + } +}; + +template +struct check_array_eq_fn; + +template +struct check_array_eq_fn { + void operator()(legate::AccessorRO acc, T* values_ptr, const std::vector& shape) + { + std::cerr << check_array_eq_1d(acc, values_ptr, shape) << std::endl; + } +}; + +template +struct check_array_eq_fn { + void operator()(legate::AccessorRO acc, T* values_ptr, const std::vector& shape) + { + std::cerr << check_array_eq_2d(acc, values_ptr, shape) << std::endl; + } +}; + +template +struct check_array_eq_fn { + void operator()(legate::AccessorRO acc, T* values_ptr, const std::vector& shape) + { + std::cerr << check_array_eq_3d(acc, values_ptr, shape) << std::endl; + } +}; + +template +struct assign_array_fn; + +template +struct assign_array_fn { + void operator()(legate::AccessorWO acc, T* values_ptr, const std::vector& shape) + { + for (auto i = 0; i < shape[0]; ++i) { acc[i] = values_ptr[i]; } + } +}; + +template +struct assign_array_fn { + void operator()(legate::AccessorWO acc, T* values_ptr, const std::vector& shape) + { + for (auto i = 0; i < shape[0]; ++i) { + for (auto j = 0; j < shape[1]; ++j) { acc[i][j] = values_ptr[i * shape[1] + j]; } + } + } +}; + +template +struct assign_array_fn { + void operator()(legate::AccessorWO acc, T* values_ptr, const std::vector& shape) + { + for (auto i = 0; i < shape[0]; ++i) { + for (auto j = 0; j < shape[1]; ++j) { + for (auto k = 0; k < shape[2]; ++k) { + acc[i][j][k] = values_ptr[i * shape[1] * shape[2] + j * shape[2] + k]; + } + } + } + } +}; + +template +void print_array(cunumeric::NDArray array) +{ + auto acc = array.get_read_accessor(); + auto& shape = array.shape(); + print_fn()(acc, shape); +} + +template +void check_array_eq(cunumeric::NDArray array, T* values_ptr, size_t length) +{ + assert(array.size() == length); + auto acc = array.get_read_accessor(); + auto& shape = array.shape(); + check_array_eq_fn()(acc, values_ptr, shape); +} + +template +void assign_values_to_array(cunumeric::NDArray array, T* values_ptr, size_t length) +{ + assert(array.size() == length); + auto acc = array.get_write_accessor(); + auto& shape = array.shape(); + assign_array_fn()(acc, values_ptr, shape); +} +} // namespace diff --git a/tests/cpp/main.cc b/tests/cpp/main.cc new file mode 100644 index 0000000000..4ab1fca2ff --- /dev/null +++ b/tests/cpp/main.cc @@ -0,0 +1,39 @@ +/* + * SPDX-FileCopyrightText: Copyright (c) 2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * SPDX-License-Identifier: LicenseRef-NvidiaProprietary + * + * NVIDIA CORPORATION, its affiliates and licensors retain all intellectual + * property and proprietary rights in and to this material, related + * documentation and any modifications thereto. Any use, reproduction, + * disclosure or distribution of this material and related documentation + * without an express license agreement from NVIDIA CORPORATION or + * its affiliates is strictly prohibited. + */ + +#include +#include "legate.h" +#include "cunumeric.h" + +class Environment : public ::testing::Environment { + public: + Environment(int argc, char** argv) : argc_(argc), argv_(argv) {} + + void SetUp() override + { + EXPECT_EQ(legate::start(argc_, argv_), 0); + cunumeric::initialize(argc_, argv_); + } + void TearDown() override { EXPECT_EQ(legate::finish(), 0); } + + private: + int argc_; + char** argv_; +}; + +int main(int argc, char** argv) +{ + ::testing::InitGoogleTest(&argc, argv); + ::testing::AddGlobalTestEnvironment(new Environment(argc, argv)); + + return RUN_ALL_TESTS(); +} diff --git a/tests/cpp/run.py b/tests/cpp/run.py new file mode 100755 index 0000000000..2a2f90fbc6 --- /dev/null +++ b/tests/cpp/run.py @@ -0,0 +1,162 @@ +# SPDX-FileCopyrightText: Copyright (c) 2023 NVIDIA CORPORATION & AFFILIATES. +# All rights reserved. +# SPDX-License-Identifier: LicenseRef-NvidiaProprietary +# +# NVIDIA CORPORATION, its affiliates and licensors retain all intellectual +# property and proprietary rights in and to this material, related +# documentation and any modifications thereto. Any use, reproduction, +# disclosure or distribution of this material and related documentation +# without an express license agreement from NVIDIA CORPORATION or +# its affiliates is strictly prohibited. + + +import argparse +import os +import subprocess +import sys + +LAUNCHER_VAR_PREFIXES = ( + "CONDA_", + "LEGATE_", + "LEGION_", + "LG_", + "REALM_", + "GASNET_", + "PYTHON", + "UCX_", + "NCCL_", + "CUNUMERIC_", + "NVIDIA_", +) + +test_args_dict = { + # Example of usage + # "Alignment.Basic" : ["-logfile", "build/example_file.log"] +} + + +def fetch_test_names(binary_path): + list_command = [binary_path] + ["--gtest_list_tests"] + + result = subprocess.check_output(list_command, stderr=subprocess.STDOUT) + result = result.decode(sys.stdout.encoding).split("\n") + + test_group = "" + test_names = [] + for line in result: + # Skip empty entry + if not line.strip(): + continue + + # Check if this is a test group + if line[0] != " ": + test_group = line.strip() + continue + + # Assign test to test group + test_names += [test_group + line.strip()] + + return test_names + + +def run_test(config, test_name, log, extra_args): + test_command = [] + if config.ranks != 0: + test_command += ["mpirun", "-n", str(config.ranks)] + test_command += ["--output-filename", "build/mpi_result"] + test_command += ["--merge-stderr-to-stdout"] + + def is_launcher_var(name: str) -> bool: + # Whether an environment variable name is relevant for the laucher + return name.endswith("PATH") or any( + name.startswith(prefix) for prefix in LAUNCHER_VAR_PREFIXES + ) + + for var in dict(os.environ): + if is_launcher_var(var): + test_command += ["-x", var] + + test_command += [config.binary_path] + test_command += [f"--gtest_filter={test_name}"] + test_command += ["-ll:cpu", str(config.cpus)] + test_command += extra_args + + if test_name in test_args_dict: + test_command += test_args_dict[test_name] + + task = subprocess.Popen(test_command, stdout=log, stderr=subprocess.STDOUT) + task.communicate() + + return task.returncode + + +def main(): + parser = argparse.ArgumentParser(description="Run Legate cpp tests.") + parser.add_argument( + "--binary-path", + dest="binary_path", + required=False, + default="build/cpp_tests", + help="Path to binary under test.", + ) + parser.add_argument( + "--log-path", + dest="log_path", + required=False, + default="build/results.log", + help="Path to output log file.", + ) + parser.add_argument( + "--ranks", + dest="ranks", + required=False, + type=int, + default=0, + help="Runs mpirun with rank if non-zero.", + ) + parser.add_argument( + "--cpus", + dest="cpus", + required=False, + type=int, + default=4, + help="Legion cmd argument for CPU processors to create per process.", + ) + config, extra_args = parser.parse_known_args() + + # Get names + test_names = fetch_test_names(config.binary_path) + + # Run each test with popen + total_count = len(test_names) + failed_count = 0 + failed_tests = [] + with open(config.log_path, "w") as log: + for count, test_name in enumerate(test_names): + return_code = run_test(config, test_name, log, extra_args) + + # Record test result + if return_code: + failed_tests += [test_name] + failed_count += 1 + print( + f"{count+1:3d}/{total_count}: {test_name} ".ljust(50, "."), + "Failed" if return_code else "Passed", + ) + + # Summarize results + print( + f"\n{int((total_count - failed_count) / total_count * 100)}% " + f"tests passed, {failed_count} tests failed out of {total_count}" + ) + if failed_tests: + print("\nThe following tests FAILED:") + for test in failed_tests: + print(f" - {test} (Failed)") + print(f"\nLog file generated: {config.log_path}") + return 1 + return 0 + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/tests/cpp/run.sh b/tests/cpp/run.sh new file mode 100755 index 0000000000..6e012b101e --- /dev/null +++ b/tests/cpp/run.sh @@ -0,0 +1,13 @@ +#!/bin/bash + +if [ $# -eq 0 ] + then + REALM_BACKTRACE=1 LEGATE_TEST=1 python run.py +elif [ $# -eq 1 ] && [ "$1" = "ctest" ] + then + echo "Using ctest" + cd build + REALM_BACKTRACE=1 LEGATE_TEST=1 LEGION_DEFAULT_ARGS="-ll:cpu 4" ctest --output-on-failure "$@" +else + echo "Invalid arguments" +fi From 378060d799d02edc48be09a8ce9fdebb1013a56d Mon Sep 17 00:00:00 2001 From: Parag Kulkarni Date: Mon, 21 Aug 2023 21:12:54 +0530 Subject: [PATCH 089/462] Merge CI to sync branches from public repository. (#2) * - The workflow merges/syncs branches from public repository to internal. - It works with all branch-* branches. - It is scheduled to run weekly every Monday and Thursday at 10:00 AM. * Verify using GITHUB_TOKEN instead of PAT. * Remove code to trigger the pipeline on schedule. Retain manual triggerring. --- .github/workflows/merge-branches.sh | 50 +++++++++++++++++++++++++++++ .github/workflows/merge-ci.yml | 30 +++++++++++++++++ 2 files changed, 80 insertions(+) create mode 100755 .github/workflows/merge-branches.sh create mode 100644 .github/workflows/merge-ci.yml diff --git a/.github/workflows/merge-branches.sh b/.github/workflows/merge-branches.sh new file mode 100755 index 0000000000..881358cd99 --- /dev/null +++ b/.github/workflows/merge-branches.sh @@ -0,0 +1,50 @@ +#!/bin/bash + +main() { + REMOTES="$@"; + if [ -z "$REMOTES" ]; then + REMOTES=$(git remote); + fi + REMOTES=$(echo "$REMOTES" | xargs -n1 echo) + CLB=$(git rev-parse --abbrev-ref HEAD); + echo "$REMOTES" | while read REMOTE; do + git remote update $REMOTE + git branch -r \ + | git branch -r | awk 'BEGIN { FS = "/" };/'"$REMOTE"'/{print $2}' \ + | while read BRANCH; do + if [[ $BRANCH == !(main|branch-*|gh-pages) ]]; then + echo "Skipping branch $BRANCH because it does not match the (main|branch-*) pattern."; + continue; + fi + # first, delete the local branch if there is one + git branch -D $BRANCH 2>/dev/null || true + # checkout the branch tracking from origin or fail if there isn't one yet + git checkout --track origin/$BRANCH 2>/dev/null || true + # reset the branch, or fail if the branch is not checked out + git reset --hard origin/$BRANCH 2>/dev/null || true + ARB="refs/remotes/$REMOTE/$BRANCH"; + ALB="refs/heads/$BRANCH"; + NBEHIND=$(( $(git rev-list --count $ALB..$ARB 2>/dev/null || echo "-1") )); + NAHEAD=$(( $(git rev-list --count $ARB..$ALB 2>/dev/null || true) )); + if [ "$NBEHIND" -gt 0 ]; then + if [ "$NAHEAD" -gt 0 ]; then + echo " branch $BRANCH is $NAHEAD commit(s) ahead of $REMOTE/$BRANCH. Public branches cannot contain internal commits."; + exit 1; + else + echo " branch $BRANCH was $NBEHIND commit(s) behind of $REMOTE/$BRANCH. resetting local branch to remote"; + git reset --hard $REMOTE/$BRANCH >/dev/null; + git push origin $BRANCH + fi + elif [ "$NBEHIND" -eq -1 ]; then + echo " branch $BRANCH does not exist yet. Creating a new branch to track remote"; + git branch -f $BRANCH -t $ARB >/dev/null; + git push origin $BRANCH + else + echo "Nothing to be done for branch $BRANCH" + fi + done + done +} + +main $@ + diff --git a/.github/workflows/merge-ci.yml b/.github/workflows/merge-ci.yml new file mode 100644 index 0000000000..4bd310638c --- /dev/null +++ b/.github/workflows/merge-ci.yml @@ -0,0 +1,30 @@ +name: merge-public + +on: + workflow_dispatch: + +jobs: + merge: + runs-on: ubuntu-latest + steps: + - name: Cunumeric Internal repository + uses: actions/checkout@v3 + with: + token: ${{ secrets.GITHUB_TOKEN }} + + - name: Merge the branches + run: | + DEFAULT_BRANCH=$(git remote show origin | awk '/HEAD branch/ {print $NF}') + git fetch origin + git checkout $DEFAULT_BRANCH + git reset --hard origin/$DEFAULT_BRANCH + git config --local user.name 'SyncAction' + git config --local user.email 'sync@nowhere' + git remote add GhCunumericPublic https://github.com/nv-legate/cunumeric.git || true + git branch -a + git remote show origin + git remote set-url --push origin https://github.com/nv-legate/cunumeric.internal.git + git remote show origin + . .github/workflows/merge-branches.sh GhCunumericPublic + git push origin --tags + From 6f878aa5ee6bdbac18743f1d1976e4098613b4ca Mon Sep 17 00:00:00 2001 From: Parag Kulkarni Date: Fri, 25 Aug 2023 11:32:02 +0530 Subject: [PATCH 090/462] Provide requisite workflow permission (#5) * - Replace GITHUB_TOKEN with WORKFLOW_TOKEN. - Trigger script with push also. * - Remove the push mechanism. That was only added for testing. --- .github/workflows/merge-ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/merge-ci.yml b/.github/workflows/merge-ci.yml index 4bd310638c..d0631ef14a 100644 --- a/.github/workflows/merge-ci.yml +++ b/.github/workflows/merge-ci.yml @@ -10,7 +10,7 @@ jobs: - name: Cunumeric Internal repository uses: actions/checkout@v3 with: - token: ${{ secrets.GITHUB_TOKEN }} + token: ${{ secrets.WORKFLOW_TOKEN }} - name: Merge the branches run: | From ad8bb1152ceed6d9bda0d896c8b079d46f16cd7c Mon Sep 17 00:00:00 2001 From: AJ Schmidt Date: Tue, 29 Aug 2023 16:19:25 -0400 Subject: [PATCH 091/462] Use `copy-pr-bot` (#6) This PR replaces the `copy_prs` functionality from the `ops-bot` with the new dedicated `copy-pr-bot` GitHub application. Thorough documentation for the new `copy-pr-bot` application can be viewed below. - https://docs.gha-runners.nvidia.com/apps/copy-pr-bot/ **Important**: `copy-pr-bot` enforces signed commits. If an organization member opens a PR that contains unsigned commits, it will be deemed untrusted and therefore require an `/ok to test` comment. See the GitHub docs [here](https://docs.github.com/en/authentication/managing-commit-signature-verification/about-commit-signature-verification) for information on how to set up commit signing. Any time a PR is deemed untrusted, it will receive a comment that looks like this: https://github.com/rapidsai/ci-imgs/pull/63#issuecomment-1688973208. Every subsequent commit on an untrusted PR will require an additional `/ok to test` comment. Any existing PRs that have unsigned commits after this change is merged will require an `/ok to test` comment for each subsequent commit _or_ the PR can be rebased to include signed commits as mentioned in the docs below: https://docs.gha-runners.nvidia.com/cpr/contributors. This information is all included on the documentation page linked above. _I've skipped CI on this PR since it's not a change that is tested._ [skip ci] --- .github/copy-pr-bot.yaml | 4 ++++ .github/ops-bot.yaml | 4 ---- 2 files changed, 4 insertions(+), 4 deletions(-) create mode 100644 .github/copy-pr-bot.yaml delete mode 100644 .github/ops-bot.yaml diff --git a/.github/copy-pr-bot.yaml b/.github/copy-pr-bot.yaml new file mode 100644 index 0000000000..895ba83ee5 --- /dev/null +++ b/.github/copy-pr-bot.yaml @@ -0,0 +1,4 @@ +# Configuration file for `copy-pr-bot` GitHub App +# https://docs.gha-runners.nvidia.com/apps/copy-pr-bot/ + +enabled: true diff --git a/.github/ops-bot.yaml b/.github/ops-bot.yaml deleted file mode 100644 index 84bbe71f46..0000000000 --- a/.github/ops-bot.yaml +++ /dev/null @@ -1,4 +0,0 @@ -# This file controls which features from the `ops-bot` repository below are enabled. -# - https://github.com/rapidsai/ops-bot - -copy_prs: true From 3085eb74559ca4dcc207258a2f190e6ab64b46a9 Mon Sep 17 00:00:00 2001 From: Wonchan Lee Date: Wed, 30 Aug 2023 01:16:30 -0700 Subject: [PATCH 092/462] Use a single registration callback for all code paths (#7) --- src/cunumeric/cunumeric.cc | 16 +++------------- src/cunumeric/runtime.cc | 5 +---- 2 files changed, 4 insertions(+), 17 deletions(-) diff --git a/src/cunumeric/cunumeric.cc b/src/cunumeric/cunumeric.cc index b048e592fc..6546bba501 100644 --- a/src/cunumeric/cunumeric.cc +++ b/src/cunumeric/cunumeric.cc @@ -37,21 +37,11 @@ void registration_callback() config.max_tasks = CUNUMERIC_MAX_TASKS; config.max_reduction_ops = CUNUMERIC_MAX_REDOPS; - auto library = Runtime::get_runtime()->create_library( - cunumeric_library_name, config, std::make_unique()); - - CuNumericRegistrar::get_registrar().register_all_tasks(library); -} - -void bootstrapping_callback(Legion::Machine machine, - Legion::Runtime* legion_runtime, - const std::set& local_procs) -{ - registration_callback(); - auto runtime = legate::Runtime::get_runtime(); - auto library = runtime->find_library(cunumeric_library_name); + auto library = + runtime->create_library(cunumeric_library_name, config, std::make_unique()); + CuNumericRegistrar::get_registrar().register_all_tasks(library); CuNumericRuntime::initialize(runtime, library); } diff --git a/src/cunumeric/runtime.cc b/src/cunumeric/runtime.cc index 8a4da330e4..f14cc749de 100644 --- a/src/cunumeric/runtime.cc +++ b/src/cunumeric/runtime.cc @@ -28,10 +28,7 @@ extern void bootstrapping_callback(Legion::Machine machine, Legion::Runtime* runtime, const std::set& local_procs); -void initialize(int32_t argc, char** argv) -{ - Legion::Runtime::perform_registration_callback(bootstrapping_callback, true /*global*/); -} +void initialize(int32_t argc, char** argv) { cunumeric_perform_registration(); } CuNumericRuntime::CuNumericRuntime(legate::Runtime* legate_runtime, legate::Library library) : legate_runtime_(legate_runtime), library_(library) From 3264a0e3b55837e03f7224da38adcc343425ff40 Mon Sep 17 00:00:00 2001 From: Parag Kulkarni Date: Wed, 6 Sep 2023 18:24:31 +0530 Subject: [PATCH 093/462] Add cron properties to run the script every 15 mins. (#9) --- .github/workflows/merge-ci.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/merge-ci.yml b/.github/workflows/merge-ci.yml index d0631ef14a..cb8d782f1e 100644 --- a/.github/workflows/merge-ci.yml +++ b/.github/workflows/merge-ci.yml @@ -2,6 +2,8 @@ name: merge-public on: workflow_dispatch: + schedule: + - cron: '*/15 * * * *' # Run every 15 mins jobs: merge: From 6e373d008100f7e3399f359b31ef49976e6d4718 Mon Sep 17 00:00:00 2001 From: Parag Kulkarni Date: Fri, 8 Sep 2023 21:15:12 +0530 Subject: [PATCH 094/462] Run this workflow only for nv-legate owned repositories. (#13) --- .github/workflows/merge-ci.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/merge-ci.yml b/.github/workflows/merge-ci.yml index cb8d782f1e..264547be1b 100644 --- a/.github/workflows/merge-ci.yml +++ b/.github/workflows/merge-ci.yml @@ -7,6 +7,7 @@ on: jobs: merge: + if: github.repository_owner == 'nv-legate' runs-on: ubuntu-latest steps: - name: Cunumeric Internal repository From 59c80ae0c0bb8001334f1f38bc4a87c36394616b Mon Sep 17 00:00:00 2001 From: Wonchan Lee Date: Mon, 11 Sep 2023 12:44:45 -0700 Subject: [PATCH 095/462] Remove manual partition declarations (#14) --- src/cunumeric/ndarray.cc | 115 ++++++++++++--------------------------- 1 file changed, 36 insertions(+), 79 deletions(-) diff --git a/src/cunumeric/ndarray.cc b/src/cunumeric/ndarray.cc index bbe9e71847..aa9d90bc07 100644 --- a/src/cunumeric/ndarray.cc +++ b/src/cunumeric/ndarray.cc @@ -132,9 +132,7 @@ void NDArray::random(int32_t gen_code) auto task = runtime->create_task(CuNumericOpCode::CUNUMERIC_RAND); - auto p_lhs = task.declare_partition(); - - task.add_output(store_, p_lhs); + auto p_lhs = task.add_output(store_); task.add_scalar_arg(legate::Scalar(static_cast(RandGenCode::UNIFORM))); task.add_scalar_arg(legate::Scalar(runtime->get_next_random_epoch())); auto strides = compute_strides(shape()); @@ -149,12 +147,10 @@ void NDArray::fill(const Scalar& value, bool argval) auto fill_value = runtime->create_scalar_store(value); - auto task = runtime->create_task(CuNumericOpCode::CUNUMERIC_FILL); - auto p_lhs = task.declare_partition(); - auto p_fill_value = task.declare_partition(); + auto task = runtime->create_task(CuNumericOpCode::CUNUMERIC_FILL); - task.add_output(store_, p_lhs); - task.add_input(fill_value, p_fill_value); + auto p_lhs = task.add_output(store_); + auto p_fill_value = task.add_input(fill_value); task.add_scalar_arg(legate::Scalar(argval)); runtime->submit(std::move(task)); @@ -169,11 +165,10 @@ void NDArray::eye(int32_t k) auto runtime = CuNumericRuntime::get_runtime(); - auto task = runtime->create_task(CuNumericOpCode::CUNUMERIC_EYE); - auto p_lhs = task.declare_partition(); + auto task = runtime->create_task(CuNumericOpCode::CUNUMERIC_EYE); - task.add_input(store_, p_lhs); - task.add_output(store_, p_lhs); + task.add_input(store_); + task.add_output(store_); task.add_scalar_arg(legate::Scalar(k)); runtime->submit(std::move(task)); @@ -192,15 +187,12 @@ void NDArray::bincount(NDArray rhs, std::optional weights /*=std::nullo auto task = runtime->create_task(CuNumericOpCode::CUNUMERIC_BINCOUNT); legate::ReductionOpKind redop = legate::ReductionOpKind::ADD; - auto p_lhs = task.find_or_declare_partition(store_); - auto p_rhs = task.find_or_declare_partition(rhs.store_); - task.add_reduction(store_, redop, p_lhs); - task.add_input(rhs.store_, p_rhs); + auto p_lhs = task.add_reduction(store_, redop); + auto p_rhs = task.add_input(rhs.store_); task.add_constraint(legate::broadcast(p_lhs, {0})); if (weights.has_value()) { - auto p_weight = task.find_or_declare_partition(weights.value().store_); - task.add_input(weights.value().store_, p_weight); + auto p_weight = task.add_input(weights.value().store_); task.add_constraint(legate::align(p_rhs, p_weight)); } @@ -211,9 +203,7 @@ void NDArray::trilu(NDArray rhs, int32_t k, bool lower) { auto runtime = CuNumericRuntime::get_runtime(); - auto task = runtime->create_task(CuNumericOpCode::CUNUMERIC_TRILU); - auto p_lhs = task.find_or_declare_partition(store_); - auto p_rhs = task.find_or_declare_partition(rhs.store_); + auto task = runtime->create_task(CuNumericOpCode::CUNUMERIC_TRILU); auto& out_shape = shape(); rhs = rhs.broadcast(out_shape, rhs.store_); @@ -221,8 +211,8 @@ void NDArray::trilu(NDArray rhs, int32_t k, bool lower) task.add_scalar_arg(legate::Scalar(lower)); task.add_scalar_arg(legate::Scalar(k)); - task.add_output(store_, p_lhs); - task.add_input(rhs.store_, p_rhs); + auto p_lhs = task.add_output(store_); + auto p_rhs = task.add_input(rhs.store_); task.add_constraint(align(p_lhs, p_rhs)); @@ -241,13 +231,9 @@ void NDArray::binary_op(int32_t op_code, NDArray rhs1, NDArray rhs2) auto rhs1_store = broadcast(out_shape, rhs1.store_); auto rhs2_store = broadcast(out_shape, rhs2.store_); - auto p_lhs = task.find_or_declare_partition(store_); - auto p_rhs1 = task.find_or_declare_partition(rhs1_store); - auto p_rhs2 = task.find_or_declare_partition(rhs2_store); - - task.add_output(store_, p_lhs); - task.add_input(rhs1_store, p_rhs1); - task.add_input(rhs2_store, p_rhs2); + auto p_lhs = task.add_output(store_); + auto p_rhs1 = task.add_input(rhs1_store); + auto p_rhs2 = task.add_input(rhs2_store); task.add_scalar_arg(legate::Scalar(op_code)); task.add_constraint(align(p_lhs, p_rhs1)); @@ -273,13 +259,9 @@ void NDArray::binary_reduction(int32_t op_code, NDArray rhs1, NDArray rhs2) } auto task = runtime->create_task(CuNumericOpCode::CUNUMERIC_BINARY_RED); - auto p_lhs = task.find_or_declare_partition(store_); - auto p_rhs1 = task.find_or_declare_partition(rhs1_store); - auto p_rhs2 = task.find_or_declare_partition(rhs2_store); - - task.add_reduction(store_, redop, p_lhs); - task.add_input(rhs1_store, p_rhs1); - task.add_input(rhs2_store, p_rhs2); + auto p_lhs = task.add_reduction(store_, redop); + auto p_rhs1 = task.add_input(rhs1_store); + auto p_rhs2 = task.add_input(rhs2_store); task.add_scalar_arg(legate::Scalar(op_code)); task.add_constraint(align(p_rhs1, p_rhs2)); @@ -295,11 +277,8 @@ void NDArray::unary_op(int32_t op_code, NDArray input) auto rhs = broadcast(shape(), input.store_); - auto p_out = task.find_or_declare_partition(store_); - auto p_in = task.find_or_declare_partition(rhs); - - task.add_output(store_, p_out); - task.add_input(rhs, p_in); + auto p_out = task.add_output(store_); + auto p_in = task.add_input(rhs); task.add_scalar_arg(legate::Scalar(op_code)); task.add_constraint(align(p_out, p_in)); @@ -318,13 +297,10 @@ void NDArray::unary_reduction(int32_t op_code_, NDArray input) auto task = runtime->create_task(CuNumericOpCode::CUNUMERIC_SCALAR_UNARY_RED); - auto p_out = task.declare_partition(); - auto p_in = task.declare_partition(); - auto redop = runtime->get_reduction_op(op_code); - task.add_reduction(store_, redop, p_out); - task.add_input(input.store_, p_in); + task.add_reduction(store_, redop); + task.add_input(input.store_); task.add_scalar_arg(legate::Scalar(op_code_)); task.add_scalar_arg(legate::Scalar(input.shape())); @@ -350,16 +326,11 @@ void NDArray::dot(NDArray rhs1, NDArray rhs2) auto task = runtime->create_task(CuNumericOpCode::CUNUMERIC_MATMUL); - // TODO: aliased partitions will be created if the LHS and one of the RHSes are the same store - auto p_lhs = task.find_or_declare_partition(lhs_s); - auto p_rhs1 = task.find_or_declare_partition(rhs1_s); - auto p_rhs2 = task.find_or_declare_partition(rhs2_s); - auto redop = runtime->get_reduction_op(UnaryRedCode::SUM); - task.add_reduction(lhs_s, redop, p_lhs); - task.add_input(rhs1_s, p_rhs1); - task.add_input(rhs2_s, p_rhs2); + auto p_lhs = task.add_reduction(lhs_s, redop); + auto p_rhs1 = task.add_input(rhs1_s); + auto p_rhs2 = task.add_input(rhs2_s); task.add_constraint(align(p_lhs, p_rhs1)); task.add_constraint(align(p_rhs1, p_rhs2)); @@ -377,20 +348,15 @@ void NDArray::arange(double start, double stop, double step) auto task = runtime->create_task(CuNumericOpCode::CUNUMERIC_ARANGE); - auto p_lhs = task.declare_partition(); - auto p_start = task.declare_partition(); - auto p_stop = task.declare_partition(); - auto p_step = task.declare_partition(); - - task.add_output(store_, p_lhs); + task.add_output(store_); auto start_value = runtime->create_scalar_store(Scalar(start)); auto stop_value = runtime->create_scalar_store(Scalar(stop)); auto step_value = runtime->create_scalar_store(Scalar(step)); - task.add_input(start_value, p_start); - task.add_input(stop_value, p_stop); - task.add_input(step_value, p_step); + task.add_input(start_value); + task.add_input(stop_value); + task.add_input(step_value); runtime->submit(std::move(task)); } @@ -405,13 +371,8 @@ std::vector NDArray::nonzero() auto task = runtime->create_task(CuNumericOpCode::CUNUMERIC_NONZERO); - auto p_rhs = task.declare_partition(); - - for (auto& output : outputs) { - auto p_lhs = task.declare_partition(); - task.add_output(output.store_, p_lhs); - } - task.add_input(store_, p_rhs); + for (auto& output : outputs) { task.add_output(output.store_); } + auto p_rhs = task.add_input(store_); task.add_constraint(legate::broadcast(p_rhs, legate::from_range(1, ndim))); @@ -452,11 +413,8 @@ NDArray NDArray::as_type(const legate::Type& type) auto task = runtime->create_task(CuNumericOpCode::CUNUMERIC_CONVERT); - auto p_lhs = task.declare_partition(); - auto p_rhs = task.declare_partition(); - - task.add_output(out.store_, p_lhs); - task.add_input(store_, p_rhs); + auto p_lhs = task.add_output(out.store_); + auto p_rhs = task.add_input(store_); task.add_scalar_arg(legate::Scalar((int32_t)ConvertCode::NOOP)); task.add_constraint(align(p_lhs, p_rhs)); @@ -470,10 +428,9 @@ void NDArray::create_window(int32_t op_code, int64_t M, std::vector args { auto runtime = CuNumericRuntime::get_runtime(); - auto task = runtime->create_task(CuNumericOpCode::CUNUMERIC_WINDOW); - auto p_lhs = task.declare_partition(); + auto task = runtime->create_task(CuNumericOpCode::CUNUMERIC_WINDOW); - task.add_output(store_, p_lhs); + task.add_output(store_); task.add_scalar_arg(legate::Scalar(op_code)); task.add_scalar_arg(legate::Scalar(M)); From 8d41fa775cbb35887ffc3c0b32e81ed44d8cf6af Mon Sep 17 00:00:00 2001 From: Wonchan Lee Date: Mon, 11 Sep 2023 18:10:45 -0700 Subject: [PATCH 096/462] Remove an invalid test case (#15) * Remove an invalid test case * Early exit in some operators on empty arrays * Revert "Remove an invalid test case" This reverts commit 05832cbbcc64684a358cd46e8db21ad787b378fc. --- src/cunumeric/ndarray.cc | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/src/cunumeric/ndarray.cc b/src/cunumeric/ndarray.cc index aa9d90bc07..e845256eb8 100644 --- a/src/cunumeric/ndarray.cc +++ b/src/cunumeric/ndarray.cc @@ -128,6 +128,8 @@ void NDArray::assign(const legate::Scalar& other) void NDArray::random(int32_t gen_code) { + if (size() == 0) return; + auto runtime = CuNumericRuntime::get_runtime(); auto task = runtime->create_task(CuNumericOpCode::CUNUMERIC_RAND); @@ -143,6 +145,8 @@ void NDArray::random(int32_t gen_code) void NDArray::fill(const Scalar& value, bool argval) { + if (size() == 0) return; + auto runtime = CuNumericRuntime::get_runtime(); auto fill_value = runtime->create_scalar_store(value); @@ -158,6 +162,8 @@ void NDArray::fill(const Scalar& value, bool argval) void NDArray::eye(int32_t k) { + if (size() == 0) return; + assert(dim() == 2); auto zero = legate::type_dispatch(type().code(), generate_zero_fn{}); @@ -176,6 +182,8 @@ void NDArray::eye(int32_t k) void NDArray::bincount(NDArray rhs, std::optional weights /*=std::nullopt*/) { + if (size() == 0) return; + assert(dim() == 1); auto runtime = CuNumericRuntime::get_runtime(); @@ -201,6 +209,8 @@ void NDArray::bincount(NDArray rhs, std::optional weights /*=std::nullo void NDArray::trilu(NDArray rhs, int32_t k, bool lower) { + if (size() == 0) return; + auto runtime = CuNumericRuntime::get_runtime(); auto task = runtime->create_task(CuNumericOpCode::CUNUMERIC_TRILU); @@ -223,6 +233,8 @@ void NDArray::binary_op(int32_t op_code, NDArray rhs1, NDArray rhs2) { if (rhs1.type() != rhs2.type()) throw std::invalid_argument("Operands must have the same type"); + if (size() == 0) return; + auto runtime = CuNumericRuntime::get_runtime(); auto task = runtime->create_task(CuNumericOpCode::CUNUMERIC_BINARY_OP); @@ -244,6 +256,8 @@ void NDArray::binary_op(int32_t op_code, NDArray rhs1, NDArray rhs2) void NDArray::binary_reduction(int32_t op_code, NDArray rhs1, NDArray rhs2) { + if (size() == 0) return; + auto runtime = CuNumericRuntime::get_runtime(); auto rhs1_store = broadcast(rhs1, rhs2); @@ -271,6 +285,8 @@ void NDArray::binary_reduction(int32_t op_code, NDArray rhs1, NDArray rhs2) void NDArray::unary_op(int32_t op_code, NDArray input) { + if (size() == 0) return; + auto runtime = CuNumericRuntime::get_runtime(); auto task = runtime->create_task(CuNumericOpCode::CUNUMERIC_UNARY_OP); @@ -288,6 +304,8 @@ void NDArray::unary_op(int32_t op_code, NDArray input) void NDArray::unary_reduction(int32_t op_code_, NDArray input) { + if (size() == 0) return; + auto runtime = CuNumericRuntime::get_runtime(); auto op_code = static_cast(op_code_); @@ -309,6 +327,8 @@ void NDArray::unary_reduction(int32_t op_code_, NDArray input) void NDArray::dot(NDArray rhs1, NDArray rhs2) { + if (size() == 0) return; + auto runtime = CuNumericRuntime::get_runtime(); auto identity = runtime->get_reduction_identity(UnaryRedCode::SUM, type()); @@ -340,6 +360,8 @@ void NDArray::dot(NDArray rhs1, NDArray rhs2) void NDArray::arange(double start, double stop, double step) { + if (size() == 0) return; + auto runtime = CuNumericRuntime::get_runtime(); assert(dim() == 1); @@ -409,6 +431,8 @@ NDArray NDArray::as_type(const legate::Type& type) auto out = runtime->create_array(shape(), type); + if (size() == 0) return std::move(out); + assert(store_.type() != out.store_.type()); auto task = runtime->create_task(CuNumericOpCode::CUNUMERIC_CONVERT); @@ -426,6 +450,8 @@ NDArray NDArray::as_type(const legate::Type& type) void NDArray::create_window(int32_t op_code, int64_t M, std::vector args) { + if (size() == 0) return; + auto runtime = CuNumericRuntime::get_runtime(); auto task = runtime->create_task(CuNumericOpCode::CUNUMERIC_WINDOW); From 7a937d45973a16526414b328cb722b5298e5c9fc Mon Sep 17 00:00:00 2001 From: Wonchan Lee Date: Sat, 16 Sep 2023 00:27:05 -0700 Subject: [PATCH 097/462] Convolve implementation in C++ (#16) * Convolve implementation * Bump the Legate core commit --- cmake/versions.json | 2 +- src/cunumeric/ndarray.cc | 22 ++++++++++++++++++++++ src/cunumeric/ndarray.h | 1 + src/cunumeric/operators.cc | 12 ++++++++++++ src/cunumeric/operators.h | 2 ++ tests/cpp/CMakeLists.txt | 2 -- 6 files changed, 38 insertions(+), 3 deletions(-) diff --git a/cmake/versions.json b/cmake/versions.json index 96905fca0d..f8660947d4 100644 --- a/cmake/versions.json +++ b/cmake/versions.json @@ -12,7 +12,7 @@ "git_url" : "https://github.com/nv-legate/legate.core.internal.git", "git_shallow": false, "always_download": false, - "git_tag" : "ef3a2942a4b6e93124656421c02ce9a36dc6fd0d" + "git_tag" : "8290628006a01ee1efda76d8b7393f97aba23807" } } } diff --git a/src/cunumeric/ndarray.cc b/src/cunumeric/ndarray.cc index e845256eb8..0232b1e93e 100644 --- a/src/cunumeric/ndarray.cc +++ b/src/cunumeric/ndarray.cc @@ -465,6 +465,28 @@ void NDArray::create_window(int32_t op_code, int64_t M, std::vector args runtime->submit(std::move(task)); } +void NDArray::convolve(NDArray input, NDArray filter) +{ + auto runtime = CuNumericRuntime::get_runtime(); + + auto task = runtime->create_task(CuNumericOpCode::CUNUMERIC_CONVOLVE); + + auto p_filter = task.add_input(filter.store_); + auto p_input = task.add_input(input.store_); + auto p_halo = task.declare_partition(); + task.add_input(input.store_, p_halo); + auto p_output = task.add_output(store_); + task.add_scalar_arg(legate::Scalar(shape())); + + auto offsets = (filter.store_.extents() + size_t{1}) / size_t{2}; + + task.add_constraint(legate::align(p_input, p_output)); + task.add_constraint(legate::bloat(p_input, p_halo, offsets, offsets)); + task.add_constraint(legate::broadcast(p_filter, legate::from_range(dim()))); + + runtime->submit(std::move(task)); +} + legate::LogicalStore NDArray::get_store() { return store_; } legate::LogicalStore NDArray::broadcast(const std::vector& shape, diff --git a/src/cunumeric/ndarray.h b/src/cunumeric/ndarray.h index f367416c9b..d0b6c0f29c 100644 --- a/src/cunumeric/ndarray.h +++ b/src/cunumeric/ndarray.h @@ -82,6 +82,7 @@ class NDArray { NDArray unique(); void create_window(int32_t op_code, int64_t M, std::vector args); void bincount(NDArray rhs, std::optional weights = std::nullopt); + void convolve(NDArray input, NDArray filter); public: NDArray as_type(const legate::Type& type); diff --git a/src/cunumeric/operators.cc b/src/cunumeric/operators.cc index fe57e68cb0..5255b58a8b 100644 --- a/src/cunumeric/operators.cc +++ b/src/cunumeric/operators.cc @@ -296,4 +296,16 @@ NDArray hanning(int64_t M) { return create_window(M, WindowOpCode::HANNING, {}); NDArray kaiser(int64_t M, double beta) { return create_window(M, WindowOpCode::KAISER, {beta}); } +NDArray convolve(NDArray a, NDArray v) +{ + if (a.dim() != v.dim()) { throw std::invalid_argument("Arrays should have the same dimensions"); } + if (a.dim() > 3) { + throw std::runtime_error(std::to_string(a.dim()) + "-D arrays are not yet supported"); + } + auto out = CuNumericRuntime::get_runtime()->create_array(a.shape(), a.type()); + if (a.type() != v.type()) { v = v.as_type(a.type()); } + out.convolve(std::move(a), std::move(v)); + return out; +} + } // namespace cunumeric diff --git a/src/cunumeric/operators.h b/src/cunumeric/operators.h index 4c97502f15..f896e9ef9e 100644 --- a/src/cunumeric/operators.h +++ b/src/cunumeric/operators.h @@ -85,4 +85,6 @@ NDArray kaiser(int64_t M, double beta); NDArray bincount(NDArray x, std::optional weights = std::nullopt, uint32_t min_length = 0); +NDArray convolve(NDArray a, NDArray v); + } // namespace cunumeric diff --git a/tests/cpp/CMakeLists.txt b/tests/cpp/CMakeLists.txt index 8138054f1b..01a80d729e 100644 --- a/tests/cpp/CMakeLists.txt +++ b/tests/cpp/CMakeLists.txt @@ -38,8 +38,6 @@ enable_testing() file(GLOB main_SRC ${PROJECT_SOURCE_DIR}/main.cc) file(GLOB integration_SRC ${PROJECT_SOURCE_DIR}/integration/*.cc) -file(GLOB tasks_SRC ${PROJECT_SOURCE_DIR}/integration/tasks/*.cc) -file(GLOB unit_SRC ${PROJECT_SOURCE_DIR}/unit/*.cc) if(Legion_USE_CUDA) From a2b908ed277142bdd86252dc74273f244fca207c Mon Sep 17 00:00:00 2001 From: Parag Kulkarni Date: Mon, 25 Sep 2023 21:06:59 +0530 Subject: [PATCH 098/462] - Call get_yaml_and_make_conda_env directly. (#23) - Add gpu_enabled: to conda_build_config.yaml --- .github/workflows/ci-gh.yml | 2 +- .../home/coder/.local/bin/build-cunumeric-conda | 3 +++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/.github/workflows/ci-gh.yml b/.github/workflows/ci-gh.yml index 19cbcb88fa..6beb1000da 100644 --- a/.github/workflows/ci-gh.yml +++ b/.github/workflows/ci-gh.yml @@ -77,7 +77,7 @@ jobs: - name: Create conda env shell: su coder {0} - run: cd ~/; exec entrypoint get-yaml-and-make-conda-env; + run: . conda-utils; get_yaml_and_make_conda_env - name: Build legate.core C++ library shell: su coder {0} diff --git a/continuous_integration/home/coder/.local/bin/build-cunumeric-conda b/continuous_integration/home/coder/.local/bin/build-cunumeric-conda index 0be424252d..dce7283e74 100755 --- a/continuous_integration/home/coder/.local/bin/build-cunumeric-conda +++ b/continuous_integration/home/coder/.local/bin/build-cunumeric-conda @@ -39,6 +39,9 @@ build_cunumeric_conda_package() { numpy: - 1.22 +gpu_enabled: + - ${GPU_ENABLED} + package_version: - "$(git -C ~/cunumeric describe --abbrev=0 --tags | sed 's/[a-zA-Z]//g' | cut -d '.' -f -2).00" EOF From d38e3a226742584ee0d388a3861a4b17b1c28be8 Mon Sep 17 00:00:00 2001 From: Parag Kulkarni Date: Mon, 25 Sep 2023 21:37:49 +0530 Subject: [PATCH 099/462] Address Syntax Error (#18) --- .github/workflows/merge-ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/merge-ci.yml b/.github/workflows/merge-ci.yml index 264547be1b..98604d6079 100644 --- a/.github/workflows/merge-ci.yml +++ b/.github/workflows/merge-ci.yml @@ -7,7 +7,7 @@ on: jobs: merge: - if: github.repository_owner == 'nv-legate' + if: ${{ github.repository_owner == 'nv-legate' }} runs-on: ubuntu-latest steps: - name: Cunumeric Internal repository From 155d9271e94b5a6279161c2c5b838980a1d58ea4 Mon Sep 17 00:00:00 2001 From: Jacob Faibussowitsch Date: Tue, 26 Sep 2023 16:34:18 -0400 Subject: [PATCH 100/462] Feature: `LegateDefined()` (#19) * Switch to LegateDefined() * Add pre-commit hook * Bump legate.core.internal version --- .pre-commit-config.yaml | 8 ++++ cmake/versions.json | 2 +- scripts/hooks/legate_defined.sh | 43 +++++++++++++++++++ src/cunumeric/arg_redop_register.cc | 4 +- src/cunumeric/binary/binary_op.h | 4 +- src/cunumeric/binary/binary_red.h | 4 +- src/cunumeric/bits/packbits.h | 4 +- src/cunumeric/bits/unpackbits.h | 4 +- src/cunumeric/convolution/convolve.h | 4 +- src/cunumeric/cunumeric.cc | 2 +- src/cunumeric/fft/fft.h | 2 +- src/cunumeric/index/advanced_indexing.h | 4 +- src/cunumeric/index/choose.h | 4 +- src/cunumeric/index/putmask.h | 4 +- src/cunumeric/index/repeat.h | 4 +- src/cunumeric/index/wrap.h | 4 +- src/cunumeric/index/zip.h | 4 +- src/cunumeric/item/read.h | 4 +- src/cunumeric/item/write.h | 4 +- src/cunumeric/matrix/contract.h | 4 +- src/cunumeric/matrix/diag.h | 4 +- src/cunumeric/matrix/dot.h | 4 +- src/cunumeric/matrix/gemm.cc | 2 +- src/cunumeric/matrix/gemm.h | 4 +- src/cunumeric/matrix/matmul.cc | 4 +- src/cunumeric/matrix/matmul.h | 4 +- src/cunumeric/matrix/matvecmul.cc | 4 +- src/cunumeric/matrix/matvecmul.h | 4 +- src/cunumeric/matrix/potrf.cc | 2 +- src/cunumeric/matrix/potrf.h | 4 +- src/cunumeric/matrix/solve.cc | 2 +- src/cunumeric/matrix/solve.h | 4 +- src/cunumeric/matrix/syrk.cc | 2 +- src/cunumeric/matrix/syrk.h | 4 +- src/cunumeric/matrix/tile.h | 4 +- src/cunumeric/matrix/transpose.cc | 4 +- src/cunumeric/matrix/transpose.h | 4 +- src/cunumeric/matrix/trilu.h | 4 +- src/cunumeric/matrix/trsm.cc | 2 +- src/cunumeric/matrix/trsm.h | 4 +- src/cunumeric/matrix/util.cc | 10 ++--- src/cunumeric/nullary/arange.h | 4 +- src/cunumeric/nullary/eye.h | 4 +- src/cunumeric/nullary/fill.h | 4 +- src/cunumeric/nullary/window.h | 4 +- src/cunumeric/random/bitgenerator.h | 4 +- src/cunumeric/random/rand.h | 4 +- src/cunumeric/random/randutil/generator.h | 2 +- .../random/randutil/generator_host.cc | 2 +- src/cunumeric/random/randutil/randutil.h | 2 +- .../random/randutil/randutil_curand.h | 2 +- src/cunumeric/scan/scan_global.h | 4 +- src/cunumeric/scan/scan_local.h | 4 +- src/cunumeric/search/argwhere.h | 4 +- src/cunumeric/search/nonzero.h | 4 +- src/cunumeric/set/unique.h | 4 +- src/cunumeric/set/unique_reduce.h | 2 +- src/cunumeric/sort/searchsorted.h | 4 +- src/cunumeric/sort/sort.h | 4 +- src/cunumeric/stat/bincount.h | 4 +- src/cunumeric/stat/histogram.h | 4 +- src/cunumeric/stat/histogram_cpu.h | 2 +- src/cunumeric/ternary/where.h | 4 +- src/cunumeric/transform/flip.h | 4 +- src/cunumeric/unary/convert.h | 4 +- src/cunumeric/unary/scalar_unary_red.h | 4 +- src/cunumeric/unary/unary_op.h | 4 +- src/cunumeric/unary/unary_red.cu | 15 ++++--- src/cunumeric/unary/unary_red.h | 4 +- 69 files changed, 179 insertions(+), 129 deletions(-) create mode 100755 scripts/hooks/legate_defined.sh diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 1b637e8ae7..d456385d60 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -32,6 +32,14 @@ repos: entry: python scripts/hooks/enforce_boilerplate.py language: python pass_filenames: false + - id: legate-defined + name: legate-defined + description: 'Find uses of ifdef LEGATE_ that should be using LegateDefined()' + entry: ./scripts/pre-commit/legate_defined.sh + language: script + 'types_or': [c++, c, cuda] + require_serial: false + stages: [pre-commit] ci: skip: [mypy] diff --git a/cmake/versions.json b/cmake/versions.json index f8660947d4..85c3b37974 100644 --- a/cmake/versions.json +++ b/cmake/versions.json @@ -12,7 +12,7 @@ "git_url" : "https://github.com/nv-legate/legate.core.internal.git", "git_shallow": false, "always_download": false, - "git_tag" : "8290628006a01ee1efda76d8b7393f97aba23807" + "git_tag" : "3f138644e1ba8c8c3109a20f5a6a9feb5a69563c" } } } diff --git a/scripts/hooks/legate_defined.sh b/scripts/hooks/legate_defined.sh new file mode 100755 index 0000000000..e31c006250 --- /dev/null +++ b/scripts/hooks/legate_defined.sh @@ -0,0 +1,43 @@ +#!/usr/bin/env bash +output=$( + grep -E \ + -n \ + -H \ + -C 1 \ + --color=always \ + -e '#\s*if[n]?def\s+LEGATE_\w+' \ + -e '#(\s*if\s+)?[!]?defined\s*\(\s*LEGATE_\w+' \ + -e '#.*defined\s*\(\s*LEGATE_\w+' \ + -e '#\s*elif\s+LEGATE_\w+' \ + -- \ + "$@" + ) +rc=$? +if [[ ${rc} -eq 1 ]]; then + # no matches found, that's a good thing + exit 0 +elif [[ ${rc} -eq 0 ]]; then + echo "x ===------------------------------------------------------------------=== x" + echo "${output}" + echo "" + echo "Instances of preprocessor ifdef/ifndef/if defined found, use" + echo "LegateDefined() instead:" + echo "" + echo "- #ifdef LEGATE_USE_FOO" + echo "- #include \"foo.h\"" + echo "- #endif" + echo "+ #if LegateDefined(LEGATE_USE_FOO)" + echo "+ #include \"foo.h\"" + echo "+ #endif" + echo "" + echo "- #ifdef LEGATE_USE_FOO" + echo "- x = 2;" + echo "- #endif" + echo "+ if (LegateDefined(LEGATE_USE_FOO)) {" + echo "+ x = 2;" + echo "+ }" + echo "x ===------------------------------------------------------------------=== x" + exit 1 +else + exit ${rc} +fi diff --git a/src/cunumeric/arg_redop_register.cc b/src/cunumeric/arg_redop_register.cc index 454d369959..ffb334046b 100644 --- a/src/cunumeric/arg_redop_register.cc +++ b/src/cunumeric/arg_redop_register.cc @@ -57,8 +57,7 @@ DEFINE_IDENTITIES(uint64_t) } // namespace cunumeric -#ifndef LEGATE_USE_CUDA - +#if !LegateDefined(LEGATE_USE_CUDA) extern "C" { ReductionOpIds cunumeric_register_reduction_ops(int32_t code) @@ -67,5 +66,4 @@ ReductionOpIds cunumeric_register_reduction_ops(int32_t code) cunumeric::register_reduction_op_fn{}); } } - #endif diff --git a/src/cunumeric/binary/binary_op.h b/src/cunumeric/binary/binary_op.h index 7e5334257a..5828cf9d4e 100644 --- a/src/cunumeric/binary/binary_op.h +++ b/src/cunumeric/binary/binary_op.h @@ -35,10 +35,10 @@ class BinaryOpTask : public CuNumericTask { public: static void cpu_variant(legate::TaskContext context); -#ifdef LEGATE_USE_OPENMP +#if LegateDefined(LEGATE_USE_OPENMP) static void omp_variant(legate::TaskContext context); #endif -#ifdef LEGATE_USE_CUDA +#if LegateDefined(LEGATE_USE_CUDA) static void gpu_variant(legate::TaskContext context); #endif }; diff --git a/src/cunumeric/binary/binary_red.h b/src/cunumeric/binary/binary_red.h index ca11bb62fb..e5db4c8a83 100644 --- a/src/cunumeric/binary/binary_red.h +++ b/src/cunumeric/binary/binary_red.h @@ -35,10 +35,10 @@ class BinaryRedTask : public CuNumericTask { public: static void cpu_variant(legate::TaskContext context); -#ifdef LEGATE_USE_OPENMP +#if LegateDefined(LEGATE_USE_OPENMP) static void omp_variant(legate::TaskContext context); #endif -#ifdef LEGATE_USE_CUDA +#if LegateDefined(LEGATE_USE_CUDA) static void gpu_variant(legate::TaskContext context); #endif }; diff --git a/src/cunumeric/bits/packbits.h b/src/cunumeric/bits/packbits.h index 1af0b45489..712e24da08 100644 --- a/src/cunumeric/bits/packbits.h +++ b/src/cunumeric/bits/packbits.h @@ -107,10 +107,10 @@ class PackbitsTask : public CuNumericTask { public: static void cpu_variant(legate::TaskContext context); -#ifdef LEGATE_USE_OPENMP +#if LegateDefined(LEGATE_USE_OPENMP) static void omp_variant(legate::TaskContext context); #endif -#ifdef LEGATE_USE_CUDA +#if LegateDefined(LEGATE_USE_CUDA) static void gpu_variant(legate::TaskContext context); #endif }; diff --git a/src/cunumeric/bits/unpackbits.h b/src/cunumeric/bits/unpackbits.h index eb7ed47c94..0b5fc0b8ad 100644 --- a/src/cunumeric/bits/unpackbits.h +++ b/src/cunumeric/bits/unpackbits.h @@ -64,10 +64,10 @@ class UnpackbitsTask : public CuNumericTask { public: static void cpu_variant(legate::TaskContext context); -#ifdef LEGATE_USE_OPENMP +#if LegateDefined(LEGATE_USE_OPENMP) static void omp_variant(legate::TaskContext context); #endif -#ifdef LEGATE_USE_CUDA +#if LegateDefined(LEGATE_USE_CUDA) static void gpu_variant(legate::TaskContext context); #endif }; diff --git a/src/cunumeric/convolution/convolve.h b/src/cunumeric/convolution/convolve.h index 32d4439ce7..cde7e36e1f 100644 --- a/src/cunumeric/convolution/convolve.h +++ b/src/cunumeric/convolution/convolve.h @@ -42,10 +42,10 @@ class ConvolveTask : public CuNumericTask { public: static void cpu_variant(legate::TaskContext context); -#ifdef LEGATE_USE_OPENMP +#if LegateDefined(LEGATE_USE_OPENMP) static void omp_variant(legate::TaskContext context); #endif -#ifdef LEGATE_USE_CUDA +#if LegateDefined(LEGATE_USE_CUDA) static void gpu_variant(legate::TaskContext context); #endif }; diff --git a/src/cunumeric/cunumeric.cc b/src/cunumeric/cunumeric.cc index 6546bba501..9b9c7d6bc6 100644 --- a/src/cunumeric/cunumeric.cc +++ b/src/cunumeric/cunumeric.cc @@ -56,7 +56,7 @@ void cunumeric_perform_registration(void) bool cunumeric_has_curand() { -#if defined(LEGATE_USE_CUDA) || defined(CUNUMERIC_CURAND_FOR_CPU_BUILD) +#if LegateDefined(LEGATE_USE_CUDA) || defined(CUNUMERIC_CURAND_FOR_CPU_BUILD) return true; #else return false; diff --git a/src/cunumeric/fft/fft.h b/src/cunumeric/fft/fft.h index 0e32dcb68f..680da5bfc1 100644 --- a/src/cunumeric/fft/fft.h +++ b/src/cunumeric/fft/fft.h @@ -35,7 +35,7 @@ class FFTTask : public CuNumericTask { static const int TASK_ID = CUNUMERIC_FFT; public: -#ifdef LEGATE_USE_CUDA +#if LegateDefined(LEGATE_USE_CUDA) static void gpu_variant(legate::TaskContext context); #endif }; diff --git a/src/cunumeric/index/advanced_indexing.h b/src/cunumeric/index/advanced_indexing.h index 68f37152fc..dfe6c27f5e 100644 --- a/src/cunumeric/index/advanced_indexing.h +++ b/src/cunumeric/index/advanced_indexing.h @@ -34,10 +34,10 @@ class AdvancedIndexingTask : public CuNumericTask { public: static void cpu_variant(legate::TaskContext context); -#ifdef LEGATE_USE_OPENMP +#if LegateDefined(LEGATE_USE_OPENMP) static void omp_variant(legate::TaskContext context); #endif -#ifdef LEGATE_USE_CUDA +#if LegateDefined(LEGATE_USE_CUDA) static void gpu_variant(legate::TaskContext context); #endif }; diff --git a/src/cunumeric/index/choose.h b/src/cunumeric/index/choose.h index 5b7d788dfd..e865b233c0 100644 --- a/src/cunumeric/index/choose.h +++ b/src/cunumeric/index/choose.h @@ -31,10 +31,10 @@ class ChooseTask : public CuNumericTask { public: static void cpu_variant(legate::TaskContext context); -#ifdef LEGATE_USE_OPENMP +#if LegateDefined(LEGATE_USE_OPENMP) static void omp_variant(legate::TaskContext context); #endif -#ifdef LEGATE_USE_CUDA +#if LegateDefined(LEGATE_USE_CUDA) static void gpu_variant(legate::TaskContext context); #endif }; diff --git a/src/cunumeric/index/putmask.h b/src/cunumeric/index/putmask.h index 01e51fc8ac..8cfb7ba651 100644 --- a/src/cunumeric/index/putmask.h +++ b/src/cunumeric/index/putmask.h @@ -32,10 +32,10 @@ class PutmaskTask : public CuNumericTask { public: static void cpu_variant(legate::TaskContext context); -#ifdef LEGATE_USE_OPENMP +#if LegateDefined(LEGATE_USE_OPENMP) static void omp_variant(legate::TaskContext context); #endif -#ifdef LEGATE_USE_CUDA +#if LegateDefined(LEGATE_USE_CUDA) static void gpu_variant(legate::TaskContext context); #endif }; diff --git a/src/cunumeric/index/repeat.h b/src/cunumeric/index/repeat.h index 697ffe12cc..88ff59f686 100644 --- a/src/cunumeric/index/repeat.h +++ b/src/cunumeric/index/repeat.h @@ -35,10 +35,10 @@ class RepeatTask : public CuNumericTask { public: static void cpu_variant(legate::TaskContext context); -#ifdef LEGATE_USE_OPENMP +#if LegateDefined(LEGATE_USE_OPENMP) static void omp_variant(legate::TaskContext context); #endif -#ifdef LEGATE_USE_CUDA +#if LegateDefined(LEGATE_USE_CUDA) static void gpu_variant(legate::TaskContext context); #endif }; diff --git a/src/cunumeric/index/wrap.h b/src/cunumeric/index/wrap.h index 64e372a694..3a90621b77 100644 --- a/src/cunumeric/index/wrap.h +++ b/src/cunumeric/index/wrap.h @@ -36,10 +36,10 @@ class WrapTask : public CuNumericTask { public: static void cpu_variant(legate::TaskContext context); -#ifdef LEGATE_USE_OPENMP +#if LegateDefined(LEGATE_USE_OPENMP) static void omp_variant(legate::TaskContext context); #endif -#ifdef LEGATE_USE_CUDA +#if LegateDefined(LEGATE_USE_CUDA) static void gpu_variant(legate::TaskContext context); #endif }; diff --git a/src/cunumeric/index/zip.h b/src/cunumeric/index/zip.h index e9be934b55..e7e330073f 100644 --- a/src/cunumeric/index/zip.h +++ b/src/cunumeric/index/zip.h @@ -35,10 +35,10 @@ class ZipTask : public CuNumericTask { public: static void cpu_variant(legate::TaskContext context); -#ifdef LEGATE_USE_OPENMP +#if LegateDefined(LEGATE_USE_OPENMP) static void omp_variant(legate::TaskContext context); #endif -#ifdef LEGATE_USE_CUDA +#if LegateDefined(LEGATE_USE_CUDA) static void gpu_variant(legate::TaskContext context); #endif }; diff --git a/src/cunumeric/item/read.h b/src/cunumeric/item/read.h index 5b748972ac..d7d03958f7 100644 --- a/src/cunumeric/item/read.h +++ b/src/cunumeric/item/read.h @@ -26,10 +26,10 @@ class ReadTask : public CuNumericTask { public: static void cpu_variant(legate::TaskContext context); -#ifdef LEGATE_USE_OPENMP +#if LegateDefined(LEGATE_USE_OPENMP) static void omp_variant(legate::TaskContext context) { ReadTask::cpu_variant(context); } #endif -#ifdef LEGATE_USE_CUDA +#if LegateDefined(LEGATE_USE_CUDA) static void gpu_variant(legate::TaskContext context); #endif }; diff --git a/src/cunumeric/item/write.h b/src/cunumeric/item/write.h index d86050fabd..25c10d9641 100644 --- a/src/cunumeric/item/write.h +++ b/src/cunumeric/item/write.h @@ -26,10 +26,10 @@ class WriteTask : public CuNumericTask { public: static void cpu_variant(legate::TaskContext context); -#ifdef LEGATE_USE_OPENMP +#if LegateDefined(LEGATE_USE_OPENMP) static void omp_variant(legate::TaskContext context) { WriteTask::cpu_variant(context); } #endif -#ifdef LEGATE_USE_CUDA +#if LegateDefined(LEGATE_USE_CUDA) static void gpu_variant(legate::TaskContext context); #endif }; diff --git a/src/cunumeric/matrix/contract.h b/src/cunumeric/matrix/contract.h index 195b5c857e..63b9282aee 100644 --- a/src/cunumeric/matrix/contract.h +++ b/src/cunumeric/matrix/contract.h @@ -35,10 +35,10 @@ class ContractTask : public CuNumericTask { public: static void cpu_variant(legate::TaskContext context); -#ifdef LEGATE_USE_OPENMP +#if LegateDefined(LEGATE_USE_OPENMP) static void omp_variant(legate::TaskContext context); #endif -#ifdef LEGATE_USE_CUDA +#if LegateDefined(LEGATE_USE_CUDA) static void gpu_variant(legate::TaskContext context); #endif }; diff --git a/src/cunumeric/matrix/diag.h b/src/cunumeric/matrix/diag.h index 6d13aa6b35..45ae2fc98d 100644 --- a/src/cunumeric/matrix/diag.h +++ b/src/cunumeric/matrix/diag.h @@ -33,10 +33,10 @@ class DiagTask : public CuNumericTask { public: static void cpu_variant(legate::TaskContext context); -#ifdef LEGATE_USE_OPENMP +#if LegateDefined(LEGATE_USE_OPENMP) static void omp_variant(legate::TaskContext context); #endif -#ifdef LEGATE_USE_CUDA +#if LegateDefined(LEGATE_USE_CUDA) static void gpu_variant(legate::TaskContext context); #endif }; diff --git a/src/cunumeric/matrix/dot.h b/src/cunumeric/matrix/dot.h index fe0981207a..50d787f2df 100644 --- a/src/cunumeric/matrix/dot.h +++ b/src/cunumeric/matrix/dot.h @@ -32,10 +32,10 @@ class DotTask : public CuNumericTask { public: static void cpu_variant(legate::TaskContext context); -#ifdef LEGATE_USE_OPENMP +#if LegateDefined(LEGATE_USE_OPENMP) static void omp_variant(legate::TaskContext context); #endif -#ifdef LEGATE_USE_CUDA +#if LegateDefined(LEGATE_USE_CUDA) static void gpu_variant(legate::TaskContext context); #endif }; diff --git a/src/cunumeric/matrix/gemm.cc b/src/cunumeric/matrix/gemm.cc index 459ba263e6..a15b323030 100644 --- a/src/cunumeric/matrix/gemm.cc +++ b/src/cunumeric/matrix/gemm.cc @@ -99,7 +99,7 @@ struct GemmImplBody { /*static*/ void GemmTask::cpu_variant(TaskContext context) { -#ifdef LEGATE_USE_OPENMP +#if LegateDefined(LEGATE_USE_OPENMP) openblas_set_num_threads(1); // make sure this isn't overzealous #endif gemm_template(context); diff --git a/src/cunumeric/matrix/gemm.h b/src/cunumeric/matrix/gemm.h index 3aa47c2dc4..a84f3e1d5d 100644 --- a/src/cunumeric/matrix/gemm.h +++ b/src/cunumeric/matrix/gemm.h @@ -26,10 +26,10 @@ class GemmTask : public CuNumericTask { public: static void cpu_variant(legate::TaskContext context); -#ifdef LEGATE_USE_OPENMP +#if LegateDefined(LEGATE_USE_OPENMP) static void omp_variant(legate::TaskContext context); #endif -#ifdef LEGATE_USE_CUDA +#if LegateDefined(LEGATE_USE_CUDA) static void gpu_variant(legate::TaskContext context); #endif }; diff --git a/src/cunumeric/matrix/matmul.cc b/src/cunumeric/matrix/matmul.cc index b29adbeb5a..f3cfb1bf18 100644 --- a/src/cunumeric/matrix/matmul.cc +++ b/src/cunumeric/matrix/matmul.cc @@ -19,7 +19,7 @@ #include "cunumeric/matrix/matmul_cpu.inl" #include -#ifdef LEGATE_USE_OPENMP +#if LegateDefined(LEGATE_USE_OPENMP) #include #endif @@ -29,7 +29,7 @@ using namespace legate; /*static*/ void MatMulTask::cpu_variant(TaskContext context) { -#ifdef LEGATE_USE_OPENMP +#if LegateDefined(LEGATE_USE_OPENMP) openblas_set_num_threads(1); // make sure this isn't overzealous #endif matmul_template(context); diff --git a/src/cunumeric/matrix/matmul.h b/src/cunumeric/matrix/matmul.h index 8f262396de..e93553af64 100644 --- a/src/cunumeric/matrix/matmul.h +++ b/src/cunumeric/matrix/matmul.h @@ -32,10 +32,10 @@ class MatMulTask : public CuNumericTask { public: static void cpu_variant(legate::TaskContext context); -#ifdef LEGATE_USE_OPENMP +#if LegateDefined(LEGATE_USE_OPENMP) static void omp_variant(legate::TaskContext context); #endif -#ifdef LEGATE_USE_CUDA +#if LegateDefined(LEGATE_USE_CUDA) static void gpu_variant(legate::TaskContext context); #endif }; diff --git a/src/cunumeric/matrix/matvecmul.cc b/src/cunumeric/matrix/matvecmul.cc index 115d004420..3df1d2e398 100644 --- a/src/cunumeric/matrix/matvecmul.cc +++ b/src/cunumeric/matrix/matvecmul.cc @@ -19,7 +19,7 @@ #include "cunumeric/matrix/matvecmul_cpu.inl" #include -#ifdef LEGATE_USE_OPENMP +#if LegateDefined(LEGATE_USE_OPENMP) #include #endif @@ -29,7 +29,7 @@ using namespace legate; /*static*/ void MatVecMulTask::cpu_variant(TaskContext context) { -#ifdef LEGATE_USE_OPENMP +#if LegateDefined(LEGATE_USE_OPENMP) openblas_set_num_threads(1); // make sure this isn't overzealous #endif matvecmul_template(context); diff --git a/src/cunumeric/matrix/matvecmul.h b/src/cunumeric/matrix/matvecmul.h index 09fdd3aa55..abca3c6465 100644 --- a/src/cunumeric/matrix/matvecmul.h +++ b/src/cunumeric/matrix/matvecmul.h @@ -32,10 +32,10 @@ class MatVecMulTask : public CuNumericTask { public: static void cpu_variant(legate::TaskContext context); -#ifdef LEGATE_USE_OPENMP +#if LegateDefined(LEGATE_USE_OPENMP) static void omp_variant(legate::TaskContext context); #endif -#ifdef LEGATE_USE_CUDA +#if LegateDefined(LEGATE_USE_CUDA) static void gpu_variant(legate::TaskContext context); #endif }; diff --git a/src/cunumeric/matrix/potrf.cc b/src/cunumeric/matrix/potrf.cc index ac59afb3df..0e1742561c 100644 --- a/src/cunumeric/matrix/potrf.cc +++ b/src/cunumeric/matrix/potrf.cc @@ -70,7 +70,7 @@ struct PotrfImplBody { /*static*/ void PotrfTask::cpu_variant(TaskContext context) { -#ifdef LEGATE_USE_OPENMP +#if LegateDefined(LEGATE_USE_OPENMP) openblas_set_num_threads(1); // make sure this isn't overzealous #endif potrf_template(context); diff --git a/src/cunumeric/matrix/potrf.h b/src/cunumeric/matrix/potrf.h index b7abf930a5..7cc81354af 100644 --- a/src/cunumeric/matrix/potrf.h +++ b/src/cunumeric/matrix/potrf.h @@ -26,10 +26,10 @@ class PotrfTask : public CuNumericTask { public: static void cpu_variant(legate::TaskContext context); -#ifdef LEGATE_USE_OPENMP +#if LegateDefined(LEGATE_USE_OPENMP) static void omp_variant(legate::TaskContext context); #endif -#ifdef LEGATE_USE_CUDA +#if LegateDefined(LEGATE_USE_CUDA) static void gpu_variant(legate::TaskContext context); #endif }; diff --git a/src/cunumeric/matrix/solve.cc b/src/cunumeric/matrix/solve.cc index 53c4d5f56c..4dd7b78bf6 100644 --- a/src/cunumeric/matrix/solve.cc +++ b/src/cunumeric/matrix/solve.cc @@ -26,7 +26,7 @@ using namespace legate; /*static*/ void SolveTask::cpu_variant(TaskContext context) { -#ifdef LEGATE_USE_OPENMP +#if LegateDefined(LEGATE_USE_OPENMP) openblas_set_num_threads(1); // make sure this isn't overzealous #endif solve_template(context); diff --git a/src/cunumeric/matrix/solve.h b/src/cunumeric/matrix/solve.h index 58d3237177..d5a47a5c00 100644 --- a/src/cunumeric/matrix/solve.h +++ b/src/cunumeric/matrix/solve.h @@ -27,10 +27,10 @@ class SolveTask : public CuNumericTask { public: static void cpu_variant(legate::TaskContext context); -#ifdef LEGATE_USE_OPENMP +#if LegateDefined(LEGATE_USE_OPENMP) static void omp_variant(legate::TaskContext context); #endif -#ifdef LEGATE_USE_CUDA +#if LegateDefined(LEGATE_USE_CUDA) static void gpu_variant(legate::TaskContext context); #endif }; diff --git a/src/cunumeric/matrix/syrk.cc b/src/cunumeric/matrix/syrk.cc index 0d90b6146a..bcc5992931 100644 --- a/src/cunumeric/matrix/syrk.cc +++ b/src/cunumeric/matrix/syrk.cc @@ -77,7 +77,7 @@ struct SyrkImplBody { /*static*/ void SyrkTask::cpu_variant(TaskContext context) { -#ifdef LEGATE_USE_OPENMP +#if LegateDefined(LEGATE_USE_OPENMP) openblas_set_num_threads(1); // make sure this isn't overzealous #endif syrk_template(context); diff --git a/src/cunumeric/matrix/syrk.h b/src/cunumeric/matrix/syrk.h index 61917851cb..203c3b7326 100644 --- a/src/cunumeric/matrix/syrk.h +++ b/src/cunumeric/matrix/syrk.h @@ -26,10 +26,10 @@ class SyrkTask : public CuNumericTask { public: static void cpu_variant(legate::TaskContext context); -#ifdef LEGATE_USE_OPENMP +#if LegateDefined(LEGATE_USE_OPENMP) static void omp_variant(legate::TaskContext context); #endif -#ifdef LEGATE_USE_CUDA +#if LegateDefined(LEGATE_USE_CUDA) static void gpu_variant(legate::TaskContext context); #endif }; diff --git a/src/cunumeric/matrix/tile.h b/src/cunumeric/matrix/tile.h index 1d63be12ae..007387fe39 100644 --- a/src/cunumeric/matrix/tile.h +++ b/src/cunumeric/matrix/tile.h @@ -31,10 +31,10 @@ class TileTask : public CuNumericTask { public: static void cpu_variant(legate::TaskContext context); -#ifdef LEGATE_USE_OPENMP +#if LegateDefined(LEGATE_USE_OPENMP) static void omp_variant(legate::TaskContext context); #endif -#ifdef LEGATE_USE_CUDA +#if LegateDefined(LEGATE_USE_CUDA) static void gpu_variant(legate::TaskContext context); #endif }; diff --git a/src/cunumeric/matrix/transpose.cc b/src/cunumeric/matrix/transpose.cc index 4509ccfba2..1fe0f5f496 100644 --- a/src/cunumeric/matrix/transpose.cc +++ b/src/cunumeric/matrix/transpose.cc @@ -17,7 +17,7 @@ #include "cunumeric/matrix/transpose.h" #include "cunumeric/matrix/transpose_template.inl" -#ifdef LEGATE_USE_OPENMP +#if LegateDefined(LEGATE_USE_OPENMP) #include "omp.h" #endif #include "cblas.h" @@ -60,7 +60,7 @@ struct TransposeImplBody { /*static*/ void TransposeTask::cpu_variant(TaskContext context) { -#ifdef LEGATE_USE_OPENMP +#if LegateDefined(LEGATE_USE_OPENMP) openblas_set_num_threads(1); // make sure this isn't overzealous #endif transpose_template(context); diff --git a/src/cunumeric/matrix/transpose.h b/src/cunumeric/matrix/transpose.h index 1c15970197..12ca568310 100644 --- a/src/cunumeric/matrix/transpose.h +++ b/src/cunumeric/matrix/transpose.h @@ -32,10 +32,10 @@ class TransposeTask : public CuNumericTask { public: static void cpu_variant(legate::TaskContext context); -#ifdef LEGATE_USE_OPENMP +#if LegateDefined(LEGATE_USE_OPENMP) static void omp_variant(legate::TaskContext context); #endif -#ifdef LEGATE_USE_CUDA +#if LegateDefined(LEGATE_USE_CUDA) static void gpu_variant(legate::TaskContext context); #endif }; diff --git a/src/cunumeric/matrix/trilu.h b/src/cunumeric/matrix/trilu.h index 29e18daea3..dcce0e813d 100644 --- a/src/cunumeric/matrix/trilu.h +++ b/src/cunumeric/matrix/trilu.h @@ -33,10 +33,10 @@ class TriluTask : public CuNumericTask { public: static void cpu_variant(legate::TaskContext context); -#ifdef LEGATE_USE_OPENMP +#if LegateDefined(LEGATE_USE_OPENMP) static void omp_variant(legate::TaskContext context); #endif -#ifdef LEGATE_USE_CUDA +#if LegateDefined(LEGATE_USE_CUDA) static void gpu_variant(legate::TaskContext context); #endif }; diff --git a/src/cunumeric/matrix/trsm.cc b/src/cunumeric/matrix/trsm.cc index 678ae48aa2..880fa5ee57 100644 --- a/src/cunumeric/matrix/trsm.cc +++ b/src/cunumeric/matrix/trsm.cc @@ -88,7 +88,7 @@ struct TrsmImplBody { /*static*/ void TrsmTask::cpu_variant(TaskContext context) { -#ifdef LEGATE_USE_OPENMP +#if LegateDefined(LEGATE_USE_OPENMP) openblas_set_num_threads(1); // make sure this isn't overzealous #endif trsm_template(context); diff --git a/src/cunumeric/matrix/trsm.h b/src/cunumeric/matrix/trsm.h index 5bb65f8c6d..b34a4c7af4 100644 --- a/src/cunumeric/matrix/trsm.h +++ b/src/cunumeric/matrix/trsm.h @@ -26,10 +26,10 @@ class TrsmTask : public CuNumericTask { public: static void cpu_variant(legate::TaskContext context); -#ifdef LEGATE_USE_OPENMP +#if LegateDefined(LEGATE_USE_OPENMP) static void omp_variant(legate::TaskContext context); #endif -#ifdef LEGATE_USE_CUDA +#if LegateDefined(LEGATE_USE_CUDA) static void gpu_variant(legate::TaskContext context); #endif }; diff --git a/src/cunumeric/matrix/util.cc b/src/cunumeric/matrix/util.cc index 0c7db195a5..03b74e1c42 100644 --- a/src/cunumeric/matrix/util.cc +++ b/src/cunumeric/matrix/util.cc @@ -16,7 +16,7 @@ #include "core/data/buffer.h" #include "cunumeric/matrix/util.h" -#ifdef LEGATE_USE_OPENMP +#if LegateDefined(LEGATE_USE_OPENMP) #include #endif @@ -80,7 +80,7 @@ float* allocate_buffer(size_t size) void half_vector_to_float(float* out, const __half* ptr, size_t n) { -#ifdef LEGATE_USE_OPENMP +#if LegateDefined(LEGATE_USE_OPENMP) if (legate::Processor::get_executing_processor().kind() == legate::Processor::OMP_PROC) { #pragma omp parallel for schedule(static) for (size_t idx = 0; idx < n; idx++) out[idx] = ptr[idx]; @@ -92,7 +92,7 @@ void half_vector_to_float(float* out, const __half* ptr, size_t n) void half_matrix_to_float(float* out, const __half* ptr, size_t m, size_t n, size_t pitch) { -#ifdef LEGATE_USE_OPENMP +#if LegateDefined(LEGATE_USE_OPENMP) if (legate::Processor::get_executing_processor().kind() == legate::Processor::OMP_PROC) { #pragma omp parallel for schedule(static) for (size_t i = 0; i < m; i++) @@ -108,7 +108,7 @@ void half_tensor_to_float( float* out, const __half* in, size_t ndim, const int64_t* shape, const int64_t* in_strides) { int64_t volume = calculate_volume(ndim, shape); -#ifdef LEGATE_USE_OPENMP +#if LegateDefined(LEGATE_USE_OPENMP) if (legate::Processor::get_executing_processor().kind() == legate::Processor::OMP_PROC) { #pragma omp parallel for schedule(static) for (int64_t out_idx = 0; out_idx < volume; ++out_idx) { @@ -128,7 +128,7 @@ void float_tensor_to_half( __half* out, const float* in, size_t ndim, const int64_t* shape, const int64_t* out_strides) { int64_t volume = calculate_volume(ndim, shape); -#ifdef LEGATE_USE_OPENMP +#if LegateDefined(LEGATE_USE_OPENMP) if (legate::Processor::get_executing_processor().kind() == legate::Processor::OMP_PROC) { #pragma omp parallel for schedule(static) for (int64_t in_idx = 0; in_idx < volume; ++in_idx) { diff --git a/src/cunumeric/nullary/arange.h b/src/cunumeric/nullary/arange.h index 1d70922a3a..a8bbb4c927 100644 --- a/src/cunumeric/nullary/arange.h +++ b/src/cunumeric/nullary/arange.h @@ -33,10 +33,10 @@ class ArangeTask : public CuNumericTask { public: static void cpu_variant(legate::TaskContext context); -#ifdef LEGATE_USE_OPENMP +#if LegateDefined(LEGATE_USE_OPENMP) static void omp_variant(legate::TaskContext context); #endif -#ifdef LEGATE_USE_CUDA +#if LegateDefined(LEGATE_USE_CUDA) static void gpu_variant(legate::TaskContext context); #endif }; diff --git a/src/cunumeric/nullary/eye.h b/src/cunumeric/nullary/eye.h index c014538f08..5cf3910e15 100644 --- a/src/cunumeric/nullary/eye.h +++ b/src/cunumeric/nullary/eye.h @@ -31,10 +31,10 @@ class EyeTask : public CuNumericTask { public: static void cpu_variant(legate::TaskContext context); -#ifdef LEGATE_USE_OPENMP +#if LegateDefined(LEGATE_USE_OPENMP) static void omp_variant(legate::TaskContext context); #endif -#ifdef LEGATE_USE_CUDA +#if LegateDefined(LEGATE_USE_CUDA) static void gpu_variant(legate::TaskContext context); #endif }; diff --git a/src/cunumeric/nullary/fill.h b/src/cunumeric/nullary/fill.h index 0444c95c12..ac34e4784d 100644 --- a/src/cunumeric/nullary/fill.h +++ b/src/cunumeric/nullary/fill.h @@ -32,10 +32,10 @@ class FillTask : public CuNumericTask { public: static void cpu_variant(legate::TaskContext context); -#ifdef LEGATE_USE_OPENMP +#if LegateDefined(LEGATE_USE_OPENMP) static void omp_variant(legate::TaskContext context); #endif -#ifdef LEGATE_USE_CUDA +#if LegateDefined(LEGATE_USE_CUDA) static void gpu_variant(legate::TaskContext context); #endif }; diff --git a/src/cunumeric/nullary/window.h b/src/cunumeric/nullary/window.h index 9f767d75fd..80dc5c68bf 100644 --- a/src/cunumeric/nullary/window.h +++ b/src/cunumeric/nullary/window.h @@ -26,10 +26,10 @@ class WindowTask : public CuNumericTask { public: static void cpu_variant(legate::TaskContext context); -#ifdef LEGATE_USE_OPENMP +#if LegateDefined(LEGATE_USE_OPENMP) static void omp_variant(legate::TaskContext context); #endif -#ifdef LEGATE_USE_CUDA +#if LegateDefined(LEGATE_USE_CUDA) static void gpu_variant(legate::TaskContext context); #endif }; diff --git a/src/cunumeric/random/bitgenerator.h b/src/cunumeric/random/bitgenerator.h index 46292d9fd4..6dc9c1d62d 100644 --- a/src/cunumeric/random/bitgenerator.h +++ b/src/cunumeric/random/bitgenerator.h @@ -84,12 +84,12 @@ class BitGeneratorTask : public CuNumericTask { public: static void cpu_variant(legate::TaskContext context); -#ifdef LEGATE_USE_OPENMP +#if LegateDefined(LEGATE_USE_OPENMP) // TODO: Fully parallelized OpenMP implementation for BitGenerator // Doing it this way is safe, but only one thread is being used out of the OpenMP pool. static void omp_variant(legate::TaskContext context) { BitGeneratorTask::cpu_variant(context); } #endif -#ifdef LEGATE_USE_CUDA +#if LegateDefined(LEGATE_USE_CUDA) static void gpu_variant(legate::TaskContext context); #endif }; diff --git a/src/cunumeric/random/rand.h b/src/cunumeric/random/rand.h index 297e51627a..5a38293231 100644 --- a/src/cunumeric/random/rand.h +++ b/src/cunumeric/random/rand.h @@ -35,10 +35,10 @@ class RandTask : public CuNumericTask { public: static void cpu_variant(legate::TaskContext context); -#ifdef LEGATE_USE_OPENMP +#if LegateDefined(LEGATE_USE_OPENMP) static void omp_variant(legate::TaskContext context); #endif -#ifdef LEGATE_USE_CUDA +#if LegateDefined(LEGATE_USE_CUDA) static void gpu_variant(legate::TaskContext context); #endif }; diff --git a/src/cunumeric/random/randutil/generator.h b/src/cunumeric/random/randutil/generator.h index c569365035..544905ec0d 100644 --- a/src/cunumeric/random/randutil/generator.h +++ b/src/cunumeric/random/randutil/generator.h @@ -135,7 +135,7 @@ curandStatus_t dispatch(randutilimpl::basegenerator* gen, func_t func, size_t N, switch (gen->location()) { case randutilimpl::execlocation::HOST: return dispatcher::run(gen, func, N, out); -#ifdef LEGATE_USE_CUDA +#if LegateDefined(LEGATE_USE_CUDA) case randutilimpl::execlocation::DEVICE: return dispatcher::run(gen, func, N, out); #endif diff --git a/src/cunumeric/random/randutil/generator_host.cc b/src/cunumeric/random/randutil/generator_host.cc index 50bcfeefc1..7e080f9b84 100644 --- a/src/cunumeric/random/randutil/generator_host.cc +++ b/src/cunumeric/random/randutil/generator_host.cc @@ -17,7 +17,7 @@ #include "generator.h" #include "generator_create.inl" -#if !defined(LEGATE_USE_CUDA) +#if !LegateDefined(LEGATE_USE_CUDA) // the host code of cuRAND try to extern these variables out of nowhere, // so we need to define them somewhere. const dim3 blockDim{}; diff --git a/src/cunumeric/random/randutil/randutil.h b/src/cunumeric/random/randutil/randutil.h index f0871e14fb..b098f36ce3 100644 --- a/src/cunumeric/random/randutil/randutil.h +++ b/src/cunumeric/random/randutil/randutil.h @@ -23,7 +23,7 @@ typedef void* randutilGenerator_t; /* generator */ // CUDA-ONLY API -#ifdef LEGATE_USE_CUDA +#if LegateDefined(LEGATE_USE_CUDA) extern "C" curandStatus_t randutilCreateGenerator(randutilGenerator_t* generator, curandRngType_t rng_type, uint64_t seed, diff --git a/src/cunumeric/random/randutil/randutil_curand.h b/src/cunumeric/random/randutil/randutil_curand.h index 42e5c19dec..87c09253e7 100644 --- a/src/cunumeric/random/randutil/randutil_curand.h +++ b/src/cunumeric/random/randutil/randutil_curand.h @@ -20,7 +20,7 @@ // generators // also allow usage of generators on host -#ifdef LEGATE_USE_CUDA +#if LegateDefined(LEGATE_USE_CUDA) #define QUALIFIERS static __forceinline__ __device__ __host__ #define RANDUTIL_QUALIFIERS __forceinline__ __device__ __host__ diff --git a/src/cunumeric/scan/scan_global.h b/src/cunumeric/scan/scan_global.h index 2443550926..853a7518fd 100644 --- a/src/cunumeric/scan/scan_global.h +++ b/src/cunumeric/scan/scan_global.h @@ -34,10 +34,10 @@ class ScanGlobalTask : public CuNumericTask { public: static void cpu_variant(legate::TaskContext context); -#ifdef LEGATE_USE_OPENMP +#if LegateDefined(LEGATE_USE_OPENMP) static void omp_variant(legate::TaskContext context); #endif -#ifdef LEGATE_USE_CUDA +#if LegateDefined(LEGATE_USE_CUDA) static void gpu_variant(legate::TaskContext context); #endif }; diff --git a/src/cunumeric/scan/scan_local.h b/src/cunumeric/scan/scan_local.h index 71a6a3dc8c..028141fed3 100644 --- a/src/cunumeric/scan/scan_local.h +++ b/src/cunumeric/scan/scan_local.h @@ -35,10 +35,10 @@ class ScanLocalTask : public CuNumericTask { public: static void cpu_variant(legate::TaskContext context); -#ifdef LEGATE_USE_OPENMP +#if LegateDefined(LEGATE_USE_OPENMP) static void omp_variant(legate::TaskContext context); #endif -#ifdef LEGATE_USE_CUDA +#if LegateDefined(LEGATE_USE_CUDA) static void gpu_variant(legate::TaskContext context); #endif }; diff --git a/src/cunumeric/search/argwhere.h b/src/cunumeric/search/argwhere.h index f62ba2cddc..511f69bc8f 100644 --- a/src/cunumeric/search/argwhere.h +++ b/src/cunumeric/search/argwhere.h @@ -31,10 +31,10 @@ class ArgWhereTask : public CuNumericTask { public: static void cpu_variant(legate::TaskContext context); -#ifdef LEGATE_USE_OPENMP +#if LegateDefined(LEGATE_USE_OPENMP) static void omp_variant(legate::TaskContext context); #endif -#ifdef LEGATE_USE_CUDA +#if LegateDefined(LEGATE_USE_CUDA) static void gpu_variant(legate::TaskContext context); #endif }; diff --git a/src/cunumeric/search/nonzero.h b/src/cunumeric/search/nonzero.h index c756e96217..47d30790f0 100644 --- a/src/cunumeric/search/nonzero.h +++ b/src/cunumeric/search/nonzero.h @@ -31,10 +31,10 @@ class NonzeroTask : public CuNumericTask { public: static void cpu_variant(legate::TaskContext context); -#ifdef LEGATE_USE_OPENMP +#if LegateDefined(LEGATE_USE_OPENMP) static void omp_variant(legate::TaskContext context); #endif -#ifdef LEGATE_USE_CUDA +#if LegateDefined(LEGATE_USE_CUDA) static void gpu_variant(legate::TaskContext context); #endif }; diff --git a/src/cunumeric/set/unique.h b/src/cunumeric/set/unique.h index b63d69c623..c124fdab69 100644 --- a/src/cunumeric/set/unique.h +++ b/src/cunumeric/set/unique.h @@ -26,10 +26,10 @@ class UniqueTask : public CuNumericTask { public: static void cpu_variant(legate::TaskContext context); -#ifdef LEGATE_USE_OPENMP +#if LegateDefined(LEGATE_USE_OPENMP) static void omp_variant(legate::TaskContext context); #endif -#ifdef LEGATE_USE_CUDA +#if LegateDefined(LEGATE_USE_CUDA) static void gpu_variant(legate::TaskContext context); #endif }; diff --git a/src/cunumeric/set/unique_reduce.h b/src/cunumeric/set/unique_reduce.h index f26c2c9349..3d933d2add 100644 --- a/src/cunumeric/set/unique_reduce.h +++ b/src/cunumeric/set/unique_reduce.h @@ -26,7 +26,7 @@ class UniqueReduceTask : public CuNumericTask { public: static void cpu_variant(legate::TaskContext context); -#ifdef LEGATE_USE_OPENMP +#if LegateDefined(LEGATE_USE_OPENMP) static void omp_variant(legate::TaskContext context); #endif }; diff --git a/src/cunumeric/sort/searchsorted.h b/src/cunumeric/sort/searchsorted.h index 2719ec62b6..3e65e49c13 100644 --- a/src/cunumeric/sort/searchsorted.h +++ b/src/cunumeric/sort/searchsorted.h @@ -35,10 +35,10 @@ class SearchSortedTask : public CuNumericTask { public: static void cpu_variant(legate::TaskContext context); -#ifdef LEGATE_USE_OPENMP +#if LegateDefined(LEGATE_USE_OPENMP) static void omp_variant(legate::TaskContext context); #endif -#ifdef LEGATE_USE_CUDA +#if LegateDefined(LEGATE_USE_CUDA) static void gpu_variant(legate::TaskContext context); #endif }; diff --git a/src/cunumeric/sort/sort.h b/src/cunumeric/sort/sort.h index 32cbb4417f..817613edd0 100644 --- a/src/cunumeric/sort/sort.h +++ b/src/cunumeric/sort/sort.h @@ -96,10 +96,10 @@ class SortTask : public CuNumericTask { public: static void cpu_variant(legate::TaskContext context); -#ifdef LEGATE_USE_OPENMP +#if LegateDefined(LEGATE_USE_OPENMP) static void omp_variant(legate::TaskContext context); #endif -#ifdef LEGATE_USE_CUDA +#if LegateDefined(LEGATE_USE_CUDA) static void gpu_variant(legate::TaskContext context); #endif }; diff --git a/src/cunumeric/stat/bincount.h b/src/cunumeric/stat/bincount.h index 07e45de030..998eca143d 100644 --- a/src/cunumeric/stat/bincount.h +++ b/src/cunumeric/stat/bincount.h @@ -33,10 +33,10 @@ class BincountTask : public CuNumericTask { public: static void cpu_variant(legate::TaskContext context); -#ifdef LEGATE_USE_OPENMP +#if LegateDefined(LEGATE_USE_OPENMP) static void omp_variant(legate::TaskContext context); #endif -#ifdef LEGATE_USE_CUDA +#if LegateDefined(LEGATE_USE_CUDA) static void gpu_variant(legate::TaskContext context); #endif }; diff --git a/src/cunumeric/stat/histogram.h b/src/cunumeric/stat/histogram.h index 45d3cd9557..634c2de877 100644 --- a/src/cunumeric/stat/histogram.h +++ b/src/cunumeric/stat/histogram.h @@ -33,10 +33,10 @@ class HistogramTask : public CuNumericTask { public: static void cpu_variant(legate::TaskContext context); -#ifdef LEGATE_USE_OPENMP +#if LegateDefined(LEGATE_USE_OPENMP) static void omp_variant(legate::TaskContext context); #endif -#ifdef LEGATE_USE_CUDA +#if LegateDefined(LEGATE_USE_CUDA) static void gpu_variant(legate::TaskContext context); #endif }; diff --git a/src/cunumeric/stat/histogram_cpu.h b/src/cunumeric/stat/histogram_cpu.h index cd0f4304b0..cb4a2a07dd 100644 --- a/src/cunumeric/stat/histogram_cpu.h +++ b/src/cunumeric/stat/histogram_cpu.h @@ -31,7 +31,7 @@ #include "cunumeric/stat/histogram_gen.h" -#ifndef LEGATE_USE_CUDA +#if !LegateDefined(LEGATE_USE_CUDA) using cudaStream_t = void*; #endif diff --git a/src/cunumeric/ternary/where.h b/src/cunumeric/ternary/where.h index 3b307bd91e..28bc984d32 100644 --- a/src/cunumeric/ternary/where.h +++ b/src/cunumeric/ternary/where.h @@ -33,10 +33,10 @@ class WhereTask : public CuNumericTask { public: static void cpu_variant(legate::TaskContext context); -#ifdef LEGATE_USE_OPENMP +#if LegateDefined(LEGATE_USE_OPENMP) static void omp_variant(legate::TaskContext context); #endif -#ifdef LEGATE_USE_CUDA +#if LegateDefined(LEGATE_USE_CUDA) static void gpu_variant(legate::TaskContext context); #endif }; diff --git a/src/cunumeric/transform/flip.h b/src/cunumeric/transform/flip.h index 6e0ccdc12a..f85392a90d 100644 --- a/src/cunumeric/transform/flip.h +++ b/src/cunumeric/transform/flip.h @@ -32,10 +32,10 @@ class FlipTask : public CuNumericTask { public: static void cpu_variant(legate::TaskContext context); -#ifdef LEGATE_USE_OPENMP +#if LegateDefined(LEGATE_USE_OPENMP) static void omp_variant(legate::TaskContext context); #endif -#ifdef LEGATE_USE_CUDA +#if LegateDefined(LEGATE_USE_CUDA) static void gpu_variant(legate::TaskContext context); #endif }; diff --git a/src/cunumeric/unary/convert.h b/src/cunumeric/unary/convert.h index 891a130826..5d9fa3a96b 100644 --- a/src/cunumeric/unary/convert.h +++ b/src/cunumeric/unary/convert.h @@ -33,10 +33,10 @@ class ConvertTask : public CuNumericTask { public: static void cpu_variant(legate::TaskContext context); -#ifdef LEGATE_USE_OPENMP +#if LegateDefined(LEGATE_USE_OPENMP) static void omp_variant(legate::TaskContext context); #endif -#ifdef LEGATE_USE_CUDA +#if LegateDefined(LEGATE_USE_CUDA) static void gpu_variant(legate::TaskContext context); #endif }; diff --git a/src/cunumeric/unary/scalar_unary_red.h b/src/cunumeric/unary/scalar_unary_red.h index 495473a6cd..b56d90a617 100644 --- a/src/cunumeric/unary/scalar_unary_red.h +++ b/src/cunumeric/unary/scalar_unary_red.h @@ -36,10 +36,10 @@ class ScalarUnaryRedTask : public CuNumericTask { public: static void cpu_variant(legate::TaskContext context); -#ifdef LEGATE_USE_OPENMP +#if LegateDefined(LEGATE_USE_OPENMP) static void omp_variant(legate::TaskContext context); #endif -#ifdef LEGATE_USE_CUDA +#if LegateDefined(LEGATE_USE_CUDA) static void gpu_variant(legate::TaskContext context); #endif }; diff --git a/src/cunumeric/unary/unary_op.h b/src/cunumeric/unary/unary_op.h index 57919a3175..bff4199e4d 100644 --- a/src/cunumeric/unary/unary_op.h +++ b/src/cunumeric/unary/unary_op.h @@ -41,10 +41,10 @@ class UnaryOpTask : public CuNumericTask { public: static void cpu_variant(legate::TaskContext context); -#ifdef LEGATE_USE_OPENMP +#if LegateDefined(LEGATE_USE_OPENMP) static void omp_variant(legate::TaskContext context); #endif -#ifdef LEGATE_USE_CUDA +#if LegateDefined(LEGATE_USE_CUDA) static void gpu_variant(legate::TaskContext context); #endif }; diff --git a/src/cunumeric/unary/unary_red.cu b/src/cunumeric/unary/unary_red.cu index f2e03de912..272ef2c01d 100644 --- a/src/cunumeric/unary/unary_red.cu +++ b/src/cunumeric/unary/unary_red.cu @@ -269,13 +269,14 @@ static __device__ __forceinline__ Point local_reduce(LHS& result, } #endif -#ifdef LEGATE_BOUNDS_CHECKS - // Note: this isn't necessary because we know that the affine transformation on the output - // accessor will ignore coordinates of the collapsed dimension. However, Legion's bounds checks - // want the accessor to honor the sub-rectangle passed when it was created, so we need to - // put points back in the bounds to appease the checks. - point[collapsed_dim] = domain.lo[collapsed_dim]; -#endif + if (LegateDefined(LEGATE_BOUNDS_CHECKS)) { + // Note: this isn't necessary because we know that the affine transformation on the output + // accessor will ignore coordinates of the collapsed dimension. However, Legion's bounds checks + // want the accessor to honor the sub-rectangle passed when it was created, so we need to + // put points back in the bounds to appease the checks. + point[collapsed_dim] = domain.lo[collapsed_dim]; + } + return point; } diff --git a/src/cunumeric/unary/unary_red.h b/src/cunumeric/unary/unary_red.h index bb2ca561c4..778be4b55e 100644 --- a/src/cunumeric/unary/unary_red.h +++ b/src/cunumeric/unary/unary_red.h @@ -34,10 +34,10 @@ class UnaryRedTask : public CuNumericTask { public: static void cpu_variant(legate::TaskContext context); -#ifdef LEGATE_USE_OPENMP +#if LegateDefined(LEGATE_USE_OPENMP) static void omp_variant(legate::TaskContext context); #endif -#ifdef LEGATE_USE_CUDA +#if LegateDefined(LEGATE_USE_CUDA) static void gpu_variant(legate::TaskContext context); #endif }; From 246621c960ae3f2f7cf4a824995da0699e48646d Mon Sep 17 00:00:00 2001 From: Wonchan Lee Date: Tue, 3 Oct 2023 16:17:08 -0700 Subject: [PATCH 101/462] No need to use a callback for registration (#28) * No need to use a callback for registration * Upate commit hash --- cmake/versions.json | 2 +- src/cunumeric/cunumeric.cc | 5 +---- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/cmake/versions.json b/cmake/versions.json index c6cb11a572..22746ddafb 100644 --- a/cmake/versions.json +++ b/cmake/versions.json @@ -12,7 +12,7 @@ "git_url" : "https://github.com/nv-legate/legate.core.internal.git", "git_shallow": false, "always_download": false, - "git_tag" : "3f138644e1ba8c8c3109a20f5a6a9feb5a69563c" + "git_tag" : "96d03c7752fe8a320eb0e5f82e9f472e9c2afe88" } } } diff --git a/src/cunumeric/cunumeric.cc b/src/cunumeric/cunumeric.cc index 9b9c7d6bc6..72338f9430 100644 --- a/src/cunumeric/cunumeric.cc +++ b/src/cunumeric/cunumeric.cc @@ -49,10 +49,7 @@ void registration_callback() extern "C" { -void cunumeric_perform_registration(void) -{ - legate::Core::perform_registration(); -} +void cunumeric_perform_registration(void) { cunumeric::registration_callback(); } bool cunumeric_has_curand() { From 8ad38b2c63a63d1841454a6a62ee169c841cf062 Mon Sep 17 00:00:00 2001 From: Parag Kulkarni Date: Sat, 7 Oct 2023 11:34:34 +0530 Subject: [PATCH 102/462] Use Download action to download Legate builds. (#27) * Update Artifact Download action with TOT * Search the artifact. * Check1 * Check2 * Check-3 * Check-4 * Check-5 * Chk1 * Chk2 * Chk-3 * Chk-4 * Chk-3.2 * chk-3.3 inherit -L1 * Chk-3.4 Inherit L2 * Cleanup spaces * Update version --- .github/actions/download-artifacts/action.yml | 16 ++- .github/workflows/ci-gh.yml | 119 ++++-------------- .github/workflows/gh-build-and-test.yml | 9 +- .github/workflows/gh-build.yml | 16 ++- .github/workflows/gh-test.yml | 9 +- cmake/versions.json | 4 +- 6 files changed, 57 insertions(+), 116 deletions(-) diff --git a/.github/actions/download-artifacts/action.yml b/.github/actions/download-artifacts/action.yml index e8019b1b19..6f97564ff2 100644 --- a/.github/actions/download-artifacts/action.yml +++ b/.github/actions/download-artifacts/action.yml @@ -5,6 +5,9 @@ description: Download dependencies (artifacts) inputs: device: {type: string, required: true} git_sha: {type: string, required: true} + repos-name: {type: string, required: true} + workflow_token: {required: true} + runs: using: composite @@ -14,19 +17,22 @@ runs: name: Cache conda artifacts uses: actions/cache@v3 with: - key: "nv-legate/legate.core@${{ inputs.git_sha }}-${{ inputs.device }}" + key: "nv-legate/legate.core.internal@${{ inputs.git_sha }}-${{ inputs.device }}" path: .artifacts - if: steps.cache.outputs.cache-hit != 'true' name: Download conda artifacts uses: dawidd6/action-download-artifact@v2 with: + github_token: ${{ inputs.workflow_token }} path: .artifacts-dl - repo: nv-legate/legate.core + repo: nv-legate/legate.core.internal + check_artifacts: true + search_artifacts: true commit: ${{ inputs.git_sha }} workflow_conclusion: success workflow: "ci-gh-${{ inputs.device }}-build-and-test.yml" - name: "legate.core-${{ inputs.device }}-[0-9a-z]{40}" + name: "legate.core.internal-${{ inputs.device }}-release-gcc-[0-9a-z]{40}" name_is_regexp: true - if: steps.cache.outputs.cache-hit != 'true' @@ -34,9 +40,9 @@ runs: shell: bash --noprofile --norc -xeo pipefail {0} run: | mkdir -p .artifacts; - find .artifacts-dl/legate.core-${{ inputs.device }}-*/ \ + find .artifacts-dl/legate.core.internal-${{ inputs.device }}-release-gcc-*/ \ -maxdepth 2 -type d -name legate_core -exec mv {} .artifacts/ \; - find .artifacts-dl/legate.core-${{ inputs.device }}-*/ \ + find .artifacts-dl/legate.core.internal-${{ inputs.device }}-release-gcc-*/ \ -maxdepth 2 -type f -name "environment*.yaml" -exec mv {} .artifacts/ \; - name: Copy and change cache dir ownership diff --git a/.github/workflows/ci-gh.yml b/.github/workflows/ci-gh.yml index 6beb1000da..c08aa48304 100644 --- a/.github/workflows/ci-gh.yml +++ b/.github/workflows/ci-gh.yml @@ -8,103 +8,26 @@ on: push: branches: - "pull-request/[0-9]+" - - "cpp-branch-*" + - "*branch-*" jobs: - build: - permissions: - id-token: write # This is required for configure-aws-credentials - contents: read # This is required for actions/checkout - - # Ref: https://docs.rapids.ai/resources/github-actions/#cpu-labels for `linux-amd64-cpu4` - runs-on: ${{ github.repository == 'nv-legate/cunumeric.internal' && 'linux-amd64-cpu4' || 'ubuntu-latest' }} - container: - options: -u root - image: rapidsai/devcontainers:23.06-cpp-cuda11.8-mambaforge-ubuntu22.04 - volumes: - - ${{ github.workspace }}/out:/tmp/out - env: - DEFAULT_CONDA_ENV: legate - PYTHONDONTWRITEBYTECODE: 1 - SCCACHE_REGION: us-east-2 - SCCACHE_BUCKET: rapids-sccache-east - SCCACHE_S3_KEY_PREFIX: legate-cunumeric-dev - GH_TOKEN: "${{ secrets.PERSONAL_ACCESS_TOKEN || secrets.GITHUB_TOKEN }}" - GITHUB_TOKEN: "${{ secrets.PERSONAL_ACCESS_TOKEN || secrets.GITHUB_TOKEN }}" - VAULT_HOST: "${{ secrets.PERSONAL_ACCESS_TOKEN && 'https://vault.ops.k8s.rapids.ai' || '' }}" - VAULT_S3_TTL: "28800s" # 8 hours - - steps: - - name: Checkout legate.core.internal - uses: actions/checkout@v3 - with: - repository: nv-legate/legate.core.internal - fetch-depth: 0 - path: legate - token: ${{ secrets.PERSONAL_ACCESS_TOKEN }} - - - name: Checkout cunumeric.internal (= this repo) - uses: actions/checkout@v3 - with: - fetch-depth: 0 - path: cunumeric - - - name: Setup - shell: bash -eo pipefail {0} - run: | - export LEGATE_SHA=$(cat cunumeric/cmake/versions.json | jq -r '.packages.legate_core_internal.git_tag') - echo "Checking out LEGATE_SHA: ${LEGATE_SHA}" - git -C legate checkout $LEGATE_SHA - - cp -ar legate/continuous_integration/home/coder/.gitconfig /home/coder/; - cp -ar legate/continuous_integration/home/coder/.local /home/coder/; - mv legate /home/coder/legate - - cp -ar cunumeric/continuous_integration/home/coder/.local/bin/* /home/coder/.local/bin/; - mv cunumeric /home/coder/cunumeric; - - chmod a+x /home/coder/.local/bin/*; - chown -R coder:coder /home/coder/; - chown -R coder:coder /tmp/out; - - - if: github.repository == 'nv-legate/cunumeric.internal' - name: Get AWS credentials for sccache bucket - uses: aws-actions/configure-aws-credentials@v2 - with: - aws-region: us-east-2 - role-duration-seconds: 28800 # 8 hours - role-to-assume: arn:aws:iam::279114543810:role/gha-oidc-nv-legate - - - name: Create conda env - shell: su coder {0} - run: . conda-utils; get_yaml_and_make_conda_env - - - name: Build legate.core C++ library - shell: su coder {0} - run: cd ~/; exec entrypoint build-legate-cpp; - - - name: Build legate.core Python Wheel - shell: su coder {0} - run: cd ~/; exec entrypoint build-legate-wheel; - - - name: Build legate.core Conda Package - shell: su coder {0} - run: cd ~/; exec entrypoint build-legate-conda; - - - name: Build cunumeric C++ library - shell: su coder {0} - run: cd ~/; exec entrypoint build-cunumeric-cpp; - - - name: Build cunumeric Python Wheel - shell: su coder {0} - run: cd ~/; exec entrypoint build-cunumeric-wheel; - - - name: Build cunumeric Conda Package - shell: su coder {0} - run: cd ~/; exec entrypoint build-cunumeric-conda; - - - name: Upload build output - uses: actions/upload-artifact@v3 - with: - name: "cunumeric.internal-${{ github.sha }}" - path: ./out/* + build-and-test: + strategy: + fail-fast: false + matrix: + include: + - device: "gpu" + image: "rapidsai/devcontainers:23.06-cpp-mambaforge-ubuntu22.04" + + - device: "cpu" + image: "rapidsai/devcontainers:23.06-cpp-mambaforge-ubuntu22.04" + uses: + ./.github/workflows/gh-build-and-test.yml + with: + image: ${{ matrix.image }} + device: ${{ matrix.device }} + repos-name: ${{ github.event.repository.name }} + secrets: inherit + + + \ No newline at end of file diff --git a/.github/workflows/gh-build-and-test.yml b/.github/workflows/gh-build-and-test.yml index 3766a07ee6..d9c2ae0c12 100644 --- a/.github/workflows/gh-build-and-test.yml +++ b/.github/workflows/gh-build-and-test.yml @@ -7,17 +7,21 @@ on: device: type: string required: true - + repos-name: + required: true + type: string jobs: build: - name: "Build cunumeric (with ${{ inputs.device }} legate) on GH" + name: "Build ${{ inputs.repos-name }} (with ${{ inputs.device }} legate) on GH" uses: ./.github/workflows/gh-build.yml with: device: ${{ inputs.device }} + repos-name: ${{ inputs.repos-name }} image: ${{ inputs.image }} runs-on: ${{ github.repository_owner == 'nv-legate' && 'linux-amd64-32cpu' || 'ubuntu-latest' }} + secrets: inherit test: needs: @@ -86,6 +90,7 @@ jobs: name: ${{ matrix.name }} device: ${{ inputs.device }} image: ${{ inputs.image }} + repos-name: ${{ inputs.repos-name }} runs-on: ${{ matrix.runner }} has-gpu: ${{ matrix.has-gpu }} test-options: ${{ matrix.options }} diff --git a/.github/workflows/gh-build.yml b/.github/workflows/gh-build.yml index 308b5f78c2..572646438f 100644 --- a/.github/workflows/gh-build.yml +++ b/.github/workflows/gh-build.yml @@ -9,9 +9,12 @@ on: device: required: true type: string - runs-on: + repos-name: required: true type: string + runs-on: + required: true + type: string jobs: build: @@ -43,7 +46,7 @@ jobs: working-directory: /home/coder steps: - - name: Checkout cunumeric (= this repo) + - name: Checkout ${{ inputs.repos-name }} (= this repo) uses: actions/checkout@v3 with: fetch-depth: 0 @@ -69,8 +72,7 @@ jobs: name: Read legate.core SHA shell: bash --noprofile --norc -xeo pipefail {0} run: | - git_tag="$(jq -r '.packages.legate_core.git_tag' cunumeric/cmake/versions.json)"; - + git_tag="$(jq -r '.packages.legate_core_internal.git_tag' cunumeric/cmake/versions.json)"; echo "git_tag=$git_tag" | tee -a "${GITHUB_OUTPUT}"; - name: Download dependencies (artifacts) @@ -78,6 +80,8 @@ jobs: with: device: "${{ inputs.device }}" git_sha: "${{ steps.legate_core_info.outputs.git_tag }}" + repos-name: "${{ inputs.repos-name }}" + workflow_token: ${{ secrets.workflow_token }} - if: github.repository_owner == 'nv-legate' name: Get AWS credentials for sccache bucket @@ -87,7 +91,7 @@ jobs: role-duration-seconds: 28800 # 8 hours role-to-assume: arn:aws:iam::279114543810:role/gha-oidc-nv-legate - - name: Build cunumeric + - name: Build ${{ inputs.repos-name }} run: | export PATH="/home/coder/cunumeric/continuous_integration/scripts:$PATH" build-cunumeric-all @@ -95,7 +99,7 @@ jobs: - name: Upload build artifacts uses: actions/upload-artifact@v3 with: - name: "cunumeric-${{ inputs.device }}-${{ github.sha }}" + name: "${{ inputs.repos-name }}-${{ inputs.device }}-${{ github.sha }}" path: | /tmp/out /tmp/conda-build diff --git a/.github/workflows/gh-test.yml b/.github/workflows/gh-test.yml index 675f27e9ba..476e3d16e7 100644 --- a/.github/workflows/gh-test.yml +++ b/.github/workflows/gh-test.yml @@ -12,6 +12,9 @@ on: device: required: true type: string + repos-name: + required: true + type: string runs-on: required: true type: string @@ -27,7 +30,7 @@ on: type: boolean env: - build_artifact_name: "cunumeric-${{ inputs.device }}-${{ github.sha }}" + build_artifact_name: "${{ inputs.repos-name }}-${{ inputs.device }}-${{ github.sha }}" jobs: test: @@ -58,7 +61,7 @@ jobs: sudo apt-get update && \ sudo apt-get install -y numactl - - name: Checkout cunumeric + - name: Checkout ${{ inputs.repos-name }} uses: actions/checkout@v3 with: fetch-depth: 0 @@ -79,7 +82,7 @@ jobs: name: ${{ env.build_artifact_name }} path: /home/coder/.artifacts - - name: Run cunumeric test / analysis + - name: Run ${{ inputs.repos-name }} test / analysis shell: su coder {0} run: | set -x diff --git a/cmake/versions.json b/cmake/versions.json index 22746ddafb..b996136ce3 100644 --- a/cmake/versions.json +++ b/cmake/versions.json @@ -8,11 +8,11 @@ "git_tag" : "8997f997be02936304b3ac23fe785f1de7a3424b" }, "legate_core_internal" : { - "version": "23.09.00", + "version": "23.11.00", "git_url" : "https://github.com/nv-legate/legate.core.internal.git", "git_shallow": false, "always_download": false, - "git_tag" : "96d03c7752fe8a320eb0e5f82e9f472e9c2afe88" + "git_tag" : "513256409a4d7ca0b11ced320e97f2534f9795fa" } } } From d9c7a5782d7b9aa8b5f43a0dabde3c2f025990a6 Mon Sep 17 00:00:00 2001 From: Mona Han <129724851+mona-nv@users.noreply.github.com> Date: Thu, 12 Oct 2023 09:15:02 +0800 Subject: [PATCH 103/462] Port swapaxes and add swapaxes test. (#17) * Port swapaxes and add swapaxes test. * Update the code based on review comments: 1) Change int type to int32_t; 2) Update the test script * Update the code based on review comments. --- src/cunumeric/ndarray.cc | 32 ++++++++ src/cunumeric/ndarray.h | 2 + src/cunumeric/operators.cc | 5 ++ src/cunumeric/operators.h | 2 + tests/cpp/integration/test_swapaxes.cc | 103 +++++++++++++++++++++++++ 5 files changed, 144 insertions(+) create mode 100644 tests/cpp/integration/test_swapaxes.cc diff --git a/src/cunumeric/ndarray.cc b/src/cunumeric/ndarray.cc index 0232b1e93e..166794ca4a 100644 --- a/src/cunumeric/ndarray.cc +++ b/src/cunumeric/ndarray.cc @@ -207,6 +207,18 @@ void NDArray::bincount(NDArray rhs, std::optional weights /*=std::nullo runtime->submit(std::move(task)); } +int32_t NDArray::normalize_axis_index(int32_t axis) +{ + auto ndim = dim(); + std::string err_msg = "The input axis is out of the bounds"; + if (axis > ndim) throw std::invalid_argument(std::move(err_msg)); + + auto updated_axis = axis >= 0 ? axis : ndim + axis; + if (updated_axis < 0) throw std::invalid_argument(std::move(err_msg)); + + return updated_axis; +} + void NDArray::trilu(NDArray rhs, int32_t k, bool lower) { if (size() == 0) return; @@ -423,6 +435,26 @@ NDArray NDArray::unique() return result; } +NDArray NDArray::swapaxes(int32_t axis1, int32_t axis2) +{ + axis1 = normalize_axis_index(axis1); + axis2 = normalize_axis_index(axis2); + + if (shape().size() == 1 || axis1 == axis2) return *this; + + auto ndim = dim(); + std::vector dims; + for (auto i = 0; i < ndim; ++i) dims.push_back(i); + + if (axis1 < 0 || axis2 < 0) throw std::out_of_range("Index is out of range"); + + std::swap(dims[axis1], dims[axis2]); + + auto transposed = store_.transpose(std::move(dims)); + auto runtime = CuNumericRuntime::get_runtime(); + return runtime->create_array(std::move(transposed)); +} + NDArray NDArray::as_type(const legate::Type& type) { auto runtime = CuNumericRuntime::get_runtime(); diff --git a/src/cunumeric/ndarray.h b/src/cunumeric/ndarray.h index d0b6c0f29c..e6a8527365 100644 --- a/src/cunumeric/ndarray.h +++ b/src/cunumeric/ndarray.h @@ -80,6 +80,7 @@ class NDArray { void arange(double start, double stop, double step); std::vector nonzero(); NDArray unique(); + NDArray swapaxes(int32_t axis1, int32_t axis2); void create_window(int32_t op_code, int64_t M, std::vector args); void bincount(NDArray rhs, std::optional weights = std::nullopt); void convolve(NDArray input, NDArray filter); @@ -87,6 +88,7 @@ class NDArray { public: NDArray as_type(const legate::Type& type); legate::LogicalStore get_store(); + int32_t normalize_axis_index(int32_t axis); private: legate::LogicalStore broadcast(const std::vector& shape, legate::LogicalStore& store); diff --git a/src/cunumeric/operators.cc b/src/cunumeric/operators.cc index 5255b58a8b..4552716974 100644 --- a/src/cunumeric/operators.cc +++ b/src/cunumeric/operators.cc @@ -233,6 +233,11 @@ NDArray amin(NDArray input) { return unary_reduction(UnaryRedCode::MIN, std::mov NDArray unique(NDArray input) { return input.unique(); } +NDArray swapaxes(NDArray input, int32_t axis1, int32_t axis2) +{ + return input.swapaxes(axis1, axis2); +} + NDArray arange(std::optional start, std::optional stop, std::optional step, diff --git a/src/cunumeric/operators.h b/src/cunumeric/operators.h index f896e9ef9e..95cc9ae118 100644 --- a/src/cunumeric/operators.h +++ b/src/cunumeric/operators.h @@ -53,6 +53,8 @@ NDArray amin(NDArray input); NDArray unique(NDArray input); +NDArray swapaxes(NDArray input, int32_t axis1, int32_t axis2); + NDArray arange(std::optional start = 0, std::optional stop = std::nullopt, std::optional step = 1, diff --git a/tests/cpp/integration/test_swapaxes.cc b/tests/cpp/integration/test_swapaxes.cc new file mode 100644 index 0000000000..8020f34df9 --- /dev/null +++ b/tests/cpp/integration/test_swapaxes.cc @@ -0,0 +1,103 @@ +/* Copyright 2023 NVIDIA Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +#include +#include +#include + +#include +#include "legate.h" +#include "cunumeric.h" +#include "util.inl" + +void swapaxes_test() +{ + // Test small + { + auto A = cunumeric::zeros({3, 3}, legate::int32()); + EXPECT_EQ(A.shape(), (std::vector{3, 3})); + auto B = cunumeric::swapaxes(A, 0, 1); + EXPECT_EQ(B.shape(), (std::vector{3, 3})); + } + + // Test tall + { + auto A_tall = cunumeric::zeros({300, 3}, legate::int32()); + EXPECT_EQ(A_tall.shape(), (std::vector{300, 3})); + auto B_tall = cunumeric::swapaxes(A_tall, 0, 1); + EXPECT_EQ(B_tall.shape(), (std::vector{3, 300})); + } + + // Test wide + { + auto A_wide = cunumeric::zeros({3, 300}, legate::int32()); + EXPECT_EQ(A_wide.shape(), (std::vector{3, 300})); + auto B_wide = cunumeric::swapaxes(A_wide, 0, 1); + EXPECT_EQ(B_wide.shape(), (std::vector{300, 3})); + } + + // Test big + { + auto A_big = cunumeric::zeros({300, 300}, legate::int32()); + EXPECT_EQ(A_big.shape(), (std::vector{300, 300})); + auto B_big = cunumeric::swapaxes(A_big, 0, 1); + EXPECT_EQ(B_big.shape(), (std::vector{300, 300})); + } + + // Test 3-dim array with different swap axes + { + auto A = cunumeric::zeros({3, 4, 5}, legate::int32()); + EXPECT_EQ(A.shape(), (std::vector{3, 4, 5})); + + auto B1 = cunumeric::swapaxes(A, 0, 0); + EXPECT_EQ(B1.shape(), (std::vector{3, 4, 5})); + + auto B2 = cunumeric::swapaxes(A, -3, 1); + EXPECT_EQ(B2.shape(), (std::vector{4, 3, 5})); + + auto B3 = cunumeric::swapaxes(A, 0, 2); + EXPECT_EQ(B3.shape(), (std::vector{5, 4, 3})); + + auto B4 = cunumeric::swapaxes(A, -3, -2); + EXPECT_EQ(B4.shape(), (std::vector{4, 3, 5})); + } + + // Test empty array + { + auto A = cunumeric::zeros({0}, legate::int32()); + EXPECT_EQ(A.shape(), (std::vector{0})); + + auto B = cunumeric::swapaxes(A, 0, 0); + EXPECT_EQ(B.shape(), (std::vector{0})); + } +} + +void swapaxes_negative_test() +{ + // Test out-of-bound1 + auto A = cunumeric::zeros({3, 3}, legate::int32()); + EXPECT_THROW(cunumeric::swapaxes(A, 3, 0), std::invalid_argument); + EXPECT_THROW(cunumeric::swapaxes(A, 0, 3), std::invalid_argument); + + // Test out-of-bound2 + EXPECT_THROW(cunumeric::swapaxes(A, -4, 0), std::invalid_argument); + EXPECT_THROW(cunumeric::swapaxes(A, 0, -4), std::invalid_argument); +} + +// void cpp_test() +TEST(Swapaxes, Normal) { swapaxes_test(); } + +TEST(Swapaxes, Negative) { swapaxes_negative_test(); } \ No newline at end of file From 22bc47cd52c74622aa696511dd33286d553e7b26 Mon Sep 17 00:00:00 2001 From: Wonchan Lee Date: Fri, 13 Oct 2023 15:43:21 -0700 Subject: [PATCH 104/462] Fix all compiler warnings (#35) * Fix all compiler warnings * Bump the legate core commit --- cmake/versions.json | 2 +- src/cunumeric/binary/binary_op_util.h | 10 ++ src/cunumeric/bits/unpackbits_template.inl | 1 - src/cunumeric/cephes/i0.cc | 2 - src/cunumeric/convolution/convolve.cc | 5 +- src/cunumeric/convolution/convolve.cu | 2 +- src/cunumeric/convolution/convolve_omp.cc | 5 +- .../convolution/convolve_template.inl | 10 +- src/cunumeric/fft/fft.cu | 4 +- src/cunumeric/index/advanced_indexing.cc | 8 +- src/cunumeric/index/advanced_indexing_omp.cc | 10 +- src/cunumeric/index/choose.cc | 4 +- src/cunumeric/index/choose_omp.cc | 2 +- src/cunumeric/index/choose_template.inl | 4 +- src/cunumeric/index/repeat.cc | 2 +- src/cunumeric/index/repeat_omp.cc | 1 - src/cunumeric/index/wrap.h | 12 +- src/cunumeric/index/wrap_template.inl | 3 +- src/cunumeric/index/zip.cc | 2 +- src/cunumeric/index/zip_omp.cc | 2 +- src/cunumeric/index/zip_template.inl | 11 +- src/cunumeric/mapper.cc | 22 +-- src/cunumeric/matrix/diag.cc | 2 +- src/cunumeric/matrix/diag_omp.cc | 2 +- src/cunumeric/matrix/dot.cc | 2 +- src/cunumeric/matrix/solve_template.inl | 4 +- src/cunumeric/matrix/util.cc | 2 +- src/cunumeric/matrix/util.h | 2 +- src/cunumeric/ndarray.cc | 12 +- src/cunumeric/operators.cc | 20 +-- src/cunumeric/random/bitgenerator_curand.inl | 130 +----------------- .../random/randutil/generator_host.cc | 24 ---- .../randutil/generator_host_advanced.cc | 52 ------- .../generator_host_straightforward.cc | 40 ------ .../random/randutil/random_distributions.h | 12 -- src/cunumeric/runtime.cc | 2 +- src/cunumeric/scan/scan_global.h | 2 +- src/cunumeric/scan/scan_global_template.inl | 3 +- src/cunumeric/scan/scan_local.h | 2 +- src/cunumeric/scan/scan_local_template.inl | 2 +- .../scan/{scan_global_util.h => scan_util.h} | 38 ++++- src/cunumeric/search/argwhere.cc | 2 +- src/cunumeric/sort/searchsorted.cc | 4 +- src/cunumeric/sort/searchsorted_omp.cc | 4 +- src/cunumeric/sort/searchsorted_template.inl | 2 - src/cunumeric/sort/sort.cu | 10 +- src/cunumeric/sort/sort_cpu.inl | 44 +++--- src/cunumeric/sort/sort_template.inl | 2 - src/cunumeric/stat/bincount.cc | 4 +- src/cunumeric/stat/bincount_omp.cc | 8 +- src/cunumeric/stat/histogram_cpu.h | 2 +- src/cunumeric/unary/unary_op_util.h | 10 ++ 52 files changed, 174 insertions(+), 395 deletions(-) rename src/cunumeric/scan/{scan_global_util.h => scan_util.h} (55%) diff --git a/cmake/versions.json b/cmake/versions.json index b996136ce3..66b44872da 100644 --- a/cmake/versions.json +++ b/cmake/versions.json @@ -12,7 +12,7 @@ "git_url" : "https://github.com/nv-legate/legate.core.internal.git", "git_shallow": false, "always_download": false, - "git_tag" : "513256409a4d7ca0b11ced320e97f2534f9795fa" + "git_tag" : "befdfc6001ce57f36a83b64059aef9df7fcbc2f9" } } } diff --git a/src/cunumeric/binary/binary_op_util.h b/src/cunumeric/binary/binary_op_util.h index f7dcb7c564..d29b8873e0 100644 --- a/src/cunumeric/binary/binary_op_util.h +++ b/src/cunumeric/binary/binary_op_util.h @@ -524,6 +524,16 @@ struct BinaryOp { r = a / r * b; return r; } + + __CUDA_HD__ bool operator()(const bool& _a, const bool& _b) const + { + int32_t a = static_cast(_a); + int32_t b = static_cast(_b); + int32_t r = _gcd(a, b); + if (r == 0) return false; + r = a / r * b; + return static_cast(r); + } }; template diff --git a/src/cunumeric/bits/unpackbits_template.inl b/src/cunumeric/bits/unpackbits_template.inl index 11bed73bb2..5a3e6f85ca 100644 --- a/src/cunumeric/bits/unpackbits_template.inl +++ b/src/cunumeric/bits/unpackbits_template.inl @@ -64,7 +64,6 @@ static void unpackbits_template(TaskContext& context) auto axis = scalars[0].value(); auto bitorder = scalars[1].value(); - auto code = input.code(); switch (bitorder) { case Bitorder::BIG: { dim_dispatch(input.dim(), UnpackbitsImpl{}, output, input, axis); diff --git a/src/cunumeric/cephes/i0.cc b/src/cunumeric/cephes/i0.cc index a81f26257f..d601e1e20a 100644 --- a/src/cunumeric/cephes/i0.cc +++ b/src/cunumeric/cephes/i0.cc @@ -111,8 +111,6 @@ extern double chbevl(double x, double array[], int n); double i0(double x) { - double y; - if (x < 0) x = -x; if (x <= 8.0) { double y = x / 2.0 - 2.0; diff --git a/src/cunumeric/convolution/convolve.cc b/src/cunumeric/convolution/convolve.cc index d20788a3e7..b3dce4d1c4 100644 --- a/src/cunumeric/convolution/convolve.cc +++ b/src/cunumeric/convolution/convolve.cc @@ -84,9 +84,8 @@ struct ConvolveImplBody { const Rect& subrect, const Rect& filter_rect) const { - const Point zero = Point::ZEROES(); - const Point one = Point::ONES(); - Point extents = filter_rect.hi - filter_rect.lo + one; + const Point one = Point::ONES(); + Point extents = filter_rect.hi - filter_rect.lo + one; Point centers; for (int d = 0; d < DIM; d++) centers[d] = extents[d] / 2; diff --git a/src/cunumeric/convolution/convolve.cu b/src/cunumeric/convolution/convolve.cu index 6fdc6443e7..9e5b4e171e 100644 --- a/src/cunumeric/convolution/convolve.cu +++ b/src/cunumeric/convolution/convolve.cu @@ -881,7 +881,7 @@ __host__ void direct_convolution(AccessorWO out, } Point l2_filter_tile; size_t total_l2_filters = 1; - if (l2_output_tile_size <= (properties.l2CacheSize / 4)) { + if (l2_output_tile_size <= (static_cast(properties.l2CacheSize) / 4)) { for (int d = 0; d < DIM; d++) l2_filter_tile[d] = 1; // Compute the L2 filter tile size so that the L2 filter and the // corresponding L2 input tile will fit in the L2 cache diff --git a/src/cunumeric/convolution/convolve_omp.cc b/src/cunumeric/convolution/convolve_omp.cc index a3531627b4..f59bb5921d 100644 --- a/src/cunumeric/convolution/convolve_omp.cc +++ b/src/cunumeric/convolution/convolve_omp.cc @@ -35,9 +35,8 @@ struct ConvolveImplBody { const Rect& subrect, const Rect& filter_rect) const { - const Point zero = Point::ZEROES(); - const Point one = Point::ONES(); - Point extents = filter_rect.hi - filter_rect.lo + one; + const Point one = Point::ONES(); + Point extents = filter_rect.hi - filter_rect.lo + one; Point centers; for (int d = 0; d < DIM; d++) centers[d] = extents[d] / 2; diff --git a/src/cunumeric/convolution/convolve_template.inl b/src/cunumeric/convolution/convolve_template.inl index 3b0fb28805..d9402936b8 100644 --- a/src/cunumeric/convolution/convolve_template.inl +++ b/src/cunumeric/convolution/convolve_template.inl @@ -41,7 +41,7 @@ struct ConvolveImpl { if (subrect.empty()) return; auto input_subrect = subrect; - for (auto idx = 1; idx < args.inputs.size(); ++idx) { + for (uint32_t idx = 1; idx < args.inputs.size(); ++idx) { auto image_subrect = args.inputs[idx].shape(); input_subrect = input_subrect.union_bbox(image_subrect); } @@ -96,7 +96,7 @@ static unsigned compute_output_tile(Point& tile, assert((target_volume & (target_volume - 1)) == 0); unsigned volume = 1; // Try to make the last dimension at least the min last elements for locality - for (int idx = 1; idx < min_last_elements; idx *= 2) { + for (uint32_t idx = 1; idx < min_last_elements; idx *= 2) { tile[DIM - 1] *= 2; if (bounds[DIM - 1] < tile[DIM - 1]) { tile[DIM - 1] /= 2; @@ -110,7 +110,7 @@ static unsigned compute_output_tile(Point& tile, // Round-robin powers of 2 onto the other dimensions until // we hit the max or get all the dimensions balanced if (DIM > 1) { - for (int idx = 1; idx < min_last_elements; idx *= 2) { + for (uint32_t idx = 1; idx < min_last_elements; idx *= 2) { for (int d = DIM - 2; d >= 0; d--) { tile[d] *= 2; if (bounds[d] < tile[d]) @@ -290,7 +290,7 @@ static unsigned roundup_tile(Point& tile, skipdims |= (1 << d1); continue; } - unsigned bound = elements - padding[d1]; + int bound = elements - padding[d1]; if (bounds[d1] < bound) { tile[d1] = bounds[d1]; result = pitch * (tile[d1] + padding[d1]); @@ -324,7 +324,7 @@ static unsigned roundup_tile(Point& tile, if ((next_size > max_size) || (next_size == result)) break; result = next_size; for (int d = 0; d < DIM; d++) { - if (skipdims && (1 << d)) continue; + if (skipdims & (1 << d)) continue; tile[d]++; } } diff --git a/src/cunumeric/fft/fft.cu b/src/cunumeric/fft/fft.cu index 4d679a59cf..25cf7fa26e 100644 --- a/src/cunumeric/fft/fft.cu +++ b/src/cunumeric/fft/fft.cu @@ -130,8 +130,7 @@ __host__ static inline void cufft_over_axes_c2c(INOUT_TYPE* out, dim_t n[DIM]; // Full volume dimensions / strides - const Point zero = Point::ZEROES(); - const Point one = Point::ONES(); + const Point one = Point::ONES(); Point fft_size = inout_rect.hi - inout_rect.lo + one; size_t num_elements = 1; @@ -207,7 +206,6 @@ __host__ static inline void cufft_r2c_c2r(OUTPUT_TYPE* out, dim_t onembed[DIM]; // Full volume dimensions / strides - const Point zero = Point::ZEROES(); const Point one = Point::ONES(); Point fft_size_in = in_rect.hi - in_rect.lo + one; Point fft_size_out = out_rect.hi - out_rect.lo + one; diff --git a/src/cunumeric/index/advanced_indexing.cc b/src/cunumeric/index/advanced_indexing.cc index 4cf96196a9..369114e52b 100644 --- a/src/cunumeric/index/advanced_indexing.cc +++ b/src/cunumeric/index/advanced_indexing.cc @@ -41,11 +41,11 @@ struct AdvancedIndexingImplBody { if (index[p] == true) { Point out_p; out_p[0] = out_idx; - for (size_t i = 0; i < DIM - key_dim; i++) { + for (int32_t i = 0; i < static_cast(DIM - key_dim); i++) { size_t j = key_dim + i; out_p[i + 1] = p[j]; } - for (size_t i = DIM - key_dim + 1; i < DIM; i++) out_p[i] = 0; + for (int32_t i = DIM - key_dim + 1; i < DIM; i++) out_p[i] = 0; fill_out(out[out_p], p, input[p]); // The logic below is based on the assumtion that // pitches enumerate points in C-order, but this might @@ -81,11 +81,11 @@ struct AdvancedIndexingImplBody { // calculating the shape of the output region for this sub-task Point extents; extents[0] = size; - for (size_t i = 0; i < DIM - key_dim; i++) { + for (int32_t i = 0; i < static_cast(DIM - key_dim); i++) { size_t j = key_dim + i; extents[i + 1] = 1 + rect.hi[j] - rect.lo[j]; } - for (size_t i = DIM - key_dim + 1; i < DIM; i++) extents[i] = 1; + for (int32_t i = DIM - key_dim + 1; i < DIM; i++) extents[i] = 1; auto out = out_arr.create_output_buffer(extents, true); if (size > 0) compute_output(out, input, index, pitches, rect, volume, key_dim, skip_size); diff --git a/src/cunumeric/index/advanced_indexing_omp.cc b/src/cunumeric/index/advanced_indexing_omp.cc index 767bb7f322..4e715a8a30 100644 --- a/src/cunumeric/index/advanced_indexing_omp.cc +++ b/src/cunumeric/index/advanced_indexing_omp.cc @@ -39,7 +39,7 @@ struct AdvancedIndexingImplBody { const size_t max_threads) const { ThreadLocalStorage sizes(max_threads); - for (auto idx = 0; idx < max_threads; ++idx) sizes[idx] = 0; + for (size_t idx = 0; idx < max_threads; ++idx) sizes[idx] = 0; #pragma omp parallel { const int tid = omp_get_thread_num(); @@ -50,7 +50,7 @@ struct AdvancedIndexingImplBody { } } // end of parallel size_t size = 0; - for (auto idx = 0; idx < max_threads; ++idx) { + for (size_t idx = 0; idx < max_threads; ++idx) { offsets[idx] = size; size += sizes[idx]; } @@ -80,7 +80,7 @@ struct AdvancedIndexingImplBody { // calculating the shape of the output region for this sub-task Point extents; extents[0] = size; - for (size_t i = 0; i < DIM - key_dim; i++) { + for (int32_t i = 0; i < DIM - key_dim; i++) { size_t j = key_dim + i; extents[i + 1] = 1 + rect.hi[j] - rect.lo[j]; } @@ -98,11 +98,11 @@ struct AdvancedIndexingImplBody { if (index[p] == true) { Point out_p; out_p[0] = out_idx; - for (size_t i = 0; i < DIM - key_dim; i++) { + for (int32_t i = 0; i < DIM - key_dim; i++) { size_t j = key_dim + i; out_p[i + 1] = p[j]; } - for (size_t i = DIM - key_dim + 1; i < DIM; i++) out_p[i] = 0; + for (int32_t i = DIM - key_dim + 1; i < DIM; i++) out_p[i] = 0; fill_out(out[out_p], p, input[p]); if ((idx + 1) % skip_size == 0) out_idx++; } diff --git a/src/cunumeric/index/choose.cc b/src/cunumeric/index/choose.cc index 47d38a156d..d73da8eea8 100644 --- a/src/cunumeric/index/choose.cc +++ b/src/cunumeric/index/choose.cc @@ -38,7 +38,7 @@ struct ChooseImplBody { auto indexptr = index_arr.ptr(rect); for (size_t idx = 0; idx < volume; ++idx) { #ifdef DEBUG_CUNUMERIC - assert(indexptr[idx] < choices.size()); + assert(indexptr[idx] < static_cast(choices.size())); #endif auto chptr = choices[indexptr[idx]].ptr(rect); outptr[idx] = chptr[idx]; @@ -47,7 +47,7 @@ struct ChooseImplBody { for (size_t idx = 0; idx < volume; ++idx) { auto p = pitches.unflatten(idx, rect.lo); #ifdef DEBUG_CUNUMERIC - assert(index_arr[p] < choices.size()); + assert(index_arr[p] < static_cast(choices.size())); #endif out[p] = choices[index_arr[p]][p]; } diff --git a/src/cunumeric/index/choose_omp.cc b/src/cunumeric/index/choose_omp.cc index be23f467be..b6dde5b3f0 100644 --- a/src/cunumeric/index/choose_omp.cc +++ b/src/cunumeric/index/choose_omp.cc @@ -39,7 +39,7 @@ struct ChooseImplBody { #pragma omp parallel for schedule(static) for (size_t idx = 0; idx < volume; ++idx) { #ifdef DEBUG_CUNUMERIC - assert(indexptr[idx] < choices.size()); + assert(indexptr[idx] < static_cast(choices.size())); #endif auto chptr = choices[indexptr[idx]].ptr(rect); outptr[idx] = chptr[idx]; diff --git a/src/cunumeric/index/choose_template.inl b/src/cunumeric/index/choose_template.inl index e9bb8c95e3..568ca6b4b7 100644 --- a/src/cunumeric/index/choose_template.inl +++ b/src/cunumeric/index/choose_template.inl @@ -43,7 +43,7 @@ struct ChooseImpl { auto index_rect = args.inputs[0].shape(); auto index_arr = args.inputs[0].read_accessor(index_rect); -#ifndef LEGATE_BOUNDS_CHECKS +#if LegateDefined(LEGATE_BOUNDS_CHECKS) // Check to see if this is dense or not bool dense = index_arr.accessor.is_dense_row_major(out_rect) && out.accessor.is_dense_row_major(out_rect); @@ -52,7 +52,7 @@ struct ChooseImpl { bool dense = false; #endif std::vector> choices; - for (int i = 1; i < args.inputs.size(); i++) { + for (uint32_t i = 1; i < args.inputs.size(); i++) { auto rect_c = args.inputs[i].shape(); choices.push_back(args.inputs[i].read_accessor(rect_c)); dense = dense && choices[i - 1].accessor.is_dense_row_major(out_rect); diff --git a/src/cunumeric/index/repeat.cc b/src/cunumeric/index/repeat.cc index 7abb0a7791..4853a48fa8 100644 --- a/src/cunumeric/index/repeat.cc +++ b/src/cunumeric/index/repeat.cc @@ -68,7 +68,7 @@ struct RepeatImplBody { int64_t out_idx = 0; for (size_t in_idx = 0; in_idx < volume; ++in_idx) { auto p = in_pitches.unflatten(in_idx, in_rect.lo); - for (size_t r = 0; r < repeats[p]; r++) out[out_idx++] = in[p]; + for (int64_t r = 0; r < repeats[p]; r++) out[out_idx++] = in[p]; } } diff --git a/src/cunumeric/index/repeat_omp.cc b/src/cunumeric/index/repeat_omp.cc index 424a632aa9..c41fc092e3 100644 --- a/src/cunumeric/index/repeat_omp.cc +++ b/src/cunumeric/index/repeat_omp.cc @@ -96,7 +96,6 @@ struct RepeatImplBody { Pitches in_pitches; auto in_volume = in_pitches.flatten(in_rect); - int64_t axis_base = in_rect.lo[axis]; #pragma omp parallel for schedule(static) for (size_t idx = 0; idx < in_volume; ++idx) { auto in_p = in_pitches.unflatten(idx, in_rect.lo); diff --git a/src/cunumeric/index/wrap.h b/src/cunumeric/index/wrap.h index 3a90621b77..62b18b0665 100644 --- a/src/cunumeric/index/wrap.h +++ b/src/cunumeric/index/wrap.h @@ -44,12 +44,12 @@ class WrapTask : public CuNumericTask { #endif }; -__CUDA_HD__ static int64_t compute_idx(const int64_t i, const int64_t volume, const bool&) +__CUDA_HD__ inline int64_t compute_idx(const int64_t i, const int64_t volume, const bool&) { return i % volume; } -__CUDA_HD__ static int64_t compute_idx(const int64_t i, +__CUDA_HD__ inline int64_t compute_idx(const int64_t i, const int64_t volume, const legate::AccessorRO& indices) { @@ -58,7 +58,7 @@ __CUDA_HD__ static int64_t compute_idx(const int64_t i, return index; } -static void check_idx(const int64_t i, +inline void check_idx(const int64_t i, const int64_t volume, const legate::AccessorRO& indices) { @@ -67,12 +67,12 @@ static void check_idx(const int64_t i, if (index < 0 || index >= volume) throw legate::TaskException("index is out of bounds in index array"); } -static void check_idx(const int64_t i, const int64_t volume, const bool&) +inline void check_idx(const int64_t i, const int64_t volume, const bool&) { // don't do anything when wrapping indices } -static bool check_idx_omp(const int64_t i, +inline bool check_idx_omp(const int64_t i, const int64_t volume, const legate::AccessorRO& indices) { @@ -80,6 +80,6 @@ static bool check_idx_omp(const int64_t i, int64_t index = idx < 0 ? idx + volume : idx; return (index < 0 || index >= volume); } -static bool check_idx_omp(const int64_t i, const int64_t volume, const bool&) { return false; } +inline bool check_idx_omp(const int64_t i, const int64_t volume, const bool&) { return false; } } // namespace cunumeric diff --git a/src/cunumeric/index/wrap_template.inl b/src/cunumeric/index/wrap_template.inl index b65d0d611d..64cc7d3c28 100644 --- a/src/cunumeric/index/wrap_template.inl +++ b/src/cunumeric/index/wrap_template.inl @@ -32,7 +32,6 @@ struct WrapImpl { template void operator()(WrapArgs& args) const { - using VAL = Point; auto rect_out = args.out.shape<1>(); // output array is always 1D auto out = args.out.write_accessor, 1>(rect_out); @@ -40,7 +39,7 @@ struct WrapImpl { size_t volume_out = pitches_out.flatten(rect_out); if (volume_out == 0) return; -#ifndef LEGATE_BOUNDS_CHECKS +#if LegateDefined(LEGATE_BOUNDS_CHECKS) bool dense = out.accessor.is_dense_row_major(rect_out); #else bool dense = false; diff --git a/src/cunumeric/index/zip.cc b/src/cunumeric/index/zip.cc index b00209cd6e..2497553776 100644 --- a/src/cunumeric/index/zip.cc +++ b/src/cunumeric/index/zip.cc @@ -66,7 +66,7 @@ struct ZipImplBody { for (size_t idx = 0; idx < volume; ++idx) { auto p = pitches.unflatten(idx, rect.lo); Point new_point; - for (size_t i = 0; i < start_index; i++) { new_point[i] = p[i]; } + for (int64_t i = 0; i < start_index; i++) { new_point[i] = p[i]; } for (size_t i = 0; i < index_arrays.size(); i++) { new_point[start_index + i] = compute_idx(index_arrays[i][p], shape[start_index + i]); } diff --git a/src/cunumeric/index/zip_omp.cc b/src/cunumeric/index/zip_omp.cc index 643a202ed0..536a44b989 100644 --- a/src/cunumeric/index/zip_omp.cc +++ b/src/cunumeric/index/zip_omp.cc @@ -73,7 +73,7 @@ struct ZipImplBody { for (size_t idx = 0; idx < volume; ++idx) { auto p = pitches.unflatten(idx, rect.lo); Point new_point; - for (size_t i = 0; i < start_index; i++) { new_point[i] = p[i]; } + for (int64_t i = 0; i < start_index; i++) { new_point[i] = p[i]; } for (size_t i = 0; i < index_arrays.size(); i++) { auto pair = compute_idx_omp(index_arrays[i][p], shape[start_index + i]); new_point[start_index + i] = pair.first; diff --git a/src/cunumeric/index/zip_template.inl b/src/cunumeric/index/zip_template.inl index b160e612be..48c4683c17 100644 --- a/src/cunumeric/index/zip_template.inl +++ b/src/cunumeric/index/zip_template.inl @@ -32,21 +32,20 @@ struct ZipImpl { template void operator()(ZipArgs& args) const { - using VAL = int64_t; - auto out_rect = args.out.shape(); - auto out = args.out.write_accessor, DIM>(out_rect); - auto index_rect = args.inputs[0].shape(); + using VAL = int64_t; + auto out_rect = args.out.shape(); + auto out = args.out.write_accessor, DIM>(out_rect); Pitches pitches; size_t volume = pitches.flatten(out_rect); if (volume == 0) return; -#ifndef LEGATE_BOUNDS_CHECKS +#if LegateDefined(LEGATE_BOUNDS_CHECKS) bool dense = out.accessor.is_dense_row_major(out_rect); #else bool dense = false; #endif std::vector> index_arrays; - for (int i = 0; i < args.inputs.size(); i++) { + for (uint32_t i = 0; i < args.inputs.size(); i++) { index_arrays.push_back(args.inputs[i].read_accessor(args.inputs[i].shape())); dense = dense && index_arrays[i].accessor.is_dense_row_major(out_rect); } diff --git a/src/cunumeric/mapper.cc b/src/cunumeric/mapper.cc index f36289110d..33d29021dc 100644 --- a/src/cunumeric/mapper.cc +++ b/src/cunumeric/mapper.cc @@ -84,7 +84,7 @@ std::vector CuNumericMapper::store_mappings( auto& input_mapping = mappings.back(); for (uint32_t idx = 2; idx < inputs.size(); ++idx) input_mapping.add_store(inputs[idx].data()); - return std::move(mappings); + return mappings; } case CUNUMERIC_FFT: { std::vector mappings; @@ -93,7 +93,7 @@ std::vector CuNumericMapper::store_mappings( mappings.push_back(StoreMapping::default_mapping(inputs[0].data(), options.front())); mappings.push_back( StoreMapping::default_mapping(outputs[0].data(), options.front(), true /*exact*/)); - return std::move(mappings); + return mappings; } case CUNUMERIC_TRANSPOSE_COPY_2D: { auto logical = task.scalars()[0].value(); @@ -103,7 +103,7 @@ std::vector CuNumericMapper::store_mappings( mappings.push_back( StoreMapping::default_mapping(outputs[0].data(), options.front(), true /*exact*/)); mappings.back().policy().ordering.set_fortran_order(); - return std::move(mappings); + return mappings; } else return {}; } @@ -123,7 +123,7 @@ std::vector CuNumericMapper::store_mappings( mappings.push_back( StoreMapping::default_mapping(reduction.data(), options.front(), true /*exact*/)); } - return std::move(mappings); + return mappings; } case CUNUMERIC_POTRF: case CUNUMERIC_TRSM: @@ -143,7 +143,7 @@ std::vector CuNumericMapper::store_mappings( StoreMapping::default_mapping(output.data(), options.front(), true /*exact*/)); mappings.back().policy().ordering.set_fortran_order(); } - return std::move(mappings); + return mappings; } case CUNUMERIC_TRILU: { if (task.scalars().size() == 2) return {}; @@ -154,14 +154,14 @@ std::vector CuNumericMapper::store_mappings( mappings.push_back( StoreMapping::default_mapping(input.data(), options.front(), true /*exact*/)); mappings.back().policy().ordering.set_fortran_order(); - return std::move(mappings); + return mappings; } case CUNUMERIC_SEARCHSORTED: { std::vector mappings; auto inputs = task.inputs(); mappings.push_back( StoreMapping::default_mapping(inputs[0].data(), options.front(), true /*exact*/)); - return std::move(mappings); + return mappings; } case CUNUMERIC_SORT: { std::vector mappings; @@ -175,7 +175,7 @@ std::vector CuNumericMapper::store_mappings( mappings.push_back( StoreMapping::default_mapping(output.data(), options.front(), true /*exact*/)); } - return std::move(mappings); + return mappings; } case CUNUMERIC_SCAN_LOCAL: { std::vector mappings; @@ -189,7 +189,7 @@ std::vector CuNumericMapper::store_mappings( mappings.push_back( StoreMapping::default_mapping(output.data(), options.front(), true /*exact*/)); } - return std::move(mappings); + return mappings; } case CUNUMERIC_SCAN_GLOBAL: { std::vector mappings; @@ -203,7 +203,7 @@ std::vector CuNumericMapper::store_mappings( mappings.push_back( StoreMapping::default_mapping(output.data(), options.front(), true /*exact*/)); } - return std::move(mappings); + return mappings; } case CUNUMERIC_BITGENERATOR: { std::vector mappings; @@ -217,7 +217,7 @@ std::vector CuNumericMapper::store_mappings( mappings.push_back( StoreMapping::default_mapping(output.data(), options.front(), true /*exact*/)); } - return std::move(mappings); + return mappings; } default: { return {}; diff --git a/src/cunumeric/matrix/diag.cc b/src/cunumeric/matrix/diag.cc index da398e99d2..ef612ca614 100644 --- a/src/cunumeric/matrix/diag.cc +++ b/src/cunumeric/matrix/diag.cc @@ -35,7 +35,7 @@ struct DiagImplBody { { size_t skip_size = 1; - for (int i = 0; i < naxes; i++) { + for (size_t i = 0; i < naxes; i++) { auto diff = 1 + m_shape.hi[DIM - i - 1] - m_shape.lo[DIM - i - 1]; if (diff != 0) skip_size *= diff; } diff --git a/src/cunumeric/matrix/diag_omp.cc b/src/cunumeric/matrix/diag_omp.cc index 14a4842f7b..3e90457bdc 100644 --- a/src/cunumeric/matrix/diag_omp.cc +++ b/src/cunumeric/matrix/diag_omp.cc @@ -35,7 +35,7 @@ struct DiagImplBody { { size_t skip_size = 1; - for (int i = 0; i < naxes; i++) { + for (size_t i = 0; i < naxes; i++) { auto diff = 1 + m_shape.hi[DIM - i - 1] - m_shape.lo[DIM - i - 1]; if (diff != 0) skip_size *= diff; } diff --git a/src/cunumeric/matrix/dot.cc b/src/cunumeric/matrix/dot.cc index 18eaf3a87d..b57b7771f1 100644 --- a/src/cunumeric/matrix/dot.cc +++ b/src/cunumeric/matrix/dot.cc @@ -37,7 +37,7 @@ struct DotImplBody { if (dense) { auto rhs1ptr = rhs1.ptr(rect); auto rhs2ptr = rhs2.ptr(rect); - for (coord_t idx = 0; idx < volume; ++idx) { + for (size_t idx = 0; idx < volume; ++idx) { const auto prod = static_cast(rhs1ptr[idx]) * static_cast(rhs2ptr[idx]); out.reduce(0, prod); } diff --git a/src/cunumeric/matrix/solve_template.inl b/src/cunumeric/matrix/solve_template.inl index 6c9202135d..f3c182713a 100644 --- a/src/cunumeric/matrix/solve_template.inl +++ b/src/cunumeric/matrix/solve_template.inl @@ -63,7 +63,7 @@ struct SolveImpl { size_t a_strides[2]; VAL* a = a_array.read_write_accessor(a_shape).ptr(a_shape, a_strides); #ifdef DEBUG_CUNUMERIC - assert(a_array.is_future() || (a_strides[0] == 1 && a_strides[1] == m)); + assert(a_array.is_future() || (a_strides[0] == 1 && static_cast(a_strides[1]) == m)); #endif VAL* b = nullptr; @@ -84,7 +84,7 @@ struct SolveImpl { size_t b_strides[2]; b = b_array.read_write_accessor(b_shape).ptr(b_shape, b_strides); #ifdef DEBUG_CUNUMERIC - assert(b_array.is_future() || (b_strides[0] == 1 && b_strides[1] == m)); + assert(b_array.is_future() || (b_strides[0] == 1 && static_cast(b_strides[1]) == m)); #endif } diff --git a/src/cunumeric/matrix/util.cc b/src/cunumeric/matrix/util.cc index 03b74e1c42..368e2c820a 100644 --- a/src/cunumeric/matrix/util.cc +++ b/src/cunumeric/matrix/util.cc @@ -54,7 +54,7 @@ size_t stride_for_blas(size_t m, size_t n, size_t x_stride, size_t y_stride, boo // the matrix represents the transpose of a row-major nxm matrix. We then tell the BLAS library // that we are passing a row-major nxm matrix, and ask for the matrix to be transposed. #ifdef DEBUG_CUNUMERIC - assert(x_stride == 1 && y_stride > 1 || y_stride == 1 && x_stride > 1); + assert((x_stride == 1 && y_stride > 1) || (y_stride == 1 && x_stride > 1)); #endif blas_stride = std::max(x_stride, y_stride); transpose = x_stride == 1; diff --git a/src/cunumeric/matrix/util.h b/src/cunumeric/matrix/util.h index b60acd35f3..954c65767b 100644 --- a/src/cunumeric/matrix/util.h +++ b/src/cunumeric/matrix/util.h @@ -33,7 +33,7 @@ inline int64_t unflatten_with_strides(int64_t flat_idx, flat_idx /= shape[d]; } int64_t idx = 0; - for (int d = 0; d < ndim; ++d) { idx += coords[d] * strides[d]; } + for (size_t d = 0; d < ndim; ++d) { idx += coords[d] * strides[d]; } return idx; } diff --git a/src/cunumeric/ndarray.cc b/src/cunumeric/ndarray.cc index 166794ca4a..032d314c01 100644 --- a/src/cunumeric/ndarray.cc +++ b/src/cunumeric/ndarray.cc @@ -61,7 +61,7 @@ static std::vector compute_strides(const std::vector& shape) stride *= shape[dim]; } } - return std::move(strides); + return strides; } NDArray NDArray::operator+(const NDArray& other) const { return add(*this, other); } @@ -412,7 +412,7 @@ std::vector NDArray::nonzero() runtime->submit(std::move(task)); - return std::move(outputs); + return outputs; } NDArray NDArray::unique() @@ -463,7 +463,7 @@ NDArray NDArray::as_type(const legate::Type& type) auto out = runtime->create_array(shape(), type); - if (size() == 0) return std::move(out); + if (size() == 0) return out; assert(store_.type() != out.store_.type()); @@ -477,7 +477,7 @@ NDArray NDArray::as_type(const legate::Type& type) runtime->submit(std::move(task)); - return std::move(out); + return out; } void NDArray::create_window(int32_t op_code, int64_t M, std::vector args) @@ -543,10 +543,10 @@ legate::LogicalStore NDArray::broadcast(const std::vector& shape, } #ifdef DEBUG_CUNUMERIC - assert(result.dim() == shape.size()); + assert(static_cast(result.dim()) == shape.size()); #endif - return std::move(result); + return result; } legate::LogicalStore NDArray::broadcast(NDArray rhs1, NDArray rhs2) diff --git a/src/cunumeric/operators.cc b/src/cunumeric/operators.cc index 4552716974..bae5c71142 100644 --- a/src/cunumeric/operators.cc +++ b/src/cunumeric/operators.cc @@ -34,7 +34,7 @@ NDArray unary_op(UnaryOpCode op_code, NDArray input) auto runtime = CuNumericRuntime::get_runtime(); auto out = runtime->create_array(input.shape(), input.type()); out.unary_op(static_cast(op_code), std::move(input)); - return std::move(out); + return out; } NDArray unary_reduction(UnaryRedCode op_code, NDArray input) @@ -42,7 +42,7 @@ NDArray unary_reduction(UnaryRedCode op_code, NDArray input) auto runtime = CuNumericRuntime::get_runtime(); auto out = runtime->create_array({1}, input.type()); out.unary_reduction(static_cast(op_code), std::move(input)); - return std::move(out); + return out; } NDArray binary_op(BinaryOpCode op_code, NDArray rhs1, NDArray rhs2, std::optional out) @@ -53,7 +53,7 @@ NDArray binary_op(BinaryOpCode op_code, NDArray rhs1, NDArray rhs2, std::optiona out = runtime->create_array(out_shape, rhs1.type()); } out->binary_op(static_cast(op_code), std::move(rhs1), std::move(rhs2)); - return std::move(out.value()); + return out.value(); } NDArray abs(NDArray input) { return unary_op(UnaryOpCode::ABSOLUTE, std::move(input)); } @@ -75,7 +75,7 @@ NDArray random(std::vector shape) auto runtime = CuNumericRuntime::get_runtime(); auto out = runtime->create_array(std::move(shape), legate::float64()); out.random(static_cast(RandGenCode::UNIFORM)); - return std::move(out); + return out; } namespace { @@ -121,7 +121,7 @@ NDArray full(std::vector shape, const Scalar& value) auto runtime = CuNumericRuntime::get_runtime(); auto out = runtime->create_array(std::move(shape), value.type()); out.fill(value, false); - return std::move(out); + return out; } NDArray eye(size_t n, std::optional m, int32_t k, const legate::Type& type) @@ -132,7 +132,7 @@ NDArray eye(size_t n, std::optional m, int32_t k, const legate::Type& ty auto runtime = CuNumericRuntime::get_runtime(); auto out = runtime->create_array({n, m.value_or(n)}, type); out.eye(k); - return std::move(out); + return out; } NDArray bincount(NDArray x, @@ -154,7 +154,7 @@ NDArray bincount(NDArray x, auto min_val = legate::type_dispatch(min_val_arr.type().code(), generate_int_value_fn{}, min_val_arr); if (min_val < 0) throw std::invalid_argument("the input array must have no negative elements"); - if (min_length < max_val + 1) min_length = max_val + 1; + if (static_cast(min_length) < max_val + 1) min_length = max_val + 1; auto runtime = CuNumericRuntime::get_runtime(); if (!weights.has_value()) { @@ -188,7 +188,7 @@ NDArray trilu(NDArray rhs, int32_t k, bool lower) auto runtime = CuNumericRuntime::get_runtime(); auto out = runtime->create_array(std::move(out_shape), rhs.type()); out.trilu(std::move(rhs), k, lower); - return std::move(out); + return out; } NDArray tril(NDArray rhs, int32_t k) { return trilu(rhs, k, true); } @@ -222,7 +222,7 @@ NDArray dot(NDArray rhs1, NDArray rhs2) auto out = runtime->create_array(std::move(shape), rhs1.type()); out.dot(std::move(rhs1), std::move(rhs2)); - return std::move(out); + return out; } NDArray sum(NDArray input) { return unary_reduction(UnaryRedCode::SUM, std::move(input)); } @@ -251,7 +251,7 @@ NDArray arange(std::optional start, size_t N = ceil((stop.value() - start.value()) / step.value()); auto out = CuNumericRuntime::get_runtime()->create_array({N}, type); out.arange(start.value(), stop.value(), step.value()); - return std::move(out); + return out; } NDArray as_array(legate::LogicalStore store) diff --git a/src/cunumeric/random/bitgenerator_curand.inl b/src/cunumeric/random/bitgenerator_curand.inl index adb4539add..1def84550f 100644 --- a/src/cunumeric/random/bitgenerator_curand.inl +++ b/src/cunumeric/random/bitgenerator_curand.inl @@ -35,8 +35,6 @@ using namespace legate; template struct CURANDGeneratorBuilder; -#pragma region wrapper to randutil - struct CURANDGenerator { randutilGenerator_t gen_; uint64_t seed_; @@ -45,7 +43,7 @@ struct CURANDGenerator { protected: CURANDGenerator(BitGeneratorType gentype, uint64_t seed, uint64_t generatorId) - : type_(get_curandRngType(gentype)), seed_(seed), generatorId_(generatorId) + : seed_(seed), generatorId_(generatorId), type_(get_curandRngType(gentype)) { randutil_log().debug() << "CURANDGenerator::create"; } @@ -271,8 +269,6 @@ struct CURANDGenerator { } }; -#pragma endregion - struct generate_fn { template size_t operator()(CURANDGenerator& gen, legate::Store output) @@ -295,10 +291,6 @@ struct generate_fn { } }; -#pragma region generators - -#pragma region integer - template struct integer_generator; template <> @@ -350,10 +342,6 @@ struct integer_generator { } }; -#pragma endregion - -#pragma region uniform - template struct uniform_generator; template <> @@ -389,10 +377,6 @@ struct uniform_generator { } }; -#pragma endregion - -#pragma region lognormal - template struct lognormal_generator; template <> @@ -428,10 +412,6 @@ struct lognormal_generator { } }; -#pragma endregion - -#pragma region normal - template struct normal_generator; template <> @@ -467,10 +447,6 @@ struct normal_generator { } }; -#pragma endregion - -#pragma region poisson - template struct poisson_generator; template <> @@ -490,10 +466,6 @@ struct poisson_generator { } }; -#pragma endregion - -#pragma region exponential - template struct exponential_generator; template <> @@ -529,10 +501,6 @@ struct exponential_generator { } }; -#pragma endregion - -#pragma region gumbel - template struct gumbel_generator; template <> @@ -568,10 +536,6 @@ struct gumbel_generator { } }; -#pragma endregion - -#pragma region laplace - template struct laplace_generator; template <> @@ -607,10 +571,6 @@ struct laplace_generator { } }; -#pragma endregion - -#pragma region logistic - template struct logistic_generator; template <> @@ -646,10 +606,6 @@ struct logistic_generator { } }; -#pragma endregion - -#pragma region pareto - template struct pareto_generator; template <> @@ -685,10 +641,6 @@ struct pareto_generator { } }; -#pragma endregion - -#pragma region power - template struct power_generator; template <> @@ -724,10 +676,6 @@ struct power_generator { } }; -#pragma endregion - -#pragma region rayleigh - template struct rayleigh_generator; template <> @@ -763,10 +711,6 @@ struct rayleigh_generator { } }; -#pragma endregion - -#pragma region cauchy - template struct cauchy_generator; template <> @@ -802,10 +746,6 @@ struct cauchy_generator { } }; -#pragma endregion - -#pragma region triangular - template struct triangular_generator; template <> @@ -841,10 +781,6 @@ struct triangular_generator { } }; -#pragma endregion - -#pragma region weibull - template struct weibull_generator; template <> @@ -880,10 +816,6 @@ struct weibull_generator { } }; -#pragma endregion - -#pragma region bytes - template struct bytes_generator; template <> @@ -901,10 +833,6 @@ struct bytes_generator { } }; -#pragma endregion - -#pragma region beta - template struct beta_generator; template <> @@ -940,10 +868,6 @@ struct beta_generator { } }; -#pragma endregion - -#pragma region f - template struct f_generator; template <> @@ -979,10 +903,6 @@ struct f_generator { } }; -#pragma endregion - -#pragma region logseries - template struct logseries_generator; template <> @@ -1002,10 +922,6 @@ struct logseries_generator { } }; -#pragma endregion - -#pragma region noncentral_f - template struct noncentral_f_generator; template <> @@ -1041,10 +957,6 @@ struct noncentral_f_generator { } }; -#pragma endregion - -#pragma region chisquare - template struct chisquare_generator; template <> @@ -1080,10 +992,6 @@ struct chisquare_generator { } }; -#pragma endregion - -#pragma region gamma - template struct gamma_generator; template <> @@ -1119,10 +1027,6 @@ struct gamma_generator { } }; -#pragma endregion - -#pragma region hypergeometric - template struct hypergeometric_generator; template <> @@ -1142,10 +1046,6 @@ struct hypergeometric_generator { } }; -#pragma endregion - -#pragma region zipf - template struct zipf_generator; template <> @@ -1165,10 +1065,6 @@ struct zipf_generator { } }; -#pragma endregion - -#pragma region geometric - template struct geometric_generator; template <> @@ -1188,10 +1084,6 @@ struct geometric_generator { } }; -#pragma endregion - -#pragma region standard_t - template struct standard_t_generator; template <> @@ -1227,10 +1119,6 @@ struct standard_t_generator { } }; -#pragma endregion - -#pragma region vonmises - template struct vonmises_generator; template <> @@ -1266,10 +1154,6 @@ struct vonmises_generator { } }; -#pragma endregion - -#pragma region wald - template struct wald_generator; template <> @@ -1305,10 +1189,6 @@ struct wald_generator { } }; -#pragma endregion - -#pragma region binomial - template struct binomial_generator; template <> @@ -1329,10 +1209,6 @@ struct binomial_generator { } }; -#pragma endregion - -#pragma region negative_binomial - template struct negative_binomial_generator; template <> @@ -1353,10 +1229,6 @@ struct negative_binomial_generator { } }; -#pragma endregion - -#pragma endregion - template struct generate_distribution { const generator_t& generator_; diff --git a/src/cunumeric/random/randutil/generator_host.cc b/src/cunumeric/random/randutil/generator_host.cc index 7e080f9b84..c38a3f4eaa 100644 --- a/src/cunumeric/random/randutil/generator_host.cc +++ b/src/cunumeric/random/randutil/generator_host.cc @@ -42,8 +42,6 @@ extern "C" curandStatus_t randutilDestroyGenerator(randutilGenerator_t generator return CURAND_STATUS_SUCCESS; } -#pragma region integers - #include "generator_integers.inl" extern "C" curandStatus_t randutilGenerateIntegers16( @@ -76,10 +74,6 @@ extern "C" curandStatus_t randutilGenerateIntegers64( return randutilimpl::dispatch(gen, func, n, outputPtr); } -#pragma endregion - -#pragma region lognormal - #include "generator_lognormal.inl" extern "C" curandStatus_t randutilGenerateLogNormalEx( @@ -102,10 +96,6 @@ extern "C" curandStatus_t randutilGenerateLogNormalDoubleEx( return randutilimpl::dispatch(gen, func, n, outputPtr); } -#pragma endregion - -#pragma region normal - #include "generator_normal.inl" extern "C" curandStatus_t randutilGenerateNormalEx( @@ -128,10 +118,6 @@ extern "C" curandStatus_t randutilGenerateNormalDoubleEx( return randutilimpl::dispatch(gen, func, n, outputPtr); } -#pragma endregion - -#pragma region poisson - #include "generator_poisson.inl" extern "C" curandStatus_t randutilGeneratePoissonEx(randutilGenerator_t generator, @@ -145,10 +131,6 @@ extern "C" curandStatus_t randutilGeneratePoissonEx(randutilGenerator_t generato return randutilimpl::dispatch(gen, func, n, outputPtr); } -#pragma endregion - -#pragma region raw - #include "generator_raw.inl" extern "C" curandStatus_t randutilGenerateRawUInt32(randutilGenerator_t generator, @@ -160,10 +142,6 @@ extern "C" curandStatus_t randutilGenerateRawUInt32(randutilGenerator_t generato return randutilimpl::dispatch(gen, func, n, outputPtr); } -#pragma endregion - -#pragma region uniform - #include "generator_uniform.inl" extern "C" curandStatus_t randutilGenerateUniformEx( @@ -186,5 +164,3 @@ extern "C" curandStatus_t randutilGenerateUniformDoubleEx( func.mult = low - high; return randutilimpl::dispatch(gen, func, n, outputPtr); } - -#pragma endregion diff --git a/src/cunumeric/random/randutil/generator_host_advanced.cc b/src/cunumeric/random/randutil/generator_host_advanced.cc index 6f836403f7..c53dc1bb80 100644 --- a/src/cunumeric/random/randutil/generator_host_advanced.cc +++ b/src/cunumeric/random/randutil/generator_host_advanced.cc @@ -16,8 +16,6 @@ #include "generator.h" -#pragma region beta - #include "generator_beta.inl" extern "C" curandStatus_t randutilGenerateBetaEx( @@ -40,10 +38,6 @@ extern "C" curandStatus_t randutilGenerateBetaDoubleEx( return randutilimpl::dispatch(gen, func, n, outputPtr); } -#pragma endregion - -#pragma region FisherSnedecor - #include "generator_f.inl" extern "C" curandStatus_t randutilGenerateFisherSnedecorEx(randutilGenerator_t generator, @@ -91,10 +85,6 @@ extern "C" curandStatus_t randutilGenerateFisherSnedecorDoubleEx( } } -#pragma endregion - -#pragma region logseries - #include "generator_logseries.inl" extern "C" curandStatus_t randutilGenerateLogSeriesEx(randutilGenerator_t generator, @@ -108,10 +98,6 @@ extern "C" curandStatus_t randutilGenerateLogSeriesEx(randutilGenerator_t genera return randutilimpl::dispatch(gen, func, n, outputPtr); } -#pragma endregion - -#pragma region ChiSquared - #include "generator_chisquare.inl" extern "C" curandStatus_t randutilGenerateChiSquareEx( @@ -154,10 +140,6 @@ extern "C" curandStatus_t randutilGenerateChiSquareDoubleEx( } } -#pragma endregion - -#pragma region gamma - #include "generator_gamma.inl" extern "C" curandStatus_t randutilGenerateGammaEx(randutilGenerator_t generator, @@ -186,10 +168,6 @@ extern "C" curandStatus_t randutilGenerateGammaDoubleEx(randutilGenerator_t gene return randutilimpl::dispatch(gen, func, n, outputPtr); } -#pragma endregion - -#pragma region standart_t - #include "generator_standard_t.inl" extern "C" curandStatus_t randutilGenerateStandardTEx(randutilGenerator_t generator, @@ -214,10 +192,6 @@ extern "C" curandStatus_t randutilGenerateStandardTDoubleEx(randutilGenerator_t return randutilimpl::dispatch(gen, func, n, outputPtr); } -#pragma endregion - -#pragma region von mises - #include "generator_vonmises.inl" extern "C" curandStatus_t randutilGenerateVonMisesEx( @@ -240,10 +214,6 @@ extern "C" curandStatus_t randutilGenerateVonMisesDoubleEx( return randutilimpl::dispatch(gen, func, n, outputPtr); } -#pragma endregion - -#pragma region hypergeometric - #include "generator_hypergeometric.inl" extern "C" curandStatus_t randutilGenerateHyperGeometricEx(randutilGenerator_t generator, @@ -261,10 +231,6 @@ extern "C" curandStatus_t randutilGenerateHyperGeometricEx(randutilGenerator_t g return randutilimpl::dispatch(gen, func, n, outputPtr); } -#pragma endregion - -#pragma region zipf - #include "generator_zipf.inl" extern "C" curandStatus_t randutilGenerateZipfEx(randutilGenerator_t generator, @@ -278,10 +244,6 @@ extern "C" curandStatus_t randutilGenerateZipfEx(randutilGenerator_t generator, return randutilimpl::dispatch(gen, func, n, outputPtr); } -#pragma endregion - -#pragma region geometric - #include "generator_geometric.inl" extern "C" curandStatus_t randutilGenerateGeometricEx(randutilGenerator_t generator, @@ -295,10 +257,6 @@ extern "C" curandStatus_t randutilGenerateGeometricEx(randutilGenerator_t genera return randutilimpl::dispatch(gen, func, n, outputPtr); } -#pragma endregion - -#pragma region wald - #include "generator_wald.inl" extern "C" curandStatus_t randutilGenerateWaldEx( @@ -321,10 +279,6 @@ extern "C" curandStatus_t randutilGenerateWaldDoubleEx( return randutilimpl::dispatch(gen, func, n, outputPtr); } -#pragma endregion - -#pragma region binomial - #include "generator_binomial.inl" extern "C" curandStatus_t randutilGenerateBinomialEx( @@ -337,10 +291,6 @@ extern "C" curandStatus_t randutilGenerateBinomialEx( return randutilimpl::dispatch(gen, func, n, outputPtr); } -#pragma endregion - -#pragma region negative binomial - #include "generator_negative_binomial.inl" extern "C" curandStatus_t randutilGenerateNegativeBinomialEx( @@ -352,5 +302,3 @@ extern "C" curandStatus_t randutilGenerateNegativeBinomialEx( func.p = p; return randutilimpl::dispatch(gen, func, n, outputPtr); } - -#pragma endregion diff --git a/src/cunumeric/random/randutil/generator_host_straightforward.cc b/src/cunumeric/random/randutil/generator_host_straightforward.cc index 894ad55710..04683d11a9 100644 --- a/src/cunumeric/random/randutil/generator_host_straightforward.cc +++ b/src/cunumeric/random/randutil/generator_host_straightforward.cc @@ -16,8 +16,6 @@ #include "generator.h" -#pragma region exponential - #include "generator_exponential.inl" extern "C" curandStatus_t randutilGenerateExponentialEx(randutilGenerator_t generator, @@ -42,10 +40,6 @@ extern "C" curandStatus_t randutilGenerateExponentialDoubleEx(randutilGenerator_ return randutilimpl::dispatch(gen, func, n, outputPtr); } -#pragma endregion - -#pragma region gumbel - #include "generator_gumbel.inl" extern "C" curandStatus_t randutilGenerateGumbelEx( @@ -68,10 +62,6 @@ extern "C" curandStatus_t randutilGenerateGumbelDoubleEx( return randutilimpl::dispatch(gen, func, n, outputPtr); } -#pragma endregion - -#pragma region laplace - #include "generator_laplace.inl" extern "C" curandStatus_t randutilGenerateLaplaceEx( @@ -94,10 +84,6 @@ extern "C" curandStatus_t randutilGenerateLaplaceDoubleEx( return randutilimpl::dispatch(gen, func, n, outputPtr); } -#pragma endregion - -#pragma region logistic - #include "generator_logistic.inl" extern "C" curandStatus_t randutilGenerateLogisticEx( @@ -120,10 +106,6 @@ extern "C" curandStatus_t randutilGenerateLogisticDoubleEx( return randutilimpl::dispatch(gen, func, n, outputPtr); } -#pragma endregion - -#pragma region pareto - #include "generator_pareto.inl" extern "C" curandStatus_t randutilGenerateParetoEx( @@ -146,10 +128,6 @@ extern "C" curandStatus_t randutilGenerateParetoDoubleEx( return randutilimpl::dispatch(gen, func, n, outputPtr); } -#pragma endregion - -#pragma region power - #include "generator_power.inl" extern "C" curandStatus_t randutilGeneratePowerEx(randutilGenerator_t generator, @@ -174,10 +152,6 @@ extern "C" curandStatus_t randutilGeneratePowerDoubleEx(randutilGenerator_t gene return randutilimpl::dispatch(gen, func, n, outputPtr); } -#pragma endregion - -#pragma region rayleigh - #include "generator_rayleigh.inl" extern "C" curandStatus_t randutilGenerateRayleighEx(randutilGenerator_t generator, @@ -202,10 +176,6 @@ extern "C" curandStatus_t randutilGenerateRayleighDoubleEx(randutilGenerator_t g return randutilimpl::dispatch(gen, func, n, outputPtr); } -#pragma endregion - -#pragma region cauchy - #include "generator_cauchy.inl" extern "C" curandStatus_t randutilGenerateCauchyEx( @@ -228,10 +198,6 @@ extern "C" curandStatus_t randutilGenerateCauchyDoubleEx( return randutilimpl::dispatch(gen, func, n, outputPtr); } -#pragma endregion - -#pragma region triangular - #include "generator_triangular.inl" extern "C" curandStatus_t randutilGenerateTriangularEx( @@ -256,10 +222,6 @@ extern "C" curandStatus_t randutilGenerateTriangularDoubleEx( return randutilimpl::dispatch(gen, func, n, outputPtr); } -#pragma endregion - -#pragma region weibull - #include "generator_weibull.inl" extern "C" curandStatus_t randutilGenerateWeibullEx( @@ -281,5 +243,3 @@ extern "C" curandStatus_t randutilGenerateWeibullDoubleEx( func.invk = 1.0 / k; return randutilimpl::dispatch(gen, func, n, outputPtr); } - -#pragma endregion diff --git a/src/cunumeric/random/randutil/random_distributions.h b/src/cunumeric/random/randutil/random_distributions.h index db4e609e46..62364fd359 100644 --- a/src/cunumeric/random/randutil/random_distributions.h +++ b/src/cunumeric/random/randutil/random_distributions.h @@ -326,8 +326,6 @@ RANDUTIL_QUALIFIERS double rk_standard_t(rk_state* state, double df) return sqrt(df / 2) * rk_gauss(state) / sqrt(rk_standard_gamma(state, df / 2)); } -#pragma region geometric - template RANDUTIL_QUALIFIERS long rk_geometric_search(rk_state* state, double p) { @@ -421,10 +419,6 @@ RANDUTIL_QUALIFIERS double rk_vonmises(rk_state* state, double mu, double kappa) } } -#pragma endregion - -#pragma region hypergeometric - RANDUTIL_QUALIFIERS long long_min(long a, long b) { return a < b ? a : b; } RANDUTIL_QUALIFIERS long long_max(long a, long b) { return a > b ? a : b; } @@ -505,10 +499,6 @@ RANDUTIL_QUALIFIERS long rk_hypergeometric(rk_state* state, long good, long bad, } } -#pragma endregion - -#pragma region binomial - template RANDUTIL_QUALIFIERS unsigned rk_binomial_btpe(rk_state* state, unsigned n, double p) { @@ -645,5 +635,3 @@ RANDUTIL_QUALIFIERS unsigned rk_binomial(rk_state* state, unsigned n, double p) } } } - -#pragma endregion \ No newline at end of file diff --git a/src/cunumeric/runtime.cc b/src/cunumeric/runtime.cc index f14cc749de..e1bf3ae61b 100644 --- a/src/cunumeric/runtime.cc +++ b/src/cunumeric/runtime.cc @@ -110,7 +110,7 @@ legate::Type CuNumericRuntime::get_argred_type(const legate::Type& value_type) auto argred_type = legate::struct_type({legate::int64(), value_type}, true /*align*/); argred_types_.insert({value_type.code(), argred_type}); - return std::move(argred_type); + return argred_type; } namespace { diff --git a/src/cunumeric/scan/scan_global.h b/src/cunumeric/scan/scan_global.h index 853a7518fd..267394ac2a 100644 --- a/src/cunumeric/scan/scan_global.h +++ b/src/cunumeric/scan/scan_global.h @@ -16,7 +16,7 @@ #pragma once -#include "cunumeric/scan/scan_global_util.h" +#include "cunumeric/scan/scan_util.h" #include "cunumeric/cunumeric_task.h" namespace cunumeric { diff --git a/src/cunumeric/scan/scan_global_template.inl b/src/cunumeric/scan/scan_global_template.inl index 2134900a52..bddacf8950 100644 --- a/src/cunumeric/scan/scan_global_template.inl +++ b/src/cunumeric/scan/scan_global_template.inl @@ -14,7 +14,7 @@ * */ -#include "cunumeric/scan/scan_global_util.h" +#include "cunumeric/scan/scan_util.h" #include "cunumeric/pitches.h" namespace cunumeric { @@ -38,7 +38,6 @@ struct ScanGlobalImpl { Pitches out_pitches; size_t volume = out_pitches.flatten(out_rect); Pitches sum_vals_pitches; - size_t sum_vals_volume = sum_vals_pitches.flatten(sum_vals_rect); if (volume == 0) return; diff --git a/src/cunumeric/scan/scan_local.h b/src/cunumeric/scan/scan_local.h index 028141fed3..d215a5c4eb 100644 --- a/src/cunumeric/scan/scan_local.h +++ b/src/cunumeric/scan/scan_local.h @@ -16,7 +16,7 @@ #pragma once -#include "cunumeric/scan/scan_local_util.h" +#include "cunumeric/scan/scan_util.h" #include "cunumeric/cunumeric_task.h" namespace cunumeric { diff --git a/src/cunumeric/scan/scan_local_template.inl b/src/cunumeric/scan/scan_local_template.inl index 566d8e4c32..85021b830a 100644 --- a/src/cunumeric/scan/scan_local_template.inl +++ b/src/cunumeric/scan/scan_local_template.inl @@ -14,7 +14,7 @@ * */ -#include "cunumeric/scan/scan_local_util.h" +#include "cunumeric/scan/scan_util.h" #include "cunumeric/pitches.h" namespace cunumeric { diff --git a/src/cunumeric/scan/scan_global_util.h b/src/cunumeric/scan/scan_util.h similarity index 55% rename from src/cunumeric/scan/scan_global_util.h rename to src/cunumeric/scan/scan_util.h index ed5b33b5d6..d2f3d6c492 100644 --- a/src/cunumeric/scan/scan_global_util.h +++ b/src/cunumeric/scan/scan_util.h @@ -40,19 +40,51 @@ constexpr decltype(auto) op_dispatch(ScanCode op_code, Functor f, Fnargs&&... ar return f.template operator()(std::forward(args)...); } +template +constexpr decltype(auto) op_dispatch(ScanCode op_code, + bool nan_to_identity, + Functor f, + Fnargs&&... args) +{ + switch (op_code) { + case ScanCode::PROD: + if (nan_to_identity) { + return f.template operator()(std::forward(args)...); + } else { + return f.template operator()(std::forward(args)...); + } + case ScanCode::SUM: + if (nan_to_identity) { + return f.template operator()(std::forward(args)...); + } else { + return f.template operator()(std::forward(args)...); + } + default: break; + } + assert(false); + return f.template operator()(std::forward(args)...); +} + template -struct ScanOp {}; +struct ScanOp; template struct ScanOp : thrust::plus> { + using T = legate::legate_type_of; static constexpr int nan_identity = 0; - ScanOp() {} }; template struct ScanOp : thrust::multiplies> { + using T = legate::legate_type_of; static constexpr int nan_identity = 1; - ScanOp() {} +}; + +template <> +struct ScanOp { + using T = bool; + static constexpr bool nan_identity = true; + constexpr T operator()(const bool& lhs, const bool& rhs) const { return lhs && rhs; } }; } // namespace cunumeric diff --git a/src/cunumeric/search/argwhere.cc b/src/cunumeric/search/argwhere.cc index f4ed83cb6f..dc056d5672 100644 --- a/src/cunumeric/search/argwhere.cc +++ b/src/cunumeric/search/argwhere.cc @@ -49,7 +49,7 @@ struct ArgWhereImplBody { } } - assert(size == out_idx); + assert(static_cast(size) == out_idx); } }; diff --git a/src/cunumeric/sort/searchsorted.cc b/src/cunumeric/sort/searchsorted.cc index 4e541c4900..3fb4a23b5e 100644 --- a/src/cunumeric/sort/searchsorted.cc +++ b/src/cunumeric/sort/searchsorted.cc @@ -53,7 +53,9 @@ struct SearchSortedImplBody { VAL key = input_v_ptr[idx]; auto v_point = pitches.unflatten(idx, rect_values.lo); int64_t lower_bound = std::lower_bound(input_ptr, input_ptr + volume, key) - input_ptr; - if (lower_bound < volume) { output_reduction.reduce(v_point, lower_bound + offset); } + if (lower_bound < static_cast(volume)) { + output_reduction.reduce(v_point, lower_bound + offset); + } } } else { auto output_reduction = diff --git a/src/cunumeric/sort/searchsorted_omp.cc b/src/cunumeric/sort/searchsorted_omp.cc index 408082948e..787065020c 100644 --- a/src/cunumeric/sort/searchsorted_omp.cc +++ b/src/cunumeric/sort/searchsorted_omp.cc @@ -56,7 +56,9 @@ struct SearchSortedImplBody { VAL key = input_v_ptr[idx]; auto v_point = pitches.unflatten(idx, rect_values.lo); int64_t lower_bound = std::lower_bound(input_ptr, input_ptr + volume, key) - input_ptr; - if (lower_bound < volume) { output_reduction.reduce(v_point, lower_bound + offset); } + if (lower_bound < static_cast(volume)) { + output_reduction.reduce(v_point, lower_bound + offset); + } } } else { auto output_reduction = diff --git a/src/cunumeric/sort/searchsorted_template.inl b/src/cunumeric/sort/searchsorted_template.inl index 118cd4d833..ed967aa980 100644 --- a/src/cunumeric/sort/searchsorted_template.inl +++ b/src/cunumeric/sort/searchsorted_template.inl @@ -32,8 +32,6 @@ struct SearchSortedImpl { template void operator()(SearchSortedArgs& args) const { - using VAL = legate_type_of; - auto rect_base = args.input_base.shape<1>(); auto rect_values_in = args.input_values.shape(); auto rect_values_out = args.output_reduction.shape(); diff --git a/src/cunumeric/sort/sort.cu b/src/cunumeric/sort/sort.cu index 3f75edeb33..9e6df85072 100644 --- a/src/cunumeric/sort/sort.cu +++ b/src/cunumeric/sort/sort.cu @@ -587,7 +587,7 @@ SegmentMergePiece> merge_all_buffers( create_buffer(num_sort_ranks, legate::Memory::Z_COPY_MEM); // loop comparably small -> no init kernel - for (int i = 0; i < num_sort_ranks; ++i) { + for (size_t i = 0; i < num_sort_ranks; ++i) { target_offsets[i] = merged_size; merged_size += merge_buffers[i].size; } @@ -628,7 +628,7 @@ SegmentMergePiece> merge_all_buffers( target_offsets.destroy(); // destroy buffers - for (int i = 0; i < num_sort_ranks; ++i) { + for (size_t i = 0; i < num_sort_ranks; ++i) { SegmentMergePiece piece = merge_buffers[i]; piece.values.destroy(); if (argsort) { piece.indices.destroy(); } @@ -729,7 +729,7 @@ SegmentMergePiece> merge_all_buffers( } // destroy buffers only after each sweep - for (int i = 0; i < destroy_queue.size(); ++i) { + for (size_t i = 0; i < destroy_queue.size(); ++i) { SegmentMergePiece piece = destroy_queue[i]; piece.values.destroy(); if (segmented) { piece.segments.destroy(); } @@ -1222,7 +1222,7 @@ void sample_sort_nccl_nd( // a full sort group being empty, this should not affect local sort rank size. { auto worker_count_d = create_buffer(1, legate::Memory::GPU_FB_MEM); - int worker_count = (segment_size_l > 0 ? 1 : 0); + size_t worker_count = (segment_size_l > 0 ? 1 : 0); CHECK_CUDA(cudaMemcpyAsync( worker_count_d.ptr(0), &worker_count, sizeof(int32_t), cudaMemcpyHostToDevice, stream)); CHECK_NCCL(ncclAllReduce( @@ -1758,7 +1758,7 @@ struct SortImplBody { assert(is_index_space || is_unbound_1d_storage); std::vector sort_ranks(num_sort_ranks); size_t rank_group = local_rank / num_sort_ranks; - for (int r = 0; r < num_sort_ranks; ++r) sort_ranks[r] = rank_group * num_sort_ranks + r; + for (size_t r = 0; r < num_sort_ranks; ++r) sort_ranks[r] = rank_group * num_sort_ranks + r; void* output_ptr = nullptr; // in case the storage *is NOT* unbound -- we provide a target pointer diff --git a/src/cunumeric/sort/sort_cpu.inl b/src/cunumeric/sort/sort_cpu.inl index 31dd848463..1db25dfd3f 100644 --- a/src/cunumeric/sort/sort_cpu.inl +++ b/src/cunumeric/sort/sort_cpu.inl @@ -100,9 +100,9 @@ void rebalance_data(SegmentMergePiece& merge_buffer, { if (num_segments_l > 1) { auto* p_segments = merge_buffer.segments.ptr(0); - int64_t position = 0; - int64_t count = 0; - for (int64_t segment = 0; segment < num_segments_l; ++segment) { + size_t position = 0; + size_t count = 0; + for (size_t segment = 0; segment < num_segments_l; ++segment) { while (position < merge_buffer.size && p_segments[position] == segment) { position++; count++; @@ -163,8 +163,8 @@ void rebalance_data(SegmentMergePiece& merge_buffer, auto segment_diff_2d = create_buffer(num_segments_l * num_sort_ranks); { int pos = 0; - for (int64_t segment = 0; segment < num_segments_l; ++segment) { - for (int64_t sort_rank = 0; sort_rank < num_sort_ranks; ++sort_rank) { + for (size_t segment = 0; segment < num_segments_l; ++segment) { + for (size_t sort_rank = 0; sort_rank < num_sort_ranks; ++sort_rank) { segment_diff_2d[pos++] = segment_diff_buffers[sort_rank * num_segments_l + segment]; } } @@ -208,7 +208,7 @@ void rebalance_data(SegmentMergePiece& merge_buffer, segment_diff_2d_ptr, segment_diff_2d_ptr + num_segments_l * num_sort_ranks, segment_diff_2d_scan_ptr); - for (int64_t segment = 0; segment < num_segments_l; ++segment) { + for (size_t segment = 0; segment < num_segments_l; ++segment) { send_right[segment] = segment_diff_2d_scan_ptr[segment * num_sort_ranks + my_sort_rank]; } @@ -228,7 +228,7 @@ void rebalance_data(SegmentMergePiece& merge_buffer, size_t recv_left_size = 0; size_t send_right_size = 0; size_t recv_right_size = 0; - for (int64_t segment = 0; segment < num_segments_l; ++segment) { + for (size_t segment = 0; segment < num_segments_l; ++segment) { if (send_left[segment] > 0) send_left_size += send_left[segment]; else @@ -485,7 +485,7 @@ void sample_sort_nd(SortPiece> local_sorted, int32_t worker_count = std::accumulate(p_worker_count, p_worker_count + num_ranks, 0, std::plus()); - if (worker_count < num_ranks) { + if (static_cast(worker_count) < num_ranks) { const size_t number_sort_groups = num_ranks / num_sort_ranks; num_sort_ranks = worker_count / number_sort_groups; @@ -602,12 +602,6 @@ void sample_sort_nd(SortPiece> local_sorted, num_usable_samples_per_segment--; } - SegmentSample init_sample; - init_sample.rank = -1; - auto lower_bound = std::lower_bound( - p_samples, p_samples + num_samples_per_segment_g, init_sample, SegmentSampleComparator()); - int32_t num_usable_samples = lower_bound - p_samples; - // segment_blocks[r][segment]->global position in data for segment and r // perform blocksize wide scan on size_send[r][block*blocksize] within warp auto segment_blocks = create_buffer(num_sort_ranks * num_segments_l); @@ -618,22 +612,22 @@ void sample_sort_nd(SortPiece> local_sorted, std::fill(p_size_send, p_size_send + num_sort_ranks * (num_segments_l + 1), 0); { - for (int32_t segment = 0; segment < num_segments_l; ++segment) { + for (size_t segment = 0; segment < num_segments_l; ++segment) { int32_t start_position = segment_size_l * segment; - for (int32_t sort_rank = 0; sort_rank < num_sort_ranks; ++sort_rank) { + for (size_t sort_rank = 0; sort_rank < num_sort_ranks; ++sort_rank) { int32_t end_position = (segment + 1) * segment_size_l; if (sort_rank < num_sort_ranks - 1) { // actually search for split position in data const int32_t index = (sort_rank + 1) * num_usable_samples_per_segment / (num_sort_ranks)-1; auto& splitter = p_samples[segment * num_samples_per_segment_g + index]; - if (my_sort_rank > splitter.rank) { + if (my_sort_rank > static_cast(splitter.rank)) { // position of the last position with smaller value than splitter.value + 1 end_position = std::lower_bound( local_values + start_position, local_values + end_position, splitter.value) - local_values; - } else if (my_sort_rank < splitter.rank) { + } else if (my_sort_rank < static_cast(splitter.rank)) { // position of the first position with value larger than splitter.value end_position = std::upper_bound( @@ -663,7 +657,7 @@ void sample_sort_nd(SortPiece> local_sorted, #ifdef DEBUG_CUNUMERIC { - int32_t total_send = 0; + size_t total_send = 0; for (size_t r = 0; r < num_sort_ranks; ++r) { total_send += size_send[r * (num_segments_l + 1) + num_segments_l]; } @@ -711,15 +705,15 @@ void sample_sort_nd(SortPiece> local_sorted, auto positions = create_buffer(num_sort_ranks); positions[0] = 0; - for (int32_t sort_rank = 1; sort_rank < num_sort_ranks; ++sort_rank) { + for (size_t sort_rank = 1; sort_rank < num_sort_ranks; ++sort_rank) { positions[sort_rank] = positions[sort_rank - 1] + size_send[(sort_rank - 1) * (num_segments_l + 1) + num_segments_l]; } // fill send buffers { - for (int32_t segment = 0; segment < num_segments_l; ++segment) { - for (int32_t sort_rank = 0; sort_rank < num_sort_ranks; ++sort_rank) { + for (size_t segment = 0; segment < num_segments_l; ++segment) { + for (size_t sort_rank = 0; sort_rank < num_sort_ranks; ++sort_rank) { int32_t start_position = segment_blocks[sort_rank * num_segments_l + segment]; int32_t size = size_send[sort_rank * (num_segments_l + 1) + segment]; std::memcpy(val_send_buffer.ptr(0) + positions[sort_rank], @@ -753,8 +747,8 @@ void sample_sort_nd(SortPiece> local_sorted, auto* p_segments = merge_buffer.segments.ptr(0); // initialize segment information int32_t start_pos = 0; - for (int32_t sort_rank = 0; sort_rank < num_sort_ranks; ++sort_rank) { - for (int32_t segment = 0; segment < num_segments_l; ++segment) { + for (size_t sort_rank = 0; sort_rank < num_sort_ranks; ++sort_rank) { + for (size_t segment = 0; segment < num_segments_l; ++segment) { int32_t size = size_recv[sort_rank * (num_segments_l + 1) + segment]; std::fill(p_segments + start_pos, p_segments + start_pos + size, segment); start_pos += size; @@ -986,7 +980,7 @@ struct SortImplBodyCpu { assert(is_index_space || is_unbound_1d_storage); std::vector sort_ranks(num_sort_ranks); size_t rank_group = local_rank / num_sort_ranks; - for (int r = 0; r < num_sort_ranks; ++r) sort_ranks[r] = rank_group * num_sort_ranks + r; + for (size_t r = 0; r < num_sort_ranks; ++r) sort_ranks[r] = rank_group * num_sort_ranks + r; void* output_ptr = nullptr; // in case the storage *is NOT* unbound -- we provide a target pointer diff --git a/src/cunumeric/sort/sort_template.inl b/src/cunumeric/sort/sort_template.inl index abcec8621b..9ed6a38811 100644 --- a/src/cunumeric/sort/sort_template.inl +++ b/src/cunumeric/sort/sort_template.inl @@ -44,8 +44,6 @@ struct SortImpl { template void operator()(SortArgs& args, std::vector comms) const { - using VAL = legate_type_of; - auto rect = args.input.shape(); Pitches pitches; diff --git a/src/cunumeric/stat/bincount.cc b/src/cunumeric/stat/bincount.cc index ba7ba80229..0bae9a70f3 100644 --- a/src/cunumeric/stat/bincount.cc +++ b/src/cunumeric/stat/bincount.cc @@ -30,7 +30,7 @@ struct BincountImplBody { const Rect<1>& rect, const Rect<1>& lhs_rect) const { - for (size_t idx = rect.lo[0]; idx <= rect.hi[0]; ++idx) { + for (int64_t idx = rect.lo[0]; idx <= rect.hi[0]; ++idx) { auto value = rhs[idx]; assert(lhs_rect.contains(value)); lhs.reduce(value, 1); @@ -43,7 +43,7 @@ struct BincountImplBody { const Rect<1>& rect, const Rect<1>& lhs_rect) const { - for (size_t idx = rect.lo[0]; idx <= rect.hi[0]; ++idx) { + for (int64_t idx = rect.lo[0]; idx <= rect.hi[0]; ++idx) { auto value = rhs[idx]; assert(lhs_rect.contains(value)); lhs.reduce(value, weights[idx]); diff --git a/src/cunumeric/stat/bincount_omp.cc b/src/cunumeric/stat/bincount_omp.cc index 651b6f0e41..5a3bf3c85f 100644 --- a/src/cunumeric/stat/bincount_omp.cc +++ b/src/cunumeric/stat/bincount_omp.cc @@ -43,13 +43,13 @@ struct BincountImplBody { auto tid = omp_get_thread_num(); std::vector& local_bins = all_local_bins[tid]; #pragma omp for schedule(static) - for (size_t idx = rect.lo[0]; idx <= rect.hi[0]; ++idx) { + for (int64_t idx = rect.lo[0]; idx <= rect.hi[0]; ++idx) { auto value = rhs[idx]; assert(lhs_rect.contains(value)); SumReduction::fold(local_bins[value], 1); } } - return std::move(all_local_bins); + return all_local_bins; } std::vector> _bincount(const AccessorRO& rhs, @@ -69,13 +69,13 @@ struct BincountImplBody { auto tid = omp_get_thread_num(); std::vector& local_bins = all_local_bins[tid]; #pragma omp for schedule(static) - for (size_t idx = rect.lo[0]; idx <= rect.hi[0]; ++idx) { + for (int64_t idx = rect.lo[0]; idx <= rect.hi[0]; ++idx) { auto value = rhs[idx]; assert(lhs_rect.contains(value)); SumReduction::fold(local_bins[value], weights[idx]); } } - return std::move(all_local_bins); + return all_local_bins; } void operator()(AccessorRD, true, 1> lhs, diff --git a/src/cunumeric/stat/histogram_cpu.h b/src/cunumeric/stat/histogram_cpu.h index cb4a2a07dd..f24e8514b1 100644 --- a/src/cunumeric/stat/histogram_cpu.h +++ b/src/cunumeric/stat/histogram_cpu.h @@ -61,7 +61,7 @@ struct segmented_sum_t { constexpr T operator()(const T& x) const { return x * x; } }; +template <> +struct UnaryOp { + static constexpr bool valid = true; + using T = bool; + + UnaryOp(const std::vector& args) {} + + constexpr bool operator()(const bool& x) const { return x && x; } +}; + template struct UnaryOp { static constexpr bool valid = true; From c2fc174b3cef8563f1eed5e150fbf02290d57b7f Mon Sep 17 00:00:00 2001 From: Wonchan Lee Date: Fri, 13 Oct 2023 19:37:55 -0700 Subject: [PATCH 105/462] Use LegateDefined for checking the bounds checks macro (#36) --- src/cunumeric/binary/binary_op_template.inl | 2 +- src/cunumeric/binary/binary_red_template.inl | 2 +- src/cunumeric/index/choose_template.inl | 2 +- src/cunumeric/index/putmask_template.inl | 4 ++-- src/cunumeric/index/wrap_template.inl | 2 +- src/cunumeric/index/zip_template.inl | 2 +- src/cunumeric/matrix/dot_template.inl | 2 +- src/cunumeric/nullary/fill_template.inl | 2 +- src/cunumeric/nullary/window_template.inl | 2 +- src/cunumeric/ternary/where_template.inl | 2 +- src/cunumeric/unary/convert_template.inl | 2 +- src/cunumeric/unary/scalar_unary_red_template.inl | 4 ++-- src/cunumeric/unary/unary_op_template.inl | 6 +++--- 13 files changed, 17 insertions(+), 17 deletions(-) diff --git a/src/cunumeric/binary/binary_op_template.inl b/src/cunumeric/binary/binary_op_template.inl index 22253d036b..bdde03d484 100644 --- a/src/cunumeric/binary/binary_op_template.inl +++ b/src/cunumeric/binary/binary_op_template.inl @@ -49,7 +49,7 @@ struct BinaryOpImpl { auto in1 = args.in1.read_accessor(rect); auto in2 = args.in2.read_accessor(rect); -#ifndef LEGATE_BOUNDS_CHECKS +#if !LegateDefined(LEGATE_BOUNDS_CHECKS) // Check to see if this is dense or not bool dense = out.accessor.is_dense_row_major(rect) && in1.accessor.is_dense_row_major(rect) && in2.accessor.is_dense_row_major(rect); diff --git a/src/cunumeric/binary/binary_red_template.inl b/src/cunumeric/binary/binary_red_template.inl index d77983e2f0..57d4d3f415 100644 --- a/src/cunumeric/binary/binary_red_template.inl +++ b/src/cunumeric/binary/binary_red_template.inl @@ -54,7 +54,7 @@ struct BinaryRedImpl { auto in1 = args.in1.read_accessor(rect); auto in2 = args.in2.read_accessor(rect); -#ifndef LEGATE_BOUNDS_CHECKS +#if !LegateDefined(LEGATE_BOUNDS_CHECKS) // Check to see if this is dense or not bool dense = in1.accessor.is_dense_row_major(rect) && in2.accessor.is_dense_row_major(rect); #else diff --git a/src/cunumeric/index/choose_template.inl b/src/cunumeric/index/choose_template.inl index 568ca6b4b7..420c1fda94 100644 --- a/src/cunumeric/index/choose_template.inl +++ b/src/cunumeric/index/choose_template.inl @@ -43,7 +43,7 @@ struct ChooseImpl { auto index_rect = args.inputs[0].shape(); auto index_arr = args.inputs[0].read_accessor(index_rect); -#if LegateDefined(LEGATE_BOUNDS_CHECKS) +#if !LegateDefined(LEGATE_BOUNDS_CHECKS) // Check to see if this is dense or not bool dense = index_arr.accessor.is_dense_row_major(out_rect) && out.accessor.is_dense_row_major(out_rect); diff --git a/src/cunumeric/index/putmask_template.inl b/src/cunumeric/index/putmask_template.inl index fbff142c86..216908b116 100644 --- a/src/cunumeric/index/putmask_template.inl +++ b/src/cunumeric/index/putmask_template.inl @@ -57,7 +57,7 @@ struct Putmask { values = args.values.read_accessor(rect); volume = pitches.flatten(rect); if (volume == 0) return; -#ifndef LEGATE_BOUNDS_CHECKS +#if !LegateDefined(LEGATE_BOUNDS_CHECKS) dense = input.accessor.is_dense_row_major(rect) && mask.accessor.is_dense_row_major(rect); dense = dense && values.accessor.is_dense_row_major(rect); if (dense) { @@ -81,7 +81,7 @@ struct Putmask { void execute() const noexcept { -#ifndef LEGATE_BOUNDS_CHECKS +#if !LegateDefined(LEGATE_BOUNDS_CHECKS) if (dense) { return ParallelLoopPolicy()(rect, *this); } #endif return ParallelLoopPolicy()(rect, *this); diff --git a/src/cunumeric/index/wrap_template.inl b/src/cunumeric/index/wrap_template.inl index 64cc7d3c28..890ab7d796 100644 --- a/src/cunumeric/index/wrap_template.inl +++ b/src/cunumeric/index/wrap_template.inl @@ -39,7 +39,7 @@ struct WrapImpl { size_t volume_out = pitches_out.flatten(rect_out); if (volume_out == 0) return; -#if LegateDefined(LEGATE_BOUNDS_CHECKS) +#if !LegateDefined(LEGATE_BOUNDS_CHECKS) bool dense = out.accessor.is_dense_row_major(rect_out); #else bool dense = false; diff --git a/src/cunumeric/index/zip_template.inl b/src/cunumeric/index/zip_template.inl index 48c4683c17..bf2968e324 100644 --- a/src/cunumeric/index/zip_template.inl +++ b/src/cunumeric/index/zip_template.inl @@ -39,7 +39,7 @@ struct ZipImpl { size_t volume = pitches.flatten(out_rect); if (volume == 0) return; -#if LegateDefined(LEGATE_BOUNDS_CHECKS) +#if !LegateDefined(LEGATE_BOUNDS_CHECKS) bool dense = out.accessor.is_dense_row_major(out_rect); #else bool dense = false; diff --git a/src/cunumeric/matrix/dot_template.inl b/src/cunumeric/matrix/dot_template.inl index 973bc39812..cf801a4e11 100644 --- a/src/cunumeric/matrix/dot_template.inl +++ b/src/cunumeric/matrix/dot_template.inl @@ -57,7 +57,7 @@ struct DotImpl { if (rect.empty()) return; -#ifndef LEGATE_BOUNDS_CHECKS +#if !LegateDefined(LEGATE_BOUNDS_CHECKS) // Check to see if this is dense or not bool dense = rhs1.accessor.is_dense_row_major(rect) && rhs2.accessor.is_dense_row_major(rect); #else diff --git a/src/cunumeric/nullary/fill_template.inl b/src/cunumeric/nullary/fill_template.inl index 871622d14f..5190b68836 100644 --- a/src/cunumeric/nullary/fill_template.inl +++ b/src/cunumeric/nullary/fill_template.inl @@ -44,7 +44,7 @@ struct FillImpl { auto out = args.out.write_accessor(rect); auto fill_value = args.fill_value.read_accessor(); -#ifndef LEGATE_BOUNDS_CHECKS +#if !LegateDefined(LEGATE_BOUNDS_CHECKS) // Check to see if this is dense or not bool dense = out.accessor.is_dense_row_major(rect); #else diff --git a/src/cunumeric/nullary/window_template.inl b/src/cunumeric/nullary/window_template.inl index 17285d319d..61dfc922af 100644 --- a/src/cunumeric/nullary/window_template.inl +++ b/src/cunumeric/nullary/window_template.inl @@ -38,7 +38,7 @@ struct WindowImpl { auto out = output.write_accessor(rect); -#ifndef LEGATE_BOUNDS_CHECKS +#if !LegateDefined(LEGATE_BOUNDS_CHECKS) // Check to see if this is dense or not bool dense = out.accessor.is_dense_row_major(rect); #else diff --git a/src/cunumeric/ternary/where_template.inl b/src/cunumeric/ternary/where_template.inl index 6735a1f56d..46c6fe26d0 100644 --- a/src/cunumeric/ternary/where_template.inl +++ b/src/cunumeric/ternary/where_template.inl @@ -46,7 +46,7 @@ struct WhereImpl { auto in1 = args.in1.read_accessor(rect); auto in2 = args.in2.read_accessor(rect); -#ifndef LEGATE_BOUNDS_CHECKS +#if !LegateDefined(LEGATE_BOUNDS_CHECKS) // Check to see if this is dense or not bool dense = out.accessor.is_dense_row_major(rect) && in1.accessor.is_dense_row_major(rect) && in2.accessor.is_dense_row_major(rect) && mask.accessor.is_dense_row_major(rect); diff --git a/src/cunumeric/unary/convert_template.inl b/src/cunumeric/unary/convert_template.inl index be4e4c4783..39ff5981c6 100644 --- a/src/cunumeric/unary/convert_template.inl +++ b/src/cunumeric/unary/convert_template.inl @@ -47,7 +47,7 @@ struct ConvertImpl { auto out = args.out.write_accessor(rect); auto in = args.in.read_accessor(rect); -#ifndef LEGATE_BOUNDS_CHECKS +#if !LegateDefined(LEGATE_BOUNDS_CHECKS) // Check to see if this is dense or not bool dense = out.accessor.is_dense_row_major(rect) && in.accessor.is_dense_row_major(rect); #else diff --git a/src/cunumeric/unary/scalar_unary_red_template.inl b/src/cunumeric/unary/scalar_unary_red_template.inl index ac1634e8e0..648f3ac7cb 100644 --- a/src/cunumeric/unary/scalar_unary_red_template.inl +++ b/src/cunumeric/unary/scalar_unary_red_template.inl @@ -62,7 +62,7 @@ struct ScalarUnaryRed { out = args.out.reduce_accessor(); if constexpr (OP_CODE == UnaryRedCode::CONTAINS) { to_find = args.args[0].scalar(); } -#ifndef LEGATE_BOUNDS_CHECKS +#if !LegateDefined(LEGATE_BOUNDS_CHECKS) // Check to see if this is dense or not if (in.accessor.is_dense_row_major(rect)) { dense = true; @@ -102,7 +102,7 @@ struct ScalarUnaryRed { void execute() const noexcept { auto identity = LG_OP::identity; -#ifndef LEGATE_BOUNDS_CHECKS +#if !LegateDefined(LEGATE_BOUNDS_CHECKS) // The constexpr if here prevents the DenseReduction from being instantiated for GPU kernels // which limits compile times and binary sizes. if constexpr (KIND != VariantKind::GPU) { diff --git a/src/cunumeric/unary/unary_op_template.inl b/src/cunumeric/unary/unary_op_template.inl index fb8cd65cf2..cb4f4ea92e 100644 --- a/src/cunumeric/unary/unary_op_template.inl +++ b/src/cunumeric/unary/unary_op_template.inl @@ -52,7 +52,7 @@ struct UnaryOpImpl { auto out = args.out.write_accessor(rect); auto in = args.in.read_accessor(rect); -#ifndef LEGATE_BOUNDS_CHECKS +#if !LegateDefined(LEGATE_BOUNDS_CHECKS) // Check to see if this is dense or not bool dense = out.accessor.is_dense_row_major(rect) && in.accessor.is_dense_row_major(rect); #else @@ -94,7 +94,7 @@ struct MultiOutUnaryOpImpl { auto rhs1 = args.in.read_accessor(rect); auto rhs2 = args.out2.write_accessor(rect); -#ifndef LEGATE_BOUNDS_CHECKS +#if !LegateDefined(LEGATE_BOUNDS_CHECKS) // Check to see if this is dense or not bool dense = lhs.accessor.is_dense_row_major(rect) && rhs1.accessor.is_dense_row_major(rect) && rhs2.accessor.is_dense_row_major(rect); @@ -146,7 +146,7 @@ struct UnaryCopyImpl { auto out = args.out.write_accessor(rect); auto in = args.in.read_accessor(rect); -#ifndef LEGATE_BOUNDS_CHECKS +#if !LegateDefined(LEGATE_BOUNDS_CHECKS) // Check to see if this is dense or not bool dense = out.accessor.is_dense_row_major(rect) && in.accessor.is_dense_row_major(rect); #else From 541a1ecb8dddbb3d955cbe18145c1f5060fb67e8 Mon Sep 17 00:00:00 2001 From: Mona Han <129724851+mona-nv@users.noreply.github.com> Date: Fri, 20 Oct 2023 16:39:56 +0800 Subject: [PATCH 106/462] Port sort and add sort test. (#8) * Port sort and add sort test. This code only includes the situation that input axis equals last dim. * Add sort with input axis not equals last dim * Update sort and sort test based on review comments * Update sort code and test based on review comments * Remove unnecessary print line --- src/cunumeric/ndarray.cc | 79 ++++ src/cunumeric/ndarray.h | 7 + src/cunumeric/operators.cc | 8 + src/cunumeric/operators.h | 2 + tests/cpp/integration/test_sort.cc | 626 +++++++++++++++++++++++++++++ 5 files changed, 722 insertions(+) create mode 100644 tests/cpp/integration/test_sort.cc diff --git a/src/cunumeric/ndarray.cc b/src/cunumeric/ndarray.cc index 032d314c01..0e49a000b1 100644 --- a/src/cunumeric/ndarray.cc +++ b/src/cunumeric/ndarray.cc @@ -219,6 +219,85 @@ int32_t NDArray::normalize_axis_index(int32_t axis) return updated_axis; } +void NDArray::sort_task(NDArray rhs, bool argsort, bool stable) +{ + auto runtime = CuNumericRuntime::get_runtime(); + auto task = runtime->create_task(CuNumericOpCode::CUNUMERIC_SORT); + auto p_rhs = task.add_input(rhs.store_); + + auto machine = legate::Runtime::get_runtime()->get_machine(); + bool uses_unbound_output = machine.count() > 1 and rhs.dim() == 1; + std::optional unbound; + if (uses_unbound_output) { + unbound = runtime->create_array(type()); + task.add_output(unbound.value().get_store()); + } else { + auto p_lhs = task.add_output(store_); + task.add_constraint(align(p_lhs, p_rhs)); + } + + if (machine.count(legate::mapping::TaskTarget::GPU) > 0) + task.add_communicator("nccl"); + else + task.add_communicator("cpu"); + + task.add_scalar_arg(legate::Scalar(argsort)); + task.add_scalar_arg(legate::Scalar(rhs.shape())); + task.add_scalar_arg(legate::Scalar(stable)); + runtime->submit(std::move(task)); + if (uses_unbound_output) store_ = unbound.value().get_store(); +} + +void NDArray::sort_swapped(NDArray rhs, bool argsort, int32_t sort_axis, bool stable) +{ + sort_axis = rhs.normalize_axis_index(sort_axis); + + auto swapped = rhs.swapaxes(sort_axis, rhs.dim() - 1); + auto runtime = CuNumericRuntime::get_runtime(); + auto swapped_copy = runtime->create_array(swapped.shape(), swapped.type()); + swapped_copy.assign(swapped); + + if (argsort) { + auto sort_result = runtime->create_array(swapped_copy.shape(), type()); + sort_result.sort(swapped_copy, argsort, -1, stable); + store_ = sort_result.swapaxes(rhs.dim() - 1, sort_axis).get_store(); + } else { + swapped_copy.sort(swapped_copy, argsort, -1, stable); + store_ = swapped_copy.swapaxes(rhs.dim() - 1, sort_axis).get_store(); + } +} + +void NDArray::sort(NDArray rhs, bool argsort, std::optional axis, bool stable) +{ + if (!axis.has_value() && rhs.dim() > 1) { + // TODO: need to flatten the rhs and sort it, the implementation depends on reshape method. + assert(false); + } + int32_t computed_axis = 0; + if (axis.has_value()) computed_axis = rhs.normalize_axis_index(axis.value()); + + if (computed_axis == rhs.dim() - 1) { + sort_task(rhs, argsort, stable); + } else { + sort_swapped(rhs, argsort, computed_axis, stable); + } +} + +void NDArray::sort(NDArray rhs, + bool argsort /*=false*/, + std::optional axis /*=-1*/, + std::string kind /*="quicksort"*/) +{ + if (axis.has_value() && (axis >= rhs.dim() || axis < -rhs.dim())) + throw std::invalid_argument("invalid axis"); + + if (!(kind == "quicksort" || kind == "mergesort" || kind == "heapsort" || kind == "stable")) + throw std::invalid_argument("invalid kind"); + + bool stable = (kind == "stable"); + sort(rhs, argsort, axis, stable); +} + void NDArray::trilu(NDArray rhs, int32_t k, bool lower) { if (size() == 0) return; diff --git a/src/cunumeric/ndarray.h b/src/cunumeric/ndarray.h index e6a8527365..b299a97d65 100644 --- a/src/cunumeric/ndarray.h +++ b/src/cunumeric/ndarray.h @@ -84,15 +84,22 @@ class NDArray { void create_window(int32_t op_code, int64_t M, std::vector args); void bincount(NDArray rhs, std::optional weights = std::nullopt); void convolve(NDArray input, NDArray filter); + void sort(NDArray rhs, + bool argsort = false, + std::optional axis = -1, + std::string kind = "quicksort"); public: NDArray as_type(const legate::Type& type); legate::LogicalStore get_store(); int32_t normalize_axis_index(int32_t axis); + void sort(NDArray rhs, bool argsort, std::optional axis = -1, bool stable = false); private: legate::LogicalStore broadcast(const std::vector& shape, legate::LogicalStore& store); legate::LogicalStore broadcast(NDArray rhs1, NDArray rhs2); + void sort_task(NDArray rhs, bool argsort, bool stable); + void sort_swapped(NDArray rhs, bool argsort, int32_t sort_axis, bool stable); public: static legate::Library get_library(); diff --git a/src/cunumeric/operators.cc b/src/cunumeric/operators.cc index bae5c71142..ed18aa800c 100644 --- a/src/cunumeric/operators.cc +++ b/src/cunumeric/operators.cc @@ -313,4 +313,12 @@ NDArray convolve(NDArray a, NDArray v) return out; } +NDArray sort(NDArray input, std::optional axis /*=-1*/, std::string kind /*="quicksort"*/) +{ + auto runtime = CuNumericRuntime::get_runtime(); + auto result = runtime->create_array(input.shape(), input.type()); + result.sort(input, false, axis, kind); + return result; +} + } // namespace cunumeric diff --git a/src/cunumeric/operators.h b/src/cunumeric/operators.h index 95cc9ae118..2450cbd9ed 100644 --- a/src/cunumeric/operators.h +++ b/src/cunumeric/operators.h @@ -89,4 +89,6 @@ NDArray bincount(NDArray x, std::optional weights = std::nullopt, uint3 NDArray convolve(NDArray a, NDArray v); +NDArray sort(NDArray input, std::optional axis = -1, std::string kind = "quicksort"); + } // namespace cunumeric diff --git a/tests/cpp/integration/test_sort.cc b/tests/cpp/integration/test_sort.cc new file mode 100644 index 0000000000..4dc5cbce6f --- /dev/null +++ b/tests/cpp/integration/test_sort.cc @@ -0,0 +1,626 @@ +/* Copyright 2023 NVIDIA Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +#include +#include +#include + +#include +#include "legate.h" +#include "cunumeric.h" +#include "util.inl" + +auto get_expect_result_int() +{ + std::vector>> expect_result = { + {{0, {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12}}}, + {{-1, {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12}}, + {0, {10, 3, 12, 5, 2, 4, 8, 9, 7, 6, 11, 1}}, + {1, {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12}}}, + {{-1, {10, 3, 12, 5, 2, 4, 8, 9, 7, 6, 11, 1}}, + {0, {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12}}, + {1, {10, 3, 12, 5, 2, 4, 8, 9, 7, 6, 11, 1}}}, + {{-1, {3, 5, 10, 12, 2, 4, 8, 9, 1, 6, 7, 11}}, + {0, {2, 3, 8, 1, 7, 4, 11, 5, 10, 6, 12, 9}}, + {1, {3, 5, 10, 12, 2, 4, 8, 9, 1, 6, 7, 11}}}, + {{-2, {10, 3, 12, 5, 2, 4, 8, 9, 7, 6, 11, 1}}, + {-1, {10, 3, 12, 5, 2, 4, 8, 9, 7, 6, 11, 1}}, + {0, {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12}}, + {1, {10, 3, 12, 5, 2, 4, 8, 9, 7, 6, 11, 1}}, + {2, {10, 3, 12, 5, 2, 4, 8, 9, 7, 6, 11, 1}}}, + {{-2, {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12}}, + {-1, {10, 3, 12, 5, 2, 4, 8, 9, 7, 6, 11, 1}}, + {0, {10, 3, 12, 5, 2, 4, 8, 9, 7, 6, 11, 1}}, + {1, {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12}}, + {2, {10, 3, 12, 5, 2, 4, 8, 9, 7, 6, 11, 1}}}, + {{-2, {10, 3, 12, 5, 2, 4, 8, 9, 7, 6, 11, 1}}, + {-1, {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12}}, + {0, {10, 3, 12, 5, 2, 4, 8, 9, 7, 6, 11, 1}}, + {1, {10, 3, 12, 5, 2, 4, 8, 9, 7, 6, 11, 1}}, + {2, {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12}}}, + {{-2, {5, 2, 4, 10, 3, 12, 6, 9, 1, 8, 11, 7}}, + {-1, {3, 10, 12, 2, 4, 5, 7, 8, 9, 1, 6, 11}}, + {0, {8, 3, 7, 5, 2, 1, 10, 9, 12, 6, 11, 4}}, + {1, {5, 2, 4, 10, 3, 12, 6, 9, 1, 8, 11, 7}}, + {2, {3, 10, 12, 2, 4, 5, 7, 8, 9, 1, 6, 11}}}}; + return expect_result; +} + +auto get_expect_result_double() +{ + std::vector>> expect_result = { + {{0, {1.5, 2.2, 3.66, 4, 5.98, 6, 7.9, 8, 9, 10.5, 11, 12}}}, + {{-1, {1.5, 2.2, 3.66, 4, 5.98, 6, 7.9, 8, 9, 10.5, 11, 12}}, + {0, {1.5, 3.66, 6, 5.98, 2.2, 10.5, 8, 11, 7.9, 12, 9, 4}}, + {1, {1.5, 2.2, 3.66, 4, 5.98, 6, 7.9, 8, 9, 10.5, 11, 12}}}, + {{-1, {1.5, 3.66, 6, 5.98, 2.2, 10.5, 8, 11, 7.9, 12, 9, 4}}, + {0, {1.5, 2.2, 3.66, 4, 5.98, 6, 7.9, 8, 9, 10.5, 11, 12}}, + {1, {1.5, 3.66, 6, 5.98, 2.2, 10.5, 8, 11, 7.9, 12, 9, 4}}}, + {{-1, {1.5, 3.66, 5.98, 6, 2.2, 8, 10.5, 11, 4, 7.9, 9, 12}}, + {0, {1.5, 3.66, 6, 4, 2.2, 10.5, 8, 5.98, 7.9, 12, 9, 11}}, + {1, {1.5, 3.66, 5.98, 6, 2.2, 8, 10.5, 11, 4, 7.9, 9, 12}}}, + {{-2, {1.5, 3.66, 6, 5.98, 2.2, 10.5, 8, 11, 7.9, 12, 9, 4}}, + {-1, {1.5, 3.66, 6, 5.98, 2.2, 10.5, 8, 11, 7.9, 12, 9, 4}}, + {0, {1.5, 2.2, 3.66, 4, 5.98, 6, 7.9, 8, 9, 10.5, 11, 12}}, + {1, {1.5, 3.66, 6, 5.98, 2.2, 10.5, 8, 11, 7.9, 12, 9, 4}}, + {2, {1.5, 3.66, 6, 5.98, 2.2, 10.5, 8, 11, 7.9, 12, 9, 4}}}, + {{-2, {1.5, 2.2, 3.66, 4, 5.98, 6, 7.9, 8, 9, 10.5, 11, 12}}, + {-1, {1.5, 3.66, 6, 5.98, 2.2, 10.5, 8, 11, 7.9, 12, 9, 4}}, + {0, {1.5, 3.66, 6, 5.98, 2.2, 10.5, 8, 11, 7.9, 12, 9, 4}}, + {1, {1.5, 2.2, 3.66, 4, 5.98, 6, 7.9, 8, 9, 10.5, 11, 12}}, + {2, {1.5, 3.66, 6, 5.98, 2.2, 10.5, 8, 11, 7.9, 12, 9, 4}}}, + {{-2, {1.5, 3.66, 6, 5.98, 2.2, 10.5, 8, 11, 7.9, 12, 9, 4}}, + {-1, {1.5, 2.2, 3.66, 4, 5.98, 6, 7.9, 8, 9, 10.5, 11, 12}}, + {0, {1.5, 3.66, 6, 5.98, 2.2, 10.5, 8, 11, 7.9, 12, 9, 4}}, + {1, {1.5, 3.66, 6, 5.98, 2.2, 10.5, 8, 11, 7.9, 12, 9, 4}}, + {2, {1.5, 2.2, 3.66, 4, 5.98, 6, 7.9, 8, 9, 10.5, 11, 12}}}, + {{-2, {1.5, 2.2, 6, 5.98, 3.66, 10.5, 8, 9, 4, 12, 11, 7.9}}, + {-1, {1.5, 3.66, 6, 2.2, 5.98, 10.5, 7.9, 8, 11, 4, 9, 12}}, + {0, {1.5, 3.66, 6, 5.98, 2.2, 4, 8, 11, 7.9, 12, 9, 10.5}}, + {1, {1.5, 2.2, 6, 5.98, 3.66, 10.5, 8, 9, 4, 12, 11, 7.9}}, + {2, {1.5, 3.66, 6, 2.2, 5.98, 10.5, 7.9, 8, 11, 4, 9, 12}}}}; + return expect_result; +} + +auto get_expect_result_complex() +{ + std::vector, 12>>> expect_result = { + {{0, + {complex(1.5, 3.66), + complex(2, 4), + complex(2.2, 10.5), + complex(6, 4), + complex(6, 5.98), + complex(7, 6), + complex(7.9, 12), + complex(8, 9), + complex(8, 11), + complex(10, 3), + complex(11, 1), + complex(12, 5)}}}, + {{-1, + {complex(1.5, 3.66), + complex(2, 4), + complex(2.2, 10.5), + complex(6, 4), + complex(6, 5.98), + complex(7, 6), + complex(7.9, 12), + complex(8, 9), + complex(8, 11), + complex(10, 3), + complex(11, 1), + complex(12, 5)}}, + {0, + {complex(10, 3), + complex(12, 5), + complex(2, 4), + complex(8, 9), + complex(7, 6), + complex(11, 1), + complex(1.5, 3.66), + complex(6, 5.98), + complex(2.2, 10.5), + complex(8, 11), + complex(7.9, 12), + complex(6, 4)}}, + {1, + {complex(1.5, 3.66), + complex(2, 4), + complex(2.2, 10.5), + complex(6, 4), + complex(6, 5.98), + complex(7, 6), + complex(7.9, 12), + complex(8, 9), + complex(8, 11), + complex(10, 3), + complex(11, 1), + complex(12, 5)}}}, + {{-1, + {complex(10, 3), + complex(12, 5), + complex(2, 4), + complex(8, 9), + complex(7, 6), + complex(11, 1), + complex(1.5, 3.66), + complex(6, 5.98), + complex(2.2, 10.5), + complex(8, 11), + complex(7.9, 12), + complex(6, 4)}}, + {0, + {complex(1.5, 3.66), + complex(2, 4), + complex(2.2, 10.5), + complex(6, 4), + complex(6, 5.98), + complex(7, 6), + complex(7.9, 12), + complex(8, 9), + complex(8, 11), + complex(10, 3), + complex(11, 1), + complex(12, 5)}}, + {1, + {complex(10, 3), + complex(12, 5), + complex(2, 4), + complex(8, 9), + complex(7, 6), + complex(11, 1), + complex(1.5, 3.66), + complex(6, 5.98), + complex(2.2, 10.5), + complex(8, 11), + complex(7.9, 12), + complex(6, 4)}}}, + {{-1, + {complex(2, 4), + complex(8, 9), + complex(10, 3), + complex(12, 5), + complex(1.5, 3.66), + complex(6, 5.98), + complex(7, 6), + complex(11, 1), + complex(2.2, 10.5), + complex(6, 4), + complex(7.9, 12), + complex(8, 11)}}, + {0, + {complex(2.2, 10.5), + complex(8, 11), + complex(1.5, 3.66), + complex(6, 4), + complex(7, 6), + complex(11, 1), + complex(2, 4), + complex(6, 5.98), + complex(10, 3), + complex(12, 5), + complex(7.9, 12), + complex(8, 9)}}, + {1, + {complex(2, 4), + complex(8, 9), + complex(10, 3), + complex(12, 5), + complex(1.5, 3.66), + complex(6, 5.98), + complex(7, 6), + complex(11, 1), + complex(2.2, 10.5), + complex(6, 4), + complex(7.9, 12), + complex(8, 11)}}}, + {{-2, + {complex(10, 3), + complex(12, 5), + complex(2, 4), + complex(8, 9), + complex(7, 6), + complex(11, 1), + complex(1.5, 3.66), + complex(6, 5.98), + complex(2.2, 10.5), + complex(8, 11), + complex(7.9, 12), + complex(6, 4)}}, + {-1, + {complex(10, 3), + complex(12, 5), + complex(2, 4), + complex(8, 9), + complex(7, 6), + complex(11, 1), + complex(1.5, 3.66), + complex(6, 5.98), + complex(2.2, 10.5), + complex(8, 11), + complex(7.9, 12), + complex(6, 4)}}, + {0, + {complex(1.5, 3.66), + complex(2, 4), + complex(2.2, 10.5), + complex(6, 4), + complex(6, 5.98), + complex(7, 6), + complex(7.9, 12), + complex(8, 9), + complex(8, 11), + complex(10, 3), + complex(11, 1), + complex(12, 5)}}, + {1, + {complex(10, 3), + complex(12, 5), + complex(2, 4), + complex(8, 9), + complex(7, 6), + complex(11, 1), + complex(1.5, 3.66), + complex(6, 5.98), + complex(2.2, 10.5), + complex(8, 11), + complex(7.9, 12), + complex(6, 4)}}, + {2, + {complex(10, 3), + complex(12, 5), + complex(2, 4), + complex(8, 9), + complex(7, 6), + complex(11, 1), + complex(1.5, 3.66), + complex(6, 5.98), + complex(2.2, 10.5), + complex(8, 11), + complex(7.9, 12), + complex(6, 4)}}}, + {{-2, + {complex(1.5, 3.66), + complex(2, 4), + complex(2.2, 10.5), + complex(6, 4), + complex(6, 5.98), + complex(7, 6), + complex(7.9, 12), + complex(8, 9), + complex(8, 11), + complex(10, 3), + complex(11, 1), + complex(12, 5)}}, + {-1, + {complex(10, 3), + complex(12, 5), + complex(2, 4), + complex(8, 9), + complex(7, 6), + complex(11, 1), + complex(1.5, 3.66), + complex(6, 5.98), + complex(2.2, 10.5), + complex(8, 11), + complex(7.9, 12), + complex(6, 4)}}, + {0, + {complex(10, 3), + complex(12, 5), + complex(2, 4), + complex(8, 9), + complex(7, 6), + complex(11, 1), + complex(1.5, 3.66), + complex(6, 5.98), + complex(2.2, 10.5), + complex(8, 11), + complex(7.9, 12), + complex(6, 4)}}, + {1, + {complex(1.5, 3.66), + complex(2, 4), + complex(2.2, 10.5), + complex(6, 4), + complex(6, 5.98), + complex(7, 6), + complex(7.9, 12), + complex(8, 9), + complex(8, 11), + complex(10, 3), + complex(11, 1), + complex(12, 5)}}, + {2, + {complex(10, 3), + complex(12, 5), + complex(2, 4), + complex(8, 9), + complex(7, 6), + complex(11, 1), + complex(1.5, 3.66), + complex(6, 5.98), + complex(2.2, 10.5), + complex(8, 11), + complex(7.9, 12), + complex(6, 4)}}}, + {{-2, + {complex(10, 3), + complex(12, 5), + complex(2, 4), + complex(8, 9), + complex(7, 6), + complex(11, 1), + complex(1.5, 3.66), + complex(6, 5.98), + complex(2.2, 10.5), + complex(8, 11), + complex(7.9, 12), + complex(6, 4)}}, + {-1, + {complex(1.5, 3.66), + complex(2, 4), + complex(2.2, 10.5), + complex(6, 4), + complex(6, 5.98), + complex(7, 6), + complex(7.9, 12), + complex(8, 9), + complex(8, 11), + complex(10, 3), + complex(11, 1), + complex(12, 5)}}, + {0, + {complex(10, 3), + complex(12, 5), + complex(2, 4), + complex(8, 9), + complex(7, 6), + complex(11, 1), + complex(1.5, 3.66), + complex(6, 5.98), + complex(2.2, 10.5), + complex(8, 11), + complex(7.9, 12), + complex(6, 4)}}, + {1, + {complex(10, 3), + complex(12, 5), + complex(2, 4), + complex(8, 9), + complex(7, 6), + complex(11, 1), + complex(1.5, 3.66), + complex(6, 5.98), + complex(2.2, 10.5), + complex(8, 11), + complex(7.9, 12), + complex(6, 4)}}, + {2, + {complex(1.5, 3.66), + complex(2, 4), + complex(2.2, 10.5), + complex(6, 4), + complex(6, 5.98), + complex(7, 6), + complex(7.9, 12), + complex(8, 9), + complex(8, 11), + complex(10, 3), + complex(11, 1), + complex(12, 5)}}}, + {{-2, + {complex(8, 9), + complex(7, 6), + complex(2, 4), + complex(10, 3), + complex(12, 5), + complex(11, 1), + complex(1.5, 3.66), + complex(6, 5.98), + complex(2.2, 10.5), + complex(8, 11), + complex(7.9, 12), + complex(6, 4)}}, + {-1, + {complex(2, 4), + complex(10, 3), + complex(12, 5), + complex(7, 6), + complex(8, 9), + complex(11, 1), + complex(1.5, 3.66), + complex(2.2, 10.5), + complex(6, 5.98), + complex(6, 4), + complex(7.9, 12), + complex(8, 11)}}, + {0, + {complex(1.5, 3.66), + complex(6, 5.98), + complex(2, 4), + complex(8, 9), + complex(7, 6), + complex(6, 4), + complex(10, 3), + complex(12, 5), + complex(2.2, 10.5), + complex(8, 11), + complex(7.9, 12), + complex(11, 1)}}, + {1, + {complex(8, 9), + complex(7, 6), + complex(2, 4), + complex(10, 3), + complex(12, 5), + complex(11, 1), + complex(1.5, 3.66), + complex(6, 5.98), + complex(2.2, 10.5), + complex(8, 11), + complex(7.9, 12), + complex(6, 4)}}, + {2, + {complex(2, 4), + complex(10, 3), + complex(12, 5), + complex(7, 6), + complex(8, 9), + complex(11, 1), + complex(1.5, 3.66), + complex(2.2, 10.5), + complex(6, 5.98), + complex(6, 4), + complex(7.9, 12), + complex(8, 11)}}}}; + return expect_result; +} + +template +void test_sort(std::array& in_array, + std::array& expect, + legate::Type leg_type, + std::vector shape, + std::optional axis) +{ + auto A1 = cunumeric::zeros(shape, leg_type); + if (in_array.size() != 0) { + if (in_array.size() == 1) + A1.fill(legate::Scalar(in_array[0]), false); + else + assign_values_to_array(A1, in_array.data(), in_array.size()); + } + std::vector algos = {"quicksort", "mergesort", "heapsort", "stable"}; + for (auto algo = algos.begin(); algo < algos.end(); ++algo) { + auto B1 = cunumeric::sort(A1, axis, *algo); + if (in_array.size() != 0) check_array_eq(B1, expect.data(), expect.size()); + } +} + +template +void sort_basic_axis_impl(std::vector>& test_shapes, + std::array in_array, + std::vector>>& expect_result, + legate::Type leg_type) +{ + size_t test_shape_size = test_shapes.size(); + for (size_t i = 0; i < test_shape_size; ++i) { + auto test_shape = test_shapes[i]; + int32_t dim = test_shape.size(); + for (int32_t axis = -dim + 1; axis < dim; ++axis) { + auto expect_val = expect_result[i][axis]; + if (dim == 1) + test_sort(in_array, expect_val, leg_type, test_shape, axis); + else if (dim == 2) + test_sort(in_array, expect_val, leg_type, test_shape, axis); + else + test_sort(in_array, expect_val, leg_type, test_shape, axis); + } + } +} + +void sort_basic_axis() +{ + std::vector> test_shapes = { + {12}, {1, 12}, {12, 1}, {3, 4}, {12, 1, 1}, {1, 12, 1}, {1, 1, 12}, {2, 2, 3}}; + + // Test int type + std::array in_array1 = {10, 3, 12, 5, 2, 4, 8, 9, 7, 6, 11, 1}; + auto expect_result1 = get_expect_result_int(); + sort_basic_axis_impl(test_shapes, in_array1, expect_result1, legate::int32()); + + // Test float type + std::array int_array2 = {1.5, 3.66, 6, 5.98, 2.2, 10.5, 8, 11, 7.9, 12, 9, 4}; + auto expect_result2 = get_expect_result_double(); + sort_basic_axis_impl(test_shapes, int_array2, expect_result2, legate::float64()); + + // Test complex type + std::array, 12> in_array3 = {complex(10, 3), + complex(12, 5), + complex(2, 4), + complex(8, 9), + complex(7, 6), + complex(11, 1), + complex(1.5, 3.66), + complex(6, 5.98), + complex(2.2, 10.5), + complex(8, 11), + complex(7.9, 12), + complex(6, 4)}; + auto expect_result3 = get_expect_result_complex(); + sort_basic_axis_impl>(test_shapes, in_array3, expect_result3, legate::complex64()); +} + +void sort_empty_array() +{ + std::vector> test_shapes = { + {0}, {0, 1}, {1, 0}, {1, 0, 0}, {1, 1, 0}, {1, 0, 1}}; + + std::array in_array = {}; + size_t test_shape_size = test_shapes.size(); + for (size_t i = 0; i < test_shape_size; ++i) { + auto test_shape = test_shapes[i]; + int32_t dim = test_shape.size(); + for (int32_t axis = -dim + 1; axis < dim; ++axis) { + if (dim == 1) + test_sort(in_array, in_array, legate::int32(), test_shape, axis); + else if (dim == 2) + test_sort(in_array, in_array, legate::int32(), test_shape, axis); + else + test_sort(in_array, in_array, legate::int32(), test_shape, axis); + } + } +} + +void sort_single_item_array() +{ + std::vector> test_shapes = {{1}, {1, 1}, {1, 1, 1}}; + + std::array in_array = {12}; + size_t test_shape_size = test_shapes.size(); + for (size_t i = 0; i < test_shape_size; ++i) { + auto test_shape = test_shapes[i]; + int32_t dim = test_shape.size(); + for (int32_t axis = -dim + 1; axis < dim; ++axis) { + if (dim == 1) + test_sort(in_array, in_array, legate::int32(), test_shape, axis); + else if (dim == 2) + test_sort(in_array, in_array, legate::int32(), test_shape, axis); + else + test_sort(in_array, in_array, legate::int32(), test_shape, axis); + } + } +} + +void sort_negative_test() +{ + auto in_ar1 = cunumeric::zeros({2, 3}, legate::int32()); + + // Test invalid input sort axis + EXPECT_THROW(cunumeric::sort(in_ar1, 2, "quicksort"), std::invalid_argument); + EXPECT_THROW(cunumeric::sort(in_ar1, -3, "quicksort"), std::invalid_argument); + + // Test invalid input algorithm + EXPECT_THROW(cunumeric::sort(in_ar1, 0, "negative"), std::invalid_argument); +} + +// void cpp_test() +TEST(Sort, BasicAxis) { sort_basic_axis(); } +TEST(Sort, EmptyArray) { sort_empty_array(); } +TEST(Sort, SingleItemArray) { sort_single_item_array(); } +TEST(Sort, Negative) { sort_negative_test(); } From a6a5b42ba0b61e0ec0596a6deb334b9f7945a524 Mon Sep 17 00:00:00 2001 From: Wonchan Lee Date: Fri, 20 Oct 2023 14:43:01 -0700 Subject: [PATCH 107/462] Code changes to use the new Legate core (#31) * Use modified legate binding calls * Changes to make the code compatible with the new core * Remove obsolete ingest examples * Remove bit-rotted examples * Clean up the library registration logic * Stop using deprecated APIs * Catch up the core changes * Remove dead code * Minor fix to catch up the API change * Put back the tunable call * Changes to make mypy happy * Use native point types * Use the max number of dimensions from legate core * Changes to make the tests pass with the new core * Alignments between differently shaped stores are now banned * Stop using the legacy constraint APIs * Start attaching NumPy arrays in non-shared mode * Use the array interface from inline allocations * No need to broadcast the input in argwhere when it's 1D * Catch up changes to Scalar * Use scalars instead of scalar stores in arange * Use scalars instead of scalar stores in binary ops * Replace scalar stores with scalars in unary operations * Handle shared attachments correctly * Fixes and workarounds for eager tests * Missing broadcasting constraint for fft * Replace the remaining add_broadcast calls with the new constraint API * Address review comments * Left a TODO item for batched FFTs * Bump the Legate Core commit --------- Co-authored-by: Vladislav Zhurba --- cmake/versions.json | 2 +- cunumeric/_ufunc/ufunc.py | 2 +- cunumeric/array.py | 15 +- cunumeric/config.py | 65 +- cunumeric/deferred.py | 808 +++++++++--------- cunumeric/eager.py | 93 +- cunumeric/linalg/cholesky.py | 345 ++++---- cunumeric/linalg/solve.py | 38 +- cunumeric/module.py | 3 +- cunumeric/runtime.py | 157 ++-- cunumeric/sort.py | 5 +- cunumeric/thunk.py | 30 +- cunumeric/utils.py | 2 +- examples/benchmark.py | 8 +- examples/ingest.py | 96 --- examples/kmeans_sort.py | 228 ----- examples/lstm_full.py | 419 --------- examples/wgrad.py | 107 --- src/cunumeric/binary/binary_op.h | 2 +- src/cunumeric/binary/binary_op_template.inl | 5 +- src/cunumeric/binary/binary_op_util.h | 108 +-- src/cunumeric/binary/binary_red.h | 2 +- src/cunumeric/binary/binary_red_template.inl | 5 +- src/cunumeric/ndarray.cc | 9 +- src/cunumeric/nullary/arange.h | 5 +- src/cunumeric/nullary/arange_template.inl | 7 +- src/cunumeric/nullary/fill_template.inl | 2 +- src/cunumeric/random/rand.h | 2 +- src/cunumeric/random/rand_template.inl | 4 +- src/cunumeric/random/rand_util.h | 10 +- src/cunumeric/unary/scalar_unary_red.h | 2 +- .../unary/scalar_unary_red_template.inl | 10 +- src/cunumeric/unary/unary_op.h | 2 +- src/cunumeric/unary/unary_op_template.inl | 4 +- src/cunumeric/unary/unary_op_util.h | 129 ++- test.py | 17 - tests/integration/test_compress.py | 5 +- tests/integration/test_ingest.py | 101 --- tests/integration/test_put.py | 6 +- tests/integration/test_put_along_axis.py | 4 +- tests/integration/test_solve.py | 6 +- tests/integration/test_take_along_axis.py | 4 +- tests/unit/cunumeric/test_config.py | 4 +- 43 files changed, 958 insertions(+), 1920 deletions(-) delete mode 100644 examples/ingest.py delete mode 100644 examples/kmeans_sort.py delete mode 100644 examples/lstm_full.py delete mode 100644 examples/wgrad.py delete mode 100644 tests/integration/test_ingest.py diff --git a/cmake/versions.json b/cmake/versions.json index 66b44872da..ea1c5fe349 100644 --- a/cmake/versions.json +++ b/cmake/versions.json @@ -12,7 +12,7 @@ "git_url" : "https://github.com/nv-legate/legate.core.internal.git", "git_shallow": false, "always_download": false, - "git_tag" : "befdfc6001ce57f36a83b64059aef9df7fcbc2f9" + "git_tag" : "98c4792ab3c33ba399b7b9fbb1251a489c4835e6" } } } diff --git a/cunumeric/_ufunc/ufunc.py b/cunumeric/_ufunc/ufunc.py index 11800e53f2..e0c95f9b4c 100644 --- a/cunumeric/_ufunc/ufunc.py +++ b/cunumeric/_ufunc/ufunc.py @@ -427,7 +427,7 @@ def __call__( ) op_code = self._overrides.get(x.dtype.char, self._op_code) - result._thunk.unary_op(op_code, x._thunk, where, ()) + result._thunk.unary_op(op_code, x._thunk, where) return self._maybe_cast_output(out, result) diff --git a/cunumeric/array.py b/cunumeric/array.py index 91ad41dde5..79a50a6335 100644 --- a/cunumeric/array.py +++ b/cunumeric/array.py @@ -31,7 +31,7 @@ ) import numpy as np -from legate.core import Array, Field +from legate.core import Field, LogicalArray, Scalar from legate.core.utils import OrderedSet from numpy.core.multiarray import ( # type: ignore [attr-defined] normalize_axis_index, @@ -356,7 +356,7 @@ def __legate_data_interface__(self) -> dict[str, Any]: # We don't have nullable data for the moment # until we support masked arrays dtype = deferred_thunk.base.type - array = Array(dtype, [None, deferred_thunk.base]) + array = LogicalArray.from_store(deferred_thunk.base) self._legate_data = dict() self._legate_data["version"] = 1 field = Field("cuNumeric Array", dtype) @@ -872,12 +872,13 @@ def __contains__(self, item: Any) -> ndarray: args = (np.array(item, dtype=self.dtype),) if args[0].size != 1: raise ValueError("contains needs scalar item") + core_dtype = to_core_dtype(self.dtype) return self._perform_unary_reduction( UnaryRedCode.CONTAINS, self, axis=None, res_dtype=bool, - args=args, + args=(Scalar(args[0].squeeze()[()], core_dtype),), ) def __copy__(self) -> ndarray: @@ -2276,8 +2277,10 @@ def clip( return convert_to_cunumeric_ndarray( self.__array__().clip(args[0], args[1]) ) + core_dtype = to_core_dtype(self.dtype) + extra_args = (Scalar(min, core_dtype), Scalar(max, core_dtype)) return self._perform_unary_op( - UnaryOpCode.CLIP, self, out=out, extra_args=args + UnaryOpCode.CLIP, self, out=out, extra_args=extra_args ) def conj(self) -> ndarray: @@ -4132,7 +4135,7 @@ def _perform_unary_reduction( res_dtype: Union[npt.DTypeLike, None] = None, out: Union[ndarray, None] = None, keepdims: bool = False, - args: Union[Any, None] = None, + args: tuple[Scalar, ...] = (), initial: Union[int, float, None] = None, where: Union[bool, ndarray] = True, ) -> ndarray: @@ -4234,7 +4237,7 @@ def _perform_binary_reduction( one: ndarray, two: ndarray, dtype: np.dtype[Any], - extra_args: Union[tuple[Any, ...], None] = None, + extra_args: tuple[Scalar, ...] = (), ) -> ndarray: args = (one, two) diff --git a/cunumeric/config.py b/cunumeric/config.py index 3bf00ac62d..dccede3373 100644 --- a/cunumeric/config.py +++ b/cunumeric/config.py @@ -15,18 +15,20 @@ from __future__ import annotations import os +import platform from abc import abstractmethod +from ctypes import CDLL, RTLD_GLOBAL from enum import IntEnum, unique -from typing import TYPE_CHECKING, Union, cast +from typing import TYPE_CHECKING, Any, Union, cast +import cffi # type: ignore import numpy as np -from legate.core import Library, get_legate_runtime + +ffi = cffi.FFI() if TYPE_CHECKING: import numpy.typing as npt - from .runtime import Runtime - class _ReductionOpIds: argmax_redop_id: int @@ -282,16 +284,35 @@ def cunumeric_register_reduction_ops(self, code: int) -> _ReductionOpIds: ... +def dlopen_no_autoclose(ffi: Any, lib_path: str) -> Any: + # Use an already-opened library handle, which cffi will convert to a + # regular FFI object (using the definitions previously added using + # ffi.cdef), but will not automatically dlclose() on collection. + lib = CDLL(lib_path, mode=RTLD_GLOBAL) + return ffi.dlopen(ffi.cast("void *", lib._handle)) + + # Load the cuNumeric library first so we have a shard object that # we can use to initialize all these configuration enumerations -class CuNumericLib(Library): +class CuNumericLib: def __init__(self, name: str) -> None: self.name = name - self.runtime: Union[Runtime, None] = None - self.shared_object: Union[_CunumericSharedLib, None] = None - def get_name(self) -> str: - return self.name + shared_lib_path = self.get_shared_library() + assert shared_lib_path is not None + header = self.get_c_header() + if header is not None: + ffi.cdef(header) + # Don't use ffi.dlopen(), because that will call dlclose() + # automatically when the object gets collected, thus removing + # symbols that may be needed when destroying C++ objects later + # (e.g. vtable entries, which will be queried for virtual + # destructors), causing errors at shutdown. + shared_lib = dlopen_no_autoclose(ffi, shared_lib_path) + callback = getattr(shared_lib, "cunumeric_perform_registration") + callback() + + self.shared_object = cast(_CunumericSharedLib, shared_lib) def get_shared_library(self) -> str: from cunumeric.install_info import libpath @@ -305,27 +326,19 @@ def get_c_header(self) -> str: return header - def get_registration_callback(self) -> str: - return "cunumeric_perform_registration" - - def initialize(self, shared_object: _CunumericSharedLib) -> None: - assert self.runtime is None - self.shared_object = shared_object - - def set_runtime(self, runtime: Runtime) -> None: - assert self.runtime is None - assert self.shared_object is not None - self.runtime = runtime - - def destroy(self) -> None: - if self.runtime is not None: - self.runtime.destroy() + @staticmethod + def get_library_extension() -> str: + os_name = platform.system() + if os_name == "Linux": + return ".so" + elif os_name == "Darwin": + return ".dylib" + raise RuntimeError(f"unknown platform {os_name!r}") CUNUMERIC_LIB_NAME = "cunumeric" cunumeric_lib = CuNumericLib(CUNUMERIC_LIB_NAME) -cunumeric_context = get_legate_runtime().register_library(cunumeric_lib) -_cunumeric = cast(_CunumericSharedLib, cunumeric_lib.shared_object) +_cunumeric = cunumeric_lib.shared_object # Match these to CuNumericOpCode in cunumeric_c.h diff --git a/cunumeric/deferred.py b/cunumeric/deferred.py index 29b7ebcbf9..6e1636eaa0 100644 --- a/cunumeric/deferred.py +++ b/cunumeric/deferred.py @@ -20,7 +20,6 @@ from enum import IntEnum, unique from functools import reduce, wraps from inspect import signature -from itertools import product from typing import ( TYPE_CHECKING, Any, @@ -35,7 +34,17 @@ import legate.core.types as ty import numpy as np -from legate.core import Annotation, Future, ReductionOp, Store +from legate.core import ( + Annotation, + LogicalStore, + ReductionOp, + Scalar, + align, + bloat, + broadcast, + get_legate_runtime, + scale, +) from legate.core.utils import OrderedSet from numpy.core.numeric import ( # type: ignore [attr-defined] normalize_axis_tuple, @@ -61,8 +70,6 @@ if TYPE_CHECKING: import numpy.typing as npt - from legate.core import FieldID, Region - from legate.core.operation import AutoTask, ManualTask from .config import BitGeneratorType, FFTDirection, FFTType, WindowOpCode from .runtime import Runtime @@ -90,6 +97,8 @@ def _prod(tpl: Sequence[int]) -> int: R = TypeVar("R") P = ParamSpec("P") +legate_runtime = get_legate_runtime() + def auto_convert( *thunk_params: str, @@ -133,30 +142,6 @@ def wrapper(*args: Any, **kwargs: Any) -> R: return decorator -# This is a dummy object that is only used as an initializer for the -# RegionField object above. It is thrown away as soon as the -# RegionField is constructed. -class _CuNumericNDarray(object): - __slots__ = ["__array_interface__"] - - def __init__( - self, - shape: NdShape, - field_type: Any, - base_ptr: Any, - strides: tuple[int, ...], - read_only: bool, - ) -> None: - # See: https://docs.scipy.org/doc/numpy/reference/arrays.interface.html - self.__array_interface__ = { - "version": 3, - "shape": shape, - "typestr": field_type.str, - "data": (base_ptr, read_only), - "strides": strides, - } - - _UNARY_RED_TO_REDUCTION_OPS: Dict[int, int] = { UnaryRedCode.SUM: ReductionOp.ADD, UnaryRedCode.PROD: ReductionOp.MUL, @@ -251,28 +236,26 @@ class DeferredArray(NumPyThunk): def __init__( self, runtime: Runtime, - base: Store, + base: LogicalStore, numpy_array: Optional[npt.NDArray[Any]] = None, + needs_detach: bool = False, ) -> None: super().__init__(runtime, base.type.to_numpy_dtype()) assert base is not None - assert isinstance(base, Store) - self.base: Any = base # a Legate Store + assert isinstance(base, LogicalStore) + self.base: LogicalStore = base # a Legate Store self.numpy_array = ( None if numpy_array is None else weakref.ref(numpy_array) ) + self.needs_detach = needs_detach + + def __del__(self) -> None: + if self.needs_detach: + self.base.detach() def __str__(self) -> str: return f"DeferredArray(base: {self.base})" - @property - def storage(self) -> Union[Future, tuple[Region, FieldID]]: - storage = self.base.storage - if self.base.kind == Future: - return storage - else: - return (storage.region, storage.field.field_id) - @property def shape(self) -> NdShape: return tuple(self.base.shape) @@ -304,30 +287,9 @@ def __numpy_array__(self) -> npt.NDArray[Any]: # and type return np.empty(shape=self.shape, dtype=self.dtype) - if self.scalar: - result = np.full( - self.shape, - self.get_scalar_array(), - dtype=self.dtype, - ) - else: - alloc = self.base.get_inline_allocation() - - def construct_ndarray( - shape: NdShape, address: Any, strides: tuple[int, ...] - ) -> npt.NDArray[Any]: - initializer = _CuNumericNDarray( - shape, self.dtype, address, strides, False - ) - result = np.asarray(initializer) - if self.shape == (): - result = result.reshape(()) - return result - - result = cast("npt.NDArray[Any]", alloc.consume(construct_ndarray)) - - self.numpy_array = weakref.ref(result) - return result + return np.asarray( + self.base.get_physical_store().get_inline_allocation() + ) # TODO: We should return a view of the field instead of a copy def imag(self) -> NumPyThunk: @@ -341,7 +303,6 @@ def imag(self) -> NumPyThunk: UnaryOpCode.IMAG, self, True, - [], ) return result @@ -358,7 +319,6 @@ def real(self) -> NumPyThunk: UnaryOpCode.REAL, self, True, - [], ) return result @@ -374,7 +334,6 @@ def conj(self) -> NumPyThunk: UnaryOpCode.CONJ, self, True, - [], ) return result @@ -383,24 +342,17 @@ def conj(self) -> NumPyThunk: @auto_convert("rhs") def copy(self, rhs: Any, deep: bool = False) -> None: if self.scalar and rhs.scalar: - self.base.set_storage(rhs.base.storage) + legate_runtime.issue_fill(self.base, rhs.base) return self.unary_op( UnaryOpCode.COPY, rhs, True, - [], ) @property def scalar(self) -> bool: - return self.base.scalar - - def get_scalar_array(self) -> npt.NDArray[Any]: - assert self.scalar - buf = self.base.storage.get_buffer(self.dtype.itemsize) - result = np.frombuffer(buf, dtype=self.dtype, count=1) - return result.reshape(()) + return self.base.has_scalar_storage and self.base.size == 1 def _zip_indices( self, start_index: int, arrays: tuple[Any, ...] @@ -476,7 +428,7 @@ def _zip_indices( # dtype, to store N-dimensional index points, to be used as the # indirection field in a copy. N = self.ndim - pointN_dtype = self.runtime.get_point_type(N) + pointN_dtype = ty.point_type(N) output_arr = cast( DeferredArray, self.runtime.create_empty_thunk( @@ -487,16 +439,20 @@ def _zip_indices( ) # call ZIP function to combine index arrays into a singe array - task = self.context.create_auto_task(CuNumericOpCode.ZIP) - task.throws_exception(IndexError) - task.add_output(output_arr.base) + task = legate_runtime.create_auto_task( + self.library, CuNumericOpCode.ZIP + ) + # TODO: We need to put back the precise Python exception support + # task.throws_exception(IndexError) + task.throws_exception(True) + p_out = task.add_output(output_arr.base) task.add_scalar_arg(self.ndim, ty.int64) # N of points in Point task.add_scalar_arg(key_dim, ty.int64) # key_dim task.add_scalar_arg(start_index, ty.int64) # start_index task.add_scalar_arg(self.shape, (ty.int64,)) for a in arrays: - task.add_input(a) - task.add_alignment(output_arr.base, a) + p_in = task.add_input(a) + task.add_constraint(align(p_out, p_in)) task.execute() return output_arr @@ -515,7 +471,9 @@ def _copy_store(self, store: Any) -> DeferredArray: return cast(DeferredArray, store_copy) @staticmethod - def _slice_store(k: slice, store: Store, dim: int) -> tuple[slice, Store]: + def _slice_store( + k: slice, store: LogicalStore, dim: int + ) -> tuple[slice, LogicalStore]: start = k.start end = k.stop step = k.step @@ -697,7 +655,7 @@ def _advanced_indexing_with_boolean_array( # indirect copy operation if is_set: N = rhs.ndim - out_dtype = rhs.runtime.get_point_type(N) + out_dtype = ty.point_type(N) # TODO : current implementation of the ND output regions # requires out.ndim == rhs.ndim. This will be fixed in the @@ -705,17 +663,18 @@ def _advanced_indexing_with_boolean_array( out = rhs.runtime.create_unbound_thunk(out_dtype, ndim=rhs.ndim) key_dims = key.ndim # dimension of the original key - task = rhs.context.create_auto_task( - CuNumericOpCode.ADVANCED_INDEXING + task = legate_runtime.create_auto_task( + self.library, + CuNumericOpCode.ADVANCED_INDEXING, ) task.add_output(out.base) - task.add_input(rhs.base) - task.add_input(key_store) + p_rhs = task.add_input(rhs.base) + p_key = task.add_input(key_store) task.add_scalar_arg(is_set, ty.bool_) task.add_scalar_arg(key_dims, ty.int64) - task.add_alignment(rhs.base, key_store) - task.add_broadcast( - rhs.base, axes=tuple(range(1, len(rhs.base.shape))) + task.add_constraint(align(p_rhs, p_key)) + task.add_constraint( + broadcast(p_rhs, range(1, len(rhs.base.shape))) ) task.execute() @@ -819,7 +778,7 @@ def _create_indexing_array( for dim, k in enumerate(key): if np.isscalar(k): if k < 0: # type: ignore [operator] - k += store.shape[dim + shift] + k += store.shape[dim + shift] # type: ignore [operator] store = store.project(dim + shift, k) shift -= 1 elif k is np.newaxis: @@ -896,7 +855,7 @@ def _get_view(self, key: Any) -> DeferredArray: k, store = self._slice_store(k, store, dim + shift) elif np.isscalar(k): if k < 0: # type: ignore [operator] - k += store.shape[dim + shift] + k += store.shape[dim + shift] # type: ignore [operator] store = store.project(dim + shift, k) shift -= 1 else: @@ -931,7 +890,7 @@ def _convert_future_to_regionfield( shape: NdShape = (1,) else: shape = self.shape - store = self.context.create_store( + store = legate_runtime.create_store( self.base.type, shape=shape, optimize_scalar=False, @@ -955,12 +914,12 @@ def get_item(self, key: Any) -> NumPyThunk: ) = self._create_indexing_array(key) if copy_needed: - if rhs.base.kind == Future: + if rhs.base.has_scalar_storage: rhs = rhs._convert_future_to_regionfield() result: NumPyThunk - if index_array.base.kind == Future: + if index_array.base.has_scalar_storage: index_array = index_array._convert_future_to_regionfield() - result_store = self.context.create_store( + result_store = legate_runtime.create_store( self.base.type, shape=index_array.shape, optimize_scalar=False, @@ -977,12 +936,9 @@ def get_item(self, key: Any) -> NumPyThunk: inputs=[self], ) - copy = self.context.create_copy() - copy.set_source_indirect_out_of_range(False) - copy.add_input(rhs.base) - copy.add_source_indirect(index_array.base) - copy.add_output(result.base) # type: ignore - copy.execute() + legate_runtime.issue_gather( + result.base, rhs.base, index_array.base # type: ignore + ) else: return index_array @@ -996,7 +952,9 @@ def get_item(self, key: Any) -> NumPyThunk: (), self.base.type, inputs=[self] ) - task = self.context.create_auto_task(CuNumericOpCode.READ) + task = legate_runtime.create_auto_task( + self.library, CuNumericOpCode.READ + ) task.add_input(input.base) task.add_output(result.base) # type: ignore @@ -1032,7 +990,7 @@ def set_item(self, key: Any, rhs: Any) -> None: # the case when rhs is a scalar and indices array contains # a single value # TODO this logic should be removed when copy accepts Futures - if rhs_store.kind == Future: + if rhs_store.has_scalar_storage: rhs_tmp = DeferredArray( self.runtime, base=rhs_store, @@ -1040,21 +998,17 @@ def set_item(self, key: Any, rhs: Any) -> None: rhs_tmp2 = rhs_tmp._convert_future_to_regionfield() rhs_store = rhs_tmp2.base - if index_array.base.kind == Future: + if index_array.base.has_scalar_storage: index_array = index_array._convert_future_to_regionfield() - if lhs.base.kind == Future: + if lhs.base.has_scalar_storage: lhs = lhs._convert_future_to_regionfield() if lhs.base.transformed: lhs = lhs._copy_store(lhs.base) if index_array.size != 0: - copy = self.context.create_copy() - copy.set_target_indirect_out_of_range(False) - - copy.add_input(rhs_store) - copy.add_target_indirect(index_array.base) - copy.add_output(lhs.base) - copy.execute() + legate_runtime.issue_scatter( + lhs.base, index_array.base, rhs_store + ) # TODO this copy will be removed when affine copies are # supported in Legion/Realm @@ -1068,7 +1022,9 @@ def set_item(self, key: Any, rhs: Any) -> None: # We're just writing a single value assert rhs.size == 1 - task = self.context.create_auto_task(CuNumericOpCode.WRITE) + task = legate_runtime.create_auto_task( + self.library, CuNumericOpCode.WRITE + ) # Since we pass the view with write discard privilege, # we should make sure that the mapper either creates a fresh # instance just for this one-element view or picks one of the @@ -1325,10 +1281,8 @@ def swapaxes(self, axis1: int, axis2: int) -> DeferredArray: dims = list(range(self.ndim)) dims[axis1], dims[axis2] = dims[axis2], dims[axis1] - result = self.base.transpose(dims) - result = DeferredArray(self.runtime, result) - - return result + result = self.base.transpose(tuple(dims)) + return DeferredArray(self.runtime, result) # Convert the source array to the destination array @auto_convert("rhs") @@ -1355,50 +1309,39 @@ def convert( lhs = lhs_array.base rhs = rhs_array.base - task = self.context.create_auto_task(CuNumericOpCode.CONVERT) - task.add_output(lhs) - task.add_input(rhs) + task = legate_runtime.create_auto_task( + self.library, CuNumericOpCode.CONVERT + ) + p_lhs = task.add_output(lhs) + p_rhs = task.add_input(rhs) task.add_scalar_arg(nan_op, ty.int32) - task.add_alignment(lhs, rhs) + task.add_constraint(align(p_lhs, p_rhs)) task.execute() - if temporary: - lhs.set_linear() - @auto_convert("v", "lhs") def convolve(self, v: Any, lhs: Any, mode: ConvolveMode) -> None: input = self.base filter = v.base - out = lhs.base - - task = self.context.create_auto_task(CuNumericOpCode.CONVOLVE) - - offsets = (filter.shape + 1) // 2 - stencils: list[tuple[int, ...]] = [] - for offset in offsets: - stencils.append((-offset, 0, offset)) - stencils = list(product(*stencils)) - stencils.remove((0,) * self.ndim) - - p_out = task.declare_partition(out) - p_input = task.declare_partition(input) - p_stencils = [] - for _ in stencils: - p_stencils.append(task.declare_partition(input, complete=False)) - - task.add_output(out, partition=p_out) - task.add_input(filter) - task.add_input(input, partition=p_input) - for p_stencil in p_stencils: - task.add_input(input, partition=p_stencil) + output = lhs.base + + task = legate_runtime.create_auto_task( + self.library, CuNumericOpCode.CONVOLVE + ) + + offsets = tuple((filter.shape + 1) // 2) + + p_out = task.add_output(output) + p_filter = task.add_input(filter) + p_in = task.add_input(input) + p_halo = task.declare_partition() + task.add_input(input, p_halo) task.add_scalar_arg(self.shape, (ty.int64,)) - task.add_constraint(p_out == p_input) - for stencil, p_stencil in zip(stencils, p_stencils): - task.add_constraint(p_input + stencil <= p_stencil) # type: ignore - task.add_broadcast(filter) + task.add_constraint(align(p_out, p_in)) + task.add_constraint(bloat(p_out, p_halo, offsets, offsets)) + task.add_constraint(broadcast(p_filter)) task.execute() @@ -1421,12 +1364,12 @@ def fft( input = rhs.base output = lhs.base - task = self.context.create_auto_task(CuNumericOpCode.FFT) - p_output = task.declare_partition(output) - p_input = task.declare_partition(input) + task = legate_runtime.create_auto_task( + self.library, CuNumericOpCode.FFT + ) - task.add_output(output, partition=p_output) - task.add_input(input, partition=p_input) + p_output = task.add_output(output) + p_input = task.add_input(input) task.add_scalar_arg(kind.type_id, ty.int32) task.add_scalar_arg(direction.value, ty.int32) task.add_scalar_arg( @@ -1439,33 +1382,35 @@ def fft( task.add_scalar_arg(ax, ty.int64) if input.ndim > len(OrderedSet(axes)): - task.add_broadcast(input, axes=OrderedSet(axes)) + task.add_constraint(broadcast(p_input, OrderedSet(axes))) + else: + task.add_constraint(broadcast(p_input)) + if input.shape == output.shape: + task.add_constraint(align(p_output, p_input)) else: - task.add_broadcast(input) - task.add_constraint(p_output == p_input) + # TODO: We need the relaxed alignment to avoid serializing the + # task here. Batched FFT was relying on the relaxed alignment. + task.add_constraint(broadcast(p_output)) task.execute() # Fill the cuNumeric array with the value in the numpy array def _fill(self, value: Any) -> None: - assert value.scalar assert self.base is not None - if self.scalar: - # Handle the 0D case special - self.base.set_storage(value.storage) - elif self.dtype.kind != "V" and self.base.kind is not Future: - # Emit a Legion fill - self.context.issue_fill(self.base, value) + if self.dtype.kind != "V": + # Emit a Legate fill + legate_runtime.issue_fill(self.base, value) else: # Perform the fill using a task # If this is a fill for an arg value, make sure to pass # the value dtype so that we get it packed correctly - argval = self.dtype.kind == "V" - task = self.context.create_auto_task(CuNumericOpCode.FILL) + task = legate_runtime.create_auto_task( + self.library, CuNumericOpCode.FILL + ) task.add_output(self.base) task.add_input(value) - task.add_scalar_arg(argval, ty.bool_) + task.add_scalar_arg(True, ty.bool_) task.execute() def fill(self, numpy_array: Any) -> None: @@ -1476,11 +1421,8 @@ def fill(self, numpy_array: Any) -> None: # Have to copy the numpy array because this launch is asynchronous # and we need to make sure the application doesn't mutate the value # so make a future result, this is immediate so no dependence - value = self.runtime.create_scalar(numpy_array.data) - store = self.context.create_store( - self.base.type, shape=(1,), storage=value, optimize_scalar=True - ) - self._fill(store) + value = Scalar(numpy_array.tobytes(), self.base.type) + self._fill(legate_runtime.create_store_from_scalar(value)) @auto_convert("rhs1_thunk", "rhs2_thunk") def contract( @@ -1585,19 +1527,22 @@ def contract( # The underlying libraries are not guaranteed to work with stride # values of 0. The frontend should therefore handle broadcasting # directly, instead of promoting stores. - assert not lhs.has_fake_dims() - assert not rhs1.has_fake_dims() - assert not rhs2.has_fake_dims() + # TODO: We need a better API for this + # assert not lhs.has_fake_dims() + # assert not rhs1.has_fake_dims() + # assert not rhs2.has_fake_dims() # Special cases where we can use BLAS if blas_op is not None: if blas_op == BlasOperation.VV: # Vector dot product - task = self.context.create_auto_task(CuNumericOpCode.DOT) + task = legate_runtime.create_auto_task( + self.library, CuNumericOpCode.DOT + ) task.add_reduction(lhs, ReductionOp.ADD) - task.add_input(rhs1) - task.add_input(rhs2) - task.add_alignment(rhs1, rhs2) + p_rhs1 = task.add_input(rhs1) + p_rhs2 = task.add_input(rhs2) + task.add_constraint(align(p_rhs1, p_rhs2)) task.execute() elif blas_op == BlasOperation.MV: @@ -1616,12 +1561,14 @@ def contract( rhs2 = rhs2.promote(0, m) lhs = lhs.promote(1, n) - task = self.context.create_auto_task(CuNumericOpCode.MATVECMUL) - task.add_reduction(lhs, ReductionOp.ADD) - task.add_input(rhs1) - task.add_input(rhs2) - task.add_alignment(lhs, rhs1) - task.add_alignment(lhs, rhs2) + task = legate_runtime.create_auto_task( + self.library, CuNumericOpCode.MATVECMUL + ) + p_lhs = task.add_reduction(lhs, ReductionOp.ADD) + p_rhs1 = task.add_input(rhs1) + p_rhs2 = task.add_input(rhs2) + task.add_constraint(align(p_lhs, p_rhs1)) + task.add_constraint(align(p_lhs, p_rhs2)) task.execute() elif blas_op == BlasOperation.MM: @@ -1653,12 +1600,14 @@ def contract( rhs1 = rhs1.promote(2, n) rhs2 = rhs2.promote(0, m) - task = self.context.create_auto_task(CuNumericOpCode.MATMUL) - task.add_reduction(lhs, ReductionOp.ADD) - task.add_input(rhs1) - task.add_input(rhs2) - task.add_alignment(lhs, rhs1) - task.add_alignment(lhs, rhs2) + task = legate_runtime.create_auto_task( + self.library, CuNumericOpCode.MATMUL + ) + p_lhs = task.add_reduction(lhs, ReductionOp.ADD) + p_rhs1 = task.add_input(rhs1) + p_rhs2 = task.add_input(rhs2) + task.add_constraint(align(p_lhs, p_rhs1)) + task.add_constraint(align(p_lhs, p_rhs2)) task.execute() else: @@ -1680,8 +1629,8 @@ def contract( # Transpose arrays according to alphabetical order of mode labels def alphabetical_transpose( - store: Store, modes: Sequence[str] - ) -> Store: + store: LogicalStore, modes: Sequence[str] + ) -> LogicalStore: perm = tuple( dim for (_, dim) in sorted(zip(modes, range(len(modes)))) ) @@ -1699,7 +1648,7 @@ def alphabetical_transpose( extent = mode2extent[mode] def add_mode( - store: Store, modes: Sequence[str], dim_mask: list[bool] + store: LogicalStore, modes: Sequence[str], dim_mask: list[bool] ) -> Any: if mode not in modes: dim_mask.append(False) @@ -1715,15 +1664,17 @@ def add_mode( assert lhs.shape == rhs2.shape # Prepare the launch - task = self.context.create_auto_task(CuNumericOpCode.CONTRACT) - task.add_reduction(lhs, ReductionOp.ADD) - task.add_input(rhs1) - task.add_input(rhs2) + task = legate_runtime.create_auto_task( + self.library, CuNumericOpCode.CONTRACT + ) + p_lhs = task.add_reduction(lhs, ReductionOp.ADD) + p_rhs1 = task.add_input(rhs1) + p_rhs2 = task.add_input(rhs2) task.add_scalar_arg(tuple(lhs_dim_mask), (ty.bool_,)) task.add_scalar_arg(tuple(rhs1_dim_mask), (ty.bool_,)) task.add_scalar_arg(tuple(rhs2_dim_mask), (ty.bool_,)) - task.add_alignment(lhs, rhs1) - task.add_alignment(lhs, rhs2) + task.add_constraint(align(p_lhs, p_rhs1)) + task.add_constraint(align(p_lhs, p_rhs2)) task.execute() # Create array from input array and indices @@ -1734,18 +1685,18 @@ def choose(self, rhs: Any, *args: Any) -> None: out_arr = self.base # broadcast input array and all choices arrays to the same shape - index = index_arr._broadcast(out_arr.shape) - ch_tuple = tuple(c._broadcast(out_arr.shape) for c in ch_def) - - task = self.context.create_auto_task(CuNumericOpCode.CHOOSE) - task.add_output(out_arr) - task.add_input(index) - for c in ch_tuple: - task.add_input(c) + index = index_arr._broadcast(tuple(out_arr.shape)) + ch_tuple = tuple(c._broadcast(tuple(out_arr.shape)) for c in ch_def) - task.add_alignment(index, out_arr) + task = legate_runtime.create_auto_task( + self.library, CuNumericOpCode.CHOOSE + ) + p_out = task.add_output(out_arr) + p_ind = task.add_input(index) + task.add_constraint(align(p_ind, p_out)) for c in ch_tuple: - task.add_alignment(index, c) + p_c = task.add_input(c) + task.add_constraint(align(p_ind, p_c)) task.execute() # Create or extract a diagonal from a matrix @@ -1801,17 +1752,19 @@ def _diag_helper( else: diag = diag.promote(0, matrix.shape[0]) - task = self.context.create_auto_task(CuNumericOpCode.DIAG) + task = legate_runtime.create_auto_task( + self.library, CuNumericOpCode.DIAG + ) if extract: - task.add_reduction(diag, ReductionOp.ADD) - task.add_input(matrix) - task.add_alignment(matrix, diag) + p_diag = task.add_reduction(diag, ReductionOp.ADD) + p_mat = task.add_input(matrix) + task.add_constraint(align(p_mat, p_diag)) else: - task.add_output(matrix) - task.add_input(diag) - task.add_input(matrix) - task.add_alignment(diag, matrix) + p_mat = task.add_output(matrix) + p_diag = task.add_input(diag) + task.add_input(matrix, p_mat) + task.add_constraint(align(p_diag, p_mat)) task.add_scalar_arg(naxes, ty.int32) task.add_scalar_arg(extract, ty.bool_) @@ -1820,15 +1773,15 @@ def _diag_helper( @auto_convert("indices", "values") def put(self, indices: Any, values: Any, check_bounds: bool) -> None: - if indices.base.kind == Future or indices.base.transformed: - change_shape = indices.base.kind == Future + if indices.base.has_scalar_storage or indices.base.transformed: + change_shape = indices.base.has_scalar_storage indices = indices._convert_future_to_regionfield(change_shape) - if values.base.kind == Future or values.base.transformed: - change_shape = values.base.kind == Future + if values.base.has_scalar_storage or values.base.transformed: + change_shape = values.base.has_scalar_storage values = values._convert_future_to_regionfield(change_shape) - if self.base.kind == Future or self.base.transformed: - change_shape = self.base.kind == Future + if self.base.has_scalar_storage or self.base.transformed: + change_shape = self.base.has_scalar_storage self_tmp = self._convert_future_to_regionfield(change_shape) else: self_tmp = self @@ -1839,7 +1792,7 @@ def put(self, indices: Any, values: Any, check_bounds: bool) -> None: # (indices.size,) shape and is used to copy data from values # to the target ND array (self) N = self_tmp.ndim - pointN_dtype = self.runtime.get_point_type(N) + pointN_dtype = ty.point_type(N) indirect = cast( DeferredArray, self.runtime.create_empty_thunk( @@ -1850,24 +1803,23 @@ def put(self, indices: Any, values: Any, check_bounds: bool) -> None: ) shape = self_tmp.shape - task = self.context.create_auto_task(CuNumericOpCode.WRAP) - task.add_output(indirect.base) + task = legate_runtime.create_auto_task( + self.library, CuNumericOpCode.WRAP + ) + p_indirect = task.add_output(indirect.base) task.add_scalar_arg(shape, (ty.int64,)) task.add_scalar_arg(True, ty.bool_) # has_input task.add_scalar_arg(check_bounds, ty.bool_) - task.add_input(indices.base) - task.add_alignment(indices.base, indirect.base) - task.throws_exception(IndexError) + p_indices = task.add_input(indices.base) + task.add_constraint(align(p_indices, p_indirect)) + # TODO: We need to put back the precise Python exception support + # task.throws_exception(IndexError) + task.throws_exception(True) task.execute() - if indirect.base.kind == Future: + if indirect.base.has_scalar_storage: indirect = indirect._convert_future_to_regionfield() - copy = self.context.create_copy() - copy.set_target_indirect_out_of_range(False) - copy.add_input(values.base) - copy.add_target_indirect(indirect.base) - copy.add_output(self_tmp.base) - copy.execute() + legate_runtime.issue_scatter(self_tmp.base, indirect.base, values.base) if self_tmp is not self: self.copy(self_tmp, deep=True) @@ -1880,13 +1832,15 @@ def putmask(self, mask: Any, values: Any) -> None: values_new = values._broadcast(self.shape) else: values_new = values.base - task = self.context.create_auto_task(CuNumericOpCode.PUTMASK) - task.add_input(self.base) - task.add_input(mask.base) - task.add_input(values_new) - task.add_output(self.base) - task.add_alignment(self.base, mask.base) - task.add_alignment(self.base, values_new) + task = legate_runtime.create_auto_task( + self.library, CuNumericOpCode.PUTMASK + ) + p_self = task.add_input(self.base) + p_mask = task.add_input(mask.base) + p_values = task.add_input(values_new) + task.add_output(self.base, p_self) + task.add_constraint(align(p_self, p_mask)) + task.add_constraint(align(p_self, p_values)) task.execute() # Create an identity array with the ones offset from the diagonal by k @@ -1903,7 +1857,9 @@ def eye(self, k: int) -> None: # privilege, then, is not appropriate for this call, as it essentially # tells the runtime that it can throw away the previous contents of the # entire region. - task = self.context.create_auto_task(CuNumericOpCode.EYE) + task = legate_runtime.create_auto_task( + self.library, CuNumericOpCode.EYE + ) task.add_input(self.base) task.add_output(self.base) task.add_scalar_arg(k, ty.int32) @@ -1915,24 +1871,15 @@ def arange(self, start: float, stop: float, step: float) -> None: if self.scalar: # Handle the special case of a single value here assert self.shape[0] == 1 - array = np.array(start, dtype=self.dtype) - future = self.runtime.create_scalar(array.data) - self.base.set_storage(future) + legate_runtime.issue_fill(self.base, Scalar(start, self.base.type)) return - def create_scalar(value: Any, dtype: np.dtype[Any]) -> Any: - array = np.array(value, dtype) - return self.runtime.create_wrapped_scalar( - array.data, - array.dtype, - shape=(1,), - ).base - - task = self.context.create_auto_task(CuNumericOpCode.ARANGE) + task = legate_runtime.create_auto_task( + self.library, CuNumericOpCode.ARANGE + ) task.add_output(self.base) - task.add_input(create_scalar(start, self.dtype)) - task.add_input(create_scalar(stop, self.dtype)) - task.add_input(create_scalar(step, self.dtype)) + task.add_scalar_arg(start, self.base.type) + task.add_scalar_arg(step, self.base.type) task.execute() @@ -1947,36 +1894,39 @@ def tile(self, rhs: Any, reps: Union[Any, Sequence[int]]) -> None: self._fill(src_array.base) return - task = self.context.create_auto_task(CuNumericOpCode.TILE) + task = legate_runtime.create_auto_task( + self.library, CuNumericOpCode.TILE + ) task.add_output(self.base) - task.add_input(rhs.base) + p_rhs = task.add_input(rhs.base) - task.add_broadcast(rhs.base) + task.add_constraint(broadcast(p_rhs)) task.execute() # Transpose the matrix dimensions def transpose( - self, axes: Union[None, tuple[int, ...], list[int]] + self, axes: Union[tuple[int, ...], list[int]] ) -> DeferredArray: - result = self.base.transpose(axes) - result = DeferredArray(self.runtime, result) - return result + result = self.base.transpose(tuple(axes)) + return DeferredArray(self.runtime, result) @auto_convert("rhs") def trilu(self, rhs: Any, k: int, lower: bool) -> None: lhs = self.base rhs = rhs._broadcast(lhs.shape) - task = self.context.create_auto_task(CuNumericOpCode.TRILU) + task = legate_runtime.create_auto_task( + self.library, CuNumericOpCode.TRILU + ) - task.add_output(lhs) - task.add_input(rhs) + p_lhs = task.add_output(lhs) + p_rhs = task.add_input(rhs) task.add_scalar_arg(lower, ty.bool_) task.add_scalar_arg(k, ty.int32) - task.add_alignment(lhs, rhs) + task.add_constraint(align(p_lhs, p_rhs)) task.execute() @@ -1985,8 +1935,10 @@ def repeat( self, repeats: Any, axis: int, scalar_repeats: bool ) -> DeferredArray: out = self.runtime.create_unbound_thunk(self.base.type, ndim=self.ndim) - task = self.context.create_auto_task(CuNumericOpCode.REPEAT) - task.add_input(self.base) + task = legate_runtime.create_auto_task( + self.library, CuNumericOpCode.REPEAT + ) + p_self = task.add_input(self.base) task.add_output(out.base) # We pass axis now but don't use for 1D case (will use for ND case task.add_scalar_arg(axis, ty.int32) @@ -2000,8 +1952,8 @@ def repeat( if dim == axis: continue repeats = repeats.promote(dim, extent) - task.add_input(repeats) - task.add_alignment(self.base, repeats) + p_repeats = task.add_input(repeats) + task.add_constraint(align(p_self, p_repeats)) task.execute() return out @@ -2015,13 +1967,15 @@ def flip(self, rhs: Any, axes: Union[None, int, tuple[int, ...]]) -> None: else: axes = normalize_axis_tuple(axes, self.ndim) - task = self.context.create_auto_task(CuNumericOpCode.FLIP) - task.add_output(output) - task.add_input(input) + task = legate_runtime.create_auto_task( + self.library, CuNumericOpCode.FLIP + ) + p_out = task.add_output(output) + p_in = task.add_input(input) task.add_scalar_arg(axes, (ty.int32,)) - task.add_broadcast(input) - task.add_alignment(input, output) + task.add_constraint(broadcast(p_in)) + task.add_constraint(align(p_in, p_out)) task.execute() @@ -2040,15 +1994,16 @@ def bincount(self, rhs: Any, weights: Optional[NumPyThunk] = None) -> None: dst_array.fill(np.array(0, dst_array.dtype)) - task = self.context.create_auto_task(CuNumericOpCode.BINCOUNT) - task.add_reduction(dst_array.base, ReductionOp.ADD) - task.add_input(src_array.base) + task = legate_runtime.create_auto_task( + self.library, CuNumericOpCode.BINCOUNT + ) + p_dst = task.add_reduction(dst_array.base, ReductionOp.ADD) + p_src = task.add_input(src_array.base) + task.add_constraint(broadcast(p_dst)) if weight_array is not None: - task.add_input(weight_array.base) # type: ignore - - task.add_broadcast(dst_array.base) - if not (weight_array is None or weight_array.scalar): - task.add_alignment(src_array.base, weight_array.base) # type: ignore # noqa + p_weight = task.add_input(cast(DeferredArray, weight_array).base) + if not weight_array.scalar: + task.add_constraint(align(p_src, p_weight)) task.execute() @@ -2058,13 +2013,15 @@ def nonzero(self) -> tuple[NumPyThunk, ...]: for _ in range(self.ndim) ) - task = self.context.create_auto_task(CuNumericOpCode.NONZERO) + task = legate_runtime.create_auto_task( + self.library, CuNumericOpCode.NONZERO + ) - task.add_input(self.base) + p_self = task.add_input(self.base) for result in results: task.add_output(result.base) - task.add_broadcast(self.base, axes=range(1, self.ndim)) + task.add_constraint(broadcast(p_self, range(1, self.ndim))) task.execute() return results @@ -2076,7 +2033,9 @@ def bitgenerator_random_raw( seed: Union[int, None], flags: int, ) -> None: - task = self.context.create_auto_task(CuNumericOpCode.BITGENERATOR) + task = legate_runtime.create_auto_task( + self.library, CuNumericOpCode.BITGENERATOR + ) task.add_output(self.base) @@ -2102,7 +2061,9 @@ def bitgenerator_distribution( floatparams: tuple[float, ...], doubleparams: tuple[float, ...], ) -> None: - task = self.context.create_auto_task(CuNumericOpCode.BITGENERATOR) + task = legate_runtime.create_auto_task( + self.library, CuNumericOpCode.BITGENERATOR + ) task.add_output(self.base) @@ -3061,15 +3022,18 @@ def bitgenerator_negative_binomial( doubleparams, ) - def random(self, gen_code: Any, args: Any = ()) -> None: - task = self.context.create_auto_task(CuNumericOpCode.RAND) + def random(self, gen_code: Any, args: tuple[Scalar, ...] = ()) -> None: + task = legate_runtime.create_auto_task( + self.library, CuNumericOpCode.RAND + ) task.add_output(self.base) task.add_scalar_arg(gen_code.value, ty.int32) epoch = self.runtime.get_next_random_epoch() task.add_scalar_arg(epoch, ty.uint32) task.add_scalar_arg(self.compute_strides(self.shape), (ty.int64,)) - self.add_arguments(task, args) + for arg in args: + task.add_scalar_arg(arg) task.execute() @@ -3087,9 +3051,8 @@ def random_integer( high: Union[int, npt.NDArray[Any]], ) -> None: assert self.dtype.kind == "i" - low = np.array(low, self.dtype) - high = np.array(high, self.dtype) - self.random(RandGenCode.INTEGER, [low, high]) + args = (Scalar(low, self.base.type), Scalar(high, self.base.type)) + self.random(RandGenCode.INTEGER, args) # Perform the unary operation and put the result in the array @auto_convert("src") @@ -3098,25 +3061,28 @@ def unary_op( op: UnaryOpCode, src: Any, where: Any, - args: Any, + args: tuple[Scalar, ...] = (), multiout: Optional[Any] = None, ) -> None: lhs = self.base rhs = src._broadcast(lhs.shape) with Annotation({"OpCode": op.name}): - task = self.context.create_auto_task(CuNumericOpCode.UNARY_OP) - task.add_output(lhs) - task.add_input(rhs) + task = legate_runtime.create_auto_task( + self.library, CuNumericOpCode.UNARY_OP + ) + p_lhs = task.add_output(lhs) + p_rhs = task.add_input(rhs) task.add_scalar_arg(op.value, ty.int32) - self.add_arguments(task, args) + for arg in args: + task.add_scalar_arg(arg) - task.add_alignment(lhs, rhs) + task.add_constraint(align(p_lhs, p_rhs)) if multiout is not None: for out in multiout: - task.add_output(out.base) - task.add_alignment(out.base, rhs) + p_out = task.add_output(out.base) + task.add_constraint(align(p_out, p_rhs)) task.execute() @@ -3131,7 +3097,7 @@ def unary_reduction( orig_axis: Union[int, None], axes: tuple[int, ...], keepdims: bool, - args: Any, + args: tuple[Scalar, ...], initial: Any, ) -> None: lhs_array: Union[NumPyThunk, DeferredArray] = self @@ -3172,8 +3138,8 @@ def unary_reduction( lhs = lhs.project(0, 0) with Annotation({"OpCode": op.name, "ArgRed?": str(argred)}): - task = self.context.create_auto_task( - CuNumericOpCode.SCALAR_UNARY_RED + task = legate_runtime.create_auto_task( + self.library, CuNumericOpCode.SCALAR_UNARY_RED ) task.add_reduction(lhs, _UNARY_RED_TO_REDUCTION_OPS[op]) @@ -3181,7 +3147,8 @@ def unary_reduction( task.add_scalar_arg(op, ty.int32) task.add_scalar_arg(rhs_array.shape, (ty.int64,)) - self.add_arguments(task, args) + for arg in args: + task.add_scalar_arg(arg) task.execute() @@ -3213,16 +3180,21 @@ def unary_reduction( ) with Annotation({"OpCode": op.name, "ArgRed?": str(argred)}): - task = self.context.create_auto_task(CuNumericOpCode.UNARY_RED) + task = legate_runtime.create_auto_task( + self.library, CuNumericOpCode.UNARY_RED + ) - task.add_input(rhs_array.base) - task.add_reduction(result, _UNARY_RED_TO_REDUCTION_OPS[op]) + p_rhs = task.add_input(rhs_array.base) + p_result = task.add_reduction( + result, _UNARY_RED_TO_REDUCTION_OPS[op] + ) task.add_scalar_arg(axis, ty.int32) task.add_scalar_arg(op, ty.int32) - self.add_arguments(task, args) + for arg in args: + task.add_scalar_arg(arg) - task.add_alignment(result, rhs_array.base) + task.add_constraint(align(p_result, p_rhs)) task.execute() @@ -3231,7 +3203,6 @@ def unary_reduction( UnaryOpCode.GETARG, lhs_array, True, - [], ) def isclose( @@ -3239,8 +3210,8 @@ def isclose( ) -> None: assert not equal_nan args = ( - np.array(rtol, dtype=np.float64), - np.array(atol, dtype=np.float64), + Scalar(rtol, ty.float64), + Scalar(atol, ty.float64), ) self.binary_op(BinaryOpCode.ISCLOSE, rhs1, rhs2, True, args) @@ -3252,7 +3223,7 @@ def binary_op( src1: Any, src2: Any, where: Any, - args: Any, + args: tuple[Scalar, ...], ) -> None: lhs = self.base rhs1 = src1._broadcast(lhs.shape) @@ -3260,15 +3231,18 @@ def binary_op( with Annotation({"OpCode": op_code.name}): # Populate the Legate launcher - task = self.context.create_auto_task(CuNumericOpCode.BINARY_OP) - task.add_output(lhs) - task.add_input(rhs1) - task.add_input(rhs2) + task = legate_runtime.create_auto_task( + self.library, CuNumericOpCode.BINARY_OP + ) + p_lhs = task.add_output(lhs) + p_rhs1 = task.add_input(rhs1) + p_rhs2 = task.add_input(rhs2) task.add_scalar_arg(op_code.value, ty.int32) - self.add_arguments(task, args) + for arg in args: + task.add_scalar_arg(arg) - task.add_alignment(lhs, rhs1) - task.add_alignment(lhs, rhs2) + task.add_constraint(align(p_lhs, p_rhs1)) + task.add_constraint(align(p_lhs, p_rhs2)) task.execute() @@ -3279,12 +3253,12 @@ def binary_reduction( src1: Any, src2: Any, broadcast: Union[NdShape, None], - args: Any, + args: tuple[Scalar, ...], ) -> None: lhs = self.base rhs1 = src1.base rhs2 = src2.base - assert lhs.scalar + assert lhs.has_scalar_storage if broadcast is not None: rhs1 = rhs1._broadcast(broadcast) @@ -3297,14 +3271,17 @@ def binary_reduction( else: redop = ReductionOp.MUL self.fill(np.array(True)) - task = self.context.create_auto_task(CuNumericOpCode.BINARY_RED) + task = legate_runtime.create_auto_task( + self.library, CuNumericOpCode.BINARY_RED + ) task.add_reduction(lhs, redop) - task.add_input(rhs1) - task.add_input(rhs2) + p_rhs1 = task.add_input(rhs1) + p_rhs2 = task.add_input(rhs2) task.add_scalar_arg(op.value, ty.int32) - self.add_arguments(task, args) + for arg in args: + task.add_scalar_arg(arg) - task.add_alignment(rhs1, rhs2) + task.add_constraint(align(p_rhs1, p_rhs2)) task.execute() @@ -3316,48 +3293,36 @@ def where(self, src1: Any, src2: Any, src3: Any) -> None: rhs3 = src3._broadcast(lhs.shape) # Populate the Legate launcher - task = self.context.create_auto_task(CuNumericOpCode.WHERE) - task.add_output(lhs) - task.add_input(rhs1) - task.add_input(rhs2) - task.add_input(rhs3) + task = legate_runtime.create_auto_task( + self.library, CuNumericOpCode.WHERE + ) + p_lhs = task.add_output(lhs) + p_rhs1 = task.add_input(rhs1) + p_rhs2 = task.add_input(rhs2) + p_rhs3 = task.add_input(rhs3) - task.add_alignment(lhs, rhs1) - task.add_alignment(lhs, rhs2) - task.add_alignment(lhs, rhs3) + task.add_constraint(align(p_lhs, p_rhs1)) + task.add_constraint(align(p_lhs, p_rhs2)) + task.add_constraint(align(p_lhs, p_rhs3)) task.execute() def argwhere(self) -> NumPyThunk: result = self.runtime.create_unbound_thunk(ty.int64, ndim=2) - task = self.context.create_auto_task(CuNumericOpCode.ARGWHERE) + task = legate_runtime.create_auto_task( + self.library, CuNumericOpCode.ARGWHERE + ) task.add_output(result.base) - task.add_input(self.base) - task.add_broadcast(self.base, axes=range(1, self.ndim)) + p_self = task.add_input(self.base) + if self.ndim > 1: + task.add_constraint(broadcast(p_self, range(1, self.ndim))) task.execute() return result - # A helper method for attaching arguments - def add_arguments( - self, - task: Union[AutoTask, ManualTask], - args: Optional[Sequence[npt.NDArray[Any]]], - ) -> None: - if args is None: - return - for numpy_array in args: - assert numpy_array.size == 1 - scalar = self.runtime.create_wrapped_scalar( - numpy_array.data, - numpy_array.dtype, - shape=(1,), - ) - task.add_input(scalar.base) - @staticmethod def compute_strides(shape: NdShape) -> tuple[int, ...]: stride = 1 @@ -3402,27 +3367,31 @@ def scan( input.copy(swapped, deep=True) output = input - task = output.context.create_auto_task(CuNumericOpCode.SCAN_LOCAL) - task.add_output(output.base) - task.add_input(input.base) + task = legate_runtime.create_auto_task( + self.library, CuNumericOpCode.SCAN_LOCAL + ) + p_out = task.add_output(output.base) + p_in = task.add_input(input.base) task.add_output(temp.base) task.add_scalar_arg(op, ty.int32) task.add_scalar_arg(nan_to_identity, ty.bool_) - task.add_alignment(input.base, output.base) + task.add_constraint(align(p_in, p_out)) task.execute() # Global sum # NOTE: Assumes the partitioning stays the same from previous task. # NOTE: Each node will do a sum up to its index, alternatively could # do one centralized scan and broadcast (slightly less redundant work) - task = output.context.create_auto_task(CuNumericOpCode.SCAN_GLOBAL) + task = legate_runtime.create_auto_task( + self.library, CuNumericOpCode.SCAN_GLOBAL + ) task.add_input(output.base) - task.add_input(temp.base) + p_temp = task.add_input(temp.base) task.add_output(output.base) task.add_scalar_arg(op, ty.int32) - task.add_broadcast(temp.base) + task.add_constraint(broadcast(p_temp)) task.execute() @@ -3435,7 +3404,9 @@ def scan( def unique(self) -> NumPyThunk: result = self.runtime.create_unbound_thunk(self.base.type) - task = self.context.create_auto_task(CuNumericOpCode.UNIQUE) + task = legate_runtime.create_auto_task( + self.library, CuNumericOpCode.UNIQUE + ) task.add_output(result.base) task.add_input(self.base) @@ -3446,32 +3417,34 @@ def unique(self) -> NumPyThunk: task.execute() if self.runtime.num_gpus == 0 and self.runtime.num_procs > 1: - result.base = self.context.tree_reduce( - CuNumericOpCode.UNIQUE_REDUCE, result.base + result.base = legate_runtime.tree_reduce( + self.library, CuNumericOpCode.UNIQUE_REDUCE, result.base ) return result @auto_convert("rhs", "v") def searchsorted(self, rhs: Any, v: Any, side: SortSide = "left") -> None: - task = self.context.create_auto_task(CuNumericOpCode.SEARCHSORTED) + task = legate_runtime.create_auto_task( + self.library, CuNumericOpCode.SEARCHSORTED + ) is_left = side == "left" if is_left: self.fill(np.array(rhs.size, self.dtype)) - task.add_reduction(self.base, ReductionOp.MIN) + p_self = task.add_reduction(self.base, ReductionOp.MIN) else: self.fill(np.array(0, self.dtype)) - task.add_reduction(self.base, ReductionOp.MAX) + p_self = task.add_reduction(self.base, ReductionOp.MAX) task.add_input(rhs.base) - task.add_input(v.base) + p_v = task.add_input(v.base) # every partition needs the value information - task.add_broadcast(v.base) - task.add_broadcast(self.base) - task.add_alignment(self.base, v.base) + task.add_constraint(broadcast(p_v)) + task.add_constraint(broadcast(p_self)) + task.add_constraint(align(p_self, p_v)) task.add_scalar_arg(is_left, ty.bool_) task.add_scalar_arg(rhs.size, ty.int64) @@ -3523,7 +3496,9 @@ def partition( sort(self, rhs, argpartition, axis, False) def create_window(self, op_code: WindowOpCode, M: int, *args: Any) -> None: - task = self.context.create_auto_task(CuNumericOpCode.WINDOW) + task = legate_runtime.create_auto_task( + self.library, CuNumericOpCode.WINDOW + ) task.add_output(self.base) task.add_scalar_arg(op_code, ty.int32) task.add_scalar_arg(M, ty.int64) @@ -3536,15 +3511,17 @@ def packbits( self, src: Any, axis: Union[int, None], bitorder: BitOrder ) -> None: bitorder_code = getattr(Bitorder, bitorder.upper()) - task = self.context.create_auto_task(CuNumericOpCode.PACKBITS) - p_out = task.declare_partition(self.base) - p_in = task.declare_partition(src.base) - task.add_output(self.base, partition=p_out) - task.add_input(src.base, partition=p_in) + task = legate_runtime.create_auto_task( + self.library, CuNumericOpCode.PACKBITS + ) + p_out = task.declare_partition() + p_in = task.declare_partition() + task.add_output(self.base, p_out) + task.add_input(src.base, p_in) task.add_scalar_arg(axis, ty.uint32) task.add_scalar_arg(bitorder_code, ty.uint32) - scale = tuple(8 if dim == axis else 1 for dim in range(src.ndim)) - task.add_constraint(p_in <= p_out * scale) # type: ignore + factors = tuple(8 if dim == axis else 1 for dim in range(src.ndim)) + task.add_constraint(scale(factors, p_out, p_in)) # type: ignore task.execute() @auto_convert("src") @@ -3552,28 +3529,30 @@ def unpackbits( self, src: Any, axis: Union[int, None], bitorder: BitOrder ) -> None: bitorder_code = getattr(Bitorder, bitorder.upper()) - task = self.context.create_auto_task(CuNumericOpCode.UNPACKBITS) - p_out = task.declare_partition(self.base) - p_in = task.declare_partition(src.base) - task.add_output(self.base, partition=p_out) - task.add_input(src.base, partition=p_in) + task = legate_runtime.create_auto_task( + self.library, CuNumericOpCode.UNPACKBITS + ) + p_out = task.declare_partition() + p_in = task.declare_partition() + task.add_output(self.base, p_out) + task.add_input(src.base, p_in) task.add_scalar_arg(axis, ty.uint32) task.add_scalar_arg(bitorder_code, ty.uint32) - scale = tuple(8 if dim == axis else 1 for dim in range(src.ndim)) - task.add_constraint(p_out <= p_in * scale) # type: ignore + factors = tuple(8 if dim == axis else 1 for dim in range(src.ndim)) + task.add_constraint(scale(factors, p_in, p_out)) # type: ignore task.execute() @auto_convert("src") def _wrap(self, src: Any, new_len: int) -> None: - if src.base.kind == Future or src.base.transformed: - change_shape = src.base.kind == Future + if src.base.has_scalar_storage or src.base.transformed: + change_shape = src.base.has_scalar_storage src = src._convert_future_to_regionfield(change_shape) # first, we create indirect array with PointN type that # (len,) shape and is used to copy data from original array # to the target 1D wrapped array N = src.ndim - pointN_dtype = self.runtime.get_point_type(N) + pointN_dtype = ty.point_type(N) indirect = cast( DeferredArray, self.runtime.create_empty_thunk( @@ -3583,19 +3562,16 @@ def _wrap(self, src: Any, new_len: int) -> None: ), ) - task = self.context.create_auto_task(CuNumericOpCode.WRAP) + task = legate_runtime.create_auto_task( + self.library, CuNumericOpCode.WRAP + ) task.add_output(indirect.base) task.add_scalar_arg(src.shape, (ty.int64,)) task.add_scalar_arg(False, ty.bool_) # has_input task.add_scalar_arg(False, ty.bool_) # check bounds task.execute() - copy = self.context.create_copy() - copy.set_target_indirect_out_of_range(False) - copy.add_input(src.base) - copy.add_source_indirect(indirect.base) - copy.add_output(self.base) - copy.execute() + legate_runtime.issue_gather(self.base, src.base, indirect.base) # Perform a histogram operation on the array @auto_convert("src", "bins", "weights") @@ -3614,14 +3590,16 @@ def histogram(self, src: Any, bins: Any, weights: Any) -> None: dst_array.fill(np.array(0, dst_array.dtype)) - task = self.context.create_auto_task(CuNumericOpCode.HISTOGRAM) - task.add_reduction(dst_array.base, ReductionOp.ADD) - task.add_input(src_array.base) - task.add_input(bins_array.base) - task.add_input(weight_array.base) - - task.add_broadcast(bins_array.base) - task.add_broadcast(dst_array.base) - task.add_alignment(src_array.base, weight_array.base) + task = legate_runtime.create_auto_task( + self.library, CuNumericOpCode.HISTOGRAM + ) + p_dst = task.add_reduction(dst_array.base, ReductionOp.ADD) + p_src = task.add_input(src_array.base) + p_bins = task.add_input(bins_array.base) + p_weight = task.add_input(weight_array.base) + + task.add_constraint(broadcast(p_bins)) + task.add_constraint(broadcast(p_dst)) + task.add_constraint(align(p_src, p_weight)) task.execute() diff --git a/cunumeric/eager.py b/cunumeric/eager.py index 680f1b5a17..097ba2226b 100644 --- a/cunumeric/eager.py +++ b/cunumeric/eager.py @@ -26,6 +26,7 @@ ) import numpy as np +from legate.core import Scalar, get_legate_runtime from .config import ( FFT_C2R, @@ -42,11 +43,10 @@ ) from .deferred import DeferredArray from .thunk import NumPyThunk -from .utils import is_advanced_indexing, is_supported_type +from .utils import is_advanced_indexing, is_supported_type, to_core_dtype if TYPE_CHECKING: import numpy.typing as npt - from legate.core import FieldID, Future, Region from .config import BitGeneratorType, FFTType from .runtime import Runtime @@ -233,15 +233,6 @@ def __init__( self.deferred: Optional[Union[DeferredArray, NumPyThunk]] = None self.escaped = False - @property - def storage(self) -> Union[Future, tuple[Region, FieldID]]: - if self.deferred is None: - self.to_deferred_array() - - assert self.deferred is not None - - return self.deferred.storage - @property def shape(self) -> NdShape: return self.array.shape @@ -309,11 +300,15 @@ def to_deferred_array(self) -> DeferredArray: # We are at the root of the tree so we need to # actually make a DeferredArray to use if self.array.size == 1: - self.deferred = self.runtime.create_wrapped_scalar( - self.array.data, - dtype=self.array.dtype, + runtime = get_legate_runtime() + store = runtime.create_store_from_scalar( + Scalar( + self.array.tobytes(), + to_core_dtype(self.array.dtype), + ), shape=self.shape, ) + self.deferred = DeferredArray(self.runtime, store) else: self.deferred = self.runtime.find_or_create_array_thunk( self.array, @@ -405,11 +400,6 @@ def scalar(self) -> bool: return self.deferred.scalar return self.array.size == 1 - def get_scalar_array(self) -> npt.NDArray[Any]: - if self.deferred is not None: - return self.deferred.get_scalar_array() - return self.array.reshape(()) - def _create_indexing_key(self, key: Any) -> Any: if key is None or key is Ellipsis: return key @@ -430,7 +420,12 @@ def get_item(self, key: Any) -> NumPyThunk: return self.deferred.get_item(key) if is_advanced_indexing(key): index_key = self._create_indexing_key(key) - out = self.array[index_key] + # FIXME: Need to raise RuntimeError instead of IndexError to be + # consistent with the DeferredArray implementation + try: + out = self.array[index_key] + except IndexError as e: + raise RuntimeError(e) from e result = EagerArray(self.runtime, out) else: child = self.array[key] @@ -447,10 +442,15 @@ def set_item(self, key: Any, value: Any) -> None: else: if is_advanced_indexing(key): index_key = self._create_indexing_key(key) - if isinstance(value, EagerArray): - self.array[index_key] = value.array - else: - self.array[index_key] = value + # FIXME: Need to raise RuntimeError instead of IndexError to be + # consistent with the DeferredArray implementation + try: + if isinstance(value, EagerArray): + self.array[index_key] = value.array + else: + self.array[index_key] = value + except IndexError as e: + raise RuntimeError(e) from e else: if isinstance(value, EagerArray): self.array[key] = value.array @@ -534,9 +534,7 @@ def fill(self, value: Any) -> None: else: self.array.fill(value) - def transpose( - self, axes: Union[None, tuple[int, ...], list[int]] - ) -> NumPyThunk: + def transpose(self, axes: Union[tuple[int, ...], list[int]]) -> NumPyThunk: if self.deferred is not None: return self.deferred.transpose(axes) # See https://github.com/numpy/numpy/issues/22019 @@ -658,7 +656,12 @@ def put(self, indices: Any, values: Any, check_bounds: bool) -> None: if self.deferred is not None: self.deferred.put(indices, values, check_bounds) else: - np.put(self.array, indices.array, values.array) + # FIXME: Need to raise RuntimeError instead of IndexError to be + # consistent with the DeferredArray implementation + try: + np.put(self.array, indices.array, values.array) + except IndexError as e: + raise RuntimeError(e) from e def putmask(self, mask: Any, values: Any) -> None: self.check_eager_args(mask, values) @@ -1434,7 +1437,7 @@ def unary_op( op: UnaryOpCode, rhs: Any, where: Any, - args: Any, + args: tuple[Scalar, ...] = (), multiout: Optional[Any] = None, ) -> None: if multiout is None: @@ -1465,7 +1468,12 @@ def unary_op( else where.array, ) elif op == UnaryOpCode.CLIP: - np.clip(rhs.array, out=self.array, a_min=args[0], a_max=args[1]) + np.clip( + rhs.array, + out=self.array, + a_min=args[0].value(), + a_max=args[1].value(), + ) elif op == UnaryOpCode.COPY: self.array[:] = rhs.array[:] elif op == UnaryOpCode.IMAG: @@ -1483,7 +1491,7 @@ def unary_reduction( orig_axis: Union[int, None], axes: tuple[int, ...], keepdims: bool, - args: Any, + args: tuple[Scalar, ...], initial: Any, ) -> None: self.check_eager_args(rhs, where) @@ -1525,7 +1533,7 @@ def unary_reduction( **kws, ) elif op == UnaryRedCode.CONTAINS: - self.array.fill(args[0] in rhs.array) + self.array.fill(args[0].value() in rhs.array) elif op == UnaryRedCode.COUNT_NONZERO: self.array[()] = np.count_nonzero(rhs.array, axis=orig_axis) else: @@ -1547,7 +1555,12 @@ def isclose( ) def binary_op( - self, op: BinaryOpCode, rhs1: Any, rhs2: Any, where: Any, args: Any + self, + op: BinaryOpCode, + rhs1: Any, + rhs2: Any, + where: Any, + args: tuple[Scalar, ...], ) -> None: self.check_eager_args(rhs1, rhs2, where) if self.deferred is not None: @@ -1571,7 +1584,7 @@ def binary_reduction( rhs1: Any, rhs2: Any, broadcast: Union[NdShape, None], - args: Any, + args: tuple[Scalar, ...], ) -> None: self.check_eager_args(rhs1, rhs2) if self.deferred is not None: @@ -1580,7 +1593,10 @@ def binary_reduction( if op == BinaryOpCode.ISCLOSE: self.array = np.array( np.allclose( - rhs1.array, rhs2.array, rtol=args[0], atol=args[1] + rhs1.array, + rhs2.array, + rtol=args[0].value(), + atol=args[1].value(), ) ) elif op == BinaryOpCode.EQUAL: @@ -1636,9 +1652,12 @@ def solve(self, a: Any, b: Any) -> None: try: result = np.linalg.solve(a.array, b.array) except np.linalg.LinAlgError as e: - from .linalg import LinAlgError + # from .linalg import LinAlgError - raise LinAlgError(e) from e + # FIXME: Use RuntimeError for now to be consistent with the + # DeferredArray implementation + # raise LinAlgError(e) from e + raise RuntimeError(e) from e self.array[:] = result def scan( diff --git a/cunumeric/linalg/cholesky.py b/cunumeric/linalg/cholesky.py index 9bba033619..febe10f308 100644 --- a/cunumeric/linalg/cholesky.py +++ b/cunumeric/linalg/cholesky.py @@ -16,47 +16,57 @@ from typing import TYPE_CHECKING -from legate.core import Rect, types as ty -from legate.core.shape import Shape -from legate.settings import settings +from legate.core import broadcast, get_legate_runtime, types as ty from cunumeric.config import CuNumericOpCode -from .exception import LinAlgError +# from legate.core.shape import Shape +# from legate.settings import settings + + +# from .exception import LinAlgError + +legate_runtime = get_legate_runtime() if TYPE_CHECKING: - from legate.core.context import Context - from legate.core.store import Store, StorePartition + from legate.core import Library, LogicalStore, LogicalStorePartition from ..deferred import DeferredArray - from ..runtime import Runtime + + # from ..runtime import Runtime + + +legate_runtime = get_legate_runtime() def transpose_copy_single( - context: Context, input: Store, output: Store + library: Library, input: LogicalStore, output: LogicalStore ) -> None: - task = context.create_auto_task(CuNumericOpCode.TRANSPOSE_COPY_2D) - task.add_output(output) - task.add_input(input) + task = legate_runtime.create_auto_task( + library, CuNumericOpCode.TRANSPOSE_COPY_2D + ) + p_out = task.add_output(output) + p_in = task.add_input(input) # Output has the same shape as input, but is mapped # to a column major instance task.add_scalar_arg(False, ty.bool_) - task.add_broadcast(output) - task.add_broadcast(input) + task.add_constraint(broadcast(p_out)) + task.add_constraint(broadcast(p_in)) task.execute() def transpose_copy( - context: Context, - launch_domain: Rect, - p_input: StorePartition, - p_output: StorePartition, + library: Library, + launch_domain: list[int], + p_input: LogicalStorePartition, + p_output: LogicalStorePartition, ) -> None: - task = context.create_manual_task( + task = legate_runtime.create_manual_task( + library, CuNumericOpCode.TRANSPOSE_COPY_2D, - launch_domain=launch_domain, + launch_domain, ) task.add_output(p_output) task.add_input(p_input) @@ -67,82 +77,88 @@ def transpose_copy( task.execute() -def potrf_single(context: Context, output: Store) -> None: - task = context.create_auto_task(CuNumericOpCode.POTRF) - task.throws_exception(LinAlgError) +def potrf_single(library: Library, output: LogicalStore) -> None: + task = legate_runtime.create_auto_task(library, CuNumericOpCode.POTRF) + # TODO: We need to put back the precise Python exception support + # task.throws_exception(LinAlgError) + task.throws_exception(True) task.add_output(output) task.add_input(output) task.execute() -def potrf(context: Context, p_output: StorePartition, i: int) -> None: - launch_domain = Rect(lo=(i, i), hi=(i + 1, i + 1)) - task = context.create_manual_task( - CuNumericOpCode.POTRF, launch_domain=launch_domain - ) - task.throws_exception(LinAlgError) - task.add_output(p_output) - task.add_input(p_output) - task.execute() - - -def trsm( - context: Context, p_output: StorePartition, i: int, lo: int, hi: int -) -> None: - if lo >= hi: - return - - rhs = p_output.get_child_store(i, i) - lhs = p_output - - launch_domain = Rect(lo=(lo, i), hi=(hi, i + 1)) - task = context.create_manual_task( - CuNumericOpCode.TRSM, launch_domain=launch_domain - ) - task.add_output(lhs) - task.add_input(rhs) - task.add_input(lhs) - task.execute() - - -def syrk(context: Context, p_output: StorePartition, k: int, i: int) -> None: - rhs = p_output.get_child_store(k, i) - lhs = p_output - - launch_domain = Rect(lo=(k, k), hi=(k + 1, k + 1)) - task = context.create_manual_task( - CuNumericOpCode.SYRK, launch_domain=launch_domain - ) - task.add_output(lhs) - task.add_input(rhs) - task.add_input(lhs) - task.execute() - - -def gemm( - context: Context, - p_output: StorePartition, - k: int, - i: int, - lo: int, - hi: int, -) -> None: - if lo >= hi: - return - - rhs2 = p_output.get_child_store(k, i) - lhs = p_output - rhs1 = p_output - - launch_domain = Rect(lo=(lo, k), hi=(hi, k + 1)) - task = context.create_manual_task( - CuNumericOpCode.GEMM, launch_domain=launch_domain - ) - task.add_output(lhs) - task.add_input(rhs1, proj=lambda p: (p[0], i)) - task.add_input(rhs2) - task.add_input(lhs) - task.execute() +# def potrf(library: Library, p_output: LogicalStorePartition, i: int) -> None: +# launch_domain = [1, 1] +# task = legate_runtime.create_manual_task( +# library, CuNumericOpCode.POTRF, launch_domain +# ) +# # TODO: We need to put back the precise Python exception support +# # task.throws_exception(LinAlgError) +# task.throws_exception(True) +# task.add_output(p_output) +# task.add_input(p_output) +# task.execute() + + +# def trsm( +# library: Library, p_output: LogicalStorePartition, i: int, lo: int, hi: int +# ) -> None: +# if lo >= hi: +# return +# +# rhs = p_output.get_child_store(i, i) +# lhs = p_output +# +# launch_domain = [hi - lo, 1] +# task = legate_runtime.create_manual_task( +# library, CuNumericOpCode.TRSM, launch_domain +# ) +# task.add_output(lhs) +# task.add_input(rhs) +# task.add_input(lhs) +# task.execute() + + +# def syrk( +# library: Library, p_output: LogicalStorePartition, k: int, i: int +# ) -> None: +# rhs = p_output.get_child_store(k, i) +# lhs = p_output +# +# launch_domain = [1, 1] +# task = legate_runtime.create_manual_task( +# library, CuNumericOpCode.SYRK, launch_domain +# ) +# task.add_output(lhs) +# task.add_input(rhs) +# task.add_input(lhs) +# task.execute() + + +# def gemm( +# library: Library, +# p_output: LogicalStorePartition, +# k: int, +# i: int, +# lo: int, +# hi: int, +# ) -> None: +# if lo >= hi: +# return +# +# rhs2 = p_output.get_child_store(k, i) +# lhs = p_output +# rhs1 = p_output +# +# launch_domain = [hi - lo, 1] +# task = legate_runtime.create_manual_task( +# library, CuNumericOpCode.GEMM, launch_domain +# ) +# task.add_output(lhs) +# task.add_input(rhs1, proj=lambda p: (p[0], i)) +# task.add_input(rhs2) +# task.add_input(lhs) +# task.execute() MIN_CHOLESKY_TILE_SIZE = 2048 @@ -150,32 +166,32 @@ def gemm( # TODO: We need a better cost model -def choose_color_shape(runtime: Runtime, shape: Shape) -> Shape: - if settings.test(): - num_tiles = runtime.num_procs * 2 - return Shape((num_tiles, num_tiles)) - - extent = shape[0] - # If there's only one processor or the matrix is too small, - # don't even bother to partition it at all - if runtime.num_procs == 1 or extent <= MIN_CHOLESKY_MATRIX_SIZE: - return Shape((1, 1)) - - # If the matrix is big enough to warrant partitioning, - # pick the granularity that the tile size is greater than a threshold - num_tiles = runtime.num_procs - max_num_tiles = runtime.num_procs * 4 - while ( - (extent + num_tiles - 1) // num_tiles > MIN_CHOLESKY_TILE_SIZE - and num_tiles * 2 <= max_num_tiles - ): - num_tiles *= 2 - - return Shape((num_tiles, num_tiles)) - - -def tril_single(context: Context, output: Store) -> None: - task = context.create_auto_task(CuNumericOpCode.TRILU) +# def choose_color_shape(runtime: Runtime, shape: Shape) -> Shape: +# if settings.test(): +# num_tiles = runtime.num_procs * 2 +# return Shape((num_tiles, num_tiles)) +# +# extent = shape[0] +# # If there's only one processor or the matrix is too small, +# # don't even bother to partition it at all +# if runtime.num_procs == 1 or extent <= MIN_CHOLESKY_MATRIX_SIZE: +# return Shape((1, 1)) +# +# # If the matrix is big enough to warrant partitioning, +# # pick the granularity that the tile size is greater than a threshold +# num_tiles = runtime.num_procs +# max_num_tiles = runtime.num_procs * 4 +# while ( +# (extent + num_tiles - 1) // num_tiles > MIN_CHOLESKY_TILE_SIZE +# and num_tiles * 2 <= max_num_tiles +# ): +# num_tiles *= 2 +# +# return Shape((num_tiles, num_tiles)) + + +def tril_single(library: Library, output: LogicalStore) -> None: + task = legate_runtime.create_auto_task(library, CuNumericOpCode.TRILU) task.add_output(output) task.add_input(output) task.add_scalar_arg(True, ty.bool_) @@ -186,53 +202,64 @@ def tril_single(context: Context, output: Store) -> None: task.execute() -def tril(context: Context, p_output: StorePartition, n: int) -> None: - launch_domain = Rect((n, n)) - task = context.create_manual_task( - CuNumericOpCode.TRILU, launch_domain=launch_domain - ) +# def tril(library: Library, p_output: LogicalStorePartition, n: int) -> None: +# launch_domain = [n, n] +# task = legate_runtime.create_manual_task( +# library, CuNumericOpCode.TRILU, launch_domain +# ) +# +# task.add_output(p_output) +# task.add_input(p_output) +# task.add_scalar_arg(True, ty.bool_) +# task.add_scalar_arg(0, ty.int32) +# # Add a fake task argument to indicate that this is for Cholesky +# task.add_scalar_arg(True, ty.bool_) +# +# task.execute() - task.add_output(p_output) - task.add_input(p_output) - task.add_scalar_arg(True, ty.bool_) - task.add_scalar_arg(0, ty.int32) - # Add a fake task argument to indicate that this is for Cholesky - task.add_scalar_arg(True, ty.bool_) - task.execute() +# TODO: Put back this parallel Cholesky implementation +# def cholesky( +# output: DeferredArray, input: DeferredArray, no_tril: bool +# ) -> None: +# runtime = output.runtime +# library = output.library +# +# if runtime.num_procs == 1: +# transpose_copy_single(library, input.base, output.base) +# potrf_single(library, output.base) +# if not no_tril: +# tril_single(library, output.base) +# return +# +# shape = output.base.shape +# initial_color_shape = choose_color_shape(runtime, shape) +# tile_shape = (shape + initial_color_shape - 1) // initial_color_shape +# color_shape = (shape + tile_shape - 1) // tile_shape +# n = color_shape[0] +# +# p_input = input.base.partition_by_tiling(tile_shape) +# p_output = output.base.partition_by_tiling(tile_shape) +# transpose_copy(library, color_shape, p_input, p_output) +# +# for i in range(n): +# potrf(library, p_output, i) +# trsm(library, p_output, i, i + 1, n) +# for k in range(i + 1, n): +# syrk(library, p_output, k, i) +# gemm(library, p_output, k, i, k + 1, n) +# +# if no_tril: +# return +# +# tril(library, p_output, n) def cholesky( output: DeferredArray, input: DeferredArray, no_tril: bool ) -> None: - runtime = output.runtime - context = output.context - - if runtime.num_procs == 1: - transpose_copy_single(context, input.base, output.base) - potrf_single(context, output.base) - if not no_tril: - tril_single(context, output.base) - return - - shape = output.base.shape - initial_color_shape = choose_color_shape(runtime, shape) - tile_shape = (shape + initial_color_shape - 1) // initial_color_shape - color_shape = (shape + tile_shape - 1) // tile_shape - n = color_shape[0] - - p_input = input.base.partition_by_tiling(tile_shape) - p_output = output.base.partition_by_tiling(tile_shape) - transpose_copy(context, Rect(hi=color_shape), p_input, p_output) - - for i in range(n): - potrf(context, p_output, i) - trsm(context, p_output, i, i + 1, n) - for k in range(i + 1, n): - syrk(context, p_output, k, i) - gemm(context, p_output, k, i, k + 1, n) - - if no_tril: - return - - tril(context, p_output, n) + library = output.library + transpose_copy_single(library, input.base, output.base) + potrf_single(library, output.base) + if not no_tril: + tril_single(library, output.base) diff --git a/cunumeric/linalg/solve.py b/cunumeric/linalg/solve.py index cec277c4e3..c64fd45967 100644 --- a/cunumeric/linalg/solve.py +++ b/cunumeric/linalg/solve.py @@ -16,28 +16,34 @@ from typing import TYPE_CHECKING, cast +from legate.core import broadcast, get_legate_runtime + from cunumeric.config import CuNumericOpCode from .cholesky import transpose_copy_single -from .exception import LinAlgError + +# from .exception import LinAlgError if TYPE_CHECKING: - from legate.core.context import Context - from legate.core.store import Store + from legate.core import Library, LogicalStore from ..deferred import DeferredArray -def solve_single(context: Context, a: Store, b: Store) -> None: - task = context.create_auto_task(CuNumericOpCode.SOLVE) - task.throws_exception(LinAlgError) - task.add_input(a) - task.add_input(b) - task.add_output(a) - task.add_output(b) +def solve_single(library: Library, a: LogicalStore, b: LogicalStore) -> None: + task = get_legate_runtime().create_auto_task( + library, CuNumericOpCode.SOLVE + ) + # TODO: We need to put back the precise Python exception support + # task.throws_exception(LinAlgError) + task.throws_exception(True) + p_a = task.add_input(a) + p_b = task.add_input(b) + task.add_output(a, p_a) + task.add_output(b, p_b) - task.add_broadcast(a) - task.add_broadcast(b) + task.add_constraint(broadcast(p_a)) + task.add_constraint(broadcast(p_b)) task.execute() @@ -46,17 +52,17 @@ def solve(output: DeferredArray, a: DeferredArray, b: DeferredArray) -> None: from ..deferred import DeferredArray runtime = output.runtime - context = output.context + library = output.library a_copy = cast( DeferredArray, runtime.create_empty_thunk(a.shape, dtype=a.base.type, inputs=(a,)), ) - transpose_copy_single(context, a.base, a_copy.base) + transpose_copy_single(library, a.base, a_copy.base) if b.ndim > 1: - transpose_copy_single(context, b.base, output.base) + transpose_copy_single(library, b.base, output.base) else: output.copy(b) - solve_single(context, a_copy.base, output.base) + solve_single(library, a_copy.base, output.base) diff --git a/cunumeric/module.py b/cunumeric/module.py index e8d933da65..fcc7db9983 100644 --- a/cunumeric/module.py +++ b/cunumeric/module.py @@ -33,6 +33,7 @@ import numpy as np import opt_einsum as oe # type: ignore [import] +from legate.core import Scalar, types as ty from numpy.core.multiarray import ( # type: ignore [attr-defined] normalize_axis_index, ) @@ -5043,7 +5044,7 @@ def allclose( raise NotImplementedError( "cuNumeric does not support `equal_nan` yet for allclose" ) - args = (np.array(rtol, dtype=np.float64), np.array(atol, dtype=np.float64)) + args = (Scalar(rtol, ty.float64), Scalar(atol, ty.float64)) return ndarray._perform_binary_reduction( BinaryOpCode.ISCLOSE, a, diff --git a/cunumeric/runtime.py b/cunumeric/runtime.py index e9b476aa46..d76e8c2479 100644 --- a/cunumeric/runtime.py +++ b/cunumeric/runtime.py @@ -14,15 +14,13 @@ # from __future__ import annotations -import struct import warnings from functools import reduce from typing import TYPE_CHECKING, Any, Optional, Sequence, Union import legate.core.types as ty import numpy as np -from legate.core import LEGATE_MAX_DIM, ProcessorKind, Rect, get_legate_runtime -from legate.core.context import Context as LegateContext +from legate.core import LEGATE_MAX_DIM, Scalar, TaskTarget, get_legate_runtime from legate.settings import settings as legate_settings from typing_extensions import TypeGuard @@ -30,7 +28,6 @@ BitGeneratorOperation, CuNumericOpCode, CuNumericTunable, - cunumeric_context, cunumeric_lib, ) from .deferred import DeferredArray @@ -42,35 +39,31 @@ if TYPE_CHECKING: import numpy.typing as npt - from legate.core._legion.future import Future - from legate.core.operation import AutoTask, ManualTask + from legate.core import AutoTask, ManualTask from .array import ndarray DIMENSION = int +legate_runtime = get_legate_runtime() + class Runtime(object): - def __init__(self, legate_context: LegateContext) -> None: - self.legate_context = legate_context - self.legate_runtime = get_legate_runtime() + def __init__(self) -> None: + self.library = legate_runtime.find_library(cunumeric_lib.name) self.current_random_epoch = 0 self.current_random_bitgenid = 0 self.current_random_bitgen_zombies: tuple[Any, ...] = () self.destroyed = False self.api_calls: list[tuple[str, str, bool]] = [] - self.max_eager_volume = int( - self.legate_context.get_tunable( - CuNumericTunable.MAX_EAGER_VOLUME, - ty.int32, - ) + max_eager_volume = self.library.get_tunable( + CuNumericTunable.MAX_EAGER_VOLUME, + ty.int32, ) + self.max_eager_volume = int(np.asarray(max_eager_volume)) - # Make sure that our CuNumericLib object knows about us so it can - # destroy us - cunumeric_lib.set_runtime(self) assert cunumeric_lib.shared_object is not None self.cunumeric_lib = cunumeric_lib.shared_object self.has_curand = cunumeric_lib.shared_object.cunumeric_has_curand() @@ -80,26 +73,16 @@ def __init__(self, legate_context: LegateContext) -> None: if self.num_gpus > 0 and settings.preload_cudalibs(): self._load_cudalibs() - # Maps dimensions to point types - self._cached_point_types: dict[DIMENSION, ty.Dtype] = dict() # Maps value types to struct types used in argmin/argmax - self._cached_argred_types: dict[ty.Dtype, ty.Dtype] = dict() + self._cached_argred_types: dict[ty.Type, ty.Type] = dict() @property def num_procs(self) -> int: - return len(self.legate_runtime.machine) + return len(legate_runtime.machine) @property def num_gpus(self) -> int: - return self.legate_runtime.machine.count(ProcessorKind.GPU) - - def get_point_type(self, dim: DIMENSION) -> ty.Dtype: - cached = self._cached_point_types.get(dim) - if cached is not None: - return cached - point_dtype = ty.array_type(ty.int64, dim) if dim > 1 else ty.int64 - self._cached_point_types[dim] = point_dtype - return point_dtype + return legate_runtime.machine.count(TaskTarget.GPU) def record_api_call( self, name: str, location: str, implemented: bool @@ -108,21 +91,23 @@ def record_api_call( self.api_calls.append((name, location, implemented)) def _load_cudalibs(self) -> None: - task = self.legate_context.create_manual_task( + task = legate_runtime.create_manual_task( + self.library, CuNumericOpCode.LOAD_CUDALIBS, - launch_domain=Rect(lo=(0,), hi=(self.num_gpus,)), + [self.num_gpus], ) task.execute() - self.legate_runtime.issue_execution_fence(block=True) + legate_runtime.issue_execution_fence(block=True) def _unload_cudalibs(self) -> None: - task = self.legate_context.create_manual_task( + task = legate_runtime.create_manual_task( + self.library, CuNumericOpCode.UNLOAD_CUDALIBS, - launch_domain=Rect(lo=(0,), hi=(self.num_gpus,)), + [self.num_gpus], ) task.execute() - def get_argred_type(self, value_dtype: ty.Dtype) -> ty.Dtype: + def get_argred_type(self, value_dtype: ty.Type) -> ty.Type: cached = self._cached_argred_types.get(value_dtype) if cached is not None: return cached @@ -165,32 +150,6 @@ def destroy(self) -> None: self._report_coverage() self.destroyed = True - def create_scalar( - self, - array: Union[memoryview, npt.NDArray[Any]], - shape: Optional[NdShape] = None, - ) -> Future: - data = array.tobytes() - buf = struct.pack(f"{len(data)}s", data) - return self.legate_runtime.create_future(buf, len(buf)) - - def create_wrapped_scalar( - self, - array: Union[memoryview, npt.NDArray[Any]], - dtype: np.dtype[Any], - shape: NdShape, - ) -> DeferredArray: - future = self.create_scalar(array, shape) - assert all(extent == 1 for extent in shape) - core_dtype = to_core_dtype(dtype) - store = self.legate_context.create_store( - core_dtype, - shape=shape, - storage=future, - optimize_scalar=True, - ) - return DeferredArray(self, store) - def bitgenerator_populate_task( self, task: Union[AutoTask, ManualTask], @@ -215,9 +174,10 @@ def bitgenerator_create( ) -> int: self.current_random_bitgenid = self.current_random_bitgenid + 1 if forceCreate: - task = self.legate_context.create_manual_task( + task = legate_runtime.create_manual_task( + self.library, CuNumericOpCode.BITGENERATOR, - launch_domain=Rect(lo=(0,), hi=(self.num_procs,)), + (self.num_procs,), ) self.bitgenerator_populate_task( task, @@ -232,7 +192,7 @@ def bitgenerator_create( ) self.current_random_bitgen_zombies = () task.execute() - self.legate_runtime.issue_execution_fence() + legate_runtime.issue_execution_fence() return self.current_random_bitgenid def bitgenerator_destroy( @@ -243,10 +203,11 @@ def bitgenerator_destroy( self.current_random_bitgen_zombies += (handle,) else: # with explicit destruction, do schedule a task - self.legate_runtime.issue_execution_fence() - task = self.legate_context.create_manual_task( + legate_runtime.issue_execution_fence() + task = legate_runtime.create_manual_task( + self.library, CuNumericOpCode.BITGENERATOR, - launch_domain=Rect(lo=(0,), hi=(self.num_procs,)), + (self.num_procs,), ) self.bitgenerator_populate_task( task, BitGeneratorOperation.DESTROY, handle @@ -283,13 +244,11 @@ def get_numpy_thunk( raise ValueError("Legate data must be array-like") field = next(iter(data)) array = data[field] - stores = array.stores() - if len(stores) != 2: - raise ValueError("Legate data must be array-like") - if stores[0] is not None: - raise NotImplementedError("Need support for masked arrays") - store = stores[1] - return DeferredArray(self, store) + if array.nested or array.nullable: + raise NotImplementedError( + "Array must be non-nullable and not nested" + ) + return DeferredArray(self, array.data) # See if this is a normal numpy array # Make sure to convert numpy matrices to numpy arrays here # as the former doesn't behave quite like the latter @@ -303,12 +262,11 @@ def get_numpy_thunk( obj = obj.astype(dtype) elif not share: obj = obj.copy() + # We can't attach NumPy ndarrays in shared mode unless they are + # writeable + share = share and obj.flags["W"] return self.find_or_create_array_thunk(obj, share=share) - def has_external_attachment(self, array: Any) -> bool: - assert array.base is None or not isinstance(array.base, np.ndarray) - return self.legate_runtime.has_attachment(array.data) - @staticmethod def compute_parent_child_mapping( array: npt.NDArray[Any], @@ -418,34 +376,28 @@ def find_or_create_array_thunk( # Check to see if it is a type that we support for doing deferred # execution and big enough to be worth off-loading onto Legion dtype = to_core_dtype(array.dtype) - if ( - defer - or not self.is_eager_shape(array.shape) - or self.has_external_attachment(array) - ): + if defer or not self.is_eager_shape(array.shape): if array.size == 1 and not share: # This is a single value array # We didn't attach to this so we don't need to save it - return self.create_wrapped_scalar( - array.data, - array.dtype, - array.shape, + store = legate_runtime.create_store_from_scalar( + Scalar(array.tobytes(), dtype), + shape=array.shape, ) + return DeferredArray(self, store) # This is not a scalar so make a field - store = self.legate_context.create_store( + store = legate_runtime.create_store_from_buffer( dtype, - shape=array.shape, - optimize_scalar=False, - ) - store.attach_external_allocation( - array.data, + array.shape, + array, share, ) return DeferredArray( self, store, numpy_array=array if share else None, + needs_detach=share, ) # Make this into an eager evaluated thunk @@ -454,13 +406,13 @@ def find_or_create_array_thunk( def create_empty_thunk( self, shape: NdShape, - dtype: ty.Dtype, + dtype: ty.Type, inputs: Optional[Sequence[NumPyThunk]] = None, ) -> NumPyThunk: if self.is_eager_shape(shape) and self.are_all_eager_inputs(inputs): return self.create_eager_thunk(shape, dtype.to_numpy_dtype()) - store = self.legate_context.create_store( + store = legate_runtime.create_store( dtype, shape=shape, optimize_scalar=True ) return DeferredArray(self, store) @@ -473,9 +425,9 @@ def create_eager_thunk( return EagerArray(self, np.empty(shape, dtype=dtype)) def create_unbound_thunk( - self, dtype: ty.Dtype, ndim: int = 1 + self, dtype: ty.Type, ndim: int = 1 ) -> DeferredArray: - store = self.legate_context.create_store(dtype, ndim=ndim) + store = legate_runtime.create_store(dtype, ndim=ndim) return DeferredArray(self, store) def is_eager_shape(self, shape: NdShape) -> bool: @@ -548,4 +500,11 @@ def warn(self, msg: str, category: type = UserWarning) -> None: warnings.warn(msg, stacklevel=stacklevel, category=category) -runtime = Runtime(cunumeric_context) +runtime = Runtime() + + +def _shutdown_callback() -> None: + runtime.destroy() + + +legate_runtime.add_shutdown_callback(_shutdown_callback) diff --git a/cunumeric/sort.py b/cunumeric/sort.py index a0503bf92a..fe3ef3b93e 100644 --- a/cunumeric/sort.py +++ b/cunumeric/sort.py @@ -16,7 +16,7 @@ from typing import TYPE_CHECKING, Union, cast -from legate.core import types as ty +from legate.core import get_legate_runtime, types as ty from numpy.core.multiarray import ( # type: ignore [attr-defined] normalize_axis_index, ) @@ -86,7 +86,8 @@ def sort_swapped( def sort_task( output: DeferredArray, input: DeferredArray, argsort: bool, stable: bool ) -> None: - task = output.context.create_auto_task(CuNumericOpCode.SORT) + runtime = get_legate_runtime() + task = runtime.create_auto_task(output.library, CuNumericOpCode.SORT) uses_unbound_output = output.runtime.num_procs > 1 and input.ndim == 1 diff --git a/cunumeric/thunk.py b/cunumeric/thunk.py index 62417271a9..ca27af164e 100644 --- a/cunumeric/thunk.py +++ b/cunumeric/thunk.py @@ -22,7 +22,7 @@ if TYPE_CHECKING: import numpy as np import numpy.typing as npt - from legate.core import FieldID, Future, Region + from legate.core import Scalar from .config import ( BinaryOpCode, @@ -55,7 +55,7 @@ class NumPyThunk(ABC): def __init__(self, runtime: Runtime, dtype: np.dtype[Any]) -> None: self.runtime = runtime - self.context = runtime.legate_context + self.library = runtime.library self.dtype = dtype @property @@ -73,11 +73,6 @@ def size(self) -> int: # Abstract methods - @abstractproperty - def storage(self) -> Union[Future, tuple[Region, FieldID]]: - """Return the Legion storage primitive for this NumPy thunk""" - ... - @abstractproperty def shape(self) -> NdShape: ... @@ -127,10 +122,6 @@ def repeat( def scalar(self) -> bool: ... - @abstractmethod - def get_scalar_array(self) -> npt.NDArray[Any]: - ... - @abstractmethod def get_item(self, key: Any) -> NumPyThunk: ... @@ -166,9 +157,7 @@ def fill(self, value: Any) -> None: ... @abstractmethod - def transpose( - self, axes: Union[None, tuple[int, ...], list[int]] - ) -> NumPyThunk: + def transpose(self, axes: Union[tuple[int, ...], list[int]]) -> NumPyThunk: ... @abstractmethod @@ -640,7 +629,7 @@ def unary_op( op: UnaryOpCode, rhs: Any, where: Any, - args: Any, + args: tuple[Scalar, ...] = (), multiout: Optional[Any] = None, ) -> None: ... @@ -654,7 +643,7 @@ def unary_reduction( orig_axis: Union[int, None], axes: tuple[int, ...], keepdims: bool, - args: Any, + args: tuple[Scalar, ...], initial: Any, ) -> None: ... @@ -667,7 +656,12 @@ def isclose( @abstractmethod def binary_op( - self, op: BinaryOpCode, rhs1: Any, rhs2: Any, where: Any, args: Any + self, + op: BinaryOpCode, + rhs1: Any, + rhs2: Any, + where: Any, + args: tuple[Scalar, ...], ) -> None: ... @@ -678,7 +672,7 @@ def binary_reduction( rhs1: Any, rhs2: Any, broadcast: Union[NdShape, None], - args: Any, + args: tuple[Scalar, ...], ) -> None: ... diff --git a/cunumeric/utils.py b/cunumeric/utils.py index 93e45fb740..ab4f2d05a7 100644 --- a/cunumeric/utils.py +++ b/cunumeric/utils.py @@ -48,7 +48,7 @@ def is_supported_type(dtype: Union[str, np.dtype[Any]]) -> bool: return np.dtype(dtype) in SUPPORTED_DTYPES -def to_core_dtype(dtype: Union[str, np.dtype[Any]]) -> ty.Dtype: +def to_core_dtype(dtype: Union[str, np.dtype[Any]]) -> ty.Type: core_dtype = SUPPORTED_DTYPES.get(np.dtype(dtype)) if core_dtype is None: raise TypeError(f"cuNumeric does not support dtype={dtype}") diff --git a/examples/benchmark.py b/examples/benchmark.py index 1d0944e3b5..434c76a9e5 100644 --- a/examples/benchmark.py +++ b/examples/benchmark.py @@ -35,18 +35,18 @@ def stop(self): class CuNumericTimer(Timer): def __init__(self): - self._start_future = None + self._start_time = None def start(self): from legate.timing import time - self._start_future = time() + self._start_time = time("us") def stop(self): from legate.timing import time - end_future = time() - return (end_future - self._start_future) / 1000.0 + end_future = time("us") + return (end_future - self._start_time) / 1000.0 class CuPyTimer(Timer): diff --git a/examples/ingest.py b/examples/ingest.py deleted file mode 100644 index 12477983fb..0000000000 --- a/examples/ingest.py +++ /dev/null @@ -1,96 +0,0 @@ -#!/usr/bin/env python - -# Copyright 2021-2022 NVIDIA Corporation -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# - -import argparse -import os -from glob import glob - -import tifffile as tfl -from legate.core import CustomSplit, Rect, TiledSplit, ingest, uint16 - -import cunumeric as np - -parser = argparse.ArgumentParser() -parser.add_argument( - "scans_dir", -) -parser.add_argument( - "-c", - "--colors", - type=int, - nargs="+", - default=[3, 2, 2, 1], -) -parser.add_argument("--custom-partitioning", action="store_true") -parser.add_argument("--custom-sharding", action="store_true") -args = parser.parse_args() -dtype = uint16 -tile_shape = (1, 301, 704, 360) -colors = tuple(args.colors) -shape = tuple(ci * di for (ci, di) in zip(colors, tile_shape)) - - -def get_subdomain(color): - return Rect( - lo=[ci * di for (ci, di) in zip(color, tile_shape)], - hi=[(ci + 1) * di for (ci, di) in zip(color, tile_shape)], - ) - - -def get_buffer(color): - (channel, xtile, ytile, ztile) = color - fnames = glob( - os.path.join( - args.scans_dir, - "Scan1_NC_Iter_0_" - + f"ch{channel}_*_{xtile:03}x_{ytile:03}y_{ztile:03}z_0000t.tif", - ) - ) - assert len(fnames) == 1 - im = tfl.imread(fnames[0]) - assert tile_shape == (1,) + im.shape # channel dimension is implicit - return im.reshape(tile_shape).data - - -def get_local_colors(): - # Assumes we were launched using mpirun - num_ranks = int(os.environ["OMPI_COMM_WORLD_SIZE"]) - rank = int(os.environ["OMPI_COMM_WORLD_RANK"]) - res = [] - i = 0 - for color in Rect(colors): - if i % num_ranks == rank: - res.append(color) - i += 1 - return res - - -data_split = ( - CustomSplit(get_subdomain) - if args.custom_partitioning - else TiledSplit(tile_shape) -) -tab = ingest( - dtype, - shape, - colors, - data_split, - get_buffer, - get_local_colors if args.custom_sharding else None, -) -arr = np.array(tab) -b = arr + arr diff --git a/examples/kmeans_sort.py b/examples/kmeans_sort.py deleted file mode 100644 index a2f377e3af..0000000000 --- a/examples/kmeans_sort.py +++ /dev/null @@ -1,228 +0,0 @@ -#!/usr/bin/env python - -# Copyright 2021-2022 NVIDIA Corporation -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# - -# Derived from https://github.com/bryancatanzaro/kmeans - -import argparse - -from benchmark import parse_args, run_benchmark - - -def initialize(N, D, C, T): - # Uncomment this if we want execution to be deterministic - # np.random.seed(0) - data = np.random.random((N, D)).astype(T) - # Since points are random, we'll just generate some random centers - centroids = np.random.random((C, D)).astype(T) - return data, centroids - - -def calculate_distances(data, centroids, data_dots): - centroid_dots = np.square(np.linalg.norm(centroids, ord=2, axis=1)) - pairwise_distances = ( - data_dots[:, np.newaxis] + centroid_dots[np.newaxis, :] - ) - # ||x-y||^2 = ||x||^2 + ||y||^2 - 2 x . y - # pairwise_distances has ||x||^2 + ||y||^2, so beta = 1 - # The gemm calculates x.y for all x and y, so alpha = -2.0 - pairwise_distances -= 2.0 * np.dot(data, centroids.T) - return pairwise_distances - - -def relabel(pairwise_distances, data_index): - new_labels = np.argmin(pairwise_distances, axis=1) - distances = pairwise_distances[data_index, new_labels] - return new_labels, distances - - -def find_centroids(data, labels, C, D): - # Sort the points by their labels - indices = np.argsort(labels) - sorted_points = data[indices] - # Compute counts and indexes for ending of sets of points for each centroid - counts = np.bincount(labels, minlength=C) - indexes = np.cumsum(counts) - # Now we can use the indexes to split the array into sub-arrays and then - # sum across them to create the centroids - centroids = np.empty((C, D), dtype=data.dtype) - ragged_arrays = np.split(sorted_points, indexes) - for idx in range(C): - centroids[idx, :] = np.sum(ragged_arrays[idx], axis=0) - # To avoid introducing divide by zero errors - # If a centroid has no weight, we'll do no normalization - # This will keep its coordinates defined. - counts = np.maximum(counts, 1) - return centroids / counts[:, np.newaxis] - - -def run_kmeans(C, D, T, I, N, S, benchmarking): # noqa: E741 - print("Running kmeans...") - print("Number of data points: " + str(N)) - print("Number of dimensions: " + str(D)) - print("Number of centroids: " + str(C)) - print("Max iterations: " + str(I)) - timer.start() - data, centroids = initialize(N, D, C, T) - - data_dots = np.square(np.linalg.norm(data, ord=2, axis=1)) - data_index = np.linspace(0, N - 1, N, dtype=np.int) - - labels = None - iteration = 0 - prior_distance_sum = None - # We run for max iterations or until we converge - # We only test convergence every S iterations - while iteration < I: - pairwise_distances = calculate_distances(data, centroids, data_dots) - - new_labels, distances = relabel(pairwise_distances, data_index) - distance_sum = np.sum(distances) - - centroids = find_centroids(data, new_labels, C, D) - - if iteration > 0 and iteration % S == 0: - changes = np.not_equal(labels, new_labels) - total_changes = np.sum(changes) - delta = distance_sum / prior_distance_sum - print( - "Iteration " - + str(iteration) - + " produced " - + str(total_changes) - + " changes, and total distance is " - + str(distance_sum) - ) - # We ignore the result of the threshold test in the case that we - # are running performance benchmarks to measure performance for a - # certain number of iterations - if delta > 1 - 0.000001 and not benchmarking: - print("Threshold triggered, terminating iterations early") - break - prior_distance_sum = distance_sum - labels = new_labels - iteration += 1 - # This final distance sum also synchronizes the results - print( - "Final distance sum at iteration " - + str(iteration) - + ": " - + str(prior_distance_sum) - ) - total = timer.stop() - print("Elapsed Time: " + str(total) + " ms") - return total - - -if __name__ == "__main__": - parser = argparse.ArgumentParser() - parser.add_argument( - "-c", - "--centers", - type=int, - default=10, - dest="C", - help="number of centroids", - ) - parser.add_argument( - "-d", - "--dims", - type=int, - default=2, - dest="D", - help="number of dimensions for each input data point", - ) - parser.add_argument( - "-m", - "--max-iters", - type=int, - default=1000, - dest="I", - help="maximum number of iterations to run the algorithm for", - ) - parser.add_argument( - "-n", - "--num", - type=int, - default=10, - dest="N", - help="number of elements in the data set in thousands", - ) - parser.add_argument( - "--precision", - type=int, - default=32, - dest="P", - help="precision of the computation in bits", - ) - parser.add_argument( - "-s", - "--sample", - type=int, - default=25, - dest="S", - help="number of iterations between sampling the log likelihood", - ) - - args, np, timer = parse_args(parser) - - if args.P == 16: - run_benchmark( - run_kmeans, - args.benchmark, - "KMEANS(H)", - ( - args.C, - args.D, - np.float16, - args.I, - args.N * 1000, - args.S, - args.benchmark > 1, - ), - ) - elif args.P == 32: - run_benchmark( - run_kmeans, - args.benchmark, - "KMEANS(S)", - ( - args.C, - args.D, - np.float32, - args.I, - args.N * 1000, - args.S, - args.benchmark > 1, - ), - ) - elif args.P == 64: - run_benchmark( - run_kmeans, - args.benchmark, - "KMEANS(D)", - ( - args.C, - args.D, - np.float64, - args.I, - args.N * 1000, - args.S, - args.benchmark > 1, - ), - ) - else: - raise TypeError("Precision must be one of 16, 32, or 64") diff --git a/examples/lstm_full.py b/examples/lstm_full.py deleted file mode 100644 index c39c0c4696..0000000000 --- a/examples/lstm_full.py +++ /dev/null @@ -1,419 +0,0 @@ -#!/usr/bin/env python - -# Copyright 2021-2022 NVIDIA Corporation -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# - -import argparse - -from benchmark import parse_args, run_benchmark - - -class Param: - def __init__(self, name, value): - self.name = name - self.v = value # parameter value - self.d = np.zeros_like(value) # derivative - self.m = np.zeros_like(value) # momentum for AdaGrad - - -class Parameters: - def __init__(self, H_size, X_size, z_size, weight_sd): - self.W_f = Param( - "W_f", np.random.randn(H_size, z_size) * weight_sd + 0.5 - ) - self.b_f = Param("b_f", np.zeros((H_size, 1))) - - self.W_i = Param( - "W_i", np.random.randn(H_size, z_size) * weight_sd + 0.5 - ) - self.b_i = Param("b_i", np.zeros((H_size, 1))) - - self.W_C = Param("W_C", np.random.randn(H_size, z_size) * weight_sd) - self.b_C = Param("b_C", np.zeros((H_size, 1))) - - self.W_o = Param( - "W_o", np.random.randn(H_size, z_size) * weight_sd + 0.5 - ) - self.b_o = Param("b_o", np.zeros((H_size, 1))) - - # For final layer to predict the next character - self.W_v = Param("W_v", np.random.randn(X_size, H_size) * weight_sd) - self.b_v = Param("b_v", np.zeros((X_size, 1))) - - def all(self): - return [ - self.W_f, - self.W_i, - self.W_C, - self.W_o, - self.W_v, - self.b_f, - self.b_i, - self.b_C, - self.b_o, - self.b_v, - ] - - -def sigmoid(x): - return 1 / (1 + np.exp(-x)) - - -def dsigmoid(y): - return y * (1 - y) - - -def tanh(x): - return np.tanh(x) - - -def dtanh(y): - return 1 - y * y - - -def forward(x, h_prev, C_prev, H_size, X_size, p): - assert x.shape == (X_size, 1) - assert h_prev.shape == (H_size, 1) - assert C_prev.shape == (H_size, 1) - - z = np.row_stack((h_prev, x)) - f = sigmoid(np.dot(p.W_f.v, z) + p.b_f.v) - i = sigmoid(np.dot(p.W_i.v, z) + p.b_i.v) - C_bar = tanh(np.dot(p.W_C.v, z) + p.b_C.v) - - C = f * C_prev + i * C_bar - o = sigmoid(np.dot(p.W_o.v, z) + p.b_o.v) - h = o * tanh(C) - - v = np.dot(p.W_v.v, h) + p.b_v.v - y = np.exp(v) / np.sum(np.exp(v)) # softmax - - return z, f, i, C_bar, C, o, h, v, y - - -def backward( - target, - dh_next, - dC_next, - C_prev, - H_size, - X_size, - z, - f, - i, - C_bar, - C, - o, - h, - v, - y, - p, -): - assert z.shape == (X_size + H_size, 1) - assert v.shape == (X_size, 1) - assert y.shape == (X_size, 1) - - for param in [dh_next, dC_next, C_prev, f, i, C_bar, C, o, h]: - assert param.shape == (H_size, 1) - - dv = np.copy(y) - dv[target] -= 1 - - p.W_v.d += np.dot(dv, h.T) - p.b_v.d += dv - - dh = np.dot(p.W_v.v.T, dv) - dh += dh_next - do = dh * tanh(C) - do = dsigmoid(o) * do - p.W_o.d += np.dot(do, z.T) - p.b_o.d += do - - dC = np.copy(dC_next) - dC += dh * o * dtanh(tanh(C)) - dC_bar = dC * i - dC_bar = dtanh(C_bar) * dC_bar - p.W_C.d += np.dot(dC_bar, z.T) - p.b_C.d += dC_bar - - di = dC * C_bar - di = dsigmoid(i) * di - p.W_i.d += np.dot(di, z.T) - p.b_i.d += di - - df = dC * C_prev - df = dsigmoid(f) * df - p.W_f.d += np.dot(df, z.T) - p.b_f.d += df - - dz = ( - np.dot(p.W_f.v.T, df) - + np.dot(p.W_i.v.T, di) - + np.dot(p.W_C.v.T, dC_bar) - + np.dot(p.W_o.v.T, do) - ) - dh_prev = dz[:H_size, :] - dC_prev = f * dC - - return dh_prev, dC_prev - - -def clear_gradients(params): - for p in params.all(): - p.d.fill(0) - - -def clip_gradients(params): - for p in params.all(): - np.clip(p.d, -1, 1, out=p.d) - - -def forward_backward( - inputs, targets, h_prev, C_prev, T_steps, H_size, X_size, parameters -): - # To store the values for each time step - ( - x_s, - z_s, - f_s, - i_s, - ) = ( - {}, - {}, - {}, - {}, - ) - C_bar_s, C_s, o_s, h_s = {}, {}, {}, {} - v_s, y_s = {}, {} - - # Values at t - 1 - h_s[-1] = np.copy(h_prev) - C_s[-1] = np.copy(C_prev) - - loss = 0 - # Loop through time steps - assert len(inputs) == T_steps - for t in range(len(inputs)): - x_s[t] = np.zeros((X_size, 1)) - x_s[t][inputs[t]] = 1 # Input character - - ( - z_s[t], - f_s[t], - i_s[t], - C_bar_s[t], - C_s[t], - o_s[t], - h_s[t], - v_s[t], - y_s[t], - ) = forward( - x_s[t], h_s[t - 1], C_s[t - 1], H_size, X_size, parameters - ) # Forward pass - - loss += -np.log(y_s[t][targets[t], 0]) # Loss for at t - - clear_gradients(parameters) - - dh_next = np.zeros_like(h_s[0]) # dh from the next character - dC_next = np.zeros_like(C_s[0]) # dh from the next character - - for t in reversed(range(len(inputs))): - # Backward pass - dh_next, dC_next = backward( - target=targets[t], - dh_next=dh_next, - dC_next=dC_next, - C_prev=C_s[t - 1], - H_size=H_size, - X_size=X_size, - z=z_s[t], - f=f_s[t], - i=i_s[t], - C_bar=C_bar_s[t], - C=C_s[t], - o=o_s[t], - h=h_s[t], - v=v_s[t], - y=y_s[t], - p=parameters, - ) - - clip_gradients(parameters) - - return loss, h_s[len(inputs) - 1], C_s[len(inputs) - 1] - - -def update_parameters(learning_rate, params): - for p in params.all(): - p.m += p.d * p.d # Calculate sum of gradients - # print(learning_rate * dparam) - p.v += -(learning_rate * p.d / np.sqrt(p.m + 1e-8)) - - -def update_status(iteration, smooth_loss): - print("iter %d, loss %f" % (iteration, smooth_loss)) - - -def run_lstm( - file_name, - H_size, - T_steps, - max_iters, - learning_rate, - weight_sd, - dump, - timing, -): - with open(file_name, "r") as f: - data = f.read() - chars = list(set(data)) - data_size, X_size = len(data), len(chars) - print("data has %d characters, %d unique" % (data_size, X_size)) - char_to_idx = {ch: i for i, ch in enumerate(chars)} - - z_size = H_size + X_size # Size of concatenate(H, X) vector - - parameters = Parameters(H_size, X_size, z_size, weight_sd) - - # Exponential average of loss - # Initialize to a error of a random model - smooth_loss = -np.log(1.0 / X_size) * T_steps - - pointer = 0 - - timer.start() - - for iteration in range(max_iters): - # Reset - if pointer + T_steps >= len(data) or iteration == 0: - g_h_prev = np.zeros((H_size, 1)) - g_C_prev = np.zeros((H_size, 1)) - pointer = 0 - - inputs = [char_to_idx[ch] for ch in data[pointer : pointer + T_steps]] - targets = [ - char_to_idx[ch] for ch in data[pointer + 1 : pointer + T_steps + 1] - ] - - loss, g_h_prev, g_C_prev = forward_backward( - inputs, - targets, - g_h_prev, - g_C_prev, - T_steps, - H_size, - X_size, - parameters, - ) - smooth_loss = smooth_loss * 0.999 + loss * 0.001 - - # Print every hundred steps - if iteration % dump == 0: - update_status(iteration, smooth_loss) - - update_parameters(learning_rate, parameters) - - pointer += T_steps - update_status(max_iters, smooth_loss) - - total = timer.stop() - if timing: - print("Elapsed Time: " + str(total) + " ms") - return total - - -if __name__ == "__main__": - parser = argparse.ArgumentParser() - parser.add_argument( - "-d", - "--dump", - type=int, - default=100, - dest="dump", - help="how many iterations of training between dumping output", - ) - parser.add_argument( - "-f", - "--file", - type=str, - default="input.txt", - dest="file_name", - help="input file name", - ) - parser.add_argument( - "--hidden", - type=int, - default=100, - dest="hidden", - help="size of hidden layer", - ) - parser.add_argument( - "-l", - "--loops", - type=int, - default=10000, - dest="loops", - help="maximum number of training loops to run", - ) - parser.add_argument( - "-r", - "--rate", - type=float, - default=1e-1, - dest="rate", - help="learning rate", - ) - parser.add_argument( - "-s", - "--steps", - type=int, - default=25, - dest="steps", - help="number of time steps (length of the sequence) used for training", - ) - parser.add_argument( - "-t", - "--time", - dest="timing", - action="store_true", - help="perform timing", - ) - parser.add_argument( - "-w", - "--weight", - type=float, - default=0.1, - dest="weight", - help="standard deviation of weights for initialization", - ) - - args, np, timer = parse_args(parser) - - run_benchmark( - run_lstm, - args.benchmark, - "LSTM Full", - ( - args.file_name, - args.hidden, - args.steps, - args.loops, - args.rate, - args.weight, - args.dump, - args.timing, - ), - ) diff --git a/examples/wgrad.py b/examples/wgrad.py deleted file mode 100644 index f4767f2b0e..0000000000 --- a/examples/wgrad.py +++ /dev/null @@ -1,107 +0,0 @@ -#!/usr/bin/env python - -# Copyright 2021-2022 NVIDIA Corporation -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# - -import argparse - -from legate.timing import time - -import cunumeric as np - - -def initialize(C, K, B, H, W): - x = np.random.randn(C, B, H, W) - y = np.random.randn(K, B, H, W) - return x, y - - -def cross_correlate(x, y, C, K, R, S, B, H, W): - dw = np.zeros(shape=(R, S, C, K)) - # cross-correlate images to compute weight gradients - y_pad = np.zeros(shape=(K, B, H + R - 1, W + S - 1)) - y_pad[:, :, R / 2 : -(R / 2), S / 2 : -(S / 2)] = y - for r in range(R): - for s in range(S): - y_shift = y_pad[:, :, r : r + H, s : s + W] - for c in range(C): - for k in range(K): - dw[r, s, c, k] = np.sum( - x[c, :, :, :] * y_shift[k, :, :, :] - ) - return dw - - -def run_wgrad(H=256, W=256, B=32, C=256, K=32, R=5, S=5, timing=False): - start = time() - x, y = initialize(C, K, B, H, W) - _ = cross_correlate(x, y, C, K, R, S, B, H, W) - stop = time() - total = (stop - start) / 1000.0 - if timing: - print("Elapsed Time: " + str(total) + " ms") - return total - - -if __name__ == "__main__": - parser = argparse.ArgumentParser() - parser.add_argument( - "-b", "--batch", type=int, default=32, dest="B", help="batch size" - ) - parser.add_argument( - "--height", - type=int, - default=256, - dest="H", - help="height of images in pixels", - ) - parser.add_argument( - "-i", "--input", type=int, default=256, dest="C", help="input channels" - ) - parser.add_argument( - "-o", - "--output", - type=int, - default=32, - dest="K", - help="output channels", - ) - parser.add_argument( - "-r", - "--radix", - type=int, - default=5, - dest="R", - help="convolution radix", - ) - parser.add_argument( - "-t", - "--time", - dest="timing", - action="store_true", - help="perform timing", - ) - parser.add_argument( - "-w", - "--width", - type=int, - default=256, - dest="W", - help="width of images in pixels", - ) - args = parser.parse_args(parser) - run_wgrad( - args.H, args.W, args.B, args.C, args.K, args.R, args.R, args.timing - ) diff --git a/src/cunumeric/binary/binary_op.h b/src/cunumeric/binary/binary_op.h index 5828cf9d4e..0b45afc043 100644 --- a/src/cunumeric/binary/binary_op.h +++ b/src/cunumeric/binary/binary_op.h @@ -26,7 +26,7 @@ struct BinaryOpArgs { legate::Store in2; legate::Store out; BinaryOpCode op_code; - std::vector args; + std::vector args; }; class BinaryOpTask : public CuNumericTask { diff --git a/src/cunumeric/binary/binary_op_template.inl b/src/cunumeric/binary/binary_op_template.inl index bdde03d484..6d6ee168f3 100644 --- a/src/cunumeric/binary/binary_op_template.inl +++ b/src/cunumeric/binary/binary_op_template.inl @@ -86,8 +86,9 @@ static void binary_op_template(TaskContext& context) auto outputs = context.outputs(); auto& scalars = context.scalars(); - std::vector extra_args; - for (size_t idx = 2; idx < inputs.size(); ++idx) extra_args.push_back(std::move(inputs[idx])); + std::vector extra_args; + extra_args.reserve(scalars.size() - 1); + for (size_t idx = 1; idx < scalars.size(); ++idx) extra_args.emplace_back(scalars[idx]); BinaryOpArgs args{ inputs[0], inputs[1], outputs[0], scalars[0].value(), std::move(extra_args)}; diff --git a/src/cunumeric/binary/binary_op_util.h b/src/cunumeric/binary/binary_op_util.h index d29b8873e0..24dd2383e8 100644 --- a/src/cunumeric/binary/binary_op_util.h +++ b/src/cunumeric/binary/binary_op_util.h @@ -169,7 +169,7 @@ struct BinaryOp { template struct BinaryOp : std::plus> { static constexpr bool valid = true; - BinaryOp(const std::vector& args) {} + BinaryOp(const std::vector&) {} }; template @@ -178,7 +178,7 @@ struct BinaryOp { static constexpr bool valid = legate::is_floating_point::value; __CUDA_HD__ BinaryOp() {} - BinaryOp(const std::vector& args) {} + BinaryOp(const std::vector&) {} constexpr decltype(auto) operator()(const T& a, const T& b) const { @@ -190,7 +190,7 @@ struct BinaryOp { template <> struct BinaryOp { static constexpr bool valid = true; - BinaryOp(const std::vector& args) {} + BinaryOp(const std::vector&) {} __CUDA_HD__ __half operator()(const __half& a, const __half& b) const { @@ -203,7 +203,7 @@ struct BinaryOp { using T = legate::legate_type_of; static constexpr bool valid = std::is_integral::value; - BinaryOp(const std::vector& args) {} + BinaryOp(const std::vector&) {} constexpr decltype(auto) operator()(const T& a, const T& b) const { @@ -216,7 +216,7 @@ struct BinaryOp { using T = legate::legate_type_of; static constexpr bool valid = std::is_integral::value; - BinaryOp(const std::vector& args) {} + BinaryOp(const std::vector&) {} constexpr decltype(auto) operator()(const T& a, const T& b) const { @@ -229,7 +229,7 @@ struct BinaryOp { using T = legate::legate_type_of; static constexpr bool valid = std::is_integral::value; - BinaryOp(const std::vector& args) {} + BinaryOp(const std::vector&) {} constexpr decltype(auto) operator()(const T& a, const T& b) const { @@ -242,7 +242,7 @@ struct BinaryOp { using T = legate::legate_type_of; static constexpr bool valid = legate::is_floating_point::value; __CUDA_HD__ BinaryOp() {} - BinaryOp(const std::vector& args) {} + BinaryOp(const std::vector&) {} constexpr T operator()(const T& a, const T& b) const { @@ -255,7 +255,7 @@ template <> struct BinaryOp { using T = __half; static constexpr bool valid = true; - BinaryOp(const std::vector& args) {} + BinaryOp(const std::vector&) {} __CUDA_HD__ __half operator()(const __half& a, const __half& b) const { @@ -267,7 +267,7 @@ template struct BinaryOp { using T = legate::legate_type_of; static constexpr bool valid = true; - BinaryOp(const std::vector& args) {} + BinaryOp(const std::vector&) {} template ::value>* = nullptr> constexpr double operator()(const T& a, const T& b) const @@ -285,7 +285,7 @@ struct BinaryOp { template struct BinaryOp : std::equal_to> { static constexpr bool valid = true; - BinaryOp(const std::vector& args) {} + BinaryOp(const std::vector&) {} }; template @@ -293,7 +293,7 @@ struct BinaryOp { using T = legate::legate_type_of; static constexpr bool valid = CODE == legate::Type::Code::FLOAT64 or CODE == legate::Type::Code::COMPLEX128; - BinaryOp(const std::vector& args) {} + BinaryOp(const std::vector&) {} constexpr T operator()(const T& a, const T& b) const { @@ -306,7 +306,7 @@ template <> struct BinaryOp { using T = complex; static constexpr bool valid = true; - BinaryOp(const std::vector& args) {} + BinaryOp(const std::vector&) {} __CUDA_HD__ complex operator()(const complex& a, const complex& b) const { @@ -322,7 +322,7 @@ struct BinaryOp { not(CODE == legate::Type::Code::BOOL or legate::is_complex::value); __CUDA_HD__ BinaryOp() {} - BinaryOp(const std::vector& args) {} + BinaryOp(const std::vector&) {} template ::value>* = nullptr> constexpr _T operator()(const _T& a, const _T& b) const @@ -341,7 +341,7 @@ struct BinaryOp { template <> struct BinaryOp { static constexpr bool valid = true; - BinaryOp(const std::vector& args) {} + BinaryOp(const std::vector&) {} __CUDA_HD__ __half operator()(const __half& a, const __half& b) const { return lift(a, b, BinaryOp{}); @@ -376,7 +376,7 @@ template struct BinaryOp { using T = legate::legate_type_of; static constexpr bool valid = std::is_integral::value; - BinaryOp(const std::vector& args) {} + BinaryOp(const std::vector&) {} __CUDA_HD__ T operator()(const T& a, const T& b) const { return _gcd(a, b); } }; @@ -394,7 +394,7 @@ template struct BinaryOp { using T = legate::legate_type_of; static constexpr bool valid = true; - BinaryOp(const std::vector& args) {} + BinaryOp(const std::vector&) {} template ::value and std::is_signed<_T>::value>* = nullptr> @@ -420,26 +420,26 @@ struct BinaryOp { template <> struct BinaryOp { static constexpr bool valid = false; - BinaryOp(const std::vector& args) {} + BinaryOp(const std::vector&) {} }; template <> struct BinaryOp { static constexpr bool valid = false; - BinaryOp(const std::vector& args) {} + BinaryOp(const std::vector&) {} }; template struct BinaryOp : std::greater> { static constexpr bool valid = true; - BinaryOp(const std::vector& args) {} + BinaryOp(const std::vector&) {} }; template struct BinaryOp : std::greater_equal> { static constexpr bool valid = true; - BinaryOp(const std::vector& args) {} + BinaryOp(const std::vector&) {} }; template @@ -448,7 +448,7 @@ struct BinaryOp { static constexpr bool valid = legate::is_floating_point::value; __CUDA_HD__ BinaryOp() {} - BinaryOp(const std::vector& args) {} + BinaryOp(const std::vector&) {} constexpr decltype(auto) operator()(const T& a, const T& b) const { @@ -461,7 +461,7 @@ template <> struct BinaryOp { static constexpr bool valid = true; - BinaryOp(const std::vector& args) {} + BinaryOp(const std::vector&) {} __CUDA_HD__ __half operator()(const __half& a, const __half& b) const { @@ -474,11 +474,11 @@ struct BinaryOp { using VAL = legate::legate_type_of; static constexpr bool valid = true; - BinaryOp(const std::vector& args) + BinaryOp(const std::vector& args) { assert(args.size() == 2); - rtol_ = args[0].scalar(); - atol_ = args[1].scalar(); + rtol_ = args[0].value(); + atol_ = args[1].value(); } template ::value>* = nullptr> @@ -505,7 +505,7 @@ template struct BinaryOp { using T = legate::legate_type_of; static constexpr bool valid = std::is_integral::value; - BinaryOp(const std::vector& args) {} + BinaryOp(const std::vector&) {} template ::value>* = nullptr> __CUDA_HD__ T operator()(const T& a, const T& b) const @@ -540,7 +540,7 @@ template struct BinaryOp { using T = legate::legate_type_of; static constexpr bool valid = legate::is_floating_point::value; - BinaryOp(const std::vector& args) {} + BinaryOp(const std::vector&) {} __CUDA_HD__ T operator()(const T& a, const int32_t& b) const { @@ -553,7 +553,7 @@ template <> struct BinaryOp { using T = __half; static constexpr bool valid = true; - BinaryOp(const std::vector& args) {} + BinaryOp(const std::vector&) {} __CUDA_HD__ T operator()(const T& a, const int32_t& b) const { @@ -567,7 +567,7 @@ struct BinaryOp { using T = legate::legate_type_of; static constexpr bool valid = CODE != legate::Type::Code::BOOL && std::is_integral::value; - BinaryOp(const std::vector& args) {} + BinaryOp(const std::vector&) {} constexpr T operator()(const T& a, const T& b) const { @@ -582,13 +582,13 @@ struct BinaryOp { template struct BinaryOp : std::less> { static constexpr bool valid = true; - BinaryOp(const std::vector& args) {} + BinaryOp(const std::vector&) {} }; template struct BinaryOp : std::less_equal> { static constexpr bool valid = true; - BinaryOp(const std::vector& args) {} + BinaryOp(const std::vector&) {} }; template @@ -597,7 +597,7 @@ struct BinaryOp { static constexpr bool valid = legate::is_floating_point::value; __CUDA_HD__ BinaryOp() {} - BinaryOp(const std::vector& args) {} + BinaryOp(const std::vector&) {} constexpr decltype(auto) operator()(const T& a, const T& b) const { @@ -617,7 +617,7 @@ template <> struct BinaryOp { static constexpr bool valid = true; - BinaryOp(const std::vector& args) {} + BinaryOp(const std::vector&) {} __CUDA_HD__ __half operator()(const __half& a, const __half& b) const { @@ -631,7 +631,7 @@ struct BinaryOp { static constexpr bool valid = legate::is_floating_point::value; __CUDA_HD__ BinaryOp() {} - BinaryOp(const std::vector& args) {} + BinaryOp(const std::vector&) {} constexpr decltype(auto) operator()(const T& a, const T& b) const { @@ -650,7 +650,7 @@ template <> struct BinaryOp { static constexpr bool valid = true; - BinaryOp(const std::vector& args) {} + BinaryOp(const std::vector&) {} __CUDA_HD__ __half operator()(const __half& a, const __half& b) const { @@ -662,7 +662,7 @@ template struct BinaryOp { using T = legate::legate_type_of; static constexpr bool valid = true; - BinaryOp(const std::vector& args) {} + BinaryOp(const std::vector&) {} template ::value>* = nullptr> constexpr bool operator()(const _T& a, const _T& b) const @@ -682,7 +682,7 @@ struct BinaryOp { using T = legate::legate_type_of; static constexpr bool valid = true; - BinaryOp(const std::vector& args) {} + BinaryOp(const std::vector&) {} template ::value>* = nullptr> constexpr bool operator()(const _T& a, const _T& b) const @@ -701,7 +701,7 @@ template struct BinaryOp { using T = legate::legate_type_of; static constexpr bool valid = true; - BinaryOp(const std::vector& args) {} + BinaryOp(const std::vector&) {} template ::value>* = nullptr> constexpr bool operator()(const _T& a, const _T& b) const @@ -720,7 +720,7 @@ template struct BinaryOp { using T = legate::legate_type_of; static constexpr bool valid = true; - BinaryOp(const std::vector& args) {} + BinaryOp(const std::vector&) {} constexpr T operator()(const T& a, const T& b) const { return std::max(a, b); } }; @@ -728,7 +728,7 @@ template struct BinaryOp { using T = legate::legate_type_of; static constexpr bool valid = true; - BinaryOp(const std::vector& args) {} + BinaryOp(const std::vector&) {} constexpr T operator()(const T& a, const T& b) const { return std::min(a, b); } }; @@ -749,7 +749,7 @@ struct BinaryOp { using T = legate::legate_type_of; static constexpr bool valid = true; __CUDA_HD__ BinaryOp() {} - BinaryOp(const std::vector& args) {} + BinaryOp(const std::vector&) {} template ::value and std::is_signed<_T>::value>* = nullptr> @@ -776,7 +776,7 @@ struct BinaryOp { template <> struct BinaryOp { static constexpr bool valid = true; - BinaryOp(const std::vector& args) {} + BinaryOp(const std::vector&) {} __CUDA_HD__ __half operator()(const __half& a, const __half& b) const { return lift(a, b, BinaryOp{}); @@ -786,19 +786,19 @@ struct BinaryOp { template <> struct BinaryOp { static constexpr bool valid = false; - BinaryOp(const std::vector& args) {} + BinaryOp(const std::vector&) {} }; template <> struct BinaryOp { static constexpr bool valid = false; - BinaryOp(const std::vector& args) {} + BinaryOp(const std::vector&) {} }; template struct BinaryOp : std::multiplies> { static constexpr bool valid = true; - BinaryOp(const std::vector& args) {} + BinaryOp(const std::vector&) {} }; template @@ -806,7 +806,7 @@ struct BinaryOp { using T = legate::legate_type_of; static constexpr bool valid = legate::is_floating_point::value; __CUDA_HD__ BinaryOp() {} - BinaryOp(const std::vector& args) {} + BinaryOp(const std::vector&) {} constexpr T operator()(const T& a, const T& b) const { @@ -819,7 +819,7 @@ template <> struct BinaryOp { using T = __half; static constexpr bool valid = true; - BinaryOp(const std::vector& args) {} + BinaryOp(const std::vector&) {} __CUDA_HD__ __half operator()(const __half& a, const __half& b) const { @@ -830,14 +830,14 @@ struct BinaryOp { template struct BinaryOp : std::not_equal_to> { static constexpr bool valid = true; - BinaryOp(const std::vector& args) {} + BinaryOp(const std::vector&) {} }; template struct BinaryOp { using VAL = legate::legate_type_of; static constexpr bool valid = true; - BinaryOp(const std::vector& args) {} + BinaryOp(const std::vector&) {} constexpr VAL operator()(const VAL& a, const VAL& b) const { return std::pow(static_cast(a), static_cast(b)); @@ -847,14 +847,14 @@ struct BinaryOp { template <> struct BinaryOp { static constexpr bool valid = true; - BinaryOp(const std::vector& args) {} + BinaryOp(const std::vector&) {} __CUDA_HD__ __half operator()(const __half& a, const __half& b) const { return pow(a, b); } }; template <> struct BinaryOp { static constexpr bool valid = true; - BinaryOp(const std::vector& args) {} + BinaryOp(const std::vector&) {} __CUDA_HD__ complex operator()(const complex& a, const complex& b) const { return pow(a, b); @@ -864,7 +864,7 @@ struct BinaryOp { template <> struct BinaryOp { static constexpr bool valid = true; - BinaryOp(const std::vector& args) {} + BinaryOp(const std::vector&) {} __CUDA_HD__ complex operator()(const complex& a, const complex& b) const { return pow(a, b); @@ -876,7 +876,7 @@ struct BinaryOp { using T = legate::legate_type_of; static constexpr bool valid = CODE != legate::Type::Code::BOOL && std::is_integral::value; - BinaryOp(const std::vector& args) {} + BinaryOp(const std::vector&) {} constexpr T operator()(const T& a, const T& b) const { return a >> b; } }; @@ -884,7 +884,7 @@ struct BinaryOp { template struct BinaryOp : std::minus> { static constexpr bool valid = true; - BinaryOp(const std::vector& args) {} + BinaryOp(const std::vector&) {} }; template diff --git a/src/cunumeric/binary/binary_red.h b/src/cunumeric/binary/binary_red.h index e5db4c8a83..0b10f0c4af 100644 --- a/src/cunumeric/binary/binary_red.h +++ b/src/cunumeric/binary/binary_red.h @@ -26,7 +26,7 @@ struct BinaryRedArgs { legate::Store in1; legate::Store in2; BinaryOpCode op_code; - std::vector args; + std::vector args; }; class BinaryRedTask : public CuNumericTask { diff --git a/src/cunumeric/binary/binary_red_template.inl b/src/cunumeric/binary/binary_red_template.inl index 57d4d3f415..a6a34731ad 100644 --- a/src/cunumeric/binary/binary_red_template.inl +++ b/src/cunumeric/binary/binary_red_template.inl @@ -89,8 +89,9 @@ static void binary_red_template(TaskContext& context) auto inputs = context.inputs(); auto& scalars = context.scalars(); - std::vector extra_args; - for (size_t idx = 2; idx < inputs.size(); ++idx) extra_args.push_back(std::move(inputs[idx])); + std::vector extra_args; + extra_args.reserve(scalars.size() - 1); + for (size_t idx = 1; idx < scalars.size(); ++idx) extra_args.emplace_back(scalars[idx]); BinaryRedArgs args{context.reduction(0), inputs[0], diff --git a/src/cunumeric/ndarray.cc b/src/cunumeric/ndarray.cc index 0e49a000b1..d111058e82 100644 --- a/src/cunumeric/ndarray.cc +++ b/src/cunumeric/ndarray.cc @@ -463,13 +463,8 @@ void NDArray::arange(double start, double stop, double step) task.add_output(store_); - auto start_value = runtime->create_scalar_store(Scalar(start)); - auto stop_value = runtime->create_scalar_store(Scalar(stop)); - auto step_value = runtime->create_scalar_store(Scalar(step)); - - task.add_input(start_value); - task.add_input(stop_value); - task.add_input(step_value); + task.add_scalar_arg(Scalar(start)); + task.add_scalar_arg(Scalar(step)); runtime->submit(std::move(task)); } diff --git a/src/cunumeric/nullary/arange.h b/src/cunumeric/nullary/arange.h index a8bbb4c927..e875909153 100644 --- a/src/cunumeric/nullary/arange.h +++ b/src/cunumeric/nullary/arange.h @@ -22,9 +22,8 @@ namespace cunumeric { struct ArangeArgs { legate::Store out; - legate::Store start; - legate::Store stop; - legate::Store step; + legate::Scalar start; + legate::Scalar step; }; class ArangeTask : public CuNumericTask { diff --git a/src/cunumeric/nullary/arange_template.inl b/src/cunumeric/nullary/arange_template.inl index 703d733b30..2e0bf82f70 100644 --- a/src/cunumeric/nullary/arange_template.inl +++ b/src/cunumeric/nullary/arange_template.inl @@ -42,8 +42,8 @@ struct ArangeImpl { auto out = args.out.write_accessor(); - const auto start = args.start.scalar(); - const auto step = args.step.scalar(); + const auto start = args.start.value(); + const auto step = args.step.value(); ArangeImplBody{}(out, rect, start, step); } @@ -52,8 +52,7 @@ struct ArangeImpl { template static void arange_template(TaskContext& context) { - auto inputs = context.inputs(); - ArangeArgs args{context.output(0), inputs[0], inputs[1], inputs[2]}; + ArangeArgs args{context.output(0), context.scalar(0), context.scalar(1)}; type_dispatch(args.out.code(), ArangeImpl{}, args); } diff --git a/src/cunumeric/nullary/fill_template.inl b/src/cunumeric/nullary/fill_template.inl index 5190b68836..5181a0372f 100644 --- a/src/cunumeric/nullary/fill_template.inl +++ b/src/cunumeric/nullary/fill_template.inl @@ -79,7 +79,7 @@ static void fill_template(TaskContext& context) auto field_type = args.out.type().as_struct_type().field_type(1); code = field_type.code(); } - double_dispatch(args.out.dim(), code, FillImpl{}, args); + double_dispatch(std::max(args.out.dim(), 1), code, FillImpl{}, args); } } // namespace cunumeric diff --git a/src/cunumeric/random/rand.h b/src/cunumeric/random/rand.h index 5a38293231..86b48aa8d9 100644 --- a/src/cunumeric/random/rand.h +++ b/src/cunumeric/random/rand.h @@ -26,7 +26,7 @@ struct RandArgs { RandGenCode gen_code; uint32_t epoch; legate::DomainPoint strides; - std::vector args; + std::vector args; }; class RandTask : public CuNumericTask { diff --git a/src/cunumeric/random/rand_template.inl b/src/cunumeric/random/rand_template.inl index b0cf83813a..a9487dba0b 100644 --- a/src/cunumeric/random/rand_template.inl +++ b/src/cunumeric/random/rand_template.inl @@ -82,8 +82,8 @@ static void rand_template(TaskContext& context) auto epoch = scalars[1].value(); auto strides = scalars[2].value(); - std::vector extra_args; - for (auto& input : inputs) extra_args.push_back(std::move(input)); + std::vector extra_args; + for (uint32_t idx = 3; idx < scalars.size(); ++idx) extra_args.push_back(scalars[idx]); RandArgs args{outputs[0], gen_code, epoch, strides, std::move(extra_args)}; op_dispatch(args.gen_code, RandDispatch{}, args); diff --git a/src/cunumeric/random/rand_util.h b/src/cunumeric/random/rand_util.h index 08ece02ffd..31458f9c4a 100644 --- a/src/cunumeric/random/rand_util.h +++ b/src/cunumeric/random/rand_util.h @@ -56,7 +56,7 @@ struct RandomGenerator { using RNG = Philox_2x32<10>; static constexpr bool valid = CODE == legate::Type::Code::FLOAT64; - RandomGenerator(uint32_t ep, const std::vector& args) : epoch(ep) {} + RandomGenerator(uint32_t ep, const std::vector& args) : epoch(ep) {} __CUDAPREFIX__ double operator()(uint32_t hi, uint32_t lo) const { @@ -71,7 +71,7 @@ struct RandomGenerator { using RNG = Philox_2x32<10>; static constexpr bool valid = CODE == legate::Type::Code::FLOAT64; - RandomGenerator(uint32_t ep, const std::vector& args) : epoch(ep) {} + RandomGenerator(uint32_t ep, const std::vector& args) : epoch(ep) {} #ifndef __NVCC__ static inline double erfinv(double a) @@ -181,11 +181,11 @@ struct RandomGenerator { static constexpr bool valid = legate::is_integral::value; - RandomGenerator(uint32_t ep, const std::vector& args) : epoch(ep) + RandomGenerator(uint32_t ep, const std::vector& args) : epoch(ep) { assert(args.size() == 2); - lo = args[0].scalar(); - diff = args[1].scalar() - lo; + lo = args[0].value(); + diff = args[1].value() - lo; } __CUDAPREFIX__ double operator()(uint32_t hi_bits, uint32_t lo_bits) const diff --git a/src/cunumeric/unary/scalar_unary_red.h b/src/cunumeric/unary/scalar_unary_red.h index b56d90a617..c6d051853d 100644 --- a/src/cunumeric/unary/scalar_unary_red.h +++ b/src/cunumeric/unary/scalar_unary_red.h @@ -26,7 +26,7 @@ struct ScalarUnaryRedArgs { legate::Store in; UnaryRedCode op_code; legate::DomainPoint shape; - std::vector args; + std::vector args; }; // Unary reduction task that produces scalar results diff --git a/src/cunumeric/unary/scalar_unary_red_template.inl b/src/cunumeric/unary/scalar_unary_red_template.inl index 648f3ac7cb..eaad3d29f6 100644 --- a/src/cunumeric/unary/scalar_unary_red_template.inl +++ b/src/cunumeric/unary/scalar_unary_red_template.inl @@ -60,7 +60,7 @@ struct ScalarUnaryRed { shape = args.shape; out = args.out.reduce_accessor(); - if constexpr (OP_CODE == UnaryRedCode::CONTAINS) { to_find = args.args[0].scalar(); } + if constexpr (OP_CODE == UnaryRedCode::CONTAINS) { to_find = args.args.front().value(); } #if !LegateDefined(LEGATE_BOUNDS_CHECKS) // Check to see if this is dense or not @@ -142,11 +142,10 @@ struct ScalarUnaryRedDispatch { template static void scalar_unary_red_template(TaskContext& context) { - auto inputs = context.inputs(); auto& scalars = context.scalars(); - std::vector extra_args; - for (size_t idx = 1; idx < inputs.size(); ++idx) extra_args.push_back(std::move(inputs[idx])); + std::vector extra_args; + for (size_t idx = 2; idx < scalars.size(); ++idx) extra_args.push_back(scalars[idx]); auto op_code = scalars[0].value(); auto shape = scalars[1].value(); @@ -155,7 +154,8 @@ static void scalar_unary_red_template(TaskContext& context) shape.dim = 1; shape[0] = 1; } - ScalarUnaryRedArgs args{context.reduction(0), inputs[0], op_code, shape, std::move(extra_args)}; + ScalarUnaryRedArgs args{ + context.reduction(0), context.input(0), op_code, shape, std::move(extra_args)}; op_dispatch(args.op_code, ScalarUnaryRedDispatch{}, args); } diff --git a/src/cunumeric/unary/unary_op.h b/src/cunumeric/unary/unary_op.h index bff4199e4d..793778a635 100644 --- a/src/cunumeric/unary/unary_op.h +++ b/src/cunumeric/unary/unary_op.h @@ -25,7 +25,7 @@ struct UnaryOpArgs { legate::Store in; legate::Store out; UnaryOpCode op_code; - std::vector args; + std::vector args; }; struct MultiOutUnaryOpArgs { diff --git a/src/cunumeric/unary/unary_op_template.inl b/src/cunumeric/unary/unary_op_template.inl index cb4f4ea92e..1b0fefc6d0 100644 --- a/src/cunumeric/unary/unary_op_template.inl +++ b/src/cunumeric/unary/unary_op_template.inl @@ -198,8 +198,8 @@ static void unary_op_template(TaskContext& context) break; } default: { - std::vector extra_args; - for (size_t idx = 1; idx < inputs.size(); ++idx) extra_args.push_back(std::move(inputs[idx])); + std::vector extra_args; + for (size_t idx = 1; idx < scalars.size(); ++idx) extra_args.push_back(scalars[idx]); UnaryOpArgs args{inputs[0], outputs[0], op_code, std::move(extra_args)}; op_dispatch(args.op_code, UnaryOpDispatch{}, args); diff --git a/src/cunumeric/unary/unary_op_util.h b/src/cunumeric/unary/unary_op_util.h index a2d0a46a83..68ecf89def 100644 --- a/src/cunumeric/unary/unary_op_util.h +++ b/src/cunumeric/unary/unary_op_util.h @@ -201,7 +201,7 @@ struct UnaryOp { static constexpr bool valid = true; using T = legate::legate_type_of; - UnaryOp(const std::vector& args) {} + UnaryOp(const std::vector& args) {} template ::value>* = nullptr> constexpr decltype(auto) operator()(const _T& x) const @@ -240,7 +240,7 @@ struct UnaryOp { static constexpr bool valid = is_floating_or_complex; using T = legate::legate_type_of; - UnaryOp(const std::vector& args) {} + UnaryOp(const std::vector& args) {} constexpr decltype(auto) operator()(const T& x) const { @@ -254,7 +254,7 @@ struct UnaryOp { static constexpr bool valid = is_floating_or_complex; using T = legate::legate_type_of; - UnaryOp(const std::vector& args) {} + UnaryOp(const std::vector& args) {} constexpr decltype(auto) operator()(const T& x) const { @@ -268,7 +268,7 @@ struct UnaryOp { static constexpr bool valid = true; using T = __half; - UnaryOp(const std::vector& args) {} + UnaryOp(const std::vector& args) {} __CUDA_HD__ __half operator()(const __half& x) const { @@ -282,7 +282,7 @@ struct UnaryOp { static constexpr bool valid = is_floating_or_complex; using T = legate::legate_type_of; - UnaryOp(const std::vector& args) {} + UnaryOp(const std::vector& args) {} constexpr decltype(auto) operator()(const T& x) const { @@ -296,7 +296,7 @@ struct UnaryOp { static constexpr bool valid = is_floating_or_complex; using T = legate::legate_type_of; - UnaryOp(const std::vector& args) {} + UnaryOp(const std::vector& args) {} constexpr decltype(auto) operator()(const T& x) const { @@ -310,7 +310,7 @@ struct UnaryOp { static constexpr bool valid = true; using T = __half; - UnaryOp(const std::vector& args) {} + UnaryOp(const std::vector& args) {} __CUDA_HD__ __half operator()(const __half& x) const { @@ -324,7 +324,7 @@ struct UnaryOp { static constexpr bool valid = is_floating_or_complex; using T = legate::legate_type_of; - UnaryOp(const std::vector& args) {} + UnaryOp(const std::vector& args) {} constexpr decltype(auto) operator()(const T& x) const { @@ -338,7 +338,7 @@ struct UnaryOp { static constexpr bool valid = is_floating_or_complex; using T = legate::legate_type_of; - UnaryOp(const std::vector& args) {} + UnaryOp(const std::vector& args) {} constexpr decltype(auto) operator()(const T& x) const { @@ -352,7 +352,7 @@ struct UnaryOp { static constexpr bool valid = true; using T = __half; - UnaryOp(const std::vector& args) {} + UnaryOp(const std::vector& args) {} __CUDA_HD__ __half operator()(const __half& x) const { @@ -366,7 +366,7 @@ struct UnaryOp { static constexpr bool valid = legate::is_floating_point::value; using T = legate::legate_type_of; - UnaryOp(const std::vector& args) {} + UnaryOp(const std::vector& args) {} constexpr decltype(auto) operator()(const T& x) const { @@ -380,7 +380,7 @@ struct UnaryOp { static constexpr bool valid = true; using T = __half; - UnaryOp(const std::vector& args) {} + UnaryOp(const std::vector& args) {} __CUDA_HD__ __half operator()(const __half& x) const { @@ -394,7 +394,7 @@ struct UnaryOp { static constexpr bool valid = is_floating_point; using T = legate::legate_type_of; - UnaryOp(const std::vector& args) {} + UnaryOp(const std::vector& args) {} constexpr decltype(auto) operator()(const T& x) const { @@ -408,11 +408,10 @@ struct UnaryOp { static constexpr bool valid = true; using T = legate::legate_type_of; - UnaryOp(const std::vector& args) + UnaryOp(const std::vector& args) + : min{args[0].value()}, max{args[1].value()} { assert(args.size() == 2); - min = args[0].scalar(); - max = args[1].scalar(); } constexpr T operator()(const T& x) const { return (x < min) ? min : (x > max) ? max : x; } @@ -426,7 +425,7 @@ struct UnaryOp { using T = legate::legate_type_of; static constexpr bool valid = true; - UnaryOp(const std::vector& args) {} + UnaryOp(const std::vector& args) {} template ::value>* = nullptr> constexpr T operator()(const T& x) const @@ -446,7 +445,7 @@ struct UnaryOp { static constexpr bool valid = true; using T = legate::legate_type_of; - UnaryOp(const std::vector& args) {} + UnaryOp(const std::vector& args) {} constexpr T operator()(const T& x) const { return x; } }; @@ -456,7 +455,7 @@ struct UnaryOp { static constexpr bool valid = is_floating_or_complex; using T = legate::legate_type_of; - UnaryOp(const std::vector& args) {} + UnaryOp(const std::vector& args) {} constexpr decltype(auto) operator()(const T& x) const { @@ -470,7 +469,7 @@ struct UnaryOp { static constexpr bool valid = is_floating_or_complex; using T = legate::legate_type_of; - UnaryOp(const std::vector& args) {} + UnaryOp(const std::vector& args) {} constexpr decltype(auto) operator()(const T& x) const { @@ -484,7 +483,7 @@ struct UnaryOp { static constexpr bool valid = true; using T = __half; - UnaryOp(const std::vector& args) {} + UnaryOp(const std::vector& args) {} __CUDA_HD__ __half operator()(const __half& x) const { @@ -498,7 +497,7 @@ struct UnaryOp { static constexpr bool valid = is_floating_point; using T = legate::legate_type_of; - UnaryOp(const std::vector& args) {} + UnaryOp(const std::vector& args) {} constexpr decltype(auto) operator()(const T& x) const { return x * T{M_PI / 180.0}; } }; @@ -508,7 +507,7 @@ struct UnaryOp { static constexpr bool valid = true; using T = __half; - UnaryOp(const std::vector& args) {} + UnaryOp(const std::vector& args) {} __CUDA_HD__ __half operator()(const __half& x) const { @@ -521,7 +520,7 @@ struct UnaryOp { static constexpr bool valid = true; using T = legate::legate_type_of; - UnaryOp(const std::vector& args) {} + UnaryOp(const std::vector& args) {} constexpr decltype(auto) operator()(const T& x) const { @@ -535,7 +534,7 @@ struct UnaryOp { static constexpr bool valid = is_floating_or_complex; using T = legate::legate_type_of; - UnaryOp(const std::vector& args) {} + UnaryOp(const std::vector& args) {} template ::value>* = nullptr> constexpr T operator()(const T& x) const @@ -562,7 +561,7 @@ struct UnaryOp { static constexpr bool valid = true; using T = __half; - UnaryOp(const std::vector& args) {} + UnaryOp(const std::vector& args) {} __CUDA_HD__ __half operator()(const __half& x) const { @@ -576,7 +575,7 @@ struct UnaryOp { static constexpr bool valid = is_floating_or_complex; using T = legate::legate_type_of; - UnaryOp(const std::vector& args) {} + UnaryOp(const std::vector& args) {} template ::value>* = nullptr> constexpr decltype(auto) operator()(const T& x) const @@ -598,7 +597,7 @@ struct UnaryOp { static constexpr bool valid = true; using T = __half; - UnaryOp(const std::vector& args) {} + UnaryOp(const std::vector& args) {} __CUDA_HD__ __half operator()(const __half& x) const { @@ -612,7 +611,7 @@ struct UnaryOp { static constexpr bool valid = is_floating_point; using T = legate::legate_type_of; - UnaryOp(const std::vector& args) {} + UnaryOp(const std::vector& args) {} constexpr decltype(auto) operator()(const T& x) const { @@ -626,7 +625,7 @@ struct UnaryOp { using T = Argval>; static constexpr bool valid = true; - UnaryOp(const std::vector& args) {} + UnaryOp(const std::vector& args) {} constexpr decltype(auto) operator()(const T& x) const { return x.arg; } }; @@ -636,7 +635,7 @@ struct UnaryOp { using T = legate::legate_type_of; static constexpr bool valid = legate::is_complex_type::value; - UnaryOp(const std::vector& args) {} + UnaryOp(const std::vector& args) {} constexpr decltype(auto) operator()(const T& x) const { return x.imag(); } }; @@ -647,7 +646,7 @@ struct UnaryOp { legate::is_integral::value && CODE != legate::Type::Code::BOOL; using T = legate::legate_type_of; - UnaryOp(const std::vector& args) {} + UnaryOp(const std::vector& args) {} constexpr T operator()(const T& x) const { return ~x; } }; @@ -657,7 +656,7 @@ struct UnaryOp { static constexpr bool valid = true; using T = legate::legate_type_of; - UnaryOp(const std::vector& args) {} + UnaryOp(const std::vector& args) {} template ::value>* = nullptr> constexpr bool operator()(const T& x) const @@ -685,7 +684,7 @@ struct UnaryOp { static constexpr bool valid = true; using T = legate::legate_type_of; - UnaryOp(const std::vector& args) {} + UnaryOp(const std::vector& args) {} template ::value>* = nullptr> constexpr bool operator()(const T& x) const @@ -713,7 +712,7 @@ struct UnaryOp { static constexpr bool valid = true; using T = legate::legate_type_of; - UnaryOp(const std::vector& args) {} + UnaryOp(const std::vector& args) {} template ::value>* = nullptr> constexpr bool operator()(const T& x) const @@ -743,7 +742,7 @@ struct UnaryOp { ; using T = legate::legate_type_of; - UnaryOp(const std::vector& args) {} + UnaryOp(const std::vector& args) {} constexpr decltype(auto) operator()(const T& x) const { @@ -758,7 +757,7 @@ struct UnaryOp { ; using T = legate::legate_type_of; - UnaryOp(const std::vector& args) {} + UnaryOp(const std::vector& args) {} constexpr decltype(auto) operator()(const T& x) const { @@ -772,7 +771,7 @@ struct UnaryOp { static constexpr bool valid = true; using T = __half; - UnaryOp(const std::vector& args) {} + UnaryOp(const std::vector& args) {} __CUDA_HD__ __half operator()(const __half& x) const { @@ -787,7 +786,7 @@ struct UnaryOp { ; using T = legate::legate_type_of; - UnaryOp(const std::vector& args) {} + UnaryOp(const std::vector& args) {} template ::value>* = nullptr> constexpr decltype(auto) operator()(const T& x) const @@ -809,7 +808,7 @@ struct UnaryOp { static constexpr bool valid = true; using T = __half; - UnaryOp(const std::vector& args) {} + UnaryOp(const std::vector& args) {} __CUDA_HD__ __half operator()(const __half& x) const { @@ -824,7 +823,7 @@ struct UnaryOp { ; using T = legate::legate_type_of; - UnaryOp(const std::vector& args) {} + UnaryOp(const std::vector& args) {} template ::value>* = nullptr> constexpr decltype(auto) operator()(const T& x) const @@ -846,7 +845,7 @@ struct UnaryOp { static constexpr bool valid = true; using T = __half; - UnaryOp(const std::vector& args) {} + UnaryOp(const std::vector& args) {} __CUDA_HD__ __half operator()(const __half& x) const { @@ -860,7 +859,7 @@ struct UnaryOp { static constexpr bool valid = true; using T = legate::legate_type_of; - UnaryOp(const std::vector& args) {} + UnaryOp(const std::vector& args) {} template ::value>* = nullptr> constexpr bool operator()(const T& x) const @@ -880,7 +879,7 @@ struct UnaryOp { static constexpr bool valid = true; using T = legate::legate_type_of; - UnaryOp(const std::vector& args) {} + UnaryOp(const std::vector& args) {} constexpr T operator()(const T& x) const { return -x; } }; @@ -890,7 +889,7 @@ struct UnaryOp { static constexpr bool valid = legate::is_floating_point::value; using T = legate::legate_type_of; - UnaryOp(const std::vector& args) {} + UnaryOp(const std::vector& args) {} constexpr T operator()(const T& x) const { return x * 180.0 / M_PI; } }; @@ -900,7 +899,7 @@ struct UnaryOp { static constexpr bool valid = true; using T = __half; - UnaryOp(const std::vector& args) {} + UnaryOp(const std::vector& args) {} __CUDA_HD__ __half operator()(const __half& x) const { @@ -913,7 +912,7 @@ struct UnaryOp { using T = legate::legate_type_of; static constexpr bool valid = legate::is_complex_type::value; - UnaryOp(const std::vector& args) {} + UnaryOp(const std::vector& args) {} constexpr decltype(auto) operator()(const T& x) const { return x.real(); } }; @@ -923,7 +922,7 @@ struct UnaryOp { using T = legate::legate_type_of; static constexpr bool valid = true; - UnaryOp(const std::vector& args) {} + UnaryOp(const std::vector& args) {} constexpr T operator()(const T& x) const { @@ -937,7 +936,7 @@ struct UnaryOp { using T = __half; static constexpr bool valid = true; - UnaryOp(const std::vector& args) {} + UnaryOp(const std::vector& args) {} __CUDA_HD__ __half operator()(const __half& x) const { @@ -950,7 +949,7 @@ struct UnaryOp { static constexpr bool valid = true; using T = legate::legate_type_of; - UnaryOp(const std::vector& args) {} + UnaryOp(const std::vector& args) {} template ::value>* = nullptr> constexpr decltype(auto) operator()(const _T& x) const @@ -970,7 +969,7 @@ struct UnaryOp { static constexpr bool valid = true; using T = __half; - UnaryOp(const std::vector& args) {} + UnaryOp(const std::vector& args) {} __CUDA_HD__ __half operator()(const __half& x) const { @@ -1000,7 +999,7 @@ struct UnaryOp { static constexpr bool valid = true; using T = legate::legate_type_of; - UnaryOp(const std::vector& args) {} + UnaryOp(const std::vector& args) {} template ::value>* = nullptr> constexpr decltype(auto) operator()(const _T& x) const @@ -1024,7 +1023,7 @@ struct UnaryOp { static constexpr bool valid = true; using T = __half; - UnaryOp(const std::vector& args) {} + UnaryOp(const std::vector& args) {} __CUDA_HD__ __half operator()(const __half& x) const { @@ -1037,7 +1036,7 @@ struct UnaryOp { static constexpr bool valid = legate::is_floating_point::value; using T = legate::legate_type_of; - UnaryOp(const std::vector& args) {} + UnaryOp(const std::vector& args) {} constexpr bool operator()(const T& x) const { @@ -1051,7 +1050,7 @@ struct UnaryOp { static constexpr bool valid = true; using T = __half; - UnaryOp(const std::vector& args) {} + UnaryOp(const std::vector& args) {} __CUDA_HD__ bool operator()(const __half& x) const { @@ -1065,7 +1064,7 @@ struct UnaryOp { static constexpr bool valid = is_floating_or_complex; using T = legate::legate_type_of; - UnaryOp(const std::vector& args) {} + UnaryOp(const std::vector& args) {} constexpr decltype(auto) operator()(const T& x) const { @@ -1079,7 +1078,7 @@ struct UnaryOp { static constexpr bool valid = is_floating_or_complex; using T = legate::legate_type_of; - UnaryOp(const std::vector& args) {} + UnaryOp(const std::vector& args) {} constexpr decltype(auto) operator()(const T& x) const { @@ -1093,7 +1092,7 @@ struct UnaryOp { static constexpr bool valid = true; using T = __half; - UnaryOp(const std::vector& args) {} + UnaryOp(const std::vector& args) {} __CUDA_HD__ __half operator()(const __half& x) const { @@ -1107,7 +1106,7 @@ struct UnaryOp { static constexpr bool valid = true; using T = legate::legate_type_of; - UnaryOp(const std::vector& args) {} + UnaryOp(const std::vector& args) {} constexpr T operator()(const T& x) const { return x * x; } }; @@ -1117,7 +1116,7 @@ struct UnaryOp { static constexpr bool valid = true; using T = bool; - UnaryOp(const std::vector& args) {} + UnaryOp(const std::vector& args) {} constexpr bool operator()(const bool& x) const { return x && x; } }; @@ -1127,7 +1126,7 @@ struct UnaryOp { static constexpr bool valid = true; using T = legate::legate_type_of; - UnaryOp(const std::vector& args) {} + UnaryOp(const std::vector& args) {} constexpr decltype(auto) operator()(const T& x) const { @@ -1141,7 +1140,7 @@ struct UnaryOp { static constexpr bool valid = is_floating_or_complex; using T = legate::legate_type_of; - UnaryOp(const std::vector& args) {} + UnaryOp(const std::vector& args) {} constexpr decltype(auto) operator()(const T& x) const { @@ -1155,7 +1154,7 @@ struct UnaryOp { static constexpr bool valid = is_floating_or_complex; using T = legate::legate_type_of; - UnaryOp(const std::vector& args) {} + UnaryOp(const std::vector& args) {} constexpr decltype(auto) operator()(const T& x) const { @@ -1169,7 +1168,7 @@ struct UnaryOp { static constexpr bool valid = legate::is_floating_point::value; using T = legate::legate_type_of; - UnaryOp(const std::vector& args) {} + UnaryOp(const std::vector& args) {} constexpr decltype(auto) operator()(const T& x) const { @@ -1183,7 +1182,7 @@ struct UnaryOp { static constexpr bool valid = true; using T = __half; - UnaryOp(const std::vector& args) {} + UnaryOp(const std::vector& args) {} __CUDA_HD__ __half operator()(const __half& x) const { diff --git a/test.py b/test.py index 50e22ee88a..8dcda54be8 100755 --- a/test.py +++ b/test.py @@ -18,27 +18,10 @@ import sys -from legate.tester import PER_FILE_ARGS, SKIPPED_EXAMPLES from legate.tester.config import Config from legate.tester.test_plan import TestPlan from legate.tester.test_system import TestSystem -SKIPPED_EXAMPLES.update( - { - "examples/ingest.py", - "examples/kmeans_sort.py", - "examples/lstm_full.py", - "examples/wgrad.py", - } -) - -PER_FILE_ARGS.update( - { - "examples/lstm_full.py": ["--file", "resources/lstm_input.txt"], - } -) - - if __name__ == "__main__": config = Config(sys.argv) diff --git a/tests/integration/test_compress.py b/tests/integration/test_compress.py index 7247685e66..a5564363d5 100644 --- a/tests/integration/test_compress.py +++ b/tests/integration/test_compress.py @@ -164,7 +164,10 @@ def test_ndim_out(ndim): shape_new = tuple(shape_list) out_np = np.random.randint(1, 10, shape_new) - out_num = np.random.randint(-10, -1, shape_new) + # FIXME: we should be able to output to a NumPy array once we have + # the full attach support + # out_num = np.random.randint(-10, -1, shape_new) + out_num = num.random.randint(-10, -1, shape_new) np.compress(np_condition, np_arr, axis, out_np) num.compress(num_condition, num_arr, axis, out=out_num) diff --git a/tests/integration/test_ingest.py b/tests/integration/test_ingest.py deleted file mode 100644 index 8ca6bfbcd5..0000000000 --- a/tests/integration/test_ingest.py +++ /dev/null @@ -1,101 +0,0 @@ -# Copyright 2021-2022 NVIDIA Corporation -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# -import numpy as np -import pytest -from legate.core import ( - CustomSplit, - Rect, - TiledSplit, - float64, - get_legion_context, - get_legion_runtime, - ingest, - legion, -) - -import cunumeric as num - -tile_shape = (4, 7) -colors = (5, 3) -shape = tuple(ci * di for (ci, di) in zip(colors, tile_shape)) - - -def get_subdomain(color): - return Rect( - lo=[ci * di for (ci, di) in zip(color, tile_shape)], - hi=[(ci + 1) * di for (ci, di) in zip(color, tile_shape)], - ) - - -def get_buffer(color): - arr = np.zeros(tile_shape) - base = float( - color[0] * tile_shape[0] * shape[1] + color[1] * tile_shape[1] - ) - for i in range(tile_shape[0]): - for j in range(tile_shape[1]): - arr[i, j] = base + shape[1] * i + j - return arr.data - - -def get_local_colors(): - num_shards = legion.legion_runtime_total_shards( - get_legion_runtime(), get_legion_context() - ) - shard = legion.legion_runtime_local_shard( - get_legion_runtime(), get_legion_context() - ) - res = [] - i = 0 - for color in Rect(colors): - if i % num_shards == shard: - res.append(color) - i += 1 - return res - - -def _ingest(custom_partitioning, custom_sharding): - data_split = ( - CustomSplit(get_subdomain) - if custom_partitioning - else TiledSplit(tile_shape) - ) - tab = ingest( - float64, - shape, - colors, - data_split, - get_buffer, - get_local_colors if custom_sharding else None, - ) - return num.array(tab) - - -@pytest.mark.parametrize("custom_sharding", [True, False]) -@pytest.mark.parametrize("custom_partitioning", [True, False]) -def test(custom_partitioning, custom_sharding): - size = 1 - for d in shape: - size *= d - a_np = np.arange(size).reshape(shape) - a_num = _ingest(custom_partitioning, custom_sharding) - assert np.array_equal(a_np, a_num) - assert np.array_equal(a_np, a_num * 1.0) # force a copy - - -if __name__ == "__main__": - import sys - - sys.exit(pytest.main(sys.argv)) diff --git a/tests/integration/test_put.py b/tests/integration/test_put.py index aced0ce643..d00a14ca18 100644 --- a/tests/integration/test_put.py +++ b/tests/integration/test_put.py @@ -154,7 +154,7 @@ def test_ndim_default_mode(ndim): assert np.array_equal(np_arr, num_arr) -INDICES = ([1, 2, 3.2, 100], [[2, 2], [3, 100]], [1], [100]) +INDICES = ([1, 2, 3.2, 100], [[2, 1], [3, 100]], [1], [100]) @pytest.mark.parametrize("ndim", range(1, LEGATE_MAX_DIM + 1)) @@ -200,7 +200,9 @@ def test_indices_out_of_bound(self, indices): values = 10 with pytest.raises(expected_exc): np.put(x_np, indices, values) - with pytest.raises(expected_exc): + # FIXME: Needs full Python exception support + # with pytest.raises(expected_exc): + with pytest.raises(RuntimeError): num.put(x_num, indices, values) @pytest.mark.parametrize( diff --git a/tests/integration/test_put_along_axis.py b/tests/integration/test_put_along_axis.py index 9386f8d92c..8a0ec2c0d4 100644 --- a/tests/integration/test_put_along_axis.py +++ b/tests/integration/test_put_along_axis.py @@ -168,7 +168,9 @@ def test_indices_bad_dims(self, shape): def test_indices_out_of_bound(self, value): ai = num.full((3, 3), value, dtype=int) msg = "out of bounds" - with pytest.raises(IndexError, match=msg): + # FIXME: Needs full Python exception support + # with pytest.raises(IndexError, match=msg): + with pytest.raises(RuntimeError, match=msg): num.put_along_axis(self.a, ai, 100, axis=0) @pytest.mark.parametrize( diff --git a/tests/integration/test_solve.py b/tests/integration/test_solve.py index e9b0e20152..90e9034b74 100644 --- a/tests/integration/test_solve.py +++ b/tests/integration/test_solve.py @@ -115,7 +115,7 @@ def test_solve_with_output(): n = 8 a = np.random.rand(n, n).astype(np.float32) b = np.random.rand(n).astype(np.float32) - output = np.zeros((n,)).astype(np.float32) + output = num.zeros((n,)).astype(np.float32) out = num.linalg.solve(a, b, out=output) @@ -206,7 +206,9 @@ def test_output_mismatched_dtype(self): def test_a_singular_matrix(self): a = num.zeros((self.n, self.n)).astype(np.float64) msg = "Singular matrix" - with pytest.raises(num.linalg.LinAlgError, match=msg): + # FIXME: We need full Python exception support + # with pytest.raises(num.linalg.LinAlgError, match=msg): + with pytest.raises(RuntimeError, match=msg): num.linalg.solve(a, self.b) diff --git a/tests/integration/test_take_along_axis.py b/tests/integration/test_take_along_axis.py index 1597b2ff62..f88f429da1 100644 --- a/tests/integration/test_take_along_axis.py +++ b/tests/integration/test_take_along_axis.py @@ -110,7 +110,9 @@ def test_indices_bad_dims(self, shape): def test_indices_out_of_bound(self, value): ai = num.full((3, 3), value, dtype=int) msg = "out of bounds" - with pytest.raises(IndexError, match=msg): + # FIXME: Need full Python exception support + # with pytest.raises(IndexError, match=msg): + with pytest.raises(RuntimeError, match=msg): num.take_along_axis(self.a, ai, axis=0) @pytest.mark.parametrize( diff --git a/tests/unit/cunumeric/test_config.py b/tests/unit/cunumeric/test_config.py index 5e85ccfde5..2ac1ec0c1f 100644 --- a/tests/unit/cunumeric/test_config.py +++ b/tests/unit/cunumeric/test_config.py @@ -14,7 +14,7 @@ # import pytest -from legate.core import Library +# from legate.core import Library from legate.core.context import Context from mock import patch @@ -31,7 +31,7 @@ class _FakeSO: class TestCuNumericLib: def test___init__(self) -> None: lib = m.CuNumericLib("foo") - assert isinstance(lib, Library) + # assert isinstance(lib, Library) assert lib.name == "foo" assert lib.shared_object is None assert lib.runtime is None From dd021e26b36834c983d1d79046b11d44a1019149 Mon Sep 17 00:00:00 2001 From: Wonchan Lee Date: Tue, 24 Oct 2023 20:19:44 -0700 Subject: [PATCH 108/462] Bump the legate core commit (#38) * Bump the legate core commit * And minor clean-up for the c++ stencil example * Minor fixes for compile issues * Fix for a cuNumeric unit test * Bump the legate core commit again to fetch the alignment fix --- cmake/versions.json | 2 +- cunumeric/config.py | 13 ++++--- examples/cpp/stencil/stencil.cc | 4 +- src/cunumeric/ndarray.cc | 8 ++-- tests/unit/cunumeric/test_config.py | 60 ----------------------------- 5 files changed, 14 insertions(+), 73 deletions(-) diff --git a/cmake/versions.json b/cmake/versions.json index ea1c5fe349..1657a843e5 100644 --- a/cmake/versions.json +++ b/cmake/versions.json @@ -12,7 +12,7 @@ "git_url" : "https://github.com/nv-legate/legate.core.internal.git", "git_shallow": false, "always_download": false, - "git_tag" : "98c4792ab3c33ba399b7b9fbb1251a489c4835e6" + "git_tag" : "a9da2e945f703d574d92c7c444dad046d265811f" } } } diff --git a/cunumeric/config.py b/cunumeric/config.py index dccede3373..e0a0f4bbed 100644 --- a/cunumeric/config.py +++ b/cunumeric/config.py @@ -24,8 +24,6 @@ import cffi # type: ignore import numpy as np -ffi = cffi.FFI() - if TYPE_CHECKING: import numpy.typing as npt @@ -301,6 +299,7 @@ def __init__(self, name: str) -> None: shared_lib_path = self.get_shared_library() assert shared_lib_path is not None header = self.get_c_header() + ffi = cffi.FFI() if header is not None: ffi.cdef(header) # Don't use ffi.dlopen(), because that will call dlclose() @@ -309,11 +308,14 @@ def __init__(self, name: str) -> None: # (e.g. vtable entries, which will be queried for virtual # destructors), causing errors at shutdown. shared_lib = dlopen_no_autoclose(ffi, shared_lib_path) - callback = getattr(shared_lib, "cunumeric_perform_registration") - callback() - self.shared_object = cast(_CunumericSharedLib, shared_lib) + def register(self) -> None: + callback = getattr( + self.shared_object, "cunumeric_perform_registration" + ) + callback() + def get_shared_library(self) -> str: from cunumeric.install_info import libpath @@ -338,6 +340,7 @@ def get_library_extension() -> str: CUNUMERIC_LIB_NAME = "cunumeric" cunumeric_lib = CuNumericLib(CUNUMERIC_LIB_NAME) +cunumeric_lib.register() _cunumeric = cunumeric_lib.shared_object diff --git a/examples/cpp/stencil/stencil.cc b/examples/cpp/stencil/stencil.cc index 242b3e993b..71b9b3365f 100644 --- a/examples/cpp/stencil/stencil.cc +++ b/examples/cpp/stencil/stencil.cc @@ -54,7 +54,7 @@ cunumeric::NDArray initialize(uint64_t N) grid[{slice(), slice(-1, open)}].assign(-273.15); grid[{slice(-1, open), slice()}].assign(-273.15); grid[{slice(0, 1), slice()}].assign(40.0); - return std::move(grid); + return grid; } void stencil(const Config& config) @@ -72,8 +72,6 @@ void stencil(const Config& config) auto average = center + north + east + west + south; auto work = average * legate::Scalar(double(0.2)); center.assign(work); - // print_array(average); - // print_array(center); }; } diff --git a/src/cunumeric/ndarray.cc b/src/cunumeric/ndarray.cc index d111058e82..d4dd7fb22a 100644 --- a/src/cunumeric/ndarray.cc +++ b/src/cunumeric/ndarray.cc @@ -134,7 +134,7 @@ void NDArray::random(int32_t gen_code) auto task = runtime->create_task(CuNumericOpCode::CUNUMERIC_RAND); - auto p_lhs = task.add_output(store_); + task.add_output(store_); task.add_scalar_arg(legate::Scalar(static_cast(RandGenCode::UNIFORM))); task.add_scalar_arg(legate::Scalar(runtime->get_next_random_epoch())); auto strides = compute_strides(shape()); @@ -153,8 +153,8 @@ void NDArray::fill(const Scalar& value, bool argval) auto task = runtime->create_task(CuNumericOpCode::CUNUMERIC_FILL); - auto p_lhs = task.add_output(store_); - auto p_fill_value = task.add_input(fill_value); + task.add_output(store_); + task.add_input(fill_value); task.add_scalar_arg(legate::Scalar(argval)); runtime->submit(std::move(task)); @@ -364,7 +364,7 @@ void NDArray::binary_reduction(int32_t op_code, NDArray rhs1, NDArray rhs2) } auto task = runtime->create_task(CuNumericOpCode::CUNUMERIC_BINARY_RED); - auto p_lhs = task.add_reduction(store_, redop); + task.add_reduction(store_, redop); auto p_rhs1 = task.add_input(rhs1_store); auto p_rhs2 = task.add_input(rhs2_store); task.add_scalar_arg(legate::Scalar(op_code)); diff --git a/tests/unit/cunumeric/test_config.py b/tests/unit/cunumeric/test_config.py index 2ac1ec0c1f..5ff532ca38 100644 --- a/tests/unit/cunumeric/test_config.py +++ b/tests/unit/cunumeric/test_config.py @@ -14,31 +14,14 @@ # import pytest -# from legate.core import Library -from legate.core.context import Context -from mock import patch import cunumeric.config as m # module under test -from cunumeric import runtime - - -class _FakeSO: - CUNUMERIC_MAX_TASKS = 10 - CUNUMERIC_MAX_MAPPERS = 20 - CUNUMERIC_MAX_REDOPS = 30 class TestCuNumericLib: def test___init__(self) -> None: lib = m.CuNumericLib("foo") - # assert isinstance(lib, Library) assert lib.name == "foo" - assert lib.shared_object is None - assert lib.runtime is None - - def test_get_name(self) -> None: - lib = m.CuNumericLib("foo") - assert lib.get_name() == "foo" def test_get_shared_library(self) -> None: lib = m.CuNumericLib("foo") @@ -60,45 +43,6 @@ def test_get_c_header(self) -> None: assert lib.get_c_header() == header - def test_get_registration_callback(self) -> None: - lib = m.CuNumericLib("foo") - assert ( - lib.get_registration_callback() == "cunumeric_perform_registration" - ) - - def test_initialize(self) -> None: - lib = m.CuNumericLib("foo") - lib.initialize(_FakeSO) - assert lib.shared_object == _FakeSO - - # error if runtime already set - lib.runtime = runtime - with pytest.raises(AssertionError): - lib.initialize(_FakeSO) - - def test_set_runtine(self) -> None: - lib = m.CuNumericLib("foo") - - # error if not initialized - with pytest.raises(AssertionError): - lib.set_runtime(runtime) - - lib.initialize(_FakeSO) - lib.set_runtime(runtime) - assert lib.runtime == runtime - - # error if runtime already set - with pytest.raises(AssertionError): - lib.set_runtime(runtime) - - @patch("cunumeric.runtime.destroy") - def test_destroy(self, mock_destroy) -> None: - lib = m.CuNumericLib("foo") - lib.initialize(_FakeSO) - lib.set_runtime(runtime) - lib.destroy() - mock_destroy.assert_called_once_with() - def test_CUNUMERIC_LIB_NAME() -> None: assert m.CUNUMERIC_LIB_NAME == "cunumeric" @@ -108,10 +52,6 @@ def test_cunumeric_lib() -> None: assert isinstance(m.cunumeric_lib, m.CuNumericLib) -def test_cunumeric_context() -> None: - assert isinstance(m.cunumeric_context, Context) - - def test_CuNumericOpCode() -> None: assert set(m.CuNumericOpCode.__members__) == { "ADVANCED_INDEXING", From ded83dd4296e2582d417b8b50c31d711f4886a73 Mon Sep 17 00:00:00 2001 From: Manolis Papadakis Date: Thu, 26 Oct 2023 18:22:01 -0700 Subject: [PATCH 109/462] Fix typos uncovered by compiler warnings (#39) --- src/cunumeric/matrix/contract_template.inl | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/cunumeric/matrix/contract_template.inl b/src/cunumeric/matrix/contract_template.inl index feff5ffed8..4ce3042742 100644 --- a/src/cunumeric/matrix/contract_template.inl +++ b/src/cunumeric/matrix/contract_template.inl @@ -218,11 +218,11 @@ static void contract_template(legate::TaskContext& context) auto code = args.lhs.code(); #ifdef DEBUG_CUNUMERIC - assert(dim = args.rhs1.dim()); - assert(dim = args.rhs2.dim()); - assert(dim = args.lhs_dim_mask.size()); - assert(dim = args.rhs1_dim_mask.size()); - assert(dim = args.rhs2_dim_mask.size()); + assert(dim == args.rhs1.dim()); + assert(dim == args.rhs2.dim()); + assert(dim == static_cast(args.lhs_dim_mask.size())); + assert(dim == static_cast(args.rhs1_dim_mask.size())); + assert(dim == static_cast(args.rhs2_dim_mask.size())); assert(code == args.rhs1.code()); assert(code == args.rhs2.code()); #endif From 91995138a10b159ae32052746d068b378dd2d10f Mon Sep 17 00:00:00 2001 From: Yimo Jiang Date: Wed, 1 Nov 2023 13:29:31 +0800 Subject: [PATCH 110/462] Port cunumeric.transpose() to cpp (#32) * Port cunumeric.transpose() to cpp --- src/cunumeric/ndarray.cc | 15 +++ src/cunumeric/ndarray.h | 1 + src/cunumeric/operators.cc | 5 + src/cunumeric/operators.h | 2 + tests/cpp/integration/test_transpose.cc | 132 ++++++++++++++++++++++++ 5 files changed, 155 insertions(+) create mode 100644 tests/cpp/integration/test_transpose.cc diff --git a/src/cunumeric/ndarray.cc b/src/cunumeric/ndarray.cc index d4dd7fb22a..de6128fd02 100644 --- a/src/cunumeric/ndarray.cc +++ b/src/cunumeric/ndarray.cc @@ -593,6 +593,21 @@ void NDArray::convolve(NDArray input, NDArray filter) runtime->submit(std::move(task)); } +NDArray NDArray::transpose(std::optional> axes) +{ + if (dim() == 1) return NDArray(std::move(store_)); + std::vector v_axes; + if (!axes.has_value()) { + for (int32_t i = dim() - 1; i > -1; --i) v_axes.push_back(i); + } else if (axes.value().size() != dim()) { + throw std::invalid_argument("axes must be the same size as ndim for transpose"); + } else { + v_axes = axes.value(); + } + + return NDArray(store_.transpose(std::move(v_axes))); +} + legate::LogicalStore NDArray::get_store() { return store_; } legate::LogicalStore NDArray::broadcast(const std::vector& shape, diff --git a/src/cunumeric/ndarray.h b/src/cunumeric/ndarray.h index b299a97d65..f6d2c2569c 100644 --- a/src/cunumeric/ndarray.h +++ b/src/cunumeric/ndarray.h @@ -88,6 +88,7 @@ class NDArray { bool argsort = false, std::optional axis = -1, std::string kind = "quicksort"); + NDArray transpose(std::optional> axes = std::nullopt); public: NDArray as_type(const legate::Type& type); diff --git a/src/cunumeric/operators.cc b/src/cunumeric/operators.cc index ed18aa800c..5d56773b0e 100644 --- a/src/cunumeric/operators.cc +++ b/src/cunumeric/operators.cc @@ -321,4 +321,9 @@ NDArray sort(NDArray input, std::optional axis /*=-1*/, std::string kin return result; } +NDArray transpose(NDArray a, std::optional> axes) +{ + return a.transpose(axes); +} + } // namespace cunumeric diff --git a/src/cunumeric/operators.h b/src/cunumeric/operators.h index 2450cbd9ed..c25a244c25 100644 --- a/src/cunumeric/operators.h +++ b/src/cunumeric/operators.h @@ -91,4 +91,6 @@ NDArray convolve(NDArray a, NDArray v); NDArray sort(NDArray input, std::optional axis = -1, std::string kind = "quicksort"); +NDArray transpose(NDArray a, std::optional> axes = std::nullopt); + } // namespace cunumeric diff --git a/tests/cpp/integration/test_transpose.cc b/tests/cpp/integration/test_transpose.cc new file mode 100644 index 0000000000..2ded498b6f --- /dev/null +++ b/tests/cpp/integration/test_transpose.cc @@ -0,0 +1,132 @@ +/* Copyright 2023 NVIDIA Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +#include +#include "legate.h" +#include "cunumeric.h" +#include "util.inl" + +template +void transpose_int32_test(std::array input, + std::array exp, + std::vector in_shape, + std::vector out_shape, + std::optional> axes = std::nullopt) +{ + auto a_input = cunumeric::zeros(in_shape, legate::int32()); + assign_values_to_array(a_input, input.data(), input.size()); + auto a_output = cunumeric::transpose(a_input, axes); + check_array_eq(a_output, exp.data(), exp.size()); + EXPECT_EQ(a_output.shape(), out_shape); +} + +TEST(Transpose, Dim) +{ + const size_t size = 6; + const int32_t dim = 2; + std::array input = {1, 2, 3, 4, 5, 6}; + std::array exp = {1, 4, 2, 5, 3, 6}; + std::vector in_shape = {2, 3}; + std::vector out_shape = {3, 2}; + auto axes = std::nullopt; + + transpose_int32_test(input, exp, in_shape, out_shape, axes); +} + +TEST(Transpose, Axes) +{ + const size_t size = 12; + const int32_t dim = 3; + std::array input = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12}; + std::array exp = {1, 7, 4, 10, 2, 8, 5, 11, 3, 9, 6, 12}; + std::vector in_shape = {2, 2, 3}; + std::vector out_shape = {3, 2, 2}; + auto axes = {2, 1, 0}; + + transpose_int32_test(input, exp, in_shape, out_shape, axes); +} + +TEST(Transpose, EmptyArray) +{ + const size_t size = 0; + const int32_t dim = 1; + std::array input = {}; + std::array exp = input; + std::vector in_shape = {0}; + std::vector out_shape = in_shape; + auto axes = std::nullopt; + + transpose_int32_test(input, exp, in_shape, out_shape, axes); +} + +TEST(Transpose, SingletonAxes) +{ + const size_t size = 6; + const int32_t dim = 1; + std::array input = {1, 2, 3, 4, 5, 6}; + std::array exp = input; + std::vector in_shape = {6}; + std::vector out_shape = in_shape; + auto axes = {1}; + + transpose_int32_test(input, exp, in_shape, out_shape, axes); +} + +TEST(Transpose, Singleton) +{ + const size_t size = 6; + const int32_t dim = 1; + std::array input = {1, 2, 3, 4, 5, 6}; + std::array exp = input; + std::vector in_shape = {6}; + std::vector out_shape = in_shape; + auto axes = std::nullopt; + + transpose_int32_test(input, exp, in_shape, out_shape, axes); +} + +TEST(Transpose, DefaultType) +{ + const size_t size = 6; + const int32_t dim = 2; + std::array input = {1.3, 2, 3.6, 4, 5, 6}; + std::array exp = {1.3, 4, 2, 5, 3.6, 6}; + std::vector in_shape = {2, 3}; + std::vector out_shape = {3, 2}; + auto axes = std::nullopt; + + auto a_input = cunumeric::zeros(in_shape); + assign_values_to_array(a_input, input.data(), input.size()); + auto a_output = cunumeric::transpose(a_input, axes); + check_array_eq(a_output, exp.data(), exp.size()); + EXPECT_EQ(a_output.shape(), out_shape); +} + +TEST(TransposeErrors, InvalidAxes) +{ + const size_t size = 6; + const int32_t dim = 2; + std::array input = {1.3, 2, 3.6, 4, 5, 6}; + std::vector in_shape = {2, 3}; + std::vector out_shape = {3, 2}; + + auto a_input = cunumeric::zeros(in_shape); + assign_values_to_array(a_input, input.data(), input.size()); + EXPECT_THROW(cunumeric::transpose(a_input, (std::vector){0, 1, 2}), std::invalid_argument); + EXPECT_THROW(cunumeric::transpose(a_input, (std::vector){1}), std::invalid_argument); + EXPECT_THROW(cunumeric::transpose(a_input, (std::vector){3, 4}), std::invalid_argument); +} + From 62377211b3cbf7c5f0c2ded277d0eb23c4378faa Mon Sep 17 00:00:00 2001 From: Wonchan Lee Date: Wed, 1 Nov 2023 12:45:01 -0700 Subject: [PATCH 111/462] Fix compile errors (#44) --- src/cunumeric/ndarray.cc | 2 +- src/cunumeric/runtime.cc | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/cunumeric/ndarray.cc b/src/cunumeric/ndarray.cc index de6128fd02..c0accdc4fe 100644 --- a/src/cunumeric/ndarray.cc +++ b/src/cunumeric/ndarray.cc @@ -599,7 +599,7 @@ NDArray NDArray::transpose(std::optional> axes) std::vector v_axes; if (!axes.has_value()) { for (int32_t i = dim() - 1; i > -1; --i) v_axes.push_back(i); - } else if (axes.value().size() != dim()) { + } else if (static_cast(axes.value().size()) != dim()) { throw std::invalid_argument("axes must be the same size as ndim for transpose"); } else { v_axes = axes.value(); diff --git a/src/cunumeric/runtime.cc b/src/cunumeric/runtime.cc index e1bf3ae61b..84ca590bf6 100644 --- a/src/cunumeric/runtime.cc +++ b/src/cunumeric/runtime.cc @@ -43,7 +43,7 @@ NDArray CuNumericRuntime::create_array(const legate::Type& type) NDArray CuNumericRuntime::create_array(std::vector shape, const legate::Type& type) { - auto store = legate_runtime_->create_store(shape, type, true /*optimize_scalar*/); + auto store = legate_runtime_->create_store(legate::Shape{shape}, type, true /*optimize_scalar*/); return NDArray(std::move(store)); } From de5094d8f510245d4ba1ebfa1d378b680c54685f Mon Sep 17 00:00:00 2001 From: Wonchan Lee Date: Tue, 7 Nov 2023 14:06:16 -0800 Subject: [PATCH 112/462] Revert workarounds for the lack of Python exception support in the core (#41) * Revert workarounds for the lack of Python exception support in the core * Update the legate core commit hash --- cmake/versions.json | 2 +- cunumeric/deferred.py | 8 ++---- cunumeric/eager.py | 34 ++++++----------------- cunumeric/linalg/cholesky.py | 12 +++----- cunumeric/linalg/solve.py | 7 ++--- tests/integration/test_compress.py | 5 +--- tests/integration/test_put.py | 4 +-- tests/integration/test_put_along_axis.py | 4 +-- tests/integration/test_solve.py | 4 +-- tests/integration/test_take_along_axis.py | 4 +-- 10 files changed, 22 insertions(+), 62 deletions(-) diff --git a/cmake/versions.json b/cmake/versions.json index 1657a843e5..7fe414357a 100644 --- a/cmake/versions.json +++ b/cmake/versions.json @@ -12,7 +12,7 @@ "git_url" : "https://github.com/nv-legate/legate.core.internal.git", "git_shallow": false, "always_download": false, - "git_tag" : "a9da2e945f703d574d92c7c444dad046d265811f" + "git_tag" : "4b34a2088709e654e79694ad035c30c914d525f7" } } } diff --git a/cunumeric/deferred.py b/cunumeric/deferred.py index 6e1636eaa0..8e46b08593 100644 --- a/cunumeric/deferred.py +++ b/cunumeric/deferred.py @@ -442,9 +442,7 @@ def _zip_indices( task = legate_runtime.create_auto_task( self.library, CuNumericOpCode.ZIP ) - # TODO: We need to put back the precise Python exception support - # task.throws_exception(IndexError) - task.throws_exception(True) + task.throws_exception(IndexError) p_out = task.add_output(output_arr.base) task.add_scalar_arg(self.ndim, ty.int64) # N of points in Point task.add_scalar_arg(key_dim, ty.int64) # key_dim @@ -1812,9 +1810,7 @@ def put(self, indices: Any, values: Any, check_bounds: bool) -> None: task.add_scalar_arg(check_bounds, ty.bool_) p_indices = task.add_input(indices.base) task.add_constraint(align(p_indices, p_indirect)) - # TODO: We need to put back the precise Python exception support - # task.throws_exception(IndexError) - task.throws_exception(True) + task.throws_exception(IndexError) task.execute() if indirect.base.has_scalar_storage: indirect = indirect._convert_future_to_regionfield() diff --git a/cunumeric/eager.py b/cunumeric/eager.py index 097ba2226b..60341a97e6 100644 --- a/cunumeric/eager.py +++ b/cunumeric/eager.py @@ -420,12 +420,7 @@ def get_item(self, key: Any) -> NumPyThunk: return self.deferred.get_item(key) if is_advanced_indexing(key): index_key = self._create_indexing_key(key) - # FIXME: Need to raise RuntimeError instead of IndexError to be - # consistent with the DeferredArray implementation - try: - out = self.array[index_key] - except IndexError as e: - raise RuntimeError(e) from e + out = self.array[index_key] result = EagerArray(self.runtime, out) else: child = self.array[key] @@ -442,15 +437,10 @@ def set_item(self, key: Any, value: Any) -> None: else: if is_advanced_indexing(key): index_key = self._create_indexing_key(key) - # FIXME: Need to raise RuntimeError instead of IndexError to be - # consistent with the DeferredArray implementation - try: - if isinstance(value, EagerArray): - self.array[index_key] = value.array - else: - self.array[index_key] = value - except IndexError as e: - raise RuntimeError(e) from e + if isinstance(value, EagerArray): + self.array[index_key] = value.array + else: + self.array[index_key] = value else: if isinstance(value, EagerArray): self.array[key] = value.array @@ -656,12 +646,7 @@ def put(self, indices: Any, values: Any, check_bounds: bool) -> None: if self.deferred is not None: self.deferred.put(indices, values, check_bounds) else: - # FIXME: Need to raise RuntimeError instead of IndexError to be - # consistent with the DeferredArray implementation - try: - np.put(self.array, indices.array, values.array) - except IndexError as e: - raise RuntimeError(e) from e + np.put(self.array, indices.array, values.array) def putmask(self, mask: Any, values: Any) -> None: self.check_eager_args(mask, values) @@ -1652,12 +1637,9 @@ def solve(self, a: Any, b: Any) -> None: try: result = np.linalg.solve(a.array, b.array) except np.linalg.LinAlgError as e: - # from .linalg import LinAlgError + from .linalg import LinAlgError - # FIXME: Use RuntimeError for now to be consistent with the - # DeferredArray implementation - # raise LinAlgError(e) from e - raise RuntimeError(e) from e + raise LinAlgError(e) from e self.array[:] = result def scan( diff --git a/cunumeric/linalg/cholesky.py b/cunumeric/linalg/cholesky.py index febe10f308..e36868df9d 100644 --- a/cunumeric/linalg/cholesky.py +++ b/cunumeric/linalg/cholesky.py @@ -20,12 +20,12 @@ from cunumeric.config import CuNumericOpCode +from .exception import LinAlgError + # from legate.core.shape import Shape # from legate.settings import settings -# from .exception import LinAlgError - legate_runtime = get_legate_runtime() if TYPE_CHECKING: @@ -79,9 +79,7 @@ def transpose_copy( def potrf_single(library: Library, output: LogicalStore) -> None: task = legate_runtime.create_auto_task(library, CuNumericOpCode.POTRF) - # TODO: We need to put back the precise Python exception support - # task.throws_exception(LinAlgError) - task.throws_exception(True) + task.throws_exception(LinAlgError) task.add_output(output) task.add_input(output) task.execute() @@ -92,9 +90,7 @@ def potrf_single(library: Library, output: LogicalStore) -> None: # task = legate_runtime.create_manual_task( # library, CuNumericOpCode.POTRF, launch_domain # ) -# # TODO: We need to put back the precise Python exception support -# # task.throws_exception(LinAlgError) -# task.throws_exception(True) +# task.throws_exception(LinAlgError) # task.add_output(p_output) # task.add_input(p_output) # task.execute() diff --git a/cunumeric/linalg/solve.py b/cunumeric/linalg/solve.py index c64fd45967..c716f90056 100644 --- a/cunumeric/linalg/solve.py +++ b/cunumeric/linalg/solve.py @@ -21,8 +21,7 @@ from cunumeric.config import CuNumericOpCode from .cholesky import transpose_copy_single - -# from .exception import LinAlgError +from .exception import LinAlgError if TYPE_CHECKING: from legate.core import Library, LogicalStore @@ -34,9 +33,7 @@ def solve_single(library: Library, a: LogicalStore, b: LogicalStore) -> None: task = get_legate_runtime().create_auto_task( library, CuNumericOpCode.SOLVE ) - # TODO: We need to put back the precise Python exception support - # task.throws_exception(LinAlgError) - task.throws_exception(True) + task.throws_exception(LinAlgError) p_a = task.add_input(a) p_b = task.add_input(b) task.add_output(a, p_a) diff --git a/tests/integration/test_compress.py b/tests/integration/test_compress.py index a5564363d5..7247685e66 100644 --- a/tests/integration/test_compress.py +++ b/tests/integration/test_compress.py @@ -164,10 +164,7 @@ def test_ndim_out(ndim): shape_new = tuple(shape_list) out_np = np.random.randint(1, 10, shape_new) - # FIXME: we should be able to output to a NumPy array once we have - # the full attach support - # out_num = np.random.randint(-10, -1, shape_new) - out_num = num.random.randint(-10, -1, shape_new) + out_num = np.random.randint(-10, -1, shape_new) np.compress(np_condition, np_arr, axis, out_np) num.compress(num_condition, num_arr, axis, out=out_num) diff --git a/tests/integration/test_put.py b/tests/integration/test_put.py index d00a14ca18..fb945404a4 100644 --- a/tests/integration/test_put.py +++ b/tests/integration/test_put.py @@ -200,9 +200,7 @@ def test_indices_out_of_bound(self, indices): values = 10 with pytest.raises(expected_exc): np.put(x_np, indices, values) - # FIXME: Needs full Python exception support - # with pytest.raises(expected_exc): - with pytest.raises(RuntimeError): + with pytest.raises(expected_exc): num.put(x_num, indices, values) @pytest.mark.parametrize( diff --git a/tests/integration/test_put_along_axis.py b/tests/integration/test_put_along_axis.py index 8a0ec2c0d4..9386f8d92c 100644 --- a/tests/integration/test_put_along_axis.py +++ b/tests/integration/test_put_along_axis.py @@ -168,9 +168,7 @@ def test_indices_bad_dims(self, shape): def test_indices_out_of_bound(self, value): ai = num.full((3, 3), value, dtype=int) msg = "out of bounds" - # FIXME: Needs full Python exception support - # with pytest.raises(IndexError, match=msg): - with pytest.raises(RuntimeError, match=msg): + with pytest.raises(IndexError, match=msg): num.put_along_axis(self.a, ai, 100, axis=0) @pytest.mark.parametrize( diff --git a/tests/integration/test_solve.py b/tests/integration/test_solve.py index 90e9034b74..c8d11032c6 100644 --- a/tests/integration/test_solve.py +++ b/tests/integration/test_solve.py @@ -206,9 +206,7 @@ def test_output_mismatched_dtype(self): def test_a_singular_matrix(self): a = num.zeros((self.n, self.n)).astype(np.float64) msg = "Singular matrix" - # FIXME: We need full Python exception support - # with pytest.raises(num.linalg.LinAlgError, match=msg): - with pytest.raises(RuntimeError, match=msg): + with pytest.raises(num.linalg.LinAlgError, match=msg): num.linalg.solve(a, self.b) diff --git a/tests/integration/test_take_along_axis.py b/tests/integration/test_take_along_axis.py index f88f429da1..1597b2ff62 100644 --- a/tests/integration/test_take_along_axis.py +++ b/tests/integration/test_take_along_axis.py @@ -110,9 +110,7 @@ def test_indices_bad_dims(self, shape): def test_indices_out_of_bound(self, value): ai = num.full((3, 3), value, dtype=int) msg = "out of bounds" - # FIXME: Need full Python exception support - # with pytest.raises(IndexError, match=msg): - with pytest.raises(RuntimeError, match=msg): + with pytest.raises(IndexError, match=msg): num.take_along_axis(self.a, ai, axis=0) @pytest.mark.parametrize( From 8fc9d149af9a9f058ef8acb386ed2d39d152d5c6 Mon Sep 17 00:00:00 2001 From: Yimo Jiang Date: Wed, 8 Nov 2023 09:51:15 +0800 Subject: [PATCH 113/462] Use overload for axes instead of std::optional (#47) --- src/cunumeric/ndarray.cc | 20 +++++++++++--------- src/cunumeric/ndarray.h | 3 ++- src/cunumeric/operators.cc | 7 +++---- src/cunumeric/operators.h | 4 +++- tests/cpp/integration/test_transpose.cc | 15 ++++++++++----- 5 files changed, 29 insertions(+), 20 deletions(-) diff --git a/src/cunumeric/ndarray.cc b/src/cunumeric/ndarray.cc index c0accdc4fe..150aa9e7d3 100644 --- a/src/cunumeric/ndarray.cc +++ b/src/cunumeric/ndarray.cc @@ -593,19 +593,21 @@ void NDArray::convolve(NDArray input, NDArray filter) runtime->submit(std::move(task)); } -NDArray NDArray::transpose(std::optional> axes) +NDArray NDArray::transpose() { if (dim() == 1) return NDArray(std::move(store_)); - std::vector v_axes; - if (!axes.has_value()) { - for (int32_t i = dim() - 1; i > -1; --i) v_axes.push_back(i); - } else if (static_cast(axes.value().size()) != dim()) { + std::vector axes; + for (int32_t i = dim() - 1; i > -1; --i) axes.push_back(i); + return transpose(axes); +} + +NDArray NDArray::transpose(std::vector axes) +{ + if (dim() == 1) return NDArray(std::move(store_)); + if (axes.size() != dim()) { throw std::invalid_argument("axes must be the same size as ndim for transpose"); - } else { - v_axes = axes.value(); } - - return NDArray(store_.transpose(std::move(v_axes))); + return NDArray(store_.transpose(std::move(axes))); } legate::LogicalStore NDArray::get_store() { return store_; } diff --git a/src/cunumeric/ndarray.h b/src/cunumeric/ndarray.h index f6d2c2569c..3962af93e7 100644 --- a/src/cunumeric/ndarray.h +++ b/src/cunumeric/ndarray.h @@ -88,7 +88,8 @@ class NDArray { bool argsort = false, std::optional axis = -1, std::string kind = "quicksort"); - NDArray transpose(std::optional> axes = std::nullopt); + NDArray transpose(); + NDArray transpose(std::vector axes); public: NDArray as_type(const legate::Type& type); diff --git a/src/cunumeric/operators.cc b/src/cunumeric/operators.cc index 5d56773b0e..ccf10234f6 100644 --- a/src/cunumeric/operators.cc +++ b/src/cunumeric/operators.cc @@ -321,9 +321,8 @@ NDArray sort(NDArray input, std::optional axis /*=-1*/, std::string kin return result; } -NDArray transpose(NDArray a, std::optional> axes) -{ - return a.transpose(axes); -} +NDArray transpose(NDArray a) { return a.transpose(); } + +NDArray transpose(NDArray a, std::vector axes) { return a.transpose(axes); } } // namespace cunumeric diff --git a/src/cunumeric/operators.h b/src/cunumeric/operators.h index c25a244c25..71ef5c27c2 100644 --- a/src/cunumeric/operators.h +++ b/src/cunumeric/operators.h @@ -91,6 +91,8 @@ NDArray convolve(NDArray a, NDArray v); NDArray sort(NDArray input, std::optional axis = -1, std::string kind = "quicksort"); -NDArray transpose(NDArray a, std::optional> axes = std::nullopt); +NDArray transpose(NDArray a); + +NDArray transpose(NDArray a, std::vector axes); } // namespace cunumeric diff --git a/tests/cpp/integration/test_transpose.cc b/tests/cpp/integration/test_transpose.cc index 2ded498b6f..27291ca595 100644 --- a/tests/cpp/integration/test_transpose.cc +++ b/tests/cpp/integration/test_transpose.cc @@ -28,7 +28,13 @@ void transpose_int32_test(std::array input, { auto a_input = cunumeric::zeros(in_shape, legate::int32()); assign_values_to_array(a_input, input.data(), input.size()); - auto a_output = cunumeric::transpose(a_input, axes); + + auto a_output = cunumeric::array(out_shape, legate::int32()); + + if (axes) + a_output = cunumeric::transpose(a_input, axes.value()); + else + a_output = cunumeric::transpose(a_input); check_array_eq(a_output, exp.data(), exp.size()); EXPECT_EQ(a_output.shape(), out_shape); } @@ -106,11 +112,10 @@ TEST(Transpose, DefaultType) std::array exp = {1.3, 4, 2, 5, 3.6, 6}; std::vector in_shape = {2, 3}; std::vector out_shape = {3, 2}; - auto axes = std::nullopt; auto a_input = cunumeric::zeros(in_shape); assign_values_to_array(a_input, input.data(), input.size()); - auto a_output = cunumeric::transpose(a_input, axes); + auto a_output = cunumeric::transpose(a_input); check_array_eq(a_output, exp.data(), exp.size()); EXPECT_EQ(a_output.shape(), out_shape); } @@ -125,8 +130,8 @@ TEST(TransposeErrors, InvalidAxes) auto a_input = cunumeric::zeros(in_shape); assign_values_to_array(a_input, input.data(), input.size()); - EXPECT_THROW(cunumeric::transpose(a_input, (std::vector){0, 1, 2}), std::invalid_argument); + EXPECT_THROW(cunumeric::transpose(a_input, (std::vector){0, 1, 2}), + std::invalid_argument); EXPECT_THROW(cunumeric::transpose(a_input, (std::vector){1}), std::invalid_argument); EXPECT_THROW(cunumeric::transpose(a_input, (std::vector){3, 4}), std::invalid_argument); } - From 2d4dd2882f5385ffc3af817183ce16f8b6a280f5 Mon Sep 17 00:00:00 2001 From: Shijie Chen <25165513+shijie-nv@users.noreply.github.com> Date: Thu, 9 Nov 2023 17:14:18 +0800 Subject: [PATCH 114/462] Port moveaxis and add tests. (#42) * Port moveaxis and add tests * Refactor normalize_axis_index and normalize_axis_vector * Added tests to moveaxis for single element and empty array * Added tests to moveaxis for empty shapes * Added tests to moveaxis for different shapes (up to 7d) --- src/cunumeric/ndarray.cc | 20 +--- src/cunumeric/ndarray.h | 1 - src/cunumeric/operators.cc | 47 ++++++++ src/cunumeric/operators.h | 9 ++ tests/cpp/integration/test_moveaxis.cc | 150 +++++++++++++++++++++++++ 5 files changed, 210 insertions(+), 17 deletions(-) create mode 100644 tests/cpp/integration/test_moveaxis.cc diff --git a/src/cunumeric/ndarray.cc b/src/cunumeric/ndarray.cc index 150aa9e7d3..3dc5db3856 100644 --- a/src/cunumeric/ndarray.cc +++ b/src/cunumeric/ndarray.cc @@ -207,18 +207,6 @@ void NDArray::bincount(NDArray rhs, std::optional weights /*=std::nullo runtime->submit(std::move(task)); } -int32_t NDArray::normalize_axis_index(int32_t axis) -{ - auto ndim = dim(); - std::string err_msg = "The input axis is out of the bounds"; - if (axis > ndim) throw std::invalid_argument(std::move(err_msg)); - - auto updated_axis = axis >= 0 ? axis : ndim + axis; - if (updated_axis < 0) throw std::invalid_argument(std::move(err_msg)); - - return updated_axis; -} - void NDArray::sort_task(NDArray rhs, bool argsort, bool stable) { auto runtime = CuNumericRuntime::get_runtime(); @@ -250,7 +238,7 @@ void NDArray::sort_task(NDArray rhs, bool argsort, bool stable) void NDArray::sort_swapped(NDArray rhs, bool argsort, int32_t sort_axis, bool stable) { - sort_axis = rhs.normalize_axis_index(sort_axis); + sort_axis = normalize_axis_index(sort_axis, rhs.dim()); auto swapped = rhs.swapaxes(sort_axis, rhs.dim() - 1); auto runtime = CuNumericRuntime::get_runtime(); @@ -274,7 +262,7 @@ void NDArray::sort(NDArray rhs, bool argsort, std::optional axis, bool assert(false); } int32_t computed_axis = 0; - if (axis.has_value()) computed_axis = rhs.normalize_axis_index(axis.value()); + if (axis.has_value()) computed_axis = normalize_axis_index(axis.value(), rhs.dim()); if (computed_axis == rhs.dim() - 1) { sort_task(rhs, argsort, stable); @@ -511,8 +499,8 @@ NDArray NDArray::unique() NDArray NDArray::swapaxes(int32_t axis1, int32_t axis2) { - axis1 = normalize_axis_index(axis1); - axis2 = normalize_axis_index(axis2); + axis1 = normalize_axis_index(axis1, dim()); + axis2 = normalize_axis_index(axis2, dim()); if (shape().size() == 1 || axis1 == axis2) return *this; diff --git a/src/cunumeric/ndarray.h b/src/cunumeric/ndarray.h index 3962af93e7..6b2d97abca 100644 --- a/src/cunumeric/ndarray.h +++ b/src/cunumeric/ndarray.h @@ -94,7 +94,6 @@ class NDArray { public: NDArray as_type(const legate::Type& type); legate::LogicalStore get_store(); - int32_t normalize_axis_index(int32_t axis); void sort(NDArray rhs, bool argsort, std::optional axis = -1, bool stable = false); private: diff --git a/src/cunumeric/operators.cc b/src/cunumeric/operators.cc index ccf10234f6..4242bca078 100644 --- a/src/cunumeric/operators.cc +++ b/src/cunumeric/operators.cc @@ -325,4 +325,51 @@ NDArray transpose(NDArray a) { return a.transpose(); } NDArray transpose(NDArray a, std::vector axes) { return a.transpose(axes); } +int32_t normalize_axis_index(int32_t axis, int32_t ndim) +{ + if (-ndim <= axis && axis < ndim) { + axis = axis < 0 ? axis + ndim : axis; + } else { + std::stringstream ss; + ss << "AxisError: axis " << axis << " is out of bounds for array of dimension " << ndim; + throw std::invalid_argument(ss.str()); + } + return axis; +} + +std::vector normalize_axis_vector(std::vector axis, + int32_t ndim, + bool allow_duplicate) +{ + std::vector new_axis; + for (auto ax : axis) { new_axis.emplace_back(normalize_axis_index(ax, ndim)); } + std::set s(new_axis.begin(), new_axis.end()); + if (!allow_duplicate && s.size() != new_axis.size()) { + throw std::invalid_argument("repeated axis"); + } + return new_axis; +} + +NDArray moveaxis(NDArray a, std::vector source, std::vector destination) +{ + if (source.size() != destination.size()) { + throw std::invalid_argument( + "`source` and `destination` arguments must have the same number " + "of elements"); + } + auto ndim = a.dim(); + auto src = normalize_axis_vector(source, ndim); + auto dst = normalize_axis_vector(destination, ndim); + std::vector order; + std::set set_src(src.begin(), src.end()); + for (auto i = 0; i < ndim; ++i) { + if (set_src.find(i) == set_src.end()) order.emplace_back(i); + } + std::vector> vp; + for (auto i = 0; i < src.size(); ++i) { vp.push_back(std::make_pair(dst[i], src[i])); } + std::sort(vp.begin(), vp.end()); + for (auto p : vp) { order.emplace(order.begin() + p.first, p.second); } + return a.transpose(order); +} + } // namespace cunumeric diff --git a/src/cunumeric/operators.h b/src/cunumeric/operators.h index 71ef5c27c2..63e0aa49f8 100644 --- a/src/cunumeric/operators.h +++ b/src/cunumeric/operators.h @@ -95,4 +95,13 @@ NDArray transpose(NDArray a); NDArray transpose(NDArray a, std::vector axes); +NDArray moveaxis(NDArray a, std::vector source, std::vector destination); + +// helper methods +int32_t normalize_axis_index(int32_t axis, int32_t ndim); + +std::vector normalize_axis_vector(std::vector axis, + int32_t ndim, + bool allow_duplicate = false); + } // namespace cunumeric diff --git a/tests/cpp/integration/test_moveaxis.cc b/tests/cpp/integration/test_moveaxis.cc new file mode 100644 index 0000000000..3d676ff337 --- /dev/null +++ b/tests/cpp/integration/test_moveaxis.cc @@ -0,0 +1,150 @@ +/* Copyright 2023 NVIDIA Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +#include +#include +#include + +#include +#include "legate.h" +#include "cunumeric.h" +#include "util.inl" + +template +static void moveaxis_int32_test(std::vector input, + std::vector exp, + std::vector in_shape, + std::vector out_shape, + std::vector source, + std::vector destination) +{ + auto a_input = cunumeric::zeros(in_shape, legate::int32()); + assign_values_to_array(a_input, input.data(), input.size()); + auto a_output = cunumeric::moveaxis(a_input, source, destination); + check_array_eq(a_output, exp.data(), exp.size()); + EXPECT_EQ(a_output.shape(), out_shape); +} + +static void moveaxis_int32_test_2(std::vector in_shape, + std::vector out_shape, + std::vector source, + std::vector destination) +{ + auto a_input = cunumeric::zeros(in_shape, legate::int32()); + auto a_output = cunumeric::moveaxis(a_input, source, destination); + EXPECT_EQ(a_output.shape(), out_shape); +} + +TEST(MoveAxis, Normal) +{ + moveaxis_int32_test<2>({1, 2, 3, 4, 5, 6}, {1, 2, 3, 4, 5, 6}, {2, 3}, {2, 3}, {0}, {0}); + moveaxis_int32_test<2>({1, 2, 3, 4, 5, 6}, {1, 4, 2, 5, 3, 6}, {2, 3}, {3, 2}, {0}, {-1}); + moveaxis_int32_test<3>( + {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23}, + {0, 12, 1, 13, 2, 14, 3, 15, 4, 16, 5, 17, 6, 18, 7, 19, 8, 20, 9, 21, 10, 22, 11, 23}, + {2, 3, 4}, + {3, 4, 2}, + {0}, + {-1}); +} + +TEST(MoveAxis, SpecialArrays) +{ + // test single element array + { + std::vector input{99}; + auto a = cunumeric::zeros({1}, legate::int32()); + a.fill(legate::Scalar(input[0]), false); + auto a_out = cunumeric::moveaxis(a, {0}, {-1}); + check_array_eq(a_out, input.data(), input.size()); + EXPECT_EQ(a_out.shape(), a.shape()); + } + { + std::vector input{-100}; + auto a = cunumeric::zeros({1, 1}, legate::int32()); + a.fill(legate::Scalar(input[0]), false); + auto a_out = cunumeric::moveaxis(a, {0, 1}, {-1, -2}); + check_array_eq(a_out, input.data(), input.size()); + EXPECT_EQ(a_out.shape(), a.shape()); + } + + // test empty array + { + auto a = cunumeric::zeros({0}, legate::int32()); + auto a_out = cunumeric::moveaxis(a, {0}, {-1}); + EXPECT_EQ(a_out.shape(), a.shape()); + } +} + +TEST(MoveAxis, Shape) +{ + moveaxis_int32_test_2({3, 4, 5}, {4, 5, 3}, {0}, {-1}); + moveaxis_int32_test_2({3, 4, 5}, {5, 3, 4}, {-1}, {0}); + moveaxis_int32_test_2({3, 4, 5}, {5, 4, 3}, {0, 1}, {-1, -2}); + moveaxis_int32_test_2({3, 4, 5}, {5, 4, 3}, {0, 1, 2}, {-1, -2, -3}); +} + +TEST(MoveAxis, Shape7D) +{ + moveaxis_int32_test_2({3, 2, 2, 2}, {2, 2, 2, 3}, {0}, {-1}); + +#if LEGATE_MAX_DIM >= 5 + moveaxis_int32_test_2({3, 2, 2, 2, 2}, {2, 2, 2, 2, 3}, {0}, {-1}); +#endif + +#if LEGATE_MAX_DIM >= 6 + moveaxis_int32_test_2({3, 4, 2, 2, 2, 2}, {2, 2, 2, 2, 4, 3}, {0, 1}, {-1, -2}); +#endif + +#if LEGATE_MAX_DIM >= 7 + moveaxis_int32_test_2({3, 4, 5, 2, 2, 2, 2}, {2, 2, 2, 2, 3, 4, 5}, {2, 1, 0}, {-1, -2, -3}); +#endif +} + +TEST(MoveAxis, EmptyShape) +{ + moveaxis_int32_test_2({0, 1, 2}, {1, 2, 0}, {0}, {-1}); + moveaxis_int32_test_2({1, 0, 7}, {7, 1, 0}, {-1}, {0}); + moveaxis_int32_test_2({4, 0, 9, 0}, {0, 4, 0, 9}, {2, 0}, {3, 1}); +} + +TEST(MoveAxis, With_empty_array) +{ + moveaxis_int32_test_2({3, 4}, {3, 4}, {}, {}); + moveaxis_int32_test_2({3, 4, 5}, {3, 4, 5}, {}, {}); +} + +TEST(MoveAxisErrors, Repeated_axis) +{ + auto x = cunumeric::zeros({3, 4, 5}, legate::int32()); + EXPECT_THROW(cunumeric::moveaxis(x, {0, 0}, {1, 0}), std::invalid_argument); + EXPECT_THROW(cunumeric::moveaxis(x, {0, 1}, {0, -3}), std::invalid_argument); +} + +TEST(MoveAxisErrors, Axis_out_of_bound) +{ + auto x = cunumeric::zeros({3, 4, 5}, legate::int32()); + EXPECT_THROW(cunumeric::moveaxis(x, {0, 3}, {0, 1}), std::invalid_argument); + EXPECT_THROW(cunumeric::moveaxis(x, {0, 1}, {0, -4}), std::invalid_argument); + EXPECT_THROW(cunumeric::moveaxis(x, {4}, {0}), std::invalid_argument); + EXPECT_THROW(cunumeric::moveaxis(x, {0}, {-4}), std::invalid_argument); +} + +TEST(MoveAxisErrors, Axis_with_different_length) +{ + auto x = cunumeric::zeros({3, 4, 5}, legate::int32()); + EXPECT_THROW(cunumeric::moveaxis(x, {0}, {1, 0}), std::invalid_argument); +} From 36c728d9439949ed3d2e24283a31cc31417d73e2 Mon Sep 17 00:00:00 2001 From: Wonchan Lee Date: Sun, 12 Nov 2023 23:39:41 -0800 Subject: [PATCH 115/462] Fix compile warnings and bump the legate core commit hash (#52) --- cmake/versions.json | 2 +- src/cunumeric/ndarray.cc | 2 +- src/cunumeric/operators.cc | 2 +- src/cunumeric/sort/sort_cpu.inl | 109 +++++++++++++++++--------------- 4 files changed, 60 insertions(+), 55 deletions(-) diff --git a/cmake/versions.json b/cmake/versions.json index 7fe414357a..c9b7cdbcee 100644 --- a/cmake/versions.json +++ b/cmake/versions.json @@ -12,7 +12,7 @@ "git_url" : "https://github.com/nv-legate/legate.core.internal.git", "git_shallow": false, "always_download": false, - "git_tag" : "4b34a2088709e654e79694ad035c30c914d525f7" + "git_tag" : "fb57a49dd6ae6b5bfc936be2f8b730c301f1564d" } } } diff --git a/src/cunumeric/ndarray.cc b/src/cunumeric/ndarray.cc index 3dc5db3856..9a5cbff475 100644 --- a/src/cunumeric/ndarray.cc +++ b/src/cunumeric/ndarray.cc @@ -592,7 +592,7 @@ NDArray NDArray::transpose() NDArray NDArray::transpose(std::vector axes) { if (dim() == 1) return NDArray(std::move(store_)); - if (axes.size() != dim()) { + if (static_cast(axes.size()) != dim()) { throw std::invalid_argument("axes must be the same size as ndim for transpose"); } return NDArray(store_.transpose(std::move(axes))); diff --git a/src/cunumeric/operators.cc b/src/cunumeric/operators.cc index 4242bca078..a9c7e38e92 100644 --- a/src/cunumeric/operators.cc +++ b/src/cunumeric/operators.cc @@ -366,7 +366,7 @@ NDArray moveaxis(NDArray a, std::vector source, std::vector de if (set_src.find(i) == set_src.end()) order.emplace_back(i); } std::vector> vp; - for (auto i = 0; i < src.size(); ++i) { vp.push_back(std::make_pair(dst[i], src[i])); } + for (size_t i = 0; i < src.size(); ++i) { vp.push_back(std::make_pair(dst[i], src[i])); } std::sort(vp.begin(), vp.end()); for (auto p : vp) { order.emplace(order.begin() + p.first, p.second); } return a.transpose(order); diff --git a/src/cunumeric/sort/sort_cpu.inl b/src/cunumeric/sort/sort_cpu.inl index 1db25dfd3f..14a1315ec3 100644 --- a/src/cunumeric/sort/sort_cpu.inl +++ b/src/cunumeric/sort/sort_cpu.inl @@ -145,14 +145,15 @@ void rebalance_data(SegmentMergePiece& merge_buffer, rdispls[sort_ranks[sort_rank]] = sort_rank * num_segments_l; } - comm::coll::collAlltoallv(segment_diff.ptr(0), - comm_size.ptr(0), // num_segments_l for all in sort group - sdispls.ptr(0), // zero for all - segment_diff_buffers.ptr(0), - comm_size.ptr(0), // num_segments_l for all in sort group - rdispls.ptr(0), // exclusive_scan of recv size - comm::coll::CollDataType::CollInt64, - comm); + static_cast( + comm::coll::collAlltoallv(segment_diff.ptr(0), + comm_size.ptr(0), // num_segments_l for all in sort group + sdispls.ptr(0), // zero for all + segment_diff_buffers.ptr(0), + comm_size.ptr(0), // num_segments_l for all in sort group + rdispls.ptr(0), // exclusive_scan of recv size + comm::coll::CollDataType::CollInt64, + comm)); comm_size.destroy(); sdispls.destroy(); @@ -325,23 +326,23 @@ void rebalance_data(SegmentMergePiece& merge_buffer, } if (argsort) { - comm::coll::collAlltoallv(send_leftright_data.indices.ptr(0), - comm_send_size.ptr(0), - sdispls.ptr(0), - recv_leftright_data.indices.ptr(0), - comm_recv_size.ptr(0), - rdispls.ptr(0), - comm::coll::CollDataType::CollInt8, - comm); + static_cast(comm::coll::collAlltoallv(send_leftright_data.indices.ptr(0), + comm_send_size.ptr(0), + sdispls.ptr(0), + recv_leftright_data.indices.ptr(0), + comm_recv_size.ptr(0), + rdispls.ptr(0), + comm::coll::CollDataType::CollInt8, + comm)); } else { - comm::coll::collAlltoallv(send_leftright_data.values.ptr(0), - comm_send_size.ptr(0), - sdispls.ptr(0), - recv_leftright_data.values.ptr(0), - comm_recv_size.ptr(0), - rdispls.ptr(0), - comm::coll::CollDataType::CollInt8, - comm); + static_cast(comm::coll::collAlltoallv(send_leftright_data.values.ptr(0), + comm_send_size.ptr(0), + sdispls.ptr(0), + recv_leftright_data.values.ptr(0), + comm_recv_size.ptr(0), + rdispls.ptr(0), + comm::coll::CollDataType::CollInt8, + comm)); } comm_send_size.destroy(); @@ -478,8 +479,11 @@ void sample_sort_nd(SortPiece> local_sorted, { auto worker_counts = create_buffer(num_ranks); worker_counts[my_rank] = (segment_size_l > 0 ? 1 : 0); - comm::coll::collAllgather( - worker_counts.ptr(my_rank), worker_counts.ptr(0), 1, comm::coll::CollDataType::CollInt, comm); + static_cast(comm::coll::collAllgather(worker_counts.ptr(my_rank), + worker_counts.ptr(0), + 1, + comm::coll::CollDataType::CollInt, + comm)); auto p_worker_count = worker_counts.ptr(0); int32_t worker_count = @@ -568,14 +572,15 @@ void sample_sort_nd(SortPiece> local_sorted, auto p_comm_size = comm_size.ptr(0); thrust::exclusive_scan(exec, p_comm_size, p_comm_size + num_ranks, rdispls.ptr(0), 0); - comm::coll::collAlltoallv(samples_l.ptr(0), - comm_size.ptr(0), // num_samples_l*size for all in sort group - sdispls.ptr(0), // zero for all - p_samples, - comm_size.ptr(0), // num_samples_l*size for all in sort group - rdispls.ptr(0), // exclusive_scan of recv size - comm::coll::CollDataType::CollUint8, - comm); + static_cast( + comm::coll::collAlltoallv(samples_l.ptr(0), + comm_size.ptr(0), // num_samples_l*size for all in sort group + sdispls.ptr(0), // zero for all + p_samples, + comm_size.ptr(0), // num_samples_l*size for all in sort group + rdispls.ptr(0), // exclusive_scan of recv size + comm::coll::CollDataType::CollUint8, + comm)); samples_l.destroy(); comm_size.destroy(); @@ -684,7 +689,7 @@ void sample_sort_nd(SortPiece> local_sorted, auto p_comm_size = comm_size.ptr(0); thrust::exclusive_scan(exec, p_comm_size, p_comm_size + num_ranks, displs.ptr(0), 0); - comm::coll::collAlltoallv( + static_cast(comm::coll::collAlltoallv( size_send.ptr(0), comm_size.ptr(0), // (num_segments_l+1)*size for all in sort group displs.ptr(0), // exclusive_scan of comm_size @@ -692,7 +697,7 @@ void sample_sort_nd(SortPiece> local_sorted, comm_size.ptr(0), // (num_segments_l+1)*valuesize for all in sort group displs.ptr(0), // exclusive_scan of comm_size comm::coll::CollDataType::CollInt, - comm); + comm)); comm_size.destroy(); displs.destroy(); @@ -785,14 +790,14 @@ void sample_sort_nd(SortPiece> local_sorted, thrust::exclusive_scan( exec, p_recv_size_total, p_recv_size_total + num_ranks, rdispls.ptr(0), 0); - comm::coll::collAlltoallv(val_send_buffer.ptr(0), - send_size_total.ptr(0), - sdispls.ptr(0), - merge_buffer.values.ptr(0), - recv_size_total.ptr(0), - rdispls.ptr(0), - comm::coll::CollDataType::CollUint8, - comm); + static_cast(comm::coll::collAlltoallv(val_send_buffer.ptr(0), + send_size_total.ptr(0), + sdispls.ptr(0), + merge_buffer.values.ptr(0), + recv_size_total.ptr(0), + rdispls.ptr(0), + comm::coll::CollDataType::CollUint8, + comm)); if (argsort) { for (size_t sort_rank = 0; sort_rank < num_sort_ranks; ++sort_rank) { @@ -806,14 +811,14 @@ void sample_sort_nd(SortPiece> local_sorted, exec, p_send_size_total, p_send_size_total + num_ranks, sdispls.ptr(0), 0); thrust::exclusive_scan( exec, p_recv_size_total, p_recv_size_total + num_ranks, rdispls.ptr(0), 0); - comm::coll::collAlltoallv(idc_send_buffer.ptr(0), - send_size_total.ptr(0), - sdispls.ptr(0), - merge_buffer.indices.ptr(0), - recv_size_total.ptr(0), - rdispls.ptr(0), - comm::coll::CollDataType::CollInt64, - comm); + static_cast(comm::coll::collAlltoallv(idc_send_buffer.ptr(0), + send_size_total.ptr(0), + sdispls.ptr(0), + merge_buffer.indices.ptr(0), + recv_size_total.ptr(0), + rdispls.ptr(0), + comm::coll::CollDataType::CollInt64, + comm)); } send_size_total.destroy(); From 375af482b879ef47d6c7b57042ee56b792a2aa98 Mon Sep 17 00:00:00 2001 From: Jacob Faibussowitsch Date: Mon, 13 Nov 2023 15:55:07 -0500 Subject: [PATCH 116/462] Fix unvalid URL for zip archive in CPM download (#54) --- cmake/Modules/cpm_helpers.cmake | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmake/Modules/cpm_helpers.cmake b/cmake/Modules/cpm_helpers.cmake index 9fc28633d8..80a9c03a79 100644 --- a/cmake/Modules/cpm_helpers.cmake +++ b/cmake/Modules/cpm_helpers.cmake @@ -40,7 +40,7 @@ function(get_cpm_git_args _out_var) endif() if(GIT_REPOSITORY MATCHES "github\.com") # If retrieving from github use `.zip` URL to download faster - set(cpm_git_args URL "${GIT_REPOSITORY}/archive/refs/${gh_tag_prefix}/${repo_tag}.zip") + set(cpm_git_args URL "${GIT_REPOSITORY}/archive/${repo_tag}.zip") elseif(GIT_REPOSITORY MATCHES "gitlab\.com") # GitLab archive URIs replace slashes with dashes string(REPLACE "/" "-" archive_tag "${repo_tag}") From fc363435b41c708b9111d63b15f0d7e034d7d43d Mon Sep 17 00:00:00 2001 From: Wonchan Lee Date: Tue, 14 Nov 2023 23:44:33 -0800 Subject: [PATCH 117/462] Catch up renaming in the core (#53) * Catch up renaming in the core * Catch up renaminig in the type traits * Bump the legate core commit hash --- cmake/versions.json | 4 +- src/cunumeric/arg_redop_register.h | 2 +- src/cunumeric/binary/binary_op.cc | 2 +- src/cunumeric/binary/binary_op.cu | 2 +- src/cunumeric/binary/binary_op.h | 6 +- src/cunumeric/binary/binary_op_omp.cc | 2 +- src/cunumeric/binary/binary_op_template.inl | 2 +- src/cunumeric/binary/binary_op_util.h | 73 ++++++++------- src/cunumeric/binary/binary_red.cc | 2 +- src/cunumeric/binary/binary_red.cu | 2 +- src/cunumeric/binary/binary_red.h | 6 +- src/cunumeric/binary/binary_red_omp.cc | 2 +- src/cunumeric/binary/binary_red_template.inl | 2 +- src/cunumeric/bits/packbits.cc | 2 +- src/cunumeric/bits/packbits.cu | 2 +- src/cunumeric/bits/packbits_omp.cc | 2 +- src/cunumeric/bits/packbits_template.inl | 16 ++-- src/cunumeric/bits/unpackbits_template.inl | 14 +-- src/cunumeric/convolution/convolve.cc | 4 +- src/cunumeric/convolution/convolve.cu | 2 +- src/cunumeric/convolution/convolve.h | 6 +- src/cunumeric/convolution/convolve_omp.cc | 2 +- .../convolution/convolve_template.inl | 2 +- src/cunumeric/fft/fft.cu | 4 +- src/cunumeric/fft/fft.h | 4 +- src/cunumeric/fft/fft_template.inl | 4 +- src/cunumeric/index/advanced_indexing.cc | 4 +- src/cunumeric/index/advanced_indexing.cu | 4 +- src/cunumeric/index/advanced_indexing.h | 6 +- src/cunumeric/index/advanced_indexing_omp.cc | 4 +- .../index/advanced_indexing_template.inl | 2 +- src/cunumeric/index/choose.cc | 2 +- src/cunumeric/index/choose.cu | 2 +- src/cunumeric/index/choose.h | 4 +- src/cunumeric/index/choose_omp.cc | 2 +- src/cunumeric/index/choose_template.inl | 4 +- src/cunumeric/index/putmask.h | 6 +- src/cunumeric/index/putmask_template.inl | 2 +- src/cunumeric/index/repeat.cc | 8 +- src/cunumeric/index/repeat.cu | 6 +- src/cunumeric/index/repeat.h | 6 +- src/cunumeric/index/repeat_omp.cc | 6 +- src/cunumeric/index/repeat_template.inl | 4 +- src/cunumeric/index/wrap.h | 4 +- src/cunumeric/index/wrap_template.inl | 2 +- src/cunumeric/index/zip.h | 4 +- src/cunumeric/index/zip_template.inl | 2 +- src/cunumeric/item/read_template.inl | 4 +- src/cunumeric/item/write_template.inl | 4 +- src/cunumeric/matrix/contract.h | 6 +- src/cunumeric/matrix/contract_template.inl | 2 +- src/cunumeric/matrix/diag.cc | 4 +- src/cunumeric/matrix/diag.cu | 4 +- src/cunumeric/matrix/diag.h | 4 +- src/cunumeric/matrix/diag_omp.cc | 4 +- src/cunumeric/matrix/diag_template.inl | 10 +- src/cunumeric/matrix/dot.cc | 2 +- src/cunumeric/matrix/dot.cu | 2 +- src/cunumeric/matrix/dot.h | 6 +- src/cunumeric/matrix/dot_omp.cc | 2 +- src/cunumeric/matrix/dot_template.inl | 2 +- src/cunumeric/matrix/gemm_template.inl | 10 +- src/cunumeric/matrix/matmul.h | 6 +- src/cunumeric/matrix/matmul_template.inl | 2 +- src/cunumeric/matrix/matvecmul.h | 6 +- src/cunumeric/matrix/matvecmul_template.inl | 2 +- src/cunumeric/matrix/potrf_template.inl | 6 +- src/cunumeric/matrix/solve_template.inl | 6 +- src/cunumeric/matrix/syrk_template.inl | 6 +- src/cunumeric/matrix/tile.h | 4 +- src/cunumeric/matrix/tile_template.inl | 2 +- src/cunumeric/matrix/transpose.cc | 2 +- src/cunumeric/matrix/transpose.cu | 2 +- src/cunumeric/matrix/transpose.h | 4 +- src/cunumeric/matrix/transpose_omp.cc | 2 +- src/cunumeric/matrix/transpose_template.inl | 2 +- src/cunumeric/matrix/trilu.cc | 2 +- src/cunumeric/matrix/trilu.cu | 2 +- src/cunumeric/matrix/trilu.h | 4 +- src/cunumeric/matrix/trilu_omp.cc | 2 +- src/cunumeric/matrix/trilu_template.inl | 2 +- src/cunumeric/matrix/trsm_template.inl | 6 +- src/cunumeric/ndarray.cc | 2 +- src/cunumeric/nullary/arange.h | 2 +- src/cunumeric/nullary/arange_template.inl | 2 +- src/cunumeric/nullary/eye.h | 2 +- src/cunumeric/nullary/eye_template.inl | 2 +- src/cunumeric/nullary/fill.h | 4 +- src/cunumeric/nullary/fill_template.inl | 4 +- src/cunumeric/nullary/window_template.inl | 2 +- src/cunumeric/operators.cc | 4 +- src/cunumeric/random/bitgenerator.h | 8 +- src/cunumeric/random/bitgenerator_curand.inl | 18 ++-- .../random/bitgenerator_template.inl | 4 +- src/cunumeric/random/rand.h | 2 +- src/cunumeric/random/rand_template.inl | 2 +- src/cunumeric/random/rand_util.h | 2 +- src/cunumeric/scan/scan_global.cc | 2 +- src/cunumeric/scan/scan_global.cu | 2 +- src/cunumeric/scan/scan_global.h | 4 +- src/cunumeric/scan/scan_global_omp.cc | 2 +- src/cunumeric/scan/scan_global_template.inl | 2 +- src/cunumeric/scan/scan_local.cc | 8 +- src/cunumeric/scan/scan_local.cu | 8 +- src/cunumeric/scan/scan_local.h | 6 +- src/cunumeric/scan/scan_local_omp.cc | 8 +- src/cunumeric/scan/scan_local_template.inl | 4 +- src/cunumeric/scan/scan_local_util.h | 4 +- src/cunumeric/scan/scan_util.h | 8 +- src/cunumeric/search/argwhere.cc | 4 +- src/cunumeric/search/argwhere.cu | 4 +- src/cunumeric/search/argwhere.h | 4 +- src/cunumeric/search/argwhere_omp.cc | 4 +- src/cunumeric/search/argwhere_template.inl | 2 +- src/cunumeric/search/nonzero.cc | 2 +- src/cunumeric/search/nonzero.cu | 2 +- src/cunumeric/search/nonzero.h | 4 +- src/cunumeric/search/nonzero_omp.cc | 2 +- src/cunumeric/search/nonzero_template.inl | 4 +- src/cunumeric/set/unique.cc | 4 +- src/cunumeric/set/unique.cu | 6 +- src/cunumeric/set/unique_omp.cc | 4 +- src/cunumeric/set/unique_reduce_template.inl | 6 +- src/cunumeric/set/unique_template.inl | 6 +- src/cunumeric/sort/searchsorted.cc | 8 +- src/cunumeric/sort/searchsorted.cu | 8 +- src/cunumeric/sort/searchsorted.h | 6 +- src/cunumeric/sort/searchsorted_omp.cc | 8 +- src/cunumeric/sort/sort.cc | 6 +- src/cunumeric/sort/sort.cu | 30 +++--- src/cunumeric/sort/sort.h | 4 +- src/cunumeric/sort/sort_cpu.inl | 43 ++++----- src/cunumeric/sort/sort_omp.cc | 6 +- src/cunumeric/stat/bincount.cc | 2 +- src/cunumeric/stat/bincount.cu | 2 +- src/cunumeric/stat/bincount.h | 6 +- src/cunumeric/stat/bincount_omp.cc | 2 +- src/cunumeric/stat/bincount_template.inl | 2 +- src/cunumeric/stat/histogram.cc | 2 +- src/cunumeric/stat/histogram.cu | 2 +- src/cunumeric/stat/histogram.h | 8 +- src/cunumeric/stat/histogram_omp.cc | 2 +- src/cunumeric/stat/histogram_template.inl | 2 +- src/cunumeric/ternary/where.cc | 2 +- src/cunumeric/ternary/where.cu | 2 +- src/cunumeric/ternary/where.h | 8 +- src/cunumeric/ternary/where_omp.cc | 2 +- src/cunumeric/ternary/where_template.inl | 2 +- src/cunumeric/transform/flip.cc | 2 +- src/cunumeric/transform/flip.cu | 2 +- src/cunumeric/transform/flip.h | 4 +- src/cunumeric/transform/flip_omp.cc | 2 +- src/cunumeric/transform/flip_template.inl | 2 +- src/cunumeric/typedefs.h | 2 +- src/cunumeric/unary/convert.cc | 4 +- src/cunumeric/unary/convert.cu | 4 +- src/cunumeric/unary/convert.h | 4 +- src/cunumeric/unary/convert_omp.cc | 4 +- src/cunumeric/unary/convert_template.inl | 4 +- src/cunumeric/unary/convert_util.h | 24 ++--- src/cunumeric/unary/scalar_unary_red.h | 4 +- .../unary/scalar_unary_red_template.inl | 2 +- src/cunumeric/unary/unary_op.h | 10 +- src/cunumeric/unary/unary_op_template.inl | 2 +- src/cunumeric/unary/unary_op_util.h | 92 +++++++++---------- src/cunumeric/unary/unary_red.cc | 2 +- src/cunumeric/unary/unary_red.cu | 2 +- src/cunumeric/unary/unary_red.h | 4 +- src/cunumeric/unary/unary_red_omp.cc | 2 +- src/cunumeric/unary/unary_red_template.inl | 2 +- src/cunumeric/unary/unary_red_util.h | 32 +++---- 171 files changed, 474 insertions(+), 470 deletions(-) diff --git a/cmake/versions.json b/cmake/versions.json index c9b7cdbcee..edbd2ebe27 100644 --- a/cmake/versions.json +++ b/cmake/versions.json @@ -8,11 +8,11 @@ "git_tag" : "8997f997be02936304b3ac23fe785f1de7a3424b" }, "legate_core_internal" : { - "version": "23.11.00", + "version": "24.01.00", "git_url" : "https://github.com/nv-legate/legate.core.internal.git", "git_shallow": false, "always_download": false, - "git_tag" : "fb57a49dd6ae6b5bfc936be2f8b730c301f1564d" + "git_tag" : "63a5541366542e765693e340c2b851f126dea9f2" } } } diff --git a/src/cunumeric/arg_redop_register.h b/src/cunumeric/arg_redop_register.h index a06e245d8e..56608ef651 100644 --- a/src/cunumeric/arg_redop_register.h +++ b/src/cunumeric/arg_redop_register.h @@ -26,7 +26,7 @@ struct register_reduction_op_fn { template ::value>* = nullptr> ReductionOpIds operator()() { - using VAL = legate::legate_type_of; + using VAL = legate::type_of; ReductionOpIds result; auto runtime = legate::Runtime::get_runtime(); auto context = runtime->find_library("cunumeric"); diff --git a/src/cunumeric/binary/binary_op.cc b/src/cunumeric/binary/binary_op.cc index 90adf9e033..8498275d1f 100644 --- a/src/cunumeric/binary/binary_op.cc +++ b/src/cunumeric/binary/binary_op.cc @@ -24,7 +24,7 @@ using namespace legate; template struct BinaryOpImplBody { using OP = BinaryOp; - using RHS1 = legate_type_of; + using RHS1 = type_of; using RHS2 = rhs2_of_binary_op; using LHS = std::result_of_t; diff --git a/src/cunumeric/binary/binary_op.cu b/src/cunumeric/binary/binary_op.cu index 6ef70e448a..305e7e5914 100644 --- a/src/cunumeric/binary/binary_op.cu +++ b/src/cunumeric/binary/binary_op.cu @@ -54,7 +54,7 @@ static __global__ void __launch_bounds__(THREADS_PER_BLOCK, MIN_CTAS_PER_SM) template struct BinaryOpImplBody { using OP = BinaryOp; - using RHS1 = legate_type_of; + using RHS1 = type_of; using RHS2 = rhs2_of_binary_op; using LHS = std::result_of_t; diff --git a/src/cunumeric/binary/binary_op.h b/src/cunumeric/binary/binary_op.h index 0b45afc043..e963e1f981 100644 --- a/src/cunumeric/binary/binary_op.h +++ b/src/cunumeric/binary/binary_op.h @@ -22,9 +22,9 @@ namespace cunumeric { struct BinaryOpArgs { - legate::Store in1; - legate::Store in2; - legate::Store out; + legate::PhysicalStore in1; + legate::PhysicalStore in2; + legate::PhysicalStore out; BinaryOpCode op_code; std::vector args; }; diff --git a/src/cunumeric/binary/binary_op_omp.cc b/src/cunumeric/binary/binary_op_omp.cc index e6985925a2..73985ae8f8 100644 --- a/src/cunumeric/binary/binary_op_omp.cc +++ b/src/cunumeric/binary/binary_op_omp.cc @@ -24,7 +24,7 @@ using namespace legate; template struct BinaryOpImplBody { using OP = BinaryOp; - using RHS1 = legate_type_of; + using RHS1 = type_of; using RHS2 = rhs2_of_binary_op; using LHS = std::result_of_t; diff --git a/src/cunumeric/binary/binary_op_template.inl b/src/cunumeric/binary/binary_op_template.inl index 6d6ee168f3..f4b0b4d0f1 100644 --- a/src/cunumeric/binary/binary_op_template.inl +++ b/src/cunumeric/binary/binary_op_template.inl @@ -34,7 +34,7 @@ struct BinaryOpImpl { void operator()(BinaryOpArgs& args) const { using OP = BinaryOp; - using RHS1 = legate_type_of; + using RHS1 = type_of; using RHS2 = rhs2_of_binary_op; using LHS = std::result_of_t; diff --git a/src/cunumeric/binary/binary_op_util.h b/src/cunumeric/binary/binary_op_util.h index 24dd2383e8..76532c0038 100644 --- a/src/cunumeric/binary/binary_op_util.h +++ b/src/cunumeric/binary/binary_op_util.h @@ -167,14 +167,14 @@ struct BinaryOp { }; template -struct BinaryOp : std::plus> { +struct BinaryOp : std::plus> { static constexpr bool valid = true; BinaryOp(const std::vector&) {} }; template struct BinaryOp { - using T = legate::legate_type_of; + using T = legate::type_of; static constexpr bool valid = legate::is_floating_point::value; __CUDA_HD__ BinaryOp() {} @@ -200,7 +200,7 @@ struct BinaryOp { template struct BinaryOp { - using T = legate::legate_type_of; + using T = legate::type_of; static constexpr bool valid = std::is_integral::value; BinaryOp(const std::vector&) {} @@ -213,7 +213,7 @@ struct BinaryOp { template struct BinaryOp { - using T = legate::legate_type_of; + using T = legate::type_of; static constexpr bool valid = std::is_integral::value; BinaryOp(const std::vector&) {} @@ -226,7 +226,7 @@ struct BinaryOp { template struct BinaryOp { - using T = legate::legate_type_of; + using T = legate::type_of; static constexpr bool valid = std::is_integral::value; BinaryOp(const std::vector&) {} @@ -239,7 +239,7 @@ struct BinaryOp { template struct BinaryOp { - using T = legate::legate_type_of; + using T = legate::type_of; static constexpr bool valid = legate::is_floating_point::value; __CUDA_HD__ BinaryOp() {} BinaryOp(const std::vector&) {} @@ -265,7 +265,7 @@ struct BinaryOp { template struct BinaryOp { - using T = legate::legate_type_of; + using T = legate::type_of; static constexpr bool valid = true; BinaryOp(const std::vector&) {} @@ -283,14 +283,14 @@ struct BinaryOp { }; template -struct BinaryOp : std::equal_to> { +struct BinaryOp : std::equal_to> { static constexpr bool valid = true; BinaryOp(const std::vector&) {} }; template struct BinaryOp { - using T = legate::legate_type_of; + using T = legate::type_of; static constexpr bool valid = CODE == legate::Type::Code::FLOAT64 or CODE == legate::Type::Code::COMPLEX128; BinaryOp(const std::vector&) {} @@ -317,7 +317,7 @@ struct BinaryOp { template struct BinaryOp { - using T = legate::legate_type_of; + using T = legate::type_of; static constexpr bool valid = not(CODE == legate::Type::Code::BOOL or legate::is_complex::value); @@ -374,7 +374,7 @@ static __CUDA_HD__ T _gcd(T a, T b) template struct BinaryOp { - using T = legate::legate_type_of; + using T = legate::type_of; static constexpr bool valid = std::is_integral::value; BinaryOp(const std::vector&) {} @@ -392,7 +392,7 @@ static constexpr T floor_divide_signed(const T& a, const T& b) using std::floor; template struct BinaryOp { - using T = legate::legate_type_of; + using T = legate::type_of; static constexpr bool valid = true; BinaryOp(const std::vector&) {} @@ -430,21 +430,20 @@ struct BinaryOp { }; template -struct BinaryOp : std::greater> { +struct BinaryOp : std::greater> { static constexpr bool valid = true; BinaryOp(const std::vector&) {} }; template -struct BinaryOp - : std::greater_equal> { +struct BinaryOp : std::greater_equal> { static constexpr bool valid = true; BinaryOp(const std::vector&) {} }; template struct BinaryOp { - using T = legate::legate_type_of; + using T = legate::type_of; static constexpr bool valid = legate::is_floating_point::value; __CUDA_HD__ BinaryOp() {} @@ -471,7 +470,7 @@ struct BinaryOp { template struct BinaryOp { - using VAL = legate::legate_type_of; + using VAL = legate::type_of; static constexpr bool valid = true; BinaryOp(const std::vector& args) @@ -503,7 +502,7 @@ struct BinaryOp { template struct BinaryOp { - using T = legate::legate_type_of; + using T = legate::type_of; static constexpr bool valid = std::is_integral::value; BinaryOp(const std::vector&) {} @@ -538,7 +537,7 @@ struct BinaryOp { template struct BinaryOp { - using T = legate::legate_type_of; + using T = legate::type_of; static constexpr bool valid = legate::is_floating_point::value; BinaryOp(const std::vector&) {} @@ -564,7 +563,7 @@ struct BinaryOp { template struct BinaryOp { - using T = legate::legate_type_of; + using T = legate::type_of; static constexpr bool valid = CODE != legate::Type::Code::BOOL && std::is_integral::value; BinaryOp(const std::vector&) {} @@ -580,20 +579,20 @@ struct BinaryOp { }; template -struct BinaryOp : std::less> { +struct BinaryOp : std::less> { static constexpr bool valid = true; BinaryOp(const std::vector&) {} }; template -struct BinaryOp : std::less_equal> { +struct BinaryOp : std::less_equal> { static constexpr bool valid = true; BinaryOp(const std::vector&) {} }; template struct BinaryOp { - using T = legate::legate_type_of; + using T = legate::type_of; static constexpr bool valid = legate::is_floating_point::value; __CUDA_HD__ BinaryOp() {} @@ -627,7 +626,7 @@ struct BinaryOp { template struct BinaryOp { - using T = legate::legate_type_of; + using T = legate::type_of; static constexpr bool valid = legate::is_floating_point::value; __CUDA_HD__ BinaryOp() {} @@ -660,7 +659,7 @@ struct BinaryOp { template struct BinaryOp { - using T = legate::legate_type_of; + using T = legate::type_of; static constexpr bool valid = true; BinaryOp(const std::vector&) {} @@ -679,7 +678,7 @@ struct BinaryOp { template struct BinaryOp { - using T = legate::legate_type_of; + using T = legate::type_of; static constexpr bool valid = true; BinaryOp(const std::vector&) {} @@ -699,7 +698,7 @@ struct BinaryOp { template struct BinaryOp { - using T = legate::legate_type_of; + using T = legate::type_of; static constexpr bool valid = true; BinaryOp(const std::vector&) {} @@ -718,7 +717,7 @@ struct BinaryOp { template struct BinaryOp { - using T = legate::legate_type_of; + using T = legate::type_of; static constexpr bool valid = true; BinaryOp(const std::vector&) {} constexpr T operator()(const T& a, const T& b) const { return std::max(a, b); } @@ -726,7 +725,7 @@ struct BinaryOp { template struct BinaryOp { - using T = legate::legate_type_of; + using T = legate::type_of; static constexpr bool valid = true; BinaryOp(const std::vector&) {} constexpr T operator()(const T& a, const T& b) const { return std::min(a, b); } @@ -746,7 +745,7 @@ constexpr T real_mod(const T& a, const T& b) template struct BinaryOp { - using T = legate::legate_type_of; + using T = legate::type_of; static constexpr bool valid = true; __CUDA_HD__ BinaryOp() {} BinaryOp(const std::vector&) {} @@ -796,14 +795,14 @@ struct BinaryOp { }; template -struct BinaryOp : std::multiplies> { +struct BinaryOp : std::multiplies> { static constexpr bool valid = true; BinaryOp(const std::vector&) {} }; template struct BinaryOp { - using T = legate::legate_type_of; + using T = legate::type_of; static constexpr bool valid = legate::is_floating_point::value; __CUDA_HD__ BinaryOp() {} BinaryOp(const std::vector&) {} @@ -828,14 +827,14 @@ struct BinaryOp { }; template -struct BinaryOp : std::not_equal_to> { +struct BinaryOp : std::not_equal_to> { static constexpr bool valid = true; BinaryOp(const std::vector&) {} }; template struct BinaryOp { - using VAL = legate::legate_type_of; + using VAL = legate::type_of; static constexpr bool valid = true; BinaryOp(const std::vector&) {} constexpr VAL operator()(const VAL& a, const VAL& b) const @@ -873,7 +872,7 @@ struct BinaryOp { template struct BinaryOp { - using T = legate::legate_type_of; + using T = legate::type_of; static constexpr bool valid = CODE != legate::Type::Code::BOOL && std::is_integral::value; BinaryOp(const std::vector&) {} @@ -882,14 +881,14 @@ struct BinaryOp { }; template -struct BinaryOp : std::minus> { +struct BinaryOp : std::minus> { static constexpr bool valid = true; BinaryOp(const std::vector&) {} }; template struct RHS2OfBinaryOp { - using type = legate::legate_type_of; + using type = legate::type_of; }; template diff --git a/src/cunumeric/binary/binary_red.cc b/src/cunumeric/binary/binary_red.cc index 47adecbf2c..6f96ab09a1 100644 --- a/src/cunumeric/binary/binary_red.cc +++ b/src/cunumeric/binary/binary_red.cc @@ -24,7 +24,7 @@ using namespace legate; template struct BinaryRedImplBody { using OP = BinaryOp; - using ARG = legate_type_of; + using ARG = type_of; template void operator()(OP func, diff --git a/src/cunumeric/binary/binary_red.cu b/src/cunumeric/binary/binary_red.cu index a0007cad9d..8702a9c91a 100644 --- a/src/cunumeric/binary/binary_red.cu +++ b/src/cunumeric/binary/binary_red.cu @@ -49,7 +49,7 @@ static __global__ void __launch_bounds__(1, 1) copy_kernel(Buffer result, RedAcc template struct BinaryRedImplBody { using OP = BinaryOp; - using ARG = legate_type_of; + using ARG = type_of; template void operator()(OP func, diff --git a/src/cunumeric/binary/binary_red.h b/src/cunumeric/binary/binary_red.h index 0b10f0c4af..749793839a 100644 --- a/src/cunumeric/binary/binary_red.h +++ b/src/cunumeric/binary/binary_red.h @@ -22,9 +22,9 @@ namespace cunumeric { struct BinaryRedArgs { - legate::Store out; - legate::Store in1; - legate::Store in2; + legate::PhysicalStore out; + legate::PhysicalStore in1; + legate::PhysicalStore in2; BinaryOpCode op_code; std::vector args; }; diff --git a/src/cunumeric/binary/binary_red_omp.cc b/src/cunumeric/binary/binary_red_omp.cc index 9237431f2f..4fb4ba6441 100644 --- a/src/cunumeric/binary/binary_red_omp.cc +++ b/src/cunumeric/binary/binary_red_omp.cc @@ -24,7 +24,7 @@ using namespace legate; template struct BinaryRedImplBody { using OP = BinaryOp; - using ARG = legate_type_of; + using ARG = type_of; template void operator()(OP func, diff --git a/src/cunumeric/binary/binary_red_template.inl b/src/cunumeric/binary/binary_red_template.inl index a6a34731ad..64d7b304aa 100644 --- a/src/cunumeric/binary/binary_red_template.inl +++ b/src/cunumeric/binary/binary_red_template.inl @@ -34,7 +34,7 @@ struct BinaryRedImpl { void operator()(BinaryRedArgs& args) const { using OP = BinaryOp; - using ARG = legate_type_of; + using ARG = type_of; // A technical note: unlike region-backed stores that are partitionable, future-backed stores // are not partitionable and replicated to all point tasks, including their metadata. diff --git a/src/cunumeric/bits/packbits.cc b/src/cunumeric/bits/packbits.cc index 98f4c1ad3d..40da4a0ccb 100644 --- a/src/cunumeric/bits/packbits.cc +++ b/src/cunumeric/bits/packbits.cc @@ -23,7 +23,7 @@ using namespace legate; template struct PackbitsImplBody { - using VAL = legate_type_of; + using VAL = type_of; void operator()(const AccessorWO& out, const AccessorRO& in, diff --git a/src/cunumeric/bits/packbits.cu b/src/cunumeric/bits/packbits.cu index 0f219b2380..5c3cfa7677 100644 --- a/src/cunumeric/bits/packbits.cu +++ b/src/cunumeric/bits/packbits.cu @@ -41,7 +41,7 @@ static __global__ void __launch_bounds__(THREADS_PER_BLOCK, MIN_CTAS_PER_SM) template struct PackbitsImplBody { - using VAL = legate_type_of; + using VAL = type_of; void operator()(const AccessorWO& out, const AccessorRO& in, diff --git a/src/cunumeric/bits/packbits_omp.cc b/src/cunumeric/bits/packbits_omp.cc index 59182c14f2..3e02642c11 100644 --- a/src/cunumeric/bits/packbits_omp.cc +++ b/src/cunumeric/bits/packbits_omp.cc @@ -23,7 +23,7 @@ using namespace legate; template struct PackbitsImplBody { - using VAL = legate_type_of; + using VAL = type_of; void operator()(const AccessorWO& out, const AccessorRO& in, diff --git a/src/cunumeric/bits/packbits_template.inl b/src/cunumeric/bits/packbits_template.inl index d70d8a7ccf..ffa2fefb48 100644 --- a/src/cunumeric/bits/packbits_template.inl +++ b/src/cunumeric/bits/packbits_template.inl @@ -30,9 +30,9 @@ struct PackbitsImplBody; template struct PackbitsImpl { template ::value>* = nullptr> - void operator()(legate::Store output, legate::Store input, uint32_t axis) const + void operator()(legate::PhysicalStore output, legate::PhysicalStore input, uint32_t axis) const { - using VAL = legate_type_of; + using VAL = type_of; auto out_rect = output.shape(); @@ -75,7 +75,7 @@ struct PackbitsImpl { } template ::value>* = nullptr> - void operator()(legate::Store output, legate::Store input, uint32_t axis) const + void operator()(legate::PhysicalStore output, legate::PhysicalStore input, uint32_t axis) const { // Unreachable assert(false); @@ -85,11 +85,11 @@ struct PackbitsImpl { template static void packbits_template(TaskContext& context) { - legate::Store output = context.output(0); - legate::Store input = context.input(0); - auto& scalars = context.scalars(); - auto axis = scalars[0].value(); - auto bitorder = scalars[1].value(); + legate::PhysicalStore output = context.output(0); + legate::PhysicalStore input = context.input(0); + auto& scalars = context.scalars(); + auto axis = scalars[0].value(); + auto bitorder = scalars[1].value(); auto code = input.code(); switch (bitorder) { diff --git a/src/cunumeric/bits/unpackbits_template.inl b/src/cunumeric/bits/unpackbits_template.inl index 5a3e6f85ca..64d831ab35 100644 --- a/src/cunumeric/bits/unpackbits_template.inl +++ b/src/cunumeric/bits/unpackbits_template.inl @@ -30,7 +30,7 @@ struct UnpackbitsImplBody; template struct UnpackbitsImpl { template - void operator()(Store output, Store input, uint32_t axis) const + void operator()(PhysicalStore output, PhysicalStore input, uint32_t axis) const { auto out_rect = output.shape(); @@ -48,7 +48,7 @@ struct UnpackbitsImpl { } template ::value>* = nullptr> - void operator()(legate::Store output, legate::Store input, uint32_t axis) const + void operator()(legate::PhysicalStore output, legate::PhysicalStore input, uint32_t axis) const { // Unreachable assert(false); @@ -58,11 +58,11 @@ struct UnpackbitsImpl { template static void unpackbits_template(TaskContext& context) { - legate::Store output = context.output(0); - legate::Store input = context.input(0); - auto& scalars = context.scalars(); - auto axis = scalars[0].value(); - auto bitorder = scalars[1].value(); + legate::PhysicalStore output = context.output(0); + legate::PhysicalStore input = context.input(0); + auto& scalars = context.scalars(); + auto axis = scalars[0].value(); + auto bitorder = scalars[1].value(); switch (bitorder) { case Bitorder::BIG: { diff --git a/src/cunumeric/convolution/convolve.cc b/src/cunumeric/convolution/convolve.cc index b3dce4d1c4..7b2f69371a 100644 --- a/src/cunumeric/convolution/convolve.cc +++ b/src/cunumeric/convolution/convolve.cc @@ -26,7 +26,7 @@ namespace cunumeric { #if 0 template struct ConvolveImplBody { - using VAL = legate_type_of; + using VAL = type_of; void operator()(AccessorWO out, AccessorRO filter, @@ -75,7 +75,7 @@ struct ConvolveImplBody { template struct ConvolveImplBody { - using VAL = legate_type_of; + using VAL = type_of; void operator()(AccessorWO out, AccessorRO filter, diff --git a/src/cunumeric/convolution/convolve.cu b/src/cunumeric/convolution/convolve.cu index 9e5b4e171e..8c381d6434 100644 --- a/src/cunumeric/convolution/convolve.cu +++ b/src/cunumeric/convolution/convolve.cu @@ -1408,7 +1408,7 @@ struct UseCUFFT { template struct ConvolveImplBody { - using VAL = legate_type_of; + using VAL = type_of; template ::value>* = nullptr> __host__ void dispatch(AccessorWO<_VAL, _DIM> out, diff --git a/src/cunumeric/convolution/convolve.h b/src/cunumeric/convolution/convolve.h index cde7e36e1f..ae0c45b898 100644 --- a/src/cunumeric/convolution/convolve.h +++ b/src/cunumeric/convolution/convolve.h @@ -30,9 +30,9 @@ namespace cunumeric { struct ConvolveArgs { - legate::Store out; - legate::Store filter; - std::vector inputs; + legate::PhysicalStore out; + legate::PhysicalStore filter; + std::vector inputs; legate::Domain root_domain; }; diff --git a/src/cunumeric/convolution/convolve_omp.cc b/src/cunumeric/convolution/convolve_omp.cc index f59bb5921d..2dc8cf7b4a 100644 --- a/src/cunumeric/convolution/convolve_omp.cc +++ b/src/cunumeric/convolution/convolve_omp.cc @@ -26,7 +26,7 @@ using namespace legate; template struct ConvolveImplBody { - using VAL = legate_type_of; + using VAL = type_of; void operator()(AccessorWO out, AccessorRO filter, diff --git a/src/cunumeric/convolution/convolve_template.inl b/src/cunumeric/convolution/convolve_template.inl index d9402936b8..9293477e8a 100644 --- a/src/cunumeric/convolution/convolve_template.inl +++ b/src/cunumeric/convolution/convolve_template.inl @@ -34,7 +34,7 @@ struct ConvolveImpl { template * = nullptr> void operator()(ConvolveArgs& args) const { - using VAL = legate_type_of; + using VAL = type_of; auto subrect = args.out.shape(); auto filter_rect = args.filter.shape(); diff --git a/src/cunumeric/fft/fft.cu b/src/cunumeric/fft/fft.cu index 25cf7fa26e..9ce5b21cdd 100644 --- a/src/cunumeric/fft/fft.cu +++ b/src/cunumeric/fft/fft.cu @@ -330,8 +330,8 @@ __host__ static inline void cufft_over_axes(AccessorWO out, template struct FFTImplBody { - using INPUT_TYPE = legate_type_of; - using OUTPUT_TYPE = legate_type_of; + using INPUT_TYPE = type_of; + using OUTPUT_TYPE = type_of; __host__ void operator()(AccessorWO out, AccessorRO in, diff --git a/src/cunumeric/fft/fft.h b/src/cunumeric/fft/fft.h index 680da5bfc1..2af433a4c4 100644 --- a/src/cunumeric/fft/fft.h +++ b/src/cunumeric/fft/fft.h @@ -22,8 +22,8 @@ namespace cunumeric { struct FFTArgs { - legate::Store output; - legate::Store input; + legate::PhysicalStore output; + legate::PhysicalStore input; CuNumericFFTType type; CuNumericFFTDirection direction; bool operate_over_axes; diff --git a/src/cunumeric/fft/fft_template.inl b/src/cunumeric/fft/fft_template.inl index 430d0b1acc..c7d5b17b9a 100644 --- a/src/cunumeric/fft/fft_template.inl +++ b/src/cunumeric/fft/fft_template.inl @@ -39,8 +39,8 @@ struct FFTImpl { std::enable_if_t<(FFT::valid)>* = nullptr> void operator()(FFTArgs& args) const { - using INPUT_TYPE = legate_type_of; - using OUTPUT_TYPE = legate_type_of::CODE_OUT>; + using INPUT_TYPE = type_of; + using OUTPUT_TYPE = type_of::CODE_OUT>; auto in_rect = args.input.shape(); auto out_rect = args.output.shape(); diff --git a/src/cunumeric/index/advanced_indexing.cc b/src/cunumeric/index/advanced_indexing.cc index 369114e52b..bdd664acaa 100644 --- a/src/cunumeric/index/advanced_indexing.cc +++ b/src/cunumeric/index/advanced_indexing.cc @@ -23,7 +23,7 @@ using namespace legate; template struct AdvancedIndexingImplBody { - using VAL = legate_type_of; + using VAL = type_of; template void compute_output(Buffer& out, @@ -56,7 +56,7 @@ struct AdvancedIndexingImplBody { } } - void operator()(legate::Store& out_arr, + void operator()(legate::PhysicalStore& out_arr, const AccessorRO& input, const AccessorRO& index, const Pitches& pitches, diff --git a/src/cunumeric/index/advanced_indexing.cu b/src/cunumeric/index/advanced_indexing.cu index 7edb49bd9d..66e8392d0c 100644 --- a/src/cunumeric/index/advanced_indexing.cu +++ b/src/cunumeric/index/advanced_indexing.cu @@ -77,7 +77,7 @@ static __global__ void __launch_bounds__(THREADS_PER_BLOCK, MIN_CTAS_PER_SM) template struct AdvancedIndexingImplBody { - using VAL = legate_type_of; + using VAL = type_of; int64_t compute_size(const AccessorRO& in, const Pitches& pitches, @@ -110,7 +110,7 @@ struct AdvancedIndexingImplBody { return size.read(stream); } - void operator()(Store& out_arr, + void operator()(PhysicalStore& out_arr, const AccessorRO& input, const AccessorRO& index, const Pitches& pitches, diff --git a/src/cunumeric/index/advanced_indexing.h b/src/cunumeric/index/advanced_indexing.h index dfe6c27f5e..c5d7997fc2 100644 --- a/src/cunumeric/index/advanced_indexing.h +++ b/src/cunumeric/index/advanced_indexing.h @@ -21,9 +21,9 @@ namespace cunumeric { struct AdvancedIndexingArgs { - legate::Store output; - legate::Store input_array; - legate::Store indexing_array; + legate::PhysicalStore output; + legate::PhysicalStore input_array; + legate::PhysicalStore indexing_array; const bool is_set; const int64_t key_dim; }; diff --git a/src/cunumeric/index/advanced_indexing_omp.cc b/src/cunumeric/index/advanced_indexing_omp.cc index 4e715a8a30..e14e6c9286 100644 --- a/src/cunumeric/index/advanced_indexing_omp.cc +++ b/src/cunumeric/index/advanced_indexing_omp.cc @@ -28,7 +28,7 @@ using namespace legate; template struct AdvancedIndexingImplBody { - using VAL = legate_type_of; + using VAL = type_of; size_t compute_output_offsets(ThreadLocalStorage& offsets, const AccessorRO& index, @@ -58,7 +58,7 @@ struct AdvancedIndexingImplBody { return size; } - void operator()(Store& out_arr, + void operator()(PhysicalStore& out_arr, const AccessorRO& input, const AccessorRO& index, const Pitches& pitches, diff --git a/src/cunumeric/index/advanced_indexing_template.inl b/src/cunumeric/index/advanced_indexing_template.inl index 22ee033e24..0f129956f3 100644 --- a/src/cunumeric/index/advanced_indexing_template.inl +++ b/src/cunumeric/index/advanced_indexing_template.inl @@ -34,7 +34,7 @@ struct AdvancedIndexingImpl { template void operator()(AdvancedIndexingArgs& args) const { - using VAL = legate_type_of; + using VAL = type_of; auto input_rect = args.input_array.shape(); auto input_arr = args.input_array.read_accessor(input_rect); Pitches input_pitches; diff --git a/src/cunumeric/index/choose.cc b/src/cunumeric/index/choose.cc index d73da8eea8..41cc46f537 100644 --- a/src/cunumeric/index/choose.cc +++ b/src/cunumeric/index/choose.cc @@ -23,7 +23,7 @@ using namespace legate; template struct ChooseImplBody { - using VAL = legate_type_of; + using VAL = type_of; void operator()(const AccessorWO& out, const AccessorRO& index_arr, diff --git a/src/cunumeric/index/choose.cu b/src/cunumeric/index/choose.cu index 9920007ff6..e6b59e313a 100644 --- a/src/cunumeric/index/choose.cu +++ b/src/cunumeric/index/choose.cu @@ -47,7 +47,7 @@ __global__ static void __launch_bounds__(THREADS_PER_BLOCK, MIN_CTAS_PER_SM) cho template struct ChooseImplBody { - using VAL = legate_type_of; + using VAL = type_of; void operator()(const AccessorWO& out, const AccessorRO& index_arr, diff --git a/src/cunumeric/index/choose.h b/src/cunumeric/index/choose.h index e865b233c0..690fe0012f 100644 --- a/src/cunumeric/index/choose.h +++ b/src/cunumeric/index/choose.h @@ -21,8 +21,8 @@ namespace cunumeric { struct ChooseArgs { - legate::Store out; - std::vector inputs; + legate::PhysicalStore out; + std::vector inputs; }; class ChooseTask : public CuNumericTask { diff --git a/src/cunumeric/index/choose_omp.cc b/src/cunumeric/index/choose_omp.cc index b6dde5b3f0..2b09a1bcad 100644 --- a/src/cunumeric/index/choose_omp.cc +++ b/src/cunumeric/index/choose_omp.cc @@ -23,7 +23,7 @@ using namespace legate; template struct ChooseImplBody { - using VAL = legate_type_of; + using VAL = type_of; void operator()(const AccessorWO& out, const AccessorRO& index_arr, diff --git a/src/cunumeric/index/choose_template.inl b/src/cunumeric/index/choose_template.inl index 420c1fda94..dadf4db7a9 100644 --- a/src/cunumeric/index/choose_template.inl +++ b/src/cunumeric/index/choose_template.inl @@ -32,7 +32,7 @@ struct ChooseImpl { template void operator()(ChooseArgs& args) const { - using VAL = legate_type_of; + using VAL = type_of; auto out_rect = args.out.shape(); Pitches pitches; @@ -64,7 +64,7 @@ struct ChooseImpl { template static void choose_template(TaskContext& context) { - std::vector inputs; + std::vector inputs; for (auto& input : context.inputs()) { inputs.emplace_back(input); } ChooseArgs args{context.output(0), std::move(inputs)}; double_dispatch(args.inputs[0].dim(), args.inputs[0].code(), ChooseImpl{}, args); diff --git a/src/cunumeric/index/putmask.h b/src/cunumeric/index/putmask.h index 8cfb7ba651..d8900c4ddf 100644 --- a/src/cunumeric/index/putmask.h +++ b/src/cunumeric/index/putmask.h @@ -21,9 +21,9 @@ namespace cunumeric { struct PutmaskArgs { - legate::Store input; - legate::Store mask; - legate::Store values; + legate::PhysicalStore input; + legate::PhysicalStore mask; + legate::PhysicalStore values; }; class PutmaskTask : public CuNumericTask { diff --git a/src/cunumeric/index/putmask_template.inl b/src/cunumeric/index/putmask_template.inl index 216908b116..e0545e104c 100644 --- a/src/cunumeric/index/putmask_template.inl +++ b/src/cunumeric/index/putmask_template.inl @@ -28,7 +28,7 @@ using namespace legate; template struct Putmask { - using T = legate_type_of; + using T = type_of; using IN = AccessorRW; using MASK = AccessorRO; using VALUES = AccessorRO; diff --git a/src/cunumeric/index/repeat.cc b/src/cunumeric/index/repeat.cc index 4853a48fa8..dc264cb7d6 100644 --- a/src/cunumeric/index/repeat.cc +++ b/src/cunumeric/index/repeat.cc @@ -23,9 +23,9 @@ using namespace legate; template struct RepeatImplBody { - using VAL = legate_type_of; + using VAL = type_of; - void operator()(legate::Store& out_array, + void operator()(legate::PhysicalStore& out_array, const AccessorRO& in, const int64_t repeats, const int32_t axis, @@ -49,7 +49,7 @@ struct RepeatImplBody { } } - void operator()(legate::Store& out_array, + void operator()(legate::PhysicalStore& out_array, const AccessorRO& in, const AccessorRO& repeats, const int32_t axis, @@ -73,7 +73,7 @@ struct RepeatImplBody { } template 1)>* = nullptr> - void operator()(legate::Store& out_array, + void operator()(legate::PhysicalStore& out_array, const AccessorRO& in, const AccessorRO& repeats, const int32_t axis, diff --git a/src/cunumeric/index/repeat.cu b/src/cunumeric/index/repeat.cu index 6368947450..fabee78bbb 100644 --- a/src/cunumeric/index/repeat.cu +++ b/src/cunumeric/index/repeat.cu @@ -95,9 +95,9 @@ __global__ static void __launch_bounds__(THREADS_PER_BLOCK, MIN_CTAS_PER_SM) template struct RepeatImplBody { - using VAL = legate_type_of; + using VAL = type_of; - void operator()(legate::Store& out_array, + void operator()(legate::PhysicalStore& out_array, const AccessorRO& in, const int64_t repeats, const int32_t axis, @@ -120,7 +120,7 @@ struct RepeatImplBody { CHECK_CUDA_STREAM(stream); } - void operator()(legate::Store& out_array, + void operator()(legate::PhysicalStore& out_array, const AccessorRO& in, const AccessorRO& repeats, const int32_t axis, diff --git a/src/cunumeric/index/repeat.h b/src/cunumeric/index/repeat.h index 88ff59f686..74ffa75046 100644 --- a/src/cunumeric/index/repeat.h +++ b/src/cunumeric/index/repeat.h @@ -21,9 +21,9 @@ namespace cunumeric { struct RepeatArgs { - legate::Store output; - legate::Store input; - legate::Store repeats_arr; + legate::PhysicalStore output; + legate::PhysicalStore input; + legate::PhysicalStore repeats_arr; int64_t repeats; int32_t axis; const bool scalar_repeats; diff --git a/src/cunumeric/index/repeat_omp.cc b/src/cunumeric/index/repeat_omp.cc index c41fc092e3..6be45a5352 100644 --- a/src/cunumeric/index/repeat_omp.cc +++ b/src/cunumeric/index/repeat_omp.cc @@ -28,9 +28,9 @@ using namespace legate; template struct RepeatImplBody { - using VAL = legate_type_of; + using VAL = type_of; - void operator()(legate::Store& out_array, + void operator()(legate::PhysicalStore& out_array, const AccessorRO& in, const int64_t repeats, const int32_t axis, @@ -55,7 +55,7 @@ struct RepeatImplBody { } } - void operator()(legate::Store& out_array, + void operator()(legate::PhysicalStore& out_array, const AccessorRO& in, const AccessorRO& repeats, const int32_t axis, diff --git a/src/cunumeric/index/repeat_template.inl b/src/cunumeric/index/repeat_template.inl index 9e0980ec1b..6f06fac2d3 100644 --- a/src/cunumeric/index/repeat_template.inl +++ b/src/cunumeric/index/repeat_template.inl @@ -32,7 +32,7 @@ struct RepeatImpl { template void operator()(RepeatArgs& args) const { - using VAL = legate_type_of; + using VAL = type_of; auto input_rect = args.input.shape(); auto input_arr = args.input.read_accessor(input_rect); @@ -59,7 +59,7 @@ static void repeat_template(TaskContext& context) if (scalar_repeats) { auto repeats = context.scalar(2).value(); RepeatArgs args{ - context.output(0), context.input(0), legate::Store(), repeats, axis, scalar_repeats}; + context.output(0), context.input(0), legate::PhysicalStore(), repeats, axis, scalar_repeats}; double_dispatch(args.input.dim(), args.input.code(), RepeatImpl{}, args); } else { auto repeats = context.input(1); diff --git a/src/cunumeric/index/wrap.h b/src/cunumeric/index/wrap.h index 62b18b0665..b00dfcd810 100644 --- a/src/cunumeric/index/wrap.h +++ b/src/cunumeric/index/wrap.h @@ -21,13 +21,13 @@ namespace cunumeric { struct WrapArgs { - legate::Store out; // Array with Point type that is used to + legate::PhysicalStore out; // Array with Point type that is used to // copy information from original array to the // `wrapped` one const legate::DomainPoint shape; // shape of the original array const bool has_input; const bool check_bounds; - legate::Store in = legate::Store(); + legate::PhysicalStore in = legate::PhysicalStore(); }; class WrapTask : public CuNumericTask { diff --git a/src/cunumeric/index/wrap_template.inl b/src/cunumeric/index/wrap_template.inl index 890ab7d796..42e85a0d73 100644 --- a/src/cunumeric/index/wrap_template.inl +++ b/src/cunumeric/index/wrap_template.inl @@ -82,7 +82,7 @@ static void wrap_template(TaskContext& context) int dim = shape.dim; bool has_input = context.scalar(1).value(); bool check_bounds = context.scalar(2).value(); - legate::Store tmp_array{}; + legate::PhysicalStore tmp_array{}; WrapArgs args{ context.output(0), shape, has_input, check_bounds, has_input ? context.input(0) : tmp_array}; dim_dispatch(dim, WrapImpl{}, args); diff --git a/src/cunumeric/index/zip.h b/src/cunumeric/index/zip.h index e7e330073f..0e0eff4db8 100644 --- a/src/cunumeric/index/zip.h +++ b/src/cunumeric/index/zip.h @@ -21,8 +21,8 @@ namespace cunumeric { struct ZipArgs { - legate::Store out; - std::vector inputs; + legate::PhysicalStore out; + std::vector inputs; const int64_t N; const int64_t key_dim; const int64_t start_index; diff --git a/src/cunumeric/index/zip_template.inl b/src/cunumeric/index/zip_template.inl index bf2968e324..5c5b0a41e6 100644 --- a/src/cunumeric/index/zip_template.inl +++ b/src/cunumeric/index/zip_template.inl @@ -92,7 +92,7 @@ static void zip_template(TaskContext& context) int64_t key_dim = context.scalar(1).value(); int64_t start_index = context.scalar(2).value(); auto shape = context.scalar(3).value(); - std::vector inputs; + std::vector inputs; for (auto& input : context.inputs()) { inputs.emplace_back(input); } ZipArgs args{context.output(0), std::move(inputs), N, key_dim, start_index, shape}; int dim = args.inputs[0].dim(); diff --git a/src/cunumeric/item/read_template.inl b/src/cunumeric/item/read_template.inl index 64d1241be4..83468dce41 100644 --- a/src/cunumeric/item/read_template.inl +++ b/src/cunumeric/item/read_template.inl @@ -29,9 +29,9 @@ struct ReadImplBody; template struct ReadImpl { template - void operator()(legate::Store out_arr, legate::Store in_arr) const + void operator()(legate::PhysicalStore out_arr, legate::PhysicalStore in_arr) const { - using VAL = legate_type_of; + using VAL = type_of; auto out = out_arr.write_accessor(); auto in = in_arr.read_accessor(); ReadImplBody()(out, in); diff --git a/src/cunumeric/item/write_template.inl b/src/cunumeric/item/write_template.inl index 2a6c766fed..fa1b9b13ec 100644 --- a/src/cunumeric/item/write_template.inl +++ b/src/cunumeric/item/write_template.inl @@ -29,9 +29,9 @@ struct WriteImplBody; template struct WriteImpl { template - void operator()(legate::Store out_arr, legate::Store in_arr) const + void operator()(legate::PhysicalStore out_arr, legate::PhysicalStore in_arr) const { - using VAL = legate_type_of; + using VAL = type_of; auto out = out_arr.write_accessor(); auto in = in_arr.read_accessor(); WriteImplBody()(out, in); diff --git a/src/cunumeric/matrix/contract.h b/src/cunumeric/matrix/contract.h index 63b9282aee..3b510fa92b 100644 --- a/src/cunumeric/matrix/contract.h +++ b/src/cunumeric/matrix/contract.h @@ -21,9 +21,9 @@ namespace cunumeric { struct ContractArgs { - legate::Store lhs; - legate::Store rhs1; - legate::Store rhs2; + legate::PhysicalStore lhs; + legate::PhysicalStore rhs1; + legate::PhysicalStore rhs2; legate::Span lhs_dim_mask; legate::Span rhs1_dim_mask; legate::Span rhs2_dim_mask; diff --git a/src/cunumeric/matrix/contract_template.inl b/src/cunumeric/matrix/contract_template.inl index 4ce3042742..d84ab1bfdc 100644 --- a/src/cunumeric/matrix/contract_template.inl +++ b/src/cunumeric/matrix/contract_template.inl @@ -80,7 +80,7 @@ struct ContractImpl { template ::value>* = nullptr> void operator()(ContractArgs& args) const { - using T = legate_type_of; + using T = type_of; std::vector lhs_shape; std::vector lhs_strides; diff --git a/src/cunumeric/matrix/diag.cc b/src/cunumeric/matrix/diag.cc index ef612ca614..c9ef6b3fa3 100644 --- a/src/cunumeric/matrix/diag.cc +++ b/src/cunumeric/matrix/diag.cc @@ -23,7 +23,7 @@ using namespace legate; template struct DiagImplBody { - using VAL = legate_type_of; + using VAL = type_of; void operator()(const AccessorRD, true, DIM>& out, const AccessorRO& in, @@ -54,7 +54,7 @@ struct DiagImplBody { // not extract (create a new 2D matrix with diagonal from vector) template struct DiagImplBody { - using VAL = legate_type_of; + using VAL = type_of; void operator()(const AccessorRO& in, const AccessorRW& out, diff --git a/src/cunumeric/matrix/diag.cu b/src/cunumeric/matrix/diag.cu index eabbb4cc06..bbfba2882f 100644 --- a/src/cunumeric/matrix/diag.cu +++ b/src/cunumeric/matrix/diag.cu @@ -62,7 +62,7 @@ __global__ static void __launch_bounds__(THREADS_PER_BLOCK, MIN_CTAS_PER_SM) template struct DiagImplBody { - using VAL = legate_type_of; + using VAL = type_of; void operator()(const AccessorRD, true, DIM>& out, const AccessorRO& in, @@ -94,7 +94,7 @@ struct DiagImplBody { // not extract (create a new 2D matrix with diagonal from vector) template struct DiagImplBody { - using VAL = legate_type_of; + using VAL = type_of; void operator()(const AccessorRO& in, const AccessorRW& out, diff --git a/src/cunumeric/matrix/diag.h b/src/cunumeric/matrix/diag.h index 45ae2fc98d..1f8d3a5826 100644 --- a/src/cunumeric/matrix/diag.h +++ b/src/cunumeric/matrix/diag.h @@ -23,8 +23,8 @@ namespace cunumeric { struct DiagArgs { int naxes; bool extract; - legate::Store matrix; - legate::Store diag; + legate::PhysicalStore matrix; + legate::PhysicalStore diag; }; class DiagTask : public CuNumericTask { diff --git a/src/cunumeric/matrix/diag_omp.cc b/src/cunumeric/matrix/diag_omp.cc index 3e90457bdc..5d4ddc7d46 100644 --- a/src/cunumeric/matrix/diag_omp.cc +++ b/src/cunumeric/matrix/diag_omp.cc @@ -23,7 +23,7 @@ using namespace legate; template struct DiagImplBody { - using VAL = legate_type_of; + using VAL = type_of; void operator()(const AccessorRD, true, DIM>& out, const AccessorRO& in, @@ -59,7 +59,7 @@ struct DiagImplBody { // not extract (create a new 2D matrix with diagonal from vector) template struct DiagImplBody { - using VAL = legate_type_of; + using VAL = type_of; void operator()(const AccessorRO& in, const AccessorRW& out, diff --git a/src/cunumeric/matrix/diag_template.inl b/src/cunumeric/matrix/diag_template.inl index ea493d4353..0c9053a122 100644 --- a/src/cunumeric/matrix/diag_template.inl +++ b/src/cunumeric/matrix/diag_template.inl @@ -32,7 +32,7 @@ struct DiagImpl { template void operator()(DiagArgs& args) const { - using VAL = legate_type_of; + using VAL = type_of; if (args.extract) { auto shape_in = args.matrix.shape(); @@ -101,10 +101,10 @@ struct DiagImpl { template static void diag_template(TaskContext& context) { - int naxes = context.scalar(0).value(); - bool extract = context.scalar(1).value(); - legate::Store matrix = extract ? context.input(0) : context.output(0); - legate::Store diag = extract ? context.reduction(0) : context.input(0); + int naxes = context.scalar(0).value(); + bool extract = context.scalar(1).value(); + legate::PhysicalStore matrix = extract ? context.input(0) : context.output(0); + legate::PhysicalStore diag = extract ? context.reduction(0) : context.input(0); DiagArgs args{naxes, extract, matrix, diag}; double_dispatch(matrix.dim(), matrix.code(), DiagImpl{}, args); } diff --git a/src/cunumeric/matrix/dot.cc b/src/cunumeric/matrix/dot.cc index b57b7771f1..c83b9ebf83 100644 --- a/src/cunumeric/matrix/dot.cc +++ b/src/cunumeric/matrix/dot.cc @@ -23,7 +23,7 @@ using namespace legate; template struct DotImplBody { - using VAL = legate_type_of; + using VAL = type_of; using ACC = acc_type_of; template diff --git a/src/cunumeric/matrix/dot.cu b/src/cunumeric/matrix/dot.cu index 9f22d447eb..57539c01a7 100644 --- a/src/cunumeric/matrix/dot.cu +++ b/src/cunumeric/matrix/dot.cu @@ -45,7 +45,7 @@ static __global__ void __launch_bounds__(1, 1) copy_kernel(Buffer result, RedAcc template struct DotImplBody { - using VAL = legate_type_of; + using VAL = type_of; using ACC = acc_type_of; template diff --git a/src/cunumeric/matrix/dot.h b/src/cunumeric/matrix/dot.h index 50d787f2df..7839a06e20 100644 --- a/src/cunumeric/matrix/dot.h +++ b/src/cunumeric/matrix/dot.h @@ -21,9 +21,9 @@ namespace cunumeric { struct DotArgs { - legate::Store lhs; - legate::Store rhs1; - legate::Store rhs2; + legate::PhysicalStore lhs; + legate::PhysicalStore rhs1; + legate::PhysicalStore rhs2; }; class DotTask : public CuNumericTask { diff --git a/src/cunumeric/matrix/dot_omp.cc b/src/cunumeric/matrix/dot_omp.cc index 33d0235c66..261e6666eb 100644 --- a/src/cunumeric/matrix/dot_omp.cc +++ b/src/cunumeric/matrix/dot_omp.cc @@ -26,7 +26,7 @@ using namespace legate; template struct DotImplBody { - using VAL = legate_type_of; + using VAL = type_of; using ACC = acc_type_of; template diff --git a/src/cunumeric/matrix/dot_template.inl b/src/cunumeric/matrix/dot_template.inl index cf801a4e11..8305ccd6be 100644 --- a/src/cunumeric/matrix/dot_template.inl +++ b/src/cunumeric/matrix/dot_template.inl @@ -44,7 +44,7 @@ struct DotImpl { template void operator()(DotArgs& args) const { - using VAL = legate_type_of; + using VAL = type_of; using ACC = acc_type_of; assert(args.rhs1.dim() == 1); diff --git a/src/cunumeric/matrix/gemm_template.inl b/src/cunumeric/matrix/gemm_template.inl index fbf6215f07..9a717a9a46 100644 --- a/src/cunumeric/matrix/gemm_template.inl +++ b/src/cunumeric/matrix/gemm_template.inl @@ -40,9 +40,11 @@ struct support_gemm : std::true_type {}; template struct GemmImpl { template ::value>* = nullptr> - void operator()(legate::Store lhs_array, legate::Store rhs1_array, legate::Store rhs2_array) const + void operator()(legate::PhysicalStore lhs_array, + legate::PhysicalStore rhs1_array, + legate::PhysicalStore rhs2_array) const { - using VAL = legate_type_of; + using VAL = type_of; auto lhs_shape = lhs_array.shape<2>(); auto rhs1_shape = rhs1_array.shape<2>(); @@ -68,7 +70,9 @@ struct GemmImpl { } template ::value>* = nullptr> - void operator()(legate::Store lhs_array, legate::Store rhs1_array, legate::Store rhs2_array) const + void operator()(legate::PhysicalStore lhs_array, + legate::PhysicalStore rhs1_array, + legate::PhysicalStore rhs2_array) const { assert(false); } diff --git a/src/cunumeric/matrix/matmul.h b/src/cunumeric/matrix/matmul.h index e93553af64..f47cb6243d 100644 --- a/src/cunumeric/matrix/matmul.h +++ b/src/cunumeric/matrix/matmul.h @@ -21,9 +21,9 @@ namespace cunumeric { struct MatMulArgs { - legate::Store lhs; - legate::Store rhs1; - legate::Store rhs2; + legate::PhysicalStore lhs; + legate::PhysicalStore rhs1; + legate::PhysicalStore rhs2; }; class MatMulTask : public CuNumericTask { diff --git a/src/cunumeric/matrix/matmul_template.inl b/src/cunumeric/matrix/matmul_template.inl index 8c72614aae..1f01dea9d0 100644 --- a/src/cunumeric/matrix/matmul_template.inl +++ b/src/cunumeric/matrix/matmul_template.inl @@ -55,7 +55,7 @@ struct MatMulImpl { template ::value>* = nullptr> void operator()(MatMulArgs& args) const { - using VAL = legate_type_of; + using VAL = type_of; using ACC = typename support_matmul::ACC_TYPE; // Note that rhs1 and rhs2 may have different shapes. Here's why: rhs1 and rhs2 are promoted diff --git a/src/cunumeric/matrix/matvecmul.h b/src/cunumeric/matrix/matvecmul.h index abca3c6465..d816f2be3e 100644 --- a/src/cunumeric/matrix/matvecmul.h +++ b/src/cunumeric/matrix/matvecmul.h @@ -21,9 +21,9 @@ namespace cunumeric { struct MatVecMulArgs { - legate::Store lhs; - legate::Store rhs1; - legate::Store rhs2; + legate::PhysicalStore lhs; + legate::PhysicalStore rhs1; + legate::PhysicalStore rhs2; }; class MatVecMulTask : public CuNumericTask { diff --git a/src/cunumeric/matrix/matvecmul_template.inl b/src/cunumeric/matrix/matvecmul_template.inl index e6868d2aec..29d82d3d8a 100644 --- a/src/cunumeric/matrix/matvecmul_template.inl +++ b/src/cunumeric/matrix/matvecmul_template.inl @@ -55,7 +55,7 @@ struct MatVecMulImpl { template ::value>* = nullptr> void operator()(MatVecMulArgs& args) const { - using VAL = legate_type_of; + using VAL = type_of; using ACC = typename support_matvecmul::ACC_TYPE; auto shape = args.rhs1.shape<2>().intersection(args.rhs2.shape<2>()); diff --git a/src/cunumeric/matrix/potrf_template.inl b/src/cunumeric/matrix/potrf_template.inl index b4280d824d..68af3ed118 100644 --- a/src/cunumeric/matrix/potrf_template.inl +++ b/src/cunumeric/matrix/potrf_template.inl @@ -40,9 +40,9 @@ struct support_potrf : std::true_type {}; template struct PotrfImpl { template ::value>* = nullptr> - void operator()(legate::Store array) const + void operator()(legate::PhysicalStore array) const { - using VAL = legate_type_of; + using VAL = type_of; auto shape = array.shape<2>(); @@ -59,7 +59,7 @@ struct PotrfImpl { } template ::value>* = nullptr> - void operator()(legate::Store array) const + void operator()(legate::PhysicalStore array) const { assert(false); } diff --git a/src/cunumeric/matrix/solve_template.inl b/src/cunumeric/matrix/solve_template.inl index f3c182713a..a4fc50dbcb 100644 --- a/src/cunumeric/matrix/solve_template.inl +++ b/src/cunumeric/matrix/solve_template.inl @@ -42,9 +42,9 @@ struct support_solve : std::true_type {}; template struct SolveImpl { template ::value>* = nullptr> - void operator()(legate::Store a_array, legate::Store b_array) const + void operator()(legate::PhysicalStore a_array, legate::PhysicalStore b_array) const { - using VAL = legate_type_of; + using VAL = type_of; #ifdef DEBUG_CUNUMERIC assert(a_array.dim() == 2); @@ -96,7 +96,7 @@ struct SolveImpl { } template ::value>* = nullptr> - void operator()(legate::Store a_array, legate::Store b_array) const + void operator()(legate::PhysicalStore a_array, legate::PhysicalStore b_array) const { assert(false); } diff --git a/src/cunumeric/matrix/syrk_template.inl b/src/cunumeric/matrix/syrk_template.inl index 6b043d57c4..960a283eb2 100644 --- a/src/cunumeric/matrix/syrk_template.inl +++ b/src/cunumeric/matrix/syrk_template.inl @@ -40,9 +40,9 @@ struct support_syrk : std::true_type {}; template struct SyrkImpl { template ::value>* = nullptr> - void operator()(legate::Store lhs_array, legate::Store rhs_array) const + void operator()(legate::PhysicalStore lhs_array, legate::PhysicalStore rhs_array) const { - using VAL = legate_type_of; + using VAL = type_of; auto lhs_shape = lhs_array.shape<2>(); auto rhs_shape = rhs_array.shape<2>(); @@ -63,7 +63,7 @@ struct SyrkImpl { } template ::value>* = nullptr> - void operator()(legate::Store lhs_array, legate::Store rhs_array) const + void operator()(legate::PhysicalStore lhs_array, legate::PhysicalStore rhs_array) const { assert(false); } diff --git a/src/cunumeric/matrix/tile.h b/src/cunumeric/matrix/tile.h index 007387fe39..af853db817 100644 --- a/src/cunumeric/matrix/tile.h +++ b/src/cunumeric/matrix/tile.h @@ -21,8 +21,8 @@ namespace cunumeric { struct TileArgs { - legate::Store in; - legate::Store out; + legate::PhysicalStore in; + legate::PhysicalStore out; }; class TileTask : public CuNumericTask { diff --git a/src/cunumeric/matrix/tile_template.inl b/src/cunumeric/matrix/tile_template.inl index 1987c2799c..8985ba33ae 100644 --- a/src/cunumeric/matrix/tile_template.inl +++ b/src/cunumeric/matrix/tile_template.inl @@ -70,7 +70,7 @@ struct TileDispatch { template void operator()(TileArgs& args) const { - using VAL = legate_type_of; + using VAL = type_of; double_dispatch(args.out.dim(), args.in.dim(), TileImpl{}, args); } }; diff --git a/src/cunumeric/matrix/transpose.cc b/src/cunumeric/matrix/transpose.cc index 1fe0f5f496..7749ec32e9 100644 --- a/src/cunumeric/matrix/transpose.cc +++ b/src/cunumeric/matrix/transpose.cc @@ -28,7 +28,7 @@ using namespace legate; template struct TransposeImplBody { - using VAL = legate_type_of; + using VAL = type_of; void operator()(const Rect<2>& out_rect, const Rect<2>& in_rect, diff --git a/src/cunumeric/matrix/transpose.cu b/src/cunumeric/matrix/transpose.cu index ff7f340e4c..d8a71a3de0 100644 --- a/src/cunumeric/matrix/transpose.cu +++ b/src/cunumeric/matrix/transpose.cu @@ -138,7 +138,7 @@ __global__ static void __launch_bounds__((TILE_DIM * BLOCK_ROWS), MIN_CTAS_PER_S template struct TransposeImplBody { - using VAL = legate_type_of; + using VAL = type_of; void operator()(const Rect<2>& out_rect, const Rect<2>& in_rect, diff --git a/src/cunumeric/matrix/transpose.h b/src/cunumeric/matrix/transpose.h index 12ca568310..a24d53eac5 100644 --- a/src/cunumeric/matrix/transpose.h +++ b/src/cunumeric/matrix/transpose.h @@ -21,8 +21,8 @@ namespace cunumeric { struct TransposeArgs { - legate::Store out; - legate::Store in; + legate::PhysicalStore out; + legate::PhysicalStore in; bool logical; }; diff --git a/src/cunumeric/matrix/transpose_omp.cc b/src/cunumeric/matrix/transpose_omp.cc index 0716632dbf..6e604ad401 100644 --- a/src/cunumeric/matrix/transpose_omp.cc +++ b/src/cunumeric/matrix/transpose_omp.cc @@ -26,7 +26,7 @@ using namespace legate; template struct TransposeImplBody { - using VAL = legate_type_of; + using VAL = type_of; void operator()(const Rect<2>& out_rect, const Rect<2>& in_rect, diff --git a/src/cunumeric/matrix/transpose_template.inl b/src/cunumeric/matrix/transpose_template.inl index 0fe3a703f0..6aa94bb260 100644 --- a/src/cunumeric/matrix/transpose_template.inl +++ b/src/cunumeric/matrix/transpose_template.inl @@ -31,7 +31,7 @@ struct TransposeImpl { template void operator()(TransposeArgs& args) const { - using VAL = legate_type_of; + using VAL = type_of; const auto out_rect = args.out.shape<2>(); if (out_rect.empty()) return; diff --git a/src/cunumeric/matrix/trilu.cc b/src/cunumeric/matrix/trilu.cc index 8eb48c51bc..55019bf9f4 100644 --- a/src/cunumeric/matrix/trilu.cc +++ b/src/cunumeric/matrix/trilu.cc @@ -23,7 +23,7 @@ using namespace legate; template struct TriluImplBody { - using VAL = legate_type_of; + using VAL = type_of; template void operator()(const AccessorWO& out, diff --git a/src/cunumeric/matrix/trilu.cu b/src/cunumeric/matrix/trilu.cu index e643876674..258e7ce195 100644 --- a/src/cunumeric/matrix/trilu.cu +++ b/src/cunumeric/matrix/trilu.cu @@ -52,7 +52,7 @@ static __global__ void __launch_bounds__(THREADS_PER_BLOCK, MIN_CTAS_PER_SM) template struct TriluImplBody { - using VAL = legate_type_of; + using VAL = type_of; template void operator()(const AccessorWO& out, diff --git a/src/cunumeric/matrix/trilu.h b/src/cunumeric/matrix/trilu.h index dcce0e813d..fd1874393d 100644 --- a/src/cunumeric/matrix/trilu.h +++ b/src/cunumeric/matrix/trilu.h @@ -23,8 +23,8 @@ namespace cunumeric { struct TriluArgs { bool lower; int32_t k; - legate::Store output; - legate::Store input; + legate::PhysicalStore output; + legate::PhysicalStore input; }; class TriluTask : public CuNumericTask { diff --git a/src/cunumeric/matrix/trilu_omp.cc b/src/cunumeric/matrix/trilu_omp.cc index 7932e4e43f..14543d5531 100644 --- a/src/cunumeric/matrix/trilu_omp.cc +++ b/src/cunumeric/matrix/trilu_omp.cc @@ -23,7 +23,7 @@ using namespace legate; template struct TriluImplBody { - using VAL = legate_type_of; + using VAL = type_of; template void operator()(const AccessorWO& out, diff --git a/src/cunumeric/matrix/trilu_template.inl b/src/cunumeric/matrix/trilu_template.inl index 4c2d7fcc94..b633ed7583 100644 --- a/src/cunumeric/matrix/trilu_template.inl +++ b/src/cunumeric/matrix/trilu_template.inl @@ -32,7 +32,7 @@ struct TriluImpl { template = 2)>* = nullptr> void operator()(TriluArgs& args) const { - using VAL = legate_type_of; + using VAL = type_of; auto shape = args.output.shape(); if (shape.empty()) return; diff --git a/src/cunumeric/matrix/trsm_template.inl b/src/cunumeric/matrix/trsm_template.inl index 3cd93288cb..fb76e3624a 100644 --- a/src/cunumeric/matrix/trsm_template.inl +++ b/src/cunumeric/matrix/trsm_template.inl @@ -40,9 +40,9 @@ struct support_trsm : std::true_type {}; template struct TrsmImpl { template ::value>* = nullptr> - void operator()(legate::Store lhs_array, legate::Store rhs_array) const + void operator()(legate::PhysicalStore lhs_array, legate::PhysicalStore rhs_array) const { - using VAL = legate_type_of; + using VAL = type_of; auto lhs_shape = lhs_array.shape<2>(); auto rhs_shape = rhs_array.shape<2>(); @@ -63,7 +63,7 @@ struct TrsmImpl { } template ::value>* = nullptr> - void operator()(legate::Store lhs_array, legate::Store rhs_array) const + void operator()(legate::PhysicalStore lhs_array, legate::PhysicalStore rhs_array) const { assert(false); } diff --git a/src/cunumeric/ndarray.cc b/src/cunumeric/ndarray.cc index 9a5cbff475..c3a71c9a94 100644 --- a/src/cunumeric/ndarray.cc +++ b/src/cunumeric/ndarray.cc @@ -32,7 +32,7 @@ struct generate_zero_fn { template legate::Scalar operator()() { - using VAL = legate::legate_type_of; + using VAL = legate::type_of; return legate::Scalar(VAL(0)); } }; diff --git a/src/cunumeric/nullary/arange.h b/src/cunumeric/nullary/arange.h index e875909153..d89cf49de0 100644 --- a/src/cunumeric/nullary/arange.h +++ b/src/cunumeric/nullary/arange.h @@ -21,7 +21,7 @@ namespace cunumeric { struct ArangeArgs { - legate::Store out; + legate::PhysicalStore out; legate::Scalar start; legate::Scalar step; }; diff --git a/src/cunumeric/nullary/arange_template.inl b/src/cunumeric/nullary/arange_template.inl index 2e0bf82f70..5a1dcb407f 100644 --- a/src/cunumeric/nullary/arange_template.inl +++ b/src/cunumeric/nullary/arange_template.inl @@ -34,7 +34,7 @@ struct ArangeImpl { template void operator()(ArangeArgs& args) const { - using VAL = legate_type_of; + using VAL = type_of; const auto rect = args.out.shape<1>(); diff --git a/src/cunumeric/nullary/eye.h b/src/cunumeric/nullary/eye.h index 5cf3910e15..0520a89665 100644 --- a/src/cunumeric/nullary/eye.h +++ b/src/cunumeric/nullary/eye.h @@ -21,7 +21,7 @@ namespace cunumeric { struct EyeArgs { - legate::Store out; + legate::PhysicalStore out; int32_t k; }; diff --git a/src/cunumeric/nullary/eye_template.inl b/src/cunumeric/nullary/eye_template.inl index 664b40efa5..80ffa63f64 100644 --- a/src/cunumeric/nullary/eye_template.inl +++ b/src/cunumeric/nullary/eye_template.inl @@ -34,7 +34,7 @@ struct EyeImpl { template void operator()(EyeArgs& args) const { - using VAL = legate_type_of; + using VAL = type_of; const auto rect = args.out.shape<2>(); auto out = args.out.write_accessor(); diff --git a/src/cunumeric/nullary/fill.h b/src/cunumeric/nullary/fill.h index ac34e4784d..cad2bf5203 100644 --- a/src/cunumeric/nullary/fill.h +++ b/src/cunumeric/nullary/fill.h @@ -21,8 +21,8 @@ namespace cunumeric { struct FillArgs { - legate::Store out; - legate::Store fill_value; + legate::PhysicalStore out; + legate::PhysicalStore fill_value; bool is_argval; }; diff --git a/src/cunumeric/nullary/fill_template.inl b/src/cunumeric/nullary/fill_template.inl index 5181a0372f..0ffa088499 100644 --- a/src/cunumeric/nullary/fill_template.inl +++ b/src/cunumeric/nullary/fill_template.inl @@ -58,10 +58,10 @@ struct FillImpl { void operator()(FillArgs& args) const { if (args.is_argval) { - using VAL = Argval>; + using VAL = Argval>; fill(args); } else { - using VAL = legate_type_of; + using VAL = type_of; fill(args); } } diff --git a/src/cunumeric/nullary/window_template.inl b/src/cunumeric/nullary/window_template.inl index 61dfc922af..16b727ca3f 100644 --- a/src/cunumeric/nullary/window_template.inl +++ b/src/cunumeric/nullary/window_template.inl @@ -30,7 +30,7 @@ struct WindowImplBody; template struct WindowImpl { template - void operator()(legate::Store output, int64_t M, double beta) const + void operator()(legate::PhysicalStore output, int64_t M, double beta) const { auto rect = output.shape<1>(); diff --git a/src/cunumeric/operators.cc b/src/cunumeric/operators.cc index a9c7e38e92..0c6f3ffcfb 100644 --- a/src/cunumeric/operators.cc +++ b/src/cunumeric/operators.cc @@ -84,7 +84,7 @@ struct generate_zero_fn { template legate::Scalar operator()() { - using VAL = legate::legate_type_of; + using VAL = legate::type_of; return legate::Scalar(VAL(0)); } }; @@ -93,7 +93,7 @@ struct generate_int_value_fn { template ::value>* = nullptr> int operator()(NDArray& array) { - using VAL = legate::legate_type_of; + using VAL = legate::type_of; return static_cast(array.get_read_accessor()[0]); } diff --git a/src/cunumeric/random/bitgenerator.h b/src/cunumeric/random/bitgenerator.h index 6dc9c1d62d..87fc37a764 100644 --- a/src/cunumeric/random/bitgenerator.h +++ b/src/cunumeric/random/bitgenerator.h @@ -35,8 +35,8 @@ struct BitGeneratorArgs { std::vector floatparams; std::vector doubleparams; - std::vector output; // size 0 or 1 - std::vector args; + std::vector output; // size 0 or 1 + std::vector args; BitGeneratorArgs() {} static BitGeneratorArgs destroy(int32_t id) @@ -60,8 +60,8 @@ struct BitGeneratorArgs { std::vector&& floatparams, std::vector&& doubleparams, - std::vector&& output, // size 0 or 1 - std::vector&& args) + std::vector&& output, // size 0 or 1 + std::vector&& args) : bitgen_op(bitgen_op), generatorID(generatorID), generatorType(generatorType), diff --git a/src/cunumeric/random/bitgenerator_curand.inl b/src/cunumeric/random/bitgenerator_curand.inl index 1def84550f..7f59237bbb 100644 --- a/src/cunumeric/random/bitgenerator_curand.inl +++ b/src/cunumeric/random/bitgenerator_curand.inl @@ -271,7 +271,7 @@ struct CURANDGenerator { struct generate_fn { template - size_t operator()(CURANDGenerator& gen, legate::Store output) + size_t operator()(CURANDGenerator& gen, legate::PhysicalStore output) { auto rect = output.shape(); uint64_t volume = rect.volume(); @@ -1236,7 +1236,7 @@ struct generate_distribution { generate_distribution(const generator_t& generator) : generator_(generator) {} template - size_t operator()(CURANDGenerator& gen, legate::Store output) + size_t operator()(CURANDGenerator& gen, legate::PhysicalStore output) { auto rect = output.shape(); uint64_t volume = rect.volume(); @@ -1255,7 +1255,7 @@ struct generate_distribution { return volume; } - static void generate(legate::Store res, + static void generate(legate::PhysicalStore res, CURANDGenerator& cugen, const std::vector& intparams, const std::vector& floatparams, @@ -1361,8 +1361,8 @@ struct BitGeneratorImplBody { std::vector intparams, std::vector floatparams, std::vector doubleparams, - std::vector& output, - std::vector& args) + std::vector& output, + std::vector& args) { generator_map_t& genmap = get_generator_map(); switch (op) { @@ -1384,8 +1384,8 @@ struct BitGeneratorImplBody { // get the generator CURANDGenerator* genptr = genmap.get(generatorID); if (output.size() != 0) { - legate::Store& res = output[0]; - CURANDGenerator& cugen = *genptr; + legate::PhysicalStore& res = output[0]; + CURANDGenerator& cugen = *genptr; dim_dispatch(res.dim(), generate_fn{}, cugen, res); } break; @@ -1396,8 +1396,8 @@ struct BitGeneratorImplBody { // get the generator CURANDGenerator* genptr = genmap.get(generatorID); if (output.size() != 0) { - legate::Store& res = output[0]; - CURANDGenerator& cugen = *genptr; + legate::PhysicalStore& res = output[0]; + CURANDGenerator& cugen = *genptr; switch (distribution) { case BitGeneratorDistribution::INTEGERS_16: generate_distribution>::generate( diff --git a/src/cunumeric/random/bitgenerator_template.inl b/src/cunumeric/random/bitgenerator_template.inl index e447f69104..056883fa07 100644 --- a/src/cunumeric/random/bitgenerator_template.inl +++ b/src/cunumeric/random/bitgenerator_template.inl @@ -93,10 +93,10 @@ static void bitgenerator_template(TaskContext& context) default: LEGATE_ABORT; } - std::vector extra_args; + std::vector extra_args; for (auto& input : inputs) extra_args.push_back(std::move(input)); - std::vector optional_output; + std::vector optional_output; for (auto& output : outputs) optional_output.push_back(std::move(output)); // destroy ? diff --git a/src/cunumeric/random/rand.h b/src/cunumeric/random/rand.h index 86b48aa8d9..9fe109e17b 100644 --- a/src/cunumeric/random/rand.h +++ b/src/cunumeric/random/rand.h @@ -22,7 +22,7 @@ namespace cunumeric { struct RandArgs { - legate::Store out; + legate::PhysicalStore out; RandGenCode gen_code; uint32_t epoch; legate::DomainPoint strides; diff --git a/src/cunumeric/random/rand_template.inl b/src/cunumeric/random/rand_template.inl index a9487dba0b..580247d1d8 100644 --- a/src/cunumeric/random/rand_template.inl +++ b/src/cunumeric/random/rand_template.inl @@ -36,7 +36,7 @@ struct RandImpl { std::enable_if_t::valid>* = nullptr> void operator()(RandArgs& args) const { - using VAL = legate_type_of; + using VAL = type_of; using RNG = RandomGenerator; auto rect = args.out.shape(); diff --git a/src/cunumeric/random/rand_util.h b/src/cunumeric/random/rand_util.h index 31458f9c4a..db25cf2be8 100644 --- a/src/cunumeric/random/rand_util.h +++ b/src/cunumeric/random/rand_util.h @@ -177,7 +177,7 @@ struct RandomGenerator { template struct RandomGenerator { using RNG = Philox_2x32<10>; - using VAL = legate::legate_type_of; + using VAL = legate::type_of; static constexpr bool valid = legate::is_integral::value; diff --git a/src/cunumeric/scan/scan_global.cc b/src/cunumeric/scan/scan_global.cc index 7215bbdd71..df324d80cb 100644 --- a/src/cunumeric/scan/scan_global.cc +++ b/src/cunumeric/scan/scan_global.cc @@ -27,7 +27,7 @@ using namespace legate; template struct ScanGlobalImplBody { using OP = ScanOp; - using VAL = legate_type_of; + using VAL = type_of; void operator()(OP func, const AccessorRW& out, diff --git a/src/cunumeric/scan/scan_global.cu b/src/cunumeric/scan/scan_global.cu index 58afee4a08..ea7047c4bd 100644 --- a/src/cunumeric/scan/scan_global.cu +++ b/src/cunumeric/scan/scan_global.cu @@ -38,7 +38,7 @@ static __global__ void __launch_bounds__(THREADS_PER_BLOCK, MIN_CTAS_PER_SM) template struct ScanGlobalImplBody { using OP = ScanOp; - using VAL = legate_type_of; + using VAL = type_of; void operator()(OP func, const AccessorRW& out, diff --git a/src/cunumeric/scan/scan_global.h b/src/cunumeric/scan/scan_global.h index 267394ac2a..88258af6c2 100644 --- a/src/cunumeric/scan/scan_global.h +++ b/src/cunumeric/scan/scan_global.h @@ -22,8 +22,8 @@ namespace cunumeric { struct ScanGlobalArgs { - legate::Store sum_vals; - legate::Store out; + legate::PhysicalStore sum_vals; + legate::PhysicalStore out; ScanCode op_code; const legate::DomainPoint& partition_index; }; diff --git a/src/cunumeric/scan/scan_global_omp.cc b/src/cunumeric/scan/scan_global_omp.cc index bb4f64273d..2f08b6fac2 100644 --- a/src/cunumeric/scan/scan_global_omp.cc +++ b/src/cunumeric/scan/scan_global_omp.cc @@ -29,7 +29,7 @@ using namespace legate; template struct ScanGlobalImplBody { using OP = ScanOp; - using VAL = legate_type_of; + using VAL = type_of; void operator()(OP func, const AccessorRW& out, diff --git a/src/cunumeric/scan/scan_global_template.inl b/src/cunumeric/scan/scan_global_template.inl index bddacf8950..92d2f52ea8 100644 --- a/src/cunumeric/scan/scan_global_template.inl +++ b/src/cunumeric/scan/scan_global_template.inl @@ -30,7 +30,7 @@ struct ScanGlobalImpl { void operator()(ScanGlobalArgs& args) const { using OP = ScanOp; - using VAL = legate_type_of; + using VAL = type_of; auto out_rect = args.out.shape(); auto sum_vals_rect = args.sum_vals.shape(); diff --git a/src/cunumeric/scan/scan_local.cc b/src/cunumeric/scan/scan_local.cc index 5dabe3486f..806f63d234 100644 --- a/src/cunumeric/scan/scan_local.cc +++ b/src/cunumeric/scan/scan_local.cc @@ -29,12 +29,12 @@ using namespace legate; template struct ScanLocalImplBody { using OP = ScanOp; - using VAL = legate_type_of; + using VAL = type_of; void operator()(OP func, const AccessorWO& out, const AccessorRO& in, - legate::Store& sum_vals, + legate::PhysicalStore& sum_vals, const Pitches& pitches, const Rect& rect) const { @@ -65,7 +65,7 @@ struct ScanLocalImplBody { template struct ScanLocalNanImplBody { using OP = ScanOp; - using VAL = legate_type_of; + using VAL = type_of; struct convert_nan_func { VAL operator()(VAL x) const @@ -77,7 +77,7 @@ struct ScanLocalNanImplBody { void operator()(OP func, const AccessorWO& out, const AccessorRO& in, - legate::Store& sum_vals, + legate::PhysicalStore& sum_vals, const Pitches& pitches, const Rect& rect) const { diff --git a/src/cunumeric/scan/scan_local.cu b/src/cunumeric/scan/scan_local.cu index 1de5582d7c..4b2fe3d06b 100644 --- a/src/cunumeric/scan/scan_local.cu +++ b/src/cunumeric/scan/scan_local.cu @@ -40,12 +40,12 @@ static __global__ void __launch_bounds__(THREADS_PER_BLOCK, MIN_CTAS_PER_SM) template struct ScanLocalImplBody { using OP = ScanOp; - using VAL = legate_type_of; + using VAL = type_of; void operator()(OP func, const AccessorWO& out, const AccessorRO& in, - legate::Store& sum_vals, + legate::PhysicalStore& sum_vals, const Pitches& pitches, const Rect& rect) const { @@ -80,7 +80,7 @@ struct ScanLocalImplBody { template struct ScanLocalNanImplBody { using OP = ScanOp; - using VAL = legate_type_of; + using VAL = type_of; struct convert_nan_func { __device__ VAL operator()(VAL x) @@ -92,7 +92,7 @@ struct ScanLocalNanImplBody { void operator()(OP func, const AccessorWO& out, const AccessorRO& in, - legate::Store& sum_vals, + legate::PhysicalStore& sum_vals, const Pitches& pitches, const Rect& rect) const { diff --git a/src/cunumeric/scan/scan_local.h b/src/cunumeric/scan/scan_local.h index d215a5c4eb..5f5534f5a6 100644 --- a/src/cunumeric/scan/scan_local.h +++ b/src/cunumeric/scan/scan_local.h @@ -22,9 +22,9 @@ namespace cunumeric { struct ScanLocalArgs { - legate::Store out; - legate::Store in; - legate::Store sum_vals; + legate::PhysicalStore out; + legate::PhysicalStore in; + legate::PhysicalStore sum_vals; ScanCode op_code; bool nan_to_identity; }; diff --git a/src/cunumeric/scan/scan_local_omp.cc b/src/cunumeric/scan/scan_local_omp.cc index 9aba5b9e35..2713b9a5c3 100644 --- a/src/cunumeric/scan/scan_local_omp.cc +++ b/src/cunumeric/scan/scan_local_omp.cc @@ -31,12 +31,12 @@ using namespace legate; template struct ScanLocalImplBody { using OP = ScanOp; - using VAL = legate_type_of; + using VAL = type_of; void operator()(OP func, const AccessorWO& out, const AccessorRO& in, - legate::Store& sum_vals, + legate::PhysicalStore& sum_vals, const Pitches& pitches, const Rect& rect) const { @@ -67,7 +67,7 @@ struct ScanLocalImplBody { template struct ScanLocalNanImplBody { using OP = ScanOp; - using VAL = legate_type_of; + using VAL = type_of; struct convert_nan_func { VAL operator()(VAL x) const @@ -79,7 +79,7 @@ struct ScanLocalNanImplBody { void operator()(OP func, const AccessorWO& out, const AccessorRO& in, - legate::Store& sum_vals, + legate::PhysicalStore& sum_vals, const Pitches& pitches, const Rect& rect) const { diff --git a/src/cunumeric/scan/scan_local_template.inl b/src/cunumeric/scan/scan_local_template.inl index 85021b830a..214bf02fc9 100644 --- a/src/cunumeric/scan/scan_local_template.inl +++ b/src/cunumeric/scan/scan_local_template.inl @@ -37,7 +37,7 @@ struct ScanLocalImpl { void operator()(ScanLocalArgs& args) const { using OP = ScanOp; - using VAL = legate_type_of; + using VAL = type_of; auto rect = args.out.shape(); @@ -63,7 +63,7 @@ struct ScanLocalImpl { void operator()(ScanLocalArgs& args) const { using OP = ScanOp; - using VAL = legate_type_of; + using VAL = type_of; auto rect = args.out.shape(); diff --git a/src/cunumeric/scan/scan_local_util.h b/src/cunumeric/scan/scan_local_util.h index 34e2952795..c348127d00 100644 --- a/src/cunumeric/scan/scan_local_util.h +++ b/src/cunumeric/scan/scan_local_util.h @@ -56,13 +56,13 @@ template struct ScanOp {}; template -struct ScanOp : thrust::plus> { +struct ScanOp : thrust::plus> { static constexpr int nan_identity = 0; ScanOp() {} }; template -struct ScanOp : thrust::multiplies> { +struct ScanOp : thrust::multiplies> { static constexpr int nan_identity = 1; ScanOp() {} }; diff --git a/src/cunumeric/scan/scan_util.h b/src/cunumeric/scan/scan_util.h index d2f3d6c492..891f3f58bd 100644 --- a/src/cunumeric/scan/scan_util.h +++ b/src/cunumeric/scan/scan_util.h @@ -69,14 +69,14 @@ template struct ScanOp; template -struct ScanOp : thrust::plus> { - using T = legate::legate_type_of; +struct ScanOp : thrust::plus> { + using T = legate::type_of; static constexpr int nan_identity = 0; }; template -struct ScanOp : thrust::multiplies> { - using T = legate::legate_type_of; +struct ScanOp : thrust::multiplies> { + using T = legate::type_of; static constexpr int nan_identity = 1; }; diff --git a/src/cunumeric/search/argwhere.cc b/src/cunumeric/search/argwhere.cc index dc056d5672..aa0a9fc721 100644 --- a/src/cunumeric/search/argwhere.cc +++ b/src/cunumeric/search/argwhere.cc @@ -23,9 +23,9 @@ using namespace legate; template struct ArgWhereImplBody { - using VAL = legate_type_of; + using VAL = type_of; - void operator()(legate::Store& out_array, + void operator()(legate::PhysicalStore& out_array, AccessorRO input, const Pitches& pitches, const Rect& rect, diff --git a/src/cunumeric/search/argwhere.cu b/src/cunumeric/search/argwhere.cu index e411dd0579..e4613b1f38 100644 --- a/src/cunumeric/search/argwhere.cu +++ b/src/cunumeric/search/argwhere.cu @@ -43,9 +43,9 @@ static __global__ void __launch_bounds__(THREADS_PER_BLOCK, MIN_CTAS_PER_SM) template struct ArgWhereImplBody { - using VAL = legate_type_of; + using VAL = type_of; - void operator()(legate::Store& out_array, + void operator()(legate::PhysicalStore& out_array, AccessorRO input, const Pitches& pitches, const Rect& rect, diff --git a/src/cunumeric/search/argwhere.h b/src/cunumeric/search/argwhere.h index 511f69bc8f..738c62d281 100644 --- a/src/cunumeric/search/argwhere.h +++ b/src/cunumeric/search/argwhere.h @@ -21,8 +21,8 @@ namespace cunumeric { struct ArgWhereArgs { - legate::Store out; - legate::Store in; + legate::PhysicalStore out; + legate::PhysicalStore in; }; class ArgWhereTask : public CuNumericTask { diff --git a/src/cunumeric/search/argwhere_omp.cc b/src/cunumeric/search/argwhere_omp.cc index 990cb804a9..cebe6da701 100644 --- a/src/cunumeric/search/argwhere_omp.cc +++ b/src/cunumeric/search/argwhere_omp.cc @@ -25,9 +25,9 @@ using namespace legate; template struct ArgWhereImplBody { - using VAL = legate_type_of; + using VAL = type_of; - void operator()(legate::Store& out_array, + void operator()(legate::PhysicalStore& out_array, AccessorRO input, const Pitches& pitches, const Rect& rect, diff --git a/src/cunumeric/search/argwhere_template.inl b/src/cunumeric/search/argwhere_template.inl index 7d8c84b099..63b6da3a2a 100644 --- a/src/cunumeric/search/argwhere_template.inl +++ b/src/cunumeric/search/argwhere_template.inl @@ -32,7 +32,7 @@ struct ArgWhereImpl { template void operator()(ArgWhereArgs& args) const { - using VAL = legate_type_of; + using VAL = type_of; auto rect_in = args.in.shape(); diff --git a/src/cunumeric/search/nonzero.cc b/src/cunumeric/search/nonzero.cc index 71f58d7be5..4bd25d1351 100644 --- a/src/cunumeric/search/nonzero.cc +++ b/src/cunumeric/search/nonzero.cc @@ -23,7 +23,7 @@ using namespace legate; template struct NonzeroImplBody { - using VAL = legate_type_of; + using VAL = type_of; void operator()(std::vector& outputs, const AccessorRO& in, diff --git a/src/cunumeric/search/nonzero.cu b/src/cunumeric/search/nonzero.cu index 4a674b3d93..90f0868450 100644 --- a/src/cunumeric/search/nonzero.cu +++ b/src/cunumeric/search/nonzero.cu @@ -41,7 +41,7 @@ static __global__ void __launch_bounds__(THREADS_PER_BLOCK, MIN_CTAS_PER_SM) template struct NonzeroImplBody { - using VAL = legate_type_of; + using VAL = type_of; void populate_nonzeros(const AccessorRO& in, const Pitches& pitches, diff --git a/src/cunumeric/search/nonzero.h b/src/cunumeric/search/nonzero.h index 47d30790f0..beee4e5162 100644 --- a/src/cunumeric/search/nonzero.h +++ b/src/cunumeric/search/nonzero.h @@ -21,8 +21,8 @@ namespace cunumeric { struct NonzeroArgs { - legate::Store input; - std::vector results; + legate::PhysicalStore input; + std::vector results; }; class NonzeroTask : public CuNumericTask { diff --git a/src/cunumeric/search/nonzero_omp.cc b/src/cunumeric/search/nonzero_omp.cc index b38a274628..fbe88c8c1a 100644 --- a/src/cunumeric/search/nonzero_omp.cc +++ b/src/cunumeric/search/nonzero_omp.cc @@ -26,7 +26,7 @@ using namespace legate; template struct NonzeroImplBody { - using VAL = legate_type_of; + using VAL = type_of; void operator()(std::vector& outputs, const AccessorRO& in, diff --git a/src/cunumeric/search/nonzero_template.inl b/src/cunumeric/search/nonzero_template.inl index ab6332016b..d0bd697b03 100644 --- a/src/cunumeric/search/nonzero_template.inl +++ b/src/cunumeric/search/nonzero_template.inl @@ -32,7 +32,7 @@ struct NonzeroImpl { template void operator()(NonzeroArgs& args) const { - using VAL = legate_type_of; + using VAL = type_of; auto rect = args.input.shape(); @@ -52,7 +52,7 @@ struct NonzeroImpl { template static void nonzero_template(TaskContext& context) { - std::vector outputs; + std::vector outputs; for (auto& output : context.outputs()) { outputs.emplace_back(output); } NonzeroArgs args{context.input(0), std::move(outputs)}; double_dispatch(args.input.dim(), args.input.code(), NonzeroImpl{}, args); diff --git a/src/cunumeric/set/unique.cc b/src/cunumeric/set/unique.cc index 482686d683..9653c5cb14 100644 --- a/src/cunumeric/set/unique.cc +++ b/src/cunumeric/set/unique.cc @@ -23,9 +23,9 @@ using namespace legate; template struct UniqueImplBody { - using VAL = legate_type_of; + using VAL = type_of; - void operator()(legate::Store& output, + void operator()(legate::PhysicalStore& output, const AccessorRO& in, const Pitches& pitches, const Rect& rect, diff --git a/src/cunumeric/set/unique.cu b/src/cunumeric/set/unique.cu index 0ee43d9240..b2a98553e5 100644 --- a/src/cunumeric/set/unique.cu +++ b/src/cunumeric/set/unique.cu @@ -48,7 +48,7 @@ using Piece = std::pair, size_t>; auto get_aligned_size = [](auto size) { return std::max(16, (size + 15) / 16 * 16); }; template -static Piece tree_reduce(legate::Store& output, +static Piece tree_reduce(legate::PhysicalStore& output, Piece my_piece, size_t my_id, size_t num_ranks, @@ -141,9 +141,9 @@ static Piece tree_reduce(legate::Store& output, template struct UniqueImplBody { - using VAL = legate_type_of; + using VAL = type_of; - void operator()(legate::Store& output, + void operator()(legate::PhysicalStore& output, const AccessorRO& in, const Pitches& pitches, const Rect& rect, diff --git a/src/cunumeric/set/unique_omp.cc b/src/cunumeric/set/unique_omp.cc index 810ff1b6fc..c3199ee288 100644 --- a/src/cunumeric/set/unique_omp.cc +++ b/src/cunumeric/set/unique_omp.cc @@ -25,9 +25,9 @@ using namespace legate; template struct UniqueImplBody { - using VAL = legate_type_of; + using VAL = type_of; - void operator()(legate::Store& output, + void operator()(legate::PhysicalStore& output, const AccessorRO& in, const Pitches& pitches, const Rect& rect, diff --git a/src/cunumeric/set/unique_reduce_template.inl b/src/cunumeric/set/unique_reduce_template.inl index 2db43c0044..5de6603200 100644 --- a/src/cunumeric/set/unique_reduce_template.inl +++ b/src/cunumeric/set/unique_reduce_template.inl @@ -32,11 +32,11 @@ using namespace legate; template struct UniqueReduceImpl { template - void operator()(legate::Store output, - const std::vector& input_arrs, + void operator()(legate::PhysicalStore output, + const std::vector& input_arrs, exe_pol_t exe_pol) { - using VAL = legate_type_of; + using VAL = type_of; size_t res_size = 0; for (auto& input_arr : input_arrs) { diff --git a/src/cunumeric/set/unique_template.inl b/src/cunumeric/set/unique_template.inl index a1530a3a2c..d2046cf416 100644 --- a/src/cunumeric/set/unique_template.inl +++ b/src/cunumeric/set/unique_template.inl @@ -30,13 +30,13 @@ struct UniqueImplBody; template struct UniqueImpl { template - void operator()(legate::Store output, - legate::Store input, + void operator()(legate::PhysicalStore output, + legate::PhysicalStore input, std::vector& comms, const DomainPoint& point, const Domain& launch_domain) const { - using VAL = legate_type_of; + using VAL = type_of; auto rect = input.shape(); Pitches pitches; diff --git a/src/cunumeric/sort/searchsorted.cc b/src/cunumeric/sort/searchsorted.cc index 3fb4a23b5e..9c9933c7bd 100644 --- a/src/cunumeric/sort/searchsorted.cc +++ b/src/cunumeric/sort/searchsorted.cc @@ -23,11 +23,11 @@ using namespace legate; template struct SearchSortedImplBody { - using VAL = legate_type_of; + using VAL = type_of; - void operator()(const Store& input_array, - const Store& input_values, - const Store& output_positions, + void operator()(const PhysicalStore& input_array, + const PhysicalStore& input_values, + const PhysicalStore& output_positions, const Rect<1>& rect_base, const Rect& rect_values, const Pitches pitches, diff --git a/src/cunumeric/sort/searchsorted.cu b/src/cunumeric/sort/searchsorted.cu index e69eded75a..4b63202072 100644 --- a/src/cunumeric/sort/searchsorted.cu +++ b/src/cunumeric/sort/searchsorted.cu @@ -66,11 +66,11 @@ static __global__ void __launch_bounds__(THREADS_PER_BLOCK, MIN_CTAS_PER_SM) template struct SearchSortedImplBody { - using VAL = legate_type_of; + using VAL = type_of; - void operator()(const Store& input_array, - const Store& input_values, - const Store& output_positions, + void operator()(const PhysicalStore& input_array, + const PhysicalStore& input_values, + const PhysicalStore& output_positions, const Rect<1>& rect_base, const Rect& rect_values, const Pitches pitches, diff --git a/src/cunumeric/sort/searchsorted.h b/src/cunumeric/sort/searchsorted.h index 3e65e49c13..7b791e152e 100644 --- a/src/cunumeric/sort/searchsorted.h +++ b/src/cunumeric/sort/searchsorted.h @@ -21,9 +21,9 @@ namespace cunumeric { struct SearchSortedArgs { - legate::Store input_base; - legate::Store input_values; - legate::Store output_reduction; + legate::PhysicalStore input_base; + legate::PhysicalStore input_values; + legate::PhysicalStore output_reduction; bool left; int64_t global_volume; bool is_index_space; diff --git a/src/cunumeric/sort/searchsorted_omp.cc b/src/cunumeric/sort/searchsorted_omp.cc index 787065020c..ae7714c829 100644 --- a/src/cunumeric/sort/searchsorted_omp.cc +++ b/src/cunumeric/sort/searchsorted_omp.cc @@ -25,11 +25,11 @@ using namespace legate; template struct SearchSortedImplBody { - using VAL = legate_type_of; + using VAL = type_of; - void operator()(const Store& input_array, - const Store& input_values, - const Store& output_positions, + void operator()(const PhysicalStore& input_array, + const PhysicalStore& input_values, + const PhysicalStore& output_positions, const Rect<1>& rect_base, const Rect& rect_values, const Pitches pitches, diff --git a/src/cunumeric/sort/sort.cc b/src/cunumeric/sort/sort.cc index c8b3085e3f..c6179dd082 100644 --- a/src/cunumeric/sort/sort.cc +++ b/src/cunumeric/sort/sort.cc @@ -30,10 +30,10 @@ using namespace legate; template struct SortImplBody { - using VAL = legate_type_of; + using VAL = type_of; - void operator()(const legate::Store& input_array, - legate::Store& output_array, + void operator()(const legate::PhysicalStore& input_array, + legate::PhysicalStore& output_array, const Pitches& pitches, const Rect& rect, const size_t volume, diff --git a/src/cunumeric/sort/sort.cu b/src/cunumeric/sort/sort.cu index 9e6df85072..e323633a30 100644 --- a/src/cunumeric/sort/sort.cu +++ b/src/cunumeric/sort/sort.cu @@ -50,8 +50,8 @@ template <> struct support_cub : std::false_type {}; template ::value>* = nullptr> -void local_sort(const legate_type_of* values_in, - legate_type_of* values_out, +void local_sort(const type_of* values_in, + type_of* values_out, const int64_t* indices_in, int64_t* indices_out, const size_t volume, @@ -59,7 +59,7 @@ void local_sort(const legate_type_of* values_in, const bool stable, // cub sort is always stable cudaStream_t stream) { - using VAL = legate_type_of; + using VAL = type_of; // fallback to thrust approach as segmented radix sort is not suited for small segments if (volume == sort_dim_size || sort_dim_size > SEGMENT_THRESHOLD_RADIX_SORT) { cub_local_sort(values_in, values_out, indices_in, indices_out, volume, sort_dim_size, stream); @@ -70,8 +70,8 @@ void local_sort(const legate_type_of* values_in, } template ::value>* = nullptr> -void local_sort(const legate_type_of* values_in, - legate_type_of* values_out, +void local_sort(const type_of* values_in, + type_of* values_out, const int64_t* indices_in, int64_t* indices_out, const size_t volume, @@ -79,7 +79,7 @@ void local_sort(const legate_type_of* values_in, const bool stable, cudaStream_t stream) { - using VAL = legate_type_of; + using VAL = type_of; thrust_local_sort( values_in, values_out, indices_in, indices_out, volume, sort_dim_size, stable, stream); } @@ -567,14 +567,14 @@ struct negative_plus : public thrust::binary_function ///////////////////////////////////////////////////////////////////////////////////////////////// template -SegmentMergePiece> merge_all_buffers( - std::vector>>& merge_buffers, +SegmentMergePiece> merge_all_buffers( + std::vector>>& merge_buffers, bool segmented, bool argsort, ThrustAllocator& alloc, cudaStream_t stream) { - using VAL = legate_type_of; + using VAL = type_of; // fallback to full sort for 1D and > 64 parts if (!segmented && merge_buffers.size() > 64) { @@ -1189,8 +1189,8 @@ void rebalance_data(SegmentMergePiece& merge_buffer, template void sample_sort_nccl_nd( - SortPiece> local_sorted, - legate::Store& output_array_unbound, // only for unbound usage when !rebalance + SortPiece> local_sorted, + legate::PhysicalStore& output_array_unbound, // only for unbound usage when !rebalance void* output_ptr, /* global domain information */ size_t my_rank, // global NCCL rank @@ -1207,7 +1207,7 @@ void sample_sort_nccl_nd( cudaStream_t stream, ncclComm_t* comm) { - using VAL = legate_type_of; + using VAL = type_of; size_t volume = local_sorted.size; bool is_unbound_1d_storage = output_array_unbound.is_unbound_store(); @@ -1661,10 +1661,10 @@ void sample_sort_nccl_nd( template struct SortImplBody { - using VAL = legate_type_of; + using VAL = type_of; - void operator()(const legate::Store& input_array, - legate::Store& output_array, + void operator()(const legate::PhysicalStore& input_array, + legate::PhysicalStore& output_array, const Pitches& pitches, const Rect& rect, const size_t volume, diff --git a/src/cunumeric/sort/sort.h b/src/cunumeric/sort/sort.h index 817613edd0..c07f4a73be 100644 --- a/src/cunumeric/sort/sort.h +++ b/src/cunumeric/sort/sort.h @@ -23,8 +23,8 @@ namespace cunumeric { struct SortArgs { - legate::Store input; - legate::Store output; + legate::PhysicalStore input; + legate::PhysicalStore output; bool argsort; bool stable; size_t segment_size_g; diff --git a/src/cunumeric/sort/sort_cpu.inl b/src/cunumeric/sort/sort_cpu.inl index 14a1315ec3..6fac41f2c7 100644 --- a/src/cunumeric/sort/sort_cpu.inl +++ b/src/cunumeric/sort/sort_cpu.inl @@ -443,25 +443,26 @@ void rebalance_data(SegmentMergePiece& merge_buffer, } template -void sample_sort_nd(SortPiece> local_sorted, - legate::Store output_array_unbound, // only for unbound usage when !rebalance - void* output_ptr, - /* global domain information */ - size_t my_rank, // global rank - size_t num_ranks, - size_t segment_size_g, - /* domain information in sort dimension */ - size_t my_sort_rank, // local rank id in sort dimension - size_t num_sort_ranks, // #ranks that share a sort dimension - size_t* sort_ranks, // rank ids that share a sort dimension with us - size_t segment_size_l, // (local) segment size - /* other */ - bool rebalance, - bool argsort, - const DerivedPolicy& exec, - comm::coll::CollComm comm) +void sample_sort_nd( + SortPiece> local_sorted, + legate::PhysicalStore output_array_unbound, // only for unbound usage when !rebalance + void* output_ptr, + /* global domain information */ + size_t my_rank, // global rank + size_t num_ranks, + size_t segment_size_g, + /* domain information in sort dimension */ + size_t my_sort_rank, // local rank id in sort dimension + size_t num_sort_ranks, // #ranks that share a sort dimension + size_t* sort_ranks, // rank ids that share a sort dimension with us + size_t segment_size_l, // (local) segment size + /* other */ + bool rebalance, + bool argsort, + const DerivedPolicy& exec, + comm::coll::CollComm comm) { - using VAL = legate_type_of; + using VAL = type_of; size_t volume = local_sorted.size; bool is_unbound_1d_storage = output_array_unbound.is_unbound_store(); @@ -895,11 +896,11 @@ void sample_sort_nd(SortPiece> local_sorted, template struct SortImplBodyCpu { - using VAL = legate_type_of; + using VAL = type_of; template - void operator()(legate::Store input_array, - legate::Store output_array, + void operator()(legate::PhysicalStore input_array, + legate::PhysicalStore output_array, const Pitches& pitches, const Rect& rect, const size_t volume, diff --git a/src/cunumeric/sort/sort_omp.cc b/src/cunumeric/sort/sort_omp.cc index 0d06bdc165..26652697a5 100644 --- a/src/cunumeric/sort/sort_omp.cc +++ b/src/cunumeric/sort/sort_omp.cc @@ -31,10 +31,10 @@ using namespace legate; template struct SortImplBody { - using VAL = legate_type_of; + using VAL = type_of; - void operator()(const legate::Store& input_array, - legate::Store& output_array, + void operator()(const legate::PhysicalStore& input_array, + legate::PhysicalStore& output_array, const Pitches& pitches, const Rect& rect, const size_t volume, diff --git a/src/cunumeric/stat/bincount.cc b/src/cunumeric/stat/bincount.cc index 0bae9a70f3..15b7f298b5 100644 --- a/src/cunumeric/stat/bincount.cc +++ b/src/cunumeric/stat/bincount.cc @@ -23,7 +23,7 @@ using namespace legate; template struct BincountImplBody { - using VAL = legate_type_of; + using VAL = type_of; void operator()(AccessorRD, true, 1> lhs, const AccessorRO& rhs, diff --git a/src/cunumeric/stat/bincount.cu b/src/cunumeric/stat/bincount.cu index e0700652c2..e6c5647e44 100644 --- a/src/cunumeric/stat/bincount.cu +++ b/src/cunumeric/stat/bincount.cu @@ -145,7 +145,7 @@ static __global__ void weighted_bincount_kernel_rd_global( template struct BincountImplBody { - using VAL = legate_type_of; + using VAL = type_of; void operator()(AccessorRD, false, 1> lhs, const AccessorRO& rhs, diff --git a/src/cunumeric/stat/bincount.h b/src/cunumeric/stat/bincount.h index 998eca143d..9ee40effb9 100644 --- a/src/cunumeric/stat/bincount.h +++ b/src/cunumeric/stat/bincount.h @@ -21,9 +21,9 @@ namespace cunumeric { struct BincountArgs { - legate::Store lhs; - legate::Store rhs; - legate::Store weights; + legate::PhysicalStore lhs; + legate::PhysicalStore rhs; + legate::PhysicalStore weights; bool has_weights; }; diff --git a/src/cunumeric/stat/bincount_omp.cc b/src/cunumeric/stat/bincount_omp.cc index 5a3bf3c85f..1b040d9af0 100644 --- a/src/cunumeric/stat/bincount_omp.cc +++ b/src/cunumeric/stat/bincount_omp.cc @@ -25,7 +25,7 @@ using namespace legate; template struct BincountImplBody { - using VAL = legate_type_of; + using VAL = type_of; std::vector> _bincount(const AccessorRO& rhs, const Rect<1>& rect, diff --git a/src/cunumeric/stat/bincount_template.inl b/src/cunumeric/stat/bincount_template.inl index 4dc693d9b9..5379b85e2e 100644 --- a/src/cunumeric/stat/bincount_template.inl +++ b/src/cunumeric/stat/bincount_template.inl @@ -31,7 +31,7 @@ struct BincountImpl { template ::value>* = nullptr> void operator()(BincountArgs& args) const { - using VAL = legate_type_of; + using VAL = type_of; auto rect = args.rhs.shape<1>(); auto lhs_rect = args.lhs.shape<1>(); diff --git a/src/cunumeric/stat/histogram.cc b/src/cunumeric/stat/histogram.cc index e07e9844b8..7537972613 100644 --- a/src/cunumeric/stat/histogram.cc +++ b/src/cunumeric/stat/histogram.cc @@ -35,7 +35,7 @@ using namespace legate; template struct HistogramImplBody { - using VAL = legate_type_of; + using VAL = type_of; // for now, it has been decided to hardcode these types: // diff --git a/src/cunumeric/stat/histogram.cu b/src/cunumeric/stat/histogram.cu index 3c8c5d0818..5f62e6c19b 100644 --- a/src/cunumeric/stat/histogram.cu +++ b/src/cunumeric/stat/histogram.cu @@ -40,7 +40,7 @@ namespace cunumeric { template struct HistogramImplBody { - using VAL = legate_type_of; + using VAL = type_of; // for now, it has been decided to hardcode these types: // diff --git a/src/cunumeric/stat/histogram.h b/src/cunumeric/stat/histogram.h index 634c2de877..f513dc72e9 100644 --- a/src/cunumeric/stat/histogram.h +++ b/src/cunumeric/stat/histogram.h @@ -21,10 +21,10 @@ namespace cunumeric { struct HistogramArgs { - legate::Store result; - legate::Store src; - legate::Store bins; - legate::Store weights; + legate::PhysicalStore result; + legate::PhysicalStore src; + legate::PhysicalStore bins; + legate::PhysicalStore weights; }; class HistogramTask : public CuNumericTask { diff --git a/src/cunumeric/stat/histogram_omp.cc b/src/cunumeric/stat/histogram_omp.cc index 96b92b28f1..5024b0237a 100644 --- a/src/cunumeric/stat/histogram_omp.cc +++ b/src/cunumeric/stat/histogram_omp.cc @@ -35,7 +35,7 @@ using namespace legate; template struct HistogramImplBody { - using VAL = legate_type_of; + using VAL = type_of; // for now, it has been decided to hardcode these types: // diff --git a/src/cunumeric/stat/histogram_template.inl b/src/cunumeric/stat/histogram_template.inl index 298be82f24..f352eb3dd7 100644 --- a/src/cunumeric/stat/histogram_template.inl +++ b/src/cunumeric/stat/histogram_template.inl @@ -39,7 +39,7 @@ struct HistogramImpl { template >* = nullptr> void operator()(HistogramArgs& args) const { - using VAL = legate_type_of; + using VAL = type_of; auto result_rect = args.result.shape<1>(); auto src_rect = args.src.shape<1>(); diff --git a/src/cunumeric/ternary/where.cc b/src/cunumeric/ternary/where.cc index 9705233015..8549986b3a 100644 --- a/src/cunumeric/ternary/where.cc +++ b/src/cunumeric/ternary/where.cc @@ -23,7 +23,7 @@ using namespace legate; template struct WhereImplBody { - using VAL = legate_type_of; + using VAL = type_of; void operator()(AccessorWO out, AccessorRO mask, diff --git a/src/cunumeric/ternary/where.cu b/src/cunumeric/ternary/where.cu index 359b61b937..a4eb93a114 100644 --- a/src/cunumeric/ternary/where.cu +++ b/src/cunumeric/ternary/where.cu @@ -42,7 +42,7 @@ static __global__ void __launch_bounds__(THREADS_PER_BLOCK, MIN_CTAS_PER_SM) gen template struct WhereImplBody { - using VAL = legate_type_of; + using VAL = type_of; void operator()(AccessorWO out, AccessorRO mask, diff --git a/src/cunumeric/ternary/where.h b/src/cunumeric/ternary/where.h index 28bc984d32..d8bbb605ed 100644 --- a/src/cunumeric/ternary/where.h +++ b/src/cunumeric/ternary/where.h @@ -21,10 +21,10 @@ namespace cunumeric { struct WhereArgs { - legate::Store out; - legate::Store mask; - legate::Store in1; - legate::Store in2; + legate::PhysicalStore out; + legate::PhysicalStore mask; + legate::PhysicalStore in1; + legate::PhysicalStore in2; }; class WhereTask : public CuNumericTask { diff --git a/src/cunumeric/ternary/where_omp.cc b/src/cunumeric/ternary/where_omp.cc index a8cc7003f0..8d9283988e 100644 --- a/src/cunumeric/ternary/where_omp.cc +++ b/src/cunumeric/ternary/where_omp.cc @@ -23,7 +23,7 @@ using namespace legate; template struct WhereImplBody { - using VAL = legate_type_of; + using VAL = type_of; void operator()(AccessorWO out, AccessorRO mask, diff --git a/src/cunumeric/ternary/where_template.inl b/src/cunumeric/ternary/where_template.inl index 46c6fe26d0..8bc57e5992 100644 --- a/src/cunumeric/ternary/where_template.inl +++ b/src/cunumeric/ternary/where_template.inl @@ -32,7 +32,7 @@ struct WhereImpl { template void operator()(WhereArgs& args) const { - using VAL = legate_type_of; + using VAL = type_of; auto rect = args.out.shape(); diff --git a/src/cunumeric/transform/flip.cc b/src/cunumeric/transform/flip.cc index 57ffcd4362..3ca356a413 100644 --- a/src/cunumeric/transform/flip.cc +++ b/src/cunumeric/transform/flip.cc @@ -23,7 +23,7 @@ using namespace legate; template struct FlipImplBody { - using VAL = legate_type_of; + using VAL = type_of; void operator()(AccessorWO out, AccessorRO in, diff --git a/src/cunumeric/transform/flip.cu b/src/cunumeric/transform/flip.cu index 18fce618ff..f689ec0fc0 100644 --- a/src/cunumeric/transform/flip.cu +++ b/src/cunumeric/transform/flip.cu @@ -43,7 +43,7 @@ static __global__ void __launch_bounds__(THREADS_PER_BLOCK, MIN_CTAS_PER_SM) template struct FlipImplBody { - using VAL = legate_type_of; + using VAL = type_of; void operator()(AccessorWO out, AccessorRO in, diff --git a/src/cunumeric/transform/flip.h b/src/cunumeric/transform/flip.h index f85392a90d..009d131885 100644 --- a/src/cunumeric/transform/flip.h +++ b/src/cunumeric/transform/flip.h @@ -21,8 +21,8 @@ namespace cunumeric { struct FlipArgs { - legate::Store in; - legate::Store out; + legate::PhysicalStore in; + legate::PhysicalStore out; legate::Span axes; }; diff --git a/src/cunumeric/transform/flip_omp.cc b/src/cunumeric/transform/flip_omp.cc index f475666f39..a9c5a269c4 100644 --- a/src/cunumeric/transform/flip_omp.cc +++ b/src/cunumeric/transform/flip_omp.cc @@ -23,7 +23,7 @@ using namespace legate; template struct FlipImplBody { - using VAL = legate_type_of; + using VAL = type_of; void operator()(AccessorWO out, AccessorRO in, diff --git a/src/cunumeric/transform/flip_template.inl b/src/cunumeric/transform/flip_template.inl index eca2782a4c..32285318e8 100644 --- a/src/cunumeric/transform/flip_template.inl +++ b/src/cunumeric/transform/flip_template.inl @@ -32,7 +32,7 @@ struct FlipImpl { template void operator()(FlipArgs& args) const { - using VAL = legate_type_of; + using VAL = type_of; auto rect = args.out.shape().intersection(args.in.shape()); diff --git a/src/cunumeric/typedefs.h b/src/cunumeric/typedefs.h index 628d208757..434ebc0bc6 100644 --- a/src/cunumeric/typedefs.h +++ b/src/cunumeric/typedefs.h @@ -22,7 +22,7 @@ namespace cunumeric { -using Array = legate::Store; +using Array = legate::PhysicalStore; using Scalar = legate::Scalar; } // namespace cunumeric diff --git a/src/cunumeric/unary/convert.cc b/src/cunumeric/unary/convert.cc index 7165116267..66aaa4f83e 100644 --- a/src/cunumeric/unary/convert.cc +++ b/src/cunumeric/unary/convert.cc @@ -24,8 +24,8 @@ using namespace legate; template struct ConvertImplBody { using OP = ConvertOp; - using SRC = legate_type_of; - using DST = legate_type_of; + using SRC = type_of; + using DST = type_of; void operator()(OP func, AccessorWO out, diff --git a/src/cunumeric/unary/convert.cu b/src/cunumeric/unary/convert.cu index 6810c0cdc5..d1f242e4f7 100644 --- a/src/cunumeric/unary/convert.cu +++ b/src/cunumeric/unary/convert.cu @@ -43,8 +43,8 @@ static __global__ void __launch_bounds__(THREADS_PER_BLOCK, MIN_CTAS_PER_SM) template struct ConvertImplBody { using OP = ConvertOp; - using SRC = legate_type_of; - using DST = legate_type_of; + using SRC = type_of; + using DST = type_of; void operator()(OP func, AccessorWO out, diff --git a/src/cunumeric/unary/convert.h b/src/cunumeric/unary/convert.h index 5d9fa3a96b..2ef7904772 100644 --- a/src/cunumeric/unary/convert.h +++ b/src/cunumeric/unary/convert.h @@ -22,8 +22,8 @@ namespace cunumeric { struct ConvertArgs { - legate::Store out; - legate::Store in; + legate::PhysicalStore out; + legate::PhysicalStore in; ConvertCode nan_op; }; diff --git a/src/cunumeric/unary/convert_omp.cc b/src/cunumeric/unary/convert_omp.cc index 911c7721a2..ebe1bfe3ec 100644 --- a/src/cunumeric/unary/convert_omp.cc +++ b/src/cunumeric/unary/convert_omp.cc @@ -24,8 +24,8 @@ using namespace legate; template struct ConvertImplBody { using OP = ConvertOp; - using SRC = legate_type_of; - using DST = legate_type_of; + using SRC = type_of; + using DST = type_of; void operator()(OP func, AccessorWO out, diff --git a/src/cunumeric/unary/convert_template.inl b/src/cunumeric/unary/convert_template.inl index 39ff5981c6..393b853d05 100644 --- a/src/cunumeric/unary/convert_template.inl +++ b/src/cunumeric/unary/convert_template.inl @@ -34,8 +34,8 @@ struct ConvertImpl { void operator()(ConvertArgs& args) const { using OP = ConvertOp; - using SRC = legate_type_of; - using DST = legate_type_of; + using SRC = type_of; + using DST = type_of; auto rect = args.out.shape(); diff --git a/src/cunumeric/unary/convert_util.h b/src/cunumeric/unary/convert_util.h index 9f3d187ab7..0f67f593d3 100644 --- a/src/cunumeric/unary/convert_util.h +++ b/src/cunumeric/unary/convert_util.h @@ -48,8 +48,8 @@ struct ConvertOp {}; template struct ConvertOp { - using SRC = legate::legate_type_of; - using DST = legate::legate_type_of; + using SRC = legate::type_of; + using DST = legate::type_of; template ::value or @@ -76,7 +76,7 @@ struct ConvertOp { template struct ConvertOp { - using SRC = legate::legate_type_of; + using SRC = legate::type_of; template ::value>* = nullptr> __CUDA_HD__ __half operator()(const _SRC& src) const @@ -93,7 +93,7 @@ struct ConvertOp { template struct ConvertOp { - using DST = legate::legate_type_of; + using DST = legate::type_of; constexpr DST operator()(const __half& src) const { @@ -103,8 +103,8 @@ struct ConvertOp { template struct ConvertOp { - using SRC = legate::legate_type_of; - using DST = legate::legate_type_of; + using SRC = legate::type_of; + using DST = legate::type_of; template ::value or @@ -125,7 +125,7 @@ struct ConvertOp { template struct ConvertOp { - using SRC = legate::legate_type_of; + using SRC = legate::type_of; template ::value>* = nullptr> __CUDA_HD__ __half operator()(const _SRC& src) const @@ -144,7 +144,7 @@ struct ConvertOp { template struct ConvertOp { - using DST = legate::legate_type_of; + using DST = legate::type_of; constexpr DST operator()(const __half& src) const { @@ -155,8 +155,8 @@ struct ConvertOp { template struct ConvertOp { - using SRC = legate::legate_type_of; - using DST = legate::legate_type_of; + using SRC = legate::type_of; + using DST = legate::type_of; template ::value or @@ -177,7 +177,7 @@ struct ConvertOp { template struct ConvertOp { - using SRC = legate::legate_type_of; + using SRC = legate::type_of; template ::value>* = nullptr> __CUDA_HD__ __half operator()(const _SRC& src) const @@ -196,7 +196,7 @@ struct ConvertOp { template struct ConvertOp { - using DST = legate::legate_type_of; + using DST = legate::type_of; constexpr DST operator()(const __half& src) const { diff --git a/src/cunumeric/unary/scalar_unary_red.h b/src/cunumeric/unary/scalar_unary_red.h index c6d051853d..d36450bdb1 100644 --- a/src/cunumeric/unary/scalar_unary_red.h +++ b/src/cunumeric/unary/scalar_unary_red.h @@ -22,8 +22,8 @@ namespace cunumeric { struct ScalarUnaryRedArgs { - legate::Store out; - legate::Store in; + legate::PhysicalStore out; + legate::PhysicalStore in; UnaryRedCode op_code; legate::DomainPoint shape; std::vector args; diff --git a/src/cunumeric/unary/scalar_unary_red_template.inl b/src/cunumeric/unary/scalar_unary_red_template.inl index eaad3d29f6..f746f96d3a 100644 --- a/src/cunumeric/unary/scalar_unary_red_template.inl +++ b/src/cunumeric/unary/scalar_unary_red_template.inl @@ -33,7 +33,7 @@ struct ScalarUnaryRed { using OP = UnaryRedOp; using LG_OP = typename OP::OP; using LHS = typename OP::VAL; - using RHS = legate_type_of; + using RHS = type_of; using OUT = AccessorRD; using IN = AccessorRO; diff --git a/src/cunumeric/unary/unary_op.h b/src/cunumeric/unary/unary_op.h index 793778a635..b6b4cafade 100644 --- a/src/cunumeric/unary/unary_op.h +++ b/src/cunumeric/unary/unary_op.h @@ -22,16 +22,16 @@ namespace cunumeric { struct UnaryOpArgs { - legate::Store in; - legate::Store out; + legate::PhysicalStore in; + legate::PhysicalStore out; UnaryOpCode op_code; std::vector args; }; struct MultiOutUnaryOpArgs { - legate::Store in; - legate::Store out1; - legate::Store out2; + legate::PhysicalStore in; + legate::PhysicalStore out1; + legate::PhysicalStore out2; UnaryOpCode op_code; }; diff --git a/src/cunumeric/unary/unary_op_template.inl b/src/cunumeric/unary/unary_op_template.inl index 1b0fefc6d0..5e5a6ebdd0 100644 --- a/src/cunumeric/unary/unary_op_template.inl +++ b/src/cunumeric/unary/unary_op_template.inl @@ -122,7 +122,7 @@ struct UnaryCopyImpl { template void operator()(UnaryOpArgs& args) const { - using VAL = legate_type_of; + using VAL = type_of; execute_copy(args); } diff --git a/src/cunumeric/unary/unary_op_util.h b/src/cunumeric/unary/unary_op_util.h index 68ecf89def..f741ee6499 100644 --- a/src/cunumeric/unary/unary_op_util.h +++ b/src/cunumeric/unary/unary_op_util.h @@ -199,7 +199,7 @@ struct UnaryOp { template struct UnaryOp { static constexpr bool valid = true; - using T = legate::legate_type_of; + using T = legate::type_of; UnaryOp(const std::vector& args) {} @@ -238,7 +238,7 @@ struct UnaryOp { template struct UnaryOp { static constexpr bool valid = is_floating_or_complex; - using T = legate::legate_type_of; + using T = legate::type_of; UnaryOp(const std::vector& args) {} @@ -252,7 +252,7 @@ struct UnaryOp { template struct UnaryOp { static constexpr bool valid = is_floating_or_complex; - using T = legate::legate_type_of; + using T = legate::type_of; UnaryOp(const std::vector& args) {} @@ -280,7 +280,7 @@ struct UnaryOp { template struct UnaryOp { static constexpr bool valid = is_floating_or_complex; - using T = legate::legate_type_of; + using T = legate::type_of; UnaryOp(const std::vector& args) {} @@ -294,7 +294,7 @@ struct UnaryOp { template struct UnaryOp { static constexpr bool valid = is_floating_or_complex; - using T = legate::legate_type_of; + using T = legate::type_of; UnaryOp(const std::vector& args) {} @@ -322,7 +322,7 @@ struct UnaryOp { template struct UnaryOp { static constexpr bool valid = is_floating_or_complex; - using T = legate::legate_type_of; + using T = legate::type_of; UnaryOp(const std::vector& args) {} @@ -336,7 +336,7 @@ struct UnaryOp { template struct UnaryOp { static constexpr bool valid = is_floating_or_complex; - using T = legate::legate_type_of; + using T = legate::type_of; UnaryOp(const std::vector& args) {} @@ -364,7 +364,7 @@ struct UnaryOp { template struct UnaryOp { static constexpr bool valid = legate::is_floating_point::value; - using T = legate::legate_type_of; + using T = legate::type_of; UnaryOp(const std::vector& args) {} @@ -392,7 +392,7 @@ struct UnaryOp { template struct UnaryOp { static constexpr bool valid = is_floating_point; - using T = legate::legate_type_of; + using T = legate::type_of; UnaryOp(const std::vector& args) {} @@ -406,7 +406,7 @@ struct UnaryOp { template struct UnaryOp { static constexpr bool valid = true; - using T = legate::legate_type_of; + using T = legate::type_of; UnaryOp(const std::vector& args) : min{args[0].value()}, max{args[1].value()} @@ -422,7 +422,7 @@ struct UnaryOp { template struct UnaryOp { - using T = legate::legate_type_of; + using T = legate::type_of; static constexpr bool valid = true; UnaryOp(const std::vector& args) {} @@ -443,7 +443,7 @@ struct UnaryOp { template struct UnaryOp { static constexpr bool valid = true; - using T = legate::legate_type_of; + using T = legate::type_of; UnaryOp(const std::vector& args) {} @@ -453,7 +453,7 @@ struct UnaryOp { template struct UnaryOp { static constexpr bool valid = is_floating_or_complex; - using T = legate::legate_type_of; + using T = legate::type_of; UnaryOp(const std::vector& args) {} @@ -467,7 +467,7 @@ struct UnaryOp { template struct UnaryOp { static constexpr bool valid = is_floating_or_complex; - using T = legate::legate_type_of; + using T = legate::type_of; UnaryOp(const std::vector& args) {} @@ -495,7 +495,7 @@ struct UnaryOp { template struct UnaryOp { static constexpr bool valid = is_floating_point; - using T = legate::legate_type_of; + using T = legate::type_of; UnaryOp(const std::vector& args) {} @@ -518,7 +518,7 @@ struct UnaryOp { template struct UnaryOp { static constexpr bool valid = true; - using T = legate::legate_type_of; + using T = legate::type_of; UnaryOp(const std::vector& args) {} @@ -532,7 +532,7 @@ struct UnaryOp { template struct UnaryOp { static constexpr bool valid = is_floating_or_complex; - using T = legate::legate_type_of; + using T = legate::type_of; UnaryOp(const std::vector& args) {} @@ -573,7 +573,7 @@ struct UnaryOp { template struct UnaryOp { static constexpr bool valid = is_floating_or_complex; - using T = legate::legate_type_of; + using T = legate::type_of; UnaryOp(const std::vector& args) {} @@ -609,7 +609,7 @@ struct UnaryOp { template struct UnaryOp { static constexpr bool valid = is_floating_point; - using T = legate::legate_type_of; + using T = legate::type_of; UnaryOp(const std::vector& args) {} @@ -622,7 +622,7 @@ struct UnaryOp { template struct UnaryOp { - using T = Argval>; + using T = Argval>; static constexpr bool valid = true; UnaryOp(const std::vector& args) {} @@ -632,7 +632,7 @@ struct UnaryOp { template struct UnaryOp { - using T = legate::legate_type_of; + using T = legate::type_of; static constexpr bool valid = legate::is_complex_type::value; UnaryOp(const std::vector& args) {} @@ -644,7 +644,7 @@ template struct UnaryOp { static constexpr bool valid = legate::is_integral::value && CODE != legate::Type::Code::BOOL; - using T = legate::legate_type_of; + using T = legate::type_of; UnaryOp(const std::vector& args) {} @@ -654,7 +654,7 @@ struct UnaryOp { template struct UnaryOp { static constexpr bool valid = true; - using T = legate::legate_type_of; + using T = legate::type_of; UnaryOp(const std::vector& args) {} @@ -682,7 +682,7 @@ struct UnaryOp { template struct UnaryOp { static constexpr bool valid = true; - using T = legate::legate_type_of; + using T = legate::type_of; UnaryOp(const std::vector& args) {} @@ -710,7 +710,7 @@ struct UnaryOp { template struct UnaryOp { static constexpr bool valid = true; - using T = legate::legate_type_of; + using T = legate::type_of; UnaryOp(const std::vector& args) {} @@ -740,7 +740,7 @@ template struct UnaryOp { static constexpr bool valid = is_floating_or_complex; ; - using T = legate::legate_type_of; + using T = legate::type_of; UnaryOp(const std::vector& args) {} @@ -755,7 +755,7 @@ template struct UnaryOp { static constexpr bool valid = is_floating_or_complex; ; - using T = legate::legate_type_of; + using T = legate::type_of; UnaryOp(const std::vector& args) {} @@ -784,7 +784,7 @@ template struct UnaryOp { static constexpr bool valid = is_floating_or_complex; ; - using T = legate::legate_type_of; + using T = legate::type_of; UnaryOp(const std::vector& args) {} @@ -821,7 +821,7 @@ template struct UnaryOp { static constexpr bool valid = is_floating_or_complex; ; - using T = legate::legate_type_of; + using T = legate::type_of; UnaryOp(const std::vector& args) {} @@ -857,7 +857,7 @@ struct UnaryOp { template struct UnaryOp { static constexpr bool valid = true; - using T = legate::legate_type_of; + using T = legate::type_of; UnaryOp(const std::vector& args) {} @@ -877,7 +877,7 @@ struct UnaryOp { template struct UnaryOp { static constexpr bool valid = true; - using T = legate::legate_type_of; + using T = legate::type_of; UnaryOp(const std::vector& args) {} @@ -887,7 +887,7 @@ struct UnaryOp { template struct UnaryOp { static constexpr bool valid = legate::is_floating_point::value; - using T = legate::legate_type_of; + using T = legate::type_of; UnaryOp(const std::vector& args) {} @@ -909,7 +909,7 @@ struct UnaryOp { template struct UnaryOp { - using T = legate::legate_type_of; + using T = legate::type_of; static constexpr bool valid = legate::is_complex_type::value; UnaryOp(const std::vector& args) {} @@ -919,7 +919,7 @@ struct UnaryOp { template struct UnaryOp { - using T = legate::legate_type_of; + using T = legate::type_of; static constexpr bool valid = true; UnaryOp(const std::vector& args) {} @@ -947,7 +947,7 @@ struct UnaryOp { template struct UnaryOp { static constexpr bool valid = true; - using T = legate::legate_type_of; + using T = legate::type_of; UnaryOp(const std::vector& args) {} @@ -997,7 +997,7 @@ constexpr T sign(const T& x) template struct UnaryOp { static constexpr bool valid = true; - using T = legate::legate_type_of; + using T = legate::type_of; UnaryOp(const std::vector& args) {} @@ -1034,7 +1034,7 @@ struct UnaryOp { template struct UnaryOp { static constexpr bool valid = legate::is_floating_point::value; - using T = legate::legate_type_of; + using T = legate::type_of; UnaryOp(const std::vector& args) {} @@ -1062,7 +1062,7 @@ struct UnaryOp { template struct UnaryOp { static constexpr bool valid = is_floating_or_complex; - using T = legate::legate_type_of; + using T = legate::type_of; UnaryOp(const std::vector& args) {} @@ -1076,7 +1076,7 @@ struct UnaryOp { template struct UnaryOp { static constexpr bool valid = is_floating_or_complex; - using T = legate::legate_type_of; + using T = legate::type_of; UnaryOp(const std::vector& args) {} @@ -1104,7 +1104,7 @@ struct UnaryOp { template struct UnaryOp { static constexpr bool valid = true; - using T = legate::legate_type_of; + using T = legate::type_of; UnaryOp(const std::vector& args) {} @@ -1124,7 +1124,7 @@ struct UnaryOp { template struct UnaryOp { static constexpr bool valid = true; - using T = legate::legate_type_of; + using T = legate::type_of; UnaryOp(const std::vector& args) {} @@ -1138,7 +1138,7 @@ struct UnaryOp { template struct UnaryOp { static constexpr bool valid = is_floating_or_complex; - using T = legate::legate_type_of; + using T = legate::type_of; UnaryOp(const std::vector& args) {} @@ -1152,7 +1152,7 @@ struct UnaryOp { template struct UnaryOp { static constexpr bool valid = is_floating_or_complex; - using T = legate::legate_type_of; + using T = legate::type_of; UnaryOp(const std::vector& args) {} @@ -1166,7 +1166,7 @@ struct UnaryOp { template struct UnaryOp { static constexpr bool valid = legate::is_floating_point::value; - using T = legate::legate_type_of; + using T = legate::type_of; UnaryOp(const std::vector& args) {} @@ -1199,7 +1199,7 @@ struct MultiOutUnaryOp { template struct MultiOutUnaryOp { static constexpr bool valid = legate::is_floating_point::value; - using RHS1 = legate::legate_type_of; + using RHS1 = legate::type_of; using RHS2 = int32_t; using LHS = RHS1; @@ -1227,7 +1227,7 @@ struct MultiOutUnaryOp { template struct MultiOutUnaryOp { static constexpr bool valid = legate::is_floating_point::value; - using RHS1 = legate::legate_type_of; + using RHS1 = legate::type_of; using RHS2 = RHS1; using LHS = RHS1; diff --git a/src/cunumeric/unary/unary_red.cc b/src/cunumeric/unary/unary_red.cc index 9040565831..1a30c9418e 100644 --- a/src/cunumeric/unary/unary_red.cc +++ b/src/cunumeric/unary/unary_red.cc @@ -25,7 +25,7 @@ template struct UnaryRedImplBody { using OP = UnaryRedOp; using LG_OP = typename OP::OP; - using RHS = legate_type_of; + using RHS = type_of; void operator()(AccessorRD lhs, AccessorRO rhs, diff --git a/src/cunumeric/unary/unary_red.cu b/src/cunumeric/unary/unary_red.cu index 272ef2c01d..860239f3e6 100644 --- a/src/cunumeric/unary/unary_red.cu +++ b/src/cunumeric/unary/unary_red.cu @@ -299,7 +299,7 @@ template struct UnaryRedImplBody { using OP = UnaryRedOp; using LG_OP = typename OP::OP; - using RHS = legate_type_of; + using RHS = type_of; using LHS = typename OP::VAL; void operator()(AccessorRD lhs, diff --git a/src/cunumeric/unary/unary_red.h b/src/cunumeric/unary/unary_red.h index 778be4b55e..03b864d281 100644 --- a/src/cunumeric/unary/unary_red.h +++ b/src/cunumeric/unary/unary_red.h @@ -22,8 +22,8 @@ namespace cunumeric { struct UnaryRedArgs { - legate::Store lhs; - legate::Store rhs; + legate::PhysicalStore lhs; + legate::PhysicalStore rhs; int32_t collapsed_dim; UnaryRedCode op_code; }; diff --git a/src/cunumeric/unary/unary_red_omp.cc b/src/cunumeric/unary/unary_red_omp.cc index 719bb9049c..cb82553e5a 100644 --- a/src/cunumeric/unary/unary_red_omp.cc +++ b/src/cunumeric/unary/unary_red_omp.cc @@ -76,7 +76,7 @@ template struct UnaryRedImplBody { using OP = UnaryRedOp; using LG_OP = typename OP::OP; - using RHS = legate_type_of; + using RHS = type_of; void operator()(AccessorRD lhs, AccessorRO rhs, diff --git a/src/cunumeric/unary/unary_red_template.inl b/src/cunumeric/unary/unary_red_template.inl index ab780d7074..4ec7868a33 100644 --- a/src/cunumeric/unary/unary_red_template.inl +++ b/src/cunumeric/unary/unary_red_template.inl @@ -38,7 +38,7 @@ struct UnaryRedImpl { void operator()(UnaryRedArgs& args) const { using OP = UnaryRedOp; - using RHS = legate_type_of; + using RHS = type_of; Pitches pitches; auto rect = args.rhs.shape(); diff --git a/src/cunumeric/unary/unary_red_util.h b/src/cunumeric/unary/unary_red_util.h index de61c9a031..cc53b4fbd9 100644 --- a/src/cunumeric/unary/unary_red_util.h +++ b/src/cunumeric/unary/unary_red_util.h @@ -104,7 +104,7 @@ template struct UnaryRedOp { static constexpr bool valid = TYPE_CODE != legate::Type::Code::COMPLEX128; - using RHS = legate::legate_type_of; + using RHS = legate::type_of; using VAL = bool; using OP = legate::ProdReduction; @@ -127,7 +127,7 @@ template struct UnaryRedOp { static constexpr bool valid = TYPE_CODE != legate::Type::Code::COMPLEX128; - using RHS = legate::legate_type_of; + using RHS = legate::type_of; using VAL = bool; using OP = legate::SumReduction; @@ -150,7 +150,7 @@ template struct UnaryRedOp { static constexpr bool valid = true; - using RHS = legate::legate_type_of; + using RHS = legate::type_of; using VAL = uint64_t; using OP = legate::SumReduction; @@ -176,7 +176,7 @@ template struct UnaryRedOp { static constexpr bool valid = !legate::is_complex::value; - using RHS = legate::legate_type_of; + using RHS = legate::type_of; using VAL = RHS; using OP = legate::MaxReduction; @@ -199,7 +199,7 @@ template struct UnaryRedOp { static constexpr bool valid = !legate::is_complex::value; - using RHS = legate::legate_type_of; + using RHS = legate::type_of; using VAL = RHS; using OP = legate::MinReduction; @@ -222,7 +222,7 @@ template struct UnaryRedOp { static constexpr bool valid = TYPE_CODE != legate::Type::Code::COMPLEX128; - using RHS = legate::legate_type_of; + using RHS = legate::type_of; using VAL = RHS; using OP = legate::ProdReduction; @@ -245,7 +245,7 @@ template struct UnaryRedOp { static constexpr bool valid = true; - using RHS = legate::legate_type_of; + using RHS = legate::type_of; using VAL = RHS; using OP = legate::SumReduction; @@ -268,7 +268,7 @@ template struct UnaryRedOp { static constexpr bool valid = !legate::is_complex::value; - using RHS = legate::legate_type_of; + using RHS = legate::type_of; using VAL = Argval; using OP = ArgmaxReduction; @@ -303,7 +303,7 @@ template struct UnaryRedOp { static constexpr bool valid = !legate::is_complex::value; - using RHS = legate::legate_type_of; + using RHS = legate::type_of; using VAL = Argval; using OP = ArgminReduction; @@ -342,7 +342,7 @@ template struct UnaryRedOp> { static constexpr bool valid = true; - using RHS = legate::legate_type_of; + using RHS = legate::type_of; using VAL = Argval; using OP = ArgmaxReduction; @@ -378,7 +378,7 @@ template struct UnaryRedOp> { static constexpr bool valid = true; - using RHS = legate::legate_type_of; + using RHS = legate::type_of; using VAL = Argval; using OP = ArgminReduction; @@ -414,7 +414,7 @@ template struct UnaryRedOp> { static constexpr bool valid = true; - using RHS = legate::legate_type_of; + using RHS = legate::type_of; using VAL = RHS; using OP = legate::MinReduction; @@ -443,7 +443,7 @@ template struct UnaryRedOp> { static constexpr bool valid = true; - using RHS = legate::legate_type_of; + using RHS = legate::type_of; using VAL = RHS; using OP = legate::MaxReduction; @@ -477,7 +477,7 @@ template struct UnaryRedOp> { static constexpr bool valid = true; - using RHS = legate::legate_type_of; + using RHS = legate::type_of; using VAL = RHS; using OP = legate::ProdReduction; @@ -511,7 +511,7 @@ template struct UnaryRedOp> { static constexpr bool valid = true; - using RHS = legate::legate_type_of; + using RHS = legate::type_of; using VAL = RHS; using OP = legate::SumReduction; @@ -542,7 +542,7 @@ struct UnaryRedOp { static constexpr bool valid = false; // This class only provides the typedefs necessary to match the other operators. // It does not provide fold/convert functions. - using RHS = legate::legate_type_of; + using RHS = legate::type_of; using VAL = bool; using _RED_OP = UnaryRedOp; using OP = _RED_OP::OP; From 55e9920cf8939f7f02cd4ff1c9b2eb3d31181776 Mon Sep 17 00:00:00 2001 From: Manolis Papadakis Date: Wed, 15 Nov 2023 14:02:06 -0800 Subject: [PATCH 118/462] Always insert braces around if-condition etc. (#55) --- .clang-format | 3 +- examples/cpp/stencil/stencil.cc | 4 +- src/cunumeric/binary/binary_op.cc | 13 +- src/cunumeric/binary/binary_op.cu | 8 +- src/cunumeric/binary/binary_op_omp.cc | 4 +- src/cunumeric/binary/binary_op_template.inl | 8 +- src/cunumeric/binary/binary_op_util.cc | 9 +- src/cunumeric/binary/binary_op_util.h | 30 +- src/cunumeric/binary/binary_red.cc | 3 +- src/cunumeric/binary/binary_red.cu | 16 +- src/cunumeric/binary/binary_red_omp.cc | 11 +- src/cunumeric/binary/binary_red_template.inl | 8 +- src/cunumeric/bits/packbits.cu | 4 +- src/cunumeric/bits/packbits_template.inl | 4 +- src/cunumeric/bits/unpackbits.cu | 4 +- src/cunumeric/bits/unpackbits_template.inl | 4 +- src/cunumeric/cephes/i0.cc | 4 +- src/cunumeric/convolution/convolve.cc | 60 ++-- src/cunumeric/convolution/convolve.cu | 281 +++++++++++++----- src/cunumeric/convolution/convolve_omp.cc | 63 ++-- .../convolution/convolve_template.inl | 135 ++++++--- src/cunumeric/cuda_help.h | 12 +- src/cunumeric/cudalibs.cu | 83 ++++-- src/cunumeric/divmod.h | 12 +- .../indexing/parallel_loop.cuh | 8 +- .../execution_policy/indexing/parallel_loop.h | 4 +- .../indexing/parallel_loop_omp.h | 4 +- .../reduction/scalar_reduction.cuh | 8 +- .../reduction/scalar_reduction.h | 4 +- .../reduction/scalar_reduction_omp.h | 12 +- src/cunumeric/fft/fft.cu | 28 +- src/cunumeric/fft/fft_template.inl | 8 +- src/cunumeric/index/advanced_indexing.cc | 24 +- src/cunumeric/index/advanced_indexing.cu | 23 +- src/cunumeric/index/advanced_indexing_omp.cc | 20 +- src/cunumeric/index/choose.cu | 16 +- src/cunumeric/index/choose_template.inl | 8 +- src/cunumeric/index/putmask_template.inl | 16 +- src/cunumeric/index/repeat.cc | 4 +- src/cunumeric/index/repeat.cu | 8 +- src/cunumeric/index/repeat_omp.cc | 8 +- src/cunumeric/index/repeat_template.inl | 4 +- src/cunumeric/index/wrap.cc | 8 +- src/cunumeric/index/wrap.cu | 20 +- src/cunumeric/index/wrap.h | 3 +- src/cunumeric/index/wrap_omp.cc | 18 +- src/cunumeric/index/wrap_template.inl | 4 +- src/cunumeric/index/zip.cc | 4 +- src/cunumeric/index/zip.cu | 32 +- src/cunumeric/index/zip.h | 3 +- src/cunumeric/index/zip_omp.cc | 20 +- src/cunumeric/index/zip_template.inl | 12 +- src/cunumeric/mapper.cc | 24 +- src/cunumeric/matrix/contract_template.inl | 16 +- src/cunumeric/matrix/diag.cc | 8 +- src/cunumeric/matrix/diag.cu | 12 +- src/cunumeric/matrix/diag_omp.cc | 4 +- src/cunumeric/matrix/diag_template.inl | 12 +- src/cunumeric/matrix/dot.cu | 3 +- src/cunumeric/matrix/dot_omp.cc | 8 +- src/cunumeric/matrix/dot_template.inl | 4 +- src/cunumeric/matrix/gemm_template.inl | 4 +- src/cunumeric/matrix/matmul_cpu.inl | 10 +- src/cunumeric/matrix/matmul_template.inl | 4 +- src/cunumeric/matrix/matvecmul.cu | 20 +- src/cunumeric/matrix/matvecmul_template.inl | 8 +- src/cunumeric/matrix/potrf.cc | 16 +- src/cunumeric/matrix/potrf.cu | 4 +- src/cunumeric/matrix/potrf_omp.cc | 16 +- src/cunumeric/matrix/potrf_template.inl | 4 +- src/cunumeric/matrix/solve.cu | 4 +- src/cunumeric/matrix/solve_cpu.inl | 16 +- src/cunumeric/matrix/syrk_template.inl | 4 +- src/cunumeric/matrix/tile.cu | 4 +- src/cunumeric/matrix/tile_template.inl | 7 +- src/cunumeric/matrix/transpose.cc | 19 +- src/cunumeric/matrix/transpose.cu | 41 ++- src/cunumeric/matrix/transpose_omp.cc | 14 +- src/cunumeric/matrix/transpose_template.inl | 7 +- src/cunumeric/matrix/trilu.cc | 15 +- src/cunumeric/matrix/trilu.cu | 14 +- src/cunumeric/matrix/trilu_omp.cc | 10 +- src/cunumeric/matrix/trilu_template.inl | 14 +- src/cunumeric/matrix/trsm_template.inl | 4 +- src/cunumeric/matrix/util.cc | 26 +- src/cunumeric/matrix/util.h | 4 +- src/cunumeric/ndarray.cc | 129 +++++--- src/cunumeric/nullary/arange.cc | 3 +- src/cunumeric/nullary/arange.cu | 4 +- src/cunumeric/nullary/arange_omp.cc | 3 +- src/cunumeric/nullary/arange_template.inl | 4 +- src/cunumeric/nullary/eye.cc | 4 +- src/cunumeric/nullary/eye.cu | 4 +- src/cunumeric/nullary/eye_omp.cc | 4 +- src/cunumeric/nullary/eye_template.inl | 4 +- src/cunumeric/nullary/fill.cc | 4 +- src/cunumeric/nullary/fill.cu | 8 +- src/cunumeric/nullary/fill_omp.cc | 4 +- src/cunumeric/nullary/fill_template.inl | 4 +- src/cunumeric/nullary/window.cc | 8 +- src/cunumeric/nullary/window.cu | 8 +- src/cunumeric/nullary/window_omp.cc | 8 +- src/cunumeric/nullary/window_template.inl | 4 +- src/cunumeric/operators.cc | 66 ++-- src/cunumeric/pitches.h | 13 +- src/cunumeric/random/bitgenerator_curand.inl | 11 +- .../random/bitgenerator_template.inl | 16 +- src/cunumeric/random/rand.cc | 4 +- src/cunumeric/random/rand.cu | 8 +- src/cunumeric/random/rand_omp.cc | 4 +- src/cunumeric/random/rand_template.inl | 8 +- src/cunumeric/random/rand_util.h | 8 +- src/cunumeric/random/randutil/generator.cuh | 13 +- src/cunumeric/random/randutil/generator.h | 4 +- .../random/randutil/generator_gumbel.inl | 8 +- .../random/randutil/generator_laplace.inl | 18 +- .../random/randutil/generator_logistic.inl | 8 +- .../random/randutil/generator_triangular.inl | 16 +- .../random/randutil/generator_wald.inl | 10 +- .../random/randutil/generator_weibull.inl | 8 +- .../random/randutil/random_distributions.h | 140 ++++++--- src/cunumeric/runtime.cc | 8 +- src/cunumeric/scan/scan_global.cu | 4 +- src/cunumeric/scan/scan_global_template.inl | 4 +- src/cunumeric/scan/scan_local.cu | 4 +- src/cunumeric/search/argwhere.cc | 4 +- src/cunumeric/search/argwhere.cu | 8 +- src/cunumeric/search/argwhere_omp.cc | 16 +- src/cunumeric/search/nonzero.cc | 11 +- src/cunumeric/search/nonzero.cu | 19 +- src/cunumeric/search/nonzero.cuh | 3 +- src/cunumeric/search/nonzero_omp.cc | 23 +- src/cunumeric/search/nonzero_template.inl | 8 +- src/cunumeric/set/unique.cc | 4 +- src/cunumeric/set/unique.cu | 7 +- src/cunumeric/set/unique_omp.cc | 4 +- src/cunumeric/sort/cub_sort.cuh | 8 +- src/cunumeric/sort/searchsorted.cc | 4 +- src/cunumeric/sort/searchsorted.cu | 16 +- src/cunumeric/sort/searchsorted_omp.cc | 4 +- src/cunumeric/sort/searchsorted_template.inl | 8 +- src/cunumeric/sort/sort.cu | 92 ++++-- src/cunumeric/sort/sort.h | 4 +- src/cunumeric/sort/sort_cpu.inl | 49 ++- src/cunumeric/sort/sort_template.inl | 8 +- src/cunumeric/stat/bincount.cu | 20 +- src/cunumeric/stat/bincount_omp.cc | 12 +- src/cunumeric/stat/bincount_template.inl | 4 +- src/cunumeric/stat/histogram_impl.h | 19 +- src/cunumeric/stat/histogram_template.inl | 4 +- src/cunumeric/ternary/where.cc | 3 +- src/cunumeric/ternary/where.cu | 8 +- src/cunumeric/ternary/where_omp.cc | 3 +- src/cunumeric/ternary/where_template.inl | 4 +- src/cunumeric/transform/flip.cc | 3 +- src/cunumeric/transform/flip.cu | 12 +- src/cunumeric/transform/flip_omp.cc | 3 +- src/cunumeric/transform/flip_template.inl | 4 +- src/cunumeric/unary/convert.cc | 4 +- src/cunumeric/unary/convert.cu | 8 +- src/cunumeric/unary/convert_omp.cc | 4 +- src/cunumeric/unary/convert_template.inl | 4 +- src/cunumeric/unary/convert_util.h | 5 +- .../unary/scalar_unary_red_template.inl | 16 +- src/cunumeric/unary/unary_op.cc | 12 +- src/cunumeric/unary/unary_op.cu | 24 +- src/cunumeric/unary/unary_op_omp.cc | 12 +- src/cunumeric/unary/unary_op_template.inl | 16 +- src/cunumeric/unary/unary_red.cu | 38 ++- src/cunumeric/unary/unary_red_omp.cc | 14 +- src/cunumeric/unary/unary_red_template.inl | 4 +- src/cunumeric/unary/unary_red_util.h | 16 +- tests/cpp/integration/test_sort.cc | 30 +- tests/cpp/integration/test_transpose.cc | 5 +- tests/cpp/integration/util.inl | 56 +++- 175 files changed, 1975 insertions(+), 776 deletions(-) diff --git a/.clang-format b/.clang-format index 2622382547..6d5353f992 100644 --- a/.clang-format +++ b/.clang-format @@ -8,7 +8,7 @@ AlignEscapedNewlines: Left AlignOperands: true AlignTrailingComments: true AllowAllParametersOfDeclarationOnNextLine: true -AllowShortBlocksOnASingleLine: true +AllowShortBlocksOnASingleLine: Empty AllowShortCaseLabelsOnASingleLine: true AllowShortFunctionsOnASingleLine: All AllowShortIfStatementsOnASingleLine: true @@ -72,6 +72,7 @@ IndentCaseLabels: true IndentPPDirectives: None IndentWidth: 2 IndentWrappedFunctionNames: false +InsertBraces: true JavaScriptQuotes: Leave JavaScriptWrapImports: true KeepEmptyLinesAtTheStartOfBlocks: false diff --git a/examples/cpp/stencil/stencil.cc b/examples/cpp/stencil/stencil.cc index 71b9b3365f..5e3169c05d 100644 --- a/examples/cpp/stencil/stencil.cc +++ b/examples/cpp/stencil/stencil.cc @@ -39,7 +39,9 @@ void print_array(cunumeric::NDArray array) std::stringstream ss; for (uint32_t i = 0; i < shape[0]; ++i) { for (uint32_t j = 0; j < shape[0]; ++j) { - if (j > 0) ss << " "; + if (j > 0) { + ss << " "; + } ss << std::setw(8) << std::setprecision(5) << acc[i][j]; } ss << std::endl; diff --git a/src/cunumeric/binary/binary_op.cc b/src/cunumeric/binary/binary_op.cc index 8498275d1f..dd53fc4939 100644 --- a/src/cunumeric/binary/binary_op.cc +++ b/src/cunumeric/binary/binary_op.cc @@ -41,7 +41,9 @@ struct BinaryOpImplBody { auto outptr = out.ptr(rect); auto in1ptr = in1.ptr(rect); auto in2ptr = in2.ptr(rect); - for (size_t idx = 0; idx < volume; ++idx) outptr[idx] = func(in1ptr[idx], in2ptr[idx]); + for (size_t idx = 0; idx < volume; ++idx) { + outptr[idx] = func(in1ptr[idx], in2ptr[idx]); + } } else { for (size_t idx = 0; idx < volume; ++idx) { auto p = pitches.unflatten(idx, rect.lo); @@ -62,7 +64,9 @@ std::vector broadcast_shapes(std::vector arrays) assert(!arrays.empty()); #endif int32_t dim = 0; - for (auto& array : arrays) dim = std::max(dim, array.dim()); + for (auto& array : arrays) { + dim = std::max(dim, array.dim()); + } std::vector result(dim, 1); @@ -72,10 +76,11 @@ std::vector broadcast_shapes(std::vector arrays) auto in_it = shape.rbegin(); auto out_it = result.rbegin(); for (; in_it != shape.rend() && out_it != result.rend(); ++in_it, ++out_it) { - if (1 == *out_it) + if (1 == *out_it) { *out_it = *in_it; - else if (*in_it != 1 && *out_it != *in_it) + } else if (*in_it != 1 && *out_it != *in_it) { throw std::exception(); + } } } return result; diff --git a/src/cunumeric/binary/binary_op.cu b/src/cunumeric/binary/binary_op.cu index 305e7e5914..2334b10adb 100644 --- a/src/cunumeric/binary/binary_op.cu +++ b/src/cunumeric/binary/binary_op.cu @@ -26,7 +26,9 @@ static __global__ void __launch_bounds__(THREADS_PER_BLOCK, MIN_CTAS_PER_SM) dense_kernel(size_t volume, Function func, LHS* out, const RHS1* in1, const RHS2* in2) { const size_t idx = global_tid_1d(); - if (idx >= volume) return; + if (idx >= volume) { + return; + } out[idx] = func(in1[idx], in2[idx]); } @@ -46,7 +48,9 @@ static __global__ void __launch_bounds__(THREADS_PER_BLOCK, MIN_CTAS_PER_SM) Rect rect) { const size_t idx = global_tid_1d(); - if (idx >= volume) return; + if (idx >= volume) { + return; + } auto point = pitches.unflatten(idx, rect.lo); out[point] = func(in1[point], in2[point]); } diff --git a/src/cunumeric/binary/binary_op_omp.cc b/src/cunumeric/binary/binary_op_omp.cc index 73985ae8f8..23910b560e 100644 --- a/src/cunumeric/binary/binary_op_omp.cc +++ b/src/cunumeric/binary/binary_op_omp.cc @@ -42,7 +42,9 @@ struct BinaryOpImplBody { auto in1ptr = in1.ptr(rect); auto in2ptr = in2.ptr(rect); #pragma omp parallel for schedule(static) - for (size_t idx = 0; idx < volume; ++idx) outptr[idx] = func(in1ptr[idx], in2ptr[idx]); + for (size_t idx = 0; idx < volume; ++idx) { + outptr[idx] = func(in1ptr[idx], in2ptr[idx]); + } } else { #pragma omp parallel for schedule(static) for (size_t idx = 0; idx < volume; ++idx) { diff --git a/src/cunumeric/binary/binary_op_template.inl b/src/cunumeric/binary/binary_op_template.inl index f4b0b4d0f1..aed10e995c 100644 --- a/src/cunumeric/binary/binary_op_template.inl +++ b/src/cunumeric/binary/binary_op_template.inl @@ -43,7 +43,9 @@ struct BinaryOpImpl { Pitches pitches; size_t volume = pitches.flatten(rect); - if (volume == 0) return; + if (volume == 0) { + return; + } auto out = args.out.write_accessor(rect); auto in1 = args.in1.read_accessor(rect); @@ -88,7 +90,9 @@ static void binary_op_template(TaskContext& context) std::vector extra_args; extra_args.reserve(scalars.size() - 1); - for (size_t idx = 1; idx < scalars.size(); ++idx) extra_args.emplace_back(scalars[idx]); + for (size_t idx = 1; idx < scalars.size(); ++idx) { + extra_args.emplace_back(scalars[idx]); + } BinaryOpArgs args{ inputs[0], inputs[1], outputs[0], scalars[0].value(), std::move(extra_args)}; diff --git a/src/cunumeric/binary/binary_op_util.cc b/src/cunumeric/binary/binary_op_util.cc index 24fdd8e96e..ca9f0e0adb 100644 --- a/src/cunumeric/binary/binary_op_util.cc +++ b/src/cunumeric/binary/binary_op_util.cc @@ -26,7 +26,9 @@ std::vector broadcast_shapes(std::vector arrays) assert(!arrays.empty()); #endif int32_t dim = 0; - for (auto& array : arrays) dim = std::max(dim, array.dim()); + for (auto& array : arrays) { + dim = std::max(dim, array.dim()); + } std::vector result(dim, 1); @@ -36,10 +38,11 @@ std::vector broadcast_shapes(std::vector arrays) auto in_it = shape.rbegin(); auto out_it = result.rbegin(); for (; in_it != shape.rend() && out_it != result.rend(); ++in_it, ++out_it) { - if (1 == *out_it) + if (1 == *out_it) { *out_it = *in_it; - else if (*in_it != 1 && *out_it != *in_it) + } else if (*in_it != 1 && *out_it != *in_it) { throw std::exception(); + } } } return result; diff --git a/src/cunumeric/binary/binary_op_util.h b/src/cunumeric/binary/binary_op_util.h index 76532c0038..bc506db85a 100644 --- a/src/cunumeric/binary/binary_op_util.h +++ b/src/cunumeric/binary/binary_op_util.h @@ -485,7 +485,9 @@ struct BinaryOp { { using std::fabs; using std::isinf; - if (isinf(a) || isinf(b)) return a == b; + if (isinf(a) || isinf(b)) { + return a == b; + } return fabs(static_cast(a) - static_cast(b)) <= atol_ + rtol_ * static_cast(fabs(b)); } @@ -510,7 +512,9 @@ struct BinaryOp { __CUDA_HD__ T operator()(const T& a, const T& b) const { T r = _gcd(a, b); - if (r == 0) return 0; + if (r == 0) { + return 0; + } r = a / r * b; return r >= 0 ? r : -r; } @@ -519,7 +523,9 @@ struct BinaryOp { __CUDA_HD__ T operator()(const T& a, const T& b) const { T r = _gcd(a, b); - if (r == 0) return 0; + if (r == 0) { + return 0; + } r = a / r * b; return r; } @@ -529,7 +535,9 @@ struct BinaryOp { int32_t a = static_cast(_a); int32_t b = static_cast(_b); int32_t r = _gcd(a, b); - if (r == 0) return false; + if (r == 0) { + return false; + } r = a / r * b; return static_cast(r); } @@ -605,10 +613,11 @@ struct BinaryOp { using std::fmax; using std::log; using std::log1p; - if (a == b) + if (a == b) { return a + log(T{2.0}); - else + } else { return fmax(a, b) + log1p(exp(-fabs(a - b))); + } } }; @@ -638,10 +647,11 @@ struct BinaryOp { using std::fabs; using std::fmax; using std::log2; - if (a == b) + if (a == b) { return a + T{1.0}; - else + } else { return fmax(a, b) + log2(T{1} + exp2(-fabs(a - b))); + } } }; @@ -736,7 +746,9 @@ constexpr T real_mod(const T& a, const T& b) { T res = std::fmod(a, b); if (res) { - if ((b < static_cast(0)) != (res < static_cast(0))) res += b; + if ((b < static_cast(0)) != (res < static_cast(0))) { + res += b; + } } else { res = std::copysign(static_cast(0), b); } diff --git a/src/cunumeric/binary/binary_red.cc b/src/cunumeric/binary/binary_red.cc index 6f96ab09a1..d9621ca09b 100644 --- a/src/cunumeric/binary/binary_red.cc +++ b/src/cunumeric/binary/binary_red.cc @@ -39,11 +39,12 @@ struct BinaryRedImplBody { if (dense) { auto in1ptr = in1.ptr(rect); auto in2ptr = in2.ptr(rect); - for (size_t idx = 0; idx < volume; ++idx) + for (size_t idx = 0; idx < volume; ++idx) { if (!func(in1ptr[idx], in2ptr[idx])) { out.reduce(0, false); return; } + } } else { for (size_t idx = 0; idx < volume; ++idx) { auto point = pitches.unflatten(idx, rect.lo); diff --git a/src/cunumeric/binary/binary_red.cu b/src/cunumeric/binary/binary_red.cu index 8702a9c91a..624977c281 100644 --- a/src/cunumeric/binary/binary_red.cu +++ b/src/cunumeric/binary/binary_red.cu @@ -26,8 +26,12 @@ static __global__ void __launch_bounds__(THREADS_PER_BLOCK, MIN_CTAS_PER_SM) dense_kernel(size_t volume, Function func, RES out, const ARG* in1, const ARG* in2) { const size_t idx = global_tid_1d(); - if (idx >= volume) return; - if (!func(in1[idx], in2[idx])) out.reduce(false); + if (idx >= volume) { + return; + } + if (!func(in1[idx], in2[idx])) { + out.reduce(false); + } } template @@ -35,9 +39,13 @@ static __global__ void __launch_bounds__(THREADS_PER_BLOCK, MIN_CTAS_PER_SM) gen size_t volume, Function func, RES out, ReadAcc in1, ReadAcc in2, Pitches pitches, Rect rect) { const size_t idx = global_tid_1d(); - if (idx >= volume) return; + if (idx >= volume) { + return; + } auto point = pitches.unflatten(idx, rect.lo); - if (!func(in1[point], in2[point])) out.reduce(false); + if (!func(in1[point], in2[point])) { + out.reduce(false); + } } template diff --git a/src/cunumeric/binary/binary_red_omp.cc b/src/cunumeric/binary/binary_red_omp.cc index 4fb4ba6441..5e839acb91 100644 --- a/src/cunumeric/binary/binary_red_omp.cc +++ b/src/cunumeric/binary/binary_red_omp.cc @@ -41,13 +41,18 @@ struct BinaryRedImplBody { auto in1ptr = in1.ptr(rect); auto in2ptr = in2.ptr(rect); #pragma omp parallel for schedule(static) - for (size_t idx = 0; idx < volume; ++idx) - if (!func(in1ptr[idx], in2ptr[idx])) result = false; + for (size_t idx = 0; idx < volume; ++idx) { + if (!func(in1ptr[idx], in2ptr[idx])) { + result = false; + } + } } else { #pragma omp parallel for schedule(static) for (size_t idx = 0; idx < volume; ++idx) { auto point = pitches.unflatten(idx, rect.lo); - if (!func(in1[point], in2[point])) result = false; + if (!func(in1[point], in2[point])) { + result = false; + } } } diff --git a/src/cunumeric/binary/binary_red_template.inl b/src/cunumeric/binary/binary_red_template.inl index 64d7b304aa..65d0f2f00c 100644 --- a/src/cunumeric/binary/binary_red_template.inl +++ b/src/cunumeric/binary/binary_red_template.inl @@ -48,7 +48,9 @@ struct BinaryRedImpl { Pitches pitches; size_t volume = pitches.flatten(rect); - if (0 == volume) return; + if (0 == volume) { + return; + } auto out = args.out.reduce_accessor, true, 1>(); auto in1 = args.in1.read_accessor(rect); @@ -91,7 +93,9 @@ static void binary_red_template(TaskContext& context) std::vector extra_args; extra_args.reserve(scalars.size() - 1); - for (size_t idx = 1; idx < scalars.size(); ++idx) extra_args.emplace_back(scalars[idx]); + for (size_t idx = 1; idx < scalars.size(); ++idx) { + extra_args.emplace_back(scalars[idx]); + } BinaryRedArgs args{context.reduction(0), inputs[0], diff --git a/src/cunumeric/bits/packbits.cu b/src/cunumeric/bits/packbits.cu index 5c3cfa7677..1fb30c5787 100644 --- a/src/cunumeric/bits/packbits.cu +++ b/src/cunumeric/bits/packbits.cu @@ -34,7 +34,9 @@ static __global__ void __launch_bounds__(THREADS_PER_BLOCK, MIN_CTAS_PER_SM) uint32_t axis) { const size_t idx = global_tid_1d(); - if (idx >= volume) return; + if (idx >= volume) { + return; + } auto out_p = pitches.unflatten(idx, lo); out[out_p] = pack(in, out_p, in_hi_axis, axis); } diff --git a/src/cunumeric/bits/packbits_template.inl b/src/cunumeric/bits/packbits_template.inl index ffa2fefb48..5eb38184a0 100644 --- a/src/cunumeric/bits/packbits_template.inl +++ b/src/cunumeric/bits/packbits_template.inl @@ -36,7 +36,9 @@ struct PackbitsImpl { auto out_rect = output.shape(); - if (out_rect.empty()) return; + if (out_rect.empty()) { + return; + } auto in_rect = input.shape(); diff --git a/src/cunumeric/bits/unpackbits.cu b/src/cunumeric/bits/unpackbits.cu index c48edde560..19a166e7d2 100644 --- a/src/cunumeric/bits/unpackbits.cu +++ b/src/cunumeric/bits/unpackbits.cu @@ -34,7 +34,9 @@ static __global__ void __launch_bounds__(THREADS_PER_BLOCK, MIN_CTAS_PER_SM) uint32_t axis) { const size_t idx = global_tid_1d(); - if (idx >= volume) return; + if (idx >= volume) { + return; + } auto in_p = in_pitches.unflatten(idx, in_lo); unpack(out, in, in_p, axis); } diff --git a/src/cunumeric/bits/unpackbits_template.inl b/src/cunumeric/bits/unpackbits_template.inl index 64d831ab35..2197a1b02c 100644 --- a/src/cunumeric/bits/unpackbits_template.inl +++ b/src/cunumeric/bits/unpackbits_template.inl @@ -34,7 +34,9 @@ struct UnpackbitsImpl { { auto out_rect = output.shape(); - if (out_rect.empty()) return; + if (out_rect.empty()) { + return; + } auto in_rect = input.shape(); diff --git a/src/cunumeric/cephes/i0.cc b/src/cunumeric/cephes/i0.cc index d601e1e20a..2717dc6ff0 100644 --- a/src/cunumeric/cephes/i0.cc +++ b/src/cunumeric/cephes/i0.cc @@ -111,7 +111,9 @@ extern double chbevl(double x, double array[], int n); double i0(double x) { - if (x < 0) x = -x; + if (x < 0) { + x = -x; + } if (x <= 8.0) { double y = x / 2.0 - 2.0; return exp(x) * chbevl(y, A, 30); diff --git a/src/cunumeric/convolution/convolve.cc b/src/cunumeric/convolution/convolve.cc index 7b2f69371a..20e7deae07 100644 --- a/src/cunumeric/convolution/convolve.cc +++ b/src/cunumeric/convolution/convolve.cc @@ -87,7 +87,9 @@ struct ConvolveImplBody { const Point one = Point::ONES(); Point extents = filter_rect.hi - filter_rect.lo + one; Point centers; - for (int d = 0; d < DIM; d++) centers[d] = extents[d] / 2; + for (int d = 0; d < DIM; d++) { + centers[d] = extents[d] / 2; + } // Compute the tiles for the L2 cache Point l2_output_tile, l2_filter_tile; @@ -102,11 +104,13 @@ struct ConvolveImplBody { compute_filter_tile( l2_filter_tile, filter_bounds, l2_output_tile, 3 * L2_CACHE_SIZE / 4); unsigned total_l2_filters = 1; - for (int d = 0; d < DIM; d++) + for (int d = 0; d < DIM; d++) { total_l2_filters *= ((extents[d] + l2_filter_tile[d] - 1) / l2_filter_tile[d]); + } unsigned total_l2_outputs = 1; - for (int d = 0; d < DIM; d++) + for (int d = 0; d < DIM; d++) { total_l2_outputs *= ((output_bounds[d] + l2_output_tile[d] - 1) / l2_output_tile[d]); + } // Compute the tiles for the L1 cache Point l1_output_tile, l1_filter_tile; @@ -119,11 +123,13 @@ struct ConvolveImplBody { compute_filter_tile( l1_filter_tile, filter_bounds, l1_output_tile, 3 * L1_CACHE_SIZE / 4); unsigned total_l1_filters = 1; - for (int d = 0; d < DIM; d++) + for (int d = 0; d < DIM; d++) { total_l1_filters *= ((l2_filter_tile[d] + l1_filter_tile[d] - 1) / l1_filter_tile[d]); + } unsigned total_l1_outputs = 1; - for (int d = 0; d < DIM; d++) + for (int d = 0; d < DIM; d++) { total_l1_outputs *= ((l2_output_tile[d] + l1_output_tile[d] - 1) / l1_output_tile[d]); + } // Zero out the output data since we're going to be doing sum accumulations Point output = subrect.lo; @@ -132,10 +138,11 @@ struct ConvolveImplBody { out[output] = VAL{0}; for (int d = DIM - 1; d >= 0; d--) { output[d]++; - if (subrect.hi[d] < output[d]) + if (subrect.hi[d] < output[d]) { output[d] = subrect.lo[d]; - else + } else { break; + } } } @@ -148,9 +155,10 @@ struct ConvolveImplBody { if (!filter_rect.contains(l2_filter_rect)) { l2_filter_rect = filter_rect.intersection(l2_filter_rect); local_l1_filters = 1; - for (int d = 0; d < DIM; d++) + for (int d = 0; d < DIM; d++) { local_l1_filters *= ((l2_filter_rect.hi[d] - l2_filter_rect.lo[d] + l1_filter_tile[d]) / l1_filter_tile[d]); + } } // Now iterate the tiles for the L2 outputs Point l2_output = subrect.lo; @@ -160,9 +168,10 @@ struct ConvolveImplBody { if (!subrect.contains(l2_output_rect)) { l2_output_rect = subrect.intersection(l2_output_rect); local_l1_outputs = 1; - for (int d = 0; d < DIM; d++) + for (int d = 0; d < DIM; d++) { local_l1_outputs *= ((l2_output_rect.hi[d] - l2_output_rect.lo[d] + l1_output_tile[d]) / l1_output_tile[d]); + } } // Do a quick check here to see if all the inputs are contained for // this particular tile @@ -189,61 +198,68 @@ struct ConvolveImplBody { Point filter_point = l1_filter_rect.lo; for (unsigned fidx = 0; fidx < l1_filter_points; fidx++) { Point input = output + extents - filter_point - one - centers; - if (input_contained || root_rect.contains(input)) + if (input_contained || root_rect.contains(input)) { acc += in[input] * filter[filter_point]; + } // Step to the next filter point for (int d = DIM - 1; d >= 0; d--) { filter_point[d]++; - if (l1_filter_rect.hi[d] < filter_point[d]) + if (l1_filter_rect.hi[d] < filter_point[d]) { filter_point[d] = l1_filter_rect.lo[d]; - else + } else { break; + } } } out[output] += acc; // Step to the next output point for (int d = DIM - 1; d >= 0; d--) { output[d]++; - if (l1_output_rect.hi[d] < output[d]) + if (l1_output_rect.hi[d] < output[d]) { output[d] = l1_output_rect.lo[d]; - else + } else { break; + } } } // Step to the next L1 filter for (int d = DIM - 1; d >= 0; d--) { l1_filter[d] += l1_filter_tile[d]; - if (l2_filter_rect.hi[d] < l1_filter[d]) + if (l2_filter_rect.hi[d] < l1_filter[d]) { l1_filter[d] = l2_filter_rect.lo[d]; - else + } else { break; + } } } // Step to the next L1 output tile for (int d = DIM - 1; d >= 0; d--) { l1_output[d] += l1_output_tile[d]; - if (l2_output_rect.hi[d] < l1_output[d]) + if (l2_output_rect.hi[d] < l1_output[d]) { l1_output[d] = l2_output_rect.lo[d]; - else + } else { break; + } } } // Step to the next output tile for (int d = DIM - 1; d >= 0; d--) { l2_output[d] += l2_output_tile[d]; - if (subrect.hi[d] < l2_output[d]) + if (subrect.hi[d] < l2_output[d]) { l2_output[d] = subrect.lo[d]; - else + } else { break; + } } } // Step to the next l2 filter for (int d = DIM - 1; d >= 0; d--) { l2_filter[d] += l2_filter_tile[d]; - if (filter_rect.hi[d] < l2_filter[d]) + if (filter_rect.hi[d] < l2_filter[d]) { l2_filter[d] = filter_rect.lo[d]; - else + } else { break; + } } } } diff --git a/src/cunumeric/convolution/convolve.cu b/src/cunumeric/convolution/convolve.cu index 8c381d6434..7d185d6d86 100644 --- a/src/cunumeric/convolution/convolve.cu +++ b/src/cunumeric/convolution/convolve.cu @@ -65,10 +65,14 @@ __global__ static void __launch_bounds__(THREADS_PER_BLOCK, 4) const size_t volume) { size_t offset = blockIdx.x * blockDim.x + threadIdx.x; - if (offset >= volume) return; + if (offset >= volume) { + return; + } Point point = subrect_lo; #pragma unroll - for (int d = 0; d < DIM; d++) point[d] += args.pitches[d].divmod(offset, offset); + for (int d = 0; d < DIM; d++) { + point[d] += args.pitches[d].divmod(offset, offset); + } out[point] = VAL{0}; } @@ -133,7 +137,9 @@ __global__ static void __launch_bounds__(CONVOLUTION_THREADS, 1) Point thread_offset; int offset = threadIdx.x; #pragma unroll - for (int d = 0; d < DIM; d++) thread_offset[d] = args.l1_output_pitches[d].divmod(offset, offset); + for (int d = 0; d < DIM; d++) { + thread_offset[d] = args.l1_output_pitches[d].divmod(offset, offset); + } Point l2_output_offset = zero; for (unsigned l2_outidx = 0; l2_outidx < args.total_l2_outputs; l2_outidx++) { // Do a quick check here to see if all the inputs are contained for this tile @@ -149,24 +155,31 @@ __global__ static void __launch_bounds__(CONVOLUTION_THREADS, 1) Point l1_output_offset; offset = l1_outidx; #pragma unroll - for (int d = 0; d < DIM; d++) + for (int d = 0; d < DIM; d++) { l1_output_offset[d] = args.l1_output_tile_pitches[d].divmod(offset, offset) * args.l1_output_tile[d]; + } // Handle the boundary case where an L1 tile is not contained in the L2 tile // becasue the L2 tile is overlapping a boundary. Note this decisions is the // same for all the threads in the threadblock so no bad divergence bool output_contained = true; #pragma unroll for (int d = 0; d < DIM; d++) { - if ((subrect.lo[d] + l2_output_offset[d] + l1_output_offset[d]) <= subrect.hi[d]) continue; + if ((subrect.lo[d] + l2_output_offset[d] + l1_output_offset[d]) <= subrect.hi[d]) { + continue; + } output_contained = false; break; } - if (!output_contained) continue; + if (!output_contained) { + continue; + } // Initialize our point data VAL acc[POINTS]; #pragma unroll - for (int p = 0; p < POINTS; p++) acc[p] = VAL{0}; + for (int p = 0; p < POINTS; p++) { + acc[p] = VAL{0}; + } // Iterate over the l1 filter tiles Point l1_filter_offset = zero; for (unsigned l1_fidx = 0; l1_fidx < args.total_l1_filters; l1_fidx++) { @@ -179,12 +192,14 @@ __global__ static void __launch_bounds__(CONVOLUTION_THREADS, 1) Point filter_point = l2_filter_rect.lo + l1_filter_offset; offset = fidx; #pragma unroll - for (int d = 0; d < DIM; d++) + for (int d = 0; d < DIM; d++) { filter_point[d] += args.l1_filter_pitches[d].divmod(offset, offset); - if (l2_filter_rect.contains(filter_point)) + } + if (l2_filter_rect.contains(filter_point)) { sharedmem[fidx] = filter[filter_point]; - else + } else { sharedmem[fidx] = VAL{0}; + } } // Load the input into shared memory // Compute the input start point @@ -197,12 +212,14 @@ __global__ static void __launch_bounds__(CONVOLUTION_THREADS, 1) Point input_point = input_start; offset = idx; #pragma unroll - for (int d = 0; d < DIM; d++) + for (int d = 0; d < DIM; d++) { input_point[d] += args.l1_input_pitches[d].divmod(offset, offset); - if (input_contained || root_rect.contains(input_point)) + } + if (input_contained || root_rect.contains(input_point)) { sharedmem[args.shared_input_offset + idx] = in[input_point]; - else + } else { sharedmem[args.shared_input_offset + idx] = VAL{0}; + } } // Wait for everything to be loaded into shared memory __syncthreads(); @@ -215,9 +232,10 @@ __global__ static void __launch_bounds__(CONVOLUTION_THREADS, 1) // Each point is a constant offset in shared from the others unsigned input_offset = args.shared_input_offset; #pragma unroll - for (int d = 0; d < DIM; d++) + for (int d = 0; d < DIM; d++) { input_offset += args.l1_input_pitches[d].divisor * (thread_offset[d] + args.l1_filter_tile[d] - 1); + } if (args.shared_input_bound) { for (unsigned fidx = 0; fidx < args.l1_filter_points; fidx++) { // Use shared memory broadcasting functionality to avoid bank conflicts @@ -225,7 +243,9 @@ __global__ static void __launch_bounds__(CONVOLUTION_THREADS, 1) unsigned point_offset = input_offset; #pragma unroll for (int p = 0; p < POINTS; p++) { - if (args.shared_input_bound <= point_offset) break; + if (args.shared_input_bound <= point_offset) { + break; + } acc[p] = acc[p] + filter_value * sharedmem[point_offset]; point_offset += args.uniform_input_stride; } @@ -274,9 +294,10 @@ __global__ static void __launch_bounds__(CONVOLUTION_THREADS, 1) for (int p = 0; p < POINTS; p++) { point_offsets[p] = args.shared_input_offset; #pragma unroll - for (int d = 0; d < DIM; d++) + for (int d = 0; d < DIM; d++) { point_offsets[p] += (input_point[d] + args.point_offsets[p][d]) * args.l1_input_pitches[d].divisor; + } } unsigned filter_offset = 0; if (args.shared_input_bound) { @@ -286,7 +307,9 @@ __global__ static void __launch_bounds__(CONVOLUTION_THREADS, 1) #pragma unroll for (int p = 0; p < POINTS; p++) { unsigned point_offset = point_offsets[p] - filter_offset; - if (args.shared_input_bound <= point_offset) continue; + if (args.shared_input_bound <= point_offset) { + continue; + } acc[p] = acc[p] + filter_value * sharedmem[point_offset]; } // Step to the next filter point @@ -330,10 +353,11 @@ __global__ static void __launch_bounds__(CONVOLUTION_THREADS, 1) #pragma unroll for (int d = DIM - 1; d >= 0; d--) { l1_filter_offset[d] += args.l1_filter_tile[d]; - if (args.l2_filter_tile[d] <= l1_filter_offset[d]) + if (args.l2_filter_tile[d] <= l1_filter_offset[d]) { l1_filter_offset[d] = 0; - else + } else { break; + } } } // Now we can stream our accumulators back to the output @@ -344,7 +368,9 @@ __global__ static void __launch_bounds__(CONVOLUTION_THREADS, 1) unsigned index = threadIdx.x; #pragma unroll for (int p = 0; p < POINTS; p++) { - if (args.total_l1_points <= index) break; + if (args.total_l1_points <= index) { + break; + } VAL* ptr = out.ptr(output + args.point_offsets[p]); // Make sure we don't pollute the L2 cache VAL value = load_streaming(ptr); @@ -366,9 +392,13 @@ __global__ static void __launch_bounds__(CONVOLUTION_THREADS, 1) unsigned index = threadIdx.x; #pragma unroll for (int p = 0; p < POINTS; p++) { - if (args.total_l1_points <= index) break; + if (args.total_l1_points <= index) { + break; + } Point point = output + args.point_offsets[p]; - if (!subrect.contains(point)) break; + if (!subrect.contains(point)) { + break; + } VAL* ptr = out.ptr(point); // Make sure we don't pollute the L2 cache VAL value = load_streaming(ptr); @@ -379,7 +409,9 @@ __global__ static void __launch_bounds__(CONVOLUTION_THREADS, 1) #pragma unroll for (int p = 0; p < POINTS; p++) { Point point = output + args.point_offsets[p]; - if (!subrect.contains(point)) continue; + if (!subrect.contains(point)) { + continue; + } VAL* ptr = out.ptr(point); // Make sure we don't pollute the L2 cache VAL value = load_streaming(ptr); @@ -392,10 +424,11 @@ __global__ static void __launch_bounds__(CONVOLUTION_THREADS, 1) #pragma unroll for (int d = DIM - 1; d >= 0; d--) { l2_output_offset[d] += args.l2_output_tile[d]; - if (args.l2_output_limits[d] <= l2_output_offset[d]) + if (args.l2_output_limits[d] <= l2_output_offset[d]) { l2_output_offset[d] = 0; - else + } else { break; + } } } } @@ -432,8 +465,9 @@ __global__ static void __launch_bounds__(512, 2) size_t offset = blockIdx.x; Point block_point = subrect.lo; #pragma unroll - for (int d = 0; d < DIM; d++) + for (int d = 0; d < DIM; d++) { block_point[d] += args.grid_pitches[d].divmod(offset, offset) * args.block_tiles[d]; + } // Load in the shared memory for this block Point tile_point; const Rect input_bounds(block_point - args.delta_lo, block_point + args.delta_hi); @@ -445,7 +479,9 @@ __global__ static void __launch_bounds__(512, 2) for (unsigned idx = threadIdx.x; idx < args.input_volume; idx += blockDim.x) { offset = idx; #pragma unroll - for (int d = 0; d < DIM; d++) tile_point[d] = args.input_pitches[d].divmod(offset, offset); + for (int d = 0; d < DIM; d++) { + tile_point[d] = args.input_pitches[d].divmod(offset, offset); + } VAL value = in[input_bounds.lo + tile_point]; // Write the value into shared memory input[idx] = value; @@ -457,8 +493,12 @@ __global__ static void __launch_bounds__(512, 2) for (unsigned idx = threadIdx.x; idx < args.input_volume; idx += blockDim.x) { offset = idx; #pragma unroll - for (int d = 0; d < DIM; d++) tile_point[d] = args.input_pitches[d].divmod(offset, offset); - if (!root_rect.contains(input_bounds.lo + tile_point)) continue; + for (int d = 0; d < DIM; d++) { + tile_point[d] = args.input_pitches[d].divmod(offset, offset); + } + if (!root_rect.contains(input_bounds.lo + tile_point)) { + continue; + } VAL value = in[input_bounds.lo + tile_point]; // Write the value into shared memory input[idx] = value; @@ -477,31 +517,40 @@ __global__ static void __launch_bounds__(512, 2) tile_point[d] = args.block_pitches[d].divmod(offset, offset); out_point[d] = block_point[d] + tile_point[d]; } - if (!subrect.contains(out_point)) continue; + if (!subrect.contains(out_point)) { + continue; + } #pragma unroll - for (int d = 0; d < DIM; d++) f_coords[d] = 0; + for (int d = 0; d < DIM; d++) { + f_coords[d] = 0; + } VAL acc{0}; for (unsigned idx = 0; idx < args.filter_volume; idx++) { #pragma unroll - for (int d = 0; d < DIM; d++) + for (int d = 0; d < DIM; d++) { in_point[d] = out_point[d] + f_coords[d] - args.filter_centers[d]; + } if (input_contained || root_rect.contains(in_point)) { offset = 0; #pragma unroll - for (int d = 0; d < DIM; d++) + for (int d = 0; d < DIM; d++) { offset += (tile_point[d] + f_coords[d]) * args.input_pitches[d].divisor; + } #pragma unroll - for (int d = 0; d < DIM; d++) filter_point[d] = args.filter_extents[d] - f_coords[d] - 1; + for (int d = 0; d < DIM; d++) { + filter_point[d] = args.filter_extents[d] - f_coords[d] - 1; + } acc = acc + input[offset] * filter[filter_point]; } // Step the filter coordinates #pragma unroll for (int d = DIM - 1; d >= 0; d--) { f_coords[d]++; - if (f_coords[d] == args.filter_extents[d]) + if (f_coords[d] == args.filter_extents[d]) { f_coords[d] = 0; - else + } else { break; + } } } store_streaming(out.ptr(out_point), acc); @@ -528,8 +577,9 @@ __global__ static void __launch_bounds__(1024, 1) size_t offset = blockIdx.x; Point block_point = subrect.lo; #pragma unroll - for (int d = 0; d < DIM; d++) + for (int d = 0; d < DIM; d++) { block_point[d] += args.grid_pitches[d].divmod(offset, offset) * args.block_tiles[d]; + } // Load in the shared memory for this block Point tile_point; const Rect input_bounds(block_point - args.delta_lo, block_point + args.delta_hi); @@ -541,7 +591,9 @@ __global__ static void __launch_bounds__(1024, 1) for (unsigned idx = threadIdx.x; idx < args.input_volume; idx += blockDim.x) { offset = idx; #pragma unroll - for (int d = 0; d < DIM; d++) tile_point[d] = args.input_pitches[d].divmod(offset, offset); + for (int d = 0; d < DIM; d++) { + tile_point[d] = args.input_pitches[d].divmod(offset, offset); + } VAL value = in[input_bounds.lo + tile_point]; // Write the value into shared memory input[idx] = value; @@ -553,8 +605,12 @@ __global__ static void __launch_bounds__(1024, 1) for (unsigned idx = threadIdx.x; idx < args.input_volume; idx += blockDim.x) { offset = idx; #pragma unroll - for (int d = 0; d < DIM; d++) tile_point[d] = args.input_pitches[d].divmod(offset, offset); - if (!root_rect.contains(input_bounds.lo + tile_point)) continue; + for (int d = 0; d < DIM; d++) { + tile_point[d] = args.input_pitches[d].divmod(offset, offset); + } + if (!root_rect.contains(input_bounds.lo + tile_point)) { + continue; + } VAL value = in[input_bounds.lo + tile_point]; // Write the value into shared memory input[idx] = value; @@ -573,31 +629,40 @@ __global__ static void __launch_bounds__(1024, 1) tile_point[d] = args.block_pitches[d].divmod(offset, offset); out_point[d] = block_point[d] + tile_point[d]; } - if (!subrect.contains(out_point)) continue; + if (!subrect.contains(out_point)) { + continue; + } #pragma unroll - for (int d = 0; d < DIM; d++) f_coords[d] = 0; + for (int d = 0; d < DIM; d++) { + f_coords[d] = 0; + } VAL acc{0}; for (unsigned idx = 0; idx < args.filter_volume; idx++) { #pragma unroll - for (int d = 0; d < DIM; d++) + for (int d = 0; d < DIM; d++) { in_point[d] = out_point[d] + f_coords[d] - args.filter_centers[d]; + } if (input_contained || root_rect.contains(in_point)) { offset = 0; #pragma unroll - for (int d = 0; d < DIM; d++) + for (int d = 0; d < DIM; d++) { offset += (tile_point[d] + f_coords[d]) * args.input_pitches[d].divisor; + } #pragma unroll - for (int d = 0; d < DIM; d++) filter_point[d] = args.filter_extents[d] - f_coords[d] - 1; + for (int d = 0; d < DIM; d++) { + filter_point[d] = args.filter_extents[d] - f_coords[d] - 1; + } acc = acc + input[offset] * filter[filter_point]; } // Step the filter coordinates #pragma unroll for (int d = DIM - 1; d >= 0; d--) { f_coords[d]++; - if (f_coords[d] == args.filter_extents[d]) + if (f_coords[d] == args.filter_extents[d]) { f_coords[d] = 0; - else + } else { break; + } } } store_streaming(out.ptr(out_point), acc); @@ -630,7 +695,9 @@ __host__ static inline void launch_small_tile_kernel(AccessorWO out, halved = true; } Point padding; - for (int d = 0; d < DIM; d++) padding[d] = 2 * centers[d]; + for (int d = 0; d < DIM; d++) { + padding[d] = 2 * centers[d]; + } Point bounds = subrect.hi - subrect.lo + Point::ONES(); smem_size = roundup_tile(tile, bounds, padding, max_smem_size); // At this point we've got the tile size that we're going to compute @@ -661,19 +728,21 @@ __host__ static inline void launch_small_tile_kernel(AccessorWO out, assert((input_pitch * sizeof(VAL)) == smem_size); auto stream = get_cached_stream(); if (halved) { - if (tile_pitch < 512) + if (tile_pitch < 512) { convolution_small_tile1<<>>( out, filter, in, root_rect, subrect, filter_rect, args); - else + } else { convolution_small_tile1<<>>( out, filter, in, root_rect, subrect, filter_rect, args); + } } else { - if (tile_pitch < 1024) + if (tile_pitch < 1024) { convolution_small_tile2<<>>( out, filter, in, root_rect, subrect, filter_rect, args); - else + } else { convolution_small_tile2<<>>( out, filter, in, root_rect, subrect, filter_rect, args); + } } CHECK_CUDA_STREAM(stream); } @@ -739,12 +808,15 @@ __host__ void direct_convolution(AccessorWO out, // In order to maximize bandwidth, we want to make sure we're loading at // least 128B of contiguous memory along the last axis (row-major) of input const unsigned min_contig_elmts = 128 / sizeof(VAL); - if ((tile[d] + 2 * centers[d]) < min_contig_elmts) + if ((tile[d] + 2 * centers[d]) < min_contig_elmts) { tile[d] = min_contig_elmts - 2 * centers[d]; + } } } unsigned smem_size = sizeof(VAL); - for (int d = 0; d < DIM; d++) smem_size *= (tile[d] + 2 * centers[d]); + for (int d = 0; d < DIM; d++) { + smem_size *= (tile[d] + 2 * centers[d]); + } if (smem_size <= max_smem_size) { // Small tile case: launch_small_tile_kernel(out, @@ -795,8 +867,9 @@ __host__ void direct_convolution(AccessorWO out, const unsigned max_l1_output_volume = CONVOLUTION_THREADS * THREADVALS; // Make sure the max_l1_output_volume doesn't consume more than half of shared memory unsigned target_l1_output_volume = max_l1_output_volume; - while ((max_smem_size / 2) < (target_l1_output_volume * sizeof(VAL))) + while ((max_smem_size / 2) < (target_l1_output_volume * sizeof(VAL))) { target_l1_output_volume /= 2; + } const Point output_bounds = subrect.hi - subrect.lo + Point::ONES(); const unsigned l1_output_volume = compute_output_tile(l1_output_tile, @@ -811,7 +884,9 @@ __host__ void direct_convolution(AccessorWO out, unsigned dynamic_smem = compute_filter_tile(l1_filter_tile, filter_bounds, l1_output_tile, max_smem_size); unsigned input_smem_offset = 1; - for (int d = 0; d < DIM; d++) input_smem_offset *= l1_filter_tile[d]; + for (int d = 0; d < DIM; d++) { + input_smem_offset *= l1_filter_tile[d]; + } // Tile the number of SMs on this GPU to compute the shape of the // L2 output tile for this kernel // We assume here that the number of SMs is easily factorable @@ -819,7 +894,9 @@ __host__ void direct_convolution(AccessorWO out, // GPU with a number of SMs these days that can't be factored // this way. If we do report a warning. unsigned l2_tiles[DIM]; - for (int d = 0; d < DIM; d++) l2_tiles[d] = 1; + for (int d = 0; d < DIM; d++) { + l2_tiles[d] = 1; + } if (DIM > 1) { unsigned twos = 0, threes = 0, fives = 0; unsigned remainder = properties.multiProcessorCount; @@ -846,7 +923,9 @@ __host__ void direct_convolution(AccessorWO out, for (unsigned idx = 0; idx < fives; idx++) { int smallest = 0; for (int d = 1; d < DIM; d++) { - if (l2_tiles[smallest] < l2_tiles[d]) continue; + if (l2_tiles[smallest] < l2_tiles[d]) { + continue; + } smallest = d; } l2_tiles[smallest] *= 5; @@ -854,7 +933,9 @@ __host__ void direct_convolution(AccessorWO out, for (unsigned idx = 0; idx < threes; idx++) { int smallest = 0; for (int d = 1; d < DIM; d++) { - if (l2_tiles[smallest] < l2_tiles[d]) continue; + if (l2_tiles[smallest] < l2_tiles[d]) { + continue; + } smallest = d; } l2_tiles[smallest] *= 3; @@ -862,7 +943,9 @@ __host__ void direct_convolution(AccessorWO out, for (unsigned idx = 0; idx < twos; idx++) { int smallest = 0; for (int d = 1; d < DIM; d++) { - if (l2_tiles[smallest] < l2_tiles[d]) continue; + if (l2_tiles[smallest] < l2_tiles[d]) { + continue; + } smallest = d; } l2_tiles[smallest] *= 2; @@ -882,19 +965,24 @@ __host__ void direct_convolution(AccessorWO out, Point l2_filter_tile; size_t total_l2_filters = 1; if (l2_output_tile_size <= (static_cast(properties.l2CacheSize) / 4)) { - for (int d = 0; d < DIM; d++) l2_filter_tile[d] = 1; + for (int d = 0; d < DIM; d++) { + l2_filter_tile[d] = 1; + } // Compute the L2 filter tile size so that the L2 filter and the // corresponding L2 input tile will fit in the L2 cache compute_filter_tile( l2_filter_tile, filter_bounds, l2_output_tile, 3 * properties.l2CacheSize / 4); - for (int d = 0; d < DIM; d++) + for (int d = 0; d < DIM; d++) { total_l2_filters *= (filter_bounds[d] + l2_filter_tile[d] - 1) / l2_filter_tile[d]; + } } else { // It's likely this tile is too big to block for the L2 cache // so we're not going to bother blocking for the L2 and just // run everything out of the framebuffer memory. The upside is // that we'll only need to make a single pass over the input - for (int d = 0; d < DIM; d++) l2_filter_tile[d] = filter_rect.hi[d] - filter_rect.lo[d] + 1; + for (int d = 0; d < DIM; d++) { + l2_filter_tile[d] = filter_rect.hi[d] - filter_rect.lo[d] + 1; + } } // Construct the arguments for the kernel launches ConvolutionLargeTileArgs args; @@ -937,14 +1025,18 @@ __host__ void direct_convolution(AccessorWO out, // Figure out how to tile the points across the l1_output_tile if (DIM > 1) { unsigned regsteps[DIM]; - for (int d = 0; d < DIM; d++) regsteps[d] = 0; + for (int d = 0; d < DIM; d++) { + regsteps[d] = 0; + } unsigned remainder = THREADVALS; // Handle the case here where we aren't going to use all // the points in the registers so we need to scale back if (l1_output_volume < max_l1_output_volume) { assert((max_l1_output_volume % l1_output_volume) == 0); remainder /= (max_l1_output_volume / l1_output_volume); - if (remainder == 0) remainder = 1; + if (remainder == 0) { + remainder = 1; + } } for (int d = 0; d < DIM; d++) { if (remainder == 1) { @@ -969,20 +1061,24 @@ __host__ void direct_convolution(AccessorWO out, for (int d = DIM - 1; d >= 0; d--) { offset[d] += regsteps[d]; if (offset[d] == l1_output_tile[d]) { - if ((d == 0) && (p != (THREADVALS - 1))) + if ((d == 0) && (p != (THREADVALS - 1))) { // Allow overflow in this case to handle the case // where we have more points than we need for the l1 output tile assert(l1_output_volume < max_l1_output_volume); - else + } else { offset[d] = 0; - } else + } + } else { break; + } } } args.uniform_input_stride = regsteps[0] * args.l1_input_pitches[0].divisor; // Check to make sure this is the uniform input stride case for (int d = 1; d < DIM; d++) { - if (regsteps[d] == l1_output_tile[d]) continue; + if (regsteps[d] == l1_output_tile[d]) { + continue; + } args.uniform_input_stride = 0; break; } @@ -994,11 +1090,15 @@ __host__ void direct_convolution(AccessorWO out, if (l1_output_volume < max_l1_output_volume) { assert((max_l1_output_volume % l1_output_volume) == 0); remainder /= (max_l1_output_volume / l1_output_volume); - if (remainder == 0) remainder = 1; + if (remainder == 0) { + remainder = 1; + } } assert((l1_output_tile[0] % remainder) == 0); unsigned regstep = l1_output_tile[0] / remainder; - for (int p = 0; p < THREADVALS; p++) args.point_offsets[p][0] = p * regstep; + for (int p = 0; p < THREADVALS; p++) { + args.point_offsets[p][0] = p * regstep; + } args.uniform_input_stride = regstep * args.l1_input_pitches[0].divisor; } if (l1_output_volume < max_l1_output_volume) { @@ -1041,10 +1141,11 @@ __host__ void direct_convolution(AccessorWO out, // Step to the next filter for (int d = DIM - 1; d >= 0; d--) { l2_filter_lo[d] += l2_filter_tile[d]; - if (filter_rect.hi[d] < l2_filter_lo[d]) + if (filter_rect.hi[d] < l2_filter_lo[d]) { l2_filter_lo[d] = filter_rect.lo[d]; - else + } else { break; + } } } } else { @@ -1103,9 +1204,13 @@ __global__ static void __launch_bounds__(THREADS_PER_BLOCK, 4) const size_t volume) { size_t offset = blockIdx.x * blockDim.x + threadIdx.x; - if (offset >= volume) return; + if (offset >= volume) { + return; + } Point point; - for (int d = 0; d < DIM; d++) point[d] = copy_pitches[d].divmod(offset, offset); + for (int d = 0; d < DIM; d++) { + point[d] = copy_pitches[d].divmod(offset, offset); + } buffer[point] = accessor[lo + point]; } @@ -1121,7 +1226,9 @@ __global__ static void __launch_bounds__(THREADS_PER_BLOCK, 4) const VAL scaling) { size_t offset = blockIdx.x * blockDim.x + threadIdx.x; - if (offset >= volume) return; + if (offset >= volume) { + return; + } Point point; size_t buffer_offset = 0; for (int d = 0; d < DIM; d++) { @@ -1136,7 +1243,9 @@ __global__ static void __launch_bounds__(THREADS_PER_BLOCK, 4) complex_multiply(complex* inout, complex* in, const size_t volume) { size_t offset = blockIdx.x * blockDim.x + threadIdx.x; - if (offset >= volume) return; + if (offset >= volume) { + return; + } inout[offset] *= in[offset]; } @@ -1240,12 +1349,15 @@ __host__ static inline void cufft_convolution(AccessorWO out, // In order to maximize bandwidth, we want to make sure we're loading at // least 128B of contiguous memory along the last axis (row-major) of input const unsigned min_contig_elmts = 128 / sizeof(VAL); - if ((tile[d] + 2 * centers[d]) < min_contig_elmts) + if ((tile[d] + 2 * centers[d]) < min_contig_elmts) { tile[d] = min_contig_elmts - 2 * centers[d]; + } } } unsigned smem_size = sizeof(VAL); - for (int d = 0; d < DIM; d++) smem_size *= (tile[d] + 2 * centers[d]); + for (int d = 0; d < DIM; d++) { + smem_size *= (tile[d] + 2 * centers[d]); + } if (smem_size <= max_smem_size) { launch_small_tile_kernel(out, filter, @@ -1281,7 +1393,9 @@ __host__ static inline void cufft_convolution(AccessorWO out, for (int d = 0; d < DIM; d++) { // Technically we can shrink this by one and still be sound but we'll // only do that if it will make the number even - if ((fftsize[d] % 2) == 1) fftsize[d]--; + if ((fftsize[d] % 2) == 1) { + fftsize[d]--; + } } // Cufft needs the last dimension to have fftsize/2+1 complex elements for // the temporary buffer @@ -1289,7 +1403,9 @@ __host__ static inline void cufft_convolution(AccessorWO out, Point buffersize = fftsize; buffersize[DIM - 1] += 2; size_t buffervolume = 1; - for (int d = 0; d < DIM; d++) buffervolume *= buffersize[d]; + for (int d = 0; d < DIM; d++) { + buffervolume *= buffersize[d]; + } // Zero pad and copy in the input data auto signal_buffer = create_buffer(buffersize, Memory::GPU_FB_MEM, 128 /*alignment*/); VAL* signal_ptr = signal_buffer.ptr(zero); @@ -1365,10 +1481,11 @@ __host__ static inline void cufft_convolution(AccessorWO out, } const VAL scaling_factor = VAL(1) / pitch; Point buffer_offset; - for (int d = 0; d < DIM; d++) + for (int d = 0; d < DIM; d++) { buffer_offset[d] = centers[d] - (((extents[d] % 2) == 0) ? 1 : 0) + ((offset_bounds.lo[d] < root_rect.lo[d]) ? (subrect.lo[d] - root_rect.lo[d]) : centers[d]); + } Point output_bounds = subrect.hi - subrect.lo + one; pitch = 1; for (int d = DIM - 1; d >= 0; d--) { diff --git a/src/cunumeric/convolution/convolve_omp.cc b/src/cunumeric/convolution/convolve_omp.cc index 2dc8cf7b4a..84bad8b8df 100644 --- a/src/cunumeric/convolution/convolve_omp.cc +++ b/src/cunumeric/convolution/convolve_omp.cc @@ -38,7 +38,9 @@ struct ConvolveImplBody { const Point one = Point::ONES(); Point extents = filter_rect.hi - filter_rect.lo + one; Point centers; - for (int d = 0; d < DIM; d++) centers[d] = extents[d] / 2; + for (int d = 0; d < DIM; d++) { + centers[d] = extents[d] / 2; + } // Compute the tiles for the L2 cache Point l2_output_tile, l2_filter_tile; @@ -53,8 +55,9 @@ struct ConvolveImplBody { compute_filter_tile( l2_filter_tile, filter_bounds, l2_output_tile, 3 * L2_CACHE_SIZE / 4); unsigned total_l2_filters = 1; - for (int d = 0; d < DIM; d++) + for (int d = 0; d < DIM; d++) { total_l2_filters *= ((extents[d] + l2_filter_tile[d] - 1) / l2_filter_tile[d]); + } unsigned total_l2_outputs = 1; FastDivmodU64 l2_tile_pitches[DIM]; for (int d = DIM - 1; d >= 0; d--) { @@ -73,11 +76,13 @@ struct ConvolveImplBody { compute_filter_tile( l1_filter_tile, filter_bounds, l1_output_tile, 3 * L1_CACHE_SIZE / 4); unsigned total_l1_filters = 1; - for (int d = 0; d < DIM; d++) + for (int d = 0; d < DIM; d++) { total_l1_filters *= ((l2_filter_tile[d] + l1_filter_tile[d] - 1) / l1_filter_tile[d]); + } unsigned total_l1_outputs = 1; - for (int d = 0; d < DIM; d++) + for (int d = 0; d < DIM; d++) { total_l1_outputs *= ((l2_output_tile[d] + l1_output_tile[d] - 1) / l1_output_tile[d]); + } // Zero out the output data since we're going to be doing sum accumulations FastDivmodU64 output_pitches[DIM]; @@ -92,17 +97,22 @@ struct ConvolveImplBody { for (int idx = 0; idx < threads; idx++) { Point output = subrect.lo; uint64_t offset = idx * blocks; - for (int d = 0; d < DIM; d++) output[d] += output_pitches[d].divmod(offset, offset); + for (int d = 0; d < DIM; d++) { + output[d] += output_pitches[d].divmod(offset, offset); + } for (size_t p = 0; p < blocks; p++) { out[output] = VAL{0}; for (int d = DIM - 1; d >= 0; d--) { output[d]++; - if (subrect.hi[d] < output[d]) + if (subrect.hi[d] < output[d]) { output[d] = subrect.lo[d]; - else + } else { break; + } + } + if (output == subrect.lo) { + break; } - if (output == subrect.lo) break; } } @@ -115,24 +125,27 @@ struct ConvolveImplBody { if (!filter_rect.contains(l2_filter_rect)) { l2_filter_rect = filter_rect.intersection(l2_filter_rect); local_l1_filters = 1; - for (int d = 0; d < DIM; d++) + for (int d = 0; d < DIM; d++) { local_l1_filters *= ((l2_filter_rect.hi[d] - l2_filter_rect.lo[d] + l1_filter_tile[d]) / l1_filter_tile[d]); + } } // Now iterate the tiles for the L2 outputs #pragma omp parallel for // schedule(dynamic) // Turn this on when Realm is fixed for (unsigned l2_outidx = 0; l2_outidx < total_l2_outputs; l2_outidx++) { Point l2_output = subrect.lo; uint64_t offset = l2_outidx; - for (int d = 0; d < DIM; d++) + for (int d = 0; d < DIM; d++) { l2_output[d] += l2_tile_pitches[d].divmod(offset, offset) * l2_output_tile[d]; + } Rect l2_output_rect(l2_output, l2_output + l2_output_tile - one); unsigned local_l1_outputs = total_l1_outputs; if (!subrect.contains(l2_output_rect)) { l2_output_rect = subrect.intersection(l2_output_rect); - for (int d = 0; d < DIM; d++) + for (int d = 0; d < DIM; d++) { local_l1_outputs *= ((l2_output_rect.hi[d] - l2_output_rect.lo[d] + l1_output_tile[d]) / l1_output_tile[d]); + } } // Do a quick check here to see if all the inputs are contained for // this particular tile @@ -159,43 +172,48 @@ struct ConvolveImplBody { Point filter_point = l1_filter_rect.lo; for (unsigned fidx = 0; fidx < l1_filter_points; fidx++) { Point input = output + extents - filter_point - one - centers; - if (input_contained || root_rect.contains(input)) + if (input_contained || root_rect.contains(input)) { acc += in[input] * filter[filter_point]; + } // Step to the next filter point for (int d = DIM - 1; d >= 0; d--) { filter_point[d]++; - if (l1_filter_rect.hi[d] < filter_point[d]) + if (l1_filter_rect.hi[d] < filter_point[d]) { filter_point[d] = l1_filter_rect.lo[d]; - else + } else { break; + } } } out[output] += acc; // Step to the next output point for (int d = DIM - 1; d >= 0; d--) { output[d]++; - if (l1_output_rect.hi[d] < output[d]) + if (l1_output_rect.hi[d] < output[d]) { output[d] = l1_output_rect.lo[d]; - else + } else { break; + } } } // Step to the next L1 filter for (int d = DIM - 1; d >= 0; d--) { l1_filter[d] += l1_filter_tile[d]; - if (l2_filter_rect.hi[d] < l1_filter[d]) + if (l2_filter_rect.hi[d] < l1_filter[d]) { l1_filter[d] = l2_filter_rect.lo[d]; - else + } else { break; + } } } // Step to the next L1 output tile for (int d = DIM - 1; d >= 0; d--) { l1_output[d] += l1_output_tile[d]; - if (l2_output_rect.hi[d] < l1_output[d]) + if (l2_output_rect.hi[d] < l1_output[d]) { l1_output[d] = l2_output_rect.lo[d]; - else + } else { break; + } } } // No need to step to the next output tile, we're @@ -204,10 +222,11 @@ struct ConvolveImplBody { // Step to the next l2 filter for (int d = DIM - 1; d >= 0; d--) { l2_filter[d] += l2_filter_tile[d]; - if (filter_rect.hi[d] < l2_filter[d]) + if (filter_rect.hi[d] < l2_filter[d]) { l2_filter[d] = filter_rect.lo[d]; - else + } else { break; + } } } } diff --git a/src/cunumeric/convolution/convolve_template.inl b/src/cunumeric/convolution/convolve_template.inl index 9293477e8a..f2874bb97f 100644 --- a/src/cunumeric/convolution/convolve_template.inl +++ b/src/cunumeric/convolution/convolve_template.inl @@ -38,7 +38,9 @@ struct ConvolveImpl { auto subrect = args.out.shape(); auto filter_rect = args.filter.shape(); - if (subrect.empty()) return; + if (subrect.empty()) { + return; + } auto input_subrect = subrect; for (uint32_t idx = 1; idx < args.inputs.size(); ++idx) { @@ -72,7 +74,9 @@ static void convolve_template(TaskContext& context) args.out = std::move(outputs[0]); args.filter = std::move(inputs[0]); - for (uint32_t idx = 1; idx < inputs.size(); ++idx) args.inputs.push_back(std::move(inputs[idx])); + for (uint32_t idx = 1; idx < inputs.size(); ++idx) { + args.inputs.push_back(std::move(inputs[idx])); + } auto shape = context.scalar(0).value(); args.root_domain.dim = shape.dim; @@ -90,7 +94,9 @@ static unsigned compute_output_tile(Point& tile, const size_t min_last_elements, const size_t target_volume) { - for (int d = 0; d < DIM; d++) tile[d] = 1; + for (int d = 0; d < DIM; d++) { + tile[d] = 1; + } // Better be a powers of 2 assert((min_last_elements & (min_last_elements - 1)) == 0); assert((target_volume & (target_volume - 1)) == 0); @@ -104,7 +110,9 @@ static unsigned compute_output_tile(Point& tile, } else { volume *= 2; } - if (volume == target_volume) break; + if (volume == target_volume) { + break; + } } int last_dim = DIM - 1; // Round-robin powers of 2 onto the other dimensions until @@ -113,30 +121,37 @@ static unsigned compute_output_tile(Point& tile, for (uint32_t idx = 1; idx < min_last_elements; idx *= 2) { for (int d = DIM - 2; d >= 0; d--) { tile[d] *= 2; - if (bounds[d] < tile[d]) + if (bounds[d] < tile[d]) { tile[d] /= 2; - else { + } else { volume *= 2; last_dim = d; - if (volume == target_volume) break; + if (volume == target_volume) { + break; + } } } - if (volume == target_volume) break; + if (volume == target_volume) { + break; + } } } // If we still have more to go round-robin powers of 2 over // all the dimensions int unchanged = 0; while (volume < target_volume) { - if (last_dim == 0) + if (last_dim == 0) { last_dim = DIM - 1; - else + } else { last_dim--; + } tile[last_dim] *= 2; if (bounds[last_dim] < tile[last_dim]) { tile[last_dim] /= 2; unchanged++; - if (unchanged == DIM) break; + if (unchanged == DIM) { + break; + } } else { volume *= 2; unchanged = 0; @@ -151,7 +166,9 @@ static unsigned compute_filter_tile(Point& tile, const Point& output, const size_t max_size) { - for (int d = 0; d < DIM; d++) tile[d] = 1; + for (int d = 0; d < DIM; d++) { + tile[d] = 1; + } // Try doubling dimensions until we can't make it any larger unsigned result = 0; bool done = false; @@ -159,18 +176,25 @@ static unsigned compute_filter_tile(Point& tile, done = true; for (int d = DIM - 1; d >= 0; d--) { // Skip if it will exceed our bounds - if (bounds[d] < (2 * tile[d])) continue; + if (bounds[d] < (2 * tile[d])) { + continue; + } unsigned filter_size = sizeof(VAL); tile[d] *= 2; - for (int d2 = 0; d2 < DIM; d2++) filter_size *= tile[d2]; + for (int d2 = 0; d2 < DIM; d2++) { + filter_size *= tile[d2]; + } unsigned input_size = sizeof(VAL); - for (int d2 = 0; d2 < DIM; d2++) input_size *= (output[d2] + 2 * (tile[d2] / 2)); + for (int d2 = 0; d2 < DIM; d2++) { + input_size *= (output[d2] + 2 * (tile[d2] / 2)); + } unsigned total_size = filter_size + input_size; if (total_size <= max_size) { result = total_size; done = false; - } else + } else { tile[d] /= 2; + } } } // Then try incrementally increasing dimensions until we can't @@ -180,18 +204,25 @@ static unsigned compute_filter_tile(Point& tile, done = true; for (int d = DIM - 1; d >= 0; d--) { // Skip if it will exceed our bounds - if (bounds[d] == tile[d]) continue; + if (bounds[d] == tile[d]) { + continue; + } unsigned filter_size = sizeof(VAL); tile[d]++; - for (int d2 = 0; d2 < DIM; d2++) filter_size *= tile[d2]; + for (int d2 = 0; d2 < DIM; d2++) { + filter_size *= tile[d2]; + } unsigned input_size = sizeof(VAL); - for (int d2 = 0; d2 < DIM; d2++) input_size *= (output[d2] + 2 * (tile[d2] / 2)); + for (int d2 = 0; d2 < DIM; d2++) { + input_size *= (output[d2] + 2 * (tile[d2] / 2)); + } unsigned total_size = filter_size + input_size; if (total_size <= max_size) { result = total_size; done = false; - } else + } else { tile[d]--; + } } } return result; @@ -209,7 +240,9 @@ static unsigned roundup_tile(Point& tile, assert(elements > padding[0]); if (tile[0] < (elements - padding[0])) { tile[0] = elements - padding[0]; - if (bounds[0] < tile[0]) tile[0] = bounds[0]; + if (bounds[0] < tile[0]) { + tile[0] = bounds[0]; + } } return (tile[0] + padding[0]) * sizeof(VAL); } else { @@ -217,7 +250,9 @@ static unsigned roundup_tile(Point& tile, // Shrink the tile to the bounds if necessary unsigned result = sizeof(VAL); for (int d = 0; d < DIM; d++) { - if (bounds[d] < tile[d]) tile[d] = bounds[d]; + if (bounds[d] < tile[d]) { + tile[d] = bounds[d]; + } result *= (tile[d] + padding[d]); } // Find the two smallest dimensions and increase one of them @@ -229,11 +264,15 @@ static unsigned roundup_tile(Point& tile, int t1 = tile[d1], t2 = 0; while (t1 == bounds[d1]) { skipdims |= (1 << d1); - if (--d1 < 0) return result; // all dims at their bounds so we're done + if (--d1 < 0) { + return result; // all dims at their bounds so we're done + } t1 = tile[d1]; } for (int d = d1 - 1; d >= 0; d--) { - if (skipdims & (1 << d)) continue; + if (skipdims & (1 << d)) { + continue; + } // Skip any dimension that is at its bound if (tile[d] == bounds[d]) { skipdims |= (1 << d); @@ -253,15 +292,20 @@ static unsigned roundup_tile(Point& tile, // All the other dimensions are at their bounds, check that // the last dimension is also at its bound if not solve unsigned pitch = sizeof(VAL); - for (int d = 0; d < DIM; d++) - if (d != d1) pitch *= (tile[d] + padding[d]); + for (int d = 0; d < DIM; d++) { + if (d != d1) { + pitch *= (tile[d] + padding[d]); + } + } // Make sure the last dimension is as large as it can go too if (tile[d1] < bounds[d1]) { unsigned elements = max_size / pitch; assert(elements > padding[d1]); assert(tile[d1] < (elements - padding[d1])); tile[d1] = elements - padding[d1]; - if (bounds[d1] < tile[d1]) tile[d1] = bounds[d1]; + if (bounds[d1] < tile[d1]) { + tile[d1] = bounds[d1]; + } } return pitch * (tile[d1] + padding[d1]); } @@ -272,19 +316,28 @@ static unsigned roundup_tile(Point& tile, if (t1 == t2) { d2 = -1; for (int d = 0; d < DIM; d++) { - if (d == d1) continue; - if (tile[d] <= tile[d1]) continue; + if (d == d1) { + continue; + } + if (tile[d] <= tile[d1]) { + continue; + } if ((d2 == -1) || (tile[d] < tile[d2])) { d2 = d; t2 = tile[d]; } } - if (d2 == -1) break; + if (d2 == -1) { + break; + } } // Solve for the max we can walk unsigned pitch = sizeof(VAL); - for (int d = 0; d < DIM; d++) - if (d != d1) pitch *= (tile[d] + padding[d]); + for (int d = 0; d < DIM; d++) { + if (d != d1) { + pitch *= (tile[d] + padding[d]); + } + } unsigned elements = max_size / pitch; if ((elements <= padding[d1]) || (t1 >= (elements - padding[d1]))) { skipdims |= (1 << d1); @@ -313,18 +366,24 @@ static unsigned roundup_tile(Point& tile, // of dimensions so it should converge quickly while (true) { unsigned next_size = sizeof(VAL); - for (int d = 0; d < DIM; d++) - if (skipdims & (1 << d)) + for (int d = 0; d < DIM; d++) { + if (skipdims & (1 << d)) { next_size *= (tile[d] + padding[d]); - else if (tile[d] == bounds[d]) { + } else if (tile[d] == bounds[d]) { next_size *= (tile[d] + padding[d]); skipdims |= (1 << d); - } else + } else { next_size *= (tile[d] + 1 + padding[d]); - if ((next_size > max_size) || (next_size == result)) break; + } + } + if ((next_size > max_size) || (next_size == result)) { + break; + } result = next_size; for (int d = 0; d < DIM; d++) { - if (skipdims & (1 << d)) continue; + if (skipdims & (1 << d)) { + continue; + } tile[d]++; } } diff --git a/src/cunumeric/cuda_help.h b/src/cunumeric/cuda_help.h index 0915019475..a711ded9ed 100644 --- a/src/cunumeric/cuda_help.h +++ b/src/cunumeric/cuda_help.h @@ -262,19 +262,23 @@ __device__ __forceinline__ void reduce_output(DeviceScalarReductionBuffer> 5; for (int i = 16; i >= 1; i /= 2) { T shuffle_value; - if constexpr (HasNativeShuffle::value) + if constexpr (HasNativeShuffle::value) { shuffle_value = __shfl_xor_sync(0xffffffff, value, i, 32); - else + } else { shuffle_value = shuffle(0xffffffff, value, i, 32); + } REDUCTION::template fold(value, shuffle_value); } // Write warp values into shared memory - if ((laneid == 0) && (warpid > 0)) trampoline[warpid] = value; + if ((laneid == 0) && (warpid > 0)) { + trampoline[warpid] = value; + } __syncthreads(); // Output reduction if (threadIdx.x == 0) { - for (int i = 1; i < (THREADS_PER_BLOCK / 32); i++) + for (int i = 1; i < (THREADS_PER_BLOCK / 32); i++) { REDUCTION::template fold(value, trampoline[i]); + } result.reduce(value); // Make sure the result is visible externally __threadfence_system(); diff --git a/src/cunumeric/cudalibs.cu b/src/cunumeric/cudalibs.cu index cc7462b26f..9803bb4139 100644 --- a/src/cunumeric/cudalibs.cu +++ b/src/cunumeric/cudalibs.cu @@ -31,7 +31,9 @@ cufftContext::cufftContext(cufftPlan* plan) : plan_(plan) {} cufftContext::~cufftContext() { auto& hdl = handle(); - for (auto type : callback_types_) CHECK_CUFFT(cufftXtClearCallback(hdl, type)); + for (auto type : callback_types_) { + CHECK_CUFFT(cufftXtClearCallback(hdl, type)); + } CHECK_CUFFT(cufftSetWorkArea(hdl, nullptr)); } @@ -57,7 +59,9 @@ cufftPlanParams::cufftPlanParams(const DomainPoint& size) odist(1), batch(1) { - for (int dim = 0; dim < rank; ++dim) n[dim] = size[dim]; + for (int dim = 0; dim < rank; ++dim) { + n[dim] = size[dim]; + } } cufftPlanParams::cufftPlanParams(int rank, @@ -87,7 +91,9 @@ bool cufftPlanParams::operator==(const cufftPlanParams& other) const equal = equal && (n[dim] == other.n[dim]); equal = equal && (inembed[dim] == other.inembed[dim]); equal = equal && (onembed[dim] == other.onembed[dim]); - if (!equal) break; + if (!equal) { + break; + } } } return equal; @@ -97,11 +103,17 @@ std::string cufftPlanParams::to_string() const { std::ostringstream ss; ss << "cufftPlanParams[rank(" << rank << "), n(" << n[0]; - for (int i = 1; i < rank; ++i) ss << "," << n[i]; + for (int i = 1; i < rank; ++i) { + ss << "," << n[i]; + } ss << "), inembed(" << inembed[0]; - for (int i = 1; i < rank; ++i) ss << "," << inembed[i]; + for (int i = 1; i < rank; ++i) { + ss << "," << inembed[i]; + } ss << "), istride(" << istride << "), idist(" << idist << "), onembed(" << onembed[0]; - for (int i = 1; i < rank; ++i) ss << "," << onembed[i]; + for (int i = 1; i < rank; ++i) { + ss << "," << onembed[i]; + } ss << "), ostride(" << ostride << "), odist(" << odist << "), batch(" << batch << ")]"; return std::move(ss).str(); } @@ -135,15 +147,22 @@ struct cufftPlanCache { cufftPlanCache::cufftPlanCache(cufftType type) : type_(type) { - for (auto& cache : cache_) - for (auto& entry : cache) assert(0 == entry.lru_index); + for (auto& cache : cache_) { + for (auto& entry : cache) { + assert(0 == entry.lru_index); + } + } } cufftPlanCache::~cufftPlanCache() { - for (auto& cache : cache_) - for (auto& entry : cache) - if (entry.plan != nullptr) CHECK_CUFFT(cufftDestroy(entry.plan->handle)); + for (auto& cache : cache_) { + for (auto& entry : cache) { + if (entry.plan != nullptr) { + CHECK_CUFFT(cufftDestroy(entry.plan->handle)); + } + } + } } cufftPlan* cufftPlanCache::get_cufft_plan(const cufftPlanParams& params) @@ -153,7 +172,9 @@ cufftPlan* cufftPlanCache::get_cufft_plan(const cufftPlanParams& params) auto& cache = cache_[params.rank]; for (int32_t idx = 0; idx < MAX_PLANS; ++idx) { auto& entry = cache[idx]; - if (nullptr == entry.plan) break; + if (nullptr == entry.plan) { + break; + } if (*entry.params == params) { match = idx; cache_hits_++; @@ -196,7 +217,9 @@ cufftPlan* cufftPlanCache::get_cufft_plan(const cufftPlanParams& params) if (entry.lru_index != 0) { for (int32_t idx = plan_index + 1; idx < MAX_PLANS; ++idx) { auto& other = cache[idx]; - if (nullptr == other.plan) break; + if (nullptr == other.plan) { + break; + } ++other.lru_index; } entry.lru_index = 0; @@ -234,7 +257,9 @@ cufftPlan* cufftPlanCache::get_cufft_plan(const cufftPlanParams& params) if (entry.lru_index != 0) { for (int32_t idx = 0; idx < MAX_PLANS; ++idx) { auto& other = cache[idx]; - if (other.lru_index < entry.lru_index) ++other.lru_index; + if (other.lru_index < entry.lru_index) { + ++other.lru_index; + } } entry.lru_index = 0; } @@ -251,11 +276,21 @@ CUDALibraries::~CUDALibraries() { finalize(); } void CUDALibraries::finalize() { - if (finalized_) return; - if (cublas_ != nullptr) finalize_cublas(); - if (cusolver_ != nullptr) finalize_cusolver(); - if (cutensor_ != nullptr) finalize_cutensor(); - for (auto& pair : plan_caches_) delete pair.second; + if (finalized_) { + return; + } + if (cublas_ != nullptr) { + finalize_cublas(); + } + if (cusolver_ != nullptr) { + finalize_cusolver(); + } + if (cutensor_ != nullptr) { + finalize_cutensor(); + } + for (auto& pair : plan_caches_) { + delete pair.second; + } finalized_ = true; } @@ -285,8 +320,9 @@ cublasHandle_t CUDALibraries::get_cublas() if (fast_math != nullptr && atoi(fast_math) > 0) { // Enable acceleration of single precision routines using TF32 tensor cores. cublasStatus_t status = cublasSetMathMode(cublas_, CUBLAS_TF32_TENSOR_OP_MATH); - if (status != CUBLAS_STATUS_SUCCESS) + if (status != CUBLAS_STATUS_SUCCESS) { fprintf(stderr, "WARNING: cuBLAS does not support Tensor cores!"); + } } } return cublas_; @@ -294,7 +330,9 @@ cublasHandle_t CUDALibraries::get_cublas() cusolverDnHandle_t CUDALibraries::get_cusolver() { - if (nullptr == cusolver_) CHECK_CUSOLVER(cusolverDnCreate(&cusolver_)); + if (nullptr == cusolver_) { + CHECK_CUSOLVER(cusolverDnCreate(&cusolver_)); + } return cusolver_; } @@ -315,8 +353,9 @@ cufftContext CUDALibraries::get_cufft_plan(cufftType type, const cufftPlanParams if (plan_caches_.end() == finder) { cache = new cufftPlanCache(type); plan_caches_[type] = cache; - } else + } else { cache = finder->second; + } return cufftContext(cache->get_cufft_plan(params)); } diff --git a/src/cunumeric/divmod.h b/src/cunumeric/divmod.h index 0182e5e382..3ed18acab9 100644 --- a/src/cunumeric/divmod.h +++ b/src/cunumeric/divmod.h @@ -245,7 +245,9 @@ struct FastDivmod { __CUDA_HD__ static inline value_t clz(value_t x) { for (int i = 31; i >= 0; --i) { - if ((1 << i) & x) return 31 - i; + if ((1 << i) & x) { + return 31 - i; + } } return 32; } @@ -385,7 +387,9 @@ struct FastDivmodU64 { static inline uint32_t integer_log2(uint64_t x) { uint32_t n = 0; - while (x >>= 1) { ++n; } + while (x >>= 1) { + ++n; + } return n; } @@ -422,7 +426,9 @@ struct FastDivmodU64 { #ifdef __CUDA_ARCH__ uint64_t x = dividend; - if (multiplier) { x = __umul64hi(dividend + round_up, multiplier); } + if (multiplier) { + x = __umul64hi(dividend + round_up, multiplier); + } quotient = (x >> shift_right); #else // TODO - use proper 'fast' division here also. No reason why x86-code diff --git a/src/cunumeric/execution_policy/indexing/parallel_loop.cuh b/src/cunumeric/execution_policy/indexing/parallel_loop.cuh index 35a0242543..63952ebb8b 100644 --- a/src/cunumeric/execution_policy/indexing/parallel_loop.cuh +++ b/src/cunumeric/execution_policy/indexing/parallel_loop.cuh @@ -27,7 +27,9 @@ static __global__ void __launch_bounds__(THREADS_PER_BLOCK, MIN_CTAS_PER_SM) parallel_loop_kernel(const size_t volume, KERNEL kernel, Tag tag) { const size_t idx = global_tid_1d(); - if (idx >= volume) return; + if (idx >= volume) { + return; + } kernel(idx, tag); } @@ -37,7 +39,9 @@ struct ParallelLoopPolicy { void operator()(const RECT& rect, KERNEL&& kernel) { const size_t volume = rect.volume(); - if (0 == volume) return; + if (0 == volume) { + return; + } auto stream = get_cached_stream(); const size_t blocks = (volume + THREADS_PER_BLOCK - 1) / THREADS_PER_BLOCK; diff --git a/src/cunumeric/execution_policy/indexing/parallel_loop.h b/src/cunumeric/execution_policy/indexing/parallel_loop.h index 8506c202c3..4e5f020666 100644 --- a/src/cunumeric/execution_policy/indexing/parallel_loop.h +++ b/src/cunumeric/execution_policy/indexing/parallel_loop.h @@ -29,7 +29,9 @@ struct ParallelLoopPolicy { void operator()(const RECT& rect, KERNEL&& kernel) { const size_t volume = rect.volume(); - for (size_t idx = 0; idx < volume; ++idx) { kernel(idx, Tag{}); } + for (size_t idx = 0; idx < volume; ++idx) { + kernel(idx, Tag{}); + } } }; diff --git a/src/cunumeric/execution_policy/indexing/parallel_loop_omp.h b/src/cunumeric/execution_policy/indexing/parallel_loop_omp.h index bffa38ccca..2c00201b85 100644 --- a/src/cunumeric/execution_policy/indexing/parallel_loop_omp.h +++ b/src/cunumeric/execution_policy/indexing/parallel_loop_omp.h @@ -31,7 +31,9 @@ struct ParallelLoopPolicy { { const size_t volume = rect.volume(); #pragma omp for schedule(static) - for (size_t idx = 0; idx < volume; ++idx) { kernel(idx, Tag{}); } + for (size_t idx = 0; idx < volume; ++idx) { + kernel(idx, Tag{}); + } } }; diff --git a/src/cunumeric/execution_policy/reduction/scalar_reduction.cuh b/src/cunumeric/execution_policy/reduction/scalar_reduction.cuh index 52c87c9366..3d86b4fc78 100644 --- a/src/cunumeric/execution_policy/reduction/scalar_reduction.cuh +++ b/src/cunumeric/execution_policy/reduction/scalar_reduction.cuh @@ -30,7 +30,9 @@ static __global__ void __launch_bounds__(THREADS_PER_BLOCK, MIN_CTAS_PER_SM) auto value = identity; for (size_t idx = 0; idx < iters; idx++) { const size_t offset = (idx * gridDim.x + blockIdx.x) * blockDim.x + threadIdx.x; - if (offset < volume) { kernel(value, offset, identity, tag); } + if (offset < volume) { + kernel(value, offset, identity, tag); + } } // Every thread in the thread block must participate in the exchange to get correct results reduce_output(out, value); @@ -52,7 +54,9 @@ struct ScalarReductionPolicy { const LHS& identity, Kernel&& kernel) { - if (0 == volume) return; + if (0 == volume) { + return; + } auto stream = get_cached_stream(); diff --git a/src/cunumeric/execution_policy/reduction/scalar_reduction.h b/src/cunumeric/execution_policy/reduction/scalar_reduction.h index f28c47d8b1..98e0ca84ca 100644 --- a/src/cunumeric/execution_policy/reduction/scalar_reduction.h +++ b/src/cunumeric/execution_policy/reduction/scalar_reduction.h @@ -42,7 +42,9 @@ struct ScalarReductionPolicy { void operator()(size_t volume, AccessorRD& out, const LHS& identity, Kernel&& kernel) { auto result = identity; - for (size_t idx = 0; idx < volume; ++idx) { kernel(result, idx, identity, Tag{}); } + for (size_t idx = 0; idx < volume; ++idx) { + kernel(result, idx, identity, Tag{}); + } out.reduce(0, result); } }; diff --git a/src/cunumeric/execution_policy/reduction/scalar_reduction_omp.h b/src/cunumeric/execution_policy/reduction/scalar_reduction_omp.h index 971f9c1273..1d617a520f 100644 --- a/src/cunumeric/execution_policy/reduction/scalar_reduction_omp.h +++ b/src/cunumeric/execution_policy/reduction/scalar_reduction_omp.h @@ -30,14 +30,20 @@ struct ScalarReductionPolicy { { const auto max_threads = omp_get_max_threads(); ThreadLocalStorage locals(max_threads); - for (auto idx = 0; idx < max_threads; ++idx) locals[idx] = identity; + for (auto idx = 0; idx < max_threads; ++idx) { + locals[idx] = identity; + } #pragma omp parallel { const int tid = omp_get_thread_num(); #pragma omp for schedule(static) - for (size_t idx = 0; idx < volume; ++idx) { kernel(locals[tid], idx, identity, Tag{}); } + for (size_t idx = 0; idx < volume; ++idx) { + kernel(locals[tid], idx, identity, Tag{}); + } + } + for (auto idx = 0; idx < max_threads; ++idx) { + out.reduce(0, locals[idx]); } - for (auto idx = 0; idx < max_threads; ++idx) out.reduce(0, locals[idx]); } }; diff --git a/src/cunumeric/fft/fft.cu b/src/cunumeric/fft/fft.cu index 9ce5b21cdd..3715dba29a 100644 --- a/src/cunumeric/fft/fft.cu +++ b/src/cunumeric/fft/fft.cu @@ -31,7 +31,9 @@ __global__ static void copy_kernel( size_t volume, TYPE* target, AccessorRO acc, Pitches pitches, Point lo) { size_t offset = blockIdx.x * blockDim.x + threadIdx.x; - if (offset >= volume) return; + if (offset >= volume) { + return; + } auto p = pitches.unflatten(offset, Point::ZEROES()); target[offset] = acc[p + lo]; } @@ -100,9 +102,9 @@ __host__ static inline void cufft_operation(AccessorWO out, } const void* in_ptr{nullptr}; - if (in.accessor.is_dense_row_major(in_rect)) + if (in.accessor.is_dense_row_major(in_rect)) { in_ptr = in.ptr(in_rect.lo); - else { + } else { auto buffer = create_buffer(fft_size_in, Memory::Kind::GPU_FB_MEM); in_ptr = buffer.ptr(zero); copy_into_buffer((INPUT_TYPE*)in_ptr, in, in_rect, in_rect.volume(), stream); @@ -155,13 +157,17 @@ __host__ static inline void cufft_over_axes_c2c(INOUT_TYPE* out, // Extract number of slices and batches per slice int64_t num_slices = 1; if (axis != DIM - 1) { - for (int32_t i = 0; i < axis; ++i) { num_slices *= n[i]; } + for (int32_t i = 0; i < axis; ++i) { + num_slices *= n[i]; + } } dim_t batches = num_elements / (num_slices * size_1d); int64_t offset = batches * size_1d; dim_t stride = 1; - for (int32_t i = axis + 1; i < DIM; ++i) { stride *= fft_size[i]; } + for (int32_t i = axis + 1; i < DIM; ++i) { + stride *= fft_size[i]; + } dim_t dist = (axis == DIM - 1) ? size_1d : 1; // get plan from cache @@ -170,7 +176,9 @@ __host__ static inline void cufft_over_axes_c2c(INOUT_TYPE* out, if (cufft_context.workareaSize() > 0) { if (cufft_context.workareaSize() > last_workarea_size) { - if (last_workarea_size > 0) workarea_buffer.destroy(); + if (last_workarea_size > 0) { + workarea_buffer.destroy(); + } workarea_buffer = create_buffer(cufft_context.workareaSize(), Memory::Kind::GPU_FB_MEM); last_workarea_size = cufft_context.workareaSize(); @@ -225,7 +233,9 @@ __host__ static inline void cufft_r2c_c2r(OUTPUT_TYPE* out, // Extract number of slices and batches per slice int64_t num_slices = 1; if (axis != DIM - 1) { - for (int32_t i = 0; i < axis; ++i) { num_slices *= n[i]; } + for (int32_t i = 0; i < axis; ++i) { + num_slices *= n[i]; + } } dim_t batches = ((direction == CUNUMERIC_FFT_FORWARD) ? num_elements_in : num_elements_out) / (num_slices * size_1d); @@ -289,7 +299,9 @@ __host__ static inline void cufft_over_axes(AccessorWO out, { Point fft_size_in = in_rect.hi - in_rect.lo + Point::ONES(); size_t num_elements_in = 1; - for (int32_t i = 0; i < DIM; ++i) { num_elements_in *= fft_size_in[i]; } + for (int32_t i = 0; i < DIM; ++i) { + num_elements_in *= fft_size_in[i]; + } if (is_c2c) { // utilize out as temporary store for c2c in_ptr = (INPUT_TYPE*)out.ptr(out_rect.lo); diff --git a/src/cunumeric/fft/fft_template.inl b/src/cunumeric/fft/fft_template.inl index c7d5b17b9a..3318d885be 100644 --- a/src/cunumeric/fft/fft_template.inl +++ b/src/cunumeric/fft/fft_template.inl @@ -44,7 +44,9 @@ struct FFTImpl { auto in_rect = args.input.shape(); auto out_rect = args.output.shape(); - if (in_rect.empty() || out_rect.empty()) return; + if (in_rect.empty() || out_rect.empty()) { + return; + } auto input = args.input.read_accessor(in_rect); auto output = args.output.write_accessor(out_rect); @@ -91,7 +93,9 @@ static void fft_template(TaskContext& context) args.direction = scalars[1].value(); args.operate_over_axes = scalars[2].value(); - for (size_t i = 3; i < scalars.size(); ++i) args.axes.push_back(scalars[i].value()); + for (size_t i = 3; i < scalars.size(); ++i) { + args.axes.push_back(scalars[i].value()); + } fft_dispatch(args.type, FFTDispatch{}, args); } diff --git a/src/cunumeric/index/advanced_indexing.cc b/src/cunumeric/index/advanced_indexing.cc index bdd664acaa..f708438c94 100644 --- a/src/cunumeric/index/advanced_indexing.cc +++ b/src/cunumeric/index/advanced_indexing.cc @@ -45,13 +45,17 @@ struct AdvancedIndexingImplBody { size_t j = key_dim + i; out_p[i + 1] = p[j]; } - for (int32_t i = DIM - key_dim + 1; i < DIM; i++) out_p[i] = 0; + for (int32_t i = DIM - key_dim + 1; i < DIM; i++) { + out_p[i] = 0; + } fill_out(out[out_p], p, input[p]); // The logic below is based on the assumtion that // pitches enumerate points in C-order, but this might // change in the future // TODO: replace with the order-aware interator when available - if ((idx + 1) % skip_size == 0) out_idx++; + if ((idx + 1) % skip_size == 0) { + out_idx++; + } } } } @@ -67,7 +71,9 @@ struct AdvancedIndexingImplBody { size_t skip_size = 1; for (size_t i = key_dim; i < DIM; i++) { auto diff = 1 + rect.hi[i] - rect.lo[i]; - if (diff != 0) skip_size *= diff; + if (diff != 0) { + skip_size *= diff; + } } // calculate size of the key_dim-1 extend in output region @@ -75,7 +81,9 @@ struct AdvancedIndexingImplBody { size_t size = 0; for (size_t idx = 0; idx < volume; idx += skip_size) { auto p = pitches.unflatten(idx, rect.lo); - if (index[p] == true) { size++; } + if (index[p] == true) { + size++; + } } // calculating the shape of the output region for this sub-task @@ -85,10 +93,14 @@ struct AdvancedIndexingImplBody { size_t j = key_dim + i; extents[i + 1] = 1 + rect.hi[j] - rect.lo[j]; } - for (int32_t i = DIM - key_dim + 1; i < DIM; i++) extents[i] = 1; + for (int32_t i = DIM - key_dim + 1; i < DIM; i++) { + extents[i] = 1; + } auto out = out_arr.create_output_buffer(extents, true); - if (size > 0) compute_output(out, input, index, pitches, rect, volume, key_dim, skip_size); + if (size > 0) { + compute_output(out, input, index, pitches, rect, volume, key_dim, skip_size); + } } }; diff --git a/src/cunumeric/index/advanced_indexing.cu b/src/cunumeric/index/advanced_indexing.cu index 66e8392d0c..b379458e72 100644 --- a/src/cunumeric/index/advanced_indexing.cu +++ b/src/cunumeric/index/advanced_indexing.cu @@ -38,7 +38,9 @@ static __global__ void __launch_bounds__(THREADS_PER_BLOCK, MIN_CTAS_PER_SM) uint64_t value = 0; for (size_t i = 0; i < iters; i++) { size_t idx = (i * gridDim.x + blockIdx.x) * blockDim.x + threadIdx.x; - if (idx >= volume) break; + if (idx >= volume) { + break; + } auto point = pitches.unflatten(idx, origin); bool val = (index[point] && ((idx + 1) % skip_size == 0)); offsets[idx] = static_cast(val); @@ -61,7 +63,9 @@ static __global__ void __launch_bounds__(THREADS_PER_BLOCK, MIN_CTAS_PER_SM) const size_t key_dim) { const size_t tid = blockIdx.x * blockDim.x + threadIdx.x; - if (tid >= volume) return; + if (tid >= volume) { + return; + } auto point = pitches.unflatten(tid, origin); if (index[point] == true) { Point out_p; @@ -70,7 +74,9 @@ static __global__ void __launch_bounds__(THREADS_PER_BLOCK, MIN_CTAS_PER_SM) size_t j = key_dim + i; out_p[i + 1] = point[j]; } - for (size_t i = DIM - key_dim + 1; i < DIM; i++) out_p[i] = 0; + for (size_t i = DIM - key_dim + 1; i < DIM; i++) { + out_p[i] = 0; + } fill_out(out[out_p], point, in[point]); } } @@ -98,9 +104,10 @@ struct AdvancedIndexingImplBody { const size_t iters = (blocks + MAX_REDUCTION_CTAS - 1) / MAX_REDUCTION_CTAS; count_nonzero_kernel<<>>( volume, size, offsets, in, pitches, rect.lo, iters, skip_size, key_dim); - } else + } else { count_nonzero_kernel<<>>( volume, size, offsets, in, pitches, rect.lo, 1, skip_size, key_dim); + } CHECK_CUDA_STREAM(stream); @@ -125,7 +132,9 @@ struct AdvancedIndexingImplBody { size_t skip_size = 1; for (int i = key_dim; i < DIM; i++) { auto diff = 1 + rect.hi[i] - rect.lo[i]; - if (diff != 0) skip_size *= diff; + if (diff != 0) { + skip_size *= diff; + } } size = compute_size(index, pitches, rect, volume, stream, offsets, skip_size, key_dim); @@ -137,7 +146,9 @@ struct AdvancedIndexingImplBody { size_t j = key_dim + i; extents[i + 1] = 1 + rect.hi[j] - rect.lo[j]; } - for (size_t i = DIM - key_dim + 1; i < DIM; i++) extents[i] = 1; + for (size_t i = DIM - key_dim + 1; i < DIM; i++) { + extents[i] = 1; + } auto out = out_arr.create_output_buffer(extents, true); diff --git a/src/cunumeric/index/advanced_indexing_omp.cc b/src/cunumeric/index/advanced_indexing_omp.cc index e14e6c9286..80de2eef3f 100644 --- a/src/cunumeric/index/advanced_indexing_omp.cc +++ b/src/cunumeric/index/advanced_indexing_omp.cc @@ -39,7 +39,9 @@ struct AdvancedIndexingImplBody { const size_t max_threads) const { ThreadLocalStorage sizes(max_threads); - for (size_t idx = 0; idx < max_threads; ++idx) sizes[idx] = 0; + for (size_t idx = 0; idx < max_threads; ++idx) { + sizes[idx] = 0; + } #pragma omp parallel { const int tid = omp_get_thread_num(); @@ -68,7 +70,9 @@ struct AdvancedIndexingImplBody { size_t skip_size = 1; for (int i = key_dim; i < DIM; i++) { auto diff = 1 + rect.hi[i] - rect.lo[i]; - if (diff != 0) skip_size *= diff; + if (diff != 0) { + skip_size *= diff; + } } const auto max_threads = omp_get_max_threads(); @@ -84,7 +88,9 @@ struct AdvancedIndexingImplBody { size_t j = key_dim + i; extents[i + 1] = 1 + rect.hi[j] - rect.lo[j]; } - for (size_t i = DIM - key_dim + 1; i < DIM; i++) extents[i] = 1; + for (size_t i = DIM - key_dim + 1; i < DIM; i++) { + extents[i] = 1; + } auto out = out_arr.create_output_buffer(extents, true); if (size > 0) @@ -102,9 +108,13 @@ struct AdvancedIndexingImplBody { size_t j = key_dim + i; out_p[i + 1] = p[j]; } - for (int32_t i = DIM - key_dim + 1; i < DIM; i++) out_p[i] = 0; + for (int32_t i = DIM - key_dim + 1; i < DIM; i++) { + out_p[i] = 0; + } fill_out(out[out_p], p, input[p]); - if ((idx + 1) % skip_size == 0) out_idx++; + if ((idx + 1) % skip_size == 0) { + out_idx++; + } } } diff --git a/src/cunumeric/index/choose.cu b/src/cunumeric/index/choose.cu index e6b59e313a..9bd573825e 100644 --- a/src/cunumeric/index/choose.cu +++ b/src/cunumeric/index/choose.cu @@ -30,7 +30,9 @@ __global__ static void __launch_bounds__(THREADS_PER_BLOCK, MIN_CTAS_PER_SM) int volume) { const size_t idx = global_tid_1d(); - if (idx >= volume) return; + if (idx >= volume) { + return; + } auto p = pitches.unflatten(idx, rect.lo); out[p] = choices[index_arr[p]][p]; } @@ -41,7 +43,9 @@ __global__ static void __launch_bounds__(THREADS_PER_BLOCK, MIN_CTAS_PER_SM) cho VAL* outptr, const int64_t* indexptr, Buffer choices, int volume) { const size_t idx = global_tid_1d(); - if (idx >= volume) return; + if (idx >= volume) { + return; + } outptr[idx] = choices[indexptr[idx]][idx]; } @@ -62,7 +66,9 @@ struct ChooseImplBody { auto stream = get_cached_stream(); if (dense) { auto ch_arr = create_buffer(choices.size(), legate::Memory::Kind::Z_COPY_MEM); - for (uint32_t idx = 0; idx < choices.size(); ++idx) ch_arr[idx] = choices[idx].ptr(rect); + for (uint32_t idx = 0; idx < choices.size(); ++idx) { + ch_arr[idx] = choices[idx].ptr(rect); + } VAL* outptr = out.ptr(rect); const int64_t* indexptr = index_arr.ptr(rect); choose_kernel_dense @@ -70,7 +76,9 @@ struct ChooseImplBody { } else { auto ch_arr = create_buffer>(choices.size(), legate::Memory::Kind::Z_COPY_MEM); - for (uint32_t idx = 0; idx < choices.size(); ++idx) ch_arr[idx] = choices[idx]; + for (uint32_t idx = 0; idx < choices.size(); ++idx) { + ch_arr[idx] = choices[idx]; + } choose_kernel <<>>(out, index_arr, ch_arr, rect, pitches, volume); } diff --git a/src/cunumeric/index/choose_template.inl b/src/cunumeric/index/choose_template.inl index dadf4db7a9..22905fa28b 100644 --- a/src/cunumeric/index/choose_template.inl +++ b/src/cunumeric/index/choose_template.inl @@ -37,7 +37,9 @@ struct ChooseImpl { Pitches pitches; size_t volume = pitches.flatten(out_rect); - if (volume == 0) return; + if (volume == 0) { + return; + } auto out = args.out.write_accessor(out_rect); auto index_rect = args.inputs[0].shape(); @@ -65,7 +67,9 @@ template static void choose_template(TaskContext& context) { std::vector inputs; - for (auto& input : context.inputs()) { inputs.emplace_back(input); } + for (auto& input : context.inputs()) { + inputs.emplace_back(input); + } ChooseArgs args{context.output(0), std::move(inputs)}; double_dispatch(args.inputs[0].dim(), args.inputs[0].code(), ChooseImpl{}, args); } diff --git a/src/cunumeric/index/putmask_template.inl b/src/cunumeric/index/putmask_template.inl index e0545e104c..e6b4bb0fd5 100644 --- a/src/cunumeric/index/putmask_template.inl +++ b/src/cunumeric/index/putmask_template.inl @@ -56,7 +56,9 @@ struct Putmask { mask = args.mask.read_accessor(rect); values = args.values.read_accessor(rect); volume = pitches.flatten(rect); - if (volume == 0) return; + if (volume == 0) { + return; + } #if !LegateDefined(LEGATE_BOUNDS_CHECKS) dense = input.accessor.is_dense_row_major(rect) && mask.accessor.is_dense_row_major(rect); dense = dense && values.accessor.is_dense_row_major(rect); @@ -70,19 +72,25 @@ struct Putmask { __CUDA_HD__ void operator()(const size_t idx, DenseTag) const noexcept { - if (maskptr[idx]) inputptr[idx] = valptr[idx]; + if (maskptr[idx]) { + inputptr[idx] = valptr[idx]; + } } __CUDA_HD__ void operator()(const size_t idx, SparseTag) const noexcept { auto p = pitches.unflatten(idx, rect.lo); - if (mask[p]) input[p] = values[p]; + if (mask[p]) { + input[p] = values[p]; + } } void execute() const noexcept { #if !LegateDefined(LEGATE_BOUNDS_CHECKS) - if (dense) { return ParallelLoopPolicy()(rect, *this); } + if (dense) { + return ParallelLoopPolicy()(rect, *this); + } #endif return ParallelLoopPolicy()(rect, *this); } diff --git a/src/cunumeric/index/repeat.cc b/src/cunumeric/index/repeat.cc index dc264cb7d6..d33a49711c 100644 --- a/src/cunumeric/index/repeat.cc +++ b/src/cunumeric/index/repeat.cc @@ -68,7 +68,9 @@ struct RepeatImplBody { int64_t out_idx = 0; for (size_t in_idx = 0; in_idx < volume; ++in_idx) { auto p = in_pitches.unflatten(in_idx, in_rect.lo); - for (int64_t r = 0; r < repeats[p]; r++) out[out_idx++] = in[p]; + for (int64_t r = 0; r < repeats[p]; r++) { + out[out_idx++] = in[p]; + } } } diff --git a/src/cunumeric/index/repeat.cu b/src/cunumeric/index/repeat.cu index fabee78bbb..1d1206ca0b 100644 --- a/src/cunumeric/index/repeat.cu +++ b/src/cunumeric/index/repeat.cu @@ -59,7 +59,9 @@ __global__ static void __launch_bounds__(THREADS_PER_BLOCK, MIN_CTAS_PER_SM) const size_t volume) { const size_t idx = global_tid_1d(); - if (idx >= volume) return; + if (idx >= volume) { + return; + } auto out_p = pitches.unflatten(idx, Point::ZEROES()); auto in_p = out_p; in_p[axis] /= repeats; @@ -79,7 +81,9 @@ __global__ static void __launch_bounds__(THREADS_PER_BLOCK, MIN_CTAS_PER_SM) const int volume) { const size_t idx = global_tid_1d(); - if (idx >= volume) return; + if (idx >= volume) { + return; + } auto in_p = pitches.unflatten(idx, in_lo); auto out_p = in_p - in_lo; diff --git a/src/cunumeric/index/repeat_omp.cc b/src/cunumeric/index/repeat_omp.cc index 6be45a5352..fca6da706b 100644 --- a/src/cunumeric/index/repeat_omp.cc +++ b/src/cunumeric/index/repeat_omp.cc @@ -66,7 +66,9 @@ struct RepeatImplBody { const auto max_threads = omp_get_max_threads(); ThreadLocalStorage local_sums(max_threads); - for (auto idx = 0; idx < max_threads; ++idx) local_sums[idx] = 0; + for (auto idx = 0; idx < max_threads; ++idx) { + local_sums[idx] = 0; + } #pragma omp parallel { @@ -86,7 +88,9 @@ struct RepeatImplBody { thrust::exclusive_scan(thrust::omp::par, p_offsets, p_offsets + axis_extent, p_offsets); int64_t sum = 0; - for (auto idx = 0; idx < max_threads; ++idx) sum += local_sums[idx]; + for (auto idx = 0; idx < max_threads; ++idx) { + sum += local_sums[idx]; + } Point extents = in_rect.hi - in_rect.lo + Point::ONES(); extents[axis] = sum; diff --git a/src/cunumeric/index/repeat_template.inl b/src/cunumeric/index/repeat_template.inl index 6f06fac2d3..18d58f4c28 100644 --- a/src/cunumeric/index/repeat_template.inl +++ b/src/cunumeric/index/repeat_template.inl @@ -41,10 +41,10 @@ struct RepeatImpl { return; } - if (args.scalar_repeats) + if (args.scalar_repeats) { RepeatImplBody{}( args.output, input_arr, args.repeats, args.axis, input_rect); - else { + } else { auto repeats_arr = args.repeats_arr.read_accessor(input_rect); RepeatImplBody{}(args.output, input_arr, repeats_arr, args.axis, input_rect); } diff --git a/src/cunumeric/index/wrap.cc b/src/cunumeric/index/wrap.cc index ca46ec6e20..b710bd502c 100644 --- a/src/cunumeric/index/wrap.cc +++ b/src/cunumeric/index/wrap.cc @@ -39,14 +39,18 @@ struct WrapImplBody { if (dense) { auto outptr = out.ptr(rect_out); for (int64_t i = start; i <= end; i++) { - if (check_bounds) check_idx(i, volume_base, indices); + if (check_bounds) { + check_idx(i, volume_base, indices); + } const int64_t input_idx = compute_idx(i, volume_base, indices); auto point = pitches_base.unflatten(input_idx, rect_base.lo); outptr[i - start] = point; } } else { for (int64_t i = start; i <= end; i++) { - if (check_bounds) check_idx(i, volume_base, indices); + if (check_bounds) { + check_idx(i, volume_base, indices); + } const int64_t input_idx = compute_idx(i, volume_base, indices); auto point = pitches_base.unflatten(input_idx, rect_base.lo); out[i] = point; diff --git a/src/cunumeric/index/wrap.cu b/src/cunumeric/index/wrap.cu index 71998e0fc7..13d6e45aa0 100644 --- a/src/cunumeric/index/wrap.cu +++ b/src/cunumeric/index/wrap.cu @@ -34,7 +34,9 @@ __global__ static void __launch_bounds__(THREADS_PER_BLOCK, MIN_CTAS_PER_SM) bool value = false; for (size_t i = 0; i < iters; i++) { const auto idx = (i * gridDim.x + blockIdx.x) * blockDim.x + threadIdx.x; - if (idx >= volume) break; + if (idx >= volume) { + break; + } auto index_tmp = indices[idx + start]; int64_t index = index_tmp < 0 ? index_tmp + volume_base : index_tmp; bool val = (index < 0 || index >= volume_base); @@ -56,7 +58,9 @@ __global__ static void __launch_bounds__(THREADS_PER_BLOCK, MIN_CTAS_PER_SM) const IND indices) { const auto idx = global_tid_1d(); - if (idx >= volume) return; + if (idx >= volume) { + return; + } const int64_t input_idx = compute_idx((idx + start), volume_base, indices); auto out_p = pitches_out.unflatten(idx, out_lo); auto p = pitches_base.unflatten(input_idx, base_lo); @@ -74,7 +78,9 @@ __global__ static void __launch_bounds__(THREADS_PER_BLOCK, MIN_CTAS_PER_SM) const IND indices) { const auto idx = global_tid_1d(); - if (idx >= volume) return; + if (idx >= volume) { + return; + } const int64_t input_idx = compute_idx((idx + start), volume_base, indices); auto p = pitches_base.unflatten(input_idx, base_lo); out[idx] = p; @@ -110,7 +116,9 @@ void check_out_of_bounds(const AccessorRO& indices, CHECK_CUDA_STREAM(stream); bool res = out_of_bounds.read(stream); - if (res) throw legate::TaskException("index is out of bounds in index array"); + if (res) { + throw legate::TaskException("index is out of bounds in index array"); + } } template @@ -131,7 +139,9 @@ struct WrapImplBody { const auto volume_base = rect_base.volume(); const size_t blocks = (volume + THREADS_PER_BLOCK - 1) / THREADS_PER_BLOCK; - if (check_bounds) check_out_of_bounds(indices, start, volume, volume_base, stream); + if (check_bounds) { + check_out_of_bounds(indices, start, volume, volume_base, stream); + } if (dense) { auto outptr = out.ptr(rect_out); diff --git a/src/cunumeric/index/wrap.h b/src/cunumeric/index/wrap.h index b00dfcd810..b1ea6b1e11 100644 --- a/src/cunumeric/index/wrap.h +++ b/src/cunumeric/index/wrap.h @@ -64,8 +64,9 @@ inline void check_idx(const int64_t i, { int64_t idx = indices[i]; int64_t index = idx < 0 ? idx + volume : idx; - if (index < 0 || index >= volume) + if (index < 0 || index >= volume) { throw legate::TaskException("index is out of bounds in index array"); + } } inline void check_idx(const int64_t i, const int64_t volume, const bool&) { diff --git a/src/cunumeric/index/wrap_omp.cc b/src/cunumeric/index/wrap_omp.cc index b69d4ae5cb..84dae4251b 100644 --- a/src/cunumeric/index/wrap_omp.cc +++ b/src/cunumeric/index/wrap_omp.cc @@ -41,8 +41,11 @@ struct WrapImplBody { auto outptr = out.ptr(rect_out); #pragma omp parallel for schedule(static) for (int64_t i = start; i <= end; i++) { - if (check_bounds) - if (check_idx_omp(i, volume_base, indices)) is_out_of_bounds = true; + if (check_bounds) { + if (check_idx_omp(i, volume_base, indices)) { + is_out_of_bounds = true; + } + } const int64_t input_idx = compute_idx(i, volume_base, indices); auto point = pitches_base.unflatten(input_idx, rect_base.lo); outptr[i - start] = point; @@ -50,15 +53,20 @@ struct WrapImplBody { } else { #pragma omp parallel for schedule(static) for (int64_t i = start; i <= end; i++) { - if (check_bounds) - if (check_idx_omp(i, volume_base, indices)) is_out_of_bounds = true; + if (check_bounds) { + if (check_idx_omp(i, volume_base, indices)) { + is_out_of_bounds = true; + } + } const int64_t input_idx = compute_idx(i, volume_base, indices); auto point = pitches_base.unflatten(input_idx, rect_base.lo); out[i] = point; } } // else - if (is_out_of_bounds) throw legate::TaskException("index is out of bounds in index array"); + if (is_out_of_bounds) { + throw legate::TaskException("index is out of bounds in index array"); + } } }; diff --git a/src/cunumeric/index/wrap_template.inl b/src/cunumeric/index/wrap_template.inl index 42e85a0d73..005e17adf7 100644 --- a/src/cunumeric/index/wrap_template.inl +++ b/src/cunumeric/index/wrap_template.inl @@ -37,7 +37,9 @@ struct WrapImpl { Pitches<0> pitches_out; size_t volume_out = pitches_out.flatten(rect_out); - if (volume_out == 0) return; + if (volume_out == 0) { + return; + } #if !LegateDefined(LEGATE_BOUNDS_CHECKS) bool dense = out.accessor.is_dense_row_major(rect_out); diff --git a/src/cunumeric/index/zip.cc b/src/cunumeric/index/zip.cc index 2497553776..53c3b769cf 100644 --- a/src/cunumeric/index/zip.cc +++ b/src/cunumeric/index/zip.cc @@ -66,7 +66,9 @@ struct ZipImplBody { for (size_t idx = 0; idx < volume; ++idx) { auto p = pitches.unflatten(idx, rect.lo); Point new_point; - for (int64_t i = 0; i < start_index; i++) { new_point[i] = p[i]; } + for (int64_t i = 0; i < start_index; i++) { + new_point[i] = p[i]; + } for (size_t i = 0; i < index_arrays.size(); i++) { new_point[start_index + i] = compute_idx(index_arrays[i][p], shape[start_index + i]); } diff --git a/src/cunumeric/index/zip.cu b/src/cunumeric/index/zip.cu index c66c59f4fe..f35586f006 100644 --- a/src/cunumeric/index/zip.cu +++ b/src/cunumeric/index/zip.cu @@ -31,10 +31,14 @@ __global__ static void __launch_bounds__(THREADS_PER_BLOCK, MIN_CTAS_PER_SM) std::index_sequence) { const size_t idx = global_tid_1d(); - if (idx >= volume) return; + if (idx >= volume) { + return; + } auto p = pitches.unflatten(idx, rect.lo); Point new_point; - for (size_t i = 0; i < N; i++) { new_point[i] = compute_idx_cuda(index_arrays[i][p], shape[i]); } + for (size_t i = 0; i < N; i++) { + new_point[i] = compute_idx_cuda(index_arrays[i][p], shape[i]); + } out[p] = new_point; } @@ -48,7 +52,9 @@ __global__ static void __launch_bounds__(THREADS_PER_BLOCK, MIN_CTAS_PER_SM) std::index_sequence) { const size_t idx = global_tid_1d(); - if (idx >= volume) return; + if (idx >= volume) { + return; + } Point new_point; for (size_t i = 0; i < N; i++) { new_point[i] = compute_idx_cuda(index_arrays[i][idx], shape[i]); @@ -69,10 +75,14 @@ __global__ static void __launch_bounds__(THREADS_PER_BLOCK, MIN_CTAS_PER_SM) const DomainPoint shape) { const size_t idx = global_tid_1d(); - if (idx >= volume) return; + if (idx >= volume) { + return; + } auto p = pitches.unflatten(idx, rect.lo); Point new_point; - for (size_t i = 0; i < start_index; i++) { new_point[i] = p[i]; } + for (size_t i = 0; i < start_index; i++) { + new_point[i] = p[i]; + } for (size_t i = 0; i < narrays; i++) { new_point[start_index + i] = compute_idx_cuda(index_arrays[i][p], shape[start_index + i]); } @@ -98,7 +108,9 @@ __global__ static void __launch_bounds__(THREADS_PER_BLOCK, MIN_CTAS_PER_SM) bool value = false; for (size_t i = 0; i < iters; i++) { const auto idx = (i * gridDim.x + blockIdx.x) * blockDim.x + threadIdx.x; - if (idx >= volume) break; + if (idx >= volume) { + break; + } auto p = pitches.unflatten(idx, rect.lo); for (size_t n = 0; n < narrays; n++) { const int64_t extent = shape[start_index + n]; @@ -137,7 +149,9 @@ struct ZipImplBody { CHECK_CUDA_STREAM(stream); bool res = out_of_bounds.read(stream); - if (res) throw legate::TaskException("index is out of bounds in index array"); + if (res) { + throw legate::TaskException("index is out of bounds in index array"); + } } template @@ -157,7 +171,9 @@ struct ZipImplBody { auto index_buf = create_buffer, 1>(index_arrays.size(), legate::Memory::Kind::Z_COPY_MEM); - for (uint32_t idx = 0; idx < index_arrays.size(); ++idx) index_buf[idx] = index_arrays[idx]; + for (uint32_t idx = 0; idx < index_arrays.size(); ++idx) { + index_buf[idx] = index_arrays[idx]; + } check_out_of_bounds( index_buf, volume, rect, pitches, index_arrays.size(), start_index, shape, stream); diff --git a/src/cunumeric/index/zip.h b/src/cunumeric/index/zip.h index 0e0eff4db8..fdd27bd61d 100644 --- a/src/cunumeric/index/zip.h +++ b/src/cunumeric/index/zip.h @@ -46,8 +46,9 @@ class ZipTask : public CuNumericTask { constexpr coord_t compute_idx(coord_t index, coord_t extent) { coord_t new_index = index < 0 ? index + extent : index; - if (new_index < 0 || new_index >= extent) + if (new_index < 0 || new_index >= extent) { throw legate::TaskException("index is out of bounds in index array"); + } return new_index; } diff --git a/src/cunumeric/index/zip_omp.cc b/src/cunumeric/index/zip_omp.cc index 536a44b989..2ab55723b3 100644 --- a/src/cunumeric/index/zip_omp.cc +++ b/src/cunumeric/index/zip_omp.cc @@ -48,7 +48,9 @@ struct ZipImplBody { for (size_t i = 0; i < N; i++) { auto pair = compute_idx_omp(indx_ptrs[i][idx], shape[i]); new_point[i] = pair.first; - if (pair.second) is_out_of_bounds = true; + if (pair.second) { + is_out_of_bounds = true; + } } outptr[idx] = new_point; } @@ -60,7 +62,9 @@ struct ZipImplBody { for (size_t i = 0; i < N; i++) { auto pair = compute_idx_omp(index_arrays[i][p], shape[i]); new_point[i] = pair.first; - if (pair.second) is_out_of_bounds = true; + if (pair.second) { + is_out_of_bounds = true; + } } out[p] = new_point; } @@ -73,11 +77,15 @@ struct ZipImplBody { for (size_t idx = 0; idx < volume; ++idx) { auto p = pitches.unflatten(idx, rect.lo); Point new_point; - for (int64_t i = 0; i < start_index; i++) { new_point[i] = p[i]; } + for (int64_t i = 0; i < start_index; i++) { + new_point[i] = p[i]; + } for (size_t i = 0; i < index_arrays.size(); i++) { auto pair = compute_idx_omp(index_arrays[i][p], shape[start_index + i]); new_point[start_index + i] = pair.first; - if (pair.second) is_out_of_bounds = true; + if (pair.second) { + is_out_of_bounds = true; + } } for (size_t i = (start_index + index_arrays.size()); i < N; i++) { int64_t j = key_dim + i - index_arrays.size(); @@ -86,7 +94,9 @@ struct ZipImplBody { out[p] = new_point; } } - if (is_out_of_bounds) throw legate::TaskException("index is out of bounds in index array"); + if (is_out_of_bounds) { + throw legate::TaskException("index is out of bounds in index array"); + } } }; diff --git a/src/cunumeric/index/zip_template.inl b/src/cunumeric/index/zip_template.inl index 5c5b0a41e6..449fdee0da 100644 --- a/src/cunumeric/index/zip_template.inl +++ b/src/cunumeric/index/zip_template.inl @@ -37,7 +37,9 @@ struct ZipImpl { auto out = args.out.write_accessor, DIM>(out_rect); Pitches pitches; size_t volume = pitches.flatten(out_rect); - if (volume == 0) return; + if (volume == 0) { + return; + } #if !LegateDefined(LEGATE_BOUNDS_CHECKS) bool dense = out.accessor.is_dense_row_major(out_rect); @@ -93,11 +95,15 @@ static void zip_template(TaskContext& context) int64_t start_index = context.scalar(2).value(); auto shape = context.scalar(3).value(); std::vector inputs; - for (auto& input : context.inputs()) { inputs.emplace_back(input); } + for (auto& input : context.inputs()) { + inputs.emplace_back(input); + } ZipArgs args{context.output(0), std::move(inputs), N, key_dim, start_index, shape}; int dim = args.inputs[0].dim(); // if scalar passed as an input, convert it to the array size 1 - if (dim == 0) { dim = 1; } + if (dim == 0) { + dim = 1; + } double_dispatch(dim, N, ZipImpl{}, args); } diff --git a/src/cunumeric/mapper.cc b/src/cunumeric/mapper.cc index 33d29021dc..69995d95ed 100644 --- a/src/cunumeric/mapper.cc +++ b/src/cunumeric/mapper.cc @@ -48,23 +48,25 @@ Scalar CuNumericMapper::tunable_value(TunableID tunable_id) } case CUNUMERIC_TUNABLE_NUM_PROCS: { int32_t num_procs = 0; - if (!machine->gpus().empty()) + if (!machine->gpus().empty()) { num_procs = machine->gpus().size() * machine->total_nodes(); - else if (!machine->omps().empty()) + } else if (!machine->omps().empty()) { num_procs = machine->omps().size() * machine->total_nodes(); - else + } else { num_procs = machine->cpus().size() * machine->total_nodes(); + } return Scalar(num_procs); } case CUNUMERIC_TUNABLE_MAX_EAGER_VOLUME: { int32_t eager_volume = 0; // TODO: make these profile guided - if (!machine->gpus().empty()) + if (!machine->gpus().empty()) { eager_volume = min_gpu_chunk; - else if (!machine->omps().empty()) + } else if (!machine->omps().empty()) { eager_volume = min_omp_chunk; - else + } else { eager_volume = min_cpu_chunk; + } return Scalar(eager_volume); } default: break; @@ -82,8 +84,9 @@ std::vector CuNumericMapper::store_mappings( mappings.push_back(StoreMapping::default_mapping(inputs[0].data(), options.front())); mappings.push_back(StoreMapping::default_mapping(inputs[1].data(), options.front())); auto& input_mapping = mappings.back(); - for (uint32_t idx = 2; idx < inputs.size(); ++idx) + for (uint32_t idx = 2; idx < inputs.size(); ++idx) { input_mapping.add_store(inputs[idx].data()); + } return mappings; } case CUNUMERIC_FFT: { @@ -104,8 +107,9 @@ std::vector CuNumericMapper::store_mappings( StoreMapping::default_mapping(outputs[0].data(), options.front(), true /*exact*/)); mappings.back().policy().ordering.set_fortran_order(); return mappings; - } else + } else { return {}; + } } case CUNUMERIC_MATMUL: case CUNUMERIC_MATVECMUL: @@ -146,7 +150,9 @@ std::vector CuNumericMapper::store_mappings( return mappings; } case CUNUMERIC_TRILU: { - if (task.scalars().size() == 2) return {}; + if (task.scalars().size() == 2) { + return {}; + } // If we're here, this task was the post-processing for Cholesky. // So we will request fortran ordering std::vector mappings; diff --git a/src/cunumeric/matrix/contract_template.inl b/src/cunumeric/matrix/contract_template.inl index d84ab1bfdc..00cc5eb5f2 100644 --- a/src/cunumeric/matrix/contract_template.inl +++ b/src/cunumeric/matrix/contract_template.inl @@ -91,7 +91,9 @@ struct ContractImpl { args.lhs.reduce_accessor, true, DIM>(lhs_bloated_shape); T* lhs_data = lhs_acc.ptr(lhs_bloated_shape, lhs_bloated_strides); for (int i = 0; i < DIM; ++i) { - if (!args.lhs_dim_mask[i]) { continue; } + if (!args.lhs_dim_mask[i]) { + continue; + } lhs_shape.push_back(lhs_bloated_shape.hi[i] - lhs_bloated_shape.lo[i] + 1); lhs_strides.push_back(lhs_bloated_strides[i]); lhs_modes.push_back(i + 'a'); @@ -105,7 +107,9 @@ struct ContractImpl { AccessorRO rhs1_acc = args.rhs1.read_accessor(rhs1_bloated_shape); const T* rhs1_data = rhs1_acc.ptr(rhs1_bloated_shape, rhs1_bloated_strides); for (int i = 0; i < DIM; ++i) { - if (!args.rhs1_dim_mask[i]) { continue; } + if (!args.rhs1_dim_mask[i]) { + continue; + } rhs1_shape.push_back(rhs1_bloated_shape.hi[i] - rhs1_bloated_shape.lo[i] + 1); rhs1_strides.push_back(rhs1_bloated_strides[i]); rhs1_modes.push_back(i + 'a'); @@ -119,7 +123,9 @@ struct ContractImpl { AccessorRO rhs2_acc = args.rhs2.read_accessor(rhs2_bloated_shape); const T* rhs2_data = rhs2_acc.ptr(rhs2_bloated_shape, rhs2_bloated_strides); for (int i = 0; i < DIM; ++i) { - if (!args.rhs2_dim_mask[i]) { continue; } + if (!args.rhs2_dim_mask[i]) { + continue; + } rhs2_shape.push_back(rhs2_bloated_shape.hi[i] - rhs2_bloated_shape.lo[i] + 1); rhs2_strides.push_back(rhs2_bloated_strides[i]); rhs2_modes.push_back(i + 'a'); @@ -132,7 +138,9 @@ struct ContractImpl { Rect bloated_shape = lhs_bloated_shape.intersection(rhs1_bloated_shape).intersection(rhs2_bloated_shape); // cuTensor will not work correctly with empty domains, so check this here - if (bloated_shape.empty()) return; + if (bloated_shape.empty()) { + return; + } #if 0 // debugging output // Stagger the debugging output from different processors diff --git a/src/cunumeric/matrix/diag.cc b/src/cunumeric/matrix/diag.cc index c9ef6b3fa3..4d9051fda3 100644 --- a/src/cunumeric/matrix/diag.cc +++ b/src/cunumeric/matrix/diag.cc @@ -37,13 +37,17 @@ struct DiagImplBody { for (size_t i = 0; i < naxes; i++) { auto diff = 1 + m_shape.hi[DIM - i - 1] - m_shape.lo[DIM - i - 1]; - if (diff != 0) skip_size *= diff; + if (diff != 0) { + skip_size *= diff; + } } const size_t volume = m_shape.volume(); for (size_t idx = 0; idx < volume; idx += skip_size) { Point p = m_pitches.unflatten(idx, m_shape.lo); for (coord_t d = 0; d < distance; ++d) { - for (size_t i = DIM - naxes; i < DIM; i++) { p[i] = start + d; } + for (size_t i = DIM - naxes; i < DIM; i++) { + p[i] = start + d; + } auto v = in[p]; out.reduce(p, v); } diff --git a/src/cunumeric/matrix/diag.cu b/src/cunumeric/matrix/diag.cu index bbfba2882f..ff64f4ebb8 100644 --- a/src/cunumeric/matrix/diag.cu +++ b/src/cunumeric/matrix/diag.cu @@ -28,7 +28,9 @@ __global__ static void __launch_bounds__(THREADS_PER_BLOCK, MIN_CTAS_PER_SM) const Point<2> start) { const int idx = blockIdx.x * blockDim.x + threadIdx.x; - if (idx >= distance) return; + if (idx >= distance) { + return; + } Point<2> p(start[0] + idx, start[1] + idx); out[p] = in[p]; } @@ -46,7 +48,9 @@ __global__ static void __launch_bounds__(THREADS_PER_BLOCK, MIN_CTAS_PER_SM) const Rect m_shape) { const int idx = skip_size * (blockIdx.x * blockDim.x + threadIdx.x); - if (idx >= volume) return; + if (idx >= volume) { + return; + } auto in_p = m_pitches.unflatten(idx, m_shape.lo); auto out_p = in_p; @@ -76,7 +80,9 @@ struct DiagImplBody { for (int i = 0; i < naxes; i++) { auto diff = 1 + m_shape.hi[DIM - i - 1] - m_shape.lo[DIM - i - 1]; - if (diff != 0) skip_size *= diff; + if (diff != 0) { + skip_size *= diff; + } } const size_t volume = m_shape.volume(); diff --git a/src/cunumeric/matrix/diag_omp.cc b/src/cunumeric/matrix/diag_omp.cc index 5d4ddc7d46..874ba8f4d8 100644 --- a/src/cunumeric/matrix/diag_omp.cc +++ b/src/cunumeric/matrix/diag_omp.cc @@ -37,7 +37,9 @@ struct DiagImplBody { for (size_t i = 0; i < naxes; i++) { auto diff = 1 + m_shape.hi[DIM - i - 1] - m_shape.lo[DIM - i - 1]; - if (diff != 0) skip_size *= diff; + if (diff != 0) { + skip_size *= diff; + } } const size_t volume = m_shape.volume(); #pragma omp parallel for schedule(static) diff --git a/src/cunumeric/matrix/diag_template.inl b/src/cunumeric/matrix/diag_template.inl index 0c9053a122..bda7ce7059 100644 --- a/src/cunumeric/matrix/diag_template.inl +++ b/src/cunumeric/matrix/diag_template.inl @@ -38,7 +38,9 @@ struct DiagImpl { auto shape_in = args.matrix.shape(); Pitches pitches_in; size_t volume_in = pitches_in.flatten(shape_in); - if (volume_in == 0) return; + if (volume_in == 0) { + return; + } auto shape_out = args.diag.shape(); size_t diag_start_dim = DIM - args.naxes; coord_t start = shape_in.lo[diag_start_dim]; @@ -49,7 +51,9 @@ struct DiagImpl { end = std::min(end, shape_in.hi[i]); } coord_t distance = end - start + 1; - if (distance < 0) return; + if (distance < 0) { + return; + } auto in = args.matrix.read_accessor(shape_in); auto out = args.diag.reduce_accessor, true, DIM>(shape_out); @@ -67,7 +71,9 @@ struct DiagImpl { // y >= shape.lo[1] const Point<2> start2(shape.lo[1], shape.lo[1]); // See if our shape intersects with the diagonal - if (!shape.contains(start1) && !shape.contains(start2)) return; + if (!shape.contains(start1) && !shape.contains(start2)) { + return; + } // Pick whichever one fits in our rect const Point<2> start = shape.contains(start1) ? start1 : start2; diff --git a/src/cunumeric/matrix/dot.cu b/src/cunumeric/matrix/dot.cu index 57539c01a7..b5a88f4b56 100644 --- a/src/cunumeric/matrix/dot.cu +++ b/src/cunumeric/matrix/dot.cu @@ -66,9 +66,10 @@ struct DotImplBody { const size_t iters = (blocks + MAX_REDUCTION_CTAS - 1) / MAX_REDUCTION_CTAS; reduction_kernel<<>>( volume, result, rhs1, rhs2, rect.lo, iters, SumReduction::identity); - } else + } else { reduction_kernel<<>>( volume, result, rhs1, rhs2, rect.lo, 1, SumReduction::identity); + } copy_kernel<<<1, 1, 0, stream>>>(result, out); CHECK_CUDA_STREAM(stream); diff --git a/src/cunumeric/matrix/dot_omp.cc b/src/cunumeric/matrix/dot_omp.cc index 261e6666eb..409ace68b5 100644 --- a/src/cunumeric/matrix/dot_omp.cc +++ b/src/cunumeric/matrix/dot_omp.cc @@ -39,7 +39,9 @@ struct DotImplBody { const auto volume = rect.volume(); const auto max_threads = omp_get_max_threads(); ThreadLocalStorage locals(max_threads); - for (auto idx = 0; idx < max_threads; ++idx) locals[idx] = SumReduction::identity; + for (auto idx = 0; idx < max_threads; ++idx) { + locals[idx] = SumReduction::identity; + } if (dense) { auto rhs1ptr = rhs1.ptr(rect); @@ -65,7 +67,9 @@ struct DotImplBody { } } - for (auto idx = 0; idx < max_threads; ++idx) out.reduce(0, locals[idx]); + for (auto idx = 0; idx < max_threads; ++idx) { + out.reduce(0, locals[idx]); + } } }; diff --git a/src/cunumeric/matrix/dot_template.inl b/src/cunumeric/matrix/dot_template.inl index 8305ccd6be..5095663205 100644 --- a/src/cunumeric/matrix/dot_template.inl +++ b/src/cunumeric/matrix/dot_template.inl @@ -55,7 +55,9 @@ struct DotImpl { auto rhs1 = args.rhs1.read_accessor(rect); auto rhs2 = args.rhs2.read_accessor(rect); - if (rect.empty()) return; + if (rect.empty()) { + return; + } #if !LegateDefined(LEGATE_BOUNDS_CHECKS) // Check to see if this is dense or not diff --git a/src/cunumeric/matrix/gemm_template.inl b/src/cunumeric/matrix/gemm_template.inl index 9a717a9a46..4037134132 100644 --- a/src/cunumeric/matrix/gemm_template.inl +++ b/src/cunumeric/matrix/gemm_template.inl @@ -50,7 +50,9 @@ struct GemmImpl { auto rhs1_shape = rhs1_array.shape<2>(); auto rhs2_shape = rhs2_array.shape<2>(); - if (lhs_shape.empty()) return; + if (lhs_shape.empty()) { + return; + } size_t lhs_strides[2]; size_t rhs1_strides[2]; diff --git a/src/cunumeric/matrix/matmul_cpu.inl b/src/cunumeric/matrix/matmul_cpu.inl index 16e2860455..2a14ed1c34 100644 --- a/src/cunumeric/matrix/matmul_cpu.inl +++ b/src/cunumeric/matrix/matmul_cpu.inl @@ -110,15 +110,17 @@ struct MatMulImplBody { auto rhs1_copy = allocate_buffer(m * k); auto rhs2_copy = allocate_buffer(k * n); - if (rhs1_transposed) + if (rhs1_transposed) { half_matrix_to_float(rhs1_copy, rhs1, k, m, rhs1_stride); - else + } else { half_matrix_to_float(rhs1_copy, rhs1, m, k, rhs1_stride); + } - if (rhs2_transposed) + if (rhs2_transposed) { half_matrix_to_float(rhs2_copy, rhs2, n, k, rhs2_stride); - else + } else { half_matrix_to_float(rhs2_copy, rhs2, k, n, rhs2_stride); + } cblas_sgemm(CblasRowMajor, rhs1_transposed ? CblasTrans : CblasNoTrans, diff --git a/src/cunumeric/matrix/matmul_template.inl b/src/cunumeric/matrix/matmul_template.inl index 1f01dea9d0..52c4015ba9 100644 --- a/src/cunumeric/matrix/matmul_template.inl +++ b/src/cunumeric/matrix/matmul_template.inl @@ -66,7 +66,9 @@ struct MatMulImpl { // in their bloated domains. auto shape = args.rhs1.shape<3>().intersection(args.rhs2.shape<3>()); - if (shape.empty()) return; + if (shape.empty()) { + return; + } const auto m = shape.hi[0] - shape.lo[0] + 1; const auto k = shape.hi[1] - shape.lo[1] + 1; diff --git a/src/cunumeric/matrix/matvecmul.cu b/src/cunumeric/matrix/matvecmul.cu index b0e1992314..e6845f57be 100644 --- a/src/cunumeric/matrix/matvecmul.cu +++ b/src/cunumeric/matrix/matvecmul.cu @@ -48,10 +48,10 @@ struct MatVecMulImplBody { // cuBLAS version int32_t version; CHECK_CUBLAS(cublasGetVersion(cublas_handle, &version)); - if (version >= 11700) + if (version >= 11700) { CHECK_CUBLAS( cublasSgemv(cublas_handle, trans, n, m, &alpha, mat, mat_stride, vec, 1, &beta, lhs, 1)); - else + } else { CHECK_CUBLAS(cublasSgemmEx(cublas_handle, trans, CUBLAS_OP_N, @@ -69,6 +69,7 @@ struct MatVecMulImplBody { lhs, CUDA_R_32F, transpose_mat ? n : m)); + } CHECK_CUDA_STREAM(task_stream); } @@ -98,10 +99,10 @@ struct MatVecMulImplBody { // 64-bit flots as well. We're simply being conservative here. int32_t version; CHECK_CUBLAS(cublasGetVersion(cublas_handle, &version)); - if (version >= 11700) + if (version >= 11700) { CHECK_CUBLAS( cublasDgemv(cublas_handle, trans, n, m, &alpha, mat, mat_stride, vec, 1, &beta, lhs, 1)); - else + } else { CHECK_CUBLAS(cublasDgemm(cublas_handle, trans, CUBLAS_OP_N, @@ -116,6 +117,7 @@ struct MatVecMulImplBody { &beta, lhs, transpose_mat ? n : m)); + } CHECK_CUDA_STREAM(task_stream); } @@ -191,10 +193,10 @@ struct MatVecMulImplBody { // complex64 as well. We're simply being conservative here. int32_t version; CHECK_CUBLAS(cublasGetVersion(cublas_handle, &version)); - if (version >= 11700) + if (version >= 11700) { CHECK_CUBLAS( cublasCgemv(cublas_handle, trans, n, m, &alpha, mat, mat_stride, vec, 1, &beta, lhs, 1)); - else + } else { CHECK_CUBLAS(cublasCgemmEx(cublas_handle, trans, CUBLAS_OP_N, @@ -212,6 +214,7 @@ struct MatVecMulImplBody { lhs, CUDA_C_32F, transpose_mat ? n : m)); + } CHECK_CUDA_STREAM(task_stream); } @@ -245,10 +248,10 @@ struct MatVecMulImplBody { // complex128 as well. We're simply being conservative here. int32_t version; CHECK_CUBLAS(cublasGetVersion(cublas_handle, &version)); - if (version >= 11700) + if (version >= 11700) { CHECK_CUBLAS( cublasZgemv(cublas_handle, trans, n, m, &alpha, mat, mat_stride, vec, 1, &beta, lhs, 1)); - else + } else { CHECK_CUBLAS(cublasZgemm(cublas_handle, trans, CUBLAS_OP_N, @@ -263,6 +266,7 @@ struct MatVecMulImplBody { &beta, lhs, transpose_mat ? n : m)); + } CHECK_CUDA_STREAM(task_stream); } diff --git a/src/cunumeric/matrix/matvecmul_template.inl b/src/cunumeric/matrix/matvecmul_template.inl index 29d82d3d8a..1190b66215 100644 --- a/src/cunumeric/matrix/matvecmul_template.inl +++ b/src/cunumeric/matrix/matvecmul_template.inl @@ -60,7 +60,9 @@ struct MatVecMulImpl { auto shape = args.rhs1.shape<2>().intersection(args.rhs2.shape<2>()); - if (shape.empty()) return; + if (shape.empty()) { + return; + } auto m = static_cast(shape.hi[0] - shape.lo[0] + 1); auto n = static_cast(shape.hi[1] - shape.lo[1] + 1); @@ -72,7 +74,9 @@ struct MatVecMulImpl { bool transpose_mat; size_t mat_stride = stride_for_blas(m, n, mat_strides[0], mat_strides[1], transpose_mat); - if (transpose_mat) std::swap(m, n); + if (transpose_mat) { + std::swap(m, n); + } size_t lhs_strides[2]; auto lhs = args.lhs.reduce_accessor, true, 2>().ptr(shape, lhs_strides); diff --git a/src/cunumeric/matrix/potrf.cc b/src/cunumeric/matrix/potrf.cc index 0e1742561c..b8fb0757bc 100644 --- a/src/cunumeric/matrix/potrf.cc +++ b/src/cunumeric/matrix/potrf.cc @@ -31,7 +31,9 @@ struct PotrfImplBody { char uplo = 'L'; int32_t info = 0; LAPACK_spotrf(&uplo, &n, array, &m, &info); - if (info != 0) throw legate::TaskException("Matrix is not positive definite"); + if (info != 0) { + throw legate::TaskException("Matrix is not positive definite"); + } } }; @@ -42,7 +44,9 @@ struct PotrfImplBody { char uplo = 'L'; int32_t info = 0; LAPACK_dpotrf(&uplo, &n, array, &m, &info); - if (info != 0) throw legate::TaskException("Matrix is not positive definite"); + if (info != 0) { + throw legate::TaskException("Matrix is not positive definite"); + } } }; @@ -53,7 +57,9 @@ struct PotrfImplBody { char uplo = 'L'; int32_t info = 0; LAPACK_cpotrf(&uplo, &n, reinterpret_cast<__complex__ float*>(array), &m, &info); - if (info != 0) throw legate::TaskException("Matrix is not positive definite"); + if (info != 0) { + throw legate::TaskException("Matrix is not positive definite"); + } } }; @@ -64,7 +70,9 @@ struct PotrfImplBody { char uplo = 'L'; int32_t info = 0; LAPACK_zpotrf(&uplo, &n, reinterpret_cast<__complex__ double*>(array), &m, &info); - if (info != 0) throw legate::TaskException("Matrix is not positive definite"); + if (info != 0) { + throw legate::TaskException("Matrix is not positive definite"); + } } }; diff --git a/src/cunumeric/matrix/potrf.cu b/src/cunumeric/matrix/potrf.cu index 9d027f0e80..45fcca02bd 100644 --- a/src/cunumeric/matrix/potrf.cu +++ b/src/cunumeric/matrix/potrf.cu @@ -45,7 +45,9 @@ static inline void potrf_template( CHECK_CUDA(cudaStreamSynchronize(stream)); CHECK_CUDA_STREAM(stream); - if (info[0] != 0) throw legate::TaskException("Matrix is not positive definite"); + if (info[0] != 0) { + throw legate::TaskException("Matrix is not positive definite"); + } } template <> diff --git a/src/cunumeric/matrix/potrf_omp.cc b/src/cunumeric/matrix/potrf_omp.cc index b495baf9e8..cfb209c144 100644 --- a/src/cunumeric/matrix/potrf_omp.cc +++ b/src/cunumeric/matrix/potrf_omp.cc @@ -32,7 +32,9 @@ struct PotrfImplBody { char uplo = 'L'; int32_t info = 0; LAPACK_spotrf(&uplo, &n, array, &m, &info); - if (info != 0) throw legate::TaskException("Matrix is not positive definite"); + if (info != 0) { + throw legate::TaskException("Matrix is not positive definite"); + } } }; @@ -43,7 +45,9 @@ struct PotrfImplBody { char uplo = 'L'; int32_t info = 0; LAPACK_dpotrf(&uplo, &n, array, &m, &info); - if (info != 0) throw legate::TaskException("Matrix is not positive definite"); + if (info != 0) { + throw legate::TaskException("Matrix is not positive definite"); + } } }; @@ -54,7 +58,9 @@ struct PotrfImplBody { char uplo = 'L'; int32_t info = 0; LAPACK_cpotrf(&uplo, &n, reinterpret_cast<__complex__ float*>(array), &m, &info); - if (info != 0) throw legate::TaskException("Matrix is not positive definite"); + if (info != 0) { + throw legate::TaskException("Matrix is not positive definite"); + } } }; @@ -65,7 +71,9 @@ struct PotrfImplBody { char uplo = 'L'; int32_t info = 0; LAPACK_zpotrf(&uplo, &n, reinterpret_cast<__complex__ double*>(array), &m, &info); - if (info != 0) throw legate::TaskException("Matrix is not positive definite"); + if (info != 0) { + throw legate::TaskException("Matrix is not positive definite"); + } } }; diff --git a/src/cunumeric/matrix/potrf_template.inl b/src/cunumeric/matrix/potrf_template.inl index 68af3ed118..86de641a14 100644 --- a/src/cunumeric/matrix/potrf_template.inl +++ b/src/cunumeric/matrix/potrf_template.inl @@ -46,7 +46,9 @@ struct PotrfImpl { auto shape = array.shape<2>(); - if (shape.empty()) return; + if (shape.empty()) { + return; + } size_t strides[2]; diff --git a/src/cunumeric/matrix/solve.cu b/src/cunumeric/matrix/solve.cu index 576c91cef1..6c5492fa64 100644 --- a/src/cunumeric/matrix/solve.cu +++ b/src/cunumeric/matrix/solve.cu @@ -49,7 +49,9 @@ static inline void solve_template(GetrfBufferSize getrf_buffer_size, CHECK_CUSOLVER(getrf(handle, m, n, a, m, buffer.ptr(0), ipiv.ptr(0), info.ptr(0))); CHECK_CUDA(cudaStreamSynchronize(stream)); - if (info[0] != 0) throw legate::TaskException(SolveTask::ERROR_MESSAGE); + if (info[0] != 0) { + throw legate::TaskException(SolveTask::ERROR_MESSAGE); + } CHECK_CUSOLVER(getrs(handle, trans, n, nrhs, a, m, ipiv.ptr(0), b, n, info.ptr(0))); diff --git a/src/cunumeric/matrix/solve_cpu.inl b/src/cunumeric/matrix/solve_cpu.inl index 7275a2c0e9..efc36a5da1 100644 --- a/src/cunumeric/matrix/solve_cpu.inl +++ b/src/cunumeric/matrix/solve_cpu.inl @@ -32,7 +32,9 @@ struct SolveImplBody { int32_t info = 0; LAPACK_sgesv(&n, &nrhs, a, &m, ipiv.ptr(0), b, &n, &info); - if (info != 0) throw legate::TaskException(SolveTask::ERROR_MESSAGE); + if (info != 0) { + throw legate::TaskException(SolveTask::ERROR_MESSAGE); + } } }; @@ -45,7 +47,9 @@ struct SolveImplBody { int32_t info = 0; LAPACK_dgesv(&n, &nrhs, a, &m, ipiv.ptr(0), b, &n, &info); - if (info != 0) throw legate::TaskException(SolveTask::ERROR_MESSAGE); + if (info != 0) { + throw legate::TaskException(SolveTask::ERROR_MESSAGE); + } } }; @@ -61,7 +65,9 @@ struct SolveImplBody { int32_t info = 0; LAPACK_cgesv(&n, &nrhs, a, &m, ipiv.ptr(0), b, &n, &info); - if (info != 0) throw legate::TaskException(SolveTask::ERROR_MESSAGE); + if (info != 0) { + throw legate::TaskException(SolveTask::ERROR_MESSAGE); + } } }; @@ -77,7 +83,9 @@ struct SolveImplBody { int32_t info = 0; LAPACK_zgesv(&n, &nrhs, a, &m, ipiv.ptr(0), b, &n, &info); - if (info != 0) throw legate::TaskException(SolveTask::ERROR_MESSAGE); + if (info != 0) { + throw legate::TaskException(SolveTask::ERROR_MESSAGE); + } } }; diff --git a/src/cunumeric/matrix/syrk_template.inl b/src/cunumeric/matrix/syrk_template.inl index 960a283eb2..546951bf61 100644 --- a/src/cunumeric/matrix/syrk_template.inl +++ b/src/cunumeric/matrix/syrk_template.inl @@ -47,7 +47,9 @@ struct SyrkImpl { auto lhs_shape = lhs_array.shape<2>(); auto rhs_shape = rhs_array.shape<2>(); - if (lhs_shape.empty()) return; + if (lhs_shape.empty()) { + return; + } size_t lhs_strides[2]; size_t rhs_strides[2]; diff --git a/src/cunumeric/matrix/tile.cu b/src/cunumeric/matrix/tile.cu index b06d7a4d76..cc2fd4ded0 100644 --- a/src/cunumeric/matrix/tile.cu +++ b/src/cunumeric/matrix/tile.cu @@ -31,7 +31,9 @@ __global__ static void __launch_bounds__(THREADS_PER_BLOCK, MIN_CTAS_PER_SM) const AccessorRO in) { const size_t out_idx = blockIdx.x * blockDim.x + threadIdx.x; - if (out_idx >= out_volume) return; + if (out_idx >= out_volume) { + return; + } const auto out_point = out_pitches.unflatten(out_idx, out_rect.lo); const auto in_point = get_tile_point(out_point, in_strides); diff --git a/src/cunumeric/matrix/tile_template.inl b/src/cunumeric/matrix/tile_template.inl index 8985ba33ae..819a596941 100644 --- a/src/cunumeric/matrix/tile_template.inl +++ b/src/cunumeric/matrix/tile_template.inl @@ -29,8 +29,9 @@ __CUDA_HD__ inline Point get_tile_point(const Point& point, const Point& strides) { Point result; - for (int32_t out_idx = OUT_DIM - 1, in_idx = IN_DIM - 1; in_idx >= 0; --out_idx, --in_idx) + for (int32_t out_idx = OUT_DIM - 1, in_idx = IN_DIM - 1; in_idx >= 0; --out_idx, --in_idx) { result[in_idx] = point[out_idx] % strides[in_idx]; + } return result; } @@ -46,7 +47,9 @@ struct TileImpl { Pitches out_pitches; auto out_volume = out_pitches.flatten(out_rect); - if (out_volume == 0) return; + if (out_volume == 0) { + return; + } const auto in_rect = args.in.shape(); Point in_strides = in_rect.hi + Point::ONES(); diff --git a/src/cunumeric/matrix/transpose.cc b/src/cunumeric/matrix/transpose.cc index 7749ec32e9..45e682c271 100644 --- a/src/cunumeric/matrix/transpose.cc +++ b/src/cunumeric/matrix/transpose.cc @@ -37,24 +37,31 @@ struct TransposeImplBody { bool logical) const { constexpr coord_t BF = 128 / sizeof(VAL); - if (logical) + if (logical) { for (auto i1 = in_rect.lo[0]; i1 <= in_rect.hi[0]; i1 += BF) { for (auto j1 = in_rect.lo[1]; j1 <= in_rect.hi[1]; j1 += BF) { const auto max_i2 = ((i1 + BF) <= in_rect.hi[0]) ? i1 + BF : in_rect.hi[0]; const auto max_j2 = ((j1 + BF) <= in_rect.hi[1]) ? j1 + BF : in_rect.hi[1]; - for (auto i2 = i1; i2 <= max_i2; i2++) - for (auto j2 = j1; j2 <= max_j2; j2++) out[j2][i2] = in[i2][j2]; + for (auto i2 = i1; i2 <= max_i2; i2++) { + for (auto j2 = j1; j2 <= max_j2; j2++) { + out[j2][i2] = in[i2][j2]; + } + } } } - else + } else { for (auto i1 = in_rect.lo[0]; i1 <= in_rect.hi[0]; i1 += BF) { for (auto j1 = in_rect.lo[1]; j1 <= in_rect.hi[1]; j1 += BF) { const auto max_i2 = ((i1 + BF) <= in_rect.hi[0]) ? i1 + BF : in_rect.hi[0]; const auto max_j2 = ((j1 + BF) <= in_rect.hi[1]) ? j1 + BF : in_rect.hi[1]; - for (auto i2 = i1; i2 <= max_i2; i2++) - for (auto j2 = j1; j2 <= max_j2; j2++) out[i2][j2] = in[i2][j2]; + for (auto i2 = i1; i2 <= max_i2; i2++) { + for (auto j2 = j1; j2 <= max_j2; j2++) { + out[i2][j2] = in[i2][j2]; + } + } } } + } } }; diff --git a/src/cunumeric/matrix/transpose.cu b/src/cunumeric/matrix/transpose.cu index d8a71a3de0..accbf23827 100644 --- a/src/cunumeric/matrix/transpose.cu +++ b/src/cunumeric/matrix/transpose.cu @@ -45,14 +45,17 @@ __global__ static void __launch_bounds__((TILE_DIM * BLOCK_ROWS), MIN_CTAS_PER_S if ((lo_in[0] + (blockIdx.y + 1) * TILE_DIM - 1) <= hi_in[0]) { // No overflow case #pragma unroll - for (int i = 0; i < TILE_DIM; i += BLOCK_ROWS) + for (int i = 0; i < TILE_DIM; i += BLOCK_ROWS) { tile[threadIdx.y + i][threadIdx.x] = in[lo_in + Point<2>(x + i, y)]; + } } else { // Overflow case #pragma unroll - for (int i = 0; i < TILE_DIM; i += BLOCK_ROWS) - if ((lo_in[0] + x + i) <= hi_in[0]) + for (int i = 0; i < TILE_DIM; i += BLOCK_ROWS) { + if ((lo_in[0] + x + i) <= hi_in[0]) { tile[threadIdx.y + i][threadIdx.x] = in[lo_in + Point<2>(x + i, y)]; + } + } } } // Make sure all the data is in shared memory @@ -68,14 +71,17 @@ __global__ static void __launch_bounds__((TILE_DIM * BLOCK_ROWS), MIN_CTAS_PER_S if ((lo_out[0] + (blockIdx.x + 1) * TILE_DIM - 1) <= hi_out[0]) { // No overflow case #pragma unroll - for (int i = 0; i < TILE_DIM; i += BLOCK_ROWS) + for (int i = 0; i < TILE_DIM; i += BLOCK_ROWS) { out[lo_out + Point<2>(x + i, y)] = tile[threadIdx.x][threadIdx.y + i]; + } } else { // Overflow case #pragma unroll - for (int i = 0; i < TILE_DIM; i += BLOCK_ROWS) - if ((lo_out[0] + x + i) <= hi_out[0]) + for (int i = 0; i < TILE_DIM; i += BLOCK_ROWS) { + if ((lo_out[0] + x + i) <= hi_out[0]) { out[lo_out + Point<2>(x + i, y)] = tile[threadIdx.x][threadIdx.y + i]; + } + } } } } @@ -101,14 +107,17 @@ __global__ static void __launch_bounds__((TILE_DIM * BLOCK_ROWS), MIN_CTAS_PER_S if ((lo_in[0] + (blockIdx.y + 1) * TILE_DIM - 1) <= hi_in[0]) { // No overflow case #pragma unroll - for (int i = 0; i < TILE_DIM; i += BLOCK_ROWS) + for (int i = 0; i < TILE_DIM; i += BLOCK_ROWS) { tile[threadIdx.y + i][threadIdx.x] = in[lo_in + Point<2>(x + i, y)]; + } } else { // Overflow case #pragma unroll - for (int i = 0; i < TILE_DIM; i += BLOCK_ROWS) - if ((lo_in[0] + x + i) <= hi_in[0]) + for (int i = 0; i < TILE_DIM; i += BLOCK_ROWS) { + if ((lo_in[0] + x + i) <= hi_in[0]) { tile[threadIdx.y + i][threadIdx.x] = in[lo_in + Point<2>(x + i, y)]; + } + } } } @@ -124,14 +133,17 @@ __global__ static void __launch_bounds__((TILE_DIM * BLOCK_ROWS), MIN_CTAS_PER_S if ((lo_out[1] + (blockIdx.x + 1) * TILE_DIM - 1) <= hi_out[1]) { // No overflow case #pragma unroll - for (int i = 0; i < TILE_DIM; i += BLOCK_ROWS) + for (int i = 0; i < TILE_DIM; i += BLOCK_ROWS) { out[lo_out + Point<2>(x, y + i)] = tile[threadIdx.x][threadIdx.y + i]; + } } else { // Overflow case #pragma unroll - for (int i = 0; i < TILE_DIM; i += BLOCK_ROWS) - if ((lo_out[1] + y + i) <= hi_out[1]) + for (int i = 0; i < TILE_DIM; i += BLOCK_ROWS) { + if ((lo_out[1] + y + i) <= hi_out[1]) { out[lo_out + Point<2>(x, y + i)] = tile[threadIdx.x][threadIdx.y + i]; + } + } } } } @@ -152,12 +164,13 @@ struct TransposeImplBody { const dim3 threads(TILE_DIM, BLOCK_ROWS, 1); auto stream = get_cached_stream(); - if (logical) + if (logical) { transpose_2d_logical <<>>(out, in, in_rect.lo, in_rect.hi, out_rect.lo, out_rect.hi); - else + } else { transpose_2d_physical <<>>(out, in, in_rect.lo, in_rect.hi, out_rect.lo, out_rect.hi); + } CHECK_CUDA_STREAM(stream); } }; diff --git a/src/cunumeric/matrix/transpose_omp.cc b/src/cunumeric/matrix/transpose_omp.cc index 6e604ad401..855ac869e6 100644 --- a/src/cunumeric/matrix/transpose_omp.cc +++ b/src/cunumeric/matrix/transpose_omp.cc @@ -41,8 +41,11 @@ struct TransposeImplBody { for (auto j1 = in_rect.lo[1]; j1 <= in_rect.hi[1]; j1 += BF) { const auto max_i2 = ((i1 + BF) <= in_rect.hi[0]) ? i1 + BF : in_rect.hi[0]; const auto max_j2 = ((j1 + BF) <= in_rect.hi[1]) ? j1 + BF : in_rect.hi[1]; - for (auto i2 = i1; i2 <= max_i2; i2++) - for (auto j2 = j1; j2 <= max_j2; j2++) out[j2][i2] = in[i2][j2]; + for (auto i2 = i1; i2 <= max_i2; i2++) { + for (auto j2 = j1; j2 <= max_j2; j2++) { + out[j2][i2] = in[i2][j2]; + } + } } } else @@ -51,8 +54,11 @@ struct TransposeImplBody { for (auto j1 = in_rect.lo[1]; j1 <= in_rect.hi[1]; j1 += BF) { const auto max_i2 = ((i1 + BF) <= in_rect.hi[0]) ? i1 + BF : in_rect.hi[0]; const auto max_j2 = ((j1 + BF) <= in_rect.hi[1]) ? j1 + BF : in_rect.hi[1]; - for (auto i2 = i1; i2 <= max_i2; i2++) - for (auto j2 = j1; j2 <= max_j2; j2++) out[i2][j2] = in[i2][j2]; + for (auto i2 = i1; i2 <= max_i2; i2++) { + for (auto j2 = j1; j2 <= max_j2; j2++) { + out[i2][j2] = in[i2][j2]; + } + } } } } diff --git a/src/cunumeric/matrix/transpose_template.inl b/src/cunumeric/matrix/transpose_template.inl index 6aa94bb260..c546ebff74 100644 --- a/src/cunumeric/matrix/transpose_template.inl +++ b/src/cunumeric/matrix/transpose_template.inl @@ -34,14 +34,17 @@ struct TransposeImpl { using VAL = type_of; const auto out_rect = args.out.shape<2>(); - if (out_rect.empty()) return; + if (out_rect.empty()) { + return; + } Rect<2> in_rect; if (args.logical) { in_rect.lo = Point<2>(out_rect.lo[1], out_rect.lo[0]); in_rect.hi = Point<2>(out_rect.hi[1], out_rect.hi[0]); - } else + } else { in_rect = out_rect; + } auto out = args.out.write_accessor(); auto in = args.in.read_accessor(); diff --git a/src/cunumeric/matrix/trilu.cc b/src/cunumeric/matrix/trilu.cc index 55019bf9f4..c1b1f83bf8 100644 --- a/src/cunumeric/matrix/trilu.cc +++ b/src/cunumeric/matrix/trilu.cc @@ -33,22 +33,25 @@ struct TriluImplBody { size_t volume, int32_t k) const { - if (LOWER) + if (LOWER) { for (size_t idx = 0; idx < volume; ++idx) { auto p = pitches.unflatten(idx, lo); - if (p[DIM - 2] + k >= p[DIM - 1]) + if (p[DIM - 2] + k >= p[DIM - 1]) { out[p] = in[p]; - else + } else { out[p] = 0; + } } - else + } else { for (size_t idx = 0; idx < volume; ++idx) { auto p = pitches.unflatten(idx, lo); - if (p[DIM - 2] + k <= p[DIM - 1]) + if (p[DIM - 2] + k <= p[DIM - 1]) { out[p] = in[p]; - else + } else { out[p] = 0; + } } + } } }; diff --git a/src/cunumeric/matrix/trilu.cu b/src/cunumeric/matrix/trilu.cu index 258e7ce195..9c2e9c5180 100644 --- a/src/cunumeric/matrix/trilu.cu +++ b/src/cunumeric/matrix/trilu.cu @@ -33,20 +33,24 @@ static __global__ void __launch_bounds__(THREADS_PER_BLOCK, MIN_CTAS_PER_SM) int32_t k) { const size_t idx = global_tid_1d(); - if (idx >= volume) return; + if (idx >= volume) { + return; + } if (LOWER) { auto p = pitches.unflatten(idx, lo); - if (p[DIM - 2] + k >= p[DIM - 1]) + if (p[DIM - 2] + k >= p[DIM - 1]) { out[p] = in[p]; - else + } else { out[p] = 0; + } } else { auto p = pitches.unflatten(idx, lo); - if (p[DIM - 2] + k <= p[DIM - 1]) + if (p[DIM - 2] + k <= p[DIM - 1]) { out[p] = in[p]; - else + } else { out[p] = 0; + } } } diff --git a/src/cunumeric/matrix/trilu_omp.cc b/src/cunumeric/matrix/trilu_omp.cc index 14543d5531..ef4cf3849b 100644 --- a/src/cunumeric/matrix/trilu_omp.cc +++ b/src/cunumeric/matrix/trilu_omp.cc @@ -37,19 +37,21 @@ struct TriluImplBody { #pragma omp parallel for schedule(static) for (size_t idx = 0; idx < volume; ++idx) { auto p = pitches.unflatten(idx, lo); - if (p[DIM - 2] + k >= p[DIM - 1]) + if (p[DIM - 2] + k >= p[DIM - 1]) { out[p] = in[p]; - else + } else { out[p] = 0; + } } else #pragma omp parallel for schedule(static) for (size_t idx = 0; idx < volume; ++idx) { auto p = pitches.unflatten(idx, lo); - if (p[DIM - 2] + k <= p[DIM - 1]) + if (p[DIM - 2] + k <= p[DIM - 1]) { out[p] = in[p]; - else + } else { out[p] = 0; + } } } }; diff --git a/src/cunumeric/matrix/trilu_template.inl b/src/cunumeric/matrix/trilu_template.inl index b633ed7583..c1df6fa423 100644 --- a/src/cunumeric/matrix/trilu_template.inl +++ b/src/cunumeric/matrix/trilu_template.inl @@ -35,7 +35,9 @@ struct TriluImpl { using VAL = type_of; auto shape = args.output.shape(); - if (shape.empty()) return; + if (shape.empty()) { + return; + } auto out = args.output.write_accessor(shape); auto in = args.input.read_accessor(shape); @@ -44,18 +46,20 @@ struct TriluImpl { Pitches pitches; size_t volume = pitches.flatten(shape); - if (args.lower) + if (args.lower) { TriluImplBody()(out, in, pitches, shape.lo, volume, args.k); - else + } else { TriluImplBody()(out, in, pitches, shape.lo, volume, args.k); + } } else { Pitches pitches; size_t volume = pitches.flatten(shape); - if (args.lower) + if (args.lower) { TriluImplBody()(out, in, pitches, shape.lo, volume, args.k); - else + } else { TriluImplBody()(out, in, pitches, shape.lo, volume, args.k); + } } } diff --git a/src/cunumeric/matrix/trsm_template.inl b/src/cunumeric/matrix/trsm_template.inl index fb76e3624a..d0b3ee9bf8 100644 --- a/src/cunumeric/matrix/trsm_template.inl +++ b/src/cunumeric/matrix/trsm_template.inl @@ -47,7 +47,9 @@ struct TrsmImpl { auto lhs_shape = lhs_array.shape<2>(); auto rhs_shape = rhs_array.shape<2>(); - if (lhs_shape.empty()) return; + if (lhs_shape.empty()) { + return; + } size_t lhs_strides[2]; size_t rhs_strides[2]; diff --git a/src/cunumeric/matrix/util.cc b/src/cunumeric/matrix/util.cc index 368e2c820a..31e4c99613 100644 --- a/src/cunumeric/matrix/util.cc +++ b/src/cunumeric/matrix/util.cc @@ -66,7 +66,9 @@ int64_t calculate_volume(size_t ndim, const int64_t* shape, int64_t* strides) { int64_t volume = 1; for (int d = ndim - 1; d >= 0; --d) { - if (strides != nullptr) { strides[d] = volume; } + if (strides != nullptr) { + strides[d] = volume; + } volume *= shape[d]; } return volume; @@ -83,11 +85,15 @@ void half_vector_to_float(float* out, const __half* ptr, size_t n) #if LegateDefined(LEGATE_USE_OPENMP) if (legate::Processor::get_executing_processor().kind() == legate::Processor::OMP_PROC) { #pragma omp parallel for schedule(static) - for (size_t idx = 0; idx < n; idx++) out[idx] = ptr[idx]; + for (size_t idx = 0; idx < n; idx++) { + out[idx] = ptr[idx]; + } return; } #endif - for (size_t idx = 0; idx < n; idx++) out[idx] = ptr[idx]; + for (size_t idx = 0; idx < n; idx++) { + out[idx] = ptr[idx]; + } } void half_matrix_to_float(float* out, const __half* ptr, size_t m, size_t n, size_t pitch) @@ -95,13 +101,19 @@ void half_matrix_to_float(float* out, const __half* ptr, size_t m, size_t n, siz #if LegateDefined(LEGATE_USE_OPENMP) if (legate::Processor::get_executing_processor().kind() == legate::Processor::OMP_PROC) { #pragma omp parallel for schedule(static) - for (size_t i = 0; i < m; i++) - for (size_t j = 0; j < n; j++) out[i * n + j] = ptr[i * pitch + j]; + for (size_t i = 0; i < m; i++) { + for (size_t j = 0; j < n; j++) { + out[i * n + j] = ptr[i * pitch + j]; + } + } return; } #endif - for (size_t i = 0; i < m; i++) - for (size_t j = 0; j < n; j++) out[i * n + j] = ptr[i * pitch + j]; + for (size_t i = 0; i < m; i++) { + for (size_t j = 0; j < n; j++) { + out[i * n + j] = ptr[i * pitch + j]; + } + } } void half_tensor_to_float( diff --git a/src/cunumeric/matrix/util.h b/src/cunumeric/matrix/util.h index 954c65767b..68b5c16851 100644 --- a/src/cunumeric/matrix/util.h +++ b/src/cunumeric/matrix/util.h @@ -33,7 +33,9 @@ inline int64_t unflatten_with_strides(int64_t flat_idx, flat_idx /= shape[d]; } int64_t idx = 0; - for (size_t d = 0; d < ndim; ++d) { idx += coords[d] * strides[d]; } + for (size_t d = 0; d < ndim; ++d) { + idx += coords[d] * strides[d]; + } return idx; } diff --git a/src/cunumeric/ndarray.cc b/src/cunumeric/ndarray.cc index c3a71c9a94..d829fb7846 100644 --- a/src/cunumeric/ndarray.cc +++ b/src/cunumeric/ndarray.cc @@ -128,7 +128,9 @@ void NDArray::assign(const legate::Scalar& other) void NDArray::random(int32_t gen_code) { - if (size() == 0) return; + if (size() == 0) { + return; + } auto runtime = CuNumericRuntime::get_runtime(); @@ -145,7 +147,9 @@ void NDArray::random(int32_t gen_code) void NDArray::fill(const Scalar& value, bool argval) { - if (size() == 0) return; + if (size() == 0) { + return; + } auto runtime = CuNumericRuntime::get_runtime(); @@ -162,7 +166,9 @@ void NDArray::fill(const Scalar& value, bool argval) void NDArray::eye(int32_t k) { - if (size() == 0) return; + if (size() == 0) { + return; + } assert(dim() == 2); @@ -182,13 +188,17 @@ void NDArray::eye(int32_t k) void NDArray::bincount(NDArray rhs, std::optional weights /*=std::nullopt*/) { - if (size() == 0) return; + if (size() == 0) { + return; + } assert(dim() == 1); auto runtime = CuNumericRuntime::get_runtime(); - if (weights.has_value()) { assert(rhs.shape() == weights.value().shape()); } + if (weights.has_value()) { + assert(rhs.shape() == weights.value().shape()); + } auto zero = legate::type_dispatch(type().code(), generate_zero_fn{}); fill(zero, false); @@ -224,16 +234,19 @@ void NDArray::sort_task(NDArray rhs, bool argsort, bool stable) task.add_constraint(align(p_lhs, p_rhs)); } - if (machine.count(legate::mapping::TaskTarget::GPU) > 0) + if (machine.count(legate::mapping::TaskTarget::GPU) > 0) { task.add_communicator("nccl"); - else + } else { task.add_communicator("cpu"); + } task.add_scalar_arg(legate::Scalar(argsort)); task.add_scalar_arg(legate::Scalar(rhs.shape())); task.add_scalar_arg(legate::Scalar(stable)); runtime->submit(std::move(task)); - if (uses_unbound_output) store_ = unbound.value().get_store(); + if (uses_unbound_output) { + store_ = unbound.value().get_store(); + } } void NDArray::sort_swapped(NDArray rhs, bool argsort, int32_t sort_axis, bool stable) @@ -262,7 +275,9 @@ void NDArray::sort(NDArray rhs, bool argsort, std::optional axis, bool assert(false); } int32_t computed_axis = 0; - if (axis.has_value()) computed_axis = normalize_axis_index(axis.value(), rhs.dim()); + if (axis.has_value()) { + computed_axis = normalize_axis_index(axis.value(), rhs.dim()); + } if (computed_axis == rhs.dim() - 1) { sort_task(rhs, argsort, stable); @@ -276,11 +291,13 @@ void NDArray::sort(NDArray rhs, std::optional axis /*=-1*/, std::string kind /*="quicksort"*/) { - if (axis.has_value() && (axis >= rhs.dim() || axis < -rhs.dim())) + if (axis.has_value() && (axis >= rhs.dim() || axis < -rhs.dim())) { throw std::invalid_argument("invalid axis"); + } - if (!(kind == "quicksort" || kind == "mergesort" || kind == "heapsort" || kind == "stable")) + if (!(kind == "quicksort" || kind == "mergesort" || kind == "heapsort" || kind == "stable")) { throw std::invalid_argument("invalid kind"); + } bool stable = (kind == "stable"); sort(rhs, argsort, axis, stable); @@ -288,7 +305,9 @@ void NDArray::sort(NDArray rhs, void NDArray::trilu(NDArray rhs, int32_t k, bool lower) { - if (size() == 0) return; + if (size() == 0) { + return; + } auto runtime = CuNumericRuntime::get_runtime(); @@ -310,9 +329,13 @@ void NDArray::trilu(NDArray rhs, int32_t k, bool lower) void NDArray::binary_op(int32_t op_code, NDArray rhs1, NDArray rhs2) { - if (rhs1.type() != rhs2.type()) throw std::invalid_argument("Operands must have the same type"); + if (rhs1.type() != rhs2.type()) { + throw std::invalid_argument("Operands must have the same type"); + } - if (size() == 0) return; + if (size() == 0) { + return; + } auto runtime = CuNumericRuntime::get_runtime(); @@ -335,7 +358,9 @@ void NDArray::binary_op(int32_t op_code, NDArray rhs1, NDArray rhs2) void NDArray::binary_reduction(int32_t op_code, NDArray rhs1, NDArray rhs2) { - if (size() == 0) return; + if (size() == 0) { + return; + } auto runtime = CuNumericRuntime::get_runtime(); @@ -364,7 +389,9 @@ void NDArray::binary_reduction(int32_t op_code, NDArray rhs1, NDArray rhs2) void NDArray::unary_op(int32_t op_code, NDArray input) { - if (size() == 0) return; + if (size() == 0) { + return; + } auto runtime = CuNumericRuntime::get_runtime(); @@ -383,7 +410,9 @@ void NDArray::unary_op(int32_t op_code, NDArray input) void NDArray::unary_reduction(int32_t op_code_, NDArray input) { - if (size() == 0) return; + if (size() == 0) { + return; + } auto runtime = CuNumericRuntime::get_runtime(); @@ -406,7 +435,9 @@ void NDArray::unary_reduction(int32_t op_code_, NDArray input) void NDArray::dot(NDArray rhs1, NDArray rhs2) { - if (size() == 0) return; + if (size() == 0) { + return; + } auto runtime = CuNumericRuntime::get_runtime(); @@ -439,7 +470,9 @@ void NDArray::dot(NDArray rhs1, NDArray rhs2) void NDArray::arange(double start, double stop, double step) { - if (size() == 0) return; + if (size() == 0) { + return; + } auto runtime = CuNumericRuntime::get_runtime(); @@ -463,11 +496,15 @@ std::vector NDArray::nonzero() std::vector outputs; auto ndim = dim(); - for (int32_t i = 0; i < ndim; ++i) outputs.emplace_back(runtime->create_array(legate::int64())); + for (int32_t i = 0; i < ndim; ++i) { + outputs.emplace_back(runtime->create_array(legate::int64())); + } auto task = runtime->create_task(CuNumericOpCode::CUNUMERIC_NONZERO); - for (auto& output : outputs) { task.add_output(output.store_); } + for (auto& output : outputs) { + task.add_output(output.store_); + } auto p_rhs = task.add_input(store_); task.add_constraint(legate::broadcast(p_rhs, legate::from_range(1, ndim))); @@ -491,8 +528,9 @@ NDArray NDArray::unique() task.add_output(result.store_, part_out); task.add_input(store_, part_in); task.add_communicator("nccl"); - if (!has_gpus) + if (!has_gpus) { task.add_constraint(legate::broadcast(part_in, legate::from_range(0, dim()))); + } runtime->submit(std::move(task)); return result; } @@ -502,13 +540,19 @@ NDArray NDArray::swapaxes(int32_t axis1, int32_t axis2) axis1 = normalize_axis_index(axis1, dim()); axis2 = normalize_axis_index(axis2, dim()); - if (shape().size() == 1 || axis1 == axis2) return *this; + if (shape().size() == 1 || axis1 == axis2) { + return *this; + } auto ndim = dim(); std::vector dims; - for (auto i = 0; i < ndim; ++i) dims.push_back(i); + for (auto i = 0; i < ndim; ++i) { + dims.push_back(i); + } - if (axis1 < 0 || axis2 < 0) throw std::out_of_range("Index is out of range"); + if (axis1 < 0 || axis2 < 0) { + throw std::out_of_range("Index is out of range"); + } std::swap(dims[axis1], dims[axis2]); @@ -525,7 +569,9 @@ NDArray NDArray::as_type(const legate::Type& type) auto out = runtime->create_array(shape(), type); - if (size() == 0) return out; + if (size() == 0) { + return out; + } assert(store_.type() != out.store_.type()); @@ -544,7 +590,9 @@ NDArray NDArray::as_type(const legate::Type& type) void NDArray::create_window(int32_t op_code, int64_t M, std::vector args) { - if (size() == 0) return; + if (size() == 0) { + return; + } auto runtime = CuNumericRuntime::get_runtime(); @@ -554,7 +602,9 @@ void NDArray::create_window(int32_t op_code, int64_t M, std::vector args task.add_scalar_arg(legate::Scalar(op_code)); task.add_scalar_arg(legate::Scalar(M)); - for (double arg : args) { task.add_scalar_arg(legate::Scalar(arg)); } + for (double arg : args) { + task.add_scalar_arg(legate::Scalar(arg)); + } runtime->submit(std::move(task)); } @@ -583,15 +633,21 @@ void NDArray::convolve(NDArray input, NDArray filter) NDArray NDArray::transpose() { - if (dim() == 1) return NDArray(std::move(store_)); + if (dim() == 1) { + return NDArray(std::move(store_)); + } std::vector axes; - for (int32_t i = dim() - 1; i > -1; --i) axes.push_back(i); + for (int32_t i = dim() - 1; i > -1; --i) { + axes.push_back(i); + } return transpose(axes); } NDArray NDArray::transpose(std::vector axes) { - if (dim() == 1) return NDArray(std::move(store_)); + if (dim() == 1) { + return NDArray(std::move(store_)); + } if (static_cast(axes.size()) != dim()) { throw std::invalid_argument("axes must be the same size as ndim for transpose"); } @@ -610,16 +666,19 @@ legate::LogicalStore NDArray::broadcast(const std::vector& shape, #endif auto result = store; - for (int32_t dim = 0; dim < diff; ++dim) result = result.promote(dim, shape[dim]); + for (int32_t dim = 0; dim < diff; ++dim) { + result = result.promote(dim, shape[dim]); + } std::vector orig_shape = result.extents().data(); - for (uint32_t dim = 0; dim < shape.size(); ++dim) + for (uint32_t dim = 0; dim < shape.size(); ++dim) { if (orig_shape[dim] != shape[dim]) { #ifdef DEBUG_CUNUMERIC assert(orig_shape[dim] == 1); #endif result = result.project(dim, 0).promote(dim, shape[dim]); } + } #ifdef DEBUG_CUNUMERIC assert(static_cast(result.dim()) == shape.size()); @@ -630,7 +689,9 @@ legate::LogicalStore NDArray::broadcast(const std::vector& shape, legate::LogicalStore NDArray::broadcast(NDArray rhs1, NDArray rhs2) { - if (rhs1.shape() == rhs2.shape()) { return rhs1.store_; } + if (rhs1.shape() == rhs2.shape()) { + return rhs1.store_; + } auto out_shape = broadcast_shapes({rhs1, rhs2}); return broadcast(out_shape, rhs1.store_); } diff --git a/src/cunumeric/nullary/arange.cc b/src/cunumeric/nullary/arange.cc index 552f24a6bb..ba9bafe9ae 100644 --- a/src/cunumeric/nullary/arange.cc +++ b/src/cunumeric/nullary/arange.cc @@ -28,8 +28,9 @@ struct ArangeImplBody { const VAL start, const VAL step) const { - for (coord_t idx = rect.lo[0]; idx <= rect.hi[0]; ++idx) + for (coord_t idx = rect.lo[0]; idx <= rect.hi[0]; ++idx) { out[idx] = static_cast(idx) * step + start; + } } }; diff --git a/src/cunumeric/nullary/arange.cu b/src/cunumeric/nullary/arange.cu index dafdae0776..7f6596d464 100644 --- a/src/cunumeric/nullary/arange.cu +++ b/src/cunumeric/nullary/arange.cu @@ -26,7 +26,9 @@ __global__ static void __launch_bounds__(THREADS_PER_BLOCK, MIN_CTAS_PER_SM) ara const AccessorWO out, const coord_t lo, const VAL start, const VAL step, const size_t max) { const size_t offset = blockIdx.x * blockDim.x + threadIdx.x; - if (offset >= max) return; + if (offset >= max) { + return; + } const auto p = lo + offset; out[p] = static_cast(p) * step + start; } diff --git a/src/cunumeric/nullary/arange_omp.cc b/src/cunumeric/nullary/arange_omp.cc index e61d0ee375..657d99161e 100644 --- a/src/cunumeric/nullary/arange_omp.cc +++ b/src/cunumeric/nullary/arange_omp.cc @@ -29,8 +29,9 @@ struct ArangeImplBody { const VAL step) const { #pragma omp parallel for - for (coord_t idx = rect.lo[0]; idx <= rect.hi[0]; ++idx) + for (coord_t idx = rect.lo[0]; idx <= rect.hi[0]; ++idx) { out[idx] = static_cast(idx) * step + start; + } } }; diff --git a/src/cunumeric/nullary/arange_template.inl b/src/cunumeric/nullary/arange_template.inl index 5a1dcb407f..2c60d9d32c 100644 --- a/src/cunumeric/nullary/arange_template.inl +++ b/src/cunumeric/nullary/arange_template.inl @@ -38,7 +38,9 @@ struct ArangeImpl { const auto rect = args.out.shape<1>(); - if (rect.empty()) return; + if (rect.empty()) { + return; + } auto out = args.out.write_accessor(); diff --git a/src/cunumeric/nullary/eye.cc b/src/cunumeric/nullary/eye.cc index bedd76953f..4df31128ab 100644 --- a/src/cunumeric/nullary/eye.cc +++ b/src/cunumeric/nullary/eye.cc @@ -27,7 +27,9 @@ struct EyeImplBody { const Point<2>& start, const coord_t distance) const { - for (coord_t idx = 0; idx < distance; idx++) out[start[0] + idx][start[1] + idx] = VAL{1}; + for (coord_t idx = 0; idx < distance; idx++) { + out[start[0] + idx][start[1] + idx] = VAL{1}; + } } }; diff --git a/src/cunumeric/nullary/eye.cu b/src/cunumeric/nullary/eye.cu index 9a2b363963..033bb856cc 100644 --- a/src/cunumeric/nullary/eye.cu +++ b/src/cunumeric/nullary/eye.cu @@ -25,7 +25,9 @@ __global__ static void __launch_bounds__(THREADS_PER_BLOCK, MIN_CTAS_PER_SM) eye_kernel(const AccessorWO out, const Point<2> start, const size_t max) { const size_t offset = blockIdx.x * blockDim.x + threadIdx.x; - if (offset >= max) return; + if (offset >= max) { + return; + } out[start[0] + offset][start[1] + offset] = 1; } diff --git a/src/cunumeric/nullary/eye_omp.cc b/src/cunumeric/nullary/eye_omp.cc index aa90027d8d..f1e00482f3 100644 --- a/src/cunumeric/nullary/eye_omp.cc +++ b/src/cunumeric/nullary/eye_omp.cc @@ -28,7 +28,9 @@ struct EyeImplBody { const coord_t distance) const { #pragma omp parallel for - for (coord_t idx = 0; idx < distance; idx++) out[start[0] + idx][start[1] + idx] = VAL{1}; + for (coord_t idx = 0; idx < distance; idx++) { + out[start[0] + idx][start[1] + idx] = VAL{1}; + } } }; diff --git a/src/cunumeric/nullary/eye_template.inl b/src/cunumeric/nullary/eye_template.inl index 80ffa63f64..e56dcaf1ee 100644 --- a/src/cunumeric/nullary/eye_template.inl +++ b/src/cunumeric/nullary/eye_template.inl @@ -47,7 +47,9 @@ struct EyeImpl { // y >= rect.lo[1] const Point<2> start2(rect.lo[1] - k, rect.lo[1]); // If we don't have a start point then there's nothing for us to do - if (!rect.contains(start1) && !rect.contains(start2)) return; + if (!rect.contains(start1) && !rect.contains(start2)) { + return; + } // Pick whichever one fits in our rect const Point<2> start = rect.contains(start1) ? start1 : start2; // Now do the same thing for the end diff --git a/src/cunumeric/nullary/fill.cc b/src/cunumeric/nullary/fill.cc index 807aeaf8c3..307dd78c0a 100644 --- a/src/cunumeric/nullary/fill.cc +++ b/src/cunumeric/nullary/fill.cc @@ -33,7 +33,9 @@ struct FillImplBody { size_t volume = rect.volume(); if (dense) { auto outptr = out.ptr(rect); - for (size_t idx = 0; idx < volume; ++idx) outptr[idx] = fill_value; + for (size_t idx = 0; idx < volume; ++idx) { + outptr[idx] = fill_value; + } } else { for (size_t idx = 0; idx < volume; ++idx) { const auto point = pitches.unflatten(idx, rect.lo); diff --git a/src/cunumeric/nullary/fill.cu b/src/cunumeric/nullary/fill.cu index 9f2d2ff45b..0fc58da856 100644 --- a/src/cunumeric/nullary/fill.cu +++ b/src/cunumeric/nullary/fill.cu @@ -26,7 +26,9 @@ static __global__ void __launch_bounds__(THREADS_PER_BLOCK, MIN_CTAS_PER_SM) dense_kernel(size_t volume, ARG* out, ReadAcc fill_value) { const size_t idx = global_tid_1d(); - if (idx >= volume) return; + if (idx >= volume) { + return; + } out[idx] = fill_value[0]; } @@ -35,7 +37,9 @@ static __global__ void __launch_bounds__(THREADS_PER_BLOCK, MIN_CTAS_PER_SM) generic_kernel(size_t volume, WriteAcc out, ReadAcc fill_value, Pitches pitches, Rect rect) { const size_t idx = global_tid_1d(); - if (idx >= volume) return; + if (idx >= volume) { + return; + } auto point = pitches.unflatten(idx, rect.lo); out[point] = fill_value[0]; } diff --git a/src/cunumeric/nullary/fill_omp.cc b/src/cunumeric/nullary/fill_omp.cc index 64b8ac41e7..495a367390 100644 --- a/src/cunumeric/nullary/fill_omp.cc +++ b/src/cunumeric/nullary/fill_omp.cc @@ -39,7 +39,9 @@ struct FillImplBody { if (dense) { auto outptr = out.ptr(rect); #pragma omp parallel for schedule(static) - for (size_t idx = 0; idx < volume; ++idx) outptr[idx] = fill_value; + for (size_t idx = 0; idx < volume; ++idx) { + outptr[idx] = fill_value; + } } else { #pragma omp parallel for schedule(static) for (size_t idx = 0; idx < volume; ++idx) { diff --git a/src/cunumeric/nullary/fill_template.inl b/src/cunumeric/nullary/fill_template.inl index 0ffa088499..6e86acebe2 100644 --- a/src/cunumeric/nullary/fill_template.inl +++ b/src/cunumeric/nullary/fill_template.inl @@ -39,7 +39,9 @@ struct FillImpl { Pitches pitches; size_t volume = pitches.flatten(rect); - if (volume == 0) return; + if (volume == 0) { + return; + } auto out = args.out.write_accessor(rect); auto fill_value = args.fill_value.read_accessor(); diff --git a/src/cunumeric/nullary/window.cc b/src/cunumeric/nullary/window.cc index 9813747f19..89fe9a94be 100644 --- a/src/cunumeric/nullary/window.cc +++ b/src/cunumeric/nullary/window.cc @@ -30,9 +30,13 @@ struct WindowImplBody { if (dense) { auto outptr = out.ptr(rect); size_t off = 0; - for (int64_t idx = rect.lo[0]; idx <= rect.hi[0]; ++idx) outptr[off++] = gen(idx); + for (int64_t idx = rect.lo[0]; idx <= rect.hi[0]; ++idx) { + outptr[off++] = gen(idx); + } } else { - for (int64_t idx = rect.lo[0]; idx <= rect.hi[0]; ++idx) out[idx] = gen(idx); + for (int64_t idx = rect.lo[0]; idx <= rect.hi[0]; ++idx) { + out[idx] = gen(idx); + } } } }; diff --git a/src/cunumeric/nullary/window.cu b/src/cunumeric/nullary/window.cu index ff3124bc39..fd919b0813 100644 --- a/src/cunumeric/nullary/window.cu +++ b/src/cunumeric/nullary/window.cu @@ -26,7 +26,9 @@ static __global__ void __launch_bounds__(THREADS_PER_BLOCK, MIN_CTAS_PER_SM) dense_kernel(WindowOp gen, int64_t volume, double* out, int64_t lo) { const int64_t idx = blockIdx.x * blockDim.x + threadIdx.x; - if (idx >= volume) return; + if (idx >= volume) { + return; + } out[idx] = gen(idx + lo); } @@ -35,7 +37,9 @@ static __global__ void __launch_bounds__(THREADS_PER_BLOCK, MIN_CTAS_PER_SM) generic_kernel(WindowOp gen, int64_t volume, AccessorWO out, int64_t lo) { int64_t idx = blockIdx.x * blockDim.x + threadIdx.x; - if (idx >= volume) return; + if (idx >= volume) { + return; + } idx += lo; Point<1> point(idx); out[point] = gen(idx); diff --git a/src/cunumeric/nullary/window_omp.cc b/src/cunumeric/nullary/window_omp.cc index 7af11e6b64..e79c33b9c6 100644 --- a/src/cunumeric/nullary/window_omp.cc +++ b/src/cunumeric/nullary/window_omp.cc @@ -31,10 +31,14 @@ struct WindowImplBody { auto* outptr = out.ptr(rect); auto base = rect.lo[0]; #pragma omp parallel for schedule(static) - for (int64_t idx = base; idx <= rect.hi[0]; ++idx) outptr[idx - base] = gen(idx); + for (int64_t idx = base; idx <= rect.hi[0]; ++idx) { + outptr[idx - base] = gen(idx); + } } else #pragma omp parallel for schedule(static) - for (int64_t idx = rect.lo[0]; idx <= rect.hi[0]; ++idx) out[idx] = gen(idx); + for (int64_t idx = rect.lo[0]; idx <= rect.hi[0]; ++idx) { + out[idx] = gen(idx); + } } }; diff --git a/src/cunumeric/nullary/window_template.inl b/src/cunumeric/nullary/window_template.inl index 16b727ca3f..1d46568cc3 100644 --- a/src/cunumeric/nullary/window_template.inl +++ b/src/cunumeric/nullary/window_template.inl @@ -34,7 +34,9 @@ struct WindowImpl { { auto rect = output.shape<1>(); - if (rect.empty()) return; + if (rect.empty()) { + return; + } auto out = output.write_accessor(rect); diff --git a/src/cunumeric/operators.cc b/src/cunumeric/operators.cc index 0c6f3ffcfb..e786db4d95 100644 --- a/src/cunumeric/operators.cc +++ b/src/cunumeric/operators.cc @@ -110,8 +110,9 @@ struct generate_int_value_fn { NDArray zeros(std::vector shape, std::optional type) { auto code = type.has_value() ? type.value().code() : legate::Type::Code::FLOAT64; - if (static_cast(code) >= static_cast(legate::Type::Code::FIXED_ARRAY)) + if (static_cast(code) >= static_cast(legate::Type::Code::FIXED_ARRAY)) { throw std::invalid_argument("Type must be a primitive type"); + } auto zero = legate::type_dispatch(code, generate_zero_fn{}); return full(shape, zero); } @@ -126,8 +127,9 @@ NDArray full(std::vector shape, const Scalar& value) NDArray eye(size_t n, std::optional m, int32_t k, const legate::Type& type) { - if (static_cast(type.code()) >= static_cast(legate::Type::Code::FIXED_ARRAY)) + if (static_cast(type.code()) >= static_cast(legate::Type::Code::FIXED_ARRAY)) { throw std::invalid_argument("Type must be a primitive type"); + } auto runtime = CuNumericRuntime::get_runtime(); auto out = runtime->create_array({n, m.value_or(n)}, type); @@ -139,13 +141,18 @@ NDArray bincount(NDArray x, std::optional weights /*=std::nullopt*/, uint32_t min_length /*=0*/) { - if (x.dim() != 1) throw std::invalid_argument("The input array must be 1-dimensional"); - if (x.size() == 0) throw std::invalid_argument("The input array must be non-empty"); + if (x.dim() != 1) { + throw std::invalid_argument("The input array must be 1-dimensional"); + } + if (x.size() == 0) { + throw std::invalid_argument("The input array must be non-empty"); + } int32_t x_type_code = static_cast(x.type().code()); if (x_type_code < static_cast(legate::Type::Code::INT8) || - x_type_code > static_cast(legate::Type::Code::UINT64)) + x_type_code > static_cast(legate::Type::Code::UINT64)) { throw std::invalid_argument("input array for bincount must be integer type"); + } auto max_val_arr = amax(x); auto max_val = @@ -153,8 +160,12 @@ NDArray bincount(NDArray x, auto min_val_arr = amin(x); auto min_val = legate::type_dispatch(min_val_arr.type().code(), generate_int_value_fn{}, min_val_arr); - if (min_val < 0) throw std::invalid_argument("the input array must have no negative elements"); - if (static_cast(min_length) < max_val + 1) min_length = max_val + 1; + if (min_val < 0) { + throw std::invalid_argument("the input array must have no negative elements"); + } + if (static_cast(min_length) < max_val + 1) { + min_length = max_val + 1; + } auto runtime = CuNumericRuntime::get_runtime(); if (!weights.has_value()) { @@ -163,13 +174,16 @@ NDArray bincount(NDArray x, return out; } else { auto weight_array = weights.value(); - if (weight_array.shape() != x.shape()) + if (weight_array.shape() != x.shape()) { throw std::invalid_argument("weights array must have the same shape as the input array"); + } auto weight_code = weight_array.type().code(); - if (static_cast(weight_code) >= static_cast(legate::Type::Code::COMPLEX64)) + if (static_cast(weight_code) >= static_cast(legate::Type::Code::COMPLEX64)) { throw std::invalid_argument("weights must be convertible to float64"); - if (weight_code != legate::Type::Code::FLOAT64) + } + if (weight_code != legate::Type::Code::FLOAT64) { weight_array = weight_array.as_type(legate::float64()); + } auto out = runtime->create_array({min_length}, weight_array.type()); out.bincount(x, weight_array); @@ -182,8 +196,12 @@ NDArray trilu(NDArray rhs, int32_t k, bool lower) auto dim = rhs.dim(); auto& shape = rhs.shape(); std::vector out_shape(shape); - if (dim == 0) throw std::invalid_argument("Dim of input array must be > 0"); - if (dim == 1) out_shape.emplace_back(shape[0]); + if (dim == 0) { + throw std::invalid_argument("Dim of input array must be > 0"); + } + if (dim == 1) { + out_shape.emplace_back(shape[0]); + } auto runtime = CuNumericRuntime::get_runtime(); auto out = runtime->create_array(std::move(out_shape), rhs.type()); @@ -303,12 +321,16 @@ NDArray kaiser(int64_t M, double beta) { return create_window(M, WindowOpCode::K NDArray convolve(NDArray a, NDArray v) { - if (a.dim() != v.dim()) { throw std::invalid_argument("Arrays should have the same dimensions"); } + if (a.dim() != v.dim()) { + throw std::invalid_argument("Arrays should have the same dimensions"); + } if (a.dim() > 3) { throw std::runtime_error(std::to_string(a.dim()) + "-D arrays are not yet supported"); } auto out = CuNumericRuntime::get_runtime()->create_array(a.shape(), a.type()); - if (a.type() != v.type()) { v = v.as_type(a.type()); } + if (a.type() != v.type()) { + v = v.as_type(a.type()); + } out.convolve(std::move(a), std::move(v)); return out; } @@ -342,7 +364,9 @@ std::vector normalize_axis_vector(std::vector axis, bool allow_duplicate) { std::vector new_axis; - for (auto ax : axis) { new_axis.emplace_back(normalize_axis_index(ax, ndim)); } + for (auto ax : axis) { + new_axis.emplace_back(normalize_axis_index(ax, ndim)); + } std::set s(new_axis.begin(), new_axis.end()); if (!allow_duplicate && s.size() != new_axis.size()) { throw std::invalid_argument("repeated axis"); @@ -363,12 +387,18 @@ NDArray moveaxis(NDArray a, std::vector source, std::vector de std::vector order; std::set set_src(src.begin(), src.end()); for (auto i = 0; i < ndim; ++i) { - if (set_src.find(i) == set_src.end()) order.emplace_back(i); + if (set_src.find(i) == set_src.end()) { + order.emplace_back(i); + } } std::vector> vp; - for (size_t i = 0; i < src.size(); ++i) { vp.push_back(std::make_pair(dst[i], src[i])); } + for (size_t i = 0; i < src.size(); ++i) { + vp.push_back(std::make_pair(dst[i], src[i])); + } std::sort(vp.begin(), vp.end()); - for (auto p : vp) { order.emplace(order.begin() + p.first, p.second); } + for (auto p : vp) { + order.emplace(order.begin() + p.first, p.second); + } return a.transpose(order); } diff --git a/src/cunumeric/pitches.h b/src/cunumeric/pitches.h index d2a63f7b32..dc33fe7726 100644 --- a/src/cunumeric/pitches.h +++ b/src/cunumeric/pitches.h @@ -32,7 +32,9 @@ class Pitches { size_t volume = 1; for (int d = DIM; d >= 0; --d) { // Quick exit for empty rectangle dimensions - if (rect.lo[d] > rect.hi[d]) return 0; + if (rect.lo[d] > rect.hi[d]) { + return 0; + } const size_t diff = rect.hi[d] - rect.lo[d] + 1; volume *= diff; if (d > 0) { @@ -68,7 +70,9 @@ class Pitches { size_t volume = 1; for (int d = 0; d <= DIM; ++d) { // Quick exit for empty rectangle dimensions - if (rect.lo[d] > rect.hi[d]) return 0; + if (rect.lo[d] > rect.hi[d]) { + return 0; + } const size_t diff = rect.hi[d] - rect.lo[d] + 1; volume *= diff; if (d < DIM) { @@ -101,10 +105,11 @@ class Pitches<0, C_ORDER> { __CUDA_HD__ inline size_t flatten(const legate::Rect<1>& rect) { - if (rect.lo[0] > rect.hi[0]) + if (rect.lo[0] > rect.hi[0]) { return 0; - else + } else { return (rect.hi[0] - rect.lo[0] + 1); + } } __CUDA_HD__ inline legate::Point<1> unflatten(size_t index, const legate::Point<1>& lo) const diff --git a/src/cunumeric/random/bitgenerator_curand.inl b/src/cunumeric/random/bitgenerator_curand.inl index 7f59237bbb..12be9e71f3 100644 --- a/src/cunumeric/random/bitgenerator_curand.inl +++ b/src/cunumeric/random/bitgenerator_curand.inl @@ -1321,9 +1321,10 @@ struct generator_map { if (m_generators.find(generatorID) != m_generators.end()) { cugenptr = m_generators[generatorID]; m_generators.erase(generatorID); - } else + } else { // in some cases, destroy is forced, but processor never created the instance return; + } } CURANDGeneratorBuilder::destroy(cugenptr); @@ -1380,7 +1381,9 @@ struct BitGeneratorImplBody { } case BitGeneratorOperation::RAND_RAW: { // allow for lazy initialization - if (!genmap.has(generatorID)) genmap.create(generatorID, generatorType, seed, flags); + if (!genmap.has(generatorID)) { + genmap.create(generatorID, generatorType, seed, flags); + } // get the generator CURANDGenerator* genptr = genmap.get(generatorID); if (output.size() != 0) { @@ -1392,7 +1395,9 @@ struct BitGeneratorImplBody { } case BitGeneratorOperation::DISTRIBUTION: { // allow for lazy initialization - if (!genmap.has(generatorID)) genmap.create(generatorID, generatorType, seed, flags); + if (!genmap.has(generatorID)) { + genmap.create(generatorID, generatorType, seed, flags); + } // get the generator CURANDGenerator* genptr = genmap.get(generatorID); if (output.size() != 0) { diff --git a/src/cunumeric/random/bitgenerator_template.inl b/src/cunumeric/random/bitgenerator_template.inl index 056883fa07..c36349282f 100644 --- a/src/cunumeric/random/bitgenerator_template.inl +++ b/src/cunumeric/random/bitgenerator_template.inl @@ -69,11 +69,15 @@ static void bitgenerator_template(TaskContext& context) switch (bitgen_op) { case BitGeneratorOperation::DESTROY: // gather same parameters as CREATE case BitGeneratorOperation::CREATE: { - if (scalars.size() > 5) todestroy = scalars[5].values(); + if (scalars.size() > 5) { + todestroy = scalars[5].values(); + } break; } case BitGeneratorOperation::RAND_RAW: { - if (scalars.size() > 5) strides = scalars[5].value(); + if (scalars.size() > 5) { + strides = scalars[5].value(); + } break; } case BitGeneratorOperation::DISTRIBUTION: { @@ -94,10 +98,14 @@ static void bitgenerator_template(TaskContext& context) } std::vector extra_args; - for (auto& input : inputs) extra_args.push_back(std::move(input)); + for (auto& input : inputs) { + extra_args.push_back(std::move(input)); + } std::vector optional_output; - for (auto& output : outputs) optional_output.push_back(std::move(output)); + for (auto& output : outputs) { + optional_output.push_back(std::move(output)); + } // destroy ? for (auto& idx : todestroy) { diff --git a/src/cunumeric/random/rand.cc b/src/cunumeric/random/rand.cc index 4beff339fc..d22d8c9582 100644 --- a/src/cunumeric/random/rand.cc +++ b/src/cunumeric/random/rand.cc @@ -33,7 +33,9 @@ struct RandImplBody { for (size_t idx = 0; idx < volume; ++idx) { const auto point = pitches.unflatten(idx, rect.lo); size_t offset = 0; - for (size_t dim = 0; dim < DIM; ++dim) offset += point[dim] * strides[dim]; + for (size_t dim = 0; dim < DIM; ++dim) { + offset += point[dim] * strides[dim]; + } out[point] = rng(HI_BITS(offset), LO_BITS(offset)); } } diff --git a/src/cunumeric/random/rand.cu b/src/cunumeric/random/rand.cu index 7b3d7d9897..c41be1469e 100644 --- a/src/cunumeric/random/rand.cu +++ b/src/cunumeric/random/rand.cu @@ -26,10 +26,14 @@ static __global__ void __launch_bounds__(THREADS_PER_BLOCK, MIN_CTAS_PER_SM) ran size_t volume, WriteAcc out, Rng rng, Point strides, Pitches pitches, Point lo) { const size_t idx = global_tid_1d(); - if (idx >= volume) return; + if (idx >= volume) { + return; + } auto point = pitches.unflatten(idx, lo); size_t offset = 0; - for (size_t dim = 0; dim < DIM; ++dim) offset += point[dim] * strides[dim]; + for (size_t dim = 0; dim < DIM; ++dim) { + offset += point[dim] * strides[dim]; + } out[point] = rng(HI_BITS(offset), LO_BITS(offset)); } diff --git a/src/cunumeric/random/rand_omp.cc b/src/cunumeric/random/rand_omp.cc index 1031b50437..57be6ef5a4 100644 --- a/src/cunumeric/random/rand_omp.cc +++ b/src/cunumeric/random/rand_omp.cc @@ -34,7 +34,9 @@ struct RandImplBody { for (size_t idx = 0; idx < volume; ++idx) { const auto point = pitches.unflatten(idx, rect.lo); size_t offset = 0; - for (size_t dim = 0; dim < DIM; ++dim) offset += point[dim] * strides[dim]; + for (size_t dim = 0; dim < DIM; ++dim) { + offset += point[dim] * strides[dim]; + } out[point] = rng(HI_BITS(offset), LO_BITS(offset)); } } diff --git a/src/cunumeric/random/rand_template.inl b/src/cunumeric/random/rand_template.inl index 580247d1d8..04ccb6ec80 100644 --- a/src/cunumeric/random/rand_template.inl +++ b/src/cunumeric/random/rand_template.inl @@ -44,7 +44,9 @@ struct RandImpl { Pitches pitches; size_t volume = pitches.flatten(rect); - if (volume == 0) return; + if (volume == 0) { + return; + } auto out = args.out.write_accessor(rect); Point strides(args.strides); @@ -83,7 +85,9 @@ static void rand_template(TaskContext& context) auto strides = scalars[2].value(); std::vector extra_args; - for (uint32_t idx = 3; idx < scalars.size(); ++idx) extra_args.push_back(scalars[idx]); + for (uint32_t idx = 3; idx < scalars.size(); ++idx) { + extra_args.push_back(scalars[idx]); + } RandArgs args{outputs[0], gen_code, epoch, strides, std::move(extra_args)}; op_dispatch(args.gen_code, RandDispatch{}, args); diff --git a/src/cunumeric/random/rand_util.h b/src/cunumeric/random/rand_util.h index db25cf2be8..e467abb33f 100644 --- a/src/cunumeric/random/rand_util.h +++ b/src/cunumeric/random/rand_util.h @@ -83,7 +83,9 @@ struct RandomGenerator { if (fa >= 1.0) { l = 0xfff8000000000000ull; memcpy(&t, &l, sizeof(double)); /* INDEFINITE */ - if (fa == 1.0) { t = a * exp(1000.0); /* Infinity */ } + if (fa == 1.0) { + t = a * exp(1000.0); /* Infinity */ + } } else if (fa >= 0.9375) { /* Based on: J.M. Blair, C.A. Edwards, J.H. Johnson: Rational Chebyshev Approximations for the Inverse of the Error Function. Mathematics of @@ -113,7 +115,9 @@ struct RandomGenerator { q = q * t + 1.3858762165532246059e-4; q = q * t + 1.1738313872397777529e-6; t = p / (q * t); - if (a < 0.0) t = -t; + if (a < 0.0) { + t = -t; + } } else if (fa >= 0.75) { /* Based on: J.M. Blair, C.A. Edwards, J.H. Johnson: Rational Chebyshev Approximations for the Inverse of the Error Function. Mathematics of diff --git a/src/cunumeric/random/randutil/generator.cuh b/src/cunumeric/random/randutil/generator.cuh index e51d1272e4..efe593bde7 100644 --- a/src/cunumeric/random/randutil/generator.cuh +++ b/src/cunumeric/random/randutil/generator.cuh @@ -51,7 +51,9 @@ __global__ void __launch_bounds__(blockDimX, blocksPerMultiProcessor) assert(id < ngenerators); // TODO: improve load gen_t gen = gens[id]; - for (size_t k = id; k < N; k += blockDim.x * gridDim.x) { draws[k] = func(gen); } + for (size_t k = id; k < N; k += blockDim.x * gridDim.x) { + draws[k] = func(gen); + } // save state gens[id] = gen; } @@ -91,8 +93,9 @@ struct inner_generator : basegenerato #else CHECK_CUDA(::cudaMalloc(&generators, ngenerators * sizeof(gen_t))); #endif - } else + } else { CHECK_CUDA(::cudaMalloc(&generators, ngenerators * sizeof(gen_t))); + } // initialize generators initgenerators<<>>( @@ -109,8 +112,9 @@ struct inner_generator : basegenerato #else CHECK_CUDA(::cudaFree(generators)); #endif - } else + } else { CHECK_CUDA(::cudaFree(generators)); + } generators = nullptr; } @@ -127,8 +131,9 @@ struct inner_generator : basegenerato template curandStatus_t draw(func_t func, size_t N, out_t* out) { - if (generators == nullptr) // destroyed was called + if (generators == nullptr) { // destroyed was called return CURAND_STATUS_NOT_INITIALIZED; + } gpu_draw<<>>( ngenerators, generators, func, N, out); return ::cudaPeekAtLastError() == cudaSuccess ? CURAND_STATUS_SUCCESS diff --git a/src/cunumeric/random/randutil/generator.h b/src/cunumeric/random/randutil/generator.h index 544905ec0d..0e147f3121 100644 --- a/src/cunumeric/random/randutil/generator.h +++ b/src/cunumeric/random/randutil/generator.h @@ -87,7 +87,9 @@ struct inner_generator : basegenerator template curandStatus_t draw(func_t func, size_t N, out_t* out) { - for (size_t k = 0; k < N; ++k) { out[k] = func(generator); } + for (size_t k = 0; k < N; ++k) { + out[k] = func(generator); + } return CURAND_STATUS_SUCCESS; } }; diff --git a/src/cunumeric/random/randutil/generator_gumbel.inl b/src/cunumeric/random/randutil/generator_gumbel.inl index 4a5d7b0b19..d121d962cb 100644 --- a/src/cunumeric/random/randutil/generator_gumbel.inl +++ b/src/cunumeric/random/randutil/generator_gumbel.inl @@ -28,7 +28,9 @@ struct gumbel_t { RANDUTIL_QUALIFIERS float operator()(gen_t& gen) { float y = curand_uniform(&gen); // y cannot be zero - if (y == 1.0f) return mu; + if (y == 1.0f) { + return mu; + } float lny = ::logf(y); return mu - beta * ::logf(-lny); } @@ -42,7 +44,9 @@ struct gumbel_t { RANDUTIL_QUALIFIERS double operator()(gen_t& gen) { double y = curand_uniform_double(&gen); // y cannot be zero - if (y == 1.0) return mu; + if (y == 1.0) { + return mu; + } double lny = ::log(y); return mu - beta * ::log(-lny); } diff --git a/src/cunumeric/random/randutil/generator_laplace.inl b/src/cunumeric/random/randutil/generator_laplace.inl index 0d4c7b1ae4..56e40d9032 100644 --- a/src/cunumeric/random/randutil/generator_laplace.inl +++ b/src/cunumeric/random/randutil/generator_laplace.inl @@ -27,11 +27,14 @@ struct laplace_t { RANDUTIL_QUALIFIERS float operator()(gen_t& gen) { float y = curand_uniform(&gen); // y cannot be zero - if (y == 0.5f) return mu; - if (y < 0.5f) + if (y == 0.5f) { + return mu; + } + if (y < 0.5f) { return mu + beta * ::logf(2.0f * y); - else + } else { return mu - beta * ::logf(2.0f * y - 1.0f); // y can be 1.0 => revert y to avoid this + } } }; @@ -43,10 +46,13 @@ struct laplace_t { RANDUTIL_QUALIFIERS double operator()(gen_t& gen) { double y = curand_uniform_double(&gen); // y cannot be zero - if (y == 0.5) return mu; - if (y < 0.5) + if (y == 0.5) { + return mu; + } + if (y < 0.5) { return mu + beta * ::log(2.0 * y); - else + } else { return mu - beta * ::log(2.0 * y - 1.0); // y can be 1.0 => revert y to avoid this + } } }; diff --git a/src/cunumeric/random/randutil/generator_logistic.inl b/src/cunumeric/random/randutil/generator_logistic.inl index 7405c18a39..2dbadd2f6d 100644 --- a/src/cunumeric/random/randutil/generator_logistic.inl +++ b/src/cunumeric/random/randutil/generator_logistic.inl @@ -28,7 +28,9 @@ struct logistic_t { { float y = curand_uniform(&gen); // y cannot be 0 float t = 1.0f / y - 1.0f; - if (t == 0) t = 1.0f; + if (t == 0) { + t = 1.0f; + } return mu - beta * ::logf(t); } }; @@ -42,7 +44,9 @@ struct logistic_t { { float y = curand_uniform_double(&gen); // y cannot be 0 float t = 1.0 / y - 1.0; - if (t == 0) t = 1.0; + if (t == 0) { + t = 1.0; + } return mu - beta * ::log(t); } }; diff --git a/src/cunumeric/random/randutil/generator_triangular.inl b/src/cunumeric/random/randutil/generator_triangular.inl index 3bfe77e289..d17d2c57a5 100644 --- a/src/cunumeric/random/randutil/generator_triangular.inl +++ b/src/cunumeric/random/randutil/generator_triangular.inl @@ -29,11 +29,15 @@ struct triangular_t { float y = curand_uniform(&gen); // y cannot be 0 if (y <= ((c - a) / (b - a))) { float delta = (y * (b - a) * (c - a)); - if (delta < 0.0f) delta = 0.0f; + if (delta < 0.0f) { + delta = 0.0f; + } return a + ::sqrtf(delta); } else { float delta = ((1.0f - y) * (b - a) * (b - c)); - if (delta < 0.0f) delta = 0.0f; + if (delta < 0.0f) { + delta = 0.0f; + } return b - ::sqrtf(delta); } } @@ -49,11 +53,15 @@ struct triangular_t { double y = curand_uniform_double(&gen); // y cannot be 0 if (y <= ((c - a) / (b - a))) { double delta = (y * (b - a) * (c - a)); - if (delta < 0.0) delta = 0.0; + if (delta < 0.0) { + delta = 0.0; + } return a + ::sqrt(delta); } else { double delta = ((1.0 - y) * (b - a) * (b - c)); - if (delta < 0.0) delta = 0.0; + if (delta < 0.0) { + delta = 0.0; + } return b - ::sqrt(delta); } } diff --git a/src/cunumeric/random/randutil/generator_wald.inl b/src/cunumeric/random/randutil/generator_wald.inl index dec310834b..7684bf54c7 100644 --- a/src/cunumeric/random/randutil/generator_wald.inl +++ b/src/cunumeric/random/randutil/generator_wald.inl @@ -32,10 +32,11 @@ struct wald_t { float x = mu + (mu * mu * y) / (2.0f * lambda) - (mu / (2.0f * lambda)) * ::sqrtf(mu * y * (4.0f * lambda + mu * y)); float z = curand_uniform(&gen); - if (z <= (mu) / (mu + x)) + if (z <= (mu) / (mu + x)) { return x; - else + } else { return (mu * mu) / x; + } } }; @@ -51,9 +52,10 @@ struct wald_t { double x = mu + (mu * mu * y) / (2.0 * lambda) - (mu / (2.0 * lambda)) * ::sqrtf(mu * y * (4.0 * lambda + mu * y)); double z = curand_uniform(&gen); - if (z <= (mu) / (mu + x)) + if (z <= (mu) / (mu + x)) { return x; - else + } else { return (mu * mu) / x; + } } }; diff --git a/src/cunumeric/random/randutil/generator_weibull.inl b/src/cunumeric/random/randutil/generator_weibull.inl index 5565c74112..bb46042fd1 100644 --- a/src/cunumeric/random/randutil/generator_weibull.inl +++ b/src/cunumeric/random/randutil/generator_weibull.inl @@ -29,7 +29,9 @@ struct weibull_t { float y = curand_uniform(&gen); // y cannot be 0 // log(y) can be zero ! float lny = ::logf(y); - if (lny == 0.0f) return 0.0f; + if (lny == 0.0f) { + return 0.0f; + } return lambda * ::expf(::logf(-lny) * invk); } }; @@ -44,7 +46,9 @@ struct weibull_t { double y = curand_uniform_double(&gen); // y cannot be 0 // log(y) can be zero ! float lny = ::log(y); - if (lny == 0.0f) return 0.0f; + if (lny == 0.0f) { + return 0.0f; + } return lambda * ::exp(::log(-lny) * invk); } }; diff --git a/src/cunumeric/random/randutil/random_distributions.h b/src/cunumeric/random/randutil/random_distributions.h index 62364fd359..845350b890 100644 --- a/src/cunumeric/random/randutil/random_distributions.h +++ b/src/cunumeric/random/randutil/random_distributions.h @@ -143,11 +143,15 @@ RANDUTIL_QUALIFIERS double rk_standard_gamma(rk_state* state, double shape) V = rk_standard_exponential(state); if (U <= 1.0 - shape) { X = pow(U, 1. / shape); - if (X <= V) { return X; } + if (X <= V) { + return X; + } } else { Y = -log((1 - U) / shape); X = pow(1.0 - shape + shape * Y, 1. / shape); - if (X <= (V + Y)) { return X; } + if (X <= (V + Y)) { + return X; + } } } } else { @@ -160,8 +164,12 @@ RANDUTIL_QUALIFIERS double rk_standard_gamma(rk_state* state, double shape) } while (V <= 0.0); V = V * V * V; U = rk_double(state); - if (U < 1.0 - 0.0331 * (X * X) * (X * X)) return (b * V); - if (log(U) < 0.5 * X * X + b * (1. - V + log(V))) return (b * V); + if (U < 1.0 - 0.0331 * (X * X) * (X * X)) { + return (b * V); + } + if (log(U) < 0.5 * X * X + b * (1. - V + log(V))) { + return (b * V); + } } } } @@ -221,8 +229,12 @@ RANDUTIL_QUALIFIERS long rk_poisson_ptrs(rk_state* state, double lam) V = rk_double(state); us = 0.5 - fabs(U); k = (long)floor((2 * a / us + b) * U + lam + 0.43); - if ((us >= 0.07) && (V <= vr)) { return k; } - if ((k < 0) || ((us < 0.013) && (V > us))) { continue; } + if ((us >= 0.07) && (V <= vr)) { + return k; + } + if ((k < 0) || ((us < 0.013) && (V > us))) { + continue; + } if ((log(V) + log(invalpha) - log(a / (us * us) + b)) <= (-lam + k * loglam - loggam(k + 1))) { return k; } @@ -272,7 +284,9 @@ RANDUTIL_QUALIFIERS double rk_chisquare(rk_state* state, double df) template RANDUTIL_QUALIFIERS double rk_noncentral_chisquare(rk_state* state, double df, double nonc) { - if (nonc == 0) { return rk_chisquare(state, df); } + if (nonc == 0) { + return rk_chisquare(state, df); + } if (1 < df) { const double Chi2 = rk_chisquare(state, df - 1); const double N = rk_gauss(state) + sqrt(nonc); @@ -304,7 +318,9 @@ RANDUTIL_QUALIFIERS long rk_logseries(rk_state* state, double p) r = log(1.0 - p); while (1) { V = rk_double(state); - if (V >= p) { return 1; } + if (V >= p) { + return 1; + } U = rk_double(state); q = 1.0 - exp(r * U); if (V <= q * q) { @@ -315,7 +331,9 @@ RANDUTIL_QUALIFIERS long rk_logseries(rk_state* state, double p) return result; } } - if (V >= q) { return 1; } + if (V >= q) { + return 1; + } return 2; } } @@ -371,9 +389,13 @@ RANDUTIL_QUALIFIERS long rk_zipf(rk_state* state, double a) U = 1.0 - rk_double(state); V = rk_double(state); X = floor(pow(U, -1.0 / am1)); - if (X < 1.0) { continue; } + if (X < 1.0) { + continue; + } T = pow(1.0 + 1.0 / X, am1); - if (V * X * (T - 1.0) / (b - 1.0) <= T / b) { return (long)X; } + if (V * X * (T - 1.0) / (b - 1.0) <= T / b) { + return (long)X; + } } } @@ -405,16 +427,22 @@ RANDUTIL_QUALIFIERS double rk_vonmises(rk_state* state, double mu, double kappa) W = (1 + s * Z) / (s + Z); Y = kappa * (s - W); V = rk_double(state); - if ((Y * (2 - Y) - V >= 0) || (log(Y / V) + 1 - Y >= 0)) { break; } + if ((Y * (2 - Y) - V >= 0) || (log(Y / V) + 1 - Y >= 0)) { + break; + } } U = rk_double(state); result = acos(W); - if (U < 0.5) { result = -result; } + if (U < 0.5) { + result = -result; + } result += mu; neg = (result < 0); mod = fabs(result); mod = (fmod(mod + M_PI, 2 * M_PI) - M_PI); - if (neg) { mod *= -1; } + if (neg) { + mod *= -1; + } return mod; } } @@ -435,10 +463,14 @@ RANDUTIL_QUALIFIERS long rk_hypergeometric_hyp(rk_state* state, long good, long U = rk_double(state); Y -= (long)floor(U + Y / (d1 + K)); K--; - if (K == 0) break; + if (K == 0) { + break; + } } Z = (long)(d2 - Y); - if (good > bad) Z = sample - Z; + if (good > bad) { + Z = sample - Z; + } return Z; } /* D1 = 2*sqrt(2/e) */ @@ -471,20 +503,32 @@ RANDUTIL_QUALIFIERS long rk_hypergeometric_hrua(rk_state* state, long good, long Y = rk_double(state); W = d6 + d8 * (Y - 0.5) / X; /* fast rejection: */ - if ((W < 0.0) || (W >= d11)) continue; + if ((W < 0.0) || (W >= d11)) { + continue; + } Z = (long)floor(W); T = d10 - (loggam(Z + 1) + loggam(mingoodbad - Z + 1) + loggam(m - Z + 1) + loggam(maxgoodbad - m + Z + 1)); /* fast acceptance: */ - if ((X * (4.0 - X) - 3.0) <= T) break; + if ((X * (4.0 - X) - 3.0) <= T) { + break; + } /* fast rejection: */ - if (X * (X - T) >= 1) continue; - if (2.0 * log(X) <= T) break; /* acceptance */ + if (X * (X - T) >= 1) { + continue; + } + if (2.0 * log(X) <= T) { + break; /* acceptance */ + } } /* this is a correction to HRUA* by Ivan Frohne in rv.py */ - if (good > bad) Z = m - Z; + if (good > bad) { + Z = m - Z; + } /* another fix from rv.py to allow sample to exceed popsize/2 */ - if (m < sample) Z = good - Z; + if (m < sample) { + Z = good - Z; + } return Z; } #undef D1 @@ -529,45 +573,69 @@ RANDUTIL_QUALIFIERS unsigned rk_binomial_btpe(rk_state* state, unsigned n, doubl nrq = n * r * q; u = rk_double(state) * p4; v = rk_double(state); - if (u > p1) goto Step20; + if (u > p1) { + goto Step20; + } y = (long)floor(xm - p1 * v + u); goto Step60; Step20: - if (u > p2) goto Step30; + if (u > p2) { + goto Step30; + } x = xl + (u - p1) / c; v = v * c + 1.0 - fabs(m - x + 0.5) / p1; - if (v > 1.0) goto Step10; + if (v > 1.0) { + goto Step10; + } y = (long)floor(x); goto Step50; Step30: - if (u > p3) goto Step40; + if (u > p3) { + goto Step40; + } y = (long)floor(xl + log(v) / laml); - if (y < 0) goto Step10; + if (y < 0) { + goto Step10; + } v = v * (u - p2) * laml; goto Step50; Step40: y = (long)floor(xr - log(v) / lamr); - if (y > n) goto Step10; + if (y > n) { + goto Step10; + } v = v * (u - p3) * lamr; Step50: k = labs(y - m); - if ((k > 20) && (k < ((nrq) / 2.0 - 1))) goto Step52; + if ((k > 20) && (k < ((nrq) / 2.0 - 1))) { + goto Step52; + } s = r / q; a = s * (n + 1); F = 1.0; if (m < y) { - for (i = m + 1; i <= y; i++) { F *= (a / i - s); } + for (i = m + 1; i <= y; i++) { + F *= (a / i - s); + } } else if (m > y) { - for (i = y + 1; i <= m; i++) { F /= (a / i - s); } + for (i = y + 1; i <= m; i++) { + F /= (a / i - s); + } + } + if (v > F) { + goto Step10; } - if (v > F) goto Step10; goto Step60; Step52: rho = (k / (nrq)) * ((k * (k / 3.0 + 0.625) + 0.16666666666666666) / nrq + 0.5); t = -k * k / (2 * nrq); A = log(v); - if (A < (t - rho)) goto Step60; - if (A > (t + rho)) goto Step10; + if (A < (t - rho)) { + goto Step60; + } + if (A > (t + rho)) { + goto Step10; + } x1 = y + 1; f1 = m + 1; z = n + 1 - m; @@ -584,7 +652,9 @@ RANDUTIL_QUALIFIERS unsigned rk_binomial_btpe(rk_state* state, unsigned n, doubl goto Step10; } Step60: - if (p > 0.5) { y = n - y; } + if (p > 0.5) { + y = n - y; + } return (unsigned)y; } diff --git a/src/cunumeric/runtime.cc b/src/cunumeric/runtime.cc index 84ca590bf6..ae5637dcf3 100644 --- a/src/cunumeric/runtime.cc +++ b/src/cunumeric/runtime.cc @@ -96,7 +96,9 @@ Scalar CuNumericRuntime::get_reduction_identity(UnaryRedCode op, const legate::T { auto key = std::make_pair(op, type.code()); auto finder = identities.find(key); - if (identities.end() != finder) return finder->second; + if (identities.end() != finder) { + return finder->second; + } auto identity = op_dispatch(op, generate_identity_fn{}, type); identities.insert({key, identity}); @@ -106,7 +108,9 @@ Scalar CuNumericRuntime::get_reduction_identity(UnaryRedCode op, const legate::T legate::Type CuNumericRuntime::get_argred_type(const legate::Type& value_type) { auto finder = argred_types_.find(value_type.code()); - if (finder != argred_types_.end()) return finder->second; + if (finder != argred_types_.end()) { + return finder->second; + } auto argred_type = legate::struct_type({legate::int64(), value_type}, true /*align*/); argred_types_.insert({value_type.code(), argred_type}); diff --git a/src/cunumeric/scan/scan_global.cu b/src/cunumeric/scan/scan_global.cu index ea7047c4bd..7266d9e1a4 100644 --- a/src/cunumeric/scan/scan_global.cu +++ b/src/cunumeric/scan/scan_global.cu @@ -31,7 +31,9 @@ static __global__ void __launch_bounds__(THREADS_PER_BLOCK, MIN_CTAS_PER_SM) scalar_kernel(uint64_t volume, Function func, RES* out, RES scalar) { const size_t idx = blockIdx.x * blockDim.x + threadIdx.x; - if (idx >= volume) return; + if (idx >= volume) { + return; + } out[idx] = func(out[idx], scalar); } diff --git a/src/cunumeric/scan/scan_global_template.inl b/src/cunumeric/scan/scan_global_template.inl index 92d2f52ea8..60b161dc7f 100644 --- a/src/cunumeric/scan/scan_global_template.inl +++ b/src/cunumeric/scan/scan_global_template.inl @@ -39,7 +39,9 @@ struct ScanGlobalImpl { size_t volume = out_pitches.flatten(out_rect); Pitches sum_vals_pitches; - if (volume == 0) return; + if (volume == 0) { + return; + } auto out = args.out.read_write_accessor(out_rect); auto sum_vals = args.sum_vals.read_accessor(sum_vals_rect); diff --git a/src/cunumeric/scan/scan_local.cu b/src/cunumeric/scan/scan_local.cu index 4b2fe3d06b..e7ef12c9a2 100644 --- a/src/cunumeric/scan/scan_local.cu +++ b/src/cunumeric/scan/scan_local.cu @@ -33,7 +33,9 @@ static __global__ void __launch_bounds__(THREADS_PER_BLOCK, MIN_CTAS_PER_SM) lazy_kernel(RES* out, RES* sum_val) { const size_t idx = blockIdx.x * blockDim.x + threadIdx.x; - if (idx >= 1) return; + if (idx >= 1) { + return; + } sum_val[0] = out[0]; } diff --git a/src/cunumeric/search/argwhere.cc b/src/cunumeric/search/argwhere.cc index aa0a9fc721..8f24e231eb 100644 --- a/src/cunumeric/search/argwhere.cc +++ b/src/cunumeric/search/argwhere.cc @@ -44,7 +44,9 @@ struct ArgWhereImplBody { auto in_p = pitches.unflatten(idx, rect.lo); if (input[in_p] != VAL(0)) { - for (int i = 0; i < DIM; ++i) { out[Point<2>(out_idx, i)] = in_p[i]; } + for (int i = 0; i < DIM; ++i) { + out[Point<2>(out_idx, i)] = in_p[i]; + } out_idx++; } } diff --git a/src/cunumeric/search/argwhere.cu b/src/cunumeric/search/argwhere.cu index e4613b1f38..131bb582f4 100644 --- a/src/cunumeric/search/argwhere.cu +++ b/src/cunumeric/search/argwhere.cu @@ -32,12 +32,16 @@ static __global__ void __launch_bounds__(THREADS_PER_BLOCK, MIN_CTAS_PER_SM) Buffer output) { const size_t tid = blockIdx.x * blockDim.x + threadIdx.x; - if (tid >= volume) return; + if (tid >= volume) { + return; + } auto in_p = pitches.unflatten(tid, origin); if (in[in_p] != VAL(0)) { auto offset = offsets[tid]; - for (int32_t dim = 0; dim < DIM; ++dim) output[Point<2>(offset, dim)] = in_p[dim]; + for (int32_t dim = 0; dim < DIM; ++dim) { + output[Point<2>(offset, dim)] = in_p[dim]; + } } } diff --git a/src/cunumeric/search/argwhere_omp.cc b/src/cunumeric/search/argwhere_omp.cc index cebe6da701..012abde09b 100644 --- a/src/cunumeric/search/argwhere_omp.cc +++ b/src/cunumeric/search/argwhere_omp.cc @@ -39,7 +39,9 @@ struct ArgWhereImplBody { ThreadLocalStorage offsets(max_threads); { ThreadLocalStorage sizes(max_threads); - for (auto idx = 0; idx < max_threads; ++idx) sizes[idx] = 0; + for (auto idx = 0; idx < max_threads; ++idx) { + sizes[idx] = 0; + } #pragma omp parallel { const int tid = omp_get_thread_num(); @@ -49,9 +51,13 @@ struct ArgWhereImplBody { sizes[tid] += input[in_p] != VAL(0); } } - for (auto idx = 0; idx < max_threads; ++idx) size += sizes[idx]; + for (auto idx = 0; idx < max_threads; ++idx) { + size += sizes[idx]; + } offsets[0] = 0; - for (auto idx = 1; idx < max_threads; ++idx) offsets[idx] = offsets[idx - 1] + sizes[idx - 1]; + for (auto idx = 1; idx < max_threads; ++idx) { + offsets[idx] = offsets[idx - 1] + sizes[idx - 1]; + } } auto out = out_array.create_output_buffer(Point<2>(size, DIM), true); @@ -65,7 +71,9 @@ struct ArgWhereImplBody { auto in_p = pitches.unflatten(idx, rect.lo); if (input[in_p] != VAL(0)) { - for (int i = 0; i < DIM; ++i) { out[Point<2>(out_idx, i)] = in_p[i]; } + for (int i = 0; i < DIM; ++i) { + out[Point<2>(out_idx, i)] = in_p[i]; + } out_idx++; } } diff --git a/src/cunumeric/search/nonzero.cc b/src/cunumeric/search/nonzero.cc index 4bd25d1351..d76872264c 100644 --- a/src/cunumeric/search/nonzero.cc +++ b/src/cunumeric/search/nonzero.cc @@ -39,14 +39,19 @@ struct NonzeroImplBody { } std::vector> results; - for (auto& output : outputs) + for (auto& output : outputs) { results.push_back(output.create_output_buffer(Point<1>(size), true)); + } int64_t out_idx = 0; for (size_t idx = 0; idx < volume; ++idx) { auto point = pitches.unflatten(idx, rect.lo); - if (in[point] == VAL(0)) continue; - for (int32_t dim = 0; dim < DIM; ++dim) results[dim][out_idx] = point[dim]; + if (in[point] == VAL(0)) { + continue; + } + for (int32_t dim = 0; dim < DIM; ++dim) { + results[dim][out_idx] = point[dim]; + } ++out_idx; } assert(size == out_idx); diff --git a/src/cunumeric/search/nonzero.cu b/src/cunumeric/search/nonzero.cu index 90f0868450..d834d62947 100644 --- a/src/cunumeric/search/nonzero.cu +++ b/src/cunumeric/search/nonzero.cu @@ -30,12 +30,16 @@ static __global__ void __launch_bounds__(THREADS_PER_BLOCK, MIN_CTAS_PER_SM) Buffer p_results) { const size_t tid = blockIdx.x * blockDim.x + threadIdx.x; - if (tid >= volume) return; + if (tid >= volume) { + return; + } auto point = pitches.unflatten(tid, origin); if (in[point] != VAL(0)) { auto offset = offsets[tid]; - for (int32_t dim = 0; dim < DIM; ++dim) p_results[dim][offset] = point[dim]; + for (int32_t dim = 0; dim < DIM; ++dim) { + p_results[dim][offset] = point[dim]; + } } } @@ -53,7 +57,9 @@ struct NonzeroImplBody { { auto ndims = static_cast(results.size()); auto p_results = create_buffer(ndims, legate::Memory::Kind::Z_COPY_MEM); - for (int32_t dim = 0; dim < ndims; ++dim) p_results[dim] = results[dim].ptr(0); + for (int32_t dim = 0; dim < ndims; ++dim) { + p_results[dim] = results[dim].ptr(0); + } const size_t blocks = (volume + THREADS_PER_BLOCK - 1) / THREADS_PER_BLOCK; nonzero_kernel<<>>( @@ -72,10 +78,13 @@ struct NonzeroImplBody { auto size = compute_offsets(in, pitches, rect, volume, offsets, stream); std::vector> results; - for (auto& output : outputs) + for (auto& output : outputs) { results.push_back(output.create_output_buffer(Point<1>(size), true)); + } - if (size > 0) populate_nonzeros(in, pitches, rect, volume, results, offsets, stream); + if (size > 0) { + populate_nonzeros(in, pitches, rect, volume, results, offsets, stream); + } CHECK_CUDA_STREAM(stream); } }; diff --git a/src/cunumeric/search/nonzero.cuh b/src/cunumeric/search/nonzero.cuh index 725ad83917..486fdff0e9 100644 --- a/src/cunumeric/search/nonzero.cuh +++ b/src/cunumeric/search/nonzero.cuh @@ -69,9 +69,10 @@ int64_t compute_offsets(const AccessorRO& in, const size_t iters = (blocks + MAX_REDUCTION_CTAS - 1) / MAX_REDUCTION_CTAS; count_nonzero_kernel<<>>( volume, size, in, pitches, rect.lo, iters, offsets); - } else + } else { count_nonzero_kernel<<>>( volume, size, in, pitches, rect.lo, 1, offsets); + } auto p_offsets = offsets.ptr(0); diff --git a/src/cunumeric/search/nonzero_omp.cc b/src/cunumeric/search/nonzero_omp.cc index fbe88c8c1a..bb0e5ee387 100644 --- a/src/cunumeric/search/nonzero_omp.cc +++ b/src/cunumeric/search/nonzero_omp.cc @@ -41,7 +41,9 @@ struct NonzeroImplBody { { ThreadLocalStorage sizes(max_threads); - for (auto idx = 0; idx < max_threads; ++idx) sizes[idx] = 0; + for (auto idx = 0; idx < max_threads; ++idx) { + sizes[idx] = 0; + } #pragma omp parallel { const int tid = omp_get_thread_num(); @@ -52,15 +54,20 @@ struct NonzeroImplBody { } } - for (auto idx = 0; idx < max_threads; ++idx) size += sizes[idx]; + for (auto idx = 0; idx < max_threads; ++idx) { + size += sizes[idx]; + } offsets[0] = 0; - for (auto idx = 1; idx < max_threads; ++idx) offsets[idx] = offsets[idx - 1] + sizes[idx - 1]; + for (auto idx = 1; idx < max_threads; ++idx) { + offsets[idx] = offsets[idx - 1] + sizes[idx - 1]; + } } std::vector> results; - for (auto& output : outputs) + for (auto& output : outputs) { results.push_back(output.create_output_buffer(Point<1>(size), true)); + } #pragma omp parallel { @@ -69,8 +76,12 @@ struct NonzeroImplBody { #pragma omp for schedule(static) for (size_t idx = 0; idx < volume; ++idx) { auto point = pitches.unflatten(idx, rect.lo); - if (in[point] == VAL(0)) continue; - for (int32_t dim = 0; dim < DIM; ++dim) results[dim][out_idx] = point[dim]; + if (in[point] == VAL(0)) { + continue; + } + for (int32_t dim = 0; dim < DIM; ++dim) { + results[dim][out_idx] = point[dim]; + } ++out_idx; } } diff --git a/src/cunumeric/search/nonzero_template.inl b/src/cunumeric/search/nonzero_template.inl index d0bd697b03..ce44e51ee9 100644 --- a/src/cunumeric/search/nonzero_template.inl +++ b/src/cunumeric/search/nonzero_template.inl @@ -40,7 +40,9 @@ struct NonzeroImpl { size_t volume = pitches.flatten(rect); if (volume == 0) { - for (auto& store : args.results) store.bind_empty_data(); + for (auto& store : args.results) { + store.bind_empty_data(); + } return; } @@ -53,7 +55,9 @@ template static void nonzero_template(TaskContext& context) { std::vector outputs; - for (auto& output : context.outputs()) { outputs.emplace_back(output); } + for (auto& output : context.outputs()) { + outputs.emplace_back(output); + } NonzeroArgs args{context.input(0), std::move(outputs)}; double_dispatch(args.input.dim(), args.input.code(), NonzeroImpl{}, args); } diff --git a/src/cunumeric/set/unique.cc b/src/cunumeric/set/unique.cc index 9653c5cb14..838112030b 100644 --- a/src/cunumeric/set/unique.cc +++ b/src/cunumeric/set/unique.cc @@ -43,7 +43,9 @@ struct UniqueImplBody { auto result = output.create_output_buffer(dedup_set.size(), true); size_t pos = 0; - for (auto e : dedup_set) result[pos++] = e; + for (auto e : dedup_set) { + result[pos++] = e; + } } }; diff --git a/src/cunumeric/set/unique.cu b/src/cunumeric/set/unique.cu index b2a98553e5..567a804ad2 100644 --- a/src/cunumeric/set/unique.cu +++ b/src/cunumeric/set/unique.cu @@ -37,7 +37,9 @@ __global__ static void __launch_bounds__(THREADS_PER_BLOCK, MIN_CTAS_PER_SM) const size_t volume) { size_t offset = blockIdx.x * blockDim.x + threadIdx.x; - if (offset >= volume) return; + if (offset >= volume) { + return; + } auto point = pitches.unflatten(offset, lo); out[offset] = accessor[point]; } @@ -180,9 +182,10 @@ struct UniqueImplBody { auto buf_size = (get_aligned_size(result.second * sizeof(VAL)) + sizeof(VAL) - 1) / sizeof(VAL); assert(end - ptr <= buf_size); result.first = output.create_output_buffer(buf_size); - if (result.second > 0) + if (result.second > 0) { CHECK_CUDA(cudaMemcpyAsync( result.first.ptr(0), ptr, sizeof(VAL) * result.second, cudaMemcpyDeviceToDevice, stream)); + } if (comms.size() > 0) { // The launch domain is 1D because of the output region diff --git a/src/cunumeric/set/unique_omp.cc b/src/cunumeric/set/unique_omp.cc index c3199ee288..4df911fa29 100644 --- a/src/cunumeric/set/unique_omp.cc +++ b/src/cunumeric/set/unique_omp.cc @@ -68,7 +68,9 @@ struct UniqueImplBody { auto& final_dedup_set = dedup_set[0]; auto result = output.create_output_buffer(final_dedup_set.size(), true); size_t pos = 0; - for (auto e : final_dedup_set) result[pos++] = e; + for (auto e : final_dedup_set) { + result[pos++] = e; + } } }; diff --git a/src/cunumeric/sort/cub_sort.cuh b/src/cunumeric/sort/cub_sort.cuh index f569d715c9..b06f741237 100644 --- a/src/cunumeric/sort/cub_sort.cuh +++ b/src/cunumeric/sort/cub_sort.cuh @@ -182,10 +182,14 @@ void cub_local_sort(const VAL* values_in, stream); temp_storage.destroy(); } - if (indices_in == indices_out) idx_in.destroy(); + if (indices_in == indices_out) { + idx_in.destroy(); + } } - if (values_in == values_out) keys_in.destroy(); + if (values_in == values_out) { + keys_in.destroy(); + } } } // namespace detail diff --git a/src/cunumeric/sort/searchsorted.cc b/src/cunumeric/sort/searchsorted.cc index 9c9933c7bd..2487ff8ea8 100644 --- a/src/cunumeric/sort/searchsorted.cc +++ b/src/cunumeric/sort/searchsorted.cc @@ -64,7 +64,9 @@ struct SearchSortedImplBody { VAL key = input_v_ptr[idx]; auto v_point = pitches.unflatten(idx, rect_values.lo); int64_t upper_bound = std::upper_bound(input_ptr, input_ptr + volume, key) - input_ptr; - if (upper_bound > 0) { output_reduction.reduce(v_point, upper_bound + offset); } + if (upper_bound > 0) { + output_reduction.reduce(v_point, upper_bound + offset); + } } } } diff --git a/src/cunumeric/sort/searchsorted.cu b/src/cunumeric/sort/searchsorted.cu index 4b63202072..9cc8e85639 100644 --- a/src/cunumeric/sort/searchsorted.cu +++ b/src/cunumeric/sort/searchsorted.cu @@ -35,12 +35,16 @@ static __global__ void __launch_bounds__(THREADS_PER_BLOCK, MIN_CTAS_PER_SM) { const size_t v_idx = blockIdx.x * blockDim.x + threadIdx.x; - if (v_idx >= num_values) return; + if (v_idx >= num_values) { + return; + } auto v_point = pitches.unflatten(v_idx, lo); int64_t lower_bound = cub::LowerBound(sorted_array.ptr(global_offset), volume, values[v_point]); - if (lower_bound < volume) { output_reduction.reduce(v_point, lower_bound + global_offset); } + if (lower_bound < volume) { + output_reduction.reduce(v_point, lower_bound + global_offset); + } } template @@ -56,12 +60,16 @@ static __global__ void __launch_bounds__(THREADS_PER_BLOCK, MIN_CTAS_PER_SM) { const size_t v_idx = blockIdx.x * blockDim.x + threadIdx.x; - if (v_idx >= num_values) return; + if (v_idx >= num_values) { + return; + } auto v_point = pitches.unflatten(v_idx, lo); int64_t upper_bound = cub::UpperBound(sorted_array.ptr(global_offset), volume, values[v_point]); - if (upper_bound > 0) { output_reduction.reduce(v_point, upper_bound + global_offset); } + if (upper_bound > 0) { + output_reduction.reduce(v_point, upper_bound + global_offset); + } } template diff --git a/src/cunumeric/sort/searchsorted_omp.cc b/src/cunumeric/sort/searchsorted_omp.cc index ae7714c829..7a830d85dc 100644 --- a/src/cunumeric/sort/searchsorted_omp.cc +++ b/src/cunumeric/sort/searchsorted_omp.cc @@ -68,7 +68,9 @@ struct SearchSortedImplBody { VAL key = input_v_ptr[idx]; auto v_point = pitches.unflatten(idx, rect_values.lo); int64_t upper_bound = std::upper_bound(input_ptr, input_ptr + volume, key) - input_ptr; - if (upper_bound > 0) { output_reduction.reduce(v_point, upper_bound + offset); } + if (upper_bound > 0) { + output_reduction.reduce(v_point, upper_bound + offset); + } } } } diff --git a/src/cunumeric/sort/searchsorted_template.inl b/src/cunumeric/sort/searchsorted_template.inl index ed967aa980..f0ae0feb50 100644 --- a/src/cunumeric/sort/searchsorted_template.inl +++ b/src/cunumeric/sort/searchsorted_template.inl @@ -36,8 +36,12 @@ struct SearchSortedImpl { auto rect_values_in = args.input_values.shape(); auto rect_values_out = args.output_reduction.shape(); - if (rect_base.empty()) return; - if (rect_values_in.empty()) return; + if (rect_base.empty()) { + return; + } + if (rect_values_in.empty()) { + return; + } Pitches<0> pitches_base; Pitches pitches_values; diff --git a/src/cunumeric/sort/sort.cu b/src/cunumeric/sort/sort.cu index e323633a30..6281c2cf1f 100644 --- a/src/cunumeric/sort/sort.cu +++ b/src/cunumeric/sort/sort.cu @@ -146,7 +146,9 @@ __global__ static void __launch_bounds__(THREADS_PER_BLOCK, MIN_CTAS_PER_SM) const size_t my_sort_rank) { const size_t splitter_idx_g = blockIdx.x * blockDim.x + threadIdx.x; - if (splitter_idx_g >= num_splitters) return; + if (splitter_idx_g >= num_splitters) { + return; + } const size_t num_splitters_per_segment = num_splitters / num_segments_l; const size_t splitter_pos = splitter_idx_g % num_splitters_per_segment; @@ -182,7 +184,9 @@ __global__ static void __launch_bounds__(THREADS_PER_BLOCK, MIN_CTAS_PER_SM) const size_t num_send_parts) { const size_t send_part = blockIdx.x * blockDim.x + threadIdx.x; - if (send_part >= num_send_parts) return; + if (send_part >= num_send_parts) { + return; + } const size_t rank = send_part / num_segments_l; const size_t segment = send_part % num_segments_l; @@ -251,7 +255,9 @@ __global__ static void __launch_bounds__(THREADS_PER_BLOCK, MIN_CTAS_PER_SM) } } // also store sum of all in last element - if (threadId == 0) { size_send[rank * num_segments_l_aligned + num_segments_l] = prefix_op(0); } + if (threadId == 0) { + size_send[rank * num_segments_l_aligned + num_segments_l] = prefix_op(0); + } } __global__ static void __launch_bounds__(THREADS_PER_BLOCK, MIN_CTAS_PER_SM) @@ -261,12 +267,16 @@ __global__ static void __launch_bounds__(THREADS_PER_BLOCK, MIN_CTAS_PER_SM) const size_t num_segment_ids) { const size_t segment_idx = blockIdx.x * blockDim.x + threadIdx.x; - if (segment_idx >= num_segments_l) return; + if (segment_idx >= num_segments_l) { + return; + } unsigned long long int* ptr = (unsigned long long int*)segment_ids; const size_t position = start_positions[segment_idx]; - if (position < num_segment_ids) atomicAdd(&(ptr[position]), (unsigned long long int)1l); + if (position < num_segment_ids) { + atomicAdd(&(ptr[position]), (unsigned long long int)1l); + } } __global__ static void __launch_bounds__(THREADS_PER_BLOCK, MIN_CTAS_PER_SM) @@ -277,7 +287,9 @@ __global__ static void __launch_bounds__(THREADS_PER_BLOCK, MIN_CTAS_PER_SM) const size_t segments_size_l) { const size_t segment_idx = blockIdx.x * blockDim.x + threadIdx.x; - if (segment_idx >= num_segments_l) return; + if (segment_idx >= num_segments_l) { + return; + } if (num_segments_l == 1) { segments_diff[segment_idx] = size - segments_size_l; @@ -306,7 +318,9 @@ __global__ static void __launch_bounds__(THREADS_PER_BLOCK, MIN_CTAS_PER_SM) const size_t thread_offset = blockIdx.x * blockDim.x + threadIdx.x; const size_t threadgroup_size = blockDim.x * gridDim.x; const size_t segment_id = blockIdx.y * blockDim.y + threadIdx.y; - if (segment_id >= num_segments_l) return; + if (segment_id >= num_segments_l) { + return; + } size_t source_offset = segment_size_l * segment_id; for (int r = 0; r < num_sort_ranks; ++r) { @@ -331,7 +345,9 @@ __global__ static void __launch_bounds__(THREADS_PER_BLOCK, MIN_CTAS_PER_SM) const size_t thread_offset = blockIdx.x * blockDim.x + threadIdx.x; const size_t threadgroup_size = blockDim.x * gridDim.x; const size_t rank_id = blockIdx.y * blockDim.y + threadIdx.y; - if (rank_id >= num_sort_ranks) return; + if (rank_id >= num_sort_ranks) { + return; + } size_t target_offset = target_offsets[rank_id]; size_t local_size = (rank_id == num_sort_ranks - 1) @@ -362,7 +378,9 @@ __global__ static void __launch_bounds__(THREADS_PER_BLOCK, MIN_CTAS_PER_SM) const size_t thread_offset = blockIdx.x * blockDim.x + threadIdx.x; const size_t threadgroup_size = blockDim.x * gridDim.x; const size_t segment_id = blockIdx.y * blockDim.y + threadIdx.y; - if (segment_id >= num_segments_l) return; + if (segment_id >= num_segments_l) { + return; + } // copy left { @@ -411,7 +429,9 @@ __global__ static void __launch_bounds__(THREADS_PER_BLOCK, MIN_CTAS_PER_SM) const size_t thread_offset = blockIdx.x * blockDim.x + threadIdx.x; const size_t threadgroup_size = blockDim.x * gridDim.x; const size_t segment_id = blockIdx.y * blockDim.y + threadIdx.y; - if (segment_id >= num_segments_l) return; + if (segment_id >= num_segments_l) { + return; + } size_t target_offset = segment_id * segment_size_l; size_t source_start = segment_size_l * segment_id + segment_diff_pos[segment_id]; @@ -422,8 +442,12 @@ __global__ static void __launch_bounds__(THREADS_PER_BLOCK, MIN_CTAS_PER_SM) int64_t recv_left_size = send_left[segment_id] * -1; int64_t recv_right_size = send_right[segment_id] * -1; - if (recv_left_size < 0) source_start -= recv_left_size; - if (recv_right_size < 0) source_end += recv_right_size; + if (recv_left_size < 0) { + source_start -= recv_left_size; + } + if (recv_right_size < 0) { + source_end += recv_right_size; + } // copy from left { @@ -476,7 +500,9 @@ __global__ static void __launch_bounds__(THREADS_PER_BLOCK, MIN_CTAS_PER_SM) { const size_t sample_idx = blockIdx.x * blockDim.x + threadIdx.x; const size_t num_samples_l = num_samples_per_segment_l * num_segments_l; - if (sample_idx >= num_samples_l) return; + if (sample_idx >= num_samples_l) { + return; + } const size_t segment_id_l = sample_idx / num_samples_per_segment_l; const size_t segment_sample_idx = sample_idx % num_samples_per_segment_l; @@ -631,7 +657,9 @@ SegmentMergePiece> merge_all_buffers( for (size_t i = 0; i < num_sort_ranks; ++i) { SegmentMergePiece piece = merge_buffers[i]; piece.values.destroy(); - if (argsort) { piece.indices.destroy(); } + if (argsort) { + piece.indices.destroy(); + } } merge_buffers.clear(); } @@ -732,8 +760,12 @@ SegmentMergePiece> merge_all_buffers( for (size_t i = 0; i < destroy_queue.size(); ++i) { SegmentMergePiece piece = destroy_queue[i]; piece.values.destroy(); - if (segmented) { piece.segments.destroy(); } - if (argsort) { piece.indices.destroy(); } + if (segmented) { + piece.segments.destroy(); + } + if (argsort) { + piece.indices.destroy(); + } } destroy_queue.clear(); } @@ -795,7 +827,9 @@ void rebalance_data(SegmentMergePiece& merge_buffer, } merge_buffer.segments.destroy(); - if (argsort) { merge_buffer.values.destroy(); } + if (argsort) { + merge_buffer.values.destroy(); + } #ifdef DEBUG_CUNUMERIC { @@ -1511,7 +1545,9 @@ void sample_sort_nccl_nd( } local_sorted.values.destroy(); - if (argsort) local_sorted.indices.destroy(); + if (argsort) { + local_sorted.indices.destroy(); + } segment_blocks.destroy(); } @@ -1565,36 +1601,40 @@ void sample_sort_nccl_nd( // communicate all2all (in sort dimension) CHECK_NCCL(ncclGroupStart()); for (size_t r = 0; r < num_sort_ranks; r++) { - if (size_send_total[r] > 0) + if (size_send_total[r] > 0) { CHECK_NCCL(ncclSend(val_send_buffers[r].ptr(0), size_send_total[r] * sizeof(VAL), ncclInt8, sort_ranks[r], *comm, stream)); - if (merge_buffers[r].size > 0) + } + if (merge_buffers[r].size > 0) { CHECK_NCCL(ncclRecv(merge_buffers[r].values.ptr(0), merge_buffers[r].size * sizeof(VAL), ncclInt8, sort_ranks[r], *comm, stream)); + } } CHECK_NCCL(ncclGroupEnd()); if (argsort) { CHECK_NCCL(ncclGroupStart()); for (size_t r = 0; r < num_sort_ranks; r++) { - if (size_send_total[r] > 0) + if (size_send_total[r] > 0) { CHECK_NCCL(ncclSend( idc_send_buffers[r].ptr(0), size_send_total[r], ncclInt64, sort_ranks[r], *comm, stream)); - if (merge_buffers[r].size > 0) + } + if (merge_buffers[r].size > 0) { CHECK_NCCL(ncclRecv(merge_buffers[r].indices.ptr(0), merge_buffers[r].size, ncclInt64, sort_ranks[r], *comm, stream)); + } } CHECK_NCCL(ncclGroupEnd()); } @@ -1606,7 +1646,9 @@ void sample_sort_nccl_nd( size_recv_total.destroy(); for (size_t r = 0; r < num_sort_ranks; r++) { val_send_buffers[r].destroy(); - if (argsort) idc_send_buffers[r].destroy(); + if (argsort) { + idc_send_buffers[r].destroy(); + } } CHECK_CUDA_STREAM(stream); @@ -1758,7 +1800,9 @@ struct SortImplBody { assert(is_index_space || is_unbound_1d_storage); std::vector sort_ranks(num_sort_ranks); size_t rank_group = local_rank / num_sort_ranks; - for (size_t r = 0; r < num_sort_ranks; ++r) sort_ranks[r] = rank_group * num_sort_ranks + r; + for (size_t r = 0; r < num_sort_ranks; ++r) { + sort_ranks[r] = rank_group * num_sort_ranks + r; + } void* output_ptr = nullptr; // in case the storage *is NOT* unbound -- we provide a target pointer diff --git a/src/cunumeric/sort/sort.h b/src/cunumeric/sort/sort.h index c07f4a73be..bb63ae6c12 100644 --- a/src/cunumeric/sort/sort.h +++ b/src/cunumeric/sort/sort.h @@ -66,7 +66,9 @@ struct SegmentSampleComparator return lhs.segment < rhs.segment; } else { // special case for unused samples - if (lhs.rank < 0 || rhs.rank < 0) { return rhs.rank < 0 && lhs.rank >= 0; } + if (lhs.rank < 0 || rhs.rank < 0) { + return rhs.rank < 0 && lhs.rank >= 0; + } if (lhs.value != rhs.value) { return lhs.value < rhs.value; diff --git a/src/cunumeric/sort/sort_cpu.inl b/src/cunumeric/sort/sort_cpu.inl index 6fac41f2c7..27d37e05c0 100644 --- a/src/cunumeric/sort/sort_cpu.inl +++ b/src/cunumeric/sort/sort_cpu.inl @@ -116,7 +116,9 @@ void rebalance_data(SegmentMergePiece& merge_buffer, } merge_buffer.segments.destroy(); - if (argsort) { merge_buffer.values.destroy(); } + if (argsort) { + merge_buffer.values.destroy(); + } #ifdef DEBUG_CUNUMERIC { @@ -230,14 +232,16 @@ void rebalance_data(SegmentMergePiece& merge_buffer, size_t send_right_size = 0; size_t recv_right_size = 0; for (size_t segment = 0; segment < num_segments_l; ++segment) { - if (send_left[segment] > 0) + if (send_left[segment] > 0) { send_left_size += send_left[segment]; - else + } else { recv_left_size -= send_left[segment]; - if (send_right[segment] > 0) + } + if (send_right[segment] > 0) { send_right_size += send_right[segment]; - else + } else { recv_right_size -= send_right[segment]; + } } SortPiece send_leftright_data, recv_leftright_data; @@ -261,14 +265,15 @@ void rebalance_data(SegmentMergePiece& merge_buffer, // copy left if (send_left[segment] > 0) { size_t size = send_left[segment]; - if (argsort) + if (argsort) { std::memcpy(send_leftright_data.indices.ptr(send_left_pos), merge_buffer.indices.ptr(segment_start), size * sizeof(int64_t)); - else + } else { std::memcpy(send_leftright_data.values.ptr(send_left_pos), merge_buffer.values.ptr(segment_start), size * sizeof(VAL)); + } send_left_pos += size; } @@ -277,14 +282,15 @@ void rebalance_data(SegmentMergePiece& merge_buffer, // copy right if (send_right[segment] > 0) { size_t size = send_right[segment]; - if (argsort) + if (argsort) { std::memcpy(send_leftright_data.indices.ptr(send_right_pos), merge_buffer.indices.ptr(segment_start - size), size * sizeof(int64_t)); - else + } else { std::memcpy(send_leftright_data.values.ptr(send_right_pos), merge_buffer.values.ptr(segment_start - size), size * sizeof(VAL)); + } send_right_pos += size; } } @@ -390,7 +396,9 @@ void rebalance_data(SegmentMergePiece& merge_buffer, offset_src = send_left[segment]; copy_size -= offset_src; } - if (send_right[segment] > 0) copy_size -= send_right[segment]; + if (send_right[segment] > 0) { + copy_size -= send_right[segment]; + } if (argsort) { std::memcpy(output_indices + target_pos, @@ -602,10 +610,11 @@ void sample_sort_nd( // check whether we have invalid samples (in case one participant did not have enough) int32_t num_usable_samples_per_segment = num_samples_per_segment_g; for (int32_t i = num_samples_per_segment_g - 1; i >= 0; i--) { - if (p_samples[i].rank != -1) + if (p_samples[i].rank != -1) { break; - else + } else { num_usable_samples_per_segment--; + } } // segment_blocks[r][segment]->global position in data for segment and r @@ -736,7 +745,9 @@ void sample_sort_nd( } local_sorted.values.destroy(); - if (argsort) local_sorted.indices.destroy(); + if (argsort) { + local_sorted.indices.destroy(); + } segment_blocks.destroy(); positions.destroy(); @@ -832,7 +843,9 @@ void sample_sort_nd( size_send.destroy(); size_recv.destroy(); val_send_buffer.destroy(); - if (argsort) idc_send_buffer.destroy(); + if (argsort) { + idc_send_buffer.destroy(); + } ///////////////////////////////////////////////////////////////////////////////////////////////// /////////////// Part 4: merge data @@ -977,7 +990,9 @@ struct SortImplBodyCpu { if (volume > 0) { // sort data (locally) auto* src = input.ptr(rect.lo); - if (src != values_ptr) std::copy(src, src + volume, values_ptr); + if (src != values_ptr) { + std::copy(src, src + volume, values_ptr); + } thrust_local_sort_inplace(values_ptr, indices_ptr, volume, segment_size_l, stable, exec); } @@ -986,7 +1001,9 @@ struct SortImplBodyCpu { assert(is_index_space || is_unbound_1d_storage); std::vector sort_ranks(num_sort_ranks); size_t rank_group = local_rank / num_sort_ranks; - for (size_t r = 0; r < num_sort_ranks; ++r) sort_ranks[r] = rank_group * num_sort_ranks + r; + for (size_t r = 0; r < num_sort_ranks; ++r) { + sort_ranks[r] = rank_group * num_sort_ranks + r; + } void* output_ptr = nullptr; // in case the storage *is NOT* unbound -- we provide a target pointer diff --git a/src/cunumeric/sort/sort_template.inl b/src/cunumeric/sort/sort_template.inl index 9ed6a38811..ceac66c758 100644 --- a/src/cunumeric/sort/sort_template.inl +++ b/src/cunumeric/sort/sort_template.inl @@ -33,7 +33,9 @@ static int get_rank(Domain domain, DomainPoint index_point) auto hi = domain.hi(); auto lo = domain.lo(); for (int i = 0; i < domain.get_dim(); ++i) { - if (i > 0) domain_index *= hi[i] - lo[i] + 1; + if (i > 0) { + domain_index *= hi[i] - lo[i] + 1; + } domain_index += index_point[i]; } return domain_index; @@ -62,7 +64,9 @@ struct SortImpl { // we shall not return on empty rectangle in case of distributed sort data // as the process needs to participate in collective communication // to identify rank-index to sort participant mapping - if ((segment_size_l == args.segment_size_g || !args.is_index_space) && rect.empty()) return; + if ((segment_size_l == args.segment_size_g || !args.is_index_space) && rect.empty()) { + return; + } SortImplBody()(args.input, args.output, diff --git a/src/cunumeric/stat/bincount.cu b/src/cunumeric/stat/bincount.cu index e6c5647e44..81d0e91b36 100644 --- a/src/cunumeric/stat/bincount.cu +++ b/src/cunumeric/stat/bincount.cu @@ -29,7 +29,9 @@ static __device__ inline void _bincount(int32_t* bins, Point<1> origin) { // Initialize the bins to 0 - for (int32_t bin = threadIdx.x; bin < num_bins; bin += blockDim.x) bins[bin] = 0; + for (int32_t bin = threadIdx.x; bin < num_bins; bin += blockDim.x) { + bins[bin] = 0; + } __syncthreads(); // Start reading values and do atomic updates to shared @@ -58,7 +60,9 @@ static __device__ inline void _weighted_bincount(double* bins, Point<1> origin) { // Initialize the bins to 0 - for (int32_t bin = threadIdx.x; bin < num_bins; bin += blockDim.x) bins[bin] = 0; + for (int32_t bin = threadIdx.x; bin < num_bins; bin += blockDim.x) { + bins[bin] = 0; + } __syncthreads(); // Start reading values and do atomic updates to shared @@ -92,7 +96,9 @@ static __global__ void __launch_bounds__(THREADS_PER_BLOCK, MIN_CTAS_PER_SM) // Now do the atomics out to global memory for (int32_t bin = threadIdx.x; bin < num_bins; bin += blockDim.x) { const auto count = bins[bin]; - if (count > 0) lhs.reduce(bin, count); + if (count > 0) { + lhs.reduce(bin, count); + } } } @@ -104,7 +110,9 @@ static __global__ void bincount_kernel_rd_global(AccessorRD= volume) return; + if (idx >= volume) { + return; + } auto bin = rhs[idx + origin[0]]; lhs[bin] <<= 1; } @@ -138,7 +146,9 @@ static __global__ void weighted_bincount_kernel_rd_global( { // Just blast out the atomic writes into global memory. auto idx = global_tid_1d(); - if (idx >= volume) return; + if (idx >= volume) { + return; + } auto bin = rhs[idx + origin[0]]; lhs[bin] <<= weights[idx + origin[0]]; } diff --git a/src/cunumeric/stat/bincount_omp.cc b/src/cunumeric/stat/bincount_omp.cc index 1b040d9af0..94c10e21e0 100644 --- a/src/cunumeric/stat/bincount_omp.cc +++ b/src/cunumeric/stat/bincount_omp.cc @@ -84,9 +84,11 @@ struct BincountImplBody { const Rect<1>& lhs_rect) const { auto all_local_bins = _bincount(rhs, rect, lhs_rect); - for (auto& local_bins : all_local_bins) - for (size_t bin_num = 0; bin_num < local_bins.size(); ++bin_num) + for (auto& local_bins : all_local_bins) { + for (size_t bin_num = 0; bin_num < local_bins.size(); ++bin_num) { lhs.reduce(bin_num, local_bins[bin_num]); + } + } } void operator()(AccessorRD, true, 1> lhs, @@ -96,9 +98,11 @@ struct BincountImplBody { const Rect<1>& lhs_rect) const { auto all_local_bins = _bincount(rhs, weights, rect, lhs_rect); - for (auto& local_bins : all_local_bins) - for (size_t bin_num = 0; bin_num < local_bins.size(); ++bin_num) + for (auto& local_bins : all_local_bins) { + for (size_t bin_num = 0; bin_num < local_bins.size(); ++bin_num) { lhs.reduce(bin_num, local_bins[bin_num]); + } + } } }; diff --git a/src/cunumeric/stat/bincount_template.inl b/src/cunumeric/stat/bincount_template.inl index 5379b85e2e..0ef963787f 100644 --- a/src/cunumeric/stat/bincount_template.inl +++ b/src/cunumeric/stat/bincount_template.inl @@ -35,7 +35,9 @@ struct BincountImpl { auto rect = args.rhs.shape<1>(); auto lhs_rect = args.lhs.shape<1>(); - if (rect.empty()) return; + if (rect.empty()) { + return; + } auto rhs = args.rhs.read_accessor(rect); if (args.has_weights) { diff --git a/src/cunumeric/stat/histogram_impl.h b/src/cunumeric/stat/histogram_impl.h index d397f6dd86..79d37b10a8 100644 --- a/src/cunumeric/stat/histogram_impl.h +++ b/src/cunumeric/stat/histogram_impl.h @@ -38,22 +38,25 @@ struct lower_bound_op_t { // upcast to bin_t: // bin_t left_up = static_cast(left); - if (right == sentinel) + if (right == sentinel) { return left_up <= right; - else + } else { return left_up < right; + } } else if constexpr (std::is_same_v && std::is_integral_v) { // upcast to elem_t: // - if (right == sentinel) + if (right == sentinel) { return left <= static_cast(right); - else + } else { return left < right; + } } else { - if (right == sentinel) + if (right == sentinel) { return left <= right; - else + } else { return left < right; + } } } @@ -90,7 +93,9 @@ void histogram_weights(exe_policy_t exe_pol, alloc_t alloc_w; - if (!ptr_w) { ptr_w = alloc_w(exe_pol, n_samples, 1); } + if (!ptr_w) { + ptr_w = alloc_w(exe_pol, n_samples, 1); + } // in-place src sort + corresponding weights shuffle: // diff --git a/src/cunumeric/stat/histogram_template.inl b/src/cunumeric/stat/histogram_template.inl index f352eb3dd7..e782956abe 100644 --- a/src/cunumeric/stat/histogram_template.inl +++ b/src/cunumeric/stat/histogram_template.inl @@ -46,7 +46,9 @@ struct HistogramImpl { auto bins_rect = args.bins.shape<1>(); auto weights_rect = args.weights.shape<1>(); - if (src_rect.empty()) return; + if (src_rect.empty()) { + return; + } auto result = args.result.reduce_accessor, true, 1>(result_rect); auto src = args.src.read_accessor(src_rect); diff --git a/src/cunumeric/ternary/where.cc b/src/cunumeric/ternary/where.cc index 8549986b3a..31a7284c1b 100644 --- a/src/cunumeric/ternary/where.cc +++ b/src/cunumeric/ternary/where.cc @@ -40,8 +40,9 @@ struct WhereImplBody { auto maskptr = mask.ptr(rect); auto in1ptr = in1.ptr(rect); auto in2ptr = in2.ptr(rect); - for (size_t idx = 0; idx < volume; ++idx) + for (size_t idx = 0; idx < volume; ++idx) { outptr[idx] = maskptr[idx] ? in1ptr[idx] : in2ptr[idx]; + } } else { for (size_t idx = 0; idx < volume; ++idx) { auto point = pitches.unflatten(idx, rect.lo); diff --git a/src/cunumeric/ternary/where.cu b/src/cunumeric/ternary/where.cu index a4eb93a114..8833636411 100644 --- a/src/cunumeric/ternary/where.cu +++ b/src/cunumeric/ternary/where.cu @@ -26,7 +26,9 @@ static __global__ void __launch_bounds__(THREADS_PER_BLOCK, MIN_CTAS_PER_SM) dense_kernel(size_t volume, VAL* out, const bool* mask, const VAL* in1, const VAL* in2) { const size_t idx = global_tid_1d(); - if (idx >= volume) return; + if (idx >= volume) { + return; + } out[idx] = mask[idx] ? in1[idx] : in2[idx]; } @@ -35,7 +37,9 @@ static __global__ void __launch_bounds__(THREADS_PER_BLOCK, MIN_CTAS_PER_SM) gen size_t volume, WriteAcc out, MaskAcc mask, ReadAcc in1, ReadAcc in2, Pitches pitches, Rect rect) { const size_t idx = global_tid_1d(); - if (idx >= volume) return; + if (idx >= volume) { + return; + } auto point = pitches.unflatten(idx, rect.lo); out[point] = mask[point] ? in1[point] : in2[point]; } diff --git a/src/cunumeric/ternary/where_omp.cc b/src/cunumeric/ternary/where_omp.cc index 8d9283988e..b7d54d0cd1 100644 --- a/src/cunumeric/ternary/where_omp.cc +++ b/src/cunumeric/ternary/where_omp.cc @@ -41,8 +41,9 @@ struct WhereImplBody { auto in1ptr = in1.ptr(rect); auto in2ptr = in2.ptr(rect); #pragma omp parallel for schedule(static) - for (size_t idx = 0; idx < volume; ++idx) + for (size_t idx = 0; idx < volume; ++idx) { outptr[idx] = maskptr[idx] ? in1ptr[idx] : in2ptr[idx]; + } } else { #pragma omp parallel for schedule(static) for (size_t idx = 0; idx < volume; ++idx) { diff --git a/src/cunumeric/ternary/where_template.inl b/src/cunumeric/ternary/where_template.inl index 8bc57e5992..8d87158fd5 100644 --- a/src/cunumeric/ternary/where_template.inl +++ b/src/cunumeric/ternary/where_template.inl @@ -39,7 +39,9 @@ struct WhereImpl { Pitches pitches; size_t volume = pitches.flatten(rect); - if (volume == 0) return; + if (volume == 0) { + return; + } auto out = args.out.write_accessor(rect); auto mask = args.mask.read_accessor(rect); diff --git a/src/cunumeric/transform/flip.cc b/src/cunumeric/transform/flip.cc index 3ca356a413..6ec4e83650 100644 --- a/src/cunumeric/transform/flip.cc +++ b/src/cunumeric/transform/flip.cc @@ -34,8 +34,9 @@ struct FlipImplBody { { for (PointInRectIterator itr(rect); itr.valid(); ++itr) { auto q = *itr; - for (uint32_t idx = 0; idx < axes.size(); ++idx) + for (uint32_t idx = 0; idx < axes.size(); ++idx) { q[axes[idx]] = rect.hi[axes[idx]] - q[axes[idx]]; + } out[*itr] = in[q]; } } diff --git a/src/cunumeric/transform/flip.cu b/src/cunumeric/transform/flip.cu index f689ec0fc0..796c9aaa73 100644 --- a/src/cunumeric/transform/flip.cu +++ b/src/cunumeric/transform/flip.cu @@ -34,10 +34,14 @@ static __global__ void __launch_bounds__(THREADS_PER_BLOCK, MIN_CTAS_PER_SM) const uint32_t num_axes) { const size_t idx = global_tid_1d(); - if (idx >= volume) return; + if (idx >= volume) { + return; + } auto p = pitches.unflatten(idx, rect.lo); auto q = p; - for (uint32_t idx = 0; idx < num_axes; ++idx) q[axes[idx]] = rect.hi[axes[idx]] - q[axes[idx]]; + for (uint32_t idx = 0; idx < num_axes; ++idx) { + q[axes[idx]] = rect.hi[axes[idx]] - q[axes[idx]]; + } out[p] = in[q]; } @@ -56,7 +60,9 @@ struct FlipImplBody { const size_t blocks = (volume + THREADS_PER_BLOCK - 1) / THREADS_PER_BLOCK; auto num_axes = axes.size(); auto gpu_axes = create_buffer(num_axes, Memory::Kind::Z_COPY_MEM); - for (uint32_t idx = 0; idx < num_axes; ++idx) gpu_axes[idx] = axes[idx]; + for (uint32_t idx = 0; idx < num_axes; ++idx) { + gpu_axes[idx] = axes[idx]; + } auto stream = get_cached_stream(); flip_kernel<<>>( volume, out, in, pitches, rect, gpu_axes, num_axes); diff --git a/src/cunumeric/transform/flip_omp.cc b/src/cunumeric/transform/flip_omp.cc index a9c5a269c4..d85fb0d60f 100644 --- a/src/cunumeric/transform/flip_omp.cc +++ b/src/cunumeric/transform/flip_omp.cc @@ -37,8 +37,9 @@ struct FlipImplBody { for (size_t idx = 0; idx < volume; ++idx) { auto p = pitches.unflatten(idx, rect.lo); auto q = p; - for (uint32_t idx = 0; idx < axes.size(); ++idx) + for (uint32_t idx = 0; idx < axes.size(); ++idx) { q[axes[idx]] = rect.hi[axes[idx]] - q[axes[idx]]; + } out[p] = in[q]; } } diff --git a/src/cunumeric/transform/flip_template.inl b/src/cunumeric/transform/flip_template.inl index 32285318e8..1a79cb247d 100644 --- a/src/cunumeric/transform/flip_template.inl +++ b/src/cunumeric/transform/flip_template.inl @@ -39,7 +39,9 @@ struct FlipImpl { Pitches pitches; size_t volume = pitches.flatten(rect); - if (volume == 0) return; + if (volume == 0) { + return; + } auto out = args.out.write_accessor(rect); auto in = args.in.read_accessor(rect); diff --git a/src/cunumeric/unary/convert.cc b/src/cunumeric/unary/convert.cc index 66aaa4f83e..a8d4af8450 100644 --- a/src/cunumeric/unary/convert.cc +++ b/src/cunumeric/unary/convert.cc @@ -38,7 +38,9 @@ struct ConvertImplBody { if (dense) { auto outptr = out.ptr(rect); auto inptr = in.ptr(rect); - for (size_t idx = 0; idx < volume; ++idx) outptr[idx] = func(inptr[idx]); + for (size_t idx = 0; idx < volume; ++idx) { + outptr[idx] = func(inptr[idx]); + } } else { for (size_t idx = 0; idx < volume; ++idx) { auto p = pitches.unflatten(idx, rect.lo); diff --git a/src/cunumeric/unary/convert.cu b/src/cunumeric/unary/convert.cu index d1f242e4f7..4091a994d4 100644 --- a/src/cunumeric/unary/convert.cu +++ b/src/cunumeric/unary/convert.cu @@ -26,7 +26,9 @@ static __global__ void __launch_bounds__(THREADS_PER_BLOCK, MIN_CTAS_PER_SM) dense_kernel(size_t volume, Function func, RES* out, const ARG* in) { const size_t idx = global_tid_1d(); - if (idx >= volume) return; + if (idx >= volume) { + return; + } out[idx] = func(in[idx]); } @@ -35,7 +37,9 @@ static __global__ void __launch_bounds__(THREADS_PER_BLOCK, MIN_CTAS_PER_SM) generic_kernel(size_t volume, Function func, WriteAcc out, ReadAcc in, Pitches pitches, Rect rect) { const size_t idx = global_tid_1d(); - if (idx >= volume) return; + if (idx >= volume) { + return; + } auto point = pitches.unflatten(idx, rect.lo); out[point] = func(in[point]); } diff --git a/src/cunumeric/unary/convert_omp.cc b/src/cunumeric/unary/convert_omp.cc index ebe1bfe3ec..163b495eed 100644 --- a/src/cunumeric/unary/convert_omp.cc +++ b/src/cunumeric/unary/convert_omp.cc @@ -39,7 +39,9 @@ struct ConvertImplBody { auto outptr = out.ptr(rect); auto inptr = in.ptr(rect); #pragma omp parallel for schedule(static) - for (size_t idx = 0; idx < volume; ++idx) outptr[idx] = func(inptr[idx]); + for (size_t idx = 0; idx < volume; ++idx) { + outptr[idx] = func(inptr[idx]); + } } else { #pragma omp parallel for schedule(static) for (size_t idx = 0; idx < volume; ++idx) { diff --git a/src/cunumeric/unary/convert_template.inl b/src/cunumeric/unary/convert_template.inl index 393b853d05..f69be96eac 100644 --- a/src/cunumeric/unary/convert_template.inl +++ b/src/cunumeric/unary/convert_template.inl @@ -42,7 +42,9 @@ struct ConvertImpl { Pitches pitches; size_t volume = pitches.flatten(rect); - if (volume == 0) return; + if (volume == 0) { + return; + } auto out = args.out.write_accessor(rect); auto in = args.in.read_accessor(rect); diff --git a/src/cunumeric/unary/convert_util.h b/src/cunumeric/unary/convert_util.h index 0f67f593d3..b893c95e18 100644 --- a/src/cunumeric/unary/convert_util.h +++ b/src/cunumeric/unary/convert_util.h @@ -64,10 +64,11 @@ struct ConvertOp { !legate::is_complex_type::value>* = nullptr> constexpr DST operator()(const _SRC& src) const { - if constexpr (DST_TYPE == legate::Type::Code::BOOL) + if constexpr (DST_TYPE == legate::Type::Code::BOOL) { return static_cast(src.real()) || static_cast(src.imag()); - else + } else { return static_cast(src.real()); + } // Unreachable assert(false); return DST{}; diff --git a/src/cunumeric/unary/scalar_unary_red_template.inl b/src/cunumeric/unary/scalar_unary_red_template.inl index f746f96d3a..b8ad212cea 100644 --- a/src/cunumeric/unary/scalar_unary_red_template.inl +++ b/src/cunumeric/unary/scalar_unary_red_template.inl @@ -60,7 +60,9 @@ struct ScalarUnaryRed { shape = args.shape; out = args.out.reduce_accessor(); - if constexpr (OP_CODE == UnaryRedCode::CONTAINS) { to_find = args.args.front().value(); } + if constexpr (OP_CODE == UnaryRedCode::CONTAINS) { + to_find = args.args.front().value(); + } #if !LegateDefined(LEGATE_BOUNDS_CHECKS) // Check to see if this is dense or not @@ -74,7 +76,9 @@ struct ScalarUnaryRed { __CUDA_HD__ void operator()(LHS& lhs, size_t idx, LHS identity, DenseReduction) const noexcept { if constexpr (OP_CODE == UnaryRedCode::CONTAINS) { - if (inptr[idx] == to_find) { lhs = true; } + if (inptr[idx] == to_find) { + lhs = true; + } } else if constexpr (OP_CODE == UnaryRedCode::ARGMAX || OP_CODE == UnaryRedCode::ARGMIN || OP_CODE == UnaryRedCode::NANARGMAX || OP_CODE == UnaryRedCode::NANARGMIN) { auto p = pitches.unflatten(idx, origin); @@ -88,7 +92,9 @@ struct ScalarUnaryRed { { if constexpr (OP_CODE == UnaryRedCode::CONTAINS) { auto point = pitches.unflatten(idx, origin); - if (in[point] == to_find) { lhs = true; } + if (in[point] == to_find) { + lhs = true; + } } else if constexpr (OP_CODE == UnaryRedCode::ARGMAX || OP_CODE == UnaryRedCode::ARGMIN || OP_CODE == UnaryRedCode::NANARGMAX || OP_CODE == UnaryRedCode::NANARGMIN) { auto p = pitches.unflatten(idx, origin); @@ -145,7 +151,9 @@ static void scalar_unary_red_template(TaskContext& context) auto& scalars = context.scalars(); std::vector extra_args; - for (size_t idx = 2; idx < scalars.size(); ++idx) extra_args.push_back(scalars[idx]); + for (size_t idx = 2; idx < scalars.size(); ++idx) { + extra_args.push_back(scalars[idx]); + } auto op_code = scalars[0].value(); auto shape = scalars[1].value(); diff --git a/src/cunumeric/unary/unary_op.cc b/src/cunumeric/unary/unary_op.cc index da57b142b7..f979368439 100644 --- a/src/cunumeric/unary/unary_op.cc +++ b/src/cunumeric/unary/unary_op.cc @@ -38,7 +38,9 @@ struct UnaryOpImplBody { if (dense) { auto outptr = out.ptr(rect); auto inptr = in.ptr(rect); - for (size_t idx = 0; idx < volume; ++idx) outptr[idx] = func(inptr[idx]); + for (size_t idx = 0; idx < volume; ++idx) { + outptr[idx] = func(inptr[idx]); + } } else { for (size_t idx = 0; idx < volume; ++idx) { auto p = pitches.unflatten(idx, rect.lo); @@ -60,7 +62,9 @@ struct PointCopyImplBody { if (dense) { auto outptr = out.ptr(rect); auto inptr = in.ptr(rect); - for (size_t idx = 0; idx < volume; ++idx) outptr[idx] = inptr[idx]; + for (size_t idx = 0; idx < volume; ++idx) { + outptr[idx] = inptr[idx]; + } } else { for (size_t idx = 0; idx < volume; ++idx) { auto p = pitches.unflatten(idx, rect.lo); @@ -90,7 +94,9 @@ struct MultiOutUnaryOpImplBody { auto lhsptr = lhs.ptr(rect); auto rhs1ptr = rhs1.ptr(rect); auto rhs2ptr = rhs2.ptr(rect); - for (size_t idx = 0; idx < volume; ++idx) lhsptr[idx] = func(rhs1ptr[idx], &rhs2ptr[idx]); + for (size_t idx = 0; idx < volume; ++idx) { + lhsptr[idx] = func(rhs1ptr[idx], &rhs2ptr[idx]); + } } else { for (size_t idx = 0; idx < volume; ++idx) { auto p = pitches.unflatten(idx, rect.lo); diff --git a/src/cunumeric/unary/unary_op.cu b/src/cunumeric/unary/unary_op.cu index 81cc41bbfd..cca4b71fab 100644 --- a/src/cunumeric/unary/unary_op.cu +++ b/src/cunumeric/unary/unary_op.cu @@ -26,7 +26,9 @@ static __global__ void __launch_bounds__(THREADS_PER_BLOCK, MIN_CTAS_PER_SM) dense_kernel(size_t volume, Function func, RES* out, const ARG* in) { const size_t idx = global_tid_1d(); - if (idx >= volume) return; + if (idx >= volume) { + return; + } out[idx] = func(in[idx]); } @@ -35,7 +37,9 @@ static __global__ void __launch_bounds__(THREADS_PER_BLOCK, MIN_CTAS_PER_SM) generic_kernel(size_t volume, Function func, WriteAcc out, ReadAcc in, Pitches pitches, Rect rect) { const size_t idx = global_tid_1d(); - if (idx >= volume) return; + if (idx >= volume) { + return; + } auto point = pitches.unflatten(idx, rect.lo); out[point] = func(in[point]); } @@ -45,7 +49,9 @@ static __global__ void __launch_bounds__(THREADS_PER_BLOCK, MIN_CTAS_PER_SM) dense_copy_kernel(size_t volume, VAL* out, const VAL* in) { const size_t idx = global_tid_1d(); - if (idx >= volume) return; + if (idx >= volume) { + return; + } out[idx] = in[idx]; } @@ -58,7 +64,9 @@ static __global__ void __launch_bounds__(THREADS_PER_BLOCK, MIN_CTAS_PER_SM) Rect rect) { const size_t idx = global_tid_1d(); - if (idx >= volume) return; + if (idx >= volume) { + return; + } auto point = pitches.unflatten(idx, rect.lo); out[point] = in[point]; } @@ -118,7 +126,9 @@ static __global__ void __launch_bounds__(THREADS_PER_BLOCK, MIN_CTAS_PER_SM) dense_kernel_multiout(size_t volume, Function func, LHS* lhs, const RHS1* rhs1, RHS2* rhs2) { const size_t idx = global_tid_1d(); - if (idx >= volume) return; + if (idx >= volume) { + return; + } lhs[idx] = func(rhs1[idx], &rhs2[idx]); } @@ -138,7 +148,9 @@ static __global__ void __launch_bounds__(THREADS_PER_BLOCK, MIN_CTAS_PER_SM) Rect rect) { const size_t idx = global_tid_1d(); - if (idx >= volume) return; + if (idx >= volume) { + return; + } auto point = pitches.unflatten(idx, rect.lo); lhs[point] = func(rhs1[point], rhs2.ptr(point)); } diff --git a/src/cunumeric/unary/unary_op_omp.cc b/src/cunumeric/unary/unary_op_omp.cc index 225adaeb58..7168e1c1d2 100644 --- a/src/cunumeric/unary/unary_op_omp.cc +++ b/src/cunumeric/unary/unary_op_omp.cc @@ -39,7 +39,9 @@ struct UnaryOpImplBody { auto outptr = out.ptr(rect); auto inptr = in.ptr(rect); #pragma omp parallel for schedule(static) - for (size_t idx = 0; idx < volume; ++idx) outptr[idx] = func(inptr[idx]); + for (size_t idx = 0; idx < volume; ++idx) { + outptr[idx] = func(inptr[idx]); + } } else { #pragma omp parallel for schedule(static) for (size_t idx = 0; idx < volume; ++idx) { @@ -63,7 +65,9 @@ struct PointCopyImplBody { auto outptr = out.ptr(rect); auto inptr = in.ptr(rect); #pragma omp parallel for schedule(static) - for (size_t idx = 0; idx < volume; ++idx) outptr[idx] = inptr[idx]; + for (size_t idx = 0; idx < volume; ++idx) { + outptr[idx] = inptr[idx]; + } } else { #pragma omp parallel for schedule(static) for (size_t idx = 0; idx < volume; ++idx) { @@ -95,7 +99,9 @@ struct MultiOutUnaryOpImplBody { auto rhs1ptr = rhs1.ptr(rect); auto rhs2ptr = rhs2.ptr(rect); #pragma omp parallel for schedule(static) - for (size_t idx = 0; idx < volume; ++idx) lhsptr[idx] = func(rhs1ptr[idx], &rhs2ptr[idx]); + for (size_t idx = 0; idx < volume; ++idx) { + lhsptr[idx] = func(rhs1ptr[idx], &rhs2ptr[idx]); + } } else { #pragma omp parallel for schedule(static) for (size_t idx = 0; idx < volume; ++idx) { diff --git a/src/cunumeric/unary/unary_op_template.inl b/src/cunumeric/unary/unary_op_template.inl index 5e5a6ebdd0..dcf7cec144 100644 --- a/src/cunumeric/unary/unary_op_template.inl +++ b/src/cunumeric/unary/unary_op_template.inl @@ -47,7 +47,9 @@ struct UnaryOpImpl { Pitches pitches; size_t volume = pitches.flatten(rect); - if (volume == 0) return; + if (volume == 0) { + return; + } auto out = args.out.write_accessor(rect); auto in = args.in.read_accessor(rect); @@ -88,7 +90,9 @@ struct MultiOutUnaryOpImpl { Pitches pitches; size_t volume = pitches.flatten(rect); - if (volume == 0) return; + if (volume == 0) { + return; + } auto lhs = args.out1.write_accessor(rect); auto rhs1 = args.in.read_accessor(rect); @@ -141,7 +145,9 @@ struct UnaryCopyImpl { Pitches pitches; size_t volume = pitches.flatten(rect); - if (volume == 0) return; + if (volume == 0) { + return; + } auto out = args.out.write_accessor(rect); auto in = args.in.read_accessor(rect); @@ -199,7 +205,9 @@ static void unary_op_template(TaskContext& context) } default: { std::vector extra_args; - for (size_t idx = 1; idx < scalars.size(); ++idx) extra_args.push_back(scalars[idx]); + for (size_t idx = 1; idx < scalars.size(); ++idx) { + extra_args.push_back(scalars[idx]); + } UnaryOpArgs args{inputs[0], outputs[0], op_code, std::move(extra_args)}; op_dispatch(args.op_code, UnaryOpDispatch{}, args); diff --git a/src/cunumeric/unary/unary_red.cu b/src/cunumeric/unary/unary_red.cu index 860239f3e6..4c66b16220 100644 --- a/src/cunumeric/unary/unary_red.cu +++ b/src/cunumeric/unary/unary_red.cu @@ -45,8 +45,9 @@ struct ThreadBlock { auto remaining = static_cast(THREADS_PER_BLOCK); Point domain_extents; - for (int32_t idx = 0; idx < DIM; ++idx) + for (int32_t idx = 0; idx < DIM; ++idx) { domain_extents[idx] = domain.hi[idx] - domain.lo[idx] + 1; + } // If the innermost dimension is being collapsed, we assign at least one warp to it // for warp coalsecing. @@ -59,15 +60,18 @@ struct ThreadBlock { // Then, we compute how many threads there should be along aech dimension, // excluding the one being collapsed for (int32_t idx = DIM - 1; idx >= 0; --idx) { - if (idx == collapsed_dim) continue; + if (idx == collapsed_dim) { + continue; + } auto extent = std::min(remaining, domain_extents[idx]); extents_[idx] = extent; remaining = std::max(remaining / extent, 1); } // Finally, we determine degree of parallelism for the collapsed dimension if we didn't above - if (collapsed_dim != DIM - 1) + if (collapsed_dim != DIM - 1) { extents_[collapsed_dim] = std::min(remaining, domain_extents[collapsed_dim]); + } // Cache the aggregate number of threads per increment in each dimension, // which later will be used for de-linearization of a thread id @@ -117,8 +121,11 @@ struct ThreadBlocks { // We want the collapsed dimension to be the outermost one when // de-linearizing the block id. dim_order_[0] = collapsed_dim_; - for (int32_t dim = 0, idx = 1; dim < DIM; ++dim) - if (dim != collapsed_dim_) dim_order_[idx++] = dim; + for (int32_t dim = 0, idx = 1; dim < DIM; ++dim) { + if (dim != collapsed_dim_) { + dim_order_[idx++] = dim; + } + } // Compute the aggregate number of blocks per increment in each dimension coord_t num_blocks = 1; @@ -195,7 +202,9 @@ std::ostream& operator<<(std::ostream& os, const ThreadBlocks& blocks) os << "ThreadBlocks(" << blocks.block_ << ", extents: " << blocks.extents_ << ", pitches: " << blocks.pitches_ << ", num concurrent blocks: " << blocks.num_blocks_ << ", dim order: {"; - for (int32_t dim : blocks.dim_order_) os << dim << ", "; + for (int32_t dim : blocks.dim_order_) { + os << dim << ", "; + } os << "})"; return os; @@ -213,7 +222,9 @@ static __device__ __forceinline__ Point local_reduce(LHS& result, const coord_t bid = blockIdx.x; Point point = blocks.point(bid, tid, domain.lo); - if (!domain.contains(point)) return point; + if (!domain.contains(point)) { + return point; + } while (point[collapsed_dim] <= domain.hi[collapsed_dim]) { LHS value = OP::convert(point, collapsed_dim, identity, in[point]); @@ -232,8 +243,9 @@ static __device__ __forceinline__ Point local_reduce(LHS& result, // so instead we do a warp-level reduction so just one thread ends // up doing the full atomic coord_t bucket = 0; - for (int32_t dim = DIM - 2; dim >= 0; --dim) + for (int32_t dim = DIM - 2; dim >= 0; --dim) { bucket = bucket * (domain.hi[dim] - domain.lo[dim] + 1) + point[dim] - domain.lo[dim]; + } const uint32_t same_mask = __match_any_sync(0xffffffff, bucket); int32_t laneid; @@ -246,7 +258,7 @@ static __device__ __forceinline__ Point local_reduce(LHS& result, __syncwarp(active_mask); // Have the lowest thread in each mask pull in the values int32_t lowest_index = -1; - for (int32_t i = 0; i < warpSize; i++) + for (int32_t i = 0; i < warpSize; i++) { if (same_mask & (1 << i)) { if (lowest_index == -1) { if (i != laneid) { @@ -256,8 +268,9 @@ static __device__ __forceinline__ Point local_reduce(LHS& result, // perform the reduction out to memory result = identity; break; - } else // Make sure we don't do this test again + } else { // Make sure we don't do this test again lowest_index = i; + } // It was already our value, so just keep going } else { // Pull in the value from shared memory @@ -265,6 +278,7 @@ static __device__ __forceinline__ Point local_reduce(LHS& result, REDOP::template fold(result, trampoline[index]); } } + } } } #endif @@ -292,7 +306,9 @@ static __global__ void __launch_bounds__(THREADS_PER_BLOCK, MIN_CTAS_PER_SM) auto result = identity; auto point = local_reduce(result, in, identity, blocks, domain, collapsed_dim); - if (result != identity) out.reduce(point, result); + if (result != identity) { + out.reduce(point, result); + } } template diff --git a/src/cunumeric/unary/unary_red_omp.cc b/src/cunumeric/unary/unary_red_omp.cc index cb82553e5a..d1e6b229ea 100644 --- a/src/cunumeric/unary/unary_red_omp.cc +++ b/src/cunumeric/unary/unary_red_omp.cc @@ -31,20 +31,21 @@ class Splitter { public: Split split(const Rect& rect, int must_be_inner) { - for (int dim = 0; dim < DIM; ++dim) + for (int dim = 0; dim < DIM; ++dim) { if (dim != must_be_inner) { outer_dim_ = dim; break; } + } size_t outer = 1; size_t inner = 1; size_t pitch = 1; for (int dim = DIM - 1; dim >= 0; --dim) { auto diff = rect.hi[dim] - rect.lo[dim] + 1; - if (dim == outer_dim_) + if (dim == outer_dim_) { outer *= diff; - else { + } else { inner *= diff; pitches_[dim] = pitch; pitch *= diff; @@ -57,9 +58,9 @@ class Splitter { { Point point = lo; for (int dim = 0; dim < DIM; ++dim) { - if (dim == outer_dim_) + if (dim == outer_dim_) { point[dim] += outer_idx; - else { + } else { point[dim] += inner_idx / pitches_[dim]; inner_idx = inner_idx % pitches_[dim]; } @@ -89,12 +90,13 @@ struct UnaryRedImplBody { auto split = splitter.split(rect, collapsed_dim); #pragma omp parallel for schedule(static) - for (size_t o_idx = 0; o_idx < split.outer; ++o_idx) + for (size_t o_idx = 0; o_idx < split.outer; ++o_idx) { for (size_t i_idx = 0; i_idx < split.inner; ++i_idx) { auto point = splitter.combine(o_idx, i_idx, rect.lo); auto identity = LG_OP::identity; lhs.reduce(point, OP::convert(point, collapsed_dim, identity, rhs[point])); } + } } }; diff --git a/src/cunumeric/unary/unary_red_template.inl b/src/cunumeric/unary/unary_red_template.inl index 4ec7868a33..c8b6702dcd 100644 --- a/src/cunumeric/unary/unary_red_template.inl +++ b/src/cunumeric/unary/unary_red_template.inl @@ -44,7 +44,9 @@ struct UnaryRedImpl { auto rect = args.rhs.shape(); auto volume = pitches.flatten(rect); - if (volume == 0) return; + if (volume == 0) { + return; + } auto rhs = args.rhs.read_accessor(rect); diff --git a/src/cunumeric/unary/unary_red_util.h b/src/cunumeric/unary/unary_red_util.h index cc53b4fbd9..5892dfa3b0 100644 --- a/src/cunumeric/unary/unary_red_util.h +++ b/src/cunumeric/unary/unary_red_util.h @@ -294,7 +294,9 @@ struct UnaryRedOp { const RHS& rhs) { int64_t idx = 0; - for (int32_t dim = 0; dim < DIM; ++dim) idx = idx * shape[dim] + point[dim]; + for (int32_t dim = 0; dim < DIM; ++dim) { + idx = idx * shape[dim] + point[dim]; + } return VAL(idx, rhs); } }; @@ -329,7 +331,9 @@ struct UnaryRedOp { const RHS& rhs) { int64_t idx = 0; - for (int32_t dim = 0; dim < DIM; ++dim) idx = idx * shape[dim] + point[dim]; + for (int32_t dim = 0; dim < DIM; ++dim) { + idx = idx * shape[dim] + point[dim]; + } return VAL(idx, rhs); } }; @@ -369,7 +373,9 @@ struct UnaryRedOp& in_array, { auto A1 = cunumeric::zeros(shape, leg_type); if (in_array.size() != 0) { - if (in_array.size() == 1) + if (in_array.size() == 1) { A1.fill(legate::Scalar(in_array[0]), false); - else + } else { assign_values_to_array(A1, in_array.data(), in_array.size()); + } } std::vector algos = {"quicksort", "mergesort", "heapsort", "stable"}; for (auto algo = algos.begin(); algo < algos.end(); ++algo) { auto B1 = cunumeric::sort(A1, axis, *algo); - if (in_array.size() != 0) check_array_eq(B1, expect.data(), expect.size()); + if (in_array.size() != 0) { + check_array_eq(B1, expect.data(), expect.size()); + } } } @@ -524,12 +527,13 @@ void sort_basic_axis_impl(std::vector>& test_shapes, int32_t dim = test_shape.size(); for (int32_t axis = -dim + 1; axis < dim; ++axis) { auto expect_val = expect_result[i][axis]; - if (dim == 1) + if (dim == 1) { test_sort(in_array, expect_val, leg_type, test_shape, axis); - else if (dim == 2) + } else if (dim == 2) { test_sort(in_array, expect_val, leg_type, test_shape, axis); - else + } else { test_sort(in_array, expect_val, leg_type, test_shape, axis); + } } } } @@ -577,12 +581,13 @@ void sort_empty_array() auto test_shape = test_shapes[i]; int32_t dim = test_shape.size(); for (int32_t axis = -dim + 1; axis < dim; ++axis) { - if (dim == 1) + if (dim == 1) { test_sort(in_array, in_array, legate::int32(), test_shape, axis); - else if (dim == 2) + } else if (dim == 2) { test_sort(in_array, in_array, legate::int32(), test_shape, axis); - else + } else { test_sort(in_array, in_array, legate::int32(), test_shape, axis); + } } } } @@ -597,12 +602,13 @@ void sort_single_item_array() auto test_shape = test_shapes[i]; int32_t dim = test_shape.size(); for (int32_t axis = -dim + 1; axis < dim; ++axis) { - if (dim == 1) + if (dim == 1) { test_sort(in_array, in_array, legate::int32(), test_shape, axis); - else if (dim == 2) + } else if (dim == 2) { test_sort(in_array, in_array, legate::int32(), test_shape, axis); - else + } else { test_sort(in_array, in_array, legate::int32(), test_shape, axis); + } } } } diff --git a/tests/cpp/integration/test_transpose.cc b/tests/cpp/integration/test_transpose.cc index 27291ca595..ebbd1b700a 100644 --- a/tests/cpp/integration/test_transpose.cc +++ b/tests/cpp/integration/test_transpose.cc @@ -31,10 +31,11 @@ void transpose_int32_test(std::array input, auto a_output = cunumeric::array(out_shape, legate::int32()); - if (axes) + if (axes) { a_output = cunumeric::transpose(a_input, axes.value()); - else + } else { a_output = cunumeric::transpose(a_input); + } check_array_eq(a_output, exp.data(), exp.size()); EXPECT_EQ(a_output.shape(), out_shape); } diff --git a/tests/cpp/integration/util.inl b/tests/cpp/integration/util.inl index 7cdca692c9..230dbc1e11 100644 --- a/tests/cpp/integration/util.inl +++ b/tests/cpp/integration/util.inl @@ -22,7 +22,9 @@ std::string to_string_1d(legate::AccessorRO acc, const std::vector ss << "["; for (auto i = 0; i < shape[0]; ++i) { - if (i > 0) ss << ", "; + if (i > 0) { + ss << ", "; + } ss << std::setw(9) << std::setprecision(6) << acc[i]; } ss << "]"; @@ -37,10 +39,14 @@ std::string to_string_2d(legate::AccessorRO acc, const std::vector ss << "["; for (auto i = 0; i < shape[0]; ++i) { - if (i > 0) ss << ",\n "; + if (i > 0) { + ss << ",\n "; + } ss << "["; for (auto j = 0; j < shape[1]; ++j) { - if (j > 0) ss << ", "; + if (j > 0) { + ss << ", "; + } ss << std::setw(9) << std::setprecision(3) << acc[i][j]; } ss << "]"; @@ -56,13 +62,19 @@ std::string to_string_3d(legate::AccessorRO acc, const std::vector ss << "["; for (auto k = 0; k < shape[0]; ++k) { - if (k > 0) ss << ",\n "; + if (k > 0) { + ss << ",\n "; + } ss << "["; for (auto i = 0; i < shape[1]; ++i) { - if (i > 0) ss << ",\n "; + if (i > 0) { + ss << ",\n "; + } ss << "["; for (auto j = 0; j < shape[2]; ++j) { - if (j > 0) ss << ", "; + if (j > 0) { + ss << ", "; + } ss << std::setw(9) << std::setprecision(3) << acc[k][i][j]; } ss << "]"; @@ -83,7 +95,9 @@ std::string check_array_eq_1d(legate::AccessorRO acc, ss << "["; for (auto i = 0; i < shape[0]; ++i) { - if (i > 0) ss << ", "; + if (i > 0) { + ss << ", "; + } ss << std::setw(9) << std::setprecision(6) << acc[i]; EXPECT_EQ(acc[i], values_ptr[i]); } @@ -101,10 +115,14 @@ std::string check_array_eq_2d(legate::AccessorRO acc, ss << "["; for (auto i = 0; i < shape[0]; ++i) { - if (i > 0) ss << ",\n "; + if (i > 0) { + ss << ",\n "; + } ss << "["; for (auto j = 0; j < shape[1]; ++j) { - if (j > 0) ss << ", "; + if (j > 0) { + ss << ", "; + } ss << std::setw(9) << std::setprecision(3) << acc[i][j]; EXPECT_EQ(acc[i][j], values_ptr[i * shape[1] + j]); } @@ -124,13 +142,19 @@ std::string check_array_eq_3d(legate::AccessorRO acc, ss << "["; for (auto k = 0; k < shape[0]; ++k) { - if (k > 0) ss << ",\n "; + if (k > 0) { + ss << ",\n "; + } ss << "["; for (auto i = 0; i < shape[1]; ++i) { - if (i > 0) ss << ",\n "; + if (i > 0) { + ss << ",\n "; + } ss << "["; for (auto j = 0; j < shape[2]; ++j) { - if (j > 0) ss << ", "; + if (j > 0) { + ss << ", "; + } ss << std::setw(9) << std::setprecision(3) << acc[k][i][j]; EXPECT_EQ(acc[k][i][j], values_ptr[k * shape[1] * shape[2] + i * shape[2] + j]); } @@ -204,7 +228,9 @@ template struct assign_array_fn { void operator()(legate::AccessorWO acc, T* values_ptr, const std::vector& shape) { - for (auto i = 0; i < shape[0]; ++i) { acc[i] = values_ptr[i]; } + for (auto i = 0; i < shape[0]; ++i) { + acc[i] = values_ptr[i]; + } } }; @@ -213,7 +239,9 @@ struct assign_array_fn { void operator()(legate::AccessorWO acc, T* values_ptr, const std::vector& shape) { for (auto i = 0; i < shape[0]; ++i) { - for (auto j = 0; j < shape[1]; ++j) { acc[i][j] = values_ptr[i * shape[1] + j]; } + for (auto j = 0; j < shape[1]; ++j) { + acc[i][j] = values_ptr[i * shape[1] + j]; + } } } }; From ff19c1724ed5a36633bebc23b36f4c1e9f89615c Mon Sep 17 00:00:00 2001 From: Wonchan Lee Date: Wed, 15 Nov 2023 15:57:01 -0800 Subject: [PATCH 119/462] Bump the legate core commit hash (#56) --- cmake/versions.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmake/versions.json b/cmake/versions.json index edbd2ebe27..b147d65618 100644 --- a/cmake/versions.json +++ b/cmake/versions.json @@ -12,7 +12,7 @@ "git_url" : "https://github.com/nv-legate/legate.core.internal.git", "git_shallow": false, "always_download": false, - "git_tag" : "63a5541366542e765693e340c2b851f126dea9f2" + "git_tag" : "e5dcfec95fe2b9afd71caec2dbbcc19e2a946d70" } } } From 540ab49bf8331e174274283e2211ec708273e671 Mon Sep 17 00:00:00 2001 From: Manolis Papadakis Date: Wed, 22 Nov 2023 16:09:03 -0800 Subject: [PATCH 120/462] Bump Pyton to 3.10 (#57) * Bump Pyton to 3.10 * Remove miscommited file --- conda/conda-build/conda_build_config.yaml | 1 - conda/conda-build/meta.yaml | 1 - scripts/conda-build.sh | 2 +- setup.cfg | 2 +- setup.py | 1 - 5 files changed, 2 insertions(+), 5 deletions(-) diff --git a/conda/conda-build/conda_build_config.yaml b/conda/conda-build/conda_build_config.yaml index a508a6ed16..a72f122a58 100644 --- a/conda/conda-build/conda_build_config.yaml +++ b/conda/conda-build/conda_build_config.yaml @@ -3,7 +3,6 @@ gpu_enabled: - false python: - - 3.9 - 3.10 - 3.11 diff --git a/conda/conda-build/meta.yaml b/conda/conda-build/meta.yaml index c652d931bf..bf020cb7de 100644 --- a/conda/conda-build/meta.yaml +++ b/conda/conda-build/meta.yaml @@ -147,7 +147,6 @@ requirements: run_constrained: - __glibc >=2.17 # [linux] - - python != 3.9.7 {% if gpu_enabled_bool %} - __cuda >={{ cuda_version }} {% endif %} diff --git a/scripts/conda-build.sh b/scripts/conda-build.sh index 0d2100bc95..10607067ae 100755 --- a/scripts/conda-build.sh +++ b/scripts/conda-build.sh @@ -7,7 +7,7 @@ cd $(dirname "$(realpath "$0")")/.. mkdir -p /tmp/conda-build/cunumeric rm -rf /tmp/conda-build/cunumeric/* -PYTHON_VERSION="${PYTHON_VERSION:-3.9}" +PYTHON_VERSION="${PYTHON_VERSION:-3.10}" CUDA="$(nvcc --version | head -n4 | tail -n1 | cut -d' ' -f5 | cut -d',' -f1).*" \ conda mambabuild \ diff --git a/setup.cfg b/setup.cfg index 96fbfaea21..93ec7e20f6 100644 --- a/setup.cfg +++ b/setup.cfg @@ -51,4 +51,4 @@ packages = find: install_requires = numpy>=1.22 # TODO: Add rest of install dependencies -python_requires = >=3.9,!=3.9.7 +python_requires = >=3.10 diff --git a/setup.py b/setup.py index 1ec5180968..16db8038db 100644 --- a/setup.py +++ b/setup.py @@ -33,7 +33,6 @@ "Topic :: Scientific/Engineering", "License :: OSI Approved :: Apache Software License", "Programming Language :: Python", - "Programming Language :: Python :: 3.9", "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", ], From 8977a67a184b90879fd7942521aced3bb30ed80a Mon Sep 17 00:00:00 2001 From: Yimo Jiang Date: Wed, 29 Nov 2023 15:23:13 +0800 Subject: [PATCH 121/462] Fix arange type mismatch issue (#37) --- src/cunumeric/ndarray.cc | 10 ++- src/cunumeric/ndarray.h | 2 +- src/cunumeric/operators.cc | 46 +++++++++++--- src/cunumeric/operators.h | 9 +-- src/cunumeric/operators.inl | 36 +++++++++++ tests/cpp/integration/test_arange.cc | 91 ++++++++++++++++++++++++++++ 6 files changed, 176 insertions(+), 18 deletions(-) create mode 100644 src/cunumeric/operators.inl create mode 100644 tests/cpp/integration/test_arange.cc diff --git a/src/cunumeric/ndarray.cc b/src/cunumeric/ndarray.cc index d829fb7846..135b8e537d 100644 --- a/src/cunumeric/ndarray.cc +++ b/src/cunumeric/ndarray.cc @@ -15,6 +15,7 @@ */ #include "cunumeric/ndarray.h" +#include #include "cunumeric/binary/binary_op_util.h" #include "cunumeric/operators.h" @@ -468,7 +469,7 @@ void NDArray::dot(NDArray rhs1, NDArray rhs2) runtime->submit(std::move(task)); } -void NDArray::arange(double start, double stop, double step) +void NDArray::arange(Scalar start, Scalar stop, Scalar step) { if (size() == 0) { return; @@ -476,6 +477,9 @@ void NDArray::arange(double start, double stop, double step) auto runtime = CuNumericRuntime::get_runtime(); + if (start.type() != type() || stop.type() != type() || step.type() != type()) + throw std::invalid_argument("start/stop/step should have the same type as the array"); + assert(dim() == 1); // TODO: Optimization when value is a scalar @@ -484,8 +488,8 @@ void NDArray::arange(double start, double stop, double step) task.add_output(store_); - task.add_scalar_arg(Scalar(start)); - task.add_scalar_arg(Scalar(step)); + task.add_scalar_arg(start); + task.add_scalar_arg(step); runtime->submit(std::move(task)); } diff --git a/src/cunumeric/ndarray.h b/src/cunumeric/ndarray.h index 6b2d97abca..b32ada25f6 100644 --- a/src/cunumeric/ndarray.h +++ b/src/cunumeric/ndarray.h @@ -77,7 +77,7 @@ class NDArray { void eye(int32_t k); void trilu(NDArray rhs, int32_t k, bool lower); void dot(NDArray rhs1, NDArray rhs2); - void arange(double start, double stop, double step); + void arange(Scalar start, Scalar stop, Scalar step); std::vector nonzero(); NDArray unique(); NDArray swapaxes(int32_t axis1, int32_t axis2); diff --git a/src/cunumeric/operators.cc b/src/cunumeric/operators.cc index e786db4d95..469b486ce5 100644 --- a/src/cunumeric/operators.cc +++ b/src/cunumeric/operators.cc @@ -89,6 +89,34 @@ struct generate_zero_fn { } }; +struct generate_arange_shape_fn { + template ::value || + legate::is_floating_point::value>* = nullptr> + size_t operator()(Scalar& start, Scalar& stop, Scalar& step) + { + using VAL = legate::type_of; + if (stop.type().code() == legate::Type::Code::NIL) { + stop = start; + start = legate::Scalar(VAL(0)); + } + + if (step.type().code() == legate::Type::Code::NIL) { + step = legate::Scalar(VAL(1)); + } + + return static_cast(ceil((stop.value() - start.value()) / step.value())); + } + + template ::value || + legate::is_floating_point::value)>* = nullptr> + size_t operator()(Scalar& start, Scalar& stop, Scalar& step) + { + throw std::invalid_argument("arange input should be integer or real"); + } +}; + struct generate_int_value_fn { template ::value>* = nullptr> int operator()(NDArray& array) @@ -256,19 +284,17 @@ NDArray swapaxes(NDArray input, int32_t axis1, int32_t axis2) return input.swapaxes(axis1, axis2); } -NDArray arange(std::optional start, - std::optional stop, - std::optional step, - const legate::Type& type) +NDArray arange(Scalar start, Scalar stop, Scalar step) { - if (!stop.has_value()) { - stop = start; - start = 0; + size_t N = + legate::type_dispatch(start.type().code(), generate_arange_shape_fn{}, start, stop, step); + + if (start.type() != stop.type() || start.type() != step.type()) { + throw std::invalid_argument("start/stop/step should be of the same type"); } - size_t N = ceil((stop.value() - start.value()) / step.value()); - auto out = CuNumericRuntime::get_runtime()->create_array({N}, type); - out.arange(start.value(), stop.value(), step.value()); + auto out = CuNumericRuntime::get_runtime()->create_array({N}, start.type()); + out.arange(start, stop, step); return out; } diff --git a/src/cunumeric/operators.h b/src/cunumeric/operators.h index 63e0aa49f8..77894717d9 100644 --- a/src/cunumeric/operators.h +++ b/src/cunumeric/operators.h @@ -55,10 +55,10 @@ NDArray unique(NDArray input); NDArray swapaxes(NDArray input, int32_t axis1, int32_t axis2); -NDArray arange(std::optional start = 0, - std::optional stop = std::nullopt, - std::optional step = 1, - const legate::Type& type = legate::float64()); +template +NDArray arange(T start, std::optional stop = std::nullopt, T step = 1); + +NDArray arange(Scalar start, Scalar stop = legate::Scalar{}, Scalar step = legate::Scalar{}); NDArray as_array(legate::LogicalStore store); @@ -105,3 +105,4 @@ std::vector normalize_axis_vector(std::vector axis, bool allow_duplicate = false); } // namespace cunumeric +#include "cunumeric/operators.inl" diff --git a/src/cunumeric/operators.inl b/src/cunumeric/operators.inl new file mode 100644 index 0000000000..229972d4fe --- /dev/null +++ b/src/cunumeric/operators.inl @@ -0,0 +1,36 @@ +/* Copyright 2021 NVIDIA Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +#include "cunumeric/runtime.h" +namespace cunumeric { + +template +NDArray arange(T start, std::optional stop, T step) +{ + if (!stop.has_value()) { + stop = start; + start = 0; + } + + size_t N = ceil((stop.value() - start) / step); + auto s_start = Scalar(start); + auto s_stop = Scalar(stop.value()); + auto s_step = Scalar(step); + auto out = CuNumericRuntime::get_runtime()->create_array({N}, s_start.type()); + out.arange(s_start, s_stop, s_step); + return out; +} + +} // namespace cunumeric diff --git a/tests/cpp/integration/test_arange.cc b/tests/cpp/integration/test_arange.cc new file mode 100644 index 0000000000..6fd289d735 --- /dev/null +++ b/tests/cpp/integration/test_arange.cc @@ -0,0 +1,91 @@ +/* Copyright 2023 NVIDIA Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +#include +#include +#include +#include "legate.h" +#include "cunumeric.h" +#include "util.inl" + +TEST(ArangeType, ImplicitInt64) +{ + int64_t start = 1567891032456; + std::optional stop = 1567891032465; + std::array exp = {1567891032456, + 1567891032457, + 1567891032458, + 1567891032459, + 1567891032460, + 1567891032461, + 1567891032462, + 1567891032463, + 1567891032464}; + auto arr = cunumeric::arange(start, stop); + check_array_eq(arr, exp.data(), exp.size()); +} + +TEST(ArangeType, ImplicitInt32) +{ + int32_t stop = 10; + std::array exp = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9}; + auto arr = cunumeric::arange(stop); + check_array_eq(arr, exp.data(), exp.size()); +} + +TEST(ArangeType, ImplicitFloat64) +{ + double start = 1.5; + double stop = 10.5; + std::array exp = {1.5, 2.5, 3.5, 4.5, 5.5, 6.5, 7.5, 8.5, 9.5}; + auto arr = cunumeric::arange(start, (std::optional)stop); + check_array_eq(arr, exp.data(), exp.size()); +} + +TEST(ArangeType, ImplicitFloat32) +{ + float start = 1.5; + float stop = 10.5; + std::array exp = {1.5, 2.5, 3.5, 4.5, 5.5, 6.5, 7.5, 8.5, 9.5}; + auto arr = cunumeric::arange(start, (std::optional)stop); + check_array_eq(arr, exp.data(), exp.size()); +} + +TEST(ArangeType, ExplicitInt32) +{ + float start = 1.5; + float stop = 10.5; + std::array exp = {1, 2, 3, 4, 5, 6, 7, 8, 9}; + auto arr = cunumeric::arange(start, stop); + check_array_eq(arr, exp.data(), exp.size()); +} + +TEST(ArangeScalar, Float32) +{ + float start = 1.5; + float stop = 10.5; + std::array exp = {1.5, 2.5, 3.5, 4.5, 5.5, 6.5, 7.5, 8.5, 9.5}; + auto arr = cunumeric::arange(legate::Scalar(start), legate::Scalar(stop)); + check_array_eq(arr, exp.data(), exp.size()); +} + +TEST(ArangeErrors, ScalarTypeMismatch) +{ + float start = 1.5; + int32_t stop = 10; + EXPECT_THROW(cunumeric::arange(legate::Scalar(start), legate::Scalar(stop)), + std::invalid_argument); +} From 5cbff6f8f4e25de4e14e54d9641f15f48697f712 Mon Sep 17 00:00:00 2001 From: Parag Kulkarni Date: Thu, 30 Nov 2023 14:23:07 +0530 Subject: [PATCH 122/462] Update dependencies in meta.yaml. (#64) * Update dependencies in meta.yaml. Based on cunumeric PR#1095 * bump version to include cutensor changes in legate.core.internal. --- cmake/versions.json | 2 +- conda/conda-build/meta.yaml | 5 ++++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/cmake/versions.json b/cmake/versions.json index b147d65618..f751c7f611 100644 --- a/cmake/versions.json +++ b/cmake/versions.json @@ -12,7 +12,7 @@ "git_url" : "https://github.com/nv-legate/legate.core.internal.git", "git_shallow": false, "always_download": false, - "git_tag" : "e5dcfec95fe2b9afd71caec2dbbcc19e2a946d70" + "git_tag" : "bbfa697e4db494bd0d6b37e711e46fe8113f8669" } } } diff --git a/conda/conda-build/meta.yaml b/conda/conda-build/meta.yaml index bf020cb7de..94102b878b 100644 --- a/conda/conda-build/meta.yaml +++ b/conda/conda-build/meta.yaml @@ -116,11 +116,12 @@ requirements: - cuda-nvcc ={{ cuda_version }} - cuda-cccl ={{ cuda_version }} - cuda-cudart ={{ cuda_version }} + - cuda-cudart-static ={{ cuda_version }} - cuda-driver-dev ={{ cuda_version }} - cuda-cudart-dev ={{ cuda_version }} - cuda-nvtx ={{ cuda_version }} # - libcutensor-dev >=1.3 - - cutensor >=1.3 =*_* + - cutensor >=1.3,<2.0 =*_* - libcublas-dev - libcusolver-dev - libcufft-dev @@ -140,6 +141,8 @@ requirements: - libcublas - libcusolver >=11.4.1.48-0 - libcufft + - libnvjitlink + - libcusparse {% endif %} - opt_einsum >=3.3 - scipy From 548f1cd73ac318a3ad2b3f6f239a999db87182c3 Mon Sep 17 00:00:00 2001 From: Mona Han <129724851+mona-nv@users.noreply.github.com> Date: Fri, 1 Dec 2023 12:28:21 +0800 Subject: [PATCH 123/462] =?UTF-8?q?Support=20multiple=20dimensions=20in=20?= =?UTF-8?q?test=20util=20file=20for=20array=20check,=20print=20=E2=80=A6?= =?UTF-8?q?=20(#50)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Support multiple dimensions in test util file for array check, print and assignment functions * Replace the implementation of util functions with new way for more dimension support * Change object type based on recent legate update * Remove the compile warnings. * Add null pointer check for input pointer --- tests/cpp/integration/util.inl | 313 +++++++++++---------------------- 1 file changed, 101 insertions(+), 212 deletions(-) diff --git a/tests/cpp/integration/util.inl b/tests/cpp/integration/util.inl index 230dbc1e11..5a3b040a18 100644 --- a/tests/cpp/integration/util.inl +++ b/tests/cpp/integration/util.inl @@ -15,247 +15,119 @@ */ namespace { -template -std::string to_string_1d(legate::AccessorRO acc, const std::vector& shape) +template +std::string to_string(legate::AccessorRO acc, + const std::vector& shape, + legate::Rect rect) { std::stringstream ss; - - ss << "["; - for (auto i = 0; i < shape[0]; ++i) { - if (i > 0) { - ss << ", "; - } - ss << std::setw(9) << std::setprecision(6) << acc[i]; + auto size = static_cast(shape.size()); + + auto count = 0; + auto pro = 1; + std::vector item_count; + for (int32_t i = size - 1; i >= 0; --i) { + pro *= shape[i]; + item_count.push_back(pro); } - ss << "]"; - - return ss.str(); -} -template -std::string to_string_2d(legate::AccessorRO acc, const std::vector& shape) -{ - std::stringstream ss; - - ss << "["; - for (auto i = 0; i < shape[0]; ++i) { - if (i > 0) { - ss << ",\n "; - } - ss << "["; - for (auto j = 0; j < shape[1]; ++j) { - if (j > 0) { - ss << ", "; + auto print_brackets_in_start_end = [&](bool start) { + if (start) { + for (int32_t i = 0; i < size; ++i) { + ss << "["; + } + } else { + for (int32_t i = 0; i < size; ++i) { + ss << "]"; } - ss << std::setw(9) << std::setprecision(3) << acc[i][j]; } - ss << "]"; - } - - return ss.str(); -} + }; -template -std::string to_string_3d(legate::AccessorRO acc, const std::vector& shape) -{ - std::stringstream ss; - - ss << "["; - for (auto k = 0; k < shape[0]; ++k) { - if (k > 0) { - ss << ",\n "; - } - ss << "["; - for (auto i = 0; i < shape[1]; ++i) { - if (i > 0) { - ss << ",\n "; - } - ss << "["; - for (auto j = 0; j < shape[2]; ++j) { - if (j > 0) { - ss << ", "; + auto print_brackets_in_middle = [&]() -> bool { + for (int32_t i = size - 1; i >= 0; --i) { + if ((count % item_count[i]) == 0) { + for (int32_t j = i; j >= 0; --j) { + ss << "]"; } - ss << std::setw(9) << std::setprecision(3) << acc[k][i][j]; + ss << ",\n"; + for (int32_t j = i; j >= 0; --j) { + ss << "["; + } + return true; } - ss << "]"; - } - ss << "]"; - } - ss << "]"; - - return ss.str(); -} - -template -std::string check_array_eq_1d(legate::AccessorRO acc, - T* values_ptr, - const std::vector& shape) -{ - std::stringstream ss; - - ss << "["; - for (auto i = 0; i < shape[0]; ++i) { - if (i > 0) { - ss << ", "; } - ss << std::setw(9) << std::setprecision(6) << acc[i]; - EXPECT_EQ(acc[i], values_ptr[i]); - } - ss << "]"; - - return ss.str(); -} - -template -std::string check_array_eq_2d(legate::AccessorRO acc, - T* values_ptr, - const std::vector& shape) -{ - std::stringstream ss; - - ss << "["; - for (auto i = 0; i < shape[0]; ++i) { - if (i > 0) { - ss << ",\n "; - } - ss << "["; - for (auto j = 0; j < shape[1]; ++j) { - if (j > 0) { - ss << ", "; + return false; + }; + + print_brackets_in_start_end(true); + for (legate::PointInRectIterator itr(rect, false); itr.valid(); ++itr) { + if (count > 0) { + if (!print_brackets_in_middle()) { + ss << ","; } - ss << std::setw(9) << std::setprecision(3) << acc[i][j]; - EXPECT_EQ(acc[i][j], values_ptr[i * shape[1] + j]); } - ss << "]"; + ss << std::setw(9) << std::setprecision(3) << acc[*itr]; + count += 1; } - ss << "]"; + print_brackets_in_start_end(false); return ss.str(); } -template -std::string check_array_eq_3d(legate::AccessorRO acc, - T* values_ptr, - const std::vector& shape) +template +std::string check_array_eq(legate::AccessorRO acc, + T* values_ptr, + const std::vector& shape, + legate::Rect rect) { std::stringstream ss; - ss << "["; - for (auto k = 0; k < shape[0]; ++k) { - if (k > 0) { - ss << ",\n "; + auto index = 0; + auto size = shape.size(); + for (legate::PointInRectIterator itr(rect, false); itr.valid(); ++itr) { + auto q = *itr; + ss << std::left << std::setprecision(3); + ss << std::setw(13) << "Array value: " << std::setw(10) << acc[q] << ", "; + ss << std::setw(16) << "Expected value: " << std::setw(10) << values_ptr[index] << ", "; + ss << std::setw(8) << "index: ["; + for (uint32_t i = 0; i < size - 1; ++i) { + ss << q[i] << ","; } - ss << "["; - for (auto i = 0; i < shape[1]; ++i) { - if (i > 0) { - ss << ",\n "; - } - ss << "["; - for (auto j = 0; j < shape[2]; ++j) { - if (j > 0) { - ss << ", "; - } - ss << std::setw(9) << std::setprecision(3) << acc[k][i][j]; - EXPECT_EQ(acc[k][i][j], values_ptr[k * shape[1] * shape[2] + i * shape[2] + j]); - } - ss << "]"; - } - ss << "]"; + ss << q[size - 1] << "]\n"; + EXPECT_EQ(acc[q], values_ptr[index++]); } - ss << "]"; return ss.str(); } template -struct print_fn; - -template -struct print_fn { - void operator()(legate::AccessorRO acc, const std::vector& shape) - { - std::cerr << to_string_1d(acc, shape) << std::endl; - } -}; - -template -struct print_fn { - void operator()(legate::AccessorRO acc, const std::vector& shape) - { - std::cerr << to_string_2d(acc, shape) << std::endl; - } -}; - -template -struct print_fn { - void operator()(legate::AccessorRO acc, const std::vector& shape) +struct print_fn { + void operator()(legate::AccessorRO acc, + const std::vector& shape, + legate::Rect rect) { - std::cerr << to_string_3d(acc, shape) << std::endl; + std::cerr << to_string(acc, shape, rect) << std::endl; } }; template -struct check_array_eq_fn; - -template -struct check_array_eq_fn { - void operator()(legate::AccessorRO acc, T* values_ptr, const std::vector& shape) - { - std::cerr << check_array_eq_1d(acc, values_ptr, shape) << std::endl; - } -}; - -template -struct check_array_eq_fn { - void operator()(legate::AccessorRO acc, T* values_ptr, const std::vector& shape) +struct check_array_eq_fn { + void operator()(legate::AccessorRO acc, + T* values_ptr, + const std::vector& shape, + legate::Rect rect) { - std::cerr << check_array_eq_2d(acc, values_ptr, shape) << std::endl; - } -}; - -template -struct check_array_eq_fn { - void operator()(legate::AccessorRO acc, T* values_ptr, const std::vector& shape) - { - std::cerr << check_array_eq_3d(acc, values_ptr, shape) << std::endl; + std::cerr << check_array_eq(acc, values_ptr, shape, rect) << std::endl; } }; template -struct assign_array_fn; - -template -struct assign_array_fn { - void operator()(legate::AccessorWO acc, T* values_ptr, const std::vector& shape) - { - for (auto i = 0; i < shape[0]; ++i) { - acc[i] = values_ptr[i]; - } - } -}; - -template -struct assign_array_fn { - void operator()(legate::AccessorWO acc, T* values_ptr, const std::vector& shape) +struct assign_array_fn { + void operator()(legate::AccessorWO acc, T* values_ptr, legate::Rect rect) { - for (auto i = 0; i < shape[0]; ++i) { - for (auto j = 0; j < shape[1]; ++j) { - acc[i][j] = values_ptr[i * shape[1] + j]; - } - } - } -}; - -template -struct assign_array_fn { - void operator()(legate::AccessorWO acc, T* values_ptr, const std::vector& shape) - { - for (auto i = 0; i < shape[0]; ++i) { - for (auto j = 0; j < shape[1]; ++j) { - for (auto k = 0; k < shape[2]; ++k) { - acc[i][j][k] = values_ptr[i * shape[1] * shape[2] + j * shape[2] + k]; - } - } + auto index = 0; + for (legate::PointInRectIterator itr(rect, false); itr.valid(); ++itr) { + acc[*itr] = values_ptr[index++]; } } }; @@ -263,26 +135,43 @@ struct assign_array_fn { template void print_array(cunumeric::NDArray array) { - auto acc = array.get_read_accessor(); - auto& shape = array.shape(); - print_fn()(acc, shape); + auto acc = array.get_read_accessor(); + auto& shape = array.shape(); + auto logical_store = array.get_store(); + auto physical_store = logical_store.get_physical_store(); + auto rect = physical_store.shape(); + print_fn()(acc, shape, rect); } template void check_array_eq(cunumeric::NDArray array, T* values_ptr, size_t length) { assert(array.size() == length); - auto acc = array.get_read_accessor(); - auto& shape = array.shape(); - check_array_eq_fn()(acc, values_ptr, shape); + if (length == 0) { + return; + } + assert(values_ptr != nullptr); + auto acc = array.get_read_accessor(); + auto& shape = array.shape(); + auto logical_store = array.get_store(); + auto physical_store = logical_store.get_physical_store(); + auto rect = physical_store.shape(); + check_array_eq_fn()(acc, values_ptr, shape, rect); } template void assign_values_to_array(cunumeric::NDArray array, T* values_ptr, size_t length) { assert(array.size() == length); - auto acc = array.get_write_accessor(); - auto& shape = array.shape(); - assign_array_fn()(acc, values_ptr, shape); + if (length == 0) { + return; + } + assert(values_ptr != nullptr); + auto acc = array.get_write_accessor(); + auto logical_store = array.get_store(); + auto physical_store = logical_store.get_physical_store(); + auto rect = physical_store.shape(); + assign_array_fn()(acc, values_ptr, rect); } + } // namespace From 2e13d558ef275e6be6ba2c90c5b862695d9d022d Mon Sep 17 00:00:00 2001 From: Irina Demeshko Date: Thu, 7 Dec 2023 09:18:53 -0800 Subject: [PATCH 124/462] fixing sort task (#69) --- src/cunumeric/sort/sort_template.inl | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/cunumeric/sort/sort_template.inl b/src/cunumeric/sort/sort_template.inl index ceac66c758..bc51428ee1 100644 --- a/src/cunumeric/sort/sort_template.inl +++ b/src/cunumeric/sort/sort_template.inl @@ -65,6 +65,9 @@ struct SortImpl { // as the process needs to participate in collective communication // to identify rank-index to sort participant mapping if ((segment_size_l == args.segment_size_g || !args.is_index_space) && rect.empty()) { + if (args.output.is_unbound_store()) { + args.output.bind_empty_data(); + } return; } From e660796ca63732ccae7f3401b7874d47631c74ac Mon Sep 17 00:00:00 2001 From: Mona Han <129724851+mona-nv@users.noreply.github.com> Date: Mon, 11 Dec 2023 09:38:27 +0800 Subject: [PATCH 125/462] Port argsort and add argsort test. (#40) * Port argsort and add argsort test. * Add large array and dim>3 tests for argsort * Add max dim pre-define check based on review comments --- src/cunumeric/operators.cc | 10 + src/cunumeric/operators.h | 2 + tests/cpp/integration/test_argsort.cc | 477 ++++++++++++++++++++++++++ tests/cpp/integration/test_sort.cc | 6 +- 4 files changed, 492 insertions(+), 3 deletions(-) create mode 100644 tests/cpp/integration/test_argsort.cc diff --git a/src/cunumeric/operators.cc b/src/cunumeric/operators.cc index 469b486ce5..6782af7259 100644 --- a/src/cunumeric/operators.cc +++ b/src/cunumeric/operators.cc @@ -369,6 +369,16 @@ NDArray sort(NDArray input, std::optional axis /*=-1*/, std::string kin return result; } +NDArray argsort(NDArray input, + std::optional axis /*=-1*/, + std::string kind /*="quicksort"*/) +{ + auto runtime = CuNumericRuntime::get_runtime(); + auto result = runtime->create_array(input.shape(), legate::int64()); + result.sort(input, true, axis, kind); + return result; +} + NDArray transpose(NDArray a) { return a.transpose(); } NDArray transpose(NDArray a, std::vector axes) { return a.transpose(axes); } diff --git a/src/cunumeric/operators.h b/src/cunumeric/operators.h index 77894717d9..9abffcdb5e 100644 --- a/src/cunumeric/operators.h +++ b/src/cunumeric/operators.h @@ -91,6 +91,8 @@ NDArray convolve(NDArray a, NDArray v); NDArray sort(NDArray input, std::optional axis = -1, std::string kind = "quicksort"); +NDArray argsort(NDArray input, std::optional axis = -1, std::string kind = "quicksort"); + NDArray transpose(NDArray a); NDArray transpose(NDArray a, std::vector axes); diff --git a/tests/cpp/integration/test_argsort.cc b/tests/cpp/integration/test_argsort.cc new file mode 100644 index 0000000000..462afc52ae --- /dev/null +++ b/tests/cpp/integration/test_argsort.cc @@ -0,0 +1,477 @@ +/* Copyright 2023 NVIDIA Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +#include +#include +#include + +#include +#include "legate.h" +#include "cunumeric.h" +#include "util.inl" + +auto get_argsort_expect_result() +{ + std::vector>> expect_result = { + {{0, {11, 4, 1, 5, 3, 9, 8, 6, 7, 0, 10, 2}}}, + {{-1, {11, 4, 1, 5, 3, 9, 8, 6, 7, 0, 10, 2}}, + {0, {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}}, + {1, {11, 4, 1, 5, 3, 9, 8, 6, 7, 0, 10, 2}}}, + {{-1, {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}}, + {0, {11, 4, 1, 5, 3, 9, 8, 6, 7, 0, 10, 2}}, + {1, {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}}}, + {{-1, {1, 3, 0, 2, 0, 1, 2, 3, 3, 1, 0, 2}}, + {0, {1, 0, 1, 2, 2, 1, 2, 0, 0, 2, 0, 1}}, + {1, {1, 3, 0, 2, 0, 1, 2, 3, 3, 1, 0, 2}}}, + {{-2, {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}}, + {-1, {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}}, + {0, {11, 4, 1, 5, 3, 9, 8, 6, 7, 0, 10, 2}}, + {1, {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}}, + {2, {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}}}, + {{-2, {11, 4, 1, 5, 3, 9, 8, 6, 7, 0, 10, 2}}, + {-1, {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}}, + {0, {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}}, + {1, {11, 4, 1, 5, 3, 9, 8, 6, 7, 0, 10, 2}}, + {2, {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}}}, + {{-2, {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}}, + {-1, {11, 4, 1, 5, 3, 9, 8, 6, 7, 0, 10, 2}}, + {0, {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}}, + {1, {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}}, + {2, {11, 4, 1, 5, 3, 9, 8, 6, 7, 0, 10, 2}}}, + {{-2, {1, 1, 1, 0, 0, 0, 1, 0, 1, 0, 1, 0}}, + {-1, {1, 0, 2, 1, 2, 0, 2, 0, 1, 2, 0, 1}}, + {0, {1, 0, 1, 0, 0, 1, 0, 1, 0, 1, 1, 0}}, + {1, {1, 1, 1, 0, 0, 0, 1, 0, 1, 0, 1, 0}}, + {2, {1, 0, 2, 1, 2, 0, 2, 0, 1, 2, 0, 1}}}}; + return expect_result; +} + +auto get_argsort_expect_result_4d() +{ + std::vector>> expect_result = { + {{-3, {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}}, + {-2, {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}}, + {-1, {14, 6, 2, 7, 4, 12, 11, 9, 10, 1, 13, 3, 5, 0, 15, 8}}, + {0, {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}}, + {1, {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}}, + {2, {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}}, + {3, {14, 6, 2, 7, 4, 12, 11, 9, 10, 1, 13, 3, 5, 0, 15, 8}}}, + {{-3, {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}}, + {-2, {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}}, + {-1, {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}}, + {0, {14, 6, 2, 7, 4, 12, 11, 9, 10, 1, 13, 3, 5, 0, 15, 8}}, + {1, {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}}, + {2, {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}}, + {3, {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}}}, + {{-3, {1, 0, 1, 1, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 1}}, + {-2, {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}}, + {-1, {2, 1, 3, 0, 2, 3, 0, 1, 3, 1, 2, 0, 2, 0, 1, 3}}, + {0, {0, 1, 0, 1, 0, 1, 1, 0, 1, 0, 1, 0, 1, 0, 0, 1}}, + {1, {1, 0, 1, 1, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 1}}, + {2, {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}}, + {3, {2, 1, 3, 0, 2, 3, 0, 1, 3, 1, 2, 0, 2, 0, 1, 3}}}}; + return expect_result; +} + +auto get_argsort_expect_result_5d() +{ + std::vector>> expect_result = { + {{-4, {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}}, + {-3, {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}}, + {-2, {14, 6, 2, 7, 4, 12, 11, 9, 10, 1, 13, 3, 5, 0, 15, 8}}, + {-1, {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}}, + {0, {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}}, + {1, {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}}, + {2, {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}}, + {3, {14, 6, 2, 7, 4, 12, 11, 9, 10, 1, 13, 3, 5, 0, 15, 8}}, + {4, {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}}}, + {{-4, {14, 6, 2, 7, 4, 12, 11, 9, 10, 1, 13, 3, 5, 0, 15, 8}}, + {-3, {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}}, + {-2, {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}}, + {-1, {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}}, + {0, {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}}, + {1, {14, 6, 2, 7, 4, 12, 11, 9, 10, 1, 13, 3, 5, 0, 15, 8}}, + {2, {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}}, + {3, {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}}, + {4, {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}}}, + {{-4, {0, 1, 0, 1, 0, 1, 1, 0, 1, 0, 1, 0, 1, 0, 0, 1}}, + {-3, {1, 0, 1, 1, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 1}}, + {-2, {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}}, + {-1, {2, 1, 3, 0, 2, 3, 0, 1, 3, 1, 2, 0, 2, 0, 1, 3}}, + {0, {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}}, + {1, {0, 1, 0, 1, 0, 1, 1, 0, 1, 0, 1, 0, 1, 0, 0, 1}}, + {2, {1, 0, 1, 1, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 1}}, + {3, {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}}, + {4, {2, 1, 3, 0, 2, 3, 0, 1, 3, 1, 2, 0, 2, 0, 1, 3}}}}; + return expect_result; +} + +auto get_argsort_expect_result_6d() +{ + std::vector>> expect_result = { + {{-5, {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}}, + {-4, {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}}, + {-3, {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}}, + {-2, {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}}, + {-1, {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}}, + {0, {14, 6, 2, 7, 4, 12, 11, 9, 10, 1, 13, 3, 5, 0, 15, 8}}, + {1, {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}}, + {2, {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}}, + {3, {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}}, + {4, {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}}, + {5, {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}}}, + {{-5, {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}}, + {-4, {14, 6, 2, 7, 4, 12, 11, 9, 10, 1, 13, 3, 5, 0, 15, 8}}, + {-3, {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}}, + {-2, {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}}, + {-1, {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}}, + {0, {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}}, + {1, {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}}, + {2, {14, 6, 2, 7, 4, 12, 11, 9, 10, 1, 13, 3, 5, 0, 15, 8}}, + {3, {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}}, + {4, {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}}, + {5, {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}}}, + {{-5, {0, 1, 0, 1, 0, 1, 1, 0, 1, 0, 1, 0, 1, 0, 0, 1}}, + {-4, {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}}, + {-3, {1, 0, 1, 1, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 1}}, + {-2, {1, 0, 0, 1, 1, 1, 0, 0, 1, 1, 0, 0, 1, 0, 0, 1}}, + {-1, {1, 0, 0, 1, 0, 1, 0, 1, 1, 0, 1, 0, 0, 1, 0, 1}}, + {0, {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}}, + {1, {0, 1, 0, 1, 0, 1, 1, 0, 1, 0, 1, 0, 1, 0, 0, 1}}, + {2, {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}}, + {3, {1, 0, 1, 1, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 1}}, + {4, {1, 0, 0, 1, 1, 1, 0, 0, 1, 1, 0, 0, 1, 0, 0, 1}}, + {5, {1, 0, 0, 1, 0, 1, 0, 1, 1, 0, 1, 0, 0, 1, 0, 1}}}}; + return expect_result; +} + +auto get_argsort_expect_result_7d() +{ + std::vector>> expect_result = { + {{-6, {14, 6, 2, 7, 4, 12, 11, 9, 10, 1, 13, 3, 5, 0, 15, 8}}, + {-5, {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}}, + {-4, {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}}, + {-3, {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}}, + {-2, {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}}, + {-1, {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}}, + {0, {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}}, + {1, {14, 6, 2, 7, 4, 12, 11, 9, 10, 1, 13, 3, 5, 0, 15, 8}}, + {2, {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}}, + {3, {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}}, + {4, {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}}, + {5, {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}}, + {6, {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}}}, + {{-6, {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}}, + {-5, {0, 1, 0, 1, 0, 1, 1, 0, 1, 0, 1, 0, 1, 0, 0, 1}}, + {-4, {1, 0, 1, 1, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 1}}, + {-3, {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}}, + {-2, {2, 1, 3, 0, 2, 3, 0, 1, 3, 1, 2, 0, 2, 0, 1, 3}}, + {-1, {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}}, + {0, {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}}, + {1, {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}}, + {2, {0, 1, 0, 1, 0, 1, 1, 0, 1, 0, 1, 0, 1, 0, 0, 1}}, + {3, {1, 0, 1, 1, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 1}}, + {4, {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}}, + {5, {2, 1, 3, 0, 2, 3, 0, 1, 3, 1, 2, 0, 2, 0, 1, 3}}, + {6, {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}}}, + {{-6, {1, 0, 1, 1, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 1}}, + {-5, {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}}, + {-4, {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}}, + {-3, {1, 0, 0, 1, 1, 1, 0, 0, 1, 1, 0, 0, 1, 0, 0, 1}}, + {-2, {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}}, + {-1, {1, 0, 0, 1, 0, 1, 0, 1, 1, 0, 1, 0, 0, 1, 0, 1}}, + {0, {0, 1, 0, 1, 0, 1, 1, 0, 1, 0, 1, 0, 1, 0, 0, 1}}, + {1, {1, 0, 1, 1, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 1}}, + {2, {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}}, + {3, {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}}, + {4, {1, 0, 0, 1, 1, 1, 0, 0, 1, 1, 0, 0, 1, 0, 0, 1}}, + {5, {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}}, + {6, {1, 0, 0, 1, 0, 1, 0, 1, 1, 0, 1, 0, 0, 1, 0, 1}}}}; + return expect_result; +} + +template +void test_argsort(std::array& in_array, + std::array& expect, + legate::Type leg_type, + std::vector shape, + std::optional axis, + bool test_only_stable = false) +{ + auto A1 = cunumeric::zeros(shape, leg_type); + if (in_array.size() != 0) { + if (in_array.size() == 1) { + A1.fill(legate::Scalar(in_array[0]), false); + } else { + assign_values_to_array(A1, in_array.data(), in_array.size()); + } + } + std::vector algos = {"quicksort", "mergesort", "heapsort", "stable"}; + if (test_only_stable) { + algos = {"mergesort", "stable"}; + } + for (auto algo = algos.begin(); algo < algos.end(); ++algo) { + auto B1 = cunumeric::argsort(A1, axis, *algo); + if (in_array.size() != 0) { + check_array_eq(B1, expect.data(), expect.size()); + } + } +} + +template +void argsort_basic_axis_impl( + std::vector>& test_shapes, + std::array in_array, + std::vector>>& expect_result, + legate::Type leg_type, + bool test_only_stable = false) +{ + size_t test_shape_size = test_shapes.size(); + for (size_t i = 0; i < test_shape_size; ++i) { + auto test_shape = test_shapes[i]; + int32_t dim = test_shape.size(); + for (int32_t axis = -dim + 1; axis < dim; ++axis) { + std::cout << "Axis is: " << axis << std::endl; + auto expect_val = expect_result[i][axis]; + if (dim == 1) { + test_argsort( + in_array, expect_val, leg_type, test_shape, axis, test_only_stable); + } else if (dim == 2) { + test_argsort( + in_array, expect_val, leg_type, test_shape, axis, test_only_stable); + } else if (dim == 3) { + test_argsort( + in_array, expect_val, leg_type, test_shape, axis, test_only_stable); + } else if (dim == 4) { +#if LEGATE_MAX_DIM >= 4 + test_argsort( + in_array, expect_val, leg_type, test_shape, axis, test_only_stable); +#endif + } else if (dim == 5) { +#if LEGATE_MAX_DIM >= 5 + test_argsort( + in_array, expect_val, leg_type, test_shape, axis, test_only_stable); +#endif + } else if (dim == 6) { +#if LEGATE_MAX_DIM >= 6 + test_argsort( + in_array, expect_val, leg_type, test_shape, axis, test_only_stable); +#endif + } else if (dim == 7) { +#if LEGATE_MAX_DIM >= 7 + test_argsort( + in_array, expect_val, leg_type, test_shape, axis, test_only_stable); +#endif + } + } + } +} + +void argsort_basic_axis() +{ + std::vector> test_shapes = { + {12}, {1, 12}, {12, 1}, {3, 4}, {12, 1, 1}, {1, 12, 1}, {1, 1, 12}, {2, 2, 3}}; + + auto expect_result = get_argsort_expect_result(); + + // Test int type + std::array in_array1 = {10, 3, 12, 5, 2, 4, 8, 9, 7, 6, 11, 1}; + argsort_basic_axis_impl(test_shapes, in_array1, expect_result, legate::int32()); + + // Test float type + std::array in_array2 = {10.5, 3.66, 12, 5.98, 2.2, 4, 8, 9, 7.9, 6, 11, 1.5}; + argsort_basic_axis_impl(test_shapes, in_array2, expect_result, legate::float64()); + + // Test complex type + std::array, 12> in_array3 = {complex(10, 3), + complex(2.2, 10.5), + complex(12, 5), + complex(6, 5.98), + complex(2, 4), + complex(6, 4), + complex(8, 9), + complex(8, 11), + complex(7.9, 12), + complex(7, 6), + complex(11, 1), + complex(1.5, 3.66)}; + argsort_basic_axis_impl, 12>( + test_shapes, in_array3, expect_result, legate::complex64()); +} + +void argsort_basic_axis_stable() +{ + std::vector> test_shapes = { + {12}, {1, 12}, {12, 1}, {3, 4}, {12, 1, 1}, {1, 12, 1}, {1, 1, 12}, {2, 2, 3}}; + auto expect_result = get_argsort_expect_result(); + + // Test int type + std::array in_array1 = {10, 3, 12, 5, 2, 3, 8, 8, 7, 6, 10, 1}; + argsort_basic_axis_impl( + test_shapes, in_array1, expect_result, legate::int32(), true); + + // Test float type + std::array in_array2 = {10.5, 3.66, 12, 5.98, 2.2, 3.66, 8, 9, 7.9, 6, 10.5, 1.5}; + argsort_basic_axis_impl( + test_shapes, in_array2, expect_result, legate::float64(), true); + + // Test complex type + std::array, 12> in_array3 = {complex(10, 3), + complex(2.2, 10.5), + complex(12, 5), + complex(6, 5.98), + complex(2, 4), + complex(2.2, 10.5), + complex(8, 9), + complex(8, 11), + complex(7.9, 12), + complex(7, 6), + complex(10, 3), + complex(1.5, 3.66)}; + argsort_basic_axis_impl, 12>( + test_shapes, in_array3, expect_result, legate::complex64(), true); +} + +void argsort_basic_axis_max_dim() +{ + // Only test int type for max dim + std::array in_array = {14, 10, 3, 12, 5, 13, 2, 4, 16, 8, 9, 7, 6, 11, 1, 15}; +#if LEGATE_MAX_DIM >= 4 + std::vector> test_shapes_4d = {{1, 1, 1, 16}, {16, 1, 1, 1}, {2, 2, 1, 4}}; + auto expect_result_4d = get_argsort_expect_result_4d(); + argsort_basic_axis_impl(test_shapes_4d, in_array, expect_result_4d, legate::int32()); +#endif + +#if LEGATE_MAX_DIM >= 5 + std::vector> test_shapes_5d = { + {1, 1, 1, 16, 1}, {1, 16, 1, 1, 1}, {1, 2, 2, 1, 4}}; + auto expect_result_5d = get_argsort_expect_result_5d(); + argsort_basic_axis_impl(test_shapes_5d, in_array, expect_result_5d, legate::int32()); +#endif + +#if LEGATE_MAX_DIM >= 6 + std::vector> test_shapes_6d = { + {16, 1, 1, 1, 1, 1}, {1, 1, 16, 1, 1, 1}, {1, 2, 1, 2, 2, 2}}; + auto expect_result_6d = get_argsort_expect_result_6d(); + argsort_basic_axis_impl(test_shapes_6d, in_array, expect_result_6d, legate::int32()); +#endif + +#if LEGATE_MAX_DIM >= 7 + std::vector> test_shapes_7d = { + {1, 16, 1, 1, 1, 1, 1}, {1, 1, 2, 2, 1, 4, 1}, {2, 2, 1, 1, 2, 1, 2}}; + auto expect_result_7d = get_argsort_expect_result_7d(); + argsort_basic_axis_impl(test_shapes_7d, in_array, expect_result_7d, legate::int32()); +#endif +} + +void argsort_large_array() +{ + const int32_t count = 10000; + std::vector> test_shapes = {{count}}; + std::array expect_val; + for (int64_t j = 0; j < count; j++) { + expect_val[j] = count - 1 - j; + } + std::vector>> expect_result = {{{0, expect_val}}}; + + // Test int type for large array + std::array in_array1; + for (int32_t i = 0; i < count; i++) { + in_array1[i] = count - i; + } + argsort_basic_axis_impl(test_shapes, in_array1, expect_result, legate::int32()); + + // Test float type + std::array in_array2; + for (int32_t i = 0; i < count; i++) { + in_array2[i] = count * 1.1 - i; + } + argsort_basic_axis_impl(test_shapes, in_array2, expect_result, legate::float64()); + + // Test complex type + std::array, count> in_array3; + for (int32_t i = 0; i < count; i++) { + in_array3[i] = complex(count - i, count - i); + } + argsort_basic_axis_impl, count>( + test_shapes, in_array3, expect_result, legate::complex64()); +} + +void argsort_empty_array() +{ + std::vector> test_shapes = { + {0}, {0, 1}, {1, 0}, {1, 0, 0}, {1, 1, 0}, {1, 0, 1}}; + + std::array in_array = {}; + std::array expect_val = {}; + size_t test_shape_size = test_shapes.size(); + for (size_t i = 0; i < test_shape_size; ++i) { + auto test_shape = test_shapes[i]; + int32_t dim = test_shape.size(); + for (int32_t axis = -dim + 1; axis < dim; ++axis) { + if (dim == 1) { + test_argsort(in_array, expect_val, legate::int32(), test_shape, axis); + } else if (dim == 2) { + test_argsort(in_array, expect_val, legate::int32(), test_shape, axis); + } else { + test_argsort(in_array, expect_val, legate::int32(), test_shape, axis); + } + } + } +} + +void argsort_single_item_array() +{ + std::vector> test_shapes = {{1}, {1, 1}, {1, 1, 1}}; + + std::array in_array = {12}; + std::array expect_val = {0}; + size_t test_shape_size = test_shapes.size(); + for (size_t i = 0; i < test_shape_size; ++i) { + auto test_shape = test_shapes[i]; + int32_t dim = test_shape.size(); + for (int32_t axis = -dim + 1; axis < dim; ++axis) { + if (dim == 1) { + test_argsort(in_array, expect_val, legate::int32(), test_shape, axis); + } else if (dim == 2) { + test_argsort(in_array, expect_val, legate::int32(), test_shape, axis); + } else { + test_argsort(in_array, expect_val, legate::int32(), test_shape, axis); + } + } + } +} + +void argsort_negative_test() +{ + auto in_ar1 = cunumeric::zeros({2, 3}, legate::int32()); + + // Test invalid input sort axis + EXPECT_THROW(cunumeric::argsort(in_ar1, 2, "quicksort"), std::invalid_argument); + EXPECT_THROW(cunumeric::argsort(in_ar1, -3, "quicksort"), std::invalid_argument); + + // Test invalid input algorithm + EXPECT_THROW(cunumeric::argsort(in_ar1, 0, "negative"), std::invalid_argument); +} + +// void cpp_test() +TEST(Argsort, BasicAxis) { argsort_basic_axis(); } +TEST(Argsort, BasicAxisStable) { argsort_basic_axis_stable(); } +TEST(Argsort, BasicAxisMaxDim) { argsort_basic_axis_max_dim(); } +TEST(Argsort, LargeArray) { argsort_large_array(); } +TEST(Argsort, EmptyArray) { argsort_empty_array(); } +TEST(Argsort, SingleItemArray) { argsort_single_item_array(); } +TEST(Argsort, Negative) { argsort_negative_test(); } diff --git a/tests/cpp/integration/test_sort.cc b/tests/cpp/integration/test_sort.cc index eb9ac37cf3..02b02978ed 100644 --- a/tests/cpp/integration/test_sort.cc +++ b/tests/cpp/integration/test_sort.cc @@ -549,9 +549,9 @@ void sort_basic_axis() sort_basic_axis_impl(test_shapes, in_array1, expect_result1, legate::int32()); // Test float type - std::array int_array2 = {1.5, 3.66, 6, 5.98, 2.2, 10.5, 8, 11, 7.9, 12, 9, 4}; - auto expect_result2 = get_expect_result_double(); - sort_basic_axis_impl(test_shapes, int_array2, expect_result2, legate::float64()); + std::array in_array2 = {1.5, 3.66, 6, 5.98, 2.2, 10.5, 8, 11, 7.9, 12, 9, 4}; + auto expect_result2 = get_expect_result_double(); + sort_basic_axis_impl(test_shapes, in_array2, expect_result2, legate::float64()); // Test complex type std::array, 12> in_array3 = {complex(10, 3), From 2997ef7c8306ef78d78c9d97620ea1617ad3fd2f Mon Sep 17 00:00:00 2001 From: Mona Han <129724851+mona-nv@users.noreply.github.com> Date: Mon, 11 Dec 2023 11:08:57 +0800 Subject: [PATCH 126/462] Port msort and add tests (#45) * Port msort and add tests * Add large array and dim>3 tests for msort * Add max dim pre-define check based on review comments --- src/cunumeric/operators.cc | 2 + src/cunumeric/operators.h | 2 + tests/cpp/integration/test_msort.cc | 400 ++++++++++++++++++++++++++++ 3 files changed, 404 insertions(+) create mode 100644 tests/cpp/integration/test_msort.cc diff --git a/src/cunumeric/operators.cc b/src/cunumeric/operators.cc index 6782af7259..838dda8c35 100644 --- a/src/cunumeric/operators.cc +++ b/src/cunumeric/operators.cc @@ -379,6 +379,8 @@ NDArray argsort(NDArray input, return result; } +NDArray msort(NDArray input) { return sort(input, 0); } + NDArray transpose(NDArray a) { return a.transpose(); } NDArray transpose(NDArray a, std::vector axes) { return a.transpose(axes); } diff --git a/src/cunumeric/operators.h b/src/cunumeric/operators.h index 9abffcdb5e..e7b26f4518 100644 --- a/src/cunumeric/operators.h +++ b/src/cunumeric/operators.h @@ -93,6 +93,8 @@ NDArray sort(NDArray input, std::optional axis = -1, std::string kind = NDArray argsort(NDArray input, std::optional axis = -1, std::string kind = "quicksort"); +NDArray msort(NDArray input); + NDArray transpose(NDArray a); NDArray transpose(NDArray a, std::vector axes); diff --git a/tests/cpp/integration/test_msort.cc b/tests/cpp/integration/test_msort.cc new file mode 100644 index 0000000000..23e0c1f0aa --- /dev/null +++ b/tests/cpp/integration/test_msort.cc @@ -0,0 +1,400 @@ +/* Copyright 2023 NVIDIA Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +#include +#include +#include + +#include +#include "legate.h" +#include "cunumeric.h" +#include "util.inl" + +auto get_msort_expect_result_int() +{ + std::vector> expect_result = {{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12}, + {10, 3, 12, 5, 2, 4, 8, 9, 7, 6, 11, 1}, + {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12}, + {2, 3, 8, 1, 7, 4, 11, 5, 10, 6, 12, 9}, + {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12}, + {10, 3, 12, 5, 2, 4, 8, 9, 7, 6, 11, 1}, + {10, 3, 12, 5, 2, 4, 8, 9, 7, 6, 11, 1}, + {8, 3, 7, 5, 2, 1, 10, 9, 12, 6, 11, 4}}; + return expect_result; +} + +auto get_msort_expect_result_int_4d() +{ + std::vector> expect_result = { + {14, 10, 3, 12, 5, 13, 2, 4, 16, 8, 9, 7, 6, 11, 1, 15}, + {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16}, + {14, 8, 3, 7, 5, 11, 1, 4, 16, 10, 9, 12, 6, 13, 2, 15}}; + return expect_result; +} + +auto get_msort_expect_result_int_5d() +{ + std::vector> expect_result = { + {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16}, + {14, 10, 3, 12, 5, 13, 2, 4, 16, 8, 9, 7, 6, 11, 1, 15}, + {14, 10, 3, 12, 5, 13, 2, 4, 16, 8, 9, 7, 6, 11, 1, 15}}; + return expect_result; +} + +auto get_msort_expect_result_int_6d() +{ + std::vector> expect_result = { + {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16}, + {14, 10, 3, 12, 5, 13, 2, 4, 16, 8, 9, 7, 6, 11, 1, 15}, + {14, 8, 3, 7, 5, 11, 1, 4, 16, 10, 9, 12, 6, 13, 2, 15}}; + return expect_result; +} + +auto get_msort_expect_result_int_7d() +{ + std::vector> expect_result = { + {14, 10, 3, 12, 5, 13, 2, 4, 16, 8, 9, 7, 6, 11, 1, 15}, + {5, 8, 1, 4, 6, 10, 2, 7, 14, 11, 3, 12, 16, 13, 9, 15}, + {14, 8, 3, 7, 5, 11, 1, 4, 16, 10, 9, 12, 6, 13, 2, 15}}; + return expect_result; +} + +auto get_msort_expect_result_double() +{ + std::vector> expect_result = { + {1.5, 2.2, 3.66, 4, 5.98, 6, 7.9, 8, 9, 10.5, 11, 12}, + {1.5, 3.66, 6, 5.98, 2.2, 10.5, 8, 11, 7.9, 12, 9, 4}, + {1.5, 2.2, 3.66, 4, 5.98, 6, 7.9, 8, 9, 10.5, 11, 12}, + {1.5, 3.66, 6, 4, 2.2, 10.5, 8, 5.98, 7.9, 12, 9, 11}, + {1.5, 2.2, 3.66, 4, 5.98, 6, 7.9, 8, 9, 10.5, 11, 12}, + {1.5, 3.66, 6, 5.98, 2.2, 10.5, 8, 11, 7.9, 12, 9, 4}, + {1.5, 3.66, 6, 5.98, 2.2, 10.5, 8, 11, 7.9, 12, 9, 4}, + {1.5, 3.66, 6, 5.98, 2.2, 4, 8, 11, 7.9, 12, 9, 10.5}}; + return expect_result; +} + +auto get_msort_expect_result_complex() +{ + std::vector, 12>> expect_result = {{complex(1.5, 3.66), + complex(2, 4), + complex(2.2, 10.5), + complex(6, 4), + complex(6, 5.98), + complex(7, 6), + complex(7.9, 12), + complex(8, 9), + complex(8, 11), + complex(10, 3), + complex(11, 1), + complex(12, 5)}, + {complex(10, 3), + complex(12, 5), + complex(2, 4), + complex(8, 9), + complex(7, 6), + complex(11, 1), + complex(1.5, 3.66), + complex(6, 5.98), + complex(2.2, 10.5), + complex(8, 11), + complex(7.9, 12), + complex(6, 4)}, + {complex(1.5, 3.66), + complex(2, 4), + complex(2.2, 10.5), + complex(6, 4), + complex(6, 5.98), + complex(7, 6), + complex(7.9, 12), + complex(8, 9), + complex(8, 11), + complex(10, 3), + complex(11, 1), + complex(12, 5)}, + {complex(2.2, 10.5), + complex(8, 11), + complex(1.5, 3.66), + complex(6, 4), + complex(7, 6), + complex(11, 1), + complex(2, 4), + complex(6, 5.98), + complex(10, 3), + complex(12, 5), + complex(7.9, 12), + complex(8, 9)}, + {complex(1.5, 3.66), + complex(2, 4), + complex(2.2, 10.5), + complex(6, 4), + complex(6, 5.98), + complex(7, 6), + complex(7.9, 12), + complex(8, 9), + complex(8, 11), + complex(10, 3), + complex(11, 1), + complex(12, 5)}, + {complex(10, 3), + complex(12, 5), + complex(2, 4), + complex(8, 9), + complex(7, 6), + complex(11, 1), + complex(1.5, 3.66), + complex(6, 5.98), + complex(2.2, 10.5), + complex(8, 11), + complex(7.9, 12), + complex(6, 4)}, + {complex(10, 3), + complex(12, 5), + complex(2, 4), + complex(8, 9), + complex(7, 6), + complex(11, 1), + complex(1.5, 3.66), + complex(6, 5.98), + complex(2.2, 10.5), + complex(8, 11), + complex(7.9, 12), + complex(6, 4)}, + {complex(1.5, 3.66), + complex(6, 5.98), + complex(2, 4), + complex(8, 9), + complex(7, 6), + complex(6, 4), + complex(10, 3), + complex(12, 5), + complex(2.2, 10.5), + complex(8, 11), + complex(7.9, 12), + complex(11, 1)}}; + return expect_result; +} + +template +void test_msort(std::array& in_array, + std::array& expect, + legate::Type leg_type, + std::vector shape) +{ + auto A1 = cunumeric::zeros(shape, leg_type); + if (in_array.size() != 0) { + if (in_array.size() == 1) { + A1.fill(legate::Scalar(in_array[0]), false); + } else { + assign_values_to_array(A1, in_array.data(), in_array.size()); + } + print_array(A1); + } + + auto B1 = cunumeric::msort(A1); + // if (in_array.size() != 0) { + // check_array_eq(B1, expect.data(), expect.size()); + // } +} + +template +void msort_basic_impl(std::vector>& test_shapes, + std::array in_array, + std::vector>& expect_result, + legate::Type leg_type) +{ + size_t test_shape_size = test_shapes.size(); + for (size_t i = 0; i < test_shape_size; ++i) { + auto test_shape = test_shapes[i]; + int32_t dim = test_shape.size(); + auto expect_val = expect_result[i]; + if (dim == 1) { + test_msort(in_array, expect_val, leg_type, test_shape); + } else if (dim == 2) { + test_msort(in_array, expect_val, leg_type, test_shape); + } else if (dim == 3) { + test_msort(in_array, expect_val, leg_type, test_shape); + } else if (dim == 4) { +#if LEGATE_MAX_DIM >= 4 + test_msort(in_array, expect_val, leg_type, test_shape); +#endif + } else if (dim == 5) { +#if LEGATE_MAX_DIM >= 5 + test_msort(in_array, expect_val, leg_type, test_shape); +#endif + } else if (dim == 6) { +#if LEGATE_MAX_DIM >= 6 + test_msort(in_array, expect_val, leg_type, test_shape); +#endif + } else if (dim == 7) { +#if LEGATE_MAX_DIM >= 7 + test_msort(in_array, expect_val, leg_type, test_shape); +#endif + } + } +} + +void msort_basic() +{ + std::vector> test_shapes = { + {12}, {1, 12}, {12, 1}, {3, 4}, {12, 1, 1}, {1, 12, 1}, {1, 1, 12}, {2, 2, 3}}; + + // Test int type + std::array in_array1 = {10, 3, 12, 5, 2, 4, 8, 9, 7, 6, 11, 1}; + auto expect_result1 = get_msort_expect_result_int(); + msort_basic_impl(test_shapes, in_array1, expect_result1, legate::int32()); + + // Test float type + std::array in_array2 = {1.5, 3.66, 6, 5.98, 2.2, 10.5, 8, 11, 7.9, 12, 9, 4}; + auto expect_result2 = get_msort_expect_result_double(); + msort_basic_impl(test_shapes, in_array2, expect_result2, legate::float64()); + + // Test complex type + std::array, 12> in_array3 = {complex(10, 3), + complex(12, 5), + complex(2, 4), + complex(8, 9), + complex(7, 6), + complex(11, 1), + complex(1.5, 3.66), + complex(6, 5.98), + complex(2.2, 10.5), + complex(8, 11), + complex(7.9, 12), + complex(6, 4)}; + auto expect_result3 = get_msort_expect_result_complex(); + msort_basic_impl, 12>(test_shapes, in_array3, expect_result3, legate::complex64()); +} + +void msort_basic_max_dim() +{ + // Only test int type for max dim + std::array in_array = {14, 10, 3, 12, 5, 13, 2, 4, 16, 8, 9, 7, 6, 11, 1, 15}; +#if LEGATE_MAX_DIM >= 4 + std::vector> test_shapes_4d = {{1, 1, 1, 16}, {16, 1, 1, 1}, {2, 2, 1, 4}}; + auto expect_result_4d = get_msort_expect_result_int_4d(); + msort_basic_impl(test_shapes_4d, in_array, expect_result_4d, legate::int32()); +#endif + +#if LEGATE_MAX_DIM >= 5 + std::vector> test_shapes_5d = { + {16, 1, 1, 1, 1}, {1, 16, 1, 1, 1}, {1, 2, 2, 1, 4}}; + auto expect_result_5d = get_msort_expect_result_int_5d(); + msort_basic_impl(test_shapes_5d, in_array, expect_result_5d, legate::int32()); +#endif + +#if LEGATE_MAX_DIM >= 6 + std::vector> test_shapes_6d = { + {16, 1, 1, 1, 1, 1}, {1, 1, 16, 1, 1, 1}, {2, 1, 1, 2, 2, 2}}; + auto expect_result_6d = get_msort_expect_result_int_6d(); + msort_basic_impl(test_shapes_6d, in_array, expect_result_6d, legate::int32()); +#endif + +#if LEGATE_MAX_DIM >= 7 + std::vector> test_shapes_7d = { + {1, 16, 1, 1, 1, 1, 1}, {4, 1, 2, 2, 1, 1, 1}, {2, 2, 1, 1, 2, 1, 2}}; + auto expect_result_7d = get_msort_expect_result_int_7d(); + msort_basic_impl(test_shapes_7d, in_array, expect_result_7d, legate::int32()); +#endif +} + +void msort_large_array() +{ + const int32_t count = 10000; + std::vector> test_shapes = {{count}}; + + // Test int type for large array + std::array in_array1; + for (int32_t i = 0; i < count; i++) { + in_array1[i] = count - i; + } + std::array expect_val1; + for (int32_t j = 0; j < count; j++) { + expect_val1[j] = j + 1; + } + std::vector> expect_result1 = {expect_val1}; + msort_basic_impl(test_shapes, in_array1, expect_result1, legate::int32()); + + // Test float type + std::array in_array2; + for (int32_t i = 0; i < count; i++) { + in_array2[i] = count * 1.0 - i; + } + std::array expect_val2; + for (int32_t j = 0; j < count; j++) { + expect_val2[j] = (j + 1) * 1.0; + } + std::vector> expect_result2 = {expect_val2}; + msort_basic_impl(test_shapes, in_array2, expect_result2, legate::float64()); + + // Test complex type + std::array, count> in_array3; + for (int32_t i = 0; i < count; i++) { + in_array3[i] = complex(count - i, count - i); + } + std::array, count> expect_val3; + for (int32_t j = 0; j < count; j++) { + expect_val3[j] = complex(j + 1, j + 1); + } + std::vector, count>> expect_result3 = {expect_val3}; + msort_basic_impl, count>( + test_shapes, in_array3, expect_result3, legate::complex64()); +} + +void msort_empty_array() +{ + std::vector> test_shapes = { + {0}, {0, 1}, {1, 0}, {1, 0, 0}, {1, 1, 0}, {1, 0, 1}}; + + std::array in_array = {}; + size_t test_shape_size = test_shapes.size(); + for (size_t i = 0; i < test_shape_size; ++i) { + auto test_shape = test_shapes[i]; + int32_t dim = test_shape.size(); + if (dim == 1) { + test_msort(in_array, in_array, legate::int32(), test_shape); + } else if (dim == 2) { + test_msort(in_array, in_array, legate::int32(), test_shape); + } else { + test_msort(in_array, in_array, legate::int32(), test_shape); + } + } +} + +void msort_single_item_array() +{ + std::vector> test_shapes = {{1}, {1, 1}, {1, 1, 1}}; + + std::array in_array = {12}; + size_t test_shape_size = test_shapes.size(); + for (size_t i = 0; i < test_shape_size; ++i) { + auto test_shape = test_shapes[i]; + int32_t dim = test_shape.size(); + if (dim == 1) { + test_msort(in_array, in_array, legate::int32(), test_shape); + } else if (dim == 2) { + test_msort(in_array, in_array, legate::int32(), test_shape); + } else { + test_msort(in_array, in_array, legate::int32(), test_shape); + } + } +} + +// void cpp_test() +TEST(Msort, Basic) { msort_basic(); } +TEST(Msort, BasicMaxDim) { msort_basic_max_dim(); } +TEST(Msort, LargeArray) { msort_large_array(); } +TEST(Msort, EmptyArray) { msort_empty_array(); } +TEST(Msort, SingleItemArray) { msort_single_item_array(); } From 5e67cd51ad81e534cd400b2f40c0f907d27bf50e Mon Sep 17 00:00:00 2001 From: Mona Han <129724851+mona-nv@users.noreply.github.com> Date: Mon, 11 Dec 2023 12:41:14 +0800 Subject: [PATCH 127/462] Port sort_complex and add sort_complex test (#68) * Port sort_complex and add sort_complex test * Add int8 support and max dim pre-define check based on review comments --- src/cunumeric/operators.cc | 15 + src/cunumeric/operators.h | 2 + tests/cpp/integration/test_sort_complex.cc | 441 +++++++++++++++++++++ 3 files changed, 458 insertions(+) create mode 100644 tests/cpp/integration/test_sort_complex.cc diff --git a/src/cunumeric/operators.cc b/src/cunumeric/operators.cc index 838dda8c35..b2506f8789 100644 --- a/src/cunumeric/operators.cc +++ b/src/cunumeric/operators.cc @@ -381,6 +381,21 @@ NDArray argsort(NDArray input, NDArray msort(NDArray input) { return sort(input, 0); } +NDArray sort_complex(NDArray input) +{ + auto result = sort(input); + + auto type = result.type(); + if (type == legate::complex64() || type == legate::complex128()) { + return result; + } else if (type == legate::int8() || type == legate::int16() || type == legate::uint8() || + type == legate::uint16()) { + return result.as_type(legate::complex64()); + } else { + return result.as_type(legate::complex128()); + } +} + NDArray transpose(NDArray a) { return a.transpose(); } NDArray transpose(NDArray a, std::vector axes) { return a.transpose(axes); } diff --git a/src/cunumeric/operators.h b/src/cunumeric/operators.h index e7b26f4518..641b421914 100644 --- a/src/cunumeric/operators.h +++ b/src/cunumeric/operators.h @@ -95,6 +95,8 @@ NDArray argsort(NDArray input, std::optional axis = -1, std::string kin NDArray msort(NDArray input); +NDArray sort_complex(NDArray input); + NDArray transpose(NDArray a); NDArray transpose(NDArray a, std::vector axes); diff --git a/tests/cpp/integration/test_sort_complex.cc b/tests/cpp/integration/test_sort_complex.cc new file mode 100644 index 0000000000..0bd1d989a1 --- /dev/null +++ b/tests/cpp/integration/test_sort_complex.cc @@ -0,0 +1,441 @@ +/* Copyright 2023 NVIDIA Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +#include +#include +#include + +#include +#include "legate.h" +#include "cunumeric.h" +#include "util.inl" + +template +auto get_sort_complex_expect_result() +{ + std::vector, 12>> expect_result = {{complex(1.5, 3.66), + complex(2, 4), + complex(2.2, 10.5), + complex(6, 4), + complex(6, 5.98), + complex(7, 6), + complex(7.9, 12), + complex(8, 9), + complex(8, 11), + complex(10, 3), + complex(11, 1), + complex(12, 5)}, + {complex(1.5, 3.66), + complex(2, 4), + complex(2.2, 10.5), + complex(6, 4), + complex(6, 5.98), + complex(7, 6), + complex(7.9, 12), + complex(8, 9), + complex(8, 11), + complex(10, 3), + complex(11, 1), + complex(12, 5)}, + {complex(10, 3), + complex(12, 5), + complex(2, 4), + complex(8, 9), + complex(7, 6), + complex(11, 1), + complex(1.5, 3.66), + complex(6, 5.98), + complex(2.2, 10.5), + complex(8, 11), + complex(7.9, 12), + complex(6, 4)}, + {complex(2, 4), + complex(8, 9), + complex(10, 3), + complex(12, 5), + complex(1.5, 3.66), + complex(6, 5.98), + complex(7, 6), + complex(11, 1), + complex(2.2, 10.5), + complex(6, 4), + complex(7.9, 12), + complex(8, 11)}, + {complex(10, 3), + complex(12, 5), + complex(2, 4), + complex(8, 9), + complex(7, 6), + complex(11, 1), + complex(1.5, 3.66), + complex(6, 5.98), + complex(2.2, 10.5), + complex(8, 11), + complex(7.9, 12), + complex(6, 4)}, + {complex(10, 3), + complex(12, 5), + complex(2, 4), + complex(8, 9), + complex(7, 6), + complex(11, 1), + complex(1.5, 3.66), + complex(6, 5.98), + complex(2.2, 10.5), + complex(8, 11), + complex(7.9, 12), + complex(6, 4)}, + {complex(1.5, 3.66), + complex(2, 4), + complex(2.2, 10.5), + complex(6, 4), + complex(6, 5.98), + complex(7, 6), + complex(7.9, 12), + complex(8, 9), + complex(8, 11), + complex(10, 3), + complex(11, 1), + complex(12, 5)}, + {complex(2, 4), + complex(10, 3), + complex(12, 5), + complex(7, 6), + complex(8, 9), + complex(11, 1), + complex(1.5, 3.66), + complex(2.2, 10.5), + complex(6, 5.98), + complex(6, 4), + complex(7.9, 12), + complex(8, 11)}}; + return expect_result; +} + +template +auto change_int_to_complex(const std::vector>& input) +{ + std::vector, SIZE>> results; + for (size_t i = 0; i < input.size(); i++) { + std::array, SIZE> result; + for (size_t j = 0; j < input[i].size(); j++) { + result[j] = complex(input[i][j], 0); + } + results.push_back(result); + } + return results; +} + +template +auto get_sort_complex_expect_result_from_int() +{ + std::vector> expect_result = {{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12}, + {10, 3, 12, 5, 2, 4, 8, 9, 7, 6, 11, 1}, + {3, 5, 10, 12, 2, 4, 8, 9, 1, 6, 7, 11}, + {10, 3, 12, 5, 2, 4, 8, 9, 7, 6, 11, 1}, + {3, 10, 12, 2, 4, 5, 7, 8, 9, 1, 6, 11}}; + + return change_int_to_complex(expect_result); +} + +auto get_sort_complex_expect_result_4d() +{ + std::vector> expect_result = { + {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16}, + {14, 10, 3, 12, 5, 13, 2, 4, 16, 8, 9, 7, 6, 11, 1, 15}, + {3, 10, 12, 14, 2, 4, 5, 13, 7, 8, 9, 16, 1, 6, 11, 15}}; + + return change_int_to_complex(expect_result); +} + +auto get_sort_complex_expect_result_5d() +{ + std::vector> expect_result = { + {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16}, + {14, 10, 3, 12, 5, 13, 2, 4, 16, 8, 9, 7, 6, 11, 1, 15}, + {3, 10, 12, 14, 2, 4, 5, 13, 7, 8, 9, 16, 1, 6, 11, 15}}; + + return change_int_to_complex(expect_result); +} + +auto get_sort_complex_expect_result_6d() +{ + std::vector> expect_result = { + {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16}, + {14, 10, 3, 12, 5, 13, 2, 4, 16, 8, 9, 7, 6, 11, 1, 15}, + {10, 14, 3, 12, 5, 13, 2, 4, 8, 16, 7, 9, 6, 11, 1, 15}}; + + return change_int_to_complex(expect_result); +} + +auto get_sort_complex_expect_result_7d() +{ + std::vector> expect_result = { + {14, 10, 3, 12, 5, 13, 2, 4, 16, 8, 9, 7, 6, 11, 1, 15}, + {14, 10, 3, 12, 5, 13, 2, 4, 16, 8, 9, 7, 6, 11, 1, 15}, + {10, 14, 3, 12, 5, 13, 2, 4, 8, 16, 7, 9, 6, 11, 1, 15}}; + + return change_int_to_complex(expect_result); +} + +template +void test_sort_complex(std::array& in_array, + std::array& expect, + legate::Type leg_type, + std::vector shape) +{ + auto A1 = cunumeric::zeros(shape, leg_type); + if (in_array.size() != 0) { + if (in_array.size() == 1) { + A1.fill(legate::Scalar(in_array[0]), false); + } else { + assign_values_to_array(A1, in_array.data(), in_array.size()); + } + } + auto B1 = cunumeric::sort_complex(A1); + if (in_array.size() != 0) { + check_array_eq(B1, expect.data(), expect.size()); + } +} + +template +void sort_complex_basic_impl(std::vector>& test_shapes, + std::array in_array, + std::vector>& expect_result, + legate::Type leg_type) +{ + size_t test_shape_size = test_shapes.size(); + for (size_t i = 0; i < test_shape_size; ++i) { + auto test_shape = test_shapes[i]; + int32_t dim = test_shape.size(); + auto expect_val = expect_result[i]; + if (dim == 1) { + test_sort_complex(in_array, expect_val, leg_type, test_shape); + } else if (dim == 2) { + test_sort_complex(in_array, expect_val, leg_type, test_shape); + } else if (dim == 3) { + test_sort_complex(in_array, expect_val, leg_type, test_shape); + } else if (dim == 4) { +#if LEGATE_MAX_DIM >= 4 + test_sort_complex(in_array, expect_val, leg_type, test_shape); +#endif + } else if (dim == 5) { +#if LEGATE_MAX_DIM >= 5 + test_sort_complex(in_array, expect_val, leg_type, test_shape); +#endif + } else if (dim == 6) { +#if LEGATE_MAX_DIM >= 6 + test_sort_complex(in_array, expect_val, leg_type, test_shape); +#endif + } else if (dim == 7) { +#if LEGATE_MAX_DIM >= 7 + test_sort_complex(in_array, expect_val, leg_type, test_shape); +#endif + } + } +} + +void sort_complex_basic() +{ + // Test int8 type + std::vector> test_shapes_int = {{12}, {12, 1}, {3, 4}, {12, 1, 1}, {2, 2, 3}}; + std::array in_array1 = {10, 3, 12, 5, 2, 4, 8, 9, 7, 6, 11, 1}; + auto expect_result1 = get_sort_complex_expect_result_from_int(); + sort_complex_basic_impl, 12>( + test_shapes_int, in_array1, expect_result1, legate::int8()); + + // Test int16 type + std::array in_array2 = {10, 3, 12, 5, 2, 4, 8, 9, 7, 6, 11, 1}; + auto expect_result2 = get_sort_complex_expect_result_from_int(); + sort_complex_basic_impl, 12>( + test_shapes_int, in_array2, expect_result2, legate::int16()); + + // Test int32 type + std::array int_array3 = {10, 3, 12, 5, 2, 4, 8, 9, 7, 6, 11, 1}; + auto expect_result3 = get_sort_complex_expect_result_from_int(); + sort_complex_basic_impl, 12>( + test_shapes_int, int_array3, expect_result3, legate::int32()); + + // Test complex type + std::vector> test_shapes = { + {12}, {1, 12}, {12, 1}, {3, 4}, {12, 1, 1}, {1, 12, 1}, {1, 1, 12}, {2, 2, 3}}; + + std::array, 12> in_array4 = {complex(10, 3), + complex(12, 5), + complex(2, 4), + complex(8, 9), + complex(7, 6), + complex(11, 1), + complex(1.5, 3.66), + complex(6, 5.98), + complex(2.2, 10.5), + complex(8, 11), + complex(7.9, 12), + complex(6, 4)}; + auto expect_result4 = get_sort_complex_expect_result(); + sort_complex_basic_impl, complex, 12>( + test_shapes, in_array4, expect_result4, legate::complex64()); + + std::array, 12> in_array5 = {complex(10, 3), + complex(12, 5), + complex(2, 4), + complex(8, 9), + complex(7, 6), + complex(11, 1), + complex(1.5, 3.66), + complex(6, 5.98), + complex(2.2, 10.5), + complex(8, 11), + complex(7.9, 12), + complex(6, 4)}; + auto expect_result5 = get_sort_complex_expect_result(); + sort_complex_basic_impl, complex, 12>( + test_shapes, in_array5, expect_result5, legate::complex128()); +} + +void sort_complex_basic_max_dim() +{ + // Only test int type for max dim + std::array in_array = {14, 10, 3, 12, 5, 13, 2, 4, 16, 8, 9, 7, 6, 11, 1, 15}; +#if LEGATE_MAX_DIM >= 4 + std::vector> test_shapes_4d = {{1, 1, 1, 16}, {16, 1, 1, 1}, {2, 2, 1, 4}}; + auto expect_result_4d = get_sort_complex_expect_result_4d(); + sort_complex_basic_impl, 16>( + test_shapes_4d, in_array, expect_result_4d, legate::int16()); +#endif + +#if LEGATE_MAX_DIM >= 5 + std::vector> test_shapes_5d = { + {1, 1, 1, 1, 16}, {1, 16, 1, 1, 1}, {1, 2, 2, 1, 4}}; + auto expect_result_5d = get_sort_complex_expect_result_5d(); + sort_complex_basic_impl, 16>( + test_shapes_5d, in_array, expect_result_5d, legate::int16()); +#endif + +#if LEGATE_MAX_DIM >= 6 + std::vector> test_shapes_6d = { + {1, 1, 1, 1, 1, 16}, {1, 1, 16, 1, 1, 1}, {2, 1, 1, 2, 2, 2}}; + auto expect_result_6d = get_sort_complex_expect_result_6d(); + sort_complex_basic_impl, 16>( + test_shapes_6d, in_array, expect_result_6d, legate::int16()); +#endif + +#if LEGATE_MAX_DIM >= 7 + std::vector> test_shapes_7d = { + {1, 16, 1, 1, 1, 1, 1}, {4, 1, 2, 2, 1, 1, 1}, {2, 2, 1, 1, 2, 1, 2}}; + auto expect_result_7d = get_sort_complex_expect_result_7d(); + sort_complex_basic_impl, 16>( + test_shapes_7d, in_array, expect_result_7d, legate::int16()); +#endif +} + +void sort_complex_large_array() +{ + const int32_t count = 10000; + std::vector> test_shapes = {{count}}; + + // Test int16 type for large array + std::array in_array1; + for (int16_t i = 0; i < count; i++) { + in_array1[i] = count - i; + } + std::array, count> expect_val1; + for (int32_t j = 0; j < count; j++) { + expect_val1[j] = complex(j + 1, 0); + } + std::vector, count>> expect_result1 = {expect_val1}; + sort_complex_basic_impl, count>( + test_shapes, in_array1, expect_result1, legate::int16()); + + // Test int32 type for large array + std::array in_array2; + for (int32_t i = 0; i < count; i++) { + in_array2[i] = count - i; + } + std::array, count> expect_val2; + for (int32_t j = 0; j < count; j++) { + expect_val2[j] = complex(j + 1, 0); + } + std::vector, count>> expect_result2 = {expect_val2}; + sort_complex_basic_impl, count>( + test_shapes, in_array2, expect_result2, legate::int32()); + + // Test complex type + std::array, count> in_array3; + for (int32_t i = 0; i < count; i++) { + in_array3[i] = complex(count - i, count - i); + } + std::array, count> expect_val3; + for (int32_t j = 0; j < count; j++) { + expect_val3[j] = complex(j + 1, j + 1); + } + std::vector, count>> expect_result3 = {expect_val3}; + sort_complex_basic_impl, complex, count>( + test_shapes, in_array3, expect_result3, legate::complex64()); +} + +void sort_complex_empty_array() +{ + std::vector> test_shapes = { + {0}, {0, 1}, {1, 0}, {1, 0, 0}, {1, 1, 0}, {1, 0, 1}}; + + std::array, 0> in_array = {}; + size_t test_shape_size = test_shapes.size(); + for (size_t i = 0; i < test_shape_size; ++i) { + auto test_shape = test_shapes[i]; + int32_t dim = test_shape.size(); + if (dim == 1) { + test_sort_complex, complex, 0, 1>( + in_array, in_array, legate::complex64(), test_shape); + } else if (dim == 2) { + test_sort_complex, complex, 0, 2>( + in_array, in_array, legate::complex64(), test_shape); + } else { + test_sort_complex, complex, 0, 3>( + in_array, in_array, legate::complex64(), test_shape); + } + } +} + +void sort_complex_single_item_array() +{ + std::vector> test_shapes = {{1}, {1, 1}, {1, 1, 1}}; + + std::array in_array = {12}; + std::array, 1> expect_result = {complex(12, 0)}; + size_t test_shape_size = test_shapes.size(); + for (size_t i = 0; i < test_shape_size; ++i) { + auto test_shape = test_shapes[i]; + int32_t dim = test_shape.size(); + if (dim == 1) { + test_sort_complex, 1, 1>( + in_array, expect_result, legate::float64(), test_shape); + } else if (dim == 2) { + test_sort_complex, 1, 2>( + in_array, expect_result, legate::float64(), test_shape); + } else { + test_sort_complex, 1, 3>( + in_array, expect_result, legate::float64(), test_shape); + } + } +} + +// void cpp_test() +TEST(SortComplex, Basic) { sort_complex_basic(); } +TEST(SortComplex, BasicMaxDim) { sort_complex_basic_max_dim(); } +TEST(SortComplex, LargeArray) { sort_complex_large_array(); } +TEST(SortComplex, EmptyArray) { sort_complex_empty_array(); } +TEST(SortComplex, SingleItemArray) { sort_complex_single_item_array(); } From 94f40a72d3dde58991bea7ae254a777c1a41e6e2 Mon Sep 17 00:00:00 2001 From: Wonchan Lee Date: Tue, 19 Dec 2023 14:29:24 -0800 Subject: [PATCH 128/462] Combining #58, #59, and #63 (#81) * Fix compile errors in release build * Use a C++ shutdown callback for CUDA library clean-up * Make the callback non-throwable * Bump the legate core commit * Put back the parallel Cholesky code * Bump the legate core commit * Bump the commit hash again * Bump the legate core commit * Bump the legate core commit again * Install psutil for testing * Bump up the legate commit hash --- cmake/versions.json | 2 +- continuous_integration/scripts/test-cunumeric | 4 +- cunumeric/linalg/cholesky.py | 291 +++++++++--------- cunumeric/runtime.py | 10 - src/cunumeric/bits/packbits_template.inl | 2 +- src/cunumeric/bits/unpackbits_template.inl | 2 +- src/cunumeric/cunumeric.cc | 19 ++ src/cunumeric/fft/fft.cu | 2 +- src/cunumeric/index/advanced_indexing_omp.cc | 10 +- .../index/advanced_indexing_template.inl | 2 +- src/cunumeric/index/repeat.cu | 4 +- src/cunumeric/index/wrap_template.inl | 4 +- src/cunumeric/matrix/trilu_template.inl | 4 +- .../random/bitgenerator_template.inl | 2 +- 14 files changed, 178 insertions(+), 180 deletions(-) diff --git a/cmake/versions.json b/cmake/versions.json index f751c7f611..406bf1af65 100644 --- a/cmake/versions.json +++ b/cmake/versions.json @@ -12,7 +12,7 @@ "git_url" : "https://github.com/nv-legate/legate.core.internal.git", "git_shallow": false, "always_download": false, - "git_tag" : "bbfa697e4db494bd0d6b37e711e46fe8113f8669" + "git_tag" : "004de5db6f320d46fdcce984a841364e99b40824" } } } diff --git a/continuous_integration/scripts/test-cunumeric b/continuous_integration/scripts/test-cunumeric index ca57b42e97..57817741b9 100755 --- a/continuous_integration/scripts/test-cunumeric +++ b/continuous_integration/scripts/test-cunumeric @@ -5,7 +5,7 @@ setup_env() { } setup_test_env() { - mamba install -y "clang-tools>=8" "clang>=8" colorama coverage mock pre-commit pytest-cov pytest-lazy-fixture pytest-mock pytest types-docutils pynvml + mamba install -y "clang-tools>=8" "clang>=8" colorama coverage mock pre-commit pytest-cov pytest-lazy-fixture pytest-mock pytest types-docutils pynvml psutil pip install tifffile } @@ -58,4 +58,4 @@ test-cunumeric() { esac } -(test-cunumeric "$@"); \ No newline at end of file +(test-cunumeric "$@"); diff --git a/cunumeric/linalg/cholesky.py b/cunumeric/linalg/cholesky.py index e36868df9d..8a7e7512ec 100644 --- a/cunumeric/linalg/cholesky.py +++ b/cunumeric/linalg/cholesky.py @@ -16,24 +16,27 @@ from typing import TYPE_CHECKING -from legate.core import broadcast, get_legate_runtime, types as ty +from legate.core import ( + Shape, + broadcast, + constant, + dimension, + get_legate_runtime, + types as ty, +) +from legate.settings import settings from cunumeric.config import CuNumericOpCode from .exception import LinAlgError -# from legate.core.shape import Shape -# from legate.settings import settings - - legate_runtime = get_legate_runtime() if TYPE_CHECKING: from legate.core import Library, LogicalStore, LogicalStorePartition from ..deferred import DeferredArray - - # from ..runtime import Runtime + from ..runtime import Runtime legate_runtime = get_legate_runtime() @@ -59,7 +62,7 @@ def transpose_copy_single( def transpose_copy( library: Library, - launch_domain: list[int], + launch_domain: Shape, p_input: LogicalStorePartition, p_output: LogicalStorePartition, ) -> None: @@ -85,76 +88,72 @@ def potrf_single(library: Library, output: LogicalStore) -> None: task.execute() -# def potrf(library: Library, p_output: LogicalStorePartition, i: int) -> None: -# launch_domain = [1, 1] -# task = legate_runtime.create_manual_task( -# library, CuNumericOpCode.POTRF, launch_domain -# ) -# task.throws_exception(LinAlgError) -# task.add_output(p_output) -# task.add_input(p_output) -# task.execute() +def potrf(library: Library, p_output: LogicalStorePartition, i: int) -> None: + task = legate_runtime.create_manual_task( + library, CuNumericOpCode.POTRF, (i + 1, i + 1), lower_bounds=(i, i) + ) + task.throws_exception(LinAlgError) + task.add_output(p_output) + task.add_input(p_output) + task.execute() -# def trsm( -# library: Library, p_output: LogicalStorePartition, i: int, lo: int, hi: int -# ) -> None: -# if lo >= hi: -# return -# -# rhs = p_output.get_child_store(i, i) -# lhs = p_output -# -# launch_domain = [hi - lo, 1] -# task = legate_runtime.create_manual_task( -# library, CuNumericOpCode.TRSM, launch_domain -# ) -# task.add_output(lhs) -# task.add_input(rhs) -# task.add_input(lhs) -# task.execute() - - -# def syrk( -# library: Library, p_output: LogicalStorePartition, k: int, i: int -# ) -> None: -# rhs = p_output.get_child_store(k, i) -# lhs = p_output -# -# launch_domain = [1, 1] -# task = legate_runtime.create_manual_task( -# library, CuNumericOpCode.SYRK, launch_domain -# ) -# task.add_output(lhs) -# task.add_input(rhs) -# task.add_input(lhs) -# task.execute() - - -# def gemm( -# library: Library, -# p_output: LogicalStorePartition, -# k: int, -# i: int, -# lo: int, -# hi: int, -# ) -> None: -# if lo >= hi: -# return -# -# rhs2 = p_output.get_child_store(k, i) -# lhs = p_output -# rhs1 = p_output -# -# launch_domain = [hi - lo, 1] -# task = legate_runtime.create_manual_task( -# library, CuNumericOpCode.GEMM, launch_domain -# ) -# task.add_output(lhs) -# task.add_input(rhs1, proj=lambda p: (p[0], i)) -# task.add_input(rhs2) -# task.add_input(lhs) -# task.execute() +def trsm( + library: Library, p_output: LogicalStorePartition, i: int, lo: int, hi: int +) -> None: + if lo >= hi: + return + + rhs = p_output.get_child_store(i, i) + lhs = p_output + + task = legate_runtime.create_manual_task( + library, CuNumericOpCode.TRSM, (hi, i + 1), lower_bounds=(lo, i) + ) + task.add_output(lhs) + task.add_input(rhs) + task.add_input(lhs) + task.execute() + + +def syrk( + library: Library, p_output: LogicalStorePartition, k: int, i: int +) -> None: + rhs = p_output.get_child_store(k, i) + lhs = p_output + + task = legate_runtime.create_manual_task( + library, CuNumericOpCode.SYRK, (k + 1, k + 1), lower_bounds=(k, k) + ) + task.add_output(lhs) + task.add_input(rhs) + task.add_input(lhs) + task.execute() + + +def gemm( + library: Library, + p_output: LogicalStorePartition, + k: int, + i: int, + lo: int, + hi: int, +) -> None: + if lo >= hi: + return + + rhs2 = p_output.get_child_store(k, i) + lhs = p_output + rhs1 = p_output + + task = legate_runtime.create_manual_task( + library, CuNumericOpCode.GEMM, (hi, k + 1), lower_bounds=(lo, k) + ) + task.add_output(lhs) + task.add_input(rhs1, (dimension(0), constant(i))) + task.add_input(rhs2) + task.add_input(lhs) + task.execute() MIN_CHOLESKY_TILE_SIZE = 2048 @@ -162,28 +161,28 @@ def potrf_single(library: Library, output: LogicalStore) -> None: # TODO: We need a better cost model -# def choose_color_shape(runtime: Runtime, shape: Shape) -> Shape: -# if settings.test(): -# num_tiles = runtime.num_procs * 2 -# return Shape((num_tiles, num_tiles)) -# -# extent = shape[0] -# # If there's only one processor or the matrix is too small, -# # don't even bother to partition it at all -# if runtime.num_procs == 1 or extent <= MIN_CHOLESKY_MATRIX_SIZE: -# return Shape((1, 1)) -# -# # If the matrix is big enough to warrant partitioning, -# # pick the granularity that the tile size is greater than a threshold -# num_tiles = runtime.num_procs -# max_num_tiles = runtime.num_procs * 4 -# while ( -# (extent + num_tiles - 1) // num_tiles > MIN_CHOLESKY_TILE_SIZE -# and num_tiles * 2 <= max_num_tiles -# ): -# num_tiles *= 2 -# -# return Shape((num_tiles, num_tiles)) +def choose_color_shape(runtime: Runtime, shape: Shape) -> Shape: + if settings.test(): + num_tiles = runtime.num_procs * 2 + return Shape((num_tiles, num_tiles)) + + extent = shape[0] + # If there's only one processor or the matrix is too small, + # don't even bother to partition it at all + if runtime.num_procs == 1 or extent <= MIN_CHOLESKY_MATRIX_SIZE: + return Shape((1, 1)) + + # If the matrix is big enough to warrant partitioning, + # pick the granularity that the tile size is greater than a threshold + num_tiles = runtime.num_procs + max_num_tiles = runtime.num_procs * 4 + while ( + (extent + num_tiles - 1) // num_tiles > MIN_CHOLESKY_TILE_SIZE + and num_tiles * 2 <= max_num_tiles + ): + num_tiles *= 2 + + return Shape((num_tiles, num_tiles)) def tril_single(library: Library, output: LogicalStore) -> None: @@ -198,64 +197,52 @@ def tril_single(library: Library, output: LogicalStore) -> None: task.execute() -# def tril(library: Library, p_output: LogicalStorePartition, n: int) -> None: -# launch_domain = [n, n] -# task = legate_runtime.create_manual_task( -# library, CuNumericOpCode.TRILU, launch_domain -# ) -# -# task.add_output(p_output) -# task.add_input(p_output) -# task.add_scalar_arg(True, ty.bool_) -# task.add_scalar_arg(0, ty.int32) -# # Add a fake task argument to indicate that this is for Cholesky -# task.add_scalar_arg(True, ty.bool_) -# -# task.execute() +def tril(library: Library, p_output: LogicalStorePartition, n: int) -> None: + task = legate_runtime.create_manual_task( + library, CuNumericOpCode.TRILU, (n, n) + ) + task.add_output(p_output) + task.add_input(p_output) + task.add_scalar_arg(True, ty.bool_) + task.add_scalar_arg(0, ty.int32) + # Add a fake task argument to indicate that this is for Cholesky + task.add_scalar_arg(True, ty.bool_) -# TODO: Put back this parallel Cholesky implementation -# def cholesky( -# output: DeferredArray, input: DeferredArray, no_tril: bool -# ) -> None: -# runtime = output.runtime -# library = output.library -# -# if runtime.num_procs == 1: -# transpose_copy_single(library, input.base, output.base) -# potrf_single(library, output.base) -# if not no_tril: -# tril_single(library, output.base) -# return -# -# shape = output.base.shape -# initial_color_shape = choose_color_shape(runtime, shape) -# tile_shape = (shape + initial_color_shape - 1) // initial_color_shape -# color_shape = (shape + tile_shape - 1) // tile_shape -# n = color_shape[0] -# -# p_input = input.base.partition_by_tiling(tile_shape) -# p_output = output.base.partition_by_tiling(tile_shape) -# transpose_copy(library, color_shape, p_input, p_output) -# -# for i in range(n): -# potrf(library, p_output, i) -# trsm(library, p_output, i, i + 1, n) -# for k in range(i + 1, n): -# syrk(library, p_output, k, i) -# gemm(library, p_output, k, i, k + 1, n) -# -# if no_tril: -# return -# -# tril(library, p_output, n) + task.execute() def cholesky( output: DeferredArray, input: DeferredArray, no_tril: bool ) -> None: - library = output.library - transpose_copy_single(library, input.base, output.base) - potrf_single(library, output.base) - if not no_tril: - tril_single(library, output.base) + runtime = output.runtime + library = runtime.library + + if runtime.num_procs == 1: + transpose_copy_single(library, input.base, output.base) + potrf_single(library, output.base) + if not no_tril: + tril_single(library, output.base) + return + + shape = output.base.shape + initial_color_shape = choose_color_shape(runtime, shape) + tile_shape = (shape + initial_color_shape - 1) // initial_color_shape + color_shape = (shape + tile_shape - 1) // tile_shape + n = color_shape[0] + + p_input = input.base.partition_by_tiling(tile_shape) + p_output = output.base.partition_by_tiling(tile_shape) + transpose_copy(library, color_shape, p_input, p_output) + + for i in range(n): + potrf(library, p_output, i) + trsm(library, p_output, i, i + 1, n) + for k in range(i + 1, n): + syrk(library, p_output, k, i) + gemm(library, p_output, k, i, k + 1, n) + + if no_tril: + return + + tril(library, p_output, n) diff --git a/cunumeric/runtime.py b/cunumeric/runtime.py index d76e8c2479..00cd859085 100644 --- a/cunumeric/runtime.py +++ b/cunumeric/runtime.py @@ -99,14 +99,6 @@ def _load_cudalibs(self) -> None: task.execute() legate_runtime.issue_execution_fence(block=True) - def _unload_cudalibs(self) -> None: - task = legate_runtime.create_manual_task( - self.library, - CuNumericOpCode.UNLOAD_CUDALIBS, - [self.num_gpus], - ) - task.execute() - def get_argred_type(self, value_dtype: ty.Type) -> ty.Type: cached = self._cached_argred_types.get(value_dtype) if cached is not None: @@ -144,8 +136,6 @@ def _report_coverage(self) -> None: def destroy(self) -> None: assert not self.destroyed - if self.num_gpus > 0: - self._unload_cudalibs() if settings.report_coverage(): self._report_coverage() self.destroyed = True diff --git a/src/cunumeric/bits/packbits_template.inl b/src/cunumeric/bits/packbits_template.inl index 5eb38184a0..bac2784b69 100644 --- a/src/cunumeric/bits/packbits_template.inl +++ b/src/cunumeric/bits/packbits_template.inl @@ -60,7 +60,7 @@ struct PackbitsImpl { assert(unaligned_rect.union_bbox(aligned_rect) == out_rect); #endif - Pitches aligned_pitches, unaligned_pitches; + Pitches aligned_pitches{}, unaligned_pitches{}; auto aligned_volume = aligned_pitches.flatten(aligned_rect); auto unaligned_volume = unaligned_pitches.flatten(unaligned_rect); diff --git a/src/cunumeric/bits/unpackbits_template.inl b/src/cunumeric/bits/unpackbits_template.inl index 2197a1b02c..d6b1c2d856 100644 --- a/src/cunumeric/bits/unpackbits_template.inl +++ b/src/cunumeric/bits/unpackbits_template.inl @@ -43,7 +43,7 @@ struct UnpackbitsImpl { auto out = output.write_accessor(out_rect); auto in = input.read_accessor(in_rect); - Pitches in_pitches; + Pitches in_pitches{}; auto in_volume = in_pitches.flatten(in_rect); UnpackbitsImplBody{}(out, in, in_rect, in_pitches, in_volume, axis); diff --git a/src/cunumeric/cunumeric.cc b/src/cunumeric/cunumeric.cc index 72338f9430..3f9cc7f41b 100644 --- a/src/cunumeric/cunumeric.cc +++ b/src/cunumeric/cunumeric.cc @@ -14,6 +14,7 @@ * */ +#include "cunumeric/cunumeric_c.h" #include "cunumeric/cunumeric_task.h" #include "cunumeric/mapper.h" #include "cunumeric/runtime.h" @@ -31,6 +32,22 @@ static const char* const cunumeric_library_name = "cunumeric"; return registrar; } +void unload_cudalibs() noexcept +{ + auto machine = legate::get_machine(); + + auto num_gpus = machine.count(legate::mapping::TaskTarget::GPU); + if (0 == num_gpus) { + return; + } + + auto runtime = legate::Runtime::get_runtime(); + auto library = runtime->find_library(cunumeric_library_name); + + runtime->submit(runtime->create_task( + library, CuNumericOpCode::CUNUMERIC_UNLOAD_CUDALIBS, legate::Shape{num_gpus})); +} + void registration_callback() { ResourceConfig config; @@ -43,6 +60,8 @@ void registration_callback() CuNumericRegistrar::get_registrar().register_all_tasks(library); CuNumericRuntime::initialize(runtime, library); + + legate::register_shutdown_callback(unload_cudalibs); } } // namespace cunumeric diff --git a/src/cunumeric/fft/fft.cu b/src/cunumeric/fft/fft.cu index 3715dba29a..ba4ec53231 100644 --- a/src/cunumeric/fft/fft.cu +++ b/src/cunumeric/fft/fft.cu @@ -49,7 +49,7 @@ __host__ static inline void copy_into_buffer(TYPE* target, CHECK_CUDA(cudaMemcpyAsync( target, acc.ptr(rect.lo), volume * sizeof(TYPE), cudaMemcpyDeviceToDevice, stream)); } else { - Pitches pitches; + Pitches pitches{}; pitches.flatten(rect); const size_t num_blocks = (volume + THREADS_PER_BLOCK - 1) / THREADS_PER_BLOCK; diff --git a/src/cunumeric/index/advanced_indexing_omp.cc b/src/cunumeric/index/advanced_indexing_omp.cc index 80de2eef3f..3b00d0e327 100644 --- a/src/cunumeric/index/advanced_indexing_omp.cc +++ b/src/cunumeric/index/advanced_indexing_omp.cc @@ -65,10 +65,10 @@ struct AdvancedIndexingImplBody { const AccessorRO& index, const Pitches& pitches, const Rect& rect, - const int key_dim) const + const size_t key_dim) const { size_t skip_size = 1; - for (int i = key_dim; i < DIM; i++) { + for (size_t i = key_dim; i < DIM; i++) { auto diff = 1 + rect.hi[i] - rect.lo[i]; if (diff != 0) { skip_size *= diff; @@ -84,7 +84,7 @@ struct AdvancedIndexingImplBody { // calculating the shape of the output region for this sub-task Point extents; extents[0] = size; - for (int32_t i = 0; i < DIM - key_dim; i++) { + for (size_t i = 0; i < DIM - key_dim; i++) { size_t j = key_dim + i; extents[i + 1] = 1 + rect.hi[j] - rect.lo[j]; } @@ -104,11 +104,11 @@ struct AdvancedIndexingImplBody { if (index[p] == true) { Point out_p; out_p[0] = out_idx; - for (int32_t i = 0; i < DIM - key_dim; i++) { + for (size_t i = 0; i < DIM - key_dim; i++) { size_t j = key_dim + i; out_p[i + 1] = p[j]; } - for (int32_t i = DIM - key_dim + 1; i < DIM; i++) { + for (size_t i = DIM - key_dim + 1; i < DIM; i++) { out_p[i] = 0; } fill_out(out[out_p], p, input[p]); diff --git a/src/cunumeric/index/advanced_indexing_template.inl b/src/cunumeric/index/advanced_indexing_template.inl index 0f129956f3..876a4cf16c 100644 --- a/src/cunumeric/index/advanced_indexing_template.inl +++ b/src/cunumeric/index/advanced_indexing_template.inl @@ -37,7 +37,7 @@ struct AdvancedIndexingImpl { using VAL = type_of; auto input_rect = args.input_array.shape(); auto input_arr = args.input_array.read_accessor(input_rect); - Pitches input_pitches; + Pitches input_pitches{}; size_t volume = input_pitches.flatten(input_rect); auto index_rect = args.indexing_array.shape(); diff --git a/src/cunumeric/index/repeat.cu b/src/cunumeric/index/repeat.cu index 1d1206ca0b..40d4257301 100644 --- a/src/cunumeric/index/repeat.cu +++ b/src/cunumeric/index/repeat.cu @@ -113,7 +113,7 @@ struct RepeatImplBody { auto out = out_array.create_output_buffer(extents, true); Rect out_rect(Point::ZEROES(), extents - Point::ONES()); - Pitches pitches; + Pitches pitches{}; auto out_volume = pitches.flatten(out_rect); const auto blocks = (out_volume + THREADS_PER_BLOCK - 1) / THREADS_PER_BLOCK; @@ -132,7 +132,7 @@ struct RepeatImplBody { { auto stream = get_cached_stream(); - Pitches pitches; + Pitches pitches{}; const auto volume = pitches.flatten(in_rect); // Compute offsets diff --git a/src/cunumeric/index/wrap_template.inl b/src/cunumeric/index/wrap_template.inl index 005e17adf7..a480ebb4d7 100644 --- a/src/cunumeric/index/wrap_template.inl +++ b/src/cunumeric/index/wrap_template.inl @@ -54,10 +54,12 @@ struct WrapImpl { } Rect rect_base(point_lo, point_hi); - Pitches pitches_base; + Pitches pitches_base{}; size_t volume_base = pitches_base.flatten(rect_base); #ifdef DEBUG_CUNUMERIC assert(volume_base != 0); +#else + static_cast(volume_base); #endif if (args.has_input) { diff --git a/src/cunumeric/matrix/trilu_template.inl b/src/cunumeric/matrix/trilu_template.inl index c1df6fa423..45fc687166 100644 --- a/src/cunumeric/matrix/trilu_template.inl +++ b/src/cunumeric/matrix/trilu_template.inl @@ -43,7 +43,7 @@ struct TriluImpl { auto in = args.input.read_accessor(shape); if (out.accessor.is_dense_col_major(shape)) { - Pitches pitches; + Pitches pitches{}; size_t volume = pitches.flatten(shape); if (args.lower) { @@ -52,7 +52,7 @@ struct TriluImpl { TriluImplBody()(out, in, pitches, shape.lo, volume, args.k); } } else { - Pitches pitches; + Pitches pitches{}; size_t volume = pitches.flatten(shape); if (args.lower) { diff --git a/src/cunumeric/random/bitgenerator_template.inl b/src/cunumeric/random/bitgenerator_template.inl index c36349282f..99e143a4bf 100644 --- a/src/cunumeric/random/bitgenerator_template.inl +++ b/src/cunumeric/random/bitgenerator_template.inl @@ -59,7 +59,7 @@ static void bitgenerator_template(TaskContext& context) auto seed = scalars[3].value(); auto flags = scalars[4].value(); - BitGeneratorDistribution distribution; + BitGeneratorDistribution distribution{}; std::vector intparams; std::vector floatparams; std::vector doubleparams; From 83e78d9f38aa522681caa872bda2adb61738a479 Mon Sep 17 00:00:00 2001 From: Parag Kulkarni Date: Thu, 21 Dec 2023 09:07:17 +0530 Subject: [PATCH 129/462] Rename Workflow name for artifact download (#85) * Rename Workflow name for artifact download * Prepend platform for search --- .github/actions/download-artifacts/action.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/actions/download-artifacts/action.yml b/.github/actions/download-artifacts/action.yml index 6f97564ff2..a0323918e3 100644 --- a/.github/actions/download-artifacts/action.yml +++ b/.github/actions/download-artifacts/action.yml @@ -31,7 +31,7 @@ runs: search_artifacts: true commit: ${{ inputs.git_sha }} workflow_conclusion: success - workflow: "ci-gh-${{ inputs.device }}-build-and-test.yml" + workflow: "ci-gh.yml" name: "legate.core.internal-${{ inputs.device }}-release-gcc-[0-9a-z]{40}" name_is_regexp: true @@ -40,9 +40,9 @@ runs: shell: bash --noprofile --norc -xeo pipefail {0} run: | mkdir -p .artifacts; - find .artifacts-dl/legate.core.internal-${{ inputs.device }}-release-gcc-*/ \ + find .artifacts-dl/linux-legate.core.internal-${{ inputs.device }}-release-gcc-*/ \ -maxdepth 2 -type d -name legate_core -exec mv {} .artifacts/ \; - find .artifacts-dl/legate.core.internal-${{ inputs.device }}-release-gcc-*/ \ + find .artifacts-dl/linux-legate.core.internal-${{ inputs.device }}-release-gcc-*/ \ -maxdepth 2 -type f -name "environment*.yaml" -exec mv {} .artifacts/ \; - name: Copy and change cache dir ownership From b938761728f85e564caa3d01313554168256fd48 Mon Sep 17 00:00:00 2001 From: Wonchan Lee Date: Thu, 21 Dec 2023 12:29:02 -0800 Subject: [PATCH 130/462] Catch up changes in the create_store_from_buffer API (#84) --- cmake/versions.json | 2 +- cunumeric/deferred.py | 6 ------ cunumeric/runtime.py | 3 +-- 3 files changed, 2 insertions(+), 9 deletions(-) diff --git a/cmake/versions.json b/cmake/versions.json index 406bf1af65..c7386f7093 100644 --- a/cmake/versions.json +++ b/cmake/versions.json @@ -12,7 +12,7 @@ "git_url" : "https://github.com/nv-legate/legate.core.internal.git", "git_shallow": false, "always_download": false, - "git_tag" : "004de5db6f320d46fdcce984a841364e99b40824" + "git_tag" : "e8cd1c4fed1902f51f96c93755661c62d2a062ba" } } } diff --git a/cunumeric/deferred.py b/cunumeric/deferred.py index 8e46b08593..a5c2d736eb 100644 --- a/cunumeric/deferred.py +++ b/cunumeric/deferred.py @@ -238,7 +238,6 @@ def __init__( runtime: Runtime, base: LogicalStore, numpy_array: Optional[npt.NDArray[Any]] = None, - needs_detach: bool = False, ) -> None: super().__init__(runtime, base.type.to_numpy_dtype()) assert base is not None @@ -247,11 +246,6 @@ def __init__( self.numpy_array = ( None if numpy_array is None else weakref.ref(numpy_array) ) - self.needs_detach = needs_detach - - def __del__(self) -> None: - if self.needs_detach: - self.base.detach() def __str__(self) -> str: return f"DeferredArray(base: {self.base})" diff --git a/cunumeric/runtime.py b/cunumeric/runtime.py index 00cd859085..3cfa16ce67 100644 --- a/cunumeric/runtime.py +++ b/cunumeric/runtime.py @@ -381,13 +381,12 @@ def find_or_create_array_thunk( dtype, array.shape, array, - share, + not share, # read_only ) return DeferredArray( self, store, numpy_array=array if share else None, - needs_detach=share, ) # Make this into an eager evaluated thunk From 6960e459f359fa004a015edff283b361f97c7981 Mon Sep 17 00:00:00 2001 From: Marcin Zalewski Date: Tue, 26 Dec 2023 20:18:17 -0800 Subject: [PATCH 131/462] Run in uncontained docker container (#86) * add option * Install hwloc * update runner --- .github/workflows/gh-build-and-test.yml | 2 +- .github/workflows/gh-test.yml | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/gh-build-and-test.yml b/.github/workflows/gh-build-and-test.yml index d9c2ae0c12..bb9037d05c 100644 --- a/.github/workflows/gh-build-and-test.yml +++ b/.github/workflows/gh-build-and-test.yml @@ -20,7 +20,7 @@ jobs: device: ${{ inputs.device }} repos-name: ${{ inputs.repos-name }} image: ${{ inputs.image }} - runs-on: ${{ github.repository_owner == 'nv-legate' && 'linux-amd64-32cpu' || 'ubuntu-latest' }} + runs-on: ${{ github.repository_owner == 'nv-legate' && 'linux-amd64-cpu32' || 'ubuntu-latest' }} secrets: inherit test: diff --git a/.github/workflows/gh-test.yml b/.github/workflows/gh-test.yml index 476e3d16e7..c3cf25118a 100644 --- a/.github/workflows/gh-test.yml +++ b/.github/workflows/gh-test.yml @@ -39,7 +39,7 @@ jobs: runs-on: ${{ inputs.runs-on }} container: - options: -u root + options: -u root --security-opt seccomp=unconfined image: "${{ inputs.image }}" env: # CUDA_VERSION: "${{ inputs.CUDA }}" @@ -59,7 +59,7 @@ jobs: run: | export DEBIAN_FRONTEND=noninteractive && \ sudo apt-get update && \ - sudo apt-get install -y numactl + sudo apt-get install -y numactl libhwloc15 - name: Checkout ${{ inputs.repos-name }} uses: actions/checkout@v3 From d146f20d29ceaf0a1d69a4519ef09dd0b31ed3ac Mon Sep 17 00:00:00 2001 From: Parag Kulkarni Date: Wed, 27 Dec 2023 11:31:34 +0530 Subject: [PATCH 132/462] Use new (RO) PAT (#78) * Add new PAt with relevant names. * Add new RW PAT to merge * _RO * _RW * Cleanup * Check with RW * Change workflow name * Update legate version * Check with RW * RO w/ TOT change * Revert version change --- .github/actions/download-artifacts/action.yml | 4 ++-- .github/workflows/gh-build.yml | 2 +- .github/workflows/merge-ci.yml | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/actions/download-artifacts/action.yml b/.github/actions/download-artifacts/action.yml index a0323918e3..a1e614ea06 100644 --- a/.github/actions/download-artifacts/action.yml +++ b/.github/actions/download-artifacts/action.yml @@ -6,7 +6,7 @@ inputs: device: {type: string, required: true} git_sha: {type: string, required: true} repos-name: {type: string, required: true} - workflow_token: {required: true} + inter_repos_ro_access_token: {required: true} runs: @@ -24,7 +24,7 @@ runs: name: Download conda artifacts uses: dawidd6/action-download-artifact@v2 with: - github_token: ${{ inputs.workflow_token }} + github_token: ${{ inputs.inter_repos_ro_access_token }} path: .artifacts-dl repo: nv-legate/legate.core.internal check_artifacts: true diff --git a/.github/workflows/gh-build.yml b/.github/workflows/gh-build.yml index 572646438f..94f576bd85 100644 --- a/.github/workflows/gh-build.yml +++ b/.github/workflows/gh-build.yml @@ -81,7 +81,7 @@ jobs: device: "${{ inputs.device }}" git_sha: "${{ steps.legate_core_info.outputs.git_tag }}" repos-name: "${{ inputs.repos-name }}" - workflow_token: ${{ secrets.workflow_token }} + inter_repos_ro_access_token: ${{ secrets.NV_LEGATE_INTER_REPOS_ACCESS_RO }} - if: github.repository_owner == 'nv-legate' name: Get AWS credentials for sccache bucket diff --git a/.github/workflows/merge-ci.yml b/.github/workflows/merge-ci.yml index 98604d6079..b193c15074 100644 --- a/.github/workflows/merge-ci.yml +++ b/.github/workflows/merge-ci.yml @@ -13,7 +13,7 @@ jobs: - name: Cunumeric Internal repository uses: actions/checkout@v3 with: - token: ${{ secrets.WORKFLOW_TOKEN }} + token: ${{ secrets.NV_LEGATE_INTER_REPOS_ACCESS }} - name: Merge the branches run: | From 936e2bc20ab274158f46671dd06e67367c6b6858 Mon Sep 17 00:00:00 2001 From: Mona Han <129724851+mona-nv@users.noreply.github.com> Date: Thu, 28 Dec 2023 13:07:56 +0800 Subject: [PATCH 133/462] Port cunumeric.all and add tests (#70) * Port cunumeric.all and add tests * Update code based on review comments --- src/cunumeric/ndarray.cc | 264 ++++++++++++++- src/cunumeric/ndarray.h | 25 ++ src/cunumeric/operators.cc | 9 + src/cunumeric/operators.h | 6 + tests/cpp/integration/test_logical.cc | 451 ++++++++++++++++++++++++++ tests/cpp/integration/util.inl | 11 +- 6 files changed, 749 insertions(+), 17 deletions(-) create mode 100644 tests/cpp/integration/test_logical.cc diff --git a/src/cunumeric/ndarray.cc b/src/cunumeric/ndarray.cc index 135b8e537d..b8d450c017 100644 --- a/src/cunumeric/ndarray.cc +++ b/src/cunumeric/ndarray.cc @@ -477,8 +477,9 @@ void NDArray::arange(Scalar start, Scalar stop, Scalar step) auto runtime = CuNumericRuntime::get_runtime(); - if (start.type() != type() || stop.type() != type() || step.type() != type()) + if (start.type() != type() || stop.type() != type() || step.type() != type()) { throw std::invalid_argument("start/stop/step should have the same type as the array"); + } assert(dim() == 1); @@ -577,18 +578,7 @@ NDArray NDArray::as_type(const legate::Type& type) return out; } - assert(store_.type() != out.store_.type()); - - auto task = runtime->create_task(CuNumericOpCode::CUNUMERIC_CONVERT); - - auto p_lhs = task.add_output(out.store_); - auto p_rhs = task.add_input(store_); - task.add_scalar_arg(legate::Scalar((int32_t)ConvertCode::NOOP)); - - task.add_constraint(align(p_lhs, p_rhs)); - - runtime->submit(std::move(task)); - + out.convert(*this); return out; } @@ -658,6 +648,254 @@ NDArray NDArray::transpose(std::vector axes) return NDArray(store_.transpose(std::move(axes))); } +NDArray NDArray::all(std::optional> axis, + std::optional out, + std::optional keepdims, + std::optional initial, + std::optional where) +{ + return _perform_unary_reduction(static_cast(UnaryRedCode::ALL), + *this, + axis, + std::nullopt, + legate::bool_(), + out, + keepdims, + std::nullopt, + initial, + where); +} + +NDArray NDArray::_perform_unary_reduction(int32_t op, + NDArray src, + std::optional> axis, + std::optional dtype, + std::optional res_dtype, + std::optional out, + std::optional keepdims, + std::optional> args, + std::optional initial, + std::optional where) +{ + if (res_dtype.has_value()) { + assert(!dtype.has_value()); + dtype = src.type(); + } else { + if (dtype.has_value()) { + res_dtype = dtype; + } else if (out.has_value()) { + dtype = out.value().type(); + res_dtype = out.value().type(); + } else { + dtype = src.type(); + res_dtype = src.type(); + } + } + + if (src.type() == legate::complex64() || src.type() == legate::complex128()) { + auto ops = {UnaryRedCode::ARGMAX, UnaryRedCode::ARGMIN, UnaryRedCode::MAX, UnaryRedCode::MIN}; + if (std::find(ops.begin(), ops.end(), static_cast(op)) != ops.end()) { + throw std::runtime_error("(arg)max/min not supported for complex-type arrays"); + } + } + + if (where.has_value()) { + if (where.value().type() != legate::bool_()) { + throw std::invalid_argument("where array should be bool"); + } + } + + std::vector axes; + if (!axis.has_value()) { + for (auto i = 0; i < src.dim(); ++i) { + axes.push_back(i); + } + } else { + axes = normalize_axis_vector(axis.value(), src.dim()); + } + + std::vector out_shape; + for (auto i = 0; i < src.dim(); ++i) { + if (std::find(axes.begin(), axes.end(), i) == axes.end()) { + out_shape.push_back(src.shape()[i]); + } else if (keepdims.value_or(false)) { + out_shape.push_back(1); + } + } + + auto runtime = CuNumericRuntime::get_runtime(); + if (!out.has_value()) { + out = runtime->create_array(out_shape, res_dtype.value()); + } else if (out.value().shape() != out_shape) { + std::string err_msg = "the output shapes do not match: expected" + + std::string(out_shape.begin(), out_shape.end()) + "but got " + + std::string(out.value().shape().begin(), out.value().shape().end()); + throw std::invalid_argument(std::move(err_msg)); + } + + if (dtype.value() != src.type()) { + src = src.as_type(dtype.value()); + } + + NDArray result(out.value()); + if (out.value().type() != res_dtype.value()) { + result = runtime->create_array(out_shape, res_dtype.value()); + } + + std::optional where_array = std::nullopt; + if (where.has_value()) { + where_array = broadcast_where(where.value(), src); + } + + std::vector ops = { + UnaryRedCode::ARGMAX, UnaryRedCode::ARGMIN, UnaryRedCode::NANARGMAX, UnaryRedCode::NANARGMIN}; + auto argred = std::find(ops.begin(), ops.end(), static_cast(op)) != ops.end(); + if (argred) { + assert(!initial.has_value()); + auto argred_dtype = runtime->get_argred_type(src.type()); + result = runtime->create_array(result.shape(), argred_dtype); + } + + result.unary_reduction(op, src, where_array, axis, axes, keepdims, args, initial); + + if (argred) { + unary_op(static_cast(UnaryOpCode::GETARG), result); + } + + if (out.value().type() != result.type()) { + out.value().convert(result); + } + return out.value(); +} + +void NDArray::unary_reduction(int32_t op, + NDArray src, + std::optional where, + std::optional> orig_axis, + std::optional> axes, + std::optional keepdims, + std::optional> args, + std::optional initial) +{ + auto lhs_array = *this; + auto rhs_array = src; + assert(lhs_array.dim() <= rhs_array.dim()); + + auto runtime = CuNumericRuntime::get_runtime(); + auto op_code = static_cast(op); + + if (initial.has_value()) { + lhs_array.fill(initial.value(), false); + } else { + auto identity = runtime->get_reduction_identity(op_code, lhs_array.type()); + lhs_array.fill(identity, false); + } + + auto is_where = where.has_value(); + bool is_keepdims = keepdims.value_or(false); + if (lhs_array.size() == 1) { + assert(!axes.has_value() || + lhs_array.dim() == + (rhs_array.dim() - (is_keepdims ? 0 : static_cast(axes.value().size())))); + + auto p_lhs = lhs_array.store_; + while (p_lhs.dim() > 1) { + p_lhs = p_lhs.project(0, 0); + } + + auto task = runtime->create_task(CuNumericOpCode::CUNUMERIC_SCALAR_UNARY_RED); + + auto redop = runtime->get_reduction_op(op_code); + task.add_reduction(p_lhs, redop); + auto p_rhs = task.add_input(rhs_array.store_); + task.add_scalar_arg(legate::Scalar(op)); + task.add_scalar_arg(legate::Scalar(rhs_array.shape())); + task.add_scalar_arg(legate::Scalar(is_where)); + if (is_where) { + auto p_where = task.add_input(where.value().store_); + task.add_constraint(align(p_rhs, p_where)); + } + if (args.has_value()) { + auto arg_array = args.value(); + for (auto& arg : arg_array) { + task.add_input(arg.store_); + } + } + + runtime->submit(std::move(task)); + } else { + assert(axes.has_value()); + auto result = lhs_array.store_; + if (is_keepdims) { + for (auto axis : axes.value()) { + result = result.project(axis, 0); + } + } + auto rhs_shape = rhs_array.shape(); + for (auto axis : axes.value()) { + result = result.promote(axis, rhs_shape[axis]); + } + + if (axes.value().size() > 1) { + throw std::runtime_error("Need support for reducing multiple dimensions"); + } + + auto task = runtime->create_task(CuNumericOpCode::CUNUMERIC_UNARY_RED); + + auto redop = runtime->get_reduction_op(op_code); + auto p_lhs = task.add_reduction(result, redop); + auto p_rhs = task.add_input(rhs_array.store_); + task.add_scalar_arg(legate::Scalar(axes.value()[0])); + task.add_scalar_arg(legate::Scalar(op)); + task.add_scalar_arg(legate::Scalar(is_where)); + if (is_where) { + auto p_where = task.add_input(where.value().store_); + task.add_constraint(align(p_rhs, p_where)); + } + if (args != std::nullopt) { + auto arg_array = args.value(); + for (auto& arg : arg_array) { + task.add_input(arg.store_); + } + } + task.add_constraint(align(p_lhs, p_rhs)); + + runtime->submit(std::move(task)); + } +} + +NDArray NDArray::broadcast_where(NDArray where, NDArray source) +{ + if (where.shape() == source.shape()) { + return where; + } + + auto where_shape = broadcast_shapes({where, source}); + auto where_store = broadcast(where_shape, where.store_); + + auto runtime = CuNumericRuntime::get_runtime(); + return runtime->create_array(std::move(where_store)); +} + +void NDArray::convert(NDArray rhs, int32_t nan_op) +{ + NDArray lhs_array(*this); + NDArray rhs_array(rhs); + assert(lhs_array.type() != rhs_array.type()); + + auto lhs_s = lhs_array.store_; + auto rhs_s = rhs_array.store_; + + auto runtime = CuNumericRuntime::get_runtime(); + auto task = runtime->create_task(CuNumericOpCode::CUNUMERIC_CONVERT); + auto p_lhs = task.add_output(lhs_s); + auto p_rhs = task.add_input(rhs_s); + task.add_scalar_arg(legate::Scalar(nan_op)); + task.add_constraint(legate::align(p_lhs, p_rhs)); + + runtime->submit(std::move(task)); +} + legate::LogicalStore NDArray::get_store() { return store_; } legate::LogicalStore NDArray::broadcast(const std::vector& shape, diff --git a/src/cunumeric/ndarray.h b/src/cunumeric/ndarray.h index b32ada25f6..a722412d56 100644 --- a/src/cunumeric/ndarray.h +++ b/src/cunumeric/ndarray.h @@ -90,6 +90,11 @@ class NDArray { std::string kind = "quicksort"); NDArray transpose(); NDArray transpose(std::vector axes); + NDArray all(std::optional> axis = std::nullopt, + std::optional out = std::nullopt, + std::optional keepdims = std::nullopt, + std::optional initial = std::nullopt, + std::optional where = std::nullopt); public: NDArray as_type(const legate::Type& type); @@ -101,6 +106,26 @@ class NDArray { legate::LogicalStore broadcast(NDArray rhs1, NDArray rhs2); void sort_task(NDArray rhs, bool argsort, bool stable); void sort_swapped(NDArray rhs, bool argsort, int32_t sort_axis, bool stable); + void convert(NDArray rhs, int32_t nan_op = 1); + void unary_reduction(int32_t op, + NDArray src, + std::optional where, + std::optional> orig_axis, + std::optional> axes, + std::optional keepdims, + std::optional> args, + std::optional initial); + NDArray broadcast_where(NDArray where, NDArray source); + NDArray _perform_unary_reduction(int32_t op, + NDArray src, + std::optional> axis = std::nullopt, + std::optional dtype = std::nullopt, + std::optional res_dtype = std::nullopt, + std::optional out = std::nullopt, + std::optional keepdims = std::nullopt, + std::optional> args = std::nullopt, + std::optional initial = std::nullopt, + std::optional where = std::nullopt); public: static legate::Library get_library(); diff --git a/src/cunumeric/operators.cc b/src/cunumeric/operators.cc index b2506f8789..070a44a045 100644 --- a/src/cunumeric/operators.cc +++ b/src/cunumeric/operators.cc @@ -271,6 +271,15 @@ NDArray dot(NDArray rhs1, NDArray rhs2) return out; } +NDArray all(NDArray input, + std::optional> axis, + std::optional out, + std::optional keepdims, + std::optional where) +{ + return input.all(axis, out, keepdims, std::nullopt, where); +} + NDArray sum(NDArray input) { return unary_reduction(UnaryRedCode::SUM, std::move(input)); } NDArray amax(NDArray input) { return unary_reduction(UnaryRedCode::MAX, std::move(input)); } diff --git a/src/cunumeric/operators.h b/src/cunumeric/operators.h index 641b421914..af519b11b8 100644 --- a/src/cunumeric/operators.h +++ b/src/cunumeric/operators.h @@ -45,6 +45,12 @@ NDArray zeros(std::vector shape, std::optional type = std: NDArray full(std::vector shape, const Scalar& value); +NDArray all(NDArray input, + std::optional> axis = std::nullopt, + std::optional out = std::nullopt, + std::optional keepdims = std::nullopt, + std::optional where = std::nullopt); + NDArray sum(NDArray input); NDArray amax(NDArray input); diff --git a/tests/cpp/integration/test_logical.cc b/tests/cpp/integration/test_logical.cc new file mode 100644 index 0000000000..2dd6975795 --- /dev/null +++ b/tests/cpp/integration/test_logical.cc @@ -0,0 +1,451 @@ +/* Copyright 2023 NVIDIA Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +#include +#include +#include + +#include +#include "legate.h" +#include "cunumeric.h" +#include "util.inl" + +template +void test_all(std::array& in_array, + std::array& expect_result, + legate::Type leg_type, + std::vector shape, + std::optional> axis = std::nullopt, + std::optional out = std::nullopt, + std::optional keepdims = std::nullopt, + std::optional where = std::nullopt) +{ + auto A1 = cunumeric::zeros(shape, leg_type); + if (in_array.size() != 0) { + if (in_array.size() == 1) { + A1.fill(legate::Scalar(in_array[0]), false); + } else { + assign_values_to_array(A1, in_array.data(), in_array.size()); + } + } + + if (!out.has_value()) { + auto B1 = cunumeric::all(A1, axis, std::nullopt, keepdims, where); + check_array_eq(B1, expect_result.data(), expect_result.size()); + } else { + cunumeric::all(A1, axis, out, keepdims, where); + check_array_eq(out.value(), expect_result.data(), expect_result.size()); + } +} + +template +void test_all_each_axis(std::array& in_array, + std::map>& expect_result, + legate::Type leg_type, + std::vector shape, + bool keepdims = false) +{ + int32_t dim = shape.size(); + for (int32_t axis = -dim + 1; axis < dim; ++axis) { + auto index = axis < 0 ? axis + dim : axis; + auto expect_val = expect_result[index]; + auto axes = {axis}; + if (keepdims) { + if (dim == 1) { + test_all( + in_array, expect_val, leg_type, shape, axes, std::nullopt, keepdims); + } else if (dim == 2) { + test_all( + in_array, expect_val, leg_type, shape, axes, std::nullopt, keepdims); + } else if (dim == 3) { + test_all( + in_array, expect_val, leg_type, shape, axes, std::nullopt, keepdims); + } else if (dim == 4) { +#if LEGATE_MAX_DIM >= 4 + test_all( + in_array, expect_val, leg_type, shape, axes, std::nullopt, keepdims); +#endif + } else if (dim == 5) { +#if LEGATE_MAX_DIM >= 5 + test_all( + in_array, expect_val, leg_type, shape, axes, std::nullopt, keepdims); +#endif + } else if (dim == 6) { +#if LEGATE_MAX_DIM >= 6 + test_all( + in_array, expect_val, leg_type, shape, axes, std::nullopt, keepdims); +#endif + } else if (dim == 7) { +#if LEGATE_MAX_DIM >= 7 + test_all( + in_array, expect_val, leg_type, shape, axes, std::nullopt, keepdims); +#endif + } + } else { + if (dim == 1) { + test_all( + in_array, expect_val, leg_type, shape, axes, std::nullopt, keepdims); + } else if (dim == 2) { + test_all( + in_array, expect_val, leg_type, shape, axes, std::nullopt, keepdims); + } else if (dim == 3) { + test_all( + in_array, expect_val, leg_type, shape, axes, std::nullopt, keepdims); + } else if (dim == 4) { +#if LEGATE_MAX_DIM >= 4 + test_all( + in_array, expect_val, leg_type, shape, axes, std::nullopt, keepdims); +#endif + } else if (dim == 5) { +#if LEGATE_MAX_DIM >= 5 + test_all( + in_array, expect_val, leg_type, shape, axes, std::nullopt, keepdims); +#endif + } else if (dim == 6) { +#if LEGATE_MAX_DIM >= 6 + test_all( + in_array, expect_val, leg_type, shape, axes, std::nullopt, keepdims); +#endif + } else if (dim == 7) { +#if LEGATE_MAX_DIM >= 7 + test_all( + in_array, expect_val, leg_type, shape, axes, std::nullopt, keepdims); +#endif + } + } + } +} + +void test_all_basic() +{ + // Test int type + std::array in_array1 = {-1, 4, 5}; + std::vector shape1 = {3}; + std::map> expect_result1 = {{0, {true}}}; + test_all_each_axis(in_array1, expect_result1, legate::int32(), shape1); + test_all_each_axis(in_array1, expect_result1, legate::int32(), shape1, true); + + std::array in_array2 = {5, 10, 0, 100}; + std::map> expect_result2 = {{0, {false}}}; + std::vector shape2 = {4}; + test_all_each_axis(in_array2, expect_result2, legate::int32(), shape2); + test_all_each_axis(in_array2, expect_result2, legate::int32(), shape2, true); + + std::array in_array3 = {0, 0, 0, 0}; + std::map> expect_result3 = {{0, {false, false}}, + {1, {false, false}}}; + std::vector shape3 = {2, 2}; + test_all_each_axis(in_array3, expect_result3, legate::int32(), shape3); + test_all_each_axis(in_array3, expect_result3, legate::int32(), shape3, true); + + std::array in_array4 = {0, 1, 2, 3, 4, 0, 6, 7}; + std::map> expect_result4 = {{0, {false, false, true, true}}, + {1, {false, true, true, false}}, + {2, {false, true, false, true}}}; + std::vector shape4 = {2, 2, 2}; + test_all_each_axis(in_array4, expect_result4, legate::int32(), shape4); + test_all_each_axis(in_array4, expect_result4, legate::int32(), shape4, true); + + // Test bool type + std::array in_array5 = {true, true, false, true, true, true, true, true, false}; + std::map> expect_result5 = {{0, {true, true, false}}, + {1, {false, true, false}}}; + std::vector shape5 = {3, 3}; + test_all_each_axis(in_array5, expect_result5, legate::bool_(), shape5); + test_all_each_axis(in_array5, expect_result5, legate::bool_(), shape5, true); + + // Test float type + std::array in_array6 = {0.0, 1.0, 0.0, 5.0, 2.0, 1.0, 1.0, 2.0, 3.0}; + std::map> expect_result6 = {{0, {false, true, false}}, + {1, {false, true, true}}}; + std::vector shape6 = {3, 3}; + test_all_each_axis(in_array6, expect_result6, legate::float64(), shape6); + test_all_each_axis(in_array6, expect_result6, legate::float64(), shape6, true); + + // Test complex type + std::array, 4> in_array7 = { + complex(0, 1), complex(1, 1), complex(1, 0), complex(0, 0)}; + std::map> expect_result7 = {{0, {true, false}}, {1, {true, false}}}; + std::vector shape7 = {2, 2}; + test_all_each_axis, 4, 2>(in_array7, expect_result7, legate::complex64(), shape7); + test_all_each_axis, 4, 2>( + in_array7, expect_result7, legate::complex64(), shape7, true); + + std::array, 1> in_array8 = {complex(0, 1)}; + std::map> expect_result8 = {{0, {true}}}; + std::vector shape8 = {1}; + test_all_each_axis, 1, 1>( + in_array8, expect_result8, legate::complex128(), shape8); + test_all_each_axis, 1, 1>( + in_array8, expect_result8, legate::complex128(), shape8, true); +} + +void test_all_axis_input() +{ + std::array in_array = {5, 10, 0, 100}; + std::vector shape = {1, 2, 2}; + + std::vector axis1 = {0}; + std::array expect_val1 = {true, true, false, true}; + test_all(in_array, expect_val1, legate::int32(), shape, axis1); + + std::vector axis2 = {1, 2}; + std::array expect_val2 = {false}; + test_all(in_array, expect_val2, legate::int32(), shape, axis2); + + std::vector axis3 = {-1, 0, 1}; + std::array expect_val3 = {false}; + test_all(in_array, expect_val3, legate::int32(), shape, axis3); +} + +void test_all_where_input() +{ + std::array in_array = {true, false, true, true}; + std::vector shape = {2, 2}; + + // Test where with multiple bool values + std::array where_in1 = {true, false}; + auto where_array1 = cunumeric::zeros({2}, legate::bool_()); + assign_values_to_array(where_array1, where_in1.data(), where_in1.size()); + + std::array expect_val1 = {true}; + test_all( + in_array, expect_val1, legate::bool_(), shape, std::nullopt, std::nullopt, false, where_array1); + + // Test where with single bool value + std::array where_in2 = {true}; + auto where_array2 = cunumeric::zeros({1}, legate::bool_()); + assign_values_to_array(where_array2, where_in2.data(), where_in2.size()); + + std::array expect_val2 = {false}; + test_all( + in_array, expect_val2, legate::bool_(), shape, std::nullopt, std::nullopt, false, where_array2); + + std::array where_in3 = {false}; + auto where_array3 = cunumeric::zeros({1}, legate::bool_()); + assign_values_to_array(where_array3, where_in3.data(), where_in3.size()); + + std::array expect_val3 = {true}; + test_all( + in_array, expect_val3, legate::bool_(), shape, std::nullopt, std::nullopt, false, where_array3); +} + +void test_all_out_input() +{ + std::array in_array = {0, 1, 2, 3, 4, 5, 6, 7}; + std::vector shape = {2, 2, 2}; + std::vector out_shape = {2, 2}; + std::vector axis = {0}; + + auto out1 = cunumeric::zeros(out_shape, legate::int32()); + std::array expect_val1 = {0, 1, 1, 1}; + test_all(in_array, expect_val1, legate::int32(), shape, axis, out1); + + auto out2 = cunumeric::zeros(out_shape, legate::float64()); + std::array expect_val2 = {0.0, 1.0, 1.0, 1.0}; + test_all(in_array, expect_val2, legate::int32(), shape, axis, out2); + + auto out3 = cunumeric::zeros(out_shape, legate::complex64()); + std::array, 4> expect_val3 = { + complex(0, 0), complex(1, 0), complex(1, 0), complex(1, 0)}; + test_all, 8, 4, 3, 2>( + in_array, expect_val3, legate::int32(), shape, axis, out3); + + auto out4 = cunumeric::zeros(out_shape, legate::bool_()); + std::array expect_val4 = {false, true, true, true}; + test_all(in_array, expect_val4, legate::int32(), shape, axis, out4); +} + +template +void test_all_max_dim(int32_t dim) +{ + std::array in_array; + for (int32_t i = 0; i < IN_SIZE; i++) { + in_array[i] = i; + } + + int32_t count = IN_SIZE / OUT_SIZE; + std::vector shapes; + for (int32_t i = 0; i < dim; i++) { + shapes.push_back(count); + } + + std::array expect_val; + expect_val[0] = false; + for (int32_t i = 1; i < OUT_SIZE; i++) { + expect_val[i] = true; + } + + std::map> expect_result; + for (int32_t i = 0; i < dim; i++) { + expect_result[i] = expect_val; + } + + test_all_each_axis(in_array, expect_result, legate::int32(), shapes); + test_all_each_axis( + in_array, expect_result, legate::int32(), shapes, true); +} + +void test_all_max_dim() +{ +#if LEGATE_MAX_DIM >= 4 + const int32_t count_4d = 81; + const int32_t count_expect_4d = 27; + const int32_t dim_4d = 4; + test_all_max_dim(dim_4d); +#endif + +#if LEGATE_MAX_DIM >= 5 + const int32_t count_5d = 243; + const int32_t count_expect_5d = 81; + const int32_t dim_5d = 5; + test_all_max_dim(dim_5d); +#endif + +#if LEGATE_MAX_DIM >= 6 + const int32_t count_6d = 729; + const int32_t count_expect_6d = 243; + const int32_t dim_6d = 6; + test_all_max_dim(dim_6d); +#endif + +#if LEGATE_MAX_DIM >= 7 + const int32_t count_7d = 2187; + const int32_t count_expect_7d = 729; + const int32_t dim_7d = 7; + test_all_max_dim(dim_7d); +#endif +} + +void test_all_empty_array() +{ + std::array in_array = {}; + std::vector shape = {0}; + std::array expect_val = {true}; + + test_all(in_array, expect_val, legate::int32(), shape); +} + +void test_all_large_array() +{ + const int32_t count = 100000; + std::vector shape = {count}; + std::array expect_val = {true}; + + // Test int type for large array + std::array in_array1; + for (int32_t i = 0; i < count; i++) { + in_array1[i] = i + 1; + } + test_all(in_array1, expect_val, legate::int32(), shape); + + // Test float type + std::array in_array2; + for (int32_t i = 0; i < count; i++) { + in_array2[i] = i + 1.1; + } + test_all(in_array2, expect_val, legate::float64(), shape); + + // Test complex type + std::array, count> in_array3; + for (int32_t i = 0; i < count; i++) { + in_array3[i] = complex(i + 1, i + 1); + } + test_all, bool, count, 1, 1, 1>(in_array3, expect_val, legate::complex64(), shape); +} + +void test_all_invalid_axis() +{ + std::array in_array = {5, 10, 0, 100}; + std::vector shape = {1, 2, 2}; + auto array = cunumeric::zeros(shape, legate::int32()); + assign_values_to_array(array, in_array.data(), in_array.size()); + + // Test out-of-bound + std::vector axis1 = {-4, 3}; + EXPECT_THROW(cunumeric::all(array, axis1), std::invalid_argument); + + std::vector axis2 = {0, 3}; + EXPECT_THROW(cunumeric::all(array, axis2), std::invalid_argument); + + // Test repeated axes + std::vector axis3 = {1, 1}; + EXPECT_THROW(cunumeric::all(array, axis3), std::invalid_argument); + + std::vector axis4 = {-1, 2}; + EXPECT_THROW(cunumeric::all(array, axis4), std::invalid_argument); +} + +void test_all_invalid_shape() +{ + std::array in_array = {5, 10, 0, 100}; + std::vector shape = {1, 2, 2}; + auto array = cunumeric::zeros(shape, legate::int32()); + assign_values_to_array(array, in_array.data(), in_array.size()); + + std::vector out_shape1 = {1}; + auto out1 = cunumeric::zeros(out_shape1, legate::int32()); + EXPECT_THROW(cunumeric::all(array, std::nullopt, out1), std::invalid_argument); + + std::vector out_shape2 = {2}; + std::vector axis2 = {1}; + auto out2 = cunumeric::zeros(out_shape2, legate::int32()); + EXPECT_THROW(cunumeric::all(array, axis2, out2), std::invalid_argument); + + std::vector out_shape3 = {2, 2}; + std::vector axis3 = {1}; + auto out3 = cunumeric::zeros(out_shape3, legate::int32()); + EXPECT_THROW(cunumeric::all(array, axis3, out3), std::invalid_argument); +} + +void test_all_invalid_where() +{ + std::array in_array = {5, 10, 0, 100}; + std::vector shape = {1, 2, 2}; + auto array = cunumeric::zeros(shape, legate::int32()); + assign_values_to_array(array, in_array.data(), in_array.size()); + + // Test where with invalid type + std::array in_where1 = {0, 1, 0, 1}; + auto where1 = cunumeric::zeros(shape, legate::int32()); + assign_values_to_array(where1, in_where1.data(), in_where1.size()); + EXPECT_THROW(cunumeric::all(array, std::nullopt, std::nullopt, false, where1), + std::invalid_argument); + + // Test where with invalid shape + std::vector where_shape = {2, 2, 1}; + std::array in_where2 = {false, true, false, true}; + auto where2 = cunumeric::zeros(where_shape, legate::bool_()); + assign_values_to_array(where2, in_where2.data(), in_where2.size()); + EXPECT_THROW(cunumeric::all(array, std::nullopt, std::nullopt, false, where2), std::exception); +} + +// void cpp_test() +TEST(Logical, AllBasicTest) { test_all_basic(); } +TEST(Logical, AllAxisInput) { test_all_axis_input(); } +TEST(Logical, AllOutInput) { test_all_out_input(); } +// TODO - after where is supported +// TEST(Logical, AllWhereInput) { test_all_where_input(); } +TEST(Logical, AllEmptyArray) { test_all_empty_array(); } +TEST(Logical, AllLargeArray) { test_all_large_array(); } +TEST(Logical, AllMaxDim) { test_all_max_dim(); } +TEST(Logical, AllInvalidAxis) { test_all_invalid_axis(); } +TEST(Logical, AllInvalidShape) { test_all_invalid_shape(); } +TEST(Logical, AllInvalidWhere) { test_all_invalid_where(); } diff --git a/tests/cpp/integration/util.inl b/tests/cpp/integration/util.inl index 5a3b040a18..51e35371c5 100644 --- a/tests/cpp/integration/util.inl +++ b/tests/cpp/integration/util.inl @@ -84,16 +84,19 @@ std::string check_array_eq(legate::AccessorRO acc, auto index = 0; auto size = shape.size(); + ss << "size: " << size << "\n"; for (legate::PointInRectIterator itr(rect, false); itr.valid(); ++itr) { auto q = *itr; ss << std::left << std::setprecision(3); ss << std::setw(13) << "Array value: " << std::setw(10) << acc[q] << ", "; ss << std::setw(16) << "Expected value: " << std::setw(10) << values_ptr[index] << ", "; - ss << std::setw(8) << "index: ["; - for (uint32_t i = 0; i < size - 1; ++i) { - ss << q[i] << ","; + if (size > 0) { + ss << std::setw(8) << "index: ["; + for (uint32_t i = 0; i < size - 1; ++i) { + ss << q[i] << ","; + } + ss << q[size - 1] << "]\n"; } - ss << q[size - 1] << "]\n"; EXPECT_EQ(acc[q], values_ptr[index++]); } From 8347128b8f053497642be8b5b6191eb7f6d80d7d Mon Sep 17 00:00:00 2001 From: Mona Han <129724851+mona-nv@users.noreply.github.com> Date: Fri, 29 Dec 2023 10:07:51 +0800 Subject: [PATCH 134/462] Add tests for eye API and fix the typo issue in msort test (#77) * Add tests for eye API and fix the typo issue in msort test * Remove the unnecessary check * Update n and m datatype and check their input value --------- Signed-off-by: monah --- src/cunumeric/operators.cc | 11 +- src/cunumeric/operators.h | 4 +- tests/cpp/integration/test_eye.cc | 296 ++++++++++++++++++++++++++++ tests/cpp/integration/test_msort.cc | 4 +- 4 files changed, 307 insertions(+), 8 deletions(-) create mode 100644 tests/cpp/integration/test_eye.cc diff --git a/src/cunumeric/operators.cc b/src/cunumeric/operators.cc index 070a44a045..1381ed024e 100644 --- a/src/cunumeric/operators.cc +++ b/src/cunumeric/operators.cc @@ -153,14 +153,19 @@ NDArray full(std::vector shape, const Scalar& value) return out; } -NDArray eye(size_t n, std::optional m, int32_t k, const legate::Type& type) +NDArray eye(int32_t n, std::optional m, int32_t k, const legate::Type& type) { - if (static_cast(type.code()) >= static_cast(legate::Type::Code::FIXED_ARRAY)) { + if (n < 0 || (m.has_value() && m.value() < 0)) { + throw std::invalid_argument("eye input n and m should not less then zero"); + } + + if (!type.is_primitive()) { throw std::invalid_argument("Type must be a primitive type"); } auto runtime = CuNumericRuntime::get_runtime(); - auto out = runtime->create_array({n, m.value_or(n)}, type); + auto out = + runtime->create_array({static_cast(n), static_cast(m.value_or(n))}, type); out.eye(k); return out; } diff --git a/src/cunumeric/operators.h b/src/cunumeric/operators.h index af519b11b8..eaffe33a21 100644 --- a/src/cunumeric/operators.h +++ b/src/cunumeric/operators.h @@ -72,8 +72,8 @@ NDArray array_equal(NDArray input0, NDArray input1); std::vector nonzero(NDArray input); -NDArray eye(size_t n, - std::optional m, +NDArray eye(int32_t n, + std::optional m = std::nullopt, int32_t k = 0, const legate::Type& type = legate::float64()); diff --git a/tests/cpp/integration/test_eye.cc b/tests/cpp/integration/test_eye.cc new file mode 100644 index 0000000000..cd06de879b --- /dev/null +++ b/tests/cpp/integration/test_eye.cc @@ -0,0 +1,296 @@ +/* Copyright 2023 NVIDIA Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +#include +#include +#include + +#include +#include "legate.h" +#include "cunumeric.h" +#include "util.inl" + +template +auto get_eye_expect_result_3_2() +{ + std::map> expect_result = {{-30, {0, 0, 0, 0, 0, 0}}, + {-3, {0, 0, 0, 0, 0, 0}}, + {-2, {0, 0, 0, 0, 1, 0}}, + {-1, {0, 0, 1, 0, 0, 1}}, + {0, {1, 0, 0, 1, 0, 0}}, + {1, {0, 1, 0, 0, 0, 0}}, + {2, {0, 0, 0, 0, 0, 0}}, + {3, {0, 0, 0, 0, 0, 0}}, + {30, {0, 0, 0, 0, 0, 0}}}; + return expect_result; +} + +template +auto get_eye_expect_result_3_3() +{ + std::map> expect_result = {{-30, {0, 0, 0, 0, 0, 0, 0, 0, 0}}, + {-3, {0, 0, 0, 0, 0, 0, 0, 0, 0}}, + {-2, {0, 0, 0, 0, 0, 0, 1, 0, 0}}, + {-1, {0, 0, 0, 1, 0, 0, 0, 1, 0}}, + {0, {1, 0, 0, 0, 1, 0, 0, 0, 1}}, + {1, {0, 1, 0, 0, 0, 1, 0, 0, 0}}, + {2, {0, 0, 1, 0, 0, 0, 0, 0, 0}}, + {3, {0, 0, 0, 0, 0, 0, 0, 0, 0}}, + {30, {0, 0, 0, 0, 0, 0, 0, 0, 0}}}; + return expect_result; +} + +template +auto get_eye_expect_result_3_4() +{ + std::map> expect_result = { + {-30, {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}}, + {-3, {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}}, + {-2, {0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0}}, + {-1, {0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0}}, + {0, {1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0}}, + {1, {0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1}}, + {2, {0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0}}, + {3, {0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0}}, + {30, {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}}, + }; + return expect_result; +} + +template +auto test_eye_3_2(std::vector& k_vals, std::optional type = std::nullopt) +{ + auto expect_result = get_eye_expect_result_3_2(); + std::vector expect_shape = {3, 2}; + for (auto k : k_vals) { + if (type.has_value()) { + auto result = cunumeric::eye(3, 2, k, type.value()); + EXPECT_EQ(result.type(), type.value()); + EXPECT_EQ(result.shape(), expect_shape); + auto expect = expect_result[k]; + check_array_eq(result, expect.data(), expect.size()); + } else { + auto result = cunumeric::eye(3, 2, k); + EXPECT_EQ(result.type(), legate::float64()); + EXPECT_EQ(result.shape(), expect_shape); + auto expect = expect_result[k]; + check_array_eq(result, expect.data(), expect.size()); + } + } +} + +template +auto test_eye_3_3(std::vector& k_vals, std::optional type = std::nullopt) +{ + auto expect_result = get_eye_expect_result_3_3(); + std::vector expect_shape = {3, 3}; + for (auto k : k_vals) { + if (type.has_value()) { + auto result = cunumeric::eye(3, 3, k, type.value()); + EXPECT_EQ(result.type(), type.value()); + EXPECT_EQ(result.shape(), expect_shape); + auto expect = expect_result[k]; + check_array_eq(result, expect.data(), expect.size()); + } else { + auto result = cunumeric::eye(3, 3, k); + EXPECT_EQ(result.type(), legate::float64()); + EXPECT_EQ(result.shape(), expect_shape); + auto expect = expect_result[k]; + check_array_eq(result, expect.data(), expect.size()); + } + } +} + +template +auto test_eye_3_4(std::vector& k_vals, std::optional type = std::nullopt) +{ + auto expect_result = get_eye_expect_result_3_4(); + std::vector expect_shape = {3, 4}; + for (auto k : k_vals) { + if (type.has_value()) { + auto result = cunumeric::eye(3, 4, k, type.value()); + EXPECT_EQ(result.type(), type.value()); + EXPECT_EQ(result.shape(), expect_shape); + auto expect = expect_result[k]; + check_array_eq(result, expect.data(), expect.size()); + } else { + auto result = cunumeric::eye(3, 4, k); + EXPECT_EQ(result.type(), legate::float64()); + EXPECT_EQ(result.shape(), expect_shape); + auto expect = expect_result[k]; + check_array_eq(result, expect.data(), expect.size()); + } + } +} + +template +auto test_eye_square_3(std::optional> k_vals = std::nullopt, + std::optional type = std::nullopt) +{ + auto expect_result = get_eye_expect_result_3_3(); + std::vector expect_shape = {3, 3}; + if (k_vals.has_value()) { + for (auto k : k_vals.value()) { + if (type.has_value()) { + auto result = cunumeric::eye(3, std::nullopt, k, type.value()); + EXPECT_EQ(result.type(), type.value()); + EXPECT_EQ(result.shape(), expect_shape); + auto expect = expect_result[k]; + check_array_eq(result, expect.data(), expect.size()); + } else { + auto result = cunumeric::eye(3, std::nullopt, k); + EXPECT_EQ(result.type(), legate::float64()); + auto expect = expect_result[k]; + check_array_eq(result, expect.data(), expect.size()); + } + } + } else { + if (type.has_value()) { + auto result = cunumeric::eye(3, std::nullopt, 0, type.value()); + EXPECT_EQ(result.type(), type.value()); + EXPECT_EQ(result.shape(), expect_shape); + auto expect = expect_result[0]; + check_array_eq(result, expect.data(), expect.size()); + } else { + auto result = cunumeric::eye(3); + EXPECT_EQ(result.type(), legate::float64()); + EXPECT_EQ(result.shape(), expect_shape); + auto expect = expect_result[0]; + check_array_eq(result, expect.data(), expect.size()); + } + } +} + +void eye_basic() +{ + std::vector k_vals = {-30, -3, -2, -1, 0, 1, 2, 3, 30}; + + // Test default data type + test_eye_3_2(k_vals); + test_eye_3_3(k_vals); + test_eye_3_4(k_vals); + + // Test int type + test_eye_3_2(k_vals, legate::int32()); + test_eye_3_3(k_vals, legate::int32()); + test_eye_3_4(k_vals, legate::int32()); + + // Test complex type + test_eye_3_2>(k_vals, legate::complex64()); + test_eye_3_3>(k_vals, legate::complex64()); + test_eye_3_4>(k_vals, legate::complex64()); +} + +void eye_square() +{ + std::vector k_vals = {-30, -3, -2, -1, 0, 1, 2, 3, 30}; + + // Test default parameter + test_eye_square_3(); + + // Test with k input + test_eye_square_3(k_vals); + + // Test with datatype input + test_eye_square_3(std::nullopt, legate::int32()); + + // Test with k and datatype input + test_eye_square_3>(k_vals, legate::complex64()); +} + +void eye_input_zero() +{ + // Test n=0 + auto result1 = cunumeric::eye(0); + std::vector expect_shape1 = {0, 0}; + EXPECT_EQ(result1.type(), legate::float64()); + EXPECT_EQ(result1.size(), 0); + EXPECT_EQ(result1.shape(), expect_shape1); + + // Test m=0 + auto result2 = cunumeric::eye(3, 0); + std::vector expect_shape2 = {3, 0}; + EXPECT_EQ(result2.type(), legate::float64()); + EXPECT_EQ(result2.size(), 0); + EXPECT_EQ(result2.shape(), expect_shape2); +} + +void eye_large_array() +{ + const size_t n_or_m = 1000; + + // Test 1000 * 1000 array + auto result1 = cunumeric::eye(n_or_m); + std::vector expect_shape1 = {n_or_m, n_or_m}; + std::array expect_result1; + expect_result1.fill(0); + for (size_t i = 0; i < n_or_m; i++) { + expect_result1[i * n_or_m + i] = 1; + } + EXPECT_EQ(result1.type(), legate::float64()); + EXPECT_EQ(result1.shape(), expect_shape1); + check_array_eq(result1, expect_result1.data(), expect_result1.size()); + + // Test 3 * 1000 array + const size_t n = 3; + auto result2 = cunumeric::eye(n, n_or_m, 0, legate::int32()); + std::vector expect_shape2 = {n, n_or_m}; + std::array expect_result2; + expect_result2.fill(0); + for (size_t i = 0; i < n; i++) { + expect_result2[i * n_or_m + i] = 1; + } + EXPECT_EQ(result2.type(), legate::int32()); + EXPECT_EQ(result2.shape(), expect_shape2); + check_array_eq(result2, expect_result2.data(), expect_result2.size()); + + // Test 1000 * 3 array + const size_t m = 3; + auto result3 = cunumeric::eye(n_or_m, m, 0, legate::complex64()); + std::vector expect_shape3 = {n_or_m, m}; + std::array, n_or_m * m> expect_result3; + expect_result3.fill(0); + for (size_t i = 0; i < n_or_m; i++) { + if (i < m) { + expect_result3[i * m + i] = 1; + } + } + EXPECT_EQ(result3.type(), legate::complex64()); + EXPECT_EQ(result3.shape(), expect_shape3); + check_array_eq, 2>(result3, expect_result3.data(), expect_result3.size()); +} + +void eye_negative() +{ + // Test bad n + EXPECT_THROW(cunumeric::eye(-1), std::invalid_argument); + EXPECT_THROW(cunumeric::eye(-1, 3), std::invalid_argument); + + // Test bad m + EXPECT_THROW(cunumeric::eye(3, -1), std::invalid_argument); + EXPECT_THROW(cunumeric::eye(-1, -1), std::invalid_argument); + + // Test bad dtype + EXPECT_THROW(cunumeric::eye(3, std::nullopt, 0, legate::binary_type(2)), std::invalid_argument); + EXPECT_THROW(cunumeric::eye(3, std::nullopt, 0, legate::point_type(2)), std::invalid_argument); +} + +// void cpp_test() +TEST(Eye, Basic) { eye_basic(); } +TEST(Eye, Square) { eye_square(); } +TEST(Eye, InputZero) { eye_input_zero(); } +TEST(Eye, LargeArray) { eye_large_array(); } +TEST(Eye, Negative) { eye_negative(); } diff --git a/tests/cpp/integration/test_msort.cc b/tests/cpp/integration/test_msort.cc index 23e0c1f0aa..fd935c379f 100644 --- a/tests/cpp/integration/test_msort.cc +++ b/tests/cpp/integration/test_msort.cc @@ -204,9 +204,7 @@ void test_msort(std::array& in_array, } auto B1 = cunumeric::msort(A1); - // if (in_array.size() != 0) { - // check_array_eq(B1, expect.data(), expect.size()); - // } + check_array_eq(B1, expect.data(), expect.size()); } template From 445468ea006326bc7af987042c8636dcb528dabc Mon Sep 17 00:00:00 2001 From: Mona Han <129724851+mona-nv@users.noreply.github.com> Date: Fri, 29 Dec 2023 13:32:00 +0800 Subject: [PATCH 135/462] Port cunumeric.flip and add tests (#74) * Port cunumeric.flip and add tests * Remove the unnecessary check in the test code * Update the axes fill order based on review comments --------- Signed-off-by: monah --- src/cunumeric/ndarray.cc | 35 ++ src/cunumeric/ndarray.h | 2 + src/cunumeric/operators.cc | 2 + src/cunumeric/operators.h | 2 + tests/cpp/integration/test_flip.cc | 666 +++++++++++++++++++++++++++++ 5 files changed, 707 insertions(+) create mode 100644 tests/cpp/integration/test_flip.cc diff --git a/src/cunumeric/ndarray.cc b/src/cunumeric/ndarray.cc index b8d450c017..c4258cdbb3 100644 --- a/src/cunumeric/ndarray.cc +++ b/src/cunumeric/ndarray.cc @@ -648,6 +648,41 @@ NDArray NDArray::transpose(std::vector axes) return NDArray(store_.transpose(std::move(axes))); } +NDArray NDArray::flip(std::optional> axis) +{ + auto runtime = CuNumericRuntime::get_runtime(); + auto result = runtime->create_array(shape(), type()); + + result.flip(*this, axis); + + return result; +} + +void NDArray::flip(NDArray rhs, std::optional> axis) +{ + auto input = rhs.store_; + auto output = (*this).store_; + + std::vector axes; + if (!axis.has_value()) { + for (int32_t i = 0; i < dim(); ++i) { + axes.push_back(i); + } + } else { + axes = normalize_axis_vector(axis.value(), dim()); + } + + auto runtime = CuNumericRuntime::get_runtime(); + auto task = runtime->create_task(CuNumericOpCode::CUNUMERIC_FLIP); + auto p_out = task.add_output(output); + auto p_in = task.add_input(input); + task.add_scalar_arg(legate::Scalar(axes)); + task.add_constraint(legate::broadcast(p_in, legate::from_range(dim()))); + task.add_constraint(legate::align(p_in, p_out)); + + runtime->submit(std::move(task)); +} + NDArray NDArray::all(std::optional> axis, std::optional out, std::optional keepdims, diff --git a/src/cunumeric/ndarray.h b/src/cunumeric/ndarray.h index a722412d56..e01a267273 100644 --- a/src/cunumeric/ndarray.h +++ b/src/cunumeric/ndarray.h @@ -90,6 +90,7 @@ class NDArray { std::string kind = "quicksort"); NDArray transpose(); NDArray transpose(std::vector axes); + NDArray flip(std::optional> axis = std::nullopt); NDArray all(std::optional> axis = std::nullopt, std::optional out = std::nullopt, std::optional keepdims = std::nullopt, @@ -126,6 +127,7 @@ class NDArray { std::optional> args = std::nullopt, std::optional initial = std::nullopt, std::optional where = std::nullopt); + void flip(NDArray rhs, std::optional> axis); public: static legate::Library get_library(); diff --git a/src/cunumeric/operators.cc b/src/cunumeric/operators.cc index 1381ed024e..10f20945cb 100644 --- a/src/cunumeric/operators.cc +++ b/src/cunumeric/operators.cc @@ -469,4 +469,6 @@ NDArray moveaxis(NDArray a, std::vector source, std::vector de return a.transpose(order); } +NDArray flip(NDArray input, std::optional> axis) { return input.flip(axis); } + } // namespace cunumeric diff --git a/src/cunumeric/operators.h b/src/cunumeric/operators.h index eaffe33a21..26b7316806 100644 --- a/src/cunumeric/operators.h +++ b/src/cunumeric/operators.h @@ -109,6 +109,8 @@ NDArray transpose(NDArray a, std::vector axes); NDArray moveaxis(NDArray a, std::vector source, std::vector destination); +NDArray flip(NDArray input, std::optional> axis = std::nullopt); + // helper methods int32_t normalize_axis_index(int32_t axis, int32_t ndim); diff --git a/tests/cpp/integration/test_flip.cc b/tests/cpp/integration/test_flip.cc new file mode 100644 index 0000000000..4209da738d --- /dev/null +++ b/tests/cpp/integration/test_flip.cc @@ -0,0 +1,666 @@ +/* Copyright 2023 NVIDIA Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +#include +#include +#include + +#include +#include "legate.h" +#include "cunumeric.h" +#include "util.inl" + +auto get_flip_expect_result_int() +{ + std::vector>> expect_result = { + {{0, {1, 11, 6, 7, 9, 8, 4, 2, 5, 12, 3, 10}}}, + {{0, {10, 3, 12, 5, 2, 4, 8, 9, 7, 6, 11, 1}}, {1, {1, 11, 6, 7, 9, 8, 4, 2, 5, 12, 3, 10}}}, + {{0, {1, 11, 6, 7, 9, 8, 4, 2, 5, 12, 3, 10}}, {1, {10, 3, 12, 5, 2, 4, 8, 9, 7, 6, 11, 1}}}, + {{0, {7, 6, 11, 1, 2, 4, 8, 9, 10, 3, 12, 5}}, {1, {5, 12, 3, 10, 9, 8, 4, 2, 1, 11, 6, 7}}}, + {{0, {1, 11, 6, 7, 9, 8, 4, 2, 5, 12, 3, 10}}, + {1, {10, 3, 12, 5, 2, 4, 8, 9, 7, 6, 11, 1}}, + {2, {10, 3, 12, 5, 2, 4, 8, 9, 7, 6, 11, 1}}}, + {{0, {10, 3, 12, 5, 2, 4, 8, 9, 7, 6, 11, 1}}, + {1, {1, 11, 6, 7, 9, 8, 4, 2, 5, 12, 3, 10}}, + {2, {10, 3, 12, 5, 2, 4, 8, 9, 7, 6, 11, 1}}}, + {{0, {10, 3, 12, 5, 2, 4, 8, 9, 7, 6, 11, 1}}, + {1, {10, 3, 12, 5, 2, 4, 8, 9, 7, 6, 11, 1}}, + {2, {1, 11, 6, 7, 9, 8, 4, 2, 5, 12, 3, 10}}}, + {{0, {8, 9, 7, 6, 11, 1, 10, 3, 12, 5, 2, 4}}, + {1, {5, 2, 4, 10, 3, 12, 6, 11, 1, 8, 9, 7}}, + {2, {12, 3, 10, 4, 2, 5, 7, 9, 8, 1, 11, 6}}}}; + return expect_result; +} + +auto get_flip_expect_result_double() +{ + std::vector>> expect_result = { + {{0, {4, 9, 12, 7.9, 11, 8, 10.5, 2.2, 5.98, 6, 3.66, 1.5}}}, + {{0, {1.5, 3.66, 6, 5.98, 2.2, 10.5, 8, 11, 7.9, 12, 9, 4}}, + {1, {4, 9, 12, 7.9, 11, 8, 10.5, 2.2, 5.98, 6, 3.66, 1.5}}}, + {{0, {4, 9, 12, 7.9, 11, 8, 10.5, 2.2, 5.98, 6, 3.66, 1.5}}, + {1, {1.5, 3.66, 6, 5.98, 2.2, 10.5, 8, 11, 7.9, 12, 9, 4}}}, + {{0, {7.9, 12, 9, 4, 2.2, 10.5, 8, 11, 1.5, 3.66, 6, 5.98}}, + {1, {5.98, 6, 3.66, 1.5, 11, 8, 10.5, 2.2, 4, 9, 12, 7.9}}}, + {{0, {4, 9, 12, 7.9, 11, 8, 10.5, 2.2, 5.98, 6, 3.66, 1.5}}, + {1, {1.5, 3.66, 6, 5.98, 2.2, 10.5, 8, 11, 7.9, 12, 9, 4}}, + {2, {1.5, 3.66, 6, 5.98, 2.2, 10.5, 8, 11, 7.9, 12, 9, 4}}}, + {{0, {1.5, 3.66, 6, 5.98, 2.2, 10.5, 8, 11, 7.9, 12, 9, 4}}, + {1, {4, 9, 12, 7.9, 11, 8, 10.5, 2.2, 5.98, 6, 3.66, 1.5}}, + {2, {1.5, 3.66, 6, 5.98, 2.2, 10.5, 8, 11, 7.9, 12, 9, 4}}}, + {{0, {1.5, 3.66, 6, 5.98, 2.2, 10.5, 8, 11, 7.9, 12, 9, 4}}, + {1, {1.5, 3.66, 6, 5.98, 2.2, 10.5, 8, 11, 7.9, 12, 9, 4}}, + {2, {4, 9, 12, 7.9, 11, 8, 10.5, 2.2, 5.98, 6, 3.66, 1.5}}}, + {{0, {8, 11, 7.9, 12, 9, 4, 1.5, 3.66, 6, 5.98, 2.2, 10.5}}, + {1, {5.98, 2.2, 10.5, 1.5, 3.66, 6, 12, 9, 4, 8, 11, 7.9}}, + {2, {6, 3.66, 1.5, 10.5, 2.2, 5.98, 7.9, 11, 8, 4, 9, 12}}}}; + return expect_result; +} + +auto get_flip_expect_result_complex() +{ + std::vector, 12>>> expect_result = { + {{0, + {complex(6, 4), + complex(7.9, 12), + complex(8, 11), + complex(2.2, 10.5), + complex(6, 5.98), + complex(1.5, 3.66), + complex(11, 1), + complex(7, 6), + complex(8, 9), + complex(2, 4), + complex(12, 5), + complex(10, 3)}}}, + {{0, + {complex(10, 3), + complex(12, 5), + complex(2, 4), + complex(8, 9), + complex(7, 6), + complex(11, 1), + complex(1.5, 3.66), + complex(6, 5.98), + complex(2.2, 10.5), + complex(8, 11), + complex(7.9, 12), + complex(6, 4)}}, + {1, + {complex(6, 4), + complex(7.9, 12), + complex(8, 11), + complex(2.2, 10.5), + complex(6, 5.98), + complex(1.5, 3.66), + complex(11, 1), + complex(7, 6), + complex(8, 9), + complex(2, 4), + complex(12, 5), + complex(10, 3)}}}, + {{0, + {complex(6, 4), + complex(7.9, 12), + complex(8, 11), + complex(2.2, 10.5), + complex(6, 5.98), + complex(1.5, 3.66), + complex(11, 1), + complex(7, 6), + complex(8, 9), + complex(2, 4), + complex(12, 5), + complex(10, 3)}}, + {1, + {complex(10, 3), + complex(12, 5), + complex(2, 4), + complex(8, 9), + complex(7, 6), + complex(11, 1), + complex(1.5, 3.66), + complex(6, 5.98), + complex(2.2, 10.5), + complex(8, 11), + complex(7.9, 12), + complex(6, 4)}}}, + {{0, + {complex(2.2, 10.5), + complex(8, 11), + complex(7.9, 12), + complex(6, 4), + complex(7, 6), + complex(11, 1), + complex(1.5, 3.66), + complex(6, 5.98), + complex(10, 3), + complex(12, 5), + complex(2, 4), + complex(8, 9)}}, + {1, + {complex(8, 9), + complex(2, 4), + complex(12, 5), + complex(10, 3), + complex(6, 5.98), + complex(1.5, 3.66), + complex(11, 1), + complex(7, 6), + complex(6, 4), + complex(7.9, 12), + complex(8, 11), + complex(2.2, 10.5)}}}, + {{0, + {complex(6, 4), + complex(7.9, 12), + complex(8, 11), + complex(2.2, 10.5), + complex(6, 5.98), + complex(1.5, 3.66), + complex(11, 1), + complex(7, 6), + complex(8, 9), + complex(2, 4), + complex(12, 5), + complex(10, 3)}}, + {1, + {complex(10, 3), + complex(12, 5), + complex(2, 4), + complex(8, 9), + complex(7, 6), + complex(11, 1), + complex(1.5, 3.66), + complex(6, 5.98), + complex(2.2, 10.5), + complex(8, 11), + complex(7.9, 12), + complex(6, 4)}}, + {2, + {complex(10, 3), + complex(12, 5), + complex(2, 4), + complex(8, 9), + complex(7, 6), + complex(11, 1), + complex(1.5, 3.66), + complex(6, 5.98), + complex(2.2, 10.5), + complex(8, 11), + complex(7.9, 12), + complex(6, 4)}}}, + {{0, + {complex(10, 3), + complex(12, 5), + complex(2, 4), + complex(8, 9), + complex(7, 6), + complex(11, 1), + complex(1.5, 3.66), + complex(6, 5.98), + complex(2.2, 10.5), + complex(8, 11), + complex(7.9, 12), + complex(6, 4)}}, + {1, + {complex(6, 4), + complex(7.9, 12), + complex(8, 11), + complex(2.2, 10.5), + complex(6, 5.98), + complex(1.5, 3.66), + complex(11, 1), + complex(7, 6), + complex(8, 9), + complex(2, 4), + complex(12, 5), + complex(10, 3)}}, + {2, + {complex(10, 3), + complex(12, 5), + complex(2, 4), + complex(8, 9), + complex(7, 6), + complex(11, 1), + complex(1.5, 3.66), + complex(6, 5.98), + complex(2.2, 10.5), + complex(8, 11), + complex(7.9, 12), + complex(6, 4)}}}, + {{0, + {complex(10, 3), + complex(12, 5), + complex(2, 4), + complex(8, 9), + complex(7, 6), + complex(11, 1), + complex(1.5, 3.66), + complex(6, 5.98), + complex(2.2, 10.5), + complex(8, 11), + complex(7.9, 12), + complex(6, 4)}}, + {1, + {complex(10, 3), + complex(12, 5), + complex(2, 4), + complex(8, 9), + complex(7, 6), + complex(11, 1), + complex(1.5, 3.66), + complex(6, 5.98), + complex(2.2, 10.5), + complex(8, 11), + complex(7.9, 12), + complex(6, 4)}}, + {2, + {complex(6, 4), + complex(7.9, 12), + complex(8, 11), + complex(2.2, 10.5), + complex(6, 5.98), + complex(1.5, 3.66), + complex(11, 1), + complex(7, 6), + complex(8, 9), + complex(2, 4), + complex(12, 5), + complex(10, 3)}}}, + {{0, + {complex(1.5, 3.66), + complex(6, 5.98), + complex(2.2, 10.5), + complex(8, 11), + complex(7.9, 12), + complex(6, 4), + complex(10, 3), + complex(12, 5), + complex(2, 4), + complex(8, 9), + complex(7, 6), + complex(11, 1)}}, + {1, + {complex(8, 9), + complex(7, 6), + complex(11, 1), + complex(10, 3), + complex(12, 5), + complex(2, 4), + complex(8, 11), + complex(7.9, 12), + complex(6, 4), + complex(1.5, 3.66), + complex(6, 5.98), + complex(2.2, 10.5)}}, + {2, + {complex(2, 4), + complex(12, 5), + complex(10, 3), + complex(11, 1), + complex(7, 6), + complex(8, 9), + complex(2.2, 10.5), + complex(6, 5.98), + complex(1.5, 3.66), + complex(6, 4), + complex(7.9, 12), + complex(8, 11)}}}}; + return expect_result; +} + +template +void test_flip(std::array& in_array, + std::array& expect, + legate::Type leg_type, + std::vector shape, + std::optional> axis = std::nullopt) +{ + auto A1 = cunumeric::zeros(shape, leg_type); + if (in_array.size() != 0) { + if (in_array.size() == 1) { + A1.fill(legate::Scalar(in_array[0]), false); + } else { + assign_values_to_array(A1, in_array.data(), in_array.size()); + } + } + + auto B1 = cunumeric::flip(A1, axis); + check_array_eq(B1, expect.data(), expect.size()); +} + +template +void test_flip_none_axis(std::vector>& test_shapes, + std::array& in_array, + std::array& expect_result, + legate::Type leg_type) +{ + size_t test_shape_size = test_shapes.size(); + for (size_t i = 0; i < test_shape_size; ++i) { + auto test_shape = test_shapes[i]; + int32_t dim = test_shape.size(); + if (dim == 1) { + test_flip(in_array, expect_result, leg_type, test_shape); + } else if (dim == 2) { + test_flip(in_array, expect_result, leg_type, test_shape); + } else if (dim == 3) { + test_flip(in_array, expect_result, leg_type, test_shape); + } else if (dim == 4) { +#if LEGATE_MAX_DIM >= 4 + test_flip(in_array, expect_result, leg_type, test_shape); +#endif + } else if (dim == 5) { +#if LEGATE_MAX_DIM >= 5 + test_flip(in_array, expect_result, leg_type, test_shape); +#endif + } else if (dim == 6) { +#if LEGATE_MAX_DIM >= 6 + test_flip(in_array, expect_result, leg_type, test_shape); +#endif + } else if (dim == 7) { +#if LEGATE_MAX_DIM >= 7 + test_flip(in_array, expect_result, leg_type, test_shape); +#endif + } + } +} + +template +void test_flip_each_axis(std::vector>& test_shapes, + std::array& in_array, + std::vector>>& expect_result, + legate::Type leg_type) +{ + size_t test_shape_size = test_shapes.size(); + for (size_t i = 0; i < test_shape_size; ++i) { + auto test_shape = test_shapes[i]; + int32_t dim = test_shape.size(); + for (int32_t axis = -dim + 1; axis < dim; ++axis) { + auto index = axis < 0 ? axis + dim : axis; + auto expect_val = expect_result[i][index]; + auto axes = {axis}; + if (dim == 1) { + test_flip(in_array, expect_val, leg_type, test_shape, axes); + } else if (dim == 2) { + test_flip(in_array, expect_val, leg_type, test_shape, axes); + } else if (dim == 3) { + test_flip(in_array, expect_val, leg_type, test_shape, axes); + } else if (dim == 4) { +#if LEGATE_MAX_DIM >= 4 + test_flip(in_array, expect_val, leg_type, test_shape, axes); +#endif + } else if (dim == 5) { +#if LEGATE_MAX_DIM >= 5 + test_flip(in_array, expect_val, leg_type, test_shape, axes); +#endif + } else if (dim == 6) { +#if LEGATE_MAX_DIM >= 6 + test_flip(in_array, expect_val, leg_type, test_shape, axes); +#endif + } else if (dim == 7) { +#if LEGATE_MAX_DIM >= 7 + test_flip(in_array, expect_val, leg_type, test_shape, axes); +#endif + } + } + } +} + +void flip_basic() +{ + // If no axis is input, the expect result would equal reverse result of the input array, no matter + // what's the array shape is. + std::vector> test_shapes = { + {12}, {1, 12}, {12, 1}, {3, 4}, {12, 1, 1}, {1, 12, 1}, {1, 1, 12}, {2, 2, 3}}; + + // Test int type + std::array in_array1 = {10, 3, 12, 5, 2, 4, 8, 9, 7, 6, 11, 1}; + std::array expect_result1; + std::reverse_copy(in_array1.begin(), in_array1.end(), expect_result1.begin()); + test_flip_none_axis(test_shapes, in_array1, expect_result1, legate::int32()); + + // Test float type + std::array int_array2 = {1.5, 3.66, 6, 5.98, 2.2, 10.5, 8, 11, 7.9, 12, 9, 4}; + std::array expect_result2; + std::reverse_copy(int_array2.begin(), int_array2.end(), expect_result2.begin()); + test_flip_none_axis(test_shapes, int_array2, expect_result2, legate::float64()); + + // Test complex type + std::array, 12> in_array3 = {complex(10, 3), + complex(12, 5), + complex(2, 4), + complex(8, 9), + complex(7, 6), + complex(11, 1), + complex(1.5, 3.66), + complex(6, 5.98), + complex(2.2, 10.5), + complex(8, 11), + complex(7.9, 12), + complex(6, 4)}; + std::array, 12> expect_result3; + std::reverse_copy(in_array3.begin(), in_array3.end(), expect_result3.begin()); + test_flip_none_axis, 12>( + test_shapes, in_array3, expect_result3, legate::complex64()); +} + +void flip_single_axis() +{ + std::vector> test_shapes = { + {12}, {1, 12}, {12, 1}, {3, 4}, {12, 1, 1}, {1, 12, 1}, {1, 1, 12}, {2, 2, 3}}; + + // Test int type + std::array in_array1 = {10, 3, 12, 5, 2, 4, 8, 9, 7, 6, 11, 1}; + auto expect_result1 = get_flip_expect_result_int(); + test_flip_each_axis(test_shapes, in_array1, expect_result1, legate::int32()); + + // Test float type + std::array int_array2 = {1.5, 3.66, 6, 5.98, 2.2, 10.5, 8, 11, 7.9, 12, 9, 4}; + auto expect_result2 = get_flip_expect_result_double(); + test_flip_each_axis(test_shapes, int_array2, expect_result2, legate::float64()); + + // Test complex type + std::array, 12> in_array3 = {complex(10, 3), + complex(12, 5), + complex(2, 4), + complex(8, 9), + complex(7, 6), + complex(11, 1), + complex(1.5, 3.66), + complex(6, 5.98), + complex(2.2, 10.5), + complex(8, 11), + complex(7.9, 12), + complex(6, 4)}; + auto expect_result3 = get_flip_expect_result_complex(); + test_flip_each_axis, 12>( + test_shapes, in_array3, expect_result3, legate::complex64()); +} + +void flip_multi_axis() +{ + // Test float type + std::vector test_shape = {2, 2, 3}; + std::array in_array = {1.5, 3.66, 6, 5.98, 2.2, 10.5, 8, 11, 7.9, 12, 9, 4}; + + auto axes1 = {-1, 0}; + std::array expect_result1 = {7.9, 11, 8, 4, 9, 12, 6, 3.66, 1.5, 10.5, 2.2, 5.98}; + test_flip(in_array, expect_result1, legate::float64(), test_shape, axes1); + + auto axes2 = {-1, 1}; + std::array expect_result2 = {10.5, 2.2, 5.98, 6, 3.66, 1.5, 4, 9, 12, 7.9, 11, 8}; + test_flip(in_array, expect_result2, legate::float64(), test_shape, axes2); + + auto axes3 = {0, 1}; + std::array expect_result3 = {12, 9, 4, 8, 11, 7.9, 5.98, 2.2, 10.5, 1.5, 3.66, 6}; + test_flip(in_array, expect_result3, legate::float64(), test_shape, axes3); + + auto axes4 = {-1, 0, 1}; + std::array expect_result4 = {4, 9, 12, 7.9, 11, 8, 10.5, 2.2, 5.98, 6, 3.66, 1.5}; + test_flip(in_array, expect_result4, legate::float64(), test_shape, axes4); +} + +void flip_max_dim() +{ + std::array in_array = {14, 10, 3, 12, 5, 13, 2, 4, 16, 8, 9, 7, 6, 11, 1, 15}; + std::array expect_result = {15, 1, 11, 6, 7, 9, 8, 16, 4, 2, 13, 5, 12, 3, 10, 14}; +#if LEGATE_MAX_DIM >= 4 + std::vector test_shape_4d = {2, 2, 2, 2}; + // Flip with none axis + test_flip(in_array, expect_result, legate::int32(), test_shape_4d); + // Flip with axis + auto axes_4d = {2, 1, 3}; + std::array expect_result_4d = { + 4, 2, 13, 5, 12, 3, 10, 14, 15, 1, 11, 6, 7, 9, 8, 16}; + test_flip(in_array, expect_result_4d, legate::int32(), test_shape_4d, axes_4d); +#endif + +#if LEGATE_MAX_DIM >= 5 + std::vector test_shape_5d = {1, 2, 2, 1, 4}; + // Flip with none axis + test_flip(in_array, expect_result, legate::int32(), test_shape_5d); + // Flip with axis + auto axes_5d = {4}; + std::array expect_result_5d = { + 12, 3, 10, 14, 4, 2, 13, 5, 7, 9, 8, 16, 15, 1, 11, 6}; + test_flip(in_array, expect_result_5d, legate::int32(), test_shape_5d, axes_5d); +#endif + +#if LEGATE_MAX_DIM >= 6 + std::vector test_shape_6d = {2, 1, 1, 2, 2, 2}; + // Flip with none axis + test_flip(in_array, expect_result, legate::int32(), test_shape_6d); + // Flip with axis + auto axes_6d = {-1, -3, 0, 1}; + std::array expect_result_6d = { + 11, 6, 15, 1, 8, 16, 7, 9, 13, 5, 4, 2, 10, 14, 12, 3}; + test_flip(in_array, expect_result_6d, legate::int32(), test_shape_6d, axes_6d); +#endif + +#if LEGATE_MAX_DIM >= 7 + std::vector test_shape_7d = {1, 16, 1, 1, 1, 1, 1}; + // Flip with none axis + test_flip(in_array, expect_result, legate::int32(), test_shape_7d); + // Flip with axis + auto axes_7d = {0, 2, 3, 4, 5, 6}; + std::array expect_result_7d = { + 14, 10, 3, 12, 5, 13, 2, 4, 16, 8, 9, 7, 6, 11, 1, 15}; + test_flip(in_array, expect_result_7d, legate::int32(), test_shape_7d, axes_7d); +#endif +} + +void flip_large_array() +{ + const int32_t count = 10000; + std::vector test_shape = {count}; + + // Test int type for large array + std::array in_array1; + for (int32_t i = 0; i < count; i++) { + in_array1[i] = count - i; + } + std::array expect_val1; + for (int32_t j = 0; j < count; j++) { + expect_val1[j] = j + 1; + } + test_flip(in_array1, expect_val1, legate::int32(), test_shape); + + // Test float type + std::array in_array2; + for (int32_t i = 0; i < count; i++) { + in_array2[i] = count * 1.0 - i; + } + std::array expect_val2; + for (int32_t j = 0; j < count; j++) { + expect_val2[j] = (j + 1) * 1.0; + } + test_flip(in_array2, expect_val2, legate::float64(), test_shape); + + // Test complex type + std::array, count> in_array3; + for (int32_t i = 0; i < count; i++) { + in_array3[i] = complex(count - i, count - i); + } + std::array, count> expect_val3; + for (int32_t j = 0; j < count; j++) { + expect_val3[j] = complex(j + 1, j + 1); + } + test_flip, count, 1>(in_array3, expect_val3, legate::complex64(), test_shape); +} + +void flip_empty_array() +{ + std::array in_array = {}; + std::vector test_shape = {0}; + + // Without axis input + test_flip(in_array, in_array, legate::int32(), test_shape); + + // With axis input + auto axes = {0}; + test_flip(in_array, in_array, legate::int32(), test_shape, axes); +} + +void flip_single_item_array() +{ + std::vector test_shape = {1, 1, 1}; + std::array in_array = {12}; + + // Without axis input + test_flip(in_array, in_array, legate::int32(), test_shape); + + // With axis input + auto axes1 = {1}; + test_flip(in_array, in_array, legate::int32(), test_shape, axes1); + + auto axes2 = {-1, 1}; + test_flip(in_array, in_array, legate::int32(), test_shape, axes2); + + auto axes3 = {-1, 0, 1}; + test_flip(in_array, in_array, legate::int32(), test_shape, axes3); +} + +void flip_negative_test() +{ + auto in_array = cunumeric::zeros({2, 3}, legate::int32()); + + // Test axis out-of-bound + auto axes1 = {12}; + EXPECT_THROW(cunumeric::flip(in_array, axes1), std::invalid_argument); + + // Test axis out-of-bound negative + auto axes2 = {-12}; + EXPECT_THROW(cunumeric::flip(in_array, axes2), std::invalid_argument); + + // Test axis repeated axis + auto axes3 = {1, 1}; + EXPECT_THROW(cunumeric::flip(in_array, axes3), std::invalid_argument); + + // Test axis out-of-bound multiple + auto axes4 = {1, 2}; + EXPECT_THROW(cunumeric::flip(in_array, axes4), std::invalid_argument); +} + +// void cpp_test() +TEST(Flip, Basic) { flip_basic(); } +TEST(Flip, Single_Axis) { flip_single_axis(); } +TEST(Flip, Multi_Axis) { flip_multi_axis(); } +TEST(Flip, MaxDim) { flip_max_dim(); } +TEST(Flip, LargeArray) { flip_large_array(); } +TEST(Flip, EmptyArray) { flip_empty_array(); } +TEST(Flip, SingleItemArray) { flip_single_item_array(); } +TEST(Flip, Negative) { flip_negative_test(); } From 5e089b28140a09b0fcf5d9a5c3c331095c33ed46 Mon Sep 17 00:00:00 2001 From: Shijie Chen Date: Tue, 2 Jan 2024 09:41:27 +0800 Subject: [PATCH 136/462] Port cunumeric.put and add tests (#49) * added _wrap and _convert_future_to_regionfield * added common_utils * added as_type_vector, updated _warp * Port cunumeric.put and add tests * cunumeric.put fixed issues and added testcases --- src/cunumeric/ndarray.cc | 161 ++++++++++++++ src/cunumeric/ndarray.h | 6 + src/cunumeric/operators.cc | 9 + src/cunumeric/operators.h | 4 + src/cunumeric/runtime.cc | 6 +- src/cunumeric/runtime.h | 4 +- tests/cpp/integration/common_utils.cc | 171 +++++++++++++++ tests/cpp/integration/common_utils.h | 135 ++++++++++++ tests/cpp/integration/test_put.cc | 292 ++++++++++++++++++++++++++ 9 files changed, 785 insertions(+), 3 deletions(-) create mode 100644 tests/cpp/integration/common_utils.cc create mode 100644 tests/cpp/integration/common_utils.h create mode 100644 tests/cpp/integration/test_put.cc diff --git a/src/cunumeric/ndarray.cc b/src/cunumeric/ndarray.cc index c4258cdbb3..e3e8419a58 100644 --- a/src/cunumeric/ndarray.cc +++ b/src/cunumeric/ndarray.cc @@ -931,6 +931,167 @@ void NDArray::convert(NDArray rhs, int32_t nan_op) runtime->submit(std::move(task)); } +void NDArray::put(NDArray indices, NDArray values, std::string mode) +{ + if (values.size() == 0 || indices.size() == 0 || size() == 0) { + return; + } + + if (mode != "raise" && mode != "wrap" && mode != "clip") { + std::stringstream ss; + ss << "mode must be one of 'clip', 'raise', or 'wrap' (got " << mode << ")"; + throw std::invalid_argument(ss.str()); + } + + // type conversion + indices = indices._warn_and_convert(legate::int64()); + values = values._warn_and_convert(type()); + + // reshape indices + if (indices.dim() > 1) { + // When reshape is ready, reshape can be used. + indices = indices._wrap(indices.size()); + } + + // call _wrap on the values if they need to be wrapped + if (values.dim() != indices.dim() || values.size() != indices.size()) { + values = values._wrap(indices.size()); + } + + if (mode == "wrap") { + indices = indices.wrap_indices(Scalar(int64_t(size()))); + } else if (mode == "clip") { + indices = indices.clip_indices(Scalar(int64_t(0)), Scalar(int64_t(size() - 1))); + } + + if (indices.store_.has_scalar_storage() || indices.store_.transformed()) { + bool change_shape = indices.store_.has_scalar_storage(); + indices = indices._convert_future_to_regionfield(change_shape); + } + if (values.store_.has_scalar_storage() || values.store_.transformed()) { + bool change_shape = values.store_.has_scalar_storage(); + values = values._convert_future_to_regionfield(change_shape); + } + bool need_copy = false; + auto self_tmp = *this; + if (self_tmp.store_.has_scalar_storage() || self_tmp.store_.transformed()) { + need_copy = true; + bool change_shape = self_tmp.store_.has_scalar_storage(); + self_tmp = self_tmp._convert_future_to_regionfield(change_shape); + } + + auto runtime = CuNumericRuntime::get_runtime(); + bool check_bounds = (mode == "raise"); + auto task = runtime->create_task(CuNumericOpCode::CUNUMERIC_WRAP); + auto indirect = runtime->create_array(indices.shape(), legate::point_type(self_tmp.dim()), false); + auto p_indirect = task.add_output(indirect.store_); + auto p_indices = task.add_input(indices.store_); + task.add_scalar_arg(legate::Scalar(self_tmp.shape())); + task.add_scalar_arg(legate::Scalar(true)); // has_input + task.add_scalar_arg(legate::Scalar(check_bounds)); + task.add_constraint(legate::align(p_indices, p_indirect)); + task.throws_exception(true); + runtime->submit(std::move(task)); + + auto legate_runtime = legate::Runtime::get_runtime(); + legate_runtime->issue_scatter(self_tmp.store_, indirect.store_, values.store_); + + if (need_copy) { + assign(self_tmp); + } +} + +NDArray NDArray::_convert_future_to_regionfield(bool change_shape) +{ + auto runtime = CuNumericRuntime::get_runtime(); + if (change_shape && dim() == 0) { + auto out = runtime->create_array({1}, type(), false); + out.assign(*this); + return out; + } + auto out = runtime->create_array(shape(), type(), false); + out.assign(*this); + return out; +} + +NDArray NDArray::_wrap(size_t new_len) +{ + auto runtime = CuNumericRuntime::get_runtime(); + + if (0 == new_len) { + return runtime->create_array({0}, type()); + } + if (size() == 0) { + throw std::invalid_argument("Unable to wrap an empty array to a length greater than 0."); + } + if (1 == new_len) { + auto tmp_store = store_; + for (int32_t i = 0; i < dim(); ++i) { + tmp_store = tmp_store.project(0, 0); + } + NDArray tmp_arr(std::move(tmp_store)); + auto out = runtime->create_array(tmp_arr.shape(), tmp_arr.type()); + out.assign(tmp_arr); + return out; + } + + auto src = *this; + if (src.store_.has_scalar_storage() || src.store_.transformed()) { + bool change_shape = src.store_.has_scalar_storage(); + src = src._convert_future_to_regionfield(change_shape); + } + + auto task = runtime->create_task(CuNumericOpCode::CUNUMERIC_WRAP); + auto indirect = runtime->create_array({new_len}, legate::point_type(src.dim()), false); + task.add_output(indirect.store_); + task.add_scalar_arg(legate::Scalar(src.shape())); + task.add_scalar_arg(legate::Scalar(false)); // has_input + task.add_scalar_arg(legate::Scalar(false)); // check bounds + runtime->submit(std::move(task)); + + auto legate_runtime = legate::Runtime::get_runtime(); + auto out = runtime->create_array({new_len}, src.type(), false); + legate_runtime->issue_gather(out.store_, src.store_, indirect.store_); + + return out; +} + +NDArray NDArray::_warn_and_convert(legate::Type const& type) +{ + if (this->type() != type) { + std::stringstream ss; + ss << "converting array to " << type.to_string() << " type"; + cunumeric_log().warning() << ss.str(); + return as_type(type); + } + return *this; +} + +NDArray NDArray::wrap_indices(Scalar const& n) +{ + auto runtime = CuNumericRuntime::get_runtime(); + auto out = runtime->create_array(shape(), type()); + auto divisor_store = runtime->create_scalar_store(n); + auto divisor = runtime->create_array(std::move(divisor_store)); + out.binary_op(static_cast(cunumeric::BinaryOpCode::MOD), *this, divisor); + return out; +} + +NDArray NDArray::clip_indices(Scalar const& min, Scalar const& max) +{ + auto runtime = CuNumericRuntime::get_runtime(); + auto out = runtime->create_array(shape(), type()); + auto task = runtime->create_task(CuNumericOpCode::CUNUMERIC_UNARY_OP); + auto p_out = task.add_output(out.store_); + auto p_in = task.add_input(store_); + task.add_scalar_arg(legate::Scalar(static_cast(UnaryOpCode::CLIP))); + task.add_scalar_arg(min); + task.add_scalar_arg(max); + task.add_constraint(align(p_out, p_in)); + runtime->submit(std::move(task)); + return out; +} + legate::LogicalStore NDArray::get_store() { return store_; } legate::LogicalStore NDArray::broadcast(const std::vector& shape, diff --git a/src/cunumeric/ndarray.h b/src/cunumeric/ndarray.h index e01a267273..7dc96bf8f1 100644 --- a/src/cunumeric/ndarray.h +++ b/src/cunumeric/ndarray.h @@ -96,11 +96,17 @@ class NDArray { std::optional keepdims = std::nullopt, std::optional initial = std::nullopt, std::optional where = std::nullopt); + void put(NDArray indices, NDArray values, std::string mode = "raise"); public: NDArray as_type(const legate::Type& type); legate::LogicalStore get_store(); void sort(NDArray rhs, bool argsort, std::optional axis = -1, bool stable = false); + NDArray _convert_future_to_regionfield(bool change_shape = false); + NDArray _wrap(size_t new_len); + NDArray _warn_and_convert(legate::Type const& type); + NDArray wrap_indices(Scalar const& n); + NDArray clip_indices(Scalar const& min, Scalar const& max); private: legate::LogicalStore broadcast(const std::vector& shape, legate::LogicalStore& store); diff --git a/src/cunumeric/operators.cc b/src/cunumeric/operators.cc index 10f20945cb..e789b9ba69 100644 --- a/src/cunumeric/operators.cc +++ b/src/cunumeric/operators.cc @@ -24,6 +24,10 @@ namespace cunumeric { +static legate::Logger log_cunumeric("cunumeric"); + +legate::Logger& cunumeric_log() { return log_cunumeric; } + NDArray array(std::vector shape, const legate::Type& type) { return CuNumericRuntime::get_runtime()->create_array(std::move(shape), type); @@ -471,4 +475,9 @@ NDArray moveaxis(NDArray a, std::vector source, std::vector de NDArray flip(NDArray input, std::optional> axis) { return input.flip(axis); } +void put(NDArray& a, NDArray indices, NDArray values, std::string mode) +{ + a.put(indices, values, mode); +} + } // namespace cunumeric diff --git a/src/cunumeric/operators.h b/src/cunumeric/operators.h index 26b7316806..01ae4bf4c5 100644 --- a/src/cunumeric/operators.h +++ b/src/cunumeric/operators.h @@ -25,6 +25,8 @@ namespace cunumeric { +legate::Logger& cunumeric_log(); + void initialize(int32_t argc, char** argv); NDArray array(std::vector shape, const legate::Type& type); @@ -111,6 +113,8 @@ NDArray moveaxis(NDArray a, std::vector source, std::vector de NDArray flip(NDArray input, std::optional> axis = std::nullopt); +void put(NDArray& a, NDArray indices, NDArray values, std::string mode = "raise"); + // helper methods int32_t normalize_axis_index(int32_t axis, int32_t ndim); diff --git a/src/cunumeric/runtime.cc b/src/cunumeric/runtime.cc index ae5637dcf3..22e6176754 100644 --- a/src/cunumeric/runtime.cc +++ b/src/cunumeric/runtime.cc @@ -41,9 +41,11 @@ NDArray CuNumericRuntime::create_array(const legate::Type& type) return NDArray(std::move(store)); } -NDArray CuNumericRuntime::create_array(std::vector shape, const legate::Type& type) +NDArray CuNumericRuntime::create_array(std::vector shape, + const legate::Type& type, + bool optimize_scalar) { - auto store = legate_runtime_->create_store(legate::Shape{shape}, type, true /*optimize_scalar*/); + auto store = legate_runtime_->create_store(legate::Shape{shape}, type, optimize_scalar); return NDArray(std::move(store)); } diff --git a/src/cunumeric/runtime.h b/src/cunumeric/runtime.h index 9a1efde3da..4e52356b12 100644 --- a/src/cunumeric/runtime.h +++ b/src/cunumeric/runtime.h @@ -34,7 +34,9 @@ class CuNumericRuntime { public: NDArray create_array(const legate::Type& type); - NDArray create_array(std::vector shape, const legate::Type& type); + NDArray create_array(std::vector shape, + const legate::Type& type, + bool optimize_scalar = true); NDArray create_array(legate::LogicalStore&& store); legate::LogicalStore create_scalar_store(const Scalar& value); diff --git a/tests/cpp/integration/common_utils.cc b/tests/cpp/integration/common_utils.cc new file mode 100644 index 0000000000..0a2ab61ed7 --- /dev/null +++ b/tests/cpp/integration/common_utils.cc @@ -0,0 +1,171 @@ +/* Copyright 2023 NVIDIA Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +#include "common_utils.h" +#include +#include + +namespace cunumeric { + +template +void show_array(NDArray& a) +{ + auto acc = a.get_read_accessor(); + std::cerr << "["; + for (size_t i = 0; i < a.size(); ++i) { + std::cerr << acc[i]; + if (i != a.size() - 1) { + std::cerr << ", "; + } + } + std::cerr << "]" << std::endl; +} + +void debug_array(NDArray a, bool show_data) +{ + auto store = a.get_store(); + if (store.has_scalar_storage()) { + std::cerr << "(S) "; + } else { + std::cerr << "( ) "; + } + if (store.transformed()) { + std::cerr << "(T) "; + } else { + std::cerr << "( ) "; + } + std::cerr << "<" << store.type().to_string() << "> " << store.to_string() << std::endl; + if (!show_data) { + return; + } + if (a.size() == 0) { + std::cerr << "[]" << std::endl; + return; + } + if (a.dim() > 1) { + a = a._wrap(a.size()); + } + switch (a.type().code()) { + case legate::Type::Code::INT32: show_array(a); break; + case legate::Type::Code::INT64: show_array(a); break; + case legate::Type::Code::FLOAT32: show_array(a); break; + case legate::Type::Code::FLOAT64: show_array(a); break; + default: std::cerr << "[ Not implemented ]" << std::endl; break; + } +} + +} // namespace cunumeric + +using namespace cunumeric; + +// unit test for common_utils +namespace { + +TEST(Utils, test_check_array) +{ + { + auto x = mk_array({99}); + debug_array(x); + } + { + auto x = mk_array({99}, {1}); + debug_array(x); + } + { + auto x = mk_array({1, 2, 3, 4}, {2, 2}); + debug_array(x); + check_array(x, {1, 2, 3, 4}, {2, 2}); + } + { + std::vector shape{2, 3, 4}; + auto x_in = mk_seq_vector(shape, 10); + auto x = mk_array(x_in, shape); + debug_array(x); + check_array(x, x_in, shape); + } +} + +void fail1() +{ + std::vector shape{2, 3}; + auto x = mk_array({1, 2, 3, 4, 50, 6}, shape); + auto x_gt = mk_seq_vector(shape); + check_array(x, x_gt, shape); +}; + +void fail2() +{ + auto x = mk_array({1 + 1e-8, 1 + 1e-7, 1 + 1e-6, 1 + 1e-5}); + auto x_gt = mk_seq_vector({4}, 0, 1); + check_array(x, x_gt); +}; + +void fail3() +{ + auto x = mk_array({1 + 1e-8, 1 + 1e-7, 1 + 1e-6, 1 + 1e-5}); + auto x_gt = mk_seq_vector({4}, 0, 1); + check_array(x, x_gt); +}; + +TEST(Utils, test_check_array_neg) +{ + EXPECT_FATAL_FAILURE(fail1(), "check_array"); + EXPECT_FATAL_FAILURE(fail2(), "check_array"); + EXPECT_FATAL_FAILURE(fail3(), "check_array"); +} + +TEST(Utils, test_as_type_vector) +{ + auto x = mk_seq_vector({16}, 0.25); + debug_vector(x); + auto y = as_type_vector(x); + debug_vector(y); +} + +TEST(Utils, test_ndarray_wrap) +{ + auto x = mk_array({1, 2, 3, 4}); + debug_array(x); + auto y = x._wrap(0); + debug_array(y); + auto z = y._wrap(0); + debug_array(z); + EXPECT_ANY_THROW(y._wrap(1);); +} + +TEST(Utils, test_ndarray_warn_and_convert) +{ + auto x_in = mk_seq_vector({8}, 0.5); + auto x = mk_array(x_in); + auto y = x._warn_and_convert(legate::int32()); + debug_array(x); + debug_array(y); + cunumeric_log().warning() << "Just a test!"; +} + +TEST(Utils, test_wrap_indices_and_clip_indices) +{ + std::vector shape{10}; + auto x_in = mk_seq_vector(shape); + auto x = mk_array(x_in, shape); + auto x_warp = x.wrap_indices(Scalar(int64_t(4))); + auto x_clip = x.clip_indices(Scalar(int64_t(3)), Scalar(int64_t(7))); + debug_array(x); + debug_array(x_warp); + debug_array(x_clip); +} + +} // namespace diff --git a/tests/cpp/integration/common_utils.h b/tests/cpp/integration/common_utils.h new file mode 100644 index 0000000000..9f512272f8 --- /dev/null +++ b/tests/cpp/integration/common_utils.h @@ -0,0 +1,135 @@ +/* Copyright 2023 NVIDIA Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "legate.h" +#include "cunumeric.h" +#include "cunumeric/runtime.h" + +namespace cunumeric { + +void debug_array(NDArray a, bool show_data = true); + +template +NDArray mk_array(std::vector const& values, std::vector shape = {}) +{ + if (shape.empty() && values.size() > 1) { + shape.push_back(values.size()); + } + auto out = zeros(shape, legate::primitive_type(legate::type_code_of)); + if (values.size() != out.size()) { + throw std::invalid_argument("size and shape mismatch"); + } + if (out.size() == 0) { + return out; + } + if (out.size() == 1) { + out.fill(legate::Scalar(values[0]), false); + return out; + } + auto assign_values = [](NDArray& a, std::vector const& values) { + auto acc = a.get_write_accessor(); + for (size_t i = 0; i < values.size(); ++i) { + acc[i] = values[i]; + } + }; + if (out.dim() == 1) { + assign_values(out, values); + } else { + auto a1 = zeros({out.size()}, out.type()); + assign_values(a1, values); + auto runtime = CuNumericRuntime::get_runtime(); + auto a2 = runtime->create_array(std::move(a1.get_store().delinearize(0, shape))); + out.assign(a2); + } + return out; +} + +template +void check_array(NDArray a, std::vector values, std::vector shape = {}) +{ + if (shape.empty() && values.size() > 1) { + shape.push_back(values.size()); + } + ASSERT_EQ(a.size(), values.size()); + ASSERT_EQ(a.shape(), shape); + ASSERT_EQ(a.type().code(), legate::type_code_of); + if (a.size() == 0) { + return; + } + if (a.dim() > 1) { + a = a._wrap(a.size()); + } + auto err_msg = [](auto i) { + std::stringstream ss; + ss << "check_array failed at [i = " << i << "]"; + return ss.str(); + }; + auto acc = a.get_read_accessor(); + for (size_t i = 0; i < values.size(); ++i) { + switch (legate::type_code_of) { + case legate::Type::Code::FLOAT32: ASSERT_FLOAT_EQ(acc[i], values[i]) << err_msg(i); break; + case legate::Type::Code::FLOAT64: ASSERT_DOUBLE_EQ(acc[i], values[i]) << err_msg(i); break; + default: ASSERT_EQ(acc[i], values[i]) << err_msg(i); break; + } + } +} + +template +void debug_vector(const std::vector& vec) +{ + std::cerr << "["; + for (auto i = vec.begin(); i != vec.end(); ++i) { + std::cerr << *i; + if (i != vec.end() - 1) { + std::cerr << ", "; + } + } + std::cerr << "]" << std::endl; +} + +// x = a * i + b, i = 1, 2, 3, ... +template +std::vector mk_seq_vector(std::vector shape, T a = 1, T b = 0) +{ + size_t size = std::accumulate(shape.begin(), shape.end(), size_t(1), std::multiplies()); + std::vector v(size); + std::generate(v.begin(), v.end(), [a, x = b]() mutable { return x += a; }); + return v; +} + +template +std::vector as_type_vector(std::vector const& in) +{ + std::vector out; + for (auto elem : in) { + out.push_back(static_cast(elem)); + } + return out; +} + +} // namespace cunumeric diff --git a/tests/cpp/integration/test_put.cc b/tests/cpp/integration/test_put.cc new file mode 100644 index 0000000000..c7705f57aa --- /dev/null +++ b/tests/cpp/integration/test_put.cc @@ -0,0 +1,292 @@ +/* Copyright 2023 NVIDIA Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +#include "common_utils.h" +#include + +using namespace cunumeric; +namespace { + +template +std::vector put_result(std::vector const& a, + std::vector const& indices, + std::vector const& values, + std::string mode = "raise") +{ + if (a.size() == 0 || indices.size() == 0 || values.size() == 0) { + return a; + } + std::vector out(a); + int64_t size = static_cast(a.size()); + + for (size_t i = 0; i < indices.size(); ++i) { + auto val = static_cast(values[i % values.size()]); + auto ind = static_cast(indices[i]); + if (mode == "wrap") { + ind %= size; + } else if (mode == "clip") { + if (ind < 0) { + ind = 0; + } + if (ind >= size) { + ind = size - 1; + } + } + if (ind < 0) { + ind += size; + } + if (!(ind >= 0 && ind < size)) { + throw std::invalid_argument("vector index out of bounds"); + } + out[ind] = val; + } + return out; +} + +TEST(Put, test_scalar_indices_values) +{ + { + auto x = mk_array({1, 2, 3, 4, 5, 6}, {2, 3}); + auto indices = mk_array({0}); + auto values = mk_array({10}); + put(x, indices, values); // put(x, 0, 10) + check_array(x, {10, 2, 3, 4, 5, 6}, {2, 3}); + } + { + auto x = mk_array({1, 2, 3, 4, 5, 6}, {2, 3}); + auto indices = mk_array({0}); + auto values = mk_array({10, 20, 30}); + put(x, indices, values); // put(x, 0, [10, 20, 30]) + check_array(x, {10, 2, 3, 4, 5, 6}, {2, 3}); + } + { + auto x = mk_array({1, 2, 3, 4, 5, 6}, {2, 3}); + auto indices = mk_array({0}, {1}); + auto values = mk_array({10}); + put(x, indices, values); // put(x, [0], 10) + check_array(x, {10, 2, 3, 4, 5, 6}, {2, 3}); + } + { + auto x = mk_array({1, 2, 3, 4, 5, 6}, {2, 3}); + auto indices = mk_array({0, 1, 2.5, 1}); + auto values = mk_array({10.5}); + put(x, indices, values); // put(x, [0, 1, 2.5, 1], 10) + check_array(x, {10, 10, 10, 4, 5, 6}, {2, 3}); + } +} + +TEST(Put, test_scalar_indices_values_mode) +{ + std::vector mode_list{"wrap", "clip"}; + std::vector> values_list{{10}, {10, 20}}; + std::vector> indices_list{{100}, {-100}}; + + std::vector shape{3, 4, 5}; + auto x_in = mk_seq_vector(shape); + + for (auto indices : indices_list) { + for (auto values : values_list) { + for (auto mode : mode_list) { + auto x = mk_array(x_in, shape); + auto v = mk_array(values); + auto ind = mk_array(indices); + put(x, ind, v, mode); + auto x_gt = put_result(x_in, indices, values, mode); + check_array(x, x_gt, shape); + } + } + } +} + +TEST(Put, test_scalar_arr) +{ + std::vector, std::vector>> values_list{ + {{10}, {}}, {{10}, {1}}, {{10, 20}, {}}}; + std::vector, std::vector>> indices_list{ + {{0}, {}}, {{0}, {1}}, {{-1}, {}}, {{-1}, {1}}}; + std::vector x_in{0}; + for (auto [indices, shape_ind] : indices_list) { + for (auto [values, shape_val] : values_list) { + auto x = mk_array(x_in); + auto v = mk_array(values, shape_val); + auto ind = mk_array(indices, shape_ind); + put(x, ind, v); + auto x_gt = put_result(x_in, indices, values); + check_array(x, x_gt); + } + } +} + +TEST(Put, test_scalar_arr_mode) +{ + std::vector mode_list{"wrap", "clip"}; + std::vector> indices_list{{-1}, {1}, {-1, 0}, {-1, 0, 1, 2}}; + std::vector values{10}; + std::vector x_in{0}; + + for (auto indices : indices_list) { + for (auto mode : mode_list) { + auto x = mk_array(x_in); + auto v = mk_array(values); + auto ind = mk_array(indices); + put(x, ind, v, mode); + auto x_gt = put_result(x_in, indices, values, mode); + check_array(x, x_gt); + } + } +} + +TEST(Put, test_indices_type_convert) +{ + std::vector shape{3, 4, 5}; + auto x_in = mk_seq_vector(shape); + auto values = mk_seq_vector({6}, 10); + std::vector indices{-2, 2}; + auto x = mk_array(x_in); + auto v = mk_array(values); + auto ind = mk_array(indices); + put(x, ind, v); + auto x_gt = put_result(x_in, indices, values); + check_array(x, x_gt); +} + +TEST(Put, test_indices_array_and_shape_array) +{ + std::vector, std::vector>> INDICES_VALUES_SHAPE{ + {{0}, {1}}, + {{2}, {0}}, + {{2}, {1}}, + {{2}, {2}}, + {{2}, {3}}, + {{2}, {2, 1}}, + {{2}, {3, 2}}, + {{2, 2}, {1}}, + {{2, 2}, {4}}, + {{2, 2}, {5}}, + {{2, 2}, {2, 1}}, + {{2, 2}, {2, 2}}, + {{2, 2}, {3, 3}}, + }; + std::vector> shape_list{{2, 3, 4}, {6}}; + + for (auto shape : shape_list) { + for (auto [shape_ind, shape_val] : INDICES_VALUES_SHAPE) { + auto x_in = mk_seq_vector(shape); + auto indices = mk_seq_vector(shape_ind); + auto values = mk_seq_vector(shape_val, 10); + auto x = mk_array(x_in, shape); + auto v = mk_array(values, shape_val); + auto ind = mk_array(indices, shape_ind); + put(x, ind, v); + auto x_gt = put_result(x_in, indices, values); + check_array(x, x_gt, shape); + } + } +} + +TEST(Put, test_ndim_default_mode) +{ + std::vector shape, shape_ind, shape_val; + + for (int ndim = 1; ndim <= LEGATE_MAX_DIM; ++ndim) { + shape.push_back(5); + shape_ind.push_back(3); + shape_val.push_back(2); + auto x_in = mk_seq_vector(shape); + auto indices = mk_seq_vector(shape_ind); + auto values = mk_seq_vector(shape_val, 10); + auto x = mk_array(x_in, shape); + auto v = mk_array(values, shape_val); + auto ind = mk_array(indices, shape_ind); + put(x, ind, v); + auto x_gt = put_result(x_in, indices, values); + check_array(x, x_gt, shape); + } +} + +TEST(Put, test_ndim_mode) +{ + std::vector mode_list{"wrap", "clip"}; + std::vector, std::vector>> INDICES = { + {{1, 2, 3.2, 100}, {}}, {{2, 1, 3, 100}, {2, 2}}, {{1}, {1}}, {{100}, {1}}}; + + std::vector shape, shape_val; + for (int ndim = 1; ndim <= LEGATE_MAX_DIM; ++ndim) { + shape.push_back(5); + shape_val.push_back(2); + auto x_in = mk_seq_vector(shape); + auto values = mk_seq_vector(shape_val, 10); + for (auto [indices, shape_ind] : INDICES) { + for (auto mode : mode_list) { + auto x = mk_array(x_in, shape); + auto v = mk_array(values, shape_val); + auto ind = mk_array(indices, shape_ind); + put(x, ind, v, mode); + auto x_gt = put_result(x_in, indices, values, mode); + check_array(x, x_gt, shape); + } + } + } +} + +TEST(Put, test_empty_array) +{ + auto x = mk_array({}, {0}); + auto values = mk_array({10}); + auto indices = mk_array({}, {0}); + put(x, indices, values); + check_array(x, {}, {0}); +} + +TEST(Put, test_indices_out_of_bound) +{ + std::vector> indices_list{{-13}, {12}, {0, 1, 12}}; + std::vector shape{3, 4}; + auto x_in = mk_seq_vector(shape); + auto x = mk_array(x_in, shape); + auto v = mk_array({10}); + for (auto indices : indices_list) { + auto ind = mk_array(indices); + EXPECT_ANY_THROW(put(x, ind, v)); + EXPECT_ANY_THROW(put(x, ind, v, "raise")); + } +} + +TEST(Put, test_indices_out_of_bound_arr_is_scalar) +{ + std::vector, std::vector>> indices_list = { + {{-2}, {}}, {{1}, {}}, {{1}, {1}}}; + auto x = mk_array({0}); + auto v = mk_array({10}); + for (auto [indices, shape_ind] : indices_list) { + auto ind = mk_array(indices, shape_ind); + EXPECT_ANY_THROW(put(x, ind, v)); + EXPECT_ANY_THROW(put(x, ind, v, "raise")); + } +} + +TEST(Put, test_invalid_mode) +{ + std::string mode = "unknown"; + std::vector shape{3, 4}; + auto x_in = mk_seq_vector(shape); + auto x = mk_array(x_in, shape); + auto ind = mk_array({0}); + auto v = mk_array({10}); + EXPECT_THROW(put(x, ind, v, mode), std::invalid_argument); +} + +} // namespace From 36d54b2d4db32ee7fd3674f5e774d694986ed024 Mon Sep 17 00:00:00 2001 From: Parag Kulkarni Date: Thu, 4 Jan 2024 08:39:45 +0530 Subject: [PATCH 137/462] Use libhwloc from legate.core.internal (#89) * - Use libhwloc from legate.core.internal - Update the legate.core.internal version * Update version in CMakelists.txt * Check: Cuda 12.2 * Remove legate.core package defn. --- .github/workflows/gh-build.yml | 4 ++-- .github/workflows/gh-test.yml | 2 +- CMakeLists.txt | 2 +- cmake/versions.json | 9 +-------- 4 files changed, 5 insertions(+), 12 deletions(-) diff --git a/.github/workflows/gh-build.yml b/.github/workflows/gh-build.yml index 94f576bd85..22aa5c4048 100644 --- a/.github/workflows/gh-build.yml +++ b/.github/workflows/gh-build.yml @@ -30,9 +30,9 @@ jobs: options: -u root image: "${{ inputs.image }}" env: - CUDA_VERSION: "12.0" + CUDA_VERSION: "12.2" CUDA_VERSION_MAJOR: "12" - CUDA_VERSION_MINOR: "0" + CUDA_VERSION_MINOR: "2" SCCACHE_REGION: "us-east-2" SCCACHE_BUCKET: "rapids-sccache-devs" SCCACHE_S3_KEY_PREFIX: "legate-cunumeric-dev" diff --git a/.github/workflows/gh-test.yml b/.github/workflows/gh-test.yml index c3cf25118a..5389dc8147 100644 --- a/.github/workflows/gh-test.yml +++ b/.github/workflows/gh-test.yml @@ -59,7 +59,7 @@ jobs: run: | export DEBIAN_FRONTEND=noninteractive && \ sudo apt-get update && \ - sudo apt-get install -y numactl libhwloc15 + sudo apt-get install -y numactl - name: Checkout ${{ inputs.repos-name }} uses: actions/checkout@v3 diff --git a/CMakeLists.txt b/CMakeLists.txt index 5f1c6a8581..18b121f505 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -55,7 +55,7 @@ include(rapids-cuda) include(rapids-export) include(rapids-find) -set(cunumeric_version 23.11.00) +set(cunumeric_version 24.01.00) # For now we want the optimization flags to match on both normal make and cmake # builds so we override the cmake defaults here for release, this changes diff --git a/cmake/versions.json b/cmake/versions.json index c7386f7093..57fc119438 100644 --- a/cmake/versions.json +++ b/cmake/versions.json @@ -1,18 +1,11 @@ { "packages" : { - "legate_core" : { - "version": "23.11.00", - "git_url" : "https://github.com/nv-legate/legate.core.git", - "git_shallow": false, - "always_download": false, - "git_tag" : "8997f997be02936304b3ac23fe785f1de7a3424b" - }, "legate_core_internal" : { "version": "24.01.00", "git_url" : "https://github.com/nv-legate/legate.core.internal.git", "git_shallow": false, "always_download": false, - "git_tag" : "e8cd1c4fed1902f51f96c93755661c62d2a062ba" + "git_tag" : "5fca57bc6235bf57b205d8b789c29163e813b72c" } } } From 9c30114a8f90944aa3196bf65ca7f7b9b004cfc1 Mon Sep 17 00:00:00 2001 From: Yimo Jiang Date: Thu, 4 Jan 2024 13:43:17 +0800 Subject: [PATCH 138/462] Port cunumeric diagonal/trace to cpp (#48) * Port cunumeric.diagonal() to cpp * Port cunumeric.trace to cpp --- src/cunumeric/ndarray.cc | 238 +++++++++++++++++++ src/cunumeric/ndarray.h | 16 ++ src/cunumeric/operators.cc | 30 +++ src/cunumeric/operators.h | 14 ++ tests/cpp/integration/test_diagonal.cc | 311 +++++++++++++++++++++++++ 5 files changed, 609 insertions(+) create mode 100644 tests/cpp/integration/test_diagonal.cc diff --git a/src/cunumeric/ndarray.cc b/src/cunumeric/ndarray.cc index e3e8419a58..8f2f7cbeee 100644 --- a/src/cunumeric/ndarray.cc +++ b/src/cunumeric/ndarray.cc @@ -927,6 +927,201 @@ void NDArray::convert(NDArray rhs, int32_t nan_op) auto p_rhs = task.add_input(rhs_s); task.add_scalar_arg(legate::Scalar(nan_op)); task.add_constraint(legate::align(p_lhs, p_rhs)); + runtime->submit(std::move(task)); +} + +NDArray NDArray::diag_helper(int32_t offset, + std::vector axes, + bool extract, + bool trace, + const std::optional& type, + std::optional out) +{ + auto runtime = CuNumericRuntime::get_runtime(); + + if (dim() <= 1) { + throw std::invalid_argument("diag_helper is implemented for dim > 1"); + } + if (out.has_value() && !trace) { + throw std::invalid_argument("diag_helper supports out only for trace=true"); + } + if (type.has_value() && !trace) { + throw std::invalid_argument("diag_helper supports type only for trace=true"); + } + + auto N = axes.size(); + assert(N > 0); + + std::set s_axes(axes.begin(), axes.end()); + if (N != s_axes.size()) { + throw std::invalid_argument("axes passed to diag_helper should be all different"); + } + if (dim() < N) { + throw std::invalid_argument("Dimension of input array shouldn't be less than number of axes"); + } + std::vector transpose_axes; + for (int32_t ax = 0; ax < dim(); ++ax) { + if (std::find(axes.begin(), axes.end(), ax) == axes.end()) { + transpose_axes.push_back(ax); + } + } + + NDArray a = runtime->create_array(store_.type()); + + size_t diag_size; + if (N == 2) { + if (offset >= 0) { + transpose_axes.push_back(axes[0]); + transpose_axes.push_back(axes[1]); + } else { + transpose_axes.push_back(axes[1]); + transpose_axes.push_back(axes[0]); + offset = -offset; + } + a = transpose(transpose_axes); + if (offset >= a.shape()[dim() - 1]) { + throw std::invalid_argument("'offset' for diag or diagonal must be in range"); + } + diag_size = std::max((size_t)0, std::min(a.shape().end()[-2], a.shape().end()[-1] - offset)); + } else if (N > 2) { + if (offset != 0) { + throw std::invalid_argument("offset supported for number of axes == 2"); + } + auto sort_axes = [this](size_t i, size_t j) { return (shape()[i] < shape()[j]); }; + std::sort(axes.begin(), axes.end(), sort_axes); + std::reverse(axes.begin(), axes.end()); + transpose_axes.insert(transpose_axes.end(), axes.begin(), axes.end()); + a = transpose(transpose_axes); + diag_size = a.shape()[a.dim() - 1]; + } else if (N < 2) { + throw std::invalid_argument("number of axes should be more than 1"); + } + + std::vector tr_shape; + for (size_t i = 0; i < a.dim() - N; ++i) { + tr_shape.push_back(a.shape()[i]); + } + + std::vector out_shape; + if (trace) { + if (N != 2) { + throw std::invalid_argument("exactly 2 axes should be passed to trace"); + } + if (dim() == 2) { + out_shape = {1}; + } else if (dim() > 2) { + out_shape = tr_shape; + } else { + throw std::invalid_argument("dimension of the array for trace operation should be >=2"); + } + } else { + tr_shape.push_back(diag_size); + out_shape = tr_shape; + } + + if (out && out->shape() != out_shape) { + throw std::invalid_argument("output array has the wrong shape"); + } + + legate::Type res_type; + if (type) { + res_type = type.value(); + } else if (out) { + res_type = out->type(); + } else { + res_type = store_.type(); + } + + if (store_.type() != res_type) { + a = a.as_type(res_type); + } + + if (out && out->type() == res_type) { + out->diag_task(a, offset, N, extract, trace); + return out.value(); + } else { + auto res = runtime->create_array(out_shape, res_type); + res.diag_task(a, offset, N, extract, trace); + if (out) { + out->assign(res); + } + return res; + } +} + +void NDArray::diag_task(NDArray rhs, int32_t offset, int32_t naxes, bool extract, bool trace) +{ + auto runtime = CuNumericRuntime::get_runtime(); + + legate::LogicalStore diag = get_store(); + legate::LogicalStore matrix = get_store(); + + auto zero = legate::type_dispatch(type().code(), generate_zero_fn{}); + fill(zero, false); + + if (extract) { + diag = store_; + matrix = rhs.store_; + auto ndim = rhs.dim(); + auto start = matrix.dim() - naxes; + auto n = ndim - 1; + if (naxes == 2) { + if (offset > 0) { + matrix = matrix.slice(start + 1, legate::Slice(offset)); + } + if (trace) { + if (ndim == 2) { + diag = diag.promote(0, matrix.extents().data()[0]); + diag = diag.project(1, 0).promote(1, matrix.extents().data()[1]); + } else { + for (int32_t i = 0; i < naxes; ++i) { + diag = diag.promote(start, matrix.extents().data().end()[-i - 1]); + } + } + } else { + if (matrix.extents().data()[n - 1] < matrix.extents().data()[n]) { + diag = diag.promote(start + 1, matrix.extents().data()[ndim - 1]); + } else { + diag = diag.promote(start, matrix.extents().data()[ndim - 2]); + } + } + } else { + for (int32_t i = 1; i < naxes; ++i) { + diag = diag.promote(start, matrix.extents().data().end()[-i - 1]); + } + } + } else { + matrix = store_; + diag = rhs.store_; + auto ndim = dim(); + if (offset > 0) { + matrix = matrix.slice(1, slice(offset)); + } else if (offset < 0) { + matrix = matrix.slice(0, slice(-offset)); + } + + if (shape()[0] < shape()[1]) { + diag = diag.promote(1, shape()[1]); + } else { + diag = diag.promote(0, shape()[0]); + } + } + + auto task = runtime->create_task(CuNumericOpCode::CUNUMERIC_DIAG); + if (extract) { + auto redop = runtime->get_reduction_op(UnaryRedCode::SUM); + auto p_diag = task.add_reduction(diag, redop); + auto p_matrix = task.add_input(matrix); + task.add_constraint(legate::align(p_matrix, p_diag)); + } else { + auto p_matrix = task.add_output(matrix); + auto p_diag = task.add_input(diag); + task.add_input(matrix, p_matrix); + task.add_constraint(legate::align(p_diag, p_matrix)); + } + + task.add_scalar_arg(legate::Scalar(naxes)); + task.add_scalar_arg(legate::Scalar(extract)); runtime->submit(std::move(task)); } @@ -1092,6 +1287,49 @@ NDArray NDArray::clip_indices(Scalar const& min, Scalar const& max) return out; } +NDArray NDArray::diagonal(int32_t offset, + std::optional axis1, + std::optional axis2, + std::optional extract) +{ + if (dim() == 1) { + if (extract.has_value() && extract.value()) { + throw std::invalid_argument("extract can be true only for dim >=2"); + } + if (axis1 || axis2) { + throw std::invalid_argument("Axes shouldn't be specified when getting diagonal for 1D array"); + } + auto runtime = CuNumericRuntime::get_runtime(); + auto m = shape()[0] + std::abs(offset); + auto res = runtime->create_array({m, m}, store_.type()); + res.diag_task(NDArray(std::move(store_)), offset, 0, false, false); + return res; + } else { + if (!axis1) { + axis1 = 0; + } + if (!axis2) { + axis2 = 1; + } + if (!extract.has_value()) { + extract = true; + } + return diag_helper(offset, {axis1.value(), axis2.value()}, extract.value()); + } +} + +NDArray NDArray::trace(int32_t offset, + int32_t axis1, + int32_t axis2, + std::optional type, + std::optional out) +{ + if (dim() < 2) { + throw std::invalid_argument("trace operation can't be called on a array with DIM<2"); + } + return diag_helper(offset, {axis1, axis2}, true, true, type, out); +} + legate::LogicalStore NDArray::get_store() { return store_; } legate::LogicalStore NDArray::broadcast(const std::vector& shape, diff --git a/src/cunumeric/ndarray.h b/src/cunumeric/ndarray.h index 7dc96bf8f1..2ad9ff3a89 100644 --- a/src/cunumeric/ndarray.h +++ b/src/cunumeric/ndarray.h @@ -97,6 +97,15 @@ class NDArray { std::optional initial = std::nullopt, std::optional where = std::nullopt); void put(NDArray indices, NDArray values, std::string mode = "raise"); + NDArray diagonal(int32_t offset = 0, + std::optional axis1 = std::nullopt, + std::optional axis2 = std::nullopt, + std::optional extract = std::nullopt); + NDArray trace(int32_t offset = 0, + int32_t axis1 = 0, + int32_t axis2 = 1, + std::optional type = std::nullopt, + std::optional out = std::nullopt); public: NDArray as_type(const legate::Type& type); @@ -134,6 +143,13 @@ class NDArray { std::optional initial = std::nullopt, std::optional where = std::nullopt); void flip(NDArray rhs, std::optional> axis); + void diag_task(NDArray rhs, int32_t offset, int32_t naxes, bool extract, bool trace); + NDArray diag_helper(int32_t offset, + std::vector axes, + bool extract = true, + bool trace = false, + const std::optional& type = std::nullopt, + std::optional out = std::nullopt); public: static legate::Library get_library(); diff --git a/src/cunumeric/operators.cc b/src/cunumeric/operators.cc index e789b9ba69..9b21bb765a 100644 --- a/src/cunumeric/operators.cc +++ b/src/cunumeric/operators.cc @@ -473,6 +473,26 @@ NDArray moveaxis(NDArray a, std::vector source, std::vector de return a.transpose(order); } +NDArray diag(NDArray v, int32_t k) +{ + int32_t dim = v.dim(); + switch (dim) { + case 0: throw std::invalid_argument("Input must be 1- or 2-d"); + case 1: return v.diagonal(k, 0, 1, false); + case 2: return v.diagonal(k, 0, 1, true); + default: throw std::invalid_argument("diag requires 1- or 2-D array, use diagonal instead"); + } +} + +NDArray diagonal(NDArray a, + int32_t offset, + std::optional axis1, + std::optional axis2, + std::optional extract) +{ + return a.diagonal(offset, axis1, axis2, extract); +} + NDArray flip(NDArray input, std::optional> axis) { return input.flip(axis); } void put(NDArray& a, NDArray indices, NDArray values, std::string mode) @@ -480,4 +500,14 @@ void put(NDArray& a, NDArray indices, NDArray values, std::string mode) a.put(indices, values, mode); } +NDArray trace(NDArray a, + int32_t offset, + int32_t axis1, + int32_t axis2, + std::optional type, + std::optional out) +{ + return a.trace(offset, axis1, axis2, type, out); +} + } // namespace cunumeric diff --git a/src/cunumeric/operators.h b/src/cunumeric/operators.h index 01ae4bf4c5..14863f1852 100644 --- a/src/cunumeric/operators.h +++ b/src/cunumeric/operators.h @@ -122,5 +122,19 @@ std::vector normalize_axis_vector(std::vector axis, int32_t ndim, bool allow_duplicate = false); +NDArray diag(NDArray v, int32_t k = 0); + +NDArray diagonal(NDArray a, + int32_t offset = 0, + std::optional axis1 = std::nullopt, + std::optional axis2 = std::nullopt, + std::optional extract = std::nullopt); + +NDArray trace(NDArray a, + int32_t offset = 0, + int32_t axis1 = 0, + int32_t axis2 = 1, + std::optional type = std::nullopt, + std::optional out = std::nullopt); } // namespace cunumeric #include "cunumeric/operators.inl" diff --git a/tests/cpp/integration/test_diagonal.cc b/tests/cpp/integration/test_diagonal.cc new file mode 100644 index 0000000000..45f7b5409c --- /dev/null +++ b/tests/cpp/integration/test_diagonal.cc @@ -0,0 +1,311 @@ +/* Copyright 2023 NVIDIA Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +#include +#include "cunumeric.h" +#include "util.inl" + +template +void diagonal_test(std::array input, + std::array exp, + std::vector in_shape, + int32_t offset = 0, + int32_t axis1 = 0, + int32_t axis2 = 1, + bool extract = true) +{ + auto a_input = cunumeric::zeros(in_shape); + assign_values_to_array(a_input, input.data(), input.size()); + auto a_output = cunumeric::diagonal(a_input, offset, axis1, axis2, extract); + check_array_eq(a_output, exp.data(), exp.size()); +} + +TEST(Diagonal, Singleton) +{ + const size_t in_size = 6; + const size_t in_dim = 1; + const size_t exp_size = 36; + const size_t exp_dim = 2; + std::array input = {1.3, 2, 3.6, 4, 5, 6}; + std::array exp = {1.3, 0., 0., 0., 0., 0., 0., 2., 0., 0., 0., 0., + 0., 0., 3.6, 0., 0., 0., 0., 0., 0., 4., 0., 0., + 0., 0., 0., 0., 5., 0., 0., 0., 0., 0., 0., 6.}; + std::vector in_shape = {6}; + + auto a_input = cunumeric::zeros(in_shape); + assign_values_to_array(a_input, input.data(), input.size()); + auto a_output = cunumeric::diagonal(a_input, 0, std::nullopt, std::nullopt, false); + check_array_eq(a_output, exp.data(), exp.size()); +} + +TEST(Diagonal, SingletonExtract) +{ + std::vector in_shape = {6}; + + auto a_input = cunumeric::zeros(in_shape); + EXPECT_THROW(cunumeric::diagonal(a_input, 0, std::nullopt, std::nullopt, true), + std::invalid_argument); +} + +TEST(Diagonal, SingletonAxes) +{ + std::vector in_shape = {6}; + + auto a_input = cunumeric::zeros(in_shape); + EXPECT_THROW(cunumeric::diagonal(a_input, 0, 0, 1, false), std::invalid_argument); +} + +TEST(Diagonal, Defaults) +{ + const size_t in_size = 9; + const size_t in_dim = 2; + const size_t exp_size = 3; + const size_t exp_dim = 1; + std::array input = {9, 7, 0.5, 1.3, 2, 3.6, 4, 5, 6}; + std::array exp = {9, 2, 6}; + std::vector in_shape = {3, 3}; + + auto a_input = cunumeric::zeros(in_shape); + assign_values_to_array(a_input, input.data(), input.size()); + auto a_output = cunumeric::diagonal(a_input); + check_array_eq(a_output, exp.data(), exp.size()); +} + +TEST(Diagonal, EmptyArray) +{ + const size_t exp_size = 0; + const size_t exp_dim = 2; + std::array exp = {}; + + auto a_input = cunumeric::array({0}, legate::int32()); + auto a_output = cunumeric::diagonal(a_input, 0, std::nullopt, std::nullopt, false); + check_array_eq(a_output, exp.data(), exp.size()); +} + +TEST(Diagonal, Simple) +{ + const size_t in_size = 9; + const size_t in_dim = 2; + const size_t exp_size = 3; + const size_t exp_dim = 1; + std::array input = {9, 7, 0.5, 1.3, 2, 3.6, 4, 5, 6}; + std::array exp = {9, 2, 6}; + std::vector in_shape = {3, 3}; + + diagonal_test(input, exp, in_shape); +} + +TEST(Diagonal, Offset) +{ + const size_t in_size = 9; + const size_t in_dim = 3; + const size_t exp_size = 1; + const size_t exp_dim = 2; + std::array input = {9, 7, 0.5, 1.3, 2, 3.6, 4, 5, 6}; + std::array exp = {0.5}; + std::vector in_shape = {3, 3, 1}; + + diagonal_test(input, exp, in_shape, 2); +} + +TEST(Diagonal, Axes) +{ + const size_t in_size = 6; + const size_t in_dim = 2; + const size_t exp_size = 2; + const size_t exp_dim = 1; + std::array input = {1.3, 2, 3.6, 4, 5, 6}; + std::array exp = {1.3, 5}; + std::vector in_shape = {2, 3}; + + diagonal_test(input, exp, in_shape, 0, 1, 0); +} + +TEST(Diagonal, InvalidAxes) +{ + const size_t in_size = 6; + const size_t in_dim = 2; + std::array input = {1.3, 2, 3.6, 4, 5, 6}; + std::vector in_shape = {2, 3}; + + auto a_input = cunumeric::zeros(in_shape); + assign_values_to_array(a_input, input.data(), input.size()); + EXPECT_THROW(cunumeric::diagonal(a_input, 0, 2, 6, true), std::invalid_argument); + EXPECT_THROW(cunumeric::diagonal(a_input, 0, 1, 1, true), std::invalid_argument); +} + +TEST(Diagonal, InvalidOffset) +{ + const size_t in_size = 6; + const size_t in_dim = 2; + std::array input = {1.3, 2, 3.6, 4, 5, 6}; + std::vector in_shape = {2, 3}; + + auto a_input = cunumeric::zeros(in_shape); + assign_values_to_array(a_input, input.data(), input.size()); + EXPECT_THROW(cunumeric::diagonal(a_input, 3), std::invalid_argument); +} + +TEST(Diagonal, IntArray) +{ + const size_t in_size = 6; + const size_t in_dim = 2; + const size_t exp_size = 2; + const size_t exp_dim = 1; + std::array input = {1, 2, 3, 4, 5, 6}; + std::array exp = {1, 5}; + std::vector in_shape = {2, 3}; + + auto a_input = cunumeric::zeros(in_shape, legate::int32()); + assign_values_to_array(a_input, input.data(), input.size()); + auto a_output = cunumeric::diagonal(a_input); + check_array_eq(a_output, exp.data(), exp.size()); +} + +TEST(Diagonal, MaxDim) +{ + // Only test int type for max dim + const size_t in_size = 16; + std::array input = {14, 10, 3, 12, 5, 13, 2, 4, 16, 8, 9, 7, 6, 11, 1, 15}; +#if LEGATE_MAX_DIM >= 4 + diagonal_test(input, {14, 6, 10, 11, 3, 1, 12, 15}, {2, 2, 1, 4}); +#endif + +#if LEGATE_MAX_DIM >= 5 + diagonal_test(input, {14, 10, 3, 12, 5, 13, 2, 4}, {1, 2, 2, 1, 4}); +#endif + +#if LEGATE_MAX_DIM >= 6 + diagonal_test(input, {14, 10, 3, 12, 5, 13, 2, 4}, {2, 1, 1, 2, 2, 2}); +#endif + +#if LEGATE_MAX_DIM >= 7 + diagonal_test(input, {14, 6, 10, 11, 3, 1, 12, 15}, {2, 2, 1, 1, 2, 1, 2}); +#endif +} + +template +void trace_test(std::array input, + std::array exp, + std::vector in_shape, + int32_t offset = 0, + int32_t axis1 = 0, + int32_t axis2 = 1, + std::optional type = std::nullopt, + std::optional out = std::nullopt) +{ + auto a_input = cunumeric::zeros(in_shape); + assign_values_to_array(a_input, input.data(), input.size()); + auto a_output = cunumeric::trace(a_input, offset, axis1, axis2, type, out); + check_array_eq(a_output, exp.data(), exp.size()); +} + +TEST(Trace, Simple) +{ + const size_t in_size = 8; + const size_t in_dim = 3; + const size_t exp_size = 4; + const size_t exp_dim = 1; + std::array input = {9, 7, 0.5, 1.3, 2, 3.6, 4, 5}; + std::array exp = {9, 7, 0.5, 1.3}; + std::vector in_shape = {2, 1, 4}; + trace_test(input, exp, in_shape); +} + +TEST(Trace, Offset) +{ + const size_t in_size = 8; + const size_t in_dim = 3; + const size_t exp_size = 1; + const size_t exp_dim = 1; + std::array input = {9, 7, 0.5, 1.3, 2, 3.6, 4, 5}; + std::array exp = {5.5}; + std::vector in_shape = {2, 4, 1}; + trace_test(input, exp, in_shape, 2); +} + +TEST(Trace, Axes) +{ + const size_t in_size = 8; + const size_t in_dim = 3; + const size_t exp_size = 2; + const size_t exp_dim = 1; + std::array input = {9, 7, 0.5, 1.3, 2, 3.6, 4, 5}; + std::array exp = {9, 2}; + std::vector in_shape = {2, 4, 1}; + trace_test(input, exp, in_shape, 0, 2, 1); +} + +TEST(Trace, IntArray) +{ + const size_t in_size = 8; + const size_t in_dim = 3; + const size_t exp_size = 1; + const size_t exp_dim = 1; + std::array input = {9, 7, 5, 3, 2, 6, 4, 1}; + std::array exp = {15}; + std::vector in_shape = {2, 4, 1}; + auto a_input = cunumeric::zeros(in_shape, legate::int32()); + assign_values_to_array(a_input, input.data(), input.size()); + auto a_output = cunumeric::trace(a_input, 0, 0, 1); + check_array_eq(a_output, exp.data(), exp.size()); +} + +TEST(Trace, TypeInt) +{ + const size_t in_size = 8; + const size_t in_dim = 3; + const size_t exp_size = 1; + const size_t exp_dim = 1; + std::array input = {9, 7, 0.5, 1.3, 2, 3.6, 4, 5}; + std::array exp = {12}; + std::vector in_shape = {2, 4, 1}; + + auto a_input = cunumeric::zeros(in_shape); + assign_values_to_array(a_input, input.data(), input.size()); + auto a_output = cunumeric::trace(a_input, 0, 0, 1, legate::int32()); + check_array_eq(a_output, exp.data(), exp.size()); +} + +TEST(Trace, OutType) +{ + const size_t in_size = 8; + const size_t in_dim = 3; + const size_t exp_size = 1; + const size_t exp_dim = 1; + std::array input = {9, 7, 0.5, 1.3, 2, 3.6, 4, 5}; + std::array exp = {12}; + std::vector in_shape = {2, 4, 1}; + std::vector out_shape = {1}; + + auto a_input = cunumeric::zeros(in_shape); + auto a_output = cunumeric::zeros(out_shape, legate::int32()); + assign_values_to_array(a_input, input.data(), input.size()); + cunumeric::trace(a_input, 0, 0, 1, std::nullopt, a_output); + check_array_eq(a_output, exp.data(), exp.size()); +} + +TEST(Trace, InvalidArray) +{ + const size_t in_size = 8; + const size_t in_dim = 1; + std::array input = {9, 7, 0.5, 1.3, 2, 3.6, 4, 5}; + std::vector in_shape = {8}; + + auto a_input = cunumeric::zeros(in_shape); + assign_values_to_array(a_input, input.data(), input.size()); + EXPECT_THROW(cunumeric::trace(a_input), std::invalid_argument); +} From ddf54c4bddf5f142bf42f5f6ed73a51b756d8160 Mon Sep 17 00:00:00 2001 From: Shijie Chen Date: Fri, 5 Jan 2024 12:26:33 +0800 Subject: [PATCH 139/462] Added tests for cunumeric.tril and cunumeric.triu (#91) * Added tests for cunumeric.tril and cunumeric.triu * updated copyright info --- tests/cpp/integration/test_trilu.cc | 123 ++++++++++++++++++++++++++++ 1 file changed, 123 insertions(+) create mode 100644 tests/cpp/integration/test_trilu.cc diff --git a/tests/cpp/integration/test_trilu.cc b/tests/cpp/integration/test_trilu.cc new file mode 100644 index 0000000000..2d944106fe --- /dev/null +++ b/tests/cpp/integration/test_trilu.cc @@ -0,0 +1,123 @@ +/* Copyright 2024 NVIDIA Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +#include "common_utils.h" +#include + +using namespace cunumeric; + +namespace { + +template +std::tuple, std::vector> trilu_result(std::vector a, + std::vector shape, + int32_t k = 0, + bool lower = true) +{ + if (shape.empty()) { + throw std::invalid_argument("Array must be at least 1-D"); + } + + size_t size = std::accumulate(shape.begin(), shape.end(), size_t(1), std::multiplies()); + if (a.size() != size) { + throw std::invalid_argument("size and shape mismatch"); + } + + bool is_1D = false; + if (shape.size() == 1) { + is_1D = true; + shape.emplace_back(shape[0]); + size = shape[0] * shape[0]; + } + + if (a.size() == 0) { + return {a, shape}; + } + + int32_t ndim = static_cast(shape.size()); + size_t N = shape[ndim - 2]; + size_t M = shape[ndim - 1]; + std::vector out; + for (size_t idx = 0; idx < size; ++idx) { + int32_t j = static_cast(idx % M); + int32_t i = static_cast((idx / M) % N); + bool flag = lower ? j <= i + k : j >= i + k; + if (flag) { + if (is_1D) { + out.emplace_back(a[j]); + } else { + out.emplace_back(a[idx]); + } + } else { + out.emplace_back(0); + } + } + return {out, shape}; +} + +template +void _test(std::string func, std::vector x_in, std::vector shape, int32_t k) +{ + bool lower = (func == "tril") ? true : false; + auto num_f = (func == "tril") ? tril : triu; + auto x = mk_array(x_in, shape); + auto x_out = num_f(x, k); + auto [x_gt, shape_gt] = trilu_result(x_in, shape, k, lower); + check_array(x_out, x_gt, shape_gt); +} + +TEST(Trilu, test_trilu) +{ + std::vector func_list{"tril", "triu"}; + std::vector k_list{0, -1, 1, -2, 2, -10, 10}; + std::vector> shape_list{ + {0}, {1}, {10}, {1, 10}, {10, 10}, {1, 1, 10}, {1, 10, 10}, {10, 10, 10}}; + for (auto shape : shape_list) { + auto x_int32 = mk_seq_vector(shape, 0, 1); + auto x_float = mk_seq_vector(shape, 0, 1); + for (auto k : k_list) { + for (auto func : func_list) { + _test(func, x_int32, shape, k); + _test(func, x_float, shape, k); + } + } + } +} + +TEST(Trilu, test_ndim) +{ + std::vector func_list{"tril", "triu"}; + std::vector shape; + for (int32_t ndim = 1; ndim <= LEGATE_MAX_DIM; ++ndim) { + shape.push_back(ndim); + for (int32_t k = -ndim; k <= ndim; ++k) { + auto x_in = mk_seq_vector(shape); + for (auto func : func_list) { + _test(func, x_in, shape, k); + } + } + } +} + +class TriluErrors : public ::testing::Test {}; + +TEST_F(TriluErrors, test_m_scalar) +{ + auto x = mk_array({0}); + EXPECT_THROW(tril(x), std::invalid_argument); +} + +} // namespace From d13c54cce7753693b43ff28ecffd9b117aa16122 Mon Sep 17 00:00:00 2001 From: Wonchan Lee Date: Wed, 10 Jan 2024 21:44:27 -0800 Subject: [PATCH 140/462] Clean up the fill operation (#100) * Fill task should be used only when the target store is transformed * Clean up the fill operation --- cunumeric/deferred.py | 5 +++-- src/cunumeric/ndarray.cc | 26 ++++++++++++++----------- src/cunumeric/ndarray.h | 3 +-- src/cunumeric/nullary/fill.h | 1 - src/cunumeric/nullary/fill_template.inl | 21 ++++---------------- src/cunumeric/operators.cc | 6 +++--- 6 files changed, 26 insertions(+), 36 deletions(-) diff --git a/cunumeric/deferred.py b/cunumeric/deferred.py index a5c2d736eb..a3c262d443 100644 --- a/cunumeric/deferred.py +++ b/cunumeric/deferred.py @@ -1390,10 +1390,12 @@ def fft( def _fill(self, value: Any) -> None: assert self.base is not None - if self.dtype.kind != "V": + if not self.base.transformed: # Emit a Legate fill legate_runtime.issue_fill(self.base, value) else: + # Arg reductions would never fill transformed stores + assert self.dtype.kind != "V" # Perform the fill using a task # If this is a fill for an arg value, make sure to pass # the value dtype so that we get it packed correctly @@ -1402,7 +1404,6 @@ def _fill(self, value: Any) -> None: ) task.add_output(self.base) task.add_input(value) - task.add_scalar_arg(True, ty.bool_) task.execute() def fill(self, numpy_array: Any) -> None: diff --git a/src/cunumeric/ndarray.cc b/src/cunumeric/ndarray.cc index 8f2f7cbeee..4f29cf0484 100644 --- a/src/cunumeric/ndarray.cc +++ b/src/cunumeric/ndarray.cc @@ -146,7 +146,7 @@ void NDArray::random(int32_t gen_code) runtime->submit(std::move(task)); } -void NDArray::fill(const Scalar& value, bool argval) +void NDArray::fill(const Scalar& value) { if (size() == 0) { return; @@ -154,13 +154,17 @@ void NDArray::fill(const Scalar& value, bool argval) auto runtime = CuNumericRuntime::get_runtime(); + if (!store_.transformed()) { + legate::Runtime::get_runtime()->issue_fill(store_, value); + return; + } + auto fill_value = runtime->create_scalar_store(value); auto task = runtime->create_task(CuNumericOpCode::CUNUMERIC_FILL); task.add_output(store_); task.add_input(fill_value); - task.add_scalar_arg(legate::Scalar(argval)); runtime->submit(std::move(task)); } @@ -174,7 +178,7 @@ void NDArray::eye(int32_t k) assert(dim() == 2); auto zero = legate::type_dispatch(type().code(), generate_zero_fn{}); - fill(zero, false); + fill(zero); auto runtime = CuNumericRuntime::get_runtime(); @@ -202,7 +206,7 @@ void NDArray::bincount(NDArray rhs, std::optional weights /*=std::nullo } auto zero = legate::type_dispatch(type().code(), generate_zero_fn{}); - fill(zero, false); + fill(zero); auto task = runtime->create_task(CuNumericOpCode::CUNUMERIC_BINCOUNT); legate::ReductionOpKind redop = legate::ReductionOpKind::ADD; @@ -371,10 +375,10 @@ void NDArray::binary_reduction(int32_t op_code, NDArray rhs1, NDArray rhs2) legate::ReductionOpKind redop; if (op_code == static_cast(BinaryOpCode::NOT_EQUAL)) { redop = runtime->get_reduction_op(UnaryRedCode::SUM); - fill(legate::Scalar(false), false); + fill(legate::Scalar(false)); } else { redop = runtime->get_reduction_op(UnaryRedCode::PROD); - fill(legate::Scalar(true), false); + fill(legate::Scalar(true)); } auto task = runtime->create_task(CuNumericOpCode::CUNUMERIC_BINARY_RED); @@ -420,7 +424,7 @@ void NDArray::unary_reduction(int32_t op_code_, NDArray input) auto op_code = static_cast(op_code_); auto identity = runtime->get_reduction_identity(op_code, type()); - fill(identity, false); + fill(identity); auto task = runtime->create_task(CuNumericOpCode::CUNUMERIC_SCALAR_UNARY_RED); @@ -443,7 +447,7 @@ void NDArray::dot(NDArray rhs1, NDArray rhs2) auto runtime = CuNumericRuntime::get_runtime(); auto identity = runtime->get_reduction_identity(UnaryRedCode::SUM, type()); - fill(identity, false); + fill(identity); assert(dim() == 2 && rhs1.dim() == 2 && rhs2.dim() == 2); @@ -820,10 +824,10 @@ void NDArray::unary_reduction(int32_t op, auto op_code = static_cast(op); if (initial.has_value()) { - lhs_array.fill(initial.value(), false); + lhs_array.fill(initial.value()); } else { auto identity = runtime->get_reduction_identity(op_code, lhs_array.type()); - lhs_array.fill(identity, false); + lhs_array.fill(identity); } auto is_where = where.has_value(); @@ -1057,7 +1061,7 @@ void NDArray::diag_task(NDArray rhs, int32_t offset, int32_t naxes, bool extract legate::LogicalStore matrix = get_store(); auto zero = legate::type_dispatch(type().code(), generate_zero_fn{}); - fill(zero, false); + fill(zero); if (extract) { diag = store_; diff --git a/src/cunumeric/ndarray.h b/src/cunumeric/ndarray.h index 2ad9ff3a89..c1a69eaebf 100644 --- a/src/cunumeric/ndarray.h +++ b/src/cunumeric/ndarray.h @@ -68,12 +68,11 @@ class NDArray { public: void random(int32_t gen_code); - void fill(const Scalar& value, bool argval); + void fill(const Scalar& value); void binary_op(int32_t op_code, NDArray rhs1, NDArray rhs2); void binary_reduction(int32_t op_code, NDArray rhs1, NDArray rhs2); void unary_op(int32_t op_code, NDArray input); void unary_reduction(int32_t op_code, NDArray input); - void fill(NDArray fill_value); void eye(int32_t k); void trilu(NDArray rhs, int32_t k, bool lower); void dot(NDArray rhs1, NDArray rhs2); diff --git a/src/cunumeric/nullary/fill.h b/src/cunumeric/nullary/fill.h index cad2bf5203..aff7329226 100644 --- a/src/cunumeric/nullary/fill.h +++ b/src/cunumeric/nullary/fill.h @@ -23,7 +23,6 @@ namespace cunumeric { struct FillArgs { legate::PhysicalStore out; legate::PhysicalStore fill_value; - bool is_argval; }; class FillTask : public CuNumericTask { diff --git a/src/cunumeric/nullary/fill_template.inl b/src/cunumeric/nullary/fill_template.inl index 6e86acebe2..de9e234312 100644 --- a/src/cunumeric/nullary/fill_template.inl +++ b/src/cunumeric/nullary/fill_template.inl @@ -59,29 +59,16 @@ struct FillImpl { template void operator()(FillArgs& args) const { - if (args.is_argval) { - using VAL = Argval>; - fill(args); - } else { - using VAL = type_of; - fill(args); - } + using VAL = type_of; + fill(args); } }; template static void fill_template(TaskContext& context) { - FillArgs args{context.output(0), context.input(0), context.scalar(0).value()}; - Type::Code code{args.out.code()}; - if (Type::Code::STRUCT == code) { -#ifdef DEBUG_CUNUMERIC - assert(args.is_argval); -#endif - auto field_type = args.out.type().as_struct_type().field_type(1); - code = field_type.code(); - } - double_dispatch(std::max(args.out.dim(), 1), code, FillImpl{}, args); + FillArgs args{context.output(0), context.input(0)}; + double_dispatch(std::max(args.out.dim(), 1), args.out.code(), FillImpl{}, args); } } // namespace cunumeric diff --git a/src/cunumeric/operators.cc b/src/cunumeric/operators.cc index 9b21bb765a..2fa565359c 100644 --- a/src/cunumeric/operators.cc +++ b/src/cunumeric/operators.cc @@ -153,7 +153,7 @@ NDArray full(std::vector shape, const Scalar& value) { auto runtime = CuNumericRuntime::get_runtime(); auto out = runtime->create_array(std::move(shape), value.type()); - out.fill(value, false); + out.fill(value); return out; } @@ -326,7 +326,7 @@ NDArray array_equal(NDArray input0, NDArray input1) auto dst = CuNumericRuntime::get_runtime()->create_array({1}, legate::bool_()); if (input0.shape() != input1.shape()) { - dst.fill(legate::Scalar(false), false); + dst.fill(legate::Scalar(false)); } else { dst.binary_reduction(static_cast(BinaryOpCode::EQUAL), input0, input1); } @@ -345,7 +345,7 @@ NDArray create_window(int64_t M, WindowOpCode op_code, std::vector args) } else if (M == 1) { auto out = runtime->create_array({1}, std::move(type)); auto one = legate::Scalar(static_cast(1)); - out.fill(one, false); + out.fill(one); return out; } auto out = runtime->create_array({static_cast(M)}, std::move(type)); From eb9fe04cdd86d67f391c48b4f11108dc5a39ee5b Mon Sep 17 00:00:00 2001 From: Sandeep Datta <128171450+sandeepd-nv@users.noreply.github.com> Date: Thu, 11 Jan 2024 20:11:22 +0530 Subject: [PATCH 141/462] Partial implementation towards unification of Linux and Mac CI (#72) Actual Mac CI still pending but it be an incremental change over this PR. --- .github/actions/download-artifacts/action.yml | 42 ++- .github/workflows/ci-gh.yml | 21 +- .github/workflows/gh-build-and-test.yml | 165 ++++++----- .github/workflows/gh-build.yml | 109 ++++--- .../workflows/gh-test-within-container.yml | 85 ++++++ .github/workflows/gh-test.yml | 94 ------ .../scripts/build-cunumeric | 72 +++++ .../scripts/build-cunumeric-all | 44 --- .../scripts/build-cunumeric-conda | 16 +- .../scripts/build-cunumeric-cpp | 8 +- .../scripts/build-cunumeric-wheel | 9 +- continuous_integration/scripts/conda-utils | 99 +++++++ continuous_integration/scripts/entrypoint | 44 +-- continuous_integration/scripts/make-conda-env | 41 +++ continuous_integration/scripts/setup-utils | 278 ++++++++++++++++++ continuous_integration/scripts/test-cunumeric | 21 +- continuous_integration/scripts/vault-s3-init | 109 +++++++ 17 files changed, 907 insertions(+), 350 deletions(-) create mode 100644 .github/workflows/gh-test-within-container.yml delete mode 100644 .github/workflows/gh-test.yml create mode 100755 continuous_integration/scripts/build-cunumeric delete mode 100755 continuous_integration/scripts/build-cunumeric-all create mode 100755 continuous_integration/scripts/conda-utils create mode 100755 continuous_integration/scripts/make-conda-env create mode 100755 continuous_integration/scripts/setup-utils create mode 100755 continuous_integration/scripts/vault-s3-init diff --git a/.github/actions/download-artifacts/action.yml b/.github/actions/download-artifacts/action.yml index a1e614ea06..d9929e02c2 100644 --- a/.github/actions/download-artifacts/action.yml +++ b/.github/actions/download-artifacts/action.yml @@ -1,13 +1,13 @@ -name: setup-legate-conda +name: download-artifacts description: Download dependencies (artifacts) inputs: - device: {type: string, required: true} + target-device: {type: string, required: true} git_sha: {type: string, required: true} - repos-name: {type: string, required: true} inter_repos_ro_access_token: {required: true} - + platform: {type: string, required: true} + dest-dir: {type: string, required: true} runs: using: composite @@ -17,38 +17,30 @@ runs: name: Cache conda artifacts uses: actions/cache@v3 with: - key: "nv-legate/legate.core.internal@${{ inputs.git_sha }}-${{ inputs.device }}" - path: .artifacts + key: "nv-legate/${{ inputs.platform }}-legate.core.internal@${{ inputs.git_sha }}-${{ inputs.target-device }}" + path: ${{ inputs.dest-dir }} - if: steps.cache.outputs.cache-hit != 'true' name: Download conda artifacts uses: dawidd6/action-download-artifact@v2 with: github_token: ${{ inputs.inter_repos_ro_access_token }} - path: .artifacts-dl + path: ${{ inputs.dest-dir }} repo: nv-legate/legate.core.internal check_artifacts: true - search_artifacts: true + # search_artifacts: true commit: ${{ inputs.git_sha }} workflow_conclusion: success workflow: "ci-gh.yml" - name: "legate.core.internal-${{ inputs.device }}-release-gcc-[0-9a-z]{40}" - name_is_regexp: true + name: "${{ inputs.platform }}-legate.core.internal-${{ inputs.target-device }}-release-gcc-${{ inputs.git_sha }}" + # name_is_regexp: true + skip_unpack: true + if_no_artifact_found: fail + allow_forks: false - if: steps.cache.outputs.cache-hit != 'true' - name: Move conda artifacts into cached dir - shell: bash --noprofile --norc -xeo pipefail {0} - run: | - mkdir -p .artifacts; - find .artifacts-dl/linux-legate.core.internal-${{ inputs.device }}-release-gcc-*/ \ - -maxdepth 2 -type d -name legate_core -exec mv {} .artifacts/ \; - find .artifacts-dl/linux-legate.core.internal-${{ inputs.device }}-release-gcc-*/ \ - -maxdepth 2 -type f -name "environment*.yaml" -exec mv {} .artifacts/ \; - - - name: Copy and change cache dir ownership - shell: bash --noprofile --norc -xeo pipefail {0} + name: Unpack artifact + shell: bash --noprofile --norc -xeuo pipefail {0} run: | - # Copy and change directory ownership - cp -ar .artifacts /home/coder/.artifacts; - chown -R coder:coder /home/coder/.artifacts; - ls -R /home/coder/.artifacts + cd ${{ inputs.dest-dir }} + unzip *.zip diff --git a/.github/workflows/ci-gh.yml b/.github/workflows/ci-gh.yml index c08aa48304..a8e8a6bef3 100644 --- a/.github/workflows/ci-gh.yml +++ b/.github/workflows/ci-gh.yml @@ -1,4 +1,4 @@ -name: Build cunumeric.internal on GH +name: Build and test concurrency: group: ci-build-and-test-on-${{ github.event_name }}-from-${{ github.ref_name }} @@ -8,26 +8,19 @@ on: push: branches: - "pull-request/[0-9]+" - - "*branch-*" + - "cpp-branch-*" jobs: build-and-test: strategy: fail-fast: false matrix: - include: - - device: "gpu" - image: "rapidsai/devcontainers:23.06-cpp-mambaforge-ubuntu22.04" - - - device: "cpu" - image: "rapidsai/devcontainers:23.06-cpp-mambaforge-ubuntu22.04" + target-device: + - gpu + - cpu uses: ./.github/workflows/gh-build-and-test.yml with: - image: ${{ matrix.image }} - device: ${{ matrix.device }} - repos-name: ${{ github.event.repository.name }} + target-device: ${{ matrix.target-device }} + platform: linux secrets: inherit - - - \ No newline at end of file diff --git a/.github/workflows/gh-build-and-test.yml b/.github/workflows/gh-build-and-test.yml index bb9037d05c..88653f194f 100644 --- a/.github/workflows/gh-build-and-test.yml +++ b/.github/workflows/gh-build-and-test.yml @@ -1,97 +1,112 @@ on: workflow_call: inputs: - image: + platform: type: string required: true - device: + target-device: type: string required: true - repos-name: - required: true - type: string jobs: + setup-build: + name: Setup build + runs-on: ubuntu-latest + outputs: + runner_type: ${{ steps.set_runner.outputs.runner_type }} + steps: + - id: set_runner + run: | + if [ "${{ inputs.platform }}" = "linux" ]; then + if [ "${{ github.repository_owner }}" = "nv-legate" ]; then + echo "runner_type=linux-amd64-cpu32" >> $GITHUB_OUTPUT + else + echo "runner_type=ubuntu-latest" >> $GITHUB_OUTPUT + fi + elif [ "${{ inputs.platform }}" = "mac" ]; then + echo "runner_type=macos-latest" >> $GITHUB_OUTPUT + fi + build: - name: "Build ${{ inputs.repos-name }} (with ${{ inputs.device }} legate) on GH" + needs: setup-build + name: "Build (${{ inputs.platform }}, ${{ inputs.target-device }})" uses: ./.github/workflows/gh-build.yml with: - device: ${{ inputs.device }} - repos-name: ${{ inputs.repos-name }} - image: ${{ inputs.image }} - runs-on: ${{ github.repository_owner == 'nv-legate' && 'linux-amd64-cpu32' || 'ubuntu-latest' }} + target-device: ${{ inputs.target-device }} + runs-on: ${{ needs.setup-build.outputs.runner_type }} + build-type: ci + use-container: ${{ inputs.platform == 'linux' }} + platform: ${{ inputs.platform }} secrets: inherit - test: + setup-test: + name: Setup test needs: - build - strategy: - fail-fast: false - matrix: - include: - - name: 1 CPU test - options: test --cpus 1 --unit --debug - runner: ${{ inputs.device == 'gpu' && 'linux-amd64-gpu-v100-latest-1' || 'linux-amd64-cpu4' }} - has-gpu: false - enabled: true - - - name: 2 CPUs test - options: test --cpus 2 --debug - runner: ${{ inputs.device == 'gpu' && 'linux-amd64-gpu-v100-latest-1' || 'linux-amd64-cpu8' }} - has-gpu: false - enabled: true - - - name: GPU test - options: test --use cuda --gpus 1 --debug - runner: linux-amd64-gpu-v100-latest-1 - has-gpu: true - enabled: ${{ inputs.device == 'gpu' }} + runs-on: ubuntu-latest + outputs: + matrix: ${{ steps.set-matrix.outputs.matrix }} + steps: + - id: set-matrix + run: | + set -xeuo pipefail + MATRIX_JSON='{"include": [' + RUNNERS=( + 'linux-amd64-gpu-v100-latest-1:gpu:gpu:linux' 'linux-amd64-2gpu:gpu:2gpu:linux' 'linux-amd64-cpu32:cpu:cpu:linux' 'macos-latest:cpu:cpu:mac') + TEST_CONFIGS=( + '1 CPU test:test --cpus 1 --unit --debug:cpu' + '1 CPU test:test --cpus 1 --unit --debug:gpu' + '2 CPU test:test --cpus 2 --debug:cpu' + '2 CPU test:test --cpus 2 --debug:gpu' + 'GPU test:test --use cuda --gpus 1 --debug:gpu' + '2 GPU test:test --use cuda --gpus 2 --debug:2gpu' + 'OpenMP test:test --use openmp --omps 1 --ompthreads 2 --debug:gpu' + 'OpenMP test:test --use openmp --omps 1 --ompthreads 2 --debug:cpu' + '2 NUMA OpenMPs test:test --use openmp --omps 2 --ompthreads 2 --numamem 2048 --debug:gpu' + '2 NUMA OpenMPs test:test --use openmp --omps 2 --ompthreads 2 --numamem 2048 --debug:cpu' + 'Eager execution test:test --use eager --debug:gpu' + 'Eager execution test:test --use eager --debug:cpu' + 'mypy:mypy:cpu' + 'Documentation:docs:cpu' + ) + for RUNNER in "${RUNNERS[@]}"; do + IFS=':' read -ra RUNNER_INFO <<< "$RUNNER" + RUNNER_NAME=${RUNNER_INFO[0]} + RUNNER_TYPE=${RUNNER_INFO[1]} + RUNNER_DEVICE=${RUNNER_INFO[2]} + RUNNER_PLATFORM=${RUNNER_INFO[3]} + if [[ "$RUNNER_TYPE" == "${{ inputs.target-device }}" && "$RUNNER_PLATFORM" == "${{ inputs.platform }}" ]]; then + for TEST_CONFIG in "${TEST_CONFIGS[@]}"; do + IFS=':' read -ra CONFIG_INFO <<< "$TEST_CONFIG" + TEST_NAME=${CONFIG_INFO[0]} + TEST_OPTIONS=${CONFIG_INFO[1]} + TEST_TARGET_DEVICE=${CONFIG_INFO[2]} + if [[ "$TEST_TARGET_DEVICE" == "$RUNNER_DEVICE" ]]; then + MATRIX_JSON+="{\"runner\": {\"name\": \"$RUNNER_NAME\", \"type\": \"$RUNNER_TYPE\", \"platform\": \"$RUNNER_PLATFORM\"}, \"test-config\": {\"name\": \"$TEST_NAME\", \"test-options\": \"$TEST_OPTIONS\"}}," + fi + done + fi + done + MATRIX_JSON=$(echo "$MATRIX_JSON" | sed 's/,$//') # Remove the trailing comma + MATRIX_JSON+=']}' + echo "matrix=$MATRIX_JSON" >> $GITHUB_OUTPUT - - name: 2 GPUs test - options: test --use cuda --gpus 2 --debug - runner: linux-amd64-2gpu - has-gpu: true - enabled: ${{ inputs.device == 'gpu' }} - - - name: OpenMP test - options: test --use openmp --omps 1 --ompthreads 2 --debug - runner: ${{ inputs.device == 'gpu' && 'linux-amd64-gpu-v100-latest-1' || 'linux-amd64-32cpu' }} - has-gpu: ${{ inputs.device == 'gpu' }} - enabled: true - - - name: 2 NUMA OpenMPs test - options: test --use openmp --omps 2 --ompthreads 2 --numamem 2048 --debug - runner: ${{ inputs.device == 'gpu' && 'linux-amd64-gpu-v100-latest-1' || 'linux-amd64-32cpu' }} - has-gpu: ${{ inputs.device == 'gpu' }} - enabled: true - - - name: Eager execution test - options: test --use eager --debug - runner: ${{ inputs.device == 'gpu' && 'linux-amd64-gpu-v100-latest-1' || 'linux-amd64-cpu4' }} - has-gpu: ${{ inputs.device == 'gpu' }} - enabled: true - - - name: mypy - options: mypy - runner: linux-amd64-cpu4 - has-gpu: false - enabled: true + test: + needs: + - setup-test + name: ${{ matrix.test-config.name }} (${{ inputs.platform }}, ${{ inputs.target-device }}) - - name: documentation - options: docs - runner: linux-amd64-32cpu - has-gpu: false - enabled: ${{ inputs.device == 'gpu' }} + strategy: + fail-fast: false + matrix: ${{fromJson(needs.setup-test.outputs.matrix)}} uses: - ./.github/workflows/gh-test.yml + ./.github/workflows/gh-test-within-container.yml with: - name: ${{ matrix.name }} - device: ${{ inputs.device }} - image: ${{ inputs.image }} - repos-name: ${{ inputs.repos-name }} - runs-on: ${{ matrix.runner }} - has-gpu: ${{ matrix.has-gpu }} - test-options: ${{ matrix.options }} - enabled: ${{ matrix.enabled }} + name: ${{ matrix.test-config.name }} + target-device: ${{ inputs.target-device }} + runs-on: ${{ matrix.runner.name }} + has-gpu: ${{ matrix.runner.type == 'gpu' }} + test-options: ${{ matrix.test-config.test-options }} + platform: ${{ inputs.platform }} diff --git a/.github/workflows/gh-build.yml b/.github/workflows/gh-build.yml index 22aa5c4048..2f4e8cf224 100644 --- a/.github/workflows/gh-build.yml +++ b/.github/workflows/gh-build.yml @@ -3,85 +3,79 @@ name: Build on: workflow_call: inputs: - image: - type: string + target-device: required: true - device: + type: string + runs-on: required: true type: string - repos-name: + build-type: required: true type: string - runs-on: + description: One of ci / release + use-container: required: true - type: string - + type: boolean + platform: + required: true + type: string +env: + ARTIFACTS_DIR: "${{ github.workspace }}/artifacts" + ARTIFACT_NAME: "${{ inputs.platform }}-${{ github.event.repository.name }}-${{ inputs.target-device }}-${{ github.sha }}" + USE_CUDA: ${{ (inputs.target-device == 'cpu' && 'OFF') || 'ON' }} + DOCKER_IMAGE: "condaforge/miniforge3:latest" jobs: build: - name: build-${{ inputs.device }}-sub-workflow + name: Build (${{ inputs.platform }}, ${{ inputs.target-device }}, ${{ inputs.build-type }}, Use container=${{ inputs.use-container }} ) permissions: id-token: write # This is required for configure-aws-credentials contents: read # This is required for actions/checkout - + runs-on: ${{ inputs.runs-on }} - container: - options: -u root - image: "${{ inputs.image }}" - env: - CUDA_VERSION: "12.2" - CUDA_VERSION_MAJOR: "12" - CUDA_VERSION_MINOR: "2" - SCCACHE_REGION: "us-east-2" - SCCACHE_BUCKET: "rapids-sccache-devs" - SCCACHE_S3_KEY_PREFIX: "legate-cunumeric-dev" - USE_CUDA: "${{ inputs.device == 'gpu' && 'ON' || 'OFF' }}" - GH_TOKEN: "${{ env.GH_TOKEN }}" - GITHUB_TOKEN: "${{ env.GITHUB_TOKEN }}" - VAULT_HOST: "${{ github.repository_owner != 'nv-legate' && 'https://vault.ops.k8s.rapids.ai' || '' }}" defaults: run: - shell: su coder {0} - working-directory: /home/coder + shell: bash --noprofile --norc -xeuo pipefail {0} steps: - - name: Checkout ${{ inputs.repos-name }} (= this repo) + - name: Checkout ${{ github.event.repository.name }} uses: actions/checkout@v3 with: fetch-depth: 0 - path: cunumeric - persist-credentials: false + path: cunumeric.internal - name: Dump environment run: | env - - name: Copy source folder + - name: Dump CPU info run: | - set -x - pwd - cp -r $GITHUB_WORKSPACE/cunumeric . - chown -R coder:coder cunumeric; - ls -R + cat /proc/cpuinfo - name: Copy .gitconfig - run: cp ~/cunumeric/continuous_integration/dot-gitconfig ~/.gitconfig + run: cp cunumeric.internal/continuous_integration/dot-gitconfig ~/.gitconfig - id: legate_core_info name: Read legate.core SHA shell: bash --noprofile --norc -xeo pipefail {0} run: | - git_tag="$(jq -r '.packages.legate_core_internal.git_tag' cunumeric/cmake/versions.json)"; + git_tag="$(jq -r '.packages.legate_core_internal.git_tag' cunumeric.internal/cmake/versions.json)"; echo "git_tag=$git_tag" | tee -a "${GITHUB_OUTPUT}"; - name: Download dependencies (artifacts) - uses: ./cunumeric/.github/actions/download-artifacts + uses: ./cunumeric.internal/.github/actions/download-artifacts with: - device: "${{ inputs.device }}" + target-device: "${{ inputs.target-device }}" git_sha: "${{ steps.legate_core_info.outputs.git_tag }}" - repos-name: "${{ inputs.repos-name }}" inter_repos_ro_access_token: ${{ secrets.NV_LEGATE_INTER_REPOS_ACCESS_RO }} + platform: ${{ inputs.platform }} + dest-dir: ${{ env.ARTIFACTS_DIR }} + + - name: Display structure of downloaded artifacts + run: | + pwd + ls -lahR $ARTIFACTS_DIR - if: github.repository_owner == 'nv-legate' name: Get AWS credentials for sccache bucket @@ -91,15 +85,38 @@ jobs: role-duration-seconds: 28800 # 8 hours role-to-assume: arn:aws:iam::279114543810:role/gha-oidc-nv-legate - - name: Build ${{ inputs.repos-name }} + - if: ${{ inputs.use-container }} + name: Build (in container) + run: | + cd cunumeric.internal + + docker run \ + -e AWS_REGION \ + -e AWS_SESSION_TOKEN \ + -e AWS_ACCESS_KEY_ID \ + -e AWS_SECRET_ACCESS_KEY \ + -e GITHUB_TOKEN \ + -e ARTIFACTS_DIR \ + -e USE_CUDA \ + -v "$(pwd):$(pwd)" \ + -v "$ARTIFACTS_DIR:$(pwd)/../artifacts" \ + --rm "${{ env.DOCKER_IMAGE }}" \ + /bin/bash -c "cd $(pwd) && continuous_integration/scripts/entrypoint build-cunumeric ${{ inputs.build-type}} ${{ inputs.target-device }}" + + - if: ${{ !inputs.use-container }} + name: Build (without container) + run: | + cd cunumeric.internal + + continuous_integration/scripts/entrypoint build-cunumeric ${{ inputs.build-type}} + + - name: Display structure of the artifacts folder (post build) run: | - export PATH="/home/coder/cunumeric/continuous_integration/scripts:$PATH" - build-cunumeric-all + sudo chown -R $(whoami) ${{ env.ARTIFACTS_DIR }} + ls -lahR ${{ env.ARTIFACTS_DIR }} - name: Upload build artifacts uses: actions/upload-artifact@v3 with: - name: "${{ inputs.repos-name }}-${{ inputs.device }}-${{ github.sha }}" - path: | - /tmp/out - /tmp/conda-build + name: ${{ env.ARTIFACT_NAME }} + path: ${{ env.ARTIFACTS_DIR }} diff --git a/.github/workflows/gh-test-within-container.yml b/.github/workflows/gh-test-within-container.yml new file mode 100644 index 0000000000..ea6057ea40 --- /dev/null +++ b/.github/workflows/gh-test-within-container.yml @@ -0,0 +1,85 @@ +name: Test + +on: + workflow_call: + inputs: + name: + required: true + type: string + target-device: + required: true + type: string + runs-on: + required: true + type: string + has-gpu: + required: true + type: boolean + description: "The runner has GPU(s)." + test-options: + required: true + type: string + platform: + required: true + type: string + +env: + ARTIFACT_NAME: "${{ inputs.platform }}-${{ github.event.repository.name }}-${{ inputs.target-device }}-${{ github.sha }}" + ARTIFACTS_DIR: "${{ github.workspace }}/artifacts" + +jobs: + test: + name: ${{ inputs.name }} + if: github.repository_owner == 'nv-legate' + runs-on: ${{ inputs.runs-on }} + + container: + options: -u root --security-opt seccomp=unconfined + image: condaforge/miniforge3:latest + env: + NVIDIA_VISIBLE_DEVICES: ${{ env.NVIDIA_VISIBLE_DEVICES }} + + defaults: + run: + shell: bash --noprofile --norc -xeuo pipefail {0} + + steps: + - if: inputs.has-gpu + name: Run nvidia-smi to make sure GPU is working + run: nvidia-smi + + # This step is needed to work around a github runner bug which causes the value of github.workspace to change over time. + - name: Dump relevant enviornment variables + run: | + pwd + echo "ARTIFACTS_DIR=$ARTIFACTS_DIR" >> $GITHUB_ENV + echo ARTIFACT_NAME=$ARTIFACT_NAME + echo Platform: ${{ inputs.platform }} + + - name: Dump CPU info + run: | + cat /proc/cpuinfo + + - name: Checkout ${{ github.event.repository.name }} + uses: actions/checkout@v3 + with: + fetch-depth: 0 + path: "${{ github.workspace }}/cunumeric.internal" + persist-credentials: false + + - name: Download build artifacts + uses: actions/download-artifact@v3 + with: + name: ${{ env.ARTIFACT_NAME }} + path: ${{ env.ARTIFACTS_DIR }} + + - name: Display structure of downloaded artifacts + run: | + pwd + ls -lahR $ARTIFACTS_DIR + + - name: Run ${{ github.event.repository.name }} test / analysis + run: | + cd cunumeric.internal + + continuous_integration/scripts/entrypoint test-cunumeric ${{ inputs.test-options }} diff --git a/.github/workflows/gh-test.yml b/.github/workflows/gh-test.yml deleted file mode 100644 index 5389dc8147..0000000000 --- a/.github/workflows/gh-test.yml +++ /dev/null @@ -1,94 +0,0 @@ -name: Test cunumeric on GH - -on: - workflow_call: - inputs: - name: - required: true - type: string - image: - type: string - required: true - device: - required: true - type: string - repos-name: - required: true - type: string - runs-on: - required: true - type: string - has-gpu: - required: true - type: boolean - description: "The runner has GPU(s)." - test-options: - required: true - type: string - enabled: - required: true - type: boolean - -env: - build_artifact_name: "${{ inputs.repos-name }}-${{ inputs.device }}-${{ github.sha }}" - -jobs: - test: - name: ${{ inputs.name }} - if: inputs.enabled && github.repository_owner == 'nv-legate' - runs-on: ${{ inputs.runs-on }} - - container: - options: -u root --security-opt seccomp=unconfined - image: "${{ inputs.image }}" - env: - # CUDA_VERSION: "${{ inputs.CUDA }}" - NVIDIA_VISIBLE_DEVICES: ${{ env.NVIDIA_VISIBLE_DEVICES }} - - defaults: - run: - shell: su coder {0} - working-directory: /home/coder - - steps: - - if: inputs.has-gpu - name: Run nvidia-smi to make sure GPU is working - run: nvidia-smi - - - name: Install numactl - run: | - export DEBIAN_FRONTEND=noninteractive && \ - sudo apt-get update && \ - sudo apt-get install -y numactl - - - name: Checkout ${{ inputs.repos-name }} - uses: actions/checkout@v3 - with: - fetch-depth: 0 - path: cunumeric - persist-credentials: false - - - name: Copy source folder - run: | - set -x - pwd - cp -r $GITHUB_WORKSPACE/cunumeric . - chown -R coder:coder cunumeric; - ls -R - - - name: Download build artifacts - uses: actions/download-artifact@v3 - with: - name: ${{ env.build_artifact_name }} - path: /home/coder/.artifacts - - - name: Run ${{ inputs.repos-name }} test / analysis - shell: su coder {0} - run: | - set -x - sudo chown -R coder:coder /home/coder/.artifacts - - export PATH="/home/coder/cunumeric/continuous_integration/scripts:$PATH" - - set -eo pipefail - test-cunumeric ${{ inputs.test-options }} diff --git a/continuous_integration/scripts/build-cunumeric b/continuous_integration/scripts/build-cunumeric new file mode 100755 index 0000000000..6a908c2a18 --- /dev/null +++ b/continuous_integration/scripts/build-cunumeric @@ -0,0 +1,72 @@ +#!/usr/bin/env bash + +copy_ci_artifacts() { + set -xeuo pipefail; + echo Copying CI artifacts + + mkdir -p "$ARTIFACTS_DIR" + + cp -r /tmp/out "$ARTIFACTS_DIR" + cp -r /tmp/conda-build "$ARTIFACTS_DIR" +} + +build_cunumeric_ci() { + set -xeuo pipefail; + + printf "\n\n\n\n********* BUILDING CUNUMERIC CPP *********\n" + build-cunumeric-cpp; + + printf "\n\n\n\n********* BUILDING CUNUMERIC WHEEL *********\n" + build-cunumeric-wheel; + + printf "\n\n\n\n********* BUILDING CUNUMERIC CONDA *********\n" + build-cunumeric-conda; + + copy_ci_artifacts; +} + +build_cunumeric() { + set -x; + + . conda-utils; + . setup-utils; + + export BUILD_TYPE=$1 + + set -xeuo pipefail; + + set_base_defs; + + cd "$PREBUILD_DIR" + + setup_build_env; + init_sccache; + cd "$REPO_DIR"; + + rm -rf "$REPO_DIR/build"; + + make-conda-env "$BUILD_TYPE"; + + activate_conda_env; + conda_info; + + case "$BUILD_TYPE" in + ci) build_cunumeric_ci;; + # release) build_cunumeric_release;; + *) return 1;; + esac +} + +build_cunumeric_fake() { + set -xeuo pipefail; + + mkdir -p /tmp/out /tmp/conda-build/legate_core /tmp/conda-build/cunumeric + touch /tmp/out/legate-core-23.11.00-dummy.tar.bz2 + touch /tmp/conda-build/legate_core/dummy.txt + touch /tmp/conda-build/cunumeric/dummy.txt + + copy_ci_artifacts +} + + +(build_cunumeric "$@"); diff --git a/continuous_integration/scripts/build-cunumeric-all b/continuous_integration/scripts/build-cunumeric-all deleted file mode 100755 index bcdbf62ec5..0000000000 --- a/continuous_integration/scripts/build-cunumeric-all +++ /dev/null @@ -1,44 +0,0 @@ -#!/usr/bin/env bash - -setup_env() { - yaml_file=$(find ~/.artifacts -name "environment*.yaml" | head -n 1) - - [ "${USE_CUDA:-}" = "ON" ] && - echo " - libcublas-dev" >> "${yaml_file}" && - echo " - libcufft-dev" >> "${yaml_file}" && - echo " - libcurand-dev" >> "${yaml_file}" && - echo " - libcusolver-dev" >> "${yaml_file}"; - - echo "YAML file..." - cat "${yaml_file}" - - mkdir -p /tmp/out; - - cp "${yaml_file}" /tmp/out - - mamba env create -n legate -f "$yaml_file" - - mamba uninstall -yn legate numpy - - mamba install -yn legate -c ~/.artifacts/legate_core -c conda-forge -c nvidia legate-core - - mamba activate legate -} - -build_cunumeric_all() { - set -xeo pipefail - - setup_env; - cd ~/cunumeric; - conda info; - - set -xeuo pipefail; - printf "\n\n\n\n********* BUILDING CUNUMERIC CPP *********\n" - build-cunumeric-cpp; - printf "\n\n\n\n********* BUILDING CUNUMERIC WHEEL *********\n" - build-cunumeric-wheel; - printf "\n\n\n\n********* BUILDING CUNUMERIC CONDA *********\n" - build-cunumeric-conda; -} - -(build_cunumeric_all "$@"); diff --git a/continuous_integration/scripts/build-cunumeric-conda b/continuous_integration/scripts/build-cunumeric-conda index c328b0f051..c26c82543d 100755 --- a/continuous_integration/scripts/build-cunumeric-conda +++ b/continuous_integration/scripts/build-cunumeric-conda @@ -10,7 +10,7 @@ build_cunumeric_conda_package() { fi mkdir -p /tmp/conda-build /tmp/out - cp -r ~/.artifacts/legate_core /tmp/conda-build/ + cp -r "${ARTIFACTS_DIR}/conda-build/legate_core" /tmp/conda-build/ local conda_build_args=(); conda_build_args+=(--override-channels); @@ -37,7 +37,7 @@ build_cunumeric_conda_package() { # Synthesize new cunumeric conda-build build.sh script - cat < ~/cunumeric/conda/conda-build/conda_build_config.yaml + cat < "${REPO_DIR}/conda/conda-build/conda_build_config.yaml" gpu_enabled: - "${GPU_ENABLED}" @@ -60,10 +60,10 @@ gpu_enabled: - ${GPU_ENABLED} package_version: - - "$(git -C ~/cunumeric describe --abbrev=0 --tags | sed 's/[a-zA-Z]//g' | cut -d '.' -f -2).00" + - "$(git -C "${REPO_DIR}" describe --abbrev=0 --tags | sed 's/[a-zA-Z]//g' | cut -d '.' -f -2).00" EOF - cat <<"EOF" > ~/cunumeric/conda/conda-build/build.sh + cat <<"EOF" > "${REPO_DIR}/conda/conda-build/build.sh" # Install cunumeric C++ libs tar -C "$PREFIX" --exclude="*.a" --strip-components=1 -xvf /tmp/out/cunumeric-*-Linux.tar.gz; @@ -71,14 +71,14 @@ tar -C "$PREFIX" --exclude="*.a" --strip-components=1 -xvf /tmp/out/cunumeric-*- pip install --no-deps --root / --prefix "$PREFIX" /tmp/out/cunumeric-*.whl; EOF - git -C ~/cunumeric add .; - git -C ~/cunumeric commit --allow-empty --allow-empty-message -n -m ""; + git -C "${REPO_DIR}" add .; + git -C "${REPO_DIR}" commit --allow-empty --allow-empty-message -n -m ""; # Build cuNumeric conda package CUDA=${CUDA_VERSION_MAJOR}.${CUDA_VERSION_MINOR} \ - conda mambabuild ${conda_build_args[@]} ~/cunumeric/conda/conda-build; + conda mambabuild ${conda_build_args[@]} "${REPO_DIR}/conda/conda-build"; - git -C ~/cunumeric reset --hard HEAD~1; + git -C "${REPO_DIR}" reset --hard HEAD~1; cp /tmp/conda-build/cunumeric/linux-64/cunumeric-*.tar.bz2 /tmp/out/; diff --git a/continuous_integration/scripts/build-cunumeric-cpp b/continuous_integration/scripts/build-cunumeric-cpp index fd08ceac2f..e608ec385d 100755 --- a/continuous_integration/scripts/build-cunumeric-cpp +++ b/continuous_integration/scripts/build-cunumeric-cpp @@ -6,23 +6,23 @@ build_cunumeric_cpp() { # Build + package cuNumeric C++ libs local cmake_args=(${CMAKE_ARGS:-}); cmake_args+=(-DBUILD_SHARED_LIBS=ON); - cmake_args+=(-DBUILD_MARCH=${BUILD_MARCH:-haswell}); + cmake_args+=(-DBUILD_MARCH=${BUILD_MARCH}); cmake_args+=(-DCMAKE_BUILD_TYPE=Release); cmake_args+=(-DCMAKE_CUDA_ARCHITECTURES=RAPIDS); cmake_args+=(-DCMAKE_BUILD_PARALLEL_LEVEL=${JOBS:-$(nproc --ignore=1)}); cmake_args+=(${@}); - cmake -S ~/cunumeric -B ~/cunumeric/build ${cmake_args[@]} -GNinja; + cmake -S "${REPO_DIR}" -B "${REPO_DIR}/build" ${cmake_args[@]} -GNinja; sccache --show-stats; - time cmake --build ~/cunumeric/build --verbose --parallel ${JOBS:-$(nproc --ignore=1)}; + time cmake --build "${REPO_DIR}/build" --verbose --parallel ${JOBS:-$(nproc --ignore=1)}; sccache --show-stats; ( mkdir -p /tmp/out; - cd ~/cunumeric/build; + cd "${REPO_DIR}/build"; cpack -G TGZ; cp ./*-Linux.tar.gz /tmp/out/; ); diff --git a/continuous_integration/scripts/build-cunumeric-wheel b/continuous_integration/scripts/build-cunumeric-wheel index 5f55136eb5..93ae353118 100755 --- a/continuous_integration/scripts/build-cunumeric-wheel +++ b/continuous_integration/scripts/build-cunumeric-wheel @@ -15,11 +15,16 @@ build_cunumeric_wheel() { local cmake_args=(${CMAKE_ARGS:-}); cmake_args+=("-DFIND_CUNUMERIC_CPP=ON"); - cmake_args+=("-Dcunumeric_ROOT=$HOME/cunumeric/build"); + + pwd + echo $REPO_DIR + ls -lahR $REPO_DIR + + cmake_args+=("-Dcunumeric_ROOT=$REPO_DIR/build"); # Build + package cuNumeric Python wheel CMAKE_ARGS="${cmake_args[@]}" \ - pip wheel ${pip_args[@]} ~/cunumeric; + pip wheel ${pip_args[@]} "${REPO_DIR}"; { set +x; } 2>/dev/null; } diff --git a/continuous_integration/scripts/conda-utils b/continuous_integration/scripts/conda-utils new file mode 100755 index 0000000000..c5f411a668 --- /dev/null +++ b/continuous_integration/scripts/conda-utils @@ -0,0 +1,99 @@ +#!/usr/bin/env bash + +make_conda_env_from_yaml() { + mamba env create -n "${CONDA_ENV}" -f "${yaml_file}" --force; +} + +generate_yaml_file() { + UCX_PKG=ucx + [[ "${UCX_ENABLED:-}" = "OFF" ]] && UCX_PKG=no-ucx + + if [[ "$USE_CUDA" == "OFF" ]]; then + yaml_file="$(\ + $REPO_DIR/scripts/generate-conda-envs.py \ + --os "$OS_SHORT_NAME" \ + --compilers \ + --python ${PYTHON_VERSION} \ + --openmpi \ + --${UCX_PKG} \ + | head -n1 | cut -d' ' -f3 \ + )" + else + local cuda_version="${CUDA_VERSION:-${CUDA_VERSION_MAJOR}.${CUDA_VERSION_MINOR}}"; + cuda_version="$(echo "${cuda_version}" | cut -d'.' -f3 --complement)"; + + yaml_file="$( \ + $REPO_DIR/scripts/generate-conda-envs.py \ + --os "$OS_SHORT_NAME" \ + --compilers \ + --ctk ${cuda_version} \ + --python ${PYTHON_VERSION} \ + --openmpi \ + --${UCX_PKG} \ + | head -n1 | cut -d' ' -f3 \ + )" + fi + + $SED -i -re "s/legate-test/${CONDA_ENV}/g" "${yaml_file}"; + echo " - boa" >> "${yaml_file}"; + + if [[ "$LEGATE_CORE_CMAKE_PRESET" == "debug-sanitizer-gcc" ]]; then + echo " - libsanitizer <=${MAX_LIBSANITIZER_VERSION}" >> "${yaml_file}"; + fi + + mkdir -p /tmp/out/ + cp "${yaml_file}" /tmp/out/ + mkdir -p /tmp/env_yaml + cp "${yaml_file}" /tmp/env_yaml +} + +find_yaml_file() { + pattern="/tmp/env_yaml/*.yaml"; + files=( ${pattern} ); + yaml_file="${files[0]}"; + + if [[ -z "${yaml_file:-}" ]] || [[ ! -f "${yaml_file}" ]]; then + return 1; + fi + + return 0; +} + +get_yaml_and_make_conda_env() { + set -xe; + + local yaml_file=""; + + generate_yaml_file; + + echo YAML file: "${yaml_file}" + cat "${yaml_file}"; + + make_conda_env_from_yaml; +} + +install_legate_core_with_war() { + # WAR: legate-core depends on a different version of numpy than what is already installed. + # The correct version will be installed when legate-core is installed below. + # See github issue: https://github.com/nv-legate/legate.core/issues/812 + mamba uninstall -y -n "${CONDA_ENV}" numpy; + + mamba install -y -n "${CONDA_ENV}" -c nvidia -c conda-forge -c $ARTIFACTS_DIR/conda-build/legate_core legate-core; +} + +activate_conda_env() { + set +xu + eval "$(conda shell.bash hook)" + conda activate ${CONDA_ENV}; + set -xu +} + +conda_info() { + set +x + conda info + set -x +} + +make_release_env() { + mamba create -y -n "${CONDA_ENV}" -c conda-forge boa +} diff --git a/continuous_integration/scripts/entrypoint b/continuous_integration/scripts/entrypoint index 298fc1c7a1..621e7b13ca 100755 --- a/continuous_integration/scripts/entrypoint +++ b/continuous_integration/scripts/entrypoint @@ -1,44 +1,24 @@ #!/usr/bin/env bash -sccache_stop_server_and_show_stats() { - sccache --stop-server || true && sccache --show-stats; -} +set_repo_dir() { + set -xeuo pipefail + + # Resolve the directory of the script + SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )" -init_devcontainer() { - # disable xtrace and history - local xtrace_enabled=$(echo "${SHELLOPTS:-}" | grep -q 'xtrace'; echo $?); - local history_enabled=$(echo "${SHELLOPTS:-}" | grep -q 'history'; echo $?); - { set +xo history; } 2>/dev/null; - eval "export $(find /run/secrets/ -type f -exec bash -c 'echo ${0/\/run\/secrets\//}=$(<${0})' {} \;)"; - if [ "${history_enabled}" -eq "0" ]; then { set -o history; } 2>/dev/null; fi; - if [ "${xtrace_enabled}" -eq "0" ]; then { set -o xtrace; } 2>/dev/null; fi; + # Navigate to the parent of the parent of SCRIPT_DIR, then get the full path + REPO_DIR="$(cd "$SCRIPT_DIR/../.." && pwd)" - set -xeo pipefail + export REPO_DIR - . devcontainer-utils-post-attach-command; + export PATH="${PATH}:${REPO_DIR}/continuous_integration/scripts" - sleep 10; - . devcontainer-utils-vault-s3-test; - . devcontainer-utils-vault-s3-export 0; + # export ARTIFACTS_DIR=${ARTIFACTS_DIR:-/tmp/artifacts} } entrypoint() { - set -x - - mkdir -p /home/coder/.cache; - - local secrets_dir=/run/secrets - - if [ -d "$secrets_dir" ] && [ "$(ls -A $secrets_dir)" ]; then - init_devcontainer - else - sccache_stop_server_and_show_stats - fi - - echo AWS_REGION=${AWS_REGION:-} - echo AWS_SESSION_TOKEN=${AWS_SESSION_TOKEN:-} - echo AWS_ACCESS_KEY_ID=${AWS_ACCESS_KEY_ID:-} - echo AWS_SECRET_ACCESS_KEY=${AWS_SECRET_ACCESS_KEY:-} + set -xeuo pipefail + set_repo_dir; exec "$@"; } diff --git a/continuous_integration/scripts/make-conda-env b/continuous_integration/scripts/make-conda-env new file mode 100755 index 0000000000..b2c0c9eec7 --- /dev/null +++ b/continuous_integration/scripts/make-conda-env @@ -0,0 +1,41 @@ +#!/usr/bin/env bash + +# . conda-utils + +setup_env() { + set -xeuo pipefail + yaml_file=$(find "$ARTIFACTS_DIR" -name "environment*.yaml" | head -n 1) + + [ "${USE_CUDA}" = "ON" ] && + echo " - libcublas-dev" >> "${yaml_file}" && + echo " - libcufft-dev" >> "${yaml_file}" && + echo " - libcurand-dev" >> "${yaml_file}" && + echo " - libcusolver-dev" >> "${yaml_file}"; + + echo "YAML file..." + cat "${yaml_file}" + + mkdir -p /tmp/out; + + cp "${yaml_file}" /tmp/out + + mamba env create -n legate -f "$yaml_file" + + mamba uninstall -yn legate numpy + + mamba install -yn legate -c "${ARTIFACTS_DIR}/conda-build/legate_core" -c conda-forge -c nvidia legate-core +} + +make_conda_env() { + set -xeuo pipefail + + case "$1" in + ci) setup_env;; + # release) make_release_env;; + *) return 1;; + esac + + return 0; +} + +(make_conda_env "$@"); \ No newline at end of file diff --git a/continuous_integration/scripts/setup-utils b/continuous_integration/scripts/setup-utils new file mode 100755 index 0000000000..9f1708eedd --- /dev/null +++ b/continuous_integration/scripts/setup-utils @@ -0,0 +1,278 @@ +#!/usr/bin/env bash + +set_darwin_build_env() { + set -xeuo pipefail + + export USE_CUDA=OFF + export UCX_ENABLED=OFF + export OS_SHORT_NAME=osx + export PATH="/usr/local/opt/coreutils/libexec/gnubin:${PATH}" +} + +install_darwin_mamba() { + set -xeuo pipefail + + if [ "${GITHUB_ACTIONS:-}" == "true" ]; then + conda install -y -n base anaconda-clean + conda run -n base anaconda-clean --yes + sudo rm -rf /usr/local/miniconda + fi + + brew install --cask mambaforge +} + +install_darwin_tools() { + set -xeuo pipefail + + export SED=gsed + export READLINK=greadlink + + brew update + brew install cmake coreutils git gnu-getopt gnu-sed jq ninja wget sccache + install_darwin_mamba; +} + +install_darwin_test_tools() { + set -xeuo pipefail + + export SED=gsed + export READLINK=greadlink + + brew update + brew install coreutils git gnu-getopt gnu-sed jq wget + install_darwin_mamba; +} + +# Function to compare version numbers +version_greater_equal() { + set -xeuo pipefail + + set +x + IFS='.' read -ra ver1 <<< "$1" + IFS='.' read -ra ver2 <<< "$2" + + for i in "${!ver1[@]}"; do + if [[ -z ${ver2[i]} ]]; then + # ver1 has more segments and is greater + set -x + return 0 + fi + + if ((10#${ver1[i]} > 10#${ver2[i]})); then + set -x + return 0 + elif ((10#${ver1[i]} < 10#${ver2[i]})); then + set -x + return 1 + fi + done + + return 0 +} + +install_from_apt() { + set -xeuo pipefail + + export DEBIAN_FRONTEND=non-interactive + + # Run package updates and install packages + apt-get update + apt-get install -y wget curl jq sudo ninja-build vim numactl rsync +} + +install_sccache_linux() { + set -xeuo pipefail + + wget https://github.com/mozilla/sccache/releases/download/v0.5.4/sccache-v0.5.4-x86_64-unknown-linux-musl.tar.gz && \ + tar -xf sccache-v0.5.4-x86_64-unknown-linux-musl.tar.gz && \ + sudo mv sccache-v0.5.4-x86_64-unknown-linux-musl/sccache /usr/bin/sccache +} + +maybe_install_sccache_linux() { + set -xeuo pipefail + + if ! command -v sccache &> /dev/null; then + echo "sccache not found, proceeding with installation." + install_sccache_linux + else + sccache_version=$(sccache --version 2>&1 | awk '/sccache/ {print $2}') + if [[ -z "$sccache_version" ]] || ! version_greater_equal "$sccache_version" "0.5.4"; then + echo "sccache version less than 0.5.4, proceeding with installation." + install_sccache_linux + else + echo "sccache version is 0.5.4 or greater, no need to install." + fi + fi +} + + +install_cmake() { + set -xeuo pipefail + + wget https://github.com/Kitware/CMake/releases/download/v3.26.5/cmake-3.26.5-linux-x86_64.tar.gz + + tar -xzf cmake-3.26.5-linux-x86_64.tar.gz +} + +setup_linux_build_env() { + set -xeuo pipefail + export OS_SHORT_NAME=linux + export PATH="${PATH}:${PREBUILD_DIR}/cmake-3.26.5-linux-x86_64/bin" + + mkdir -p /tmp/out /tmp/env_yaml +} + +install_linux_tools() { + set -xeuo pipefail + + export SED=sed + export READLINK=readlink + + install_from_apt; + maybe_install_sccache_linux; + install_cmake; + + mkdir -p /tmp/out /tmp/env_yaml +} + +install_linux_test_tools() { + set -xeuo pipefail + + export SED=sed + export READLINK=readlink + + # Run package updates and install packages + apt-get update + apt-get install -y numactl +} + +set_base_defs() { + set -xeuo pipefail + + export USE_CUDA=${USE_CUDA:-OFF} + + export CONDA_ENV=legate + + CONDA_PLATFORM=$(conda info | grep 'platform' | awk -F ' : ' '{print $2}') + export CONDA_PLATFORM + + export PROJECT=cunumeric.internal + export PREBUILD_DIR=/tmp/prebuild + mkdir -p "$PREBUILD_DIR" + + export BUILD_MARCH=$(uname -m | tr '_' '-') + + export CUDA_VERSION=12.2 + export CUDA_VERSION_MAJOR=12 + export CUDA_VERSION_MINOR=2 + + export PYTHON_VERSION=3.11 + + export MAX_LIBSANITIZER_VERSION=11.4 + + export USE_OPENMP=ON + export LEGATE_CORE_CMAKE_PRESET=${LEGATE_CORE_CMAKE_PRESET:-release-gcc} +} + +# ----------------------------------------------------------------------------- + +prep_git() { + local current_email=$(git config --global user.email) + local current_name=$(git config --global user.name) + + if [ -z "$current_email" ]; then + git config --global --add user.email "users.noreply.github.com" + else + echo "Note: git user.email is already set to $current_email" + fi + + if [ -z "$current_name" ]; then + git config --global --add user.name "anon" + else + echo "Note: git user.name is already set to $current_name" + fi + + # Fix "fatal: detected dubious ownership in repository at '/tmp/legate.core'" + # during local builds. + git config --global --add safe.directory "$REPO_DIR" +} + +install_tools() { + if [[ "$(uname)" == "Darwin" ]]; then + install_darwin_tools; + elif [[ "$(uname)" == "Linux" ]]; then + install_linux_tools; + else + echo "Unknown OS" + exit 1 + fi +} + +install_test_tools() { + if [[ "$(uname)" == "Darwin" ]]; then + install_darwin_test_tools; + elif [[ "$(uname)" == "Linux" ]]; then + install_linux_test_tools; + else + echo "Unknown OS" + exit 1 + fi +} + +setup_os_specific_env() { + if [[ "$(uname)" == "Darwin" ]]; then + set_darwin_build_env; + elif [[ "$(uname)" == "Linux" ]]; then + setup_linux_build_env; + else + echo "Unknown OS" + exit 1 + fi +} + +setup_build_env() { + set -xeuo pipefail + + install_tools; + + setup_os_specific_env; + + rm -rf "$PREBUILD_DIR" + mkdir -p "$PREBUILD_DIR" + cd $PREBUILD_DIR + + prep_git; +} + +sccache_stop_server_and_show_stats() { + set -xeuo pipefail + sccache --stop-server || true && sccache --show-stats; +} + +init_sccache() { + set -xeuo pipefail + + export SCCACHE_REGION="us-east-2" + export SCCACHE_BUCKET="rapids-sccache-east" + export SCCACHE_S3_KEY_PREFIX=legate-cunumeric-dev + export VAULT_HOST=https://vault.ops.k8s.rapids.ai + CMAKE_C_COMPILER_LAUNCHER=$(which sccache) + export CMAKE_C_COMPILER_LAUNCHER + export CMAKE_CXX_COMPILER_LAUNCHER=${CMAKE_C_COMPILER_LAUNCHER} + export CMAKE_CUDA_COMPILER_LAUNCHER=${CMAKE_C_COMPILER_LAUNCHER} + + echo AWS_REGION="${AWS_REGION:-}" + echo AWS_SESSION_TOKEN="${AWS_SESSION_TOKEN:-}" + echo AWS_ACCESS_KEY_ID="${AWS_ACCESS_KEY_ID:-}" + echo AWS_SECRET_ACCESS_KEY="${AWS_SECRET_ACCESS_KEY:-}" + + mkdir -p ~/.cache; + + local secrets_dir="$REPO_DIR/.creds" + + if [ -d "$secrets_dir" ] && [ "$(ls -A "$secrets_dir")" ]; then + vault-s3-init; + else + sccache_stop_server_and_show_stats + fi +} diff --git a/continuous_integration/scripts/test-cunumeric b/continuous_integration/scripts/test-cunumeric index 57817741b9..0ec83d3c55 100755 --- a/continuous_integration/scripts/test-cunumeric +++ b/continuous_integration/scripts/test-cunumeric @@ -1,7 +1,15 @@ #!/usr/bin/env bash setup_env() { - mamba create -yn legate -c ~/.artifacts/conda-build/legate_core -c ~/.artifacts/conda-build/cunumeric -c conda-forge -c "nvidia/label/cuda-12.0.0" legate-core cunumeric + set -xeuo pipefail + + export DEBIAN_FRONTEND=non-interactive + + # Run package updates and install packages + apt-get update + apt-get install -y numactl make + + mamba create -yn legate -c "${ARTIFACTS_DIR}/conda-build/legate_core" -c "${ARTIFACTS_DIR}/conda-build/cunumeric" -c conda-forge -c "nvidia/label/cuda-12.0.0" legate-core cunumeric } setup_test_env() { @@ -13,7 +21,6 @@ setup_test_env() { setup_docs_env() { mamba install -y pandoc doxygen pip install ipython jinja2 "markdown<3.4.0" "pydata-sphinx-theme>=0.13" myst-parser nbsphinx sphinx-copybutton "sphinx>=4.4.0" - } setup_mypy_env() { @@ -23,13 +30,15 @@ setup_mypy_env() { test-cunumeric() { set -xeo pipefail + . conda-utils; + export CONDA_ENV=legate + setup_env; - set +u - mamba activate legate; - conda info; + activate_conda_env; + conda_info; - cd ~/cunumeric; + cd "${REPO_DIR}"; case "$1" in "test") diff --git a/continuous_integration/scripts/vault-s3-init b/continuous_integration/scripts/vault-s3-init new file mode 100755 index 0000000000..a72af04cae --- /dev/null +++ b/continuous_integration/scripts/vault-s3-init @@ -0,0 +1,109 @@ +#! /usr/bin/env bash + +set -xeuo pipefail; + +get_vault_token() { + set -eo pipefail + local VAULT_HOST="$1"; + local user_org="$2"; + local gh_token="$3"; + + local vault_token=null; + + vault_token="$( \ + curl -s \ + -X POST \ + -H "Content-Type: application/json" \ + -d "{\"token\": \"$gh_token\"}" \ + "$VAULT_HOST/v1/auth/github-${user_org}/login" \ + | jq -r '.auth.client_token' \ + )"; + + echo "vault_token='$vault_token'"; +} + +vault_s3_init() { + set -eo pipefail + # Attempt to retrieve temporary AWS credentials from a vault + # instance using GitHub OAuth. + + eval "export $(find $REPO_DIR/.creds -type f -exec bash -c 'echo $(basename $0)=$(<$0)' {} \;)"; + + if [[ -z "${VAULT_HOST:-}" ]]; then return; fi + if [[ -z "${SCCACHE_BUCKET:-}" ]]; then return; fi + if [[ -z "${GH_TOKEN:-}" ]]; then return; fi + + echo "" + echo "Attempting to use your GitHub account to authenticate"; + echo "with vault at '${VAULT_HOST}'."; + echo "" + + local vault_token=null; + local user_orgs=nv-legate; + + # Attempt to authenticate with GitHub + eval "$(get_vault_token "${VAULT_HOST}" ${user_orgs} $GH_TOKEN)"; + + if [[ "${vault_token:-null}" == null ]]; then + echo "Your GitHub user was not recognized by vault. Exiting." >&2; + return; + fi + + echo "Successfully authenticated with vault!"; + + local ttl="${VAULT_S3_TTL:-"43200s"}"; + local uri="${VAULT_S3_URI:-"v1/aws/creds/devs"}"; + + # Generate temporary AWS creds + local aws_creds="$( \ + curl -s \ + -X GET \ + -H "X-Vault-Token: $vault_token" \ + -H "Content-Type: application/json" \ + "${VAULT_HOST}/$uri?ttl=$ttl" \ + | jq -r '.data' \ + )"; + + export AWS_ACCESS_KEY_ID="$(echo "$aws_creds" | jq -r '.access_key')"; + export AWS_SECRET_ACCESS_KEY="$(echo "$aws_creds" | jq -r '.secret_key')"; + + if [[ "${AWS_ACCESS_KEY_ID:-null}" == null ]]; then + echo "Failed to generate temporary AWS S3 credentials. Exiting." >&2; + return; + fi + + if [[ "${AWS_SECRET_ACCESS_KEY:-null}" == null ]]; then + echo "Failed to generate temporary AWS S3 credentials. Exiting." >&2; + return; + fi + + # Generate AWS config files + mkdir -p ~/.aws; + + echo "$(date '+%s')" > ~/.aws/stamp; + + cat < ~/.aws/config +[default] +${SCCACHE_BUCKET:+bucket=$SCCACHE_BUCKET} +${SCCACHE_REGION:+region=$SCCACHE_REGION} +EOF + + cat < ~/.aws/credentials +[default] +aws_access_key_id=$AWS_ACCESS_KEY_ID +aws_secret_access_key=$AWS_SECRET_ACCESS_KEY +EOF + + chmod 0600 ~/.aws/{config,credentials}; + + echo "Successfully generated temporary AWS S3 credentials!"; + + # Stop server and reset sccache stats. + sccache --stop-server || true + + # Wait for AWS credentials to propagate + sleep 10 + sccache --show-stats +} + +(vault_s3_init "$@"); From c37e7cb86c8ba76f4a7a05445b113811af6d5260 Mon Sep 17 00:00:00 2001 From: Sebastian Berg Date: Tue, 16 Jan 2024 09:46:42 +0100 Subject: [PATCH 142/462] BUG: Ship operators.inl which is used by legate-dataframe (#71) --- cunumeric_cpp.cmake | 1 + 1 file changed, 1 insertion(+) diff --git a/cunumeric_cpp.cmake b/cunumeric_cpp.cmake index 1de4e8139b..65bf241378 100644 --- a/cunumeric_cpp.cmake +++ b/cunumeric_cpp.cmake @@ -459,6 +459,7 @@ install( src/cunumeric/ndarray.h src/cunumeric/ndarray.inl src/cunumeric/operators.h + src/cunumeric/operators.inl src/cunumeric/slice.h src/cunumeric/typedefs.h DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/cunumeric/cunumeric) From f50f852b3c2d4d600c9bd0a946d0e35e063f664b Mon Sep 17 00:00:00 2001 From: Wonchan Lee Date: Wed, 17 Jan 2024 09:20:12 -0800 Subject: [PATCH 143/462] Fix the non-editable build (#105) * Fix for the non-editable build * Update the outdated stencil example --- cunumeric_cpp.cmake | 1 + examples/cpp/stencil/build.sh | 4 +- examples/cpp/stencil/stencil.cc | 11 ++-- src/cunumeric/ndarray.cc | 101 +++++++++++++++++++++++++------- src/cunumeric/operators.cc | 1 + src/cunumeric/runtime.cc | 73 +---------------------- src/cunumeric/runtime.h | 5 -- 7 files changed, 93 insertions(+), 103 deletions(-) diff --git a/cunumeric_cpp.cmake b/cunumeric_cpp.cmake index 65bf241378..a4853e5529 100644 --- a/cunumeric_cpp.cmake +++ b/cunumeric_cpp.cmake @@ -460,6 +460,7 @@ install( src/cunumeric/ndarray.inl src/cunumeric/operators.h src/cunumeric/operators.inl + src/cunumeric/runtime.h src/cunumeric/slice.h src/cunumeric/typedefs.h DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/cunumeric/cunumeric) diff --git a/examples/cpp/stencil/build.sh b/examples/cpp/stencil/build.sh index 3b8abed26c..3fd7b274c3 100755 --- a/examples/cpp/stencil/build.sh +++ b/examples/cpp/stencil/build.sh @@ -14,7 +14,9 @@ # See the License for the specific language governing permissions and # limitations under the License. +legate_root=`python -c 'import legate.install_info as i; from pathlib import Path; print(Path(i.libpath).parent.resolve())'` +echo "Using Legate Core at $legate_root" cunumeric_root=`python -c 'import cunumeric.install_info as i; from pathlib import Path; print(Path(i.libpath).parent.resolve())'` echo "Using cuNumeric at $cunumeric_root" -cmake -S . -B build -D cunumeric_ROOT="$cunumeric_root" -D CMAKE_BUILD_TYPE=Debug +cmake -S . -B build -D legate_core_ROOT="$legate_root" -D cunumeric_ROOT="$cunumeric_root" -D CMAKE_BUILD_TYPE=Debug cmake --build build --parallel 8 diff --git a/examples/cpp/stencil/stencil.cc b/examples/cpp/stencil/stencil.cc index 5e3169c05d..201128f300 100644 --- a/examples/cpp/stencil/stencil.cc +++ b/examples/cpp/stencil/stencil.cc @@ -52,10 +52,10 @@ void print_array(cunumeric::NDArray array) cunumeric::NDArray initialize(uint64_t N) { auto grid = cunumeric::zeros({N + 2, N + 2}); - grid[{slice(), slice(0, 1)}].assign(-273.15); - grid[{slice(), slice(-1, open)}].assign(-273.15); - grid[{slice(-1, open), slice()}].assign(-273.15); - grid[{slice(0, 1), slice()}].assign(40.0); + grid[{slice(), slice(0, 1)}].assign(legate::Scalar{-273.15}); + grid[{slice(), slice(-1, open)}].assign(legate::Scalar{-273.15}); + grid[{slice(-1, open), slice()}].assign(legate::Scalar{-273.15}); + grid[{slice(0, 1), slice()}].assign(legate::Scalar{40.0}); return grid; } @@ -81,7 +81,8 @@ void stencil(const Config& config) int main(int argc, char** argv) { - legate::start(argc, argv); + auto result = legate::start(argc, argv); + assert(result == 0); cunumeric::initialize(argc, argv); diff --git a/src/cunumeric/ndarray.cc b/src/cunumeric/ndarray.cc index 4f29cf0484..28787f7721 100644 --- a/src/cunumeric/ndarray.cc +++ b/src/cunumeric/ndarray.cc @@ -27,6 +27,10 @@ namespace cunumeric { +// ========================================================================================== + +// Reduction utilities + namespace { struct generate_zero_fn { @@ -38,8 +42,75 @@ struct generate_zero_fn { } }; +struct generate_identity_fn { + template + struct generator { + template ::valid && !is_arg_reduce::value>* = nullptr> + Scalar operator()(const legate::Type&) + { + auto value = UnaryRedOp::OP::identity; + return Scalar(value); + } + + template ::valid && is_arg_reduce::value>* = nullptr> + Scalar operator()(const legate::Type& type) + { + auto value = UnaryRedOp::OP::identity; + auto argred_type = CuNumericRuntime::get_runtime()->get_argred_type(type); + return Scalar(value, argred_type); + } + + template ::valid>* = nullptr> + Scalar operator()(const legate::Type&) + { + assert(false); + return Scalar(0); + } + }; + + template + Scalar operator()(const legate::Type& type) + { + return legate::type_dispatch(type.code(), generator{}, type); + } +}; + +Scalar get_reduction_identity(UnaryRedCode op, const legate::Type& type) +{ + static std::map, Scalar> identities; + + auto key = std::make_pair(op, type.code()); + auto finder = identities.find(key); + if (identities.end() != finder) { + return finder->second; + } + + auto identity = op_dispatch(op, generate_identity_fn{}, type); + identities.insert({key, identity}); + return identity; +} + +const std::unordered_map TO_CORE_REDOP = { + {UnaryRedCode::ALL, legate::ReductionOpKind::MUL}, + {UnaryRedCode::ANY, legate::ReductionOpKind::ADD}, + {UnaryRedCode::ARGMAX, legate::ReductionOpKind::MAX}, + {UnaryRedCode::ARGMIN, legate::ReductionOpKind::MIN}, + {UnaryRedCode::CONTAINS, legate::ReductionOpKind::ADD}, + {UnaryRedCode::COUNT_NONZERO, legate::ReductionOpKind::ADD}, + {UnaryRedCode::MAX, legate::ReductionOpKind::MAX}, + {UnaryRedCode::MIN, legate::ReductionOpKind::MIN}, + {UnaryRedCode::PROD, legate::ReductionOpKind::MUL}, + {UnaryRedCode::SUM, legate::ReductionOpKind::ADD}, +}; + +legate::ReductionOpKind get_reduction_op(UnaryRedCode op) { return TO_CORE_REDOP.at(op); } + } // namespace +// ========================================================================================== + NDArray::NDArray(legate::LogicalStore&& store) : store_(std::forward(store)) { } @@ -374,10 +445,10 @@ void NDArray::binary_reduction(int32_t op_code, NDArray rhs1, NDArray rhs2) legate::ReductionOpKind redop; if (op_code == static_cast(BinaryOpCode::NOT_EQUAL)) { - redop = runtime->get_reduction_op(UnaryRedCode::SUM); + redop = get_reduction_op(UnaryRedCode::SUM); fill(legate::Scalar(false)); } else { - redop = runtime->get_reduction_op(UnaryRedCode::PROD); + redop = get_reduction_op(UnaryRedCode::PROD); fill(legate::Scalar(true)); } auto task = runtime->create_task(CuNumericOpCode::CUNUMERIC_BINARY_RED); @@ -423,14 +494,11 @@ void NDArray::unary_reduction(int32_t op_code_, NDArray input) auto op_code = static_cast(op_code_); - auto identity = runtime->get_reduction_identity(op_code, type()); - fill(identity); + fill(get_reduction_identity(op_code, type())); auto task = runtime->create_task(CuNumericOpCode::CUNUMERIC_SCALAR_UNARY_RED); - auto redop = runtime->get_reduction_op(op_code); - - task.add_reduction(store_, redop); + task.add_reduction(store_, get_reduction_op(op_code)); task.add_input(input.store_); task.add_scalar_arg(legate::Scalar(op_code_)); task.add_scalar_arg(legate::Scalar(input.shape())); @@ -446,8 +514,7 @@ void NDArray::dot(NDArray rhs1, NDArray rhs2) auto runtime = CuNumericRuntime::get_runtime(); - auto identity = runtime->get_reduction_identity(UnaryRedCode::SUM, type()); - fill(identity); + fill(get_reduction_identity(UnaryRedCode::SUM, type())); assert(dim() == 2 && rhs1.dim() == 2 && rhs2.dim() == 2); @@ -461,9 +528,7 @@ void NDArray::dot(NDArray rhs1, NDArray rhs2) auto task = runtime->create_task(CuNumericOpCode::CUNUMERIC_MATMUL); - auto redop = runtime->get_reduction_op(UnaryRedCode::SUM); - - auto p_lhs = task.add_reduction(lhs_s, redop); + auto p_lhs = task.add_reduction(lhs_s, get_reduction_op(UnaryRedCode::SUM)); auto p_rhs1 = task.add_input(rhs1_s); auto p_rhs2 = task.add_input(rhs2_s); @@ -826,8 +891,7 @@ void NDArray::unary_reduction(int32_t op, if (initial.has_value()) { lhs_array.fill(initial.value()); } else { - auto identity = runtime->get_reduction_identity(op_code, lhs_array.type()); - lhs_array.fill(identity); + lhs_array.fill(get_reduction_identity(op_code, lhs_array.type())); } auto is_where = where.has_value(); @@ -844,8 +908,7 @@ void NDArray::unary_reduction(int32_t op, auto task = runtime->create_task(CuNumericOpCode::CUNUMERIC_SCALAR_UNARY_RED); - auto redop = runtime->get_reduction_op(op_code); - task.add_reduction(p_lhs, redop); + task.add_reduction(p_lhs, get_reduction_op(op_code)); auto p_rhs = task.add_input(rhs_array.store_); task.add_scalar_arg(legate::Scalar(op)); task.add_scalar_arg(legate::Scalar(rhs_array.shape())); @@ -881,8 +944,7 @@ void NDArray::unary_reduction(int32_t op, auto task = runtime->create_task(CuNumericOpCode::CUNUMERIC_UNARY_RED); - auto redop = runtime->get_reduction_op(op_code); - auto p_lhs = task.add_reduction(result, redop); + auto p_lhs = task.add_reduction(result, get_reduction_op(op_code)); auto p_rhs = task.add_input(rhs_array.store_); task.add_scalar_arg(legate::Scalar(axes.value()[0])); task.add_scalar_arg(legate::Scalar(op)); @@ -1113,8 +1175,7 @@ void NDArray::diag_task(NDArray rhs, int32_t offset, int32_t naxes, bool extract auto task = runtime->create_task(CuNumericOpCode::CUNUMERIC_DIAG); if (extract) { - auto redop = runtime->get_reduction_op(UnaryRedCode::SUM); - auto p_diag = task.add_reduction(diag, redop); + auto p_diag = task.add_reduction(diag, get_reduction_op(UnaryRedCode::SUM)); auto p_matrix = task.add_input(matrix); task.add_constraint(legate::align(p_matrix, p_diag)); } else { diff --git a/src/cunumeric/operators.cc b/src/cunumeric/operators.cc index 2fa565359c..f679a9fba0 100644 --- a/src/cunumeric/operators.cc +++ b/src/cunumeric/operators.cc @@ -19,6 +19,7 @@ #include "cunumeric/runtime.h" #include "cunumeric/binary/binary_op_util.h" #include "cunumeric/unary/unary_op_util.h" +#include "cunumeric/unary/unary_red_util.h" #include "cunumeric/random/rand_util.h" #include "cunumeric/nullary/window_util.h" diff --git a/src/cunumeric/runtime.cc b/src/cunumeric/runtime.cc index 22e6176754..0239055c3e 100644 --- a/src/cunumeric/runtime.cc +++ b/src/cunumeric/runtime.cc @@ -17,13 +17,12 @@ #include "cunumeric/runtime.h" #include "cunumeric/ndarray.h" +#include "cunumeric/unary/unary_red_util.h" namespace cunumeric { /*static*/ CuNumericRuntime* CuNumericRuntime::runtime_; -static std::map, Scalar> identities; - extern void bootstrapping_callback(Legion::Machine machine, Legion::Runtime* runtime, const std::set& local_procs); @@ -59,54 +58,6 @@ legate::LogicalStore CuNumericRuntime::create_scalar_store(const Scalar& value) return legate_runtime_->create_store(value); } -struct generate_identity_fn { - template - struct generator { - template ::valid && !is_arg_reduce::value>* = nullptr> - Scalar operator()(const legate::Type&) - { - auto value = UnaryRedOp::OP::identity; - return Scalar(value); - } - - template ::valid && is_arg_reduce::value>* = nullptr> - Scalar operator()(const legate::Type& type) - { - auto value = UnaryRedOp::OP::identity; - auto argred_type = CuNumericRuntime::get_runtime()->get_argred_type(type); - return Scalar(value, argred_type); - } - - template ::valid>* = nullptr> - Scalar operator()(const legate::Type&) - { - assert(false); - return Scalar(0); - } - }; - - template - Scalar operator()(const legate::Type& type) - { - return legate::type_dispatch(type.code(), generator{}, type); - } -}; - -Scalar CuNumericRuntime::get_reduction_identity(UnaryRedCode op, const legate::Type& type) -{ - auto key = std::make_pair(op, type.code()); - auto finder = identities.find(key); - if (identities.end() != finder) { - return finder->second; - } - - auto identity = op_dispatch(op, generate_identity_fn{}, type); - identities.insert({key, identity}); - return identity; -} - legate::Type CuNumericRuntime::get_argred_type(const legate::Type& value_type) { auto finder = argred_types_.find(value_type.code()); @@ -119,28 +70,6 @@ legate::Type CuNumericRuntime::get_argred_type(const legate::Type& value_type) return argred_type; } -namespace { - -const std::unordered_map TO_CORE_REDOP = { - {UnaryRedCode::ALL, legate::ReductionOpKind::MUL}, - {UnaryRedCode::ANY, legate::ReductionOpKind::ADD}, - {UnaryRedCode::ARGMAX, legate::ReductionOpKind::MAX}, - {UnaryRedCode::ARGMIN, legate::ReductionOpKind::MIN}, - {UnaryRedCode::CONTAINS, legate::ReductionOpKind::ADD}, - {UnaryRedCode::COUNT_NONZERO, legate::ReductionOpKind::ADD}, - {UnaryRedCode::MAX, legate::ReductionOpKind::MAX}, - {UnaryRedCode::MIN, legate::ReductionOpKind::MIN}, - {UnaryRedCode::PROD, legate::ReductionOpKind::MUL}, - {UnaryRedCode::SUM, legate::ReductionOpKind::ADD}, -}; - -} // namespace - -legate::ReductionOpKind CuNumericRuntime::get_reduction_op(UnaryRedCode op) -{ - return TO_CORE_REDOP.at(op); -} - legate::AutoTask CuNumericRuntime::create_task(CuNumericOpCode op_code) { return legate_runtime_->create_task(library_, op_code); diff --git a/src/cunumeric/runtime.h b/src/cunumeric/runtime.h index 4e52356b12..aea34cc2d4 100644 --- a/src/cunumeric/runtime.h +++ b/src/cunumeric/runtime.h @@ -22,7 +22,6 @@ #include "cunumeric/cunumeric_c.h" #include "cunumeric/typedefs.h" -#include "cunumeric/unary/unary_red_util.h" namespace cunumeric { @@ -40,10 +39,6 @@ class CuNumericRuntime { NDArray create_array(legate::LogicalStore&& store); legate::LogicalStore create_scalar_store(const Scalar& value); - public: - Scalar get_reduction_identity(UnaryRedCode op, const legate::Type& type); - legate::ReductionOpKind get_reduction_op(UnaryRedCode op); - public: legate::Type get_argred_type(const legate::Type& value_type); From 59b5d28172ec376e1205db56b40b06a48e0eb4b0 Mon Sep 17 00:00:00 2001 From: Wonchan Lee Date: Wed, 17 Jan 2024 16:11:11 -0800 Subject: [PATCH 144/462] Catch up the core changes for the Shape class (#92) * Catch up the core changes for the Shape class * Update the commit hash to fetch the latest deferred shape code * Fix a build issue * Bump up the legate core commit again --- cmake/versions.json | 2 +- cunumeric/deferred.py | 4 ++-- cunumeric/linalg/cholesky.py | 25 ++++++++++++++++--------- src/cunumeric/cunumeric.cc | 2 +- src/cunumeric/ndarray.cc | 8 ++++---- 5 files changed, 24 insertions(+), 17 deletions(-) diff --git a/cmake/versions.json b/cmake/versions.json index 57fc119438..ae829ab62e 100644 --- a/cmake/versions.json +++ b/cmake/versions.json @@ -5,7 +5,7 @@ "git_url" : "https://github.com/nv-legate/legate.core.internal.git", "git_shallow": false, "always_download": false, - "git_tag" : "5fca57bc6235bf57b205d8b789c29163e813b72c" + "git_tag" : "14f65c97c2381d17e65feff84e91e50288ee58e0" } } } diff --git a/cunumeric/deferred.py b/cunumeric/deferred.py index a3c262d443..3a61fb079b 100644 --- a/cunumeric/deferred.py +++ b/cunumeric/deferred.py @@ -553,7 +553,7 @@ def _has_single_boolean_array( shift = 0 store = lhs.base for dim, k in enumerate(new_key): - if np.isscalar(k): + if isinstance(k, int): if k < 0: # type: ignore [operator] k += store.shape[dim + key_dim + shift] store = store.project(dim + key_dim + shift, k) @@ -1322,7 +1322,7 @@ def convolve(self, v: Any, lhs: Any, mode: ConvolveMode) -> None: self.library, CuNumericOpCode.CONVOLVE ) - offsets = tuple((filter.shape + 1) // 2) + offsets = tuple((ext + 1) // 2 for ext in filter.shape) p_out = task.add_output(output) p_filter = task.add_input(filter) diff --git a/cunumeric/linalg/cholesky.py b/cunumeric/linalg/cholesky.py index 8a7e7512ec..4a6db395f1 100644 --- a/cunumeric/linalg/cholesky.py +++ b/cunumeric/linalg/cholesky.py @@ -17,7 +17,6 @@ from typing import TYPE_CHECKING from legate.core import ( - Shape, broadcast, constant, dimension, @@ -62,7 +61,7 @@ def transpose_copy_single( def transpose_copy( library: Library, - launch_domain: Shape, + launch_domain: tuple[int, ...], p_input: LogicalStorePartition, p_output: LogicalStorePartition, ) -> None: @@ -161,16 +160,18 @@ def gemm( # TODO: We need a better cost model -def choose_color_shape(runtime: Runtime, shape: Shape) -> Shape: +def choose_color_shape( + runtime: Runtime, shape: tuple[int, ...] +) -> tuple[int, ...]: if settings.test(): num_tiles = runtime.num_procs * 2 - return Shape((num_tiles, num_tiles)) + return (num_tiles, num_tiles) extent = shape[0] # If there's only one processor or the matrix is too small, # don't even bother to partition it at all if runtime.num_procs == 1 or extent <= MIN_CHOLESKY_MATRIX_SIZE: - return Shape((1, 1)) + return (1, 1) # If the matrix is big enough to warrant partitioning, # pick the granularity that the tile size is greater than a threshold @@ -182,7 +183,7 @@ def choose_color_shape(runtime: Runtime, shape: Shape) -> Shape: ): num_tiles *= 2 - return Shape((num_tiles, num_tiles)) + return (num_tiles, num_tiles) def tril_single(library: Library, output: LogicalStore) -> None: @@ -212,6 +213,12 @@ def tril(library: Library, p_output: LogicalStorePartition, n: int) -> None: task.execute() +def _rounding_divide( + lhs: tuple[int, ...], rhs: tuple[int, ...] +) -> tuple[int, ...]: + return tuple((lh + rh - 1) // rh for (lh, rh) in zip(lhs, rhs)) + + def cholesky( output: DeferredArray, input: DeferredArray, no_tril: bool ) -> None: @@ -225,10 +232,10 @@ def cholesky( tril_single(library, output.base) return - shape = output.base.shape + shape = tuple(output.base.shape) initial_color_shape = choose_color_shape(runtime, shape) - tile_shape = (shape + initial_color_shape - 1) // initial_color_shape - color_shape = (shape + tile_shape - 1) // tile_shape + tile_shape = _rounding_divide(shape, initial_color_shape) + color_shape = _rounding_divide(shape, tile_shape) n = color_shape[0] p_input = input.base.partition_by_tiling(tile_shape) diff --git a/src/cunumeric/cunumeric.cc b/src/cunumeric/cunumeric.cc index 3f9cc7f41b..ddb2153c37 100644 --- a/src/cunumeric/cunumeric.cc +++ b/src/cunumeric/cunumeric.cc @@ -45,7 +45,7 @@ void unload_cudalibs() noexcept auto library = runtime->find_library(cunumeric_library_name); runtime->submit(runtime->create_task( - library, CuNumericOpCode::CUNUMERIC_UNLOAD_CUDALIBS, legate::Shape{num_gpus})); + library, CuNumericOpCode::CUNUMERIC_UNLOAD_CUDALIBS, legate::tuple{num_gpus})); } void registration_callback() diff --git a/src/cunumeric/ndarray.cc b/src/cunumeric/ndarray.cc index 28787f7721..cce5473ce7 100644 --- a/src/cunumeric/ndarray.cc +++ b/src/cunumeric/ndarray.cc @@ -581,7 +581,7 @@ std::vector NDArray::nonzero() } auto p_rhs = task.add_input(store_); - task.add_constraint(legate::broadcast(p_rhs, legate::from_range(1, ndim))); + task.add_constraint(legate::broadcast(p_rhs, legate::from_range(1, ndim))); runtime->submit(std::move(task)); @@ -603,7 +603,7 @@ NDArray NDArray::unique() task.add_input(store_, part_in); task.add_communicator("nccl"); if (!has_gpus) { - task.add_constraint(legate::broadcast(part_in, legate::from_range(0, dim()))); + task.add_constraint(legate::broadcast(part_in, legate::from_range(0, dim()))); } runtime->submit(std::move(task)); return result; @@ -689,7 +689,7 @@ void NDArray::convolve(NDArray input, NDArray filter) task.add_constraint(legate::align(p_input, p_output)); task.add_constraint(legate::bloat(p_input, p_halo, offsets, offsets)); - task.add_constraint(legate::broadcast(p_filter, legate::from_range(dim()))); + task.add_constraint(legate::broadcast(p_filter, legate::from_range(dim()))); runtime->submit(std::move(task)); } @@ -746,7 +746,7 @@ void NDArray::flip(NDArray rhs, std::optional> axis) auto p_out = task.add_output(output); auto p_in = task.add_input(input); task.add_scalar_arg(legate::Scalar(axes)); - task.add_constraint(legate::broadcast(p_in, legate::from_range(dim()))); + task.add_constraint(legate::broadcast(p_in, legate::from_range(dim()))); task.add_constraint(legate::align(p_in, p_out)); runtime->submit(std::move(task)); From 030e8a8780fa178c6eda6d5073ded31bcd6efaf0 Mon Sep 17 00:00:00 2001 From: Shijie Chen Date: Thu, 25 Jan 2024 14:17:07 +0800 Subject: [PATCH 145/462] Add tests for fill operation and update the tests that call fill (#101) --- tests/cpp/integration/common_utils.h | 2 +- tests/cpp/integration/test_argsort.cc | 2 +- tests/cpp/integration/test_fill.cc | 91 ++++++++++++++++++++++ tests/cpp/integration/test_flip.cc | 2 +- tests/cpp/integration/test_logical.cc | 2 +- tests/cpp/integration/test_moveaxis.cc | 4 +- tests/cpp/integration/test_msort.cc | 2 +- tests/cpp/integration/test_sort.cc | 2 +- tests/cpp/integration/test_sort_complex.cc | 2 +- 9 files changed, 100 insertions(+), 9 deletions(-) create mode 100644 tests/cpp/integration/test_fill.cc diff --git a/tests/cpp/integration/common_utils.h b/tests/cpp/integration/common_utils.h index 9f512272f8..3b315ddc86 100644 --- a/tests/cpp/integration/common_utils.h +++ b/tests/cpp/integration/common_utils.h @@ -48,7 +48,7 @@ NDArray mk_array(std::vector const& values, std::vector shape = {}) return out; } if (out.size() == 1) { - out.fill(legate::Scalar(values[0]), false); + out.fill(legate::Scalar(values[0])); return out; } auto assign_values = [](NDArray& a, std::vector const& values) { diff --git a/tests/cpp/integration/test_argsort.cc b/tests/cpp/integration/test_argsort.cc index 462afc52ae..8efdd88e28 100644 --- a/tests/cpp/integration/test_argsort.cc +++ b/tests/cpp/integration/test_argsort.cc @@ -214,7 +214,7 @@ void test_argsort(std::array& in_array, auto A1 = cunumeric::zeros(shape, leg_type); if (in_array.size() != 0) { if (in_array.size() == 1) { - A1.fill(legate::Scalar(in_array[0]), false); + A1.fill(legate::Scalar(in_array[0])); } else { assign_values_to_array(A1, in_array.data(), in_array.size()); } diff --git a/tests/cpp/integration/test_fill.cc b/tests/cpp/integration/test_fill.cc new file mode 100644 index 0000000000..fdbc1500df --- /dev/null +++ b/tests/cpp/integration/test_fill.cc @@ -0,0 +1,91 @@ +/* Copyright 2024 NVIDIA Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +#include "common_utils.h" +#include +#include + +using namespace cunumeric; + +namespace { + +TEST(Fill, test_fill_empty_array) +{ + auto x = mk_array({}, {0}); + x.fill(Scalar(int32_t(1))); + check_array(x, {}, {0}); +} + +TEST(Fill, test_fill_float_with_nan) +{ + auto x = zeros({6}, legate::float32()); + float val_nan = std::numeric_limits::quiet_NaN(); + x.fill(Scalar(val_nan)); + auto accessor = x.get_read_accessor(); + for (size_t i = 0; i < x.size(); ++i) { + ASSERT_TRUE(std::isnan(accessor[i])); + } +} + +TEST(Fill, test_fill_inf_to_float) +{ + float val_inf = std::numeric_limits::infinity(); + std::vector INF_VALUES = {val_inf, -val_inf}; + for (auto value : INF_VALUES) { + auto x = zeros({6}, legate::float32()); + std::vector x_gt(6, value); + x.fill(Scalar(value)); + check_array(x, x_gt); + } +} + +TEST(Fill, test_fill_float_to_float) +{ + std::vector FLOAT_FILL_VALUES{-2.4e120, -1.3, 8.9e-130, 0.0, 5.7e-150, 0.6, 3.7e160}; + for (auto value : FLOAT_FILL_VALUES) { + auto x = zeros({6}, legate::float64()); + std::vector x_gt(6, value); + x.fill(Scalar(value)); + check_array(x, x_gt); + } +} + +TEST(Fill, test_fill_ndim) +{ + std::vector shape; + for (int32_t ndim = 1; ndim <= LEGATE_MAX_DIM; ++ndim) { + shape.push_back(ndim); + int32_t value = ndim * 10; + auto x = zeros(shape, legate::int32()); + auto x_gt = mk_seq_vector(shape, 0, value); + x.fill(Scalar(value)); + check_array(x, x_gt, shape); + } +} + +TEST(Fill, test_full_ndim) +{ + std::vector shape; + for (int32_t ndim = 1; ndim <= LEGATE_MAX_DIM; ++ndim) { + shape.push_back(ndim); + int32_t value = ndim * 10; + auto x = full(shape, Scalar(value)); + auto x_gt = mk_seq_vector(shape, 0, value); + check_array(x, x_gt, shape); + } +} + +} // namespace diff --git a/tests/cpp/integration/test_flip.cc b/tests/cpp/integration/test_flip.cc index 4209da738d..fa1804abd9 100644 --- a/tests/cpp/integration/test_flip.cc +++ b/tests/cpp/integration/test_flip.cc @@ -333,7 +333,7 @@ void test_flip(std::array& in_array, auto A1 = cunumeric::zeros(shape, leg_type); if (in_array.size() != 0) { if (in_array.size() == 1) { - A1.fill(legate::Scalar(in_array[0]), false); + A1.fill(legate::Scalar(in_array[0])); } else { assign_values_to_array(A1, in_array.data(), in_array.size()); } diff --git a/tests/cpp/integration/test_logical.cc b/tests/cpp/integration/test_logical.cc index 2dd6975795..3d410f02ec 100644 --- a/tests/cpp/integration/test_logical.cc +++ b/tests/cpp/integration/test_logical.cc @@ -40,7 +40,7 @@ void test_all(std::array& in_array, auto A1 = cunumeric::zeros(shape, leg_type); if (in_array.size() != 0) { if (in_array.size() == 1) { - A1.fill(legate::Scalar(in_array[0]), false); + A1.fill(legate::Scalar(in_array[0])); } else { assign_values_to_array(A1, in_array.data(), in_array.size()); } diff --git a/tests/cpp/integration/test_moveaxis.cc b/tests/cpp/integration/test_moveaxis.cc index 3d676ff337..34817d5243 100644 --- a/tests/cpp/integration/test_moveaxis.cc +++ b/tests/cpp/integration/test_moveaxis.cc @@ -67,7 +67,7 @@ TEST(MoveAxis, SpecialArrays) { std::vector input{99}; auto a = cunumeric::zeros({1}, legate::int32()); - a.fill(legate::Scalar(input[0]), false); + a.fill(legate::Scalar(input[0])); auto a_out = cunumeric::moveaxis(a, {0}, {-1}); check_array_eq(a_out, input.data(), input.size()); EXPECT_EQ(a_out.shape(), a.shape()); @@ -75,7 +75,7 @@ TEST(MoveAxis, SpecialArrays) { std::vector input{-100}; auto a = cunumeric::zeros({1, 1}, legate::int32()); - a.fill(legate::Scalar(input[0]), false); + a.fill(legate::Scalar(input[0])); auto a_out = cunumeric::moveaxis(a, {0, 1}, {-1, -2}); check_array_eq(a_out, input.data(), input.size()); EXPECT_EQ(a_out.shape(), a.shape()); diff --git a/tests/cpp/integration/test_msort.cc b/tests/cpp/integration/test_msort.cc index fd935c379f..01403497d6 100644 --- a/tests/cpp/integration/test_msort.cc +++ b/tests/cpp/integration/test_msort.cc @@ -196,7 +196,7 @@ void test_msort(std::array& in_array, auto A1 = cunumeric::zeros(shape, leg_type); if (in_array.size() != 0) { if (in_array.size() == 1) { - A1.fill(legate::Scalar(in_array[0]), false); + A1.fill(legate::Scalar(in_array[0])); } else { assign_values_to_array(A1, in_array.data(), in_array.size()); } diff --git a/tests/cpp/integration/test_sort.cc b/tests/cpp/integration/test_sort.cc index 02b02978ed..570114ec9a 100644 --- a/tests/cpp/integration/test_sort.cc +++ b/tests/cpp/integration/test_sort.cc @@ -501,7 +501,7 @@ void test_sort(std::array& in_array, auto A1 = cunumeric::zeros(shape, leg_type); if (in_array.size() != 0) { if (in_array.size() == 1) { - A1.fill(legate::Scalar(in_array[0]), false); + A1.fill(legate::Scalar(in_array[0])); } else { assign_values_to_array(A1, in_array.data(), in_array.size()); } diff --git a/tests/cpp/integration/test_sort_complex.cc b/tests/cpp/integration/test_sort_complex.cc index 0bd1d989a1..9bba668982 100644 --- a/tests/cpp/integration/test_sort_complex.cc +++ b/tests/cpp/integration/test_sort_complex.cc @@ -200,7 +200,7 @@ void test_sort_complex(std::array& in_array, auto A1 = cunumeric::zeros(shape, leg_type); if (in_array.size() != 0) { if (in_array.size() == 1) { - A1.fill(legate::Scalar(in_array[0]), false); + A1.fill(legate::Scalar(in_array[0])); } else { assign_values_to_array(A1, in_array.data(), in_array.size()); } From 5557be58a64b9e729237530d8ccd806807ebacc2 Mon Sep 17 00:00:00 2001 From: Wonchan Lee Date: Thu, 25 Jan 2024 10:28:34 -0800 Subject: [PATCH 146/462] Bump the legate commit hash (#108) * Bump the legate commit hash * Make sure FFT gets serialized when input and output have different shapes * Bump the legate commit hash again * Catch up the LEGATE_ABORT change --- cmake/versions.json | 2 +- cunumeric/deferred.py | 9 +++++---- src/cunumeric/arg_redop_register.h | 2 +- src/cunumeric/cudalibs.cu | 3 +-- src/cunumeric/mapper.cc | 2 +- src/cunumeric/operators.cc | 12 +++--------- src/cunumeric/random/bitgenerator_curand.inl | 4 ++-- src/cunumeric/random/bitgenerator_template.inl | 2 +- src/cunumeric/random/curand_help.h | 2 +- src/cunumeric/random/rand_util.h | 2 +- src/cunumeric/random/randutil/generator.h | 4 ++-- src/cunumeric/random/randutil/generator_create.inl | 5 ++++- 12 files changed, 23 insertions(+), 26 deletions(-) diff --git a/cmake/versions.json b/cmake/versions.json index ae829ab62e..48be0867c6 100644 --- a/cmake/versions.json +++ b/cmake/versions.json @@ -5,7 +5,7 @@ "git_url" : "https://github.com/nv-legate/legate.core.internal.git", "git_shallow": false, "always_download": false, - "git_tag" : "14f65c97c2381d17e65feff84e91e50288ee58e0" + "git_tag" : "4d5f61b77f27c6127fcd45739ed79a57a27380b6" } } } diff --git a/cunumeric/deferred.py b/cunumeric/deferred.py index 3a61fb079b..03d673cf07 100644 --- a/cunumeric/deferred.py +++ b/cunumeric/deferred.py @@ -1373,16 +1373,17 @@ def fft( for ax in axes: task.add_scalar_arg(ax, ty.int64) - if input.ndim > len(OrderedSet(axes)): - task.add_constraint(broadcast(p_input, OrderedSet(axes))) - else: - task.add_constraint(broadcast(p_input)) if input.shape == output.shape: task.add_constraint(align(p_output, p_input)) + if input.ndim > len(OrderedSet(axes)): + task.add_constraint(broadcast(p_input, OrderedSet(axes))) + else: + task.add_constraint(broadcast(p_input)) else: # TODO: We need the relaxed alignment to avoid serializing the # task here. Batched FFT was relying on the relaxed alignment. task.add_constraint(broadcast(p_output)) + task.add_constraint(broadcast(p_input)) task.execute() diff --git a/src/cunumeric/arg_redop_register.h b/src/cunumeric/arg_redop_register.h index 56608ef651..5968abfd3a 100644 --- a/src/cunumeric/arg_redop_register.h +++ b/src/cunumeric/arg_redop_register.h @@ -40,7 +40,7 @@ struct register_reduction_op_fn { template ::value>* = nullptr> ReductionOpIds operator()() { - LEGATE_ABORT; + LEGATE_ABORT("Should never get here"); return ReductionOpIds{}; } diff --git a/src/cunumeric/cudalibs.cu b/src/cunumeric/cudalibs.cu index 9803bb4139..bf17ad35e1 100644 --- a/src/cunumeric/cudalibs.cu +++ b/src/cunumeric/cudalibs.cu @@ -362,8 +362,7 @@ cufftContext CUDALibraries::get_cufft_plan(cufftType type, const cufftPlanParams static CUDALibraries& get_cuda_libraries(legate::Processor proc) { if (proc.kind() != legate::Processor::TOC_PROC) { - fprintf(stderr, "Illegal request for CUDA libraries for non-GPU processor"); - LEGATE_ABORT; + LEGATE_ABORT("Illegal request for CUDA libraries for non-GPU processor"); } static CUDALibraries cuda_libraries[LEGION_MAX_NUM_PROCS]; diff --git a/src/cunumeric/mapper.cc b/src/cunumeric/mapper.cc index 69995d95ed..a3064372b7 100644 --- a/src/cunumeric/mapper.cc +++ b/src/cunumeric/mapper.cc @@ -71,7 +71,7 @@ Scalar CuNumericMapper::tunable_value(TunableID tunable_id) } default: break; } - LEGATE_ABORT; // unknown tunable value + LEGATE_ABORT("Unknown tunable " << tunable_id); // unknown tunable value } std::vector CuNumericMapper::store_mappings( diff --git a/src/cunumeric/operators.cc b/src/cunumeric/operators.cc index f679a9fba0..da08fe7b78 100644 --- a/src/cunumeric/operators.cc +++ b/src/cunumeric/operators.cc @@ -254,21 +254,15 @@ NDArray triu(NDArray rhs, int32_t k) { return trilu(rhs, k, false); } NDArray dot(NDArray rhs1, NDArray rhs2) { if (rhs1.dim() != 2 || rhs2.dim() != 2) { - fprintf(stderr, "cunumeric::dot only supports matrices now"); - LEGATE_ABORT; + LEGATE_ABORT("cunumeric::dot only supports matrices now"); } auto& rhs1_shape = rhs1.shape(); auto& rhs2_shape = rhs2.shape(); if (rhs1_shape[1] != rhs2_shape[0]) { - fprintf(stderr, - "Incompatible matrices: (%zd, %zd) x (%zd, %zd)\n", - rhs1_shape[0], - rhs1_shape[1], - rhs2_shape[0], - rhs2_shape[1]); - LEGATE_ABORT; + LEGATE_ABORT("Incompatible matrices: (" << rhs1_shape[0] << ", " << rhs1_shape[1] << ") x (" + << rhs2_shape[0] << ", " << rhs2_shape[1] << ")"); } auto runtime = CuNumericRuntime::get_runtime(); diff --git a/src/cunumeric/random/bitgenerator_curand.inl b/src/cunumeric/random/bitgenerator_curand.inl index 12be9e71f3..1de9e9f2dd 100644 --- a/src/cunumeric/random/bitgenerator_curand.inl +++ b/src/cunumeric/random/bitgenerator_curand.inl @@ -1616,12 +1616,12 @@ struct BitGeneratorImplBody { generate_distribution>::generate( res, cugen, intparams, floatparams, doubleparams); break; - default: LEGATE_ABORT; + default: LEGATE_ABORT("Unknown random distribution"); } } break; } - default: LEGATE_ABORT; + default: LEGATE_ABORT("Unknown bitgenerator operation"); } } }; diff --git a/src/cunumeric/random/bitgenerator_template.inl b/src/cunumeric/random/bitgenerator_template.inl index 99e143a4bf..bbf6510647 100644 --- a/src/cunumeric/random/bitgenerator_template.inl +++ b/src/cunumeric/random/bitgenerator_template.inl @@ -94,7 +94,7 @@ static void bitgenerator_template(TaskContext& context) doubleparams.insert(doubleparams.end(), _doubleparams.begin(), _doubleparams.end()); break; } - default: LEGATE_ABORT; + default: LEGATE_ABORT("Unknown bitgenerator op"); } std::vector extra_args; diff --git a/src/cunumeric/random/curand_help.h b/src/cunumeric/random/curand_help.h index dfa457b2c0..2ccf3cf31f 100644 --- a/src/cunumeric/random/curand_help.h +++ b/src/cunumeric/random/curand_help.h @@ -38,7 +38,7 @@ static inline curandRngType get_curandRngType(cunumeric::BitGeneratorType kind) case cunumeric::BitGeneratorType::MT19937: return curandRngType::CURAND_RNG_PSEUDO_MT19937; case cunumeric::BitGeneratorType::PHILOX4_32_10: return curandRngType::CURAND_RNG_PSEUDO_PHILOX4_32_10; - default: LEGATE_ABORT; + default: LEGATE_ABORT("Unknown generator type"); } return curandRngType::CURAND_RNG_TEST; } diff --git a/src/cunumeric/random/rand_util.h b/src/cunumeric/random/rand_util.h index e467abb33f..baf2a3ccc3 100644 --- a/src/cunumeric/random/rand_util.h +++ b/src/cunumeric/random/rand_util.h @@ -41,7 +41,7 @@ constexpr decltype(auto) op_dispatch(RandGenCode gen_code, Functor f, Fnargs&&.. return f.template operator()(std::forward(args)...); case RandGenCode::INTEGER: return f.template operator()(std::forward(args)...); - default: LEGATE_ABORT; + default: LEGATE_ABORT("Unknown RNG generator code"); } return f.template operator()(std::forward(args)...); } diff --git a/src/cunumeric/random/randutil/generator.h b/src/cunumeric/random/randutil/generator.h index 0e147f3121..e8f80d6576 100644 --- a/src/cunumeric/random/randutil/generator.h +++ b/src/cunumeric/random/randutil/generator.h @@ -107,7 +107,7 @@ curandStatus_t inner_dispatch_sample(basegenerator* gen, func_t func, size_t N, case CURAND_RNG_PSEUDO_MRG32K3A: return static_cast*>(gen) ->template draw(func, N, out); - default: LEGATE_ABORT; + default: LEGATE_ABORT("Unknown base generator"); } return CURAND_STATUS_INTERNAL_ERROR; } @@ -141,7 +141,7 @@ curandStatus_t dispatch(randutilimpl::basegenerator* gen, func_t func, size_t N, case randutilimpl::execlocation::DEVICE: return dispatcher::run(gen, func, N, out); #endif - default: LEGATE_ABORT; + default: LEGATE_ABORT("Unknown generator execution location"); } return CURAND_STATUS_INTERNAL_ERROR; } diff --git a/src/cunumeric/random/randutil/generator_create.inl b/src/cunumeric/random/randutil/generator_create.inl index 2f9cdf29bf..1f82520a0d 100644 --- a/src/cunumeric/random/randutil/generator_create.inl +++ b/src/cunumeric/random/randutil/generator_create.inl @@ -44,7 +44,10 @@ static curandStatus_t inner_randutilCreateGenerator(randutilGenerator_t* generat case CURAND_RNG_PSEUDO_MRG32K3A: return randutilGenerator( generator, seed, generatorID, stream); - default: LEGATE_ABORT; + default: { + LEGATE_ABORT("Unknown generator type"); + break; + } } return CURAND_STATUS_TYPE_ERROR; } From 004da8474a8cc3e7f5d97b9e2e29b96e0fd84457 Mon Sep 17 00:00:00 2001 From: Manolis Papadakis Date: Fri, 2 Feb 2024 14:12:06 -0800 Subject: [PATCH 147/462] Don't std::move store_ when we should be copying (#113) * Don't std::move store_ when we should be copying * Get pytest version fix from legate.core * Pull proper pytest version --- cmake/versions.json | 2 +- continuous_integration/scripts/test-cunumeric | 2 +- src/cunumeric/ndarray.cc | 6 +++--- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/cmake/versions.json b/cmake/versions.json index 48be0867c6..f13f07a3d6 100644 --- a/cmake/versions.json +++ b/cmake/versions.json @@ -5,7 +5,7 @@ "git_url" : "https://github.com/nv-legate/legate.core.internal.git", "git_shallow": false, "always_download": false, - "git_tag" : "4d5f61b77f27c6127fcd45739ed79a57a27380b6" + "git_tag" : "23522dfd138289b08675d94826d4a5c199ebc3b0" } } } diff --git a/continuous_integration/scripts/test-cunumeric b/continuous_integration/scripts/test-cunumeric index 0ec83d3c55..eca73a5b63 100755 --- a/continuous_integration/scripts/test-cunumeric +++ b/continuous_integration/scripts/test-cunumeric @@ -13,7 +13,7 @@ setup_env() { } setup_test_env() { - mamba install -y "clang-tools>=8" "clang>=8" colorama coverage mock pre-commit pytest-cov pytest-lazy-fixture pytest-mock pytest types-docutils pynvml psutil + mamba install -y "clang-tools>=8" "clang>=8" colorama coverage mock pre-commit pytest-cov pytest-lazy-fixture pytest-mock "pytest<8" types-docutils pynvml psutil pip install tifffile } diff --git a/src/cunumeric/ndarray.cc b/src/cunumeric/ndarray.cc index cce5473ce7..72789fb655 100644 --- a/src/cunumeric/ndarray.cc +++ b/src/cunumeric/ndarray.cc @@ -697,7 +697,7 @@ void NDArray::convolve(NDArray input, NDArray filter) NDArray NDArray::transpose() { if (dim() == 1) { - return NDArray(std::move(store_)); + return NDArray(legate::LogicalStore(store_)); } std::vector axes; for (int32_t i = dim() - 1; i > -1; --i) { @@ -709,7 +709,7 @@ NDArray NDArray::transpose() NDArray NDArray::transpose(std::vector axes) { if (dim() == 1) { - return NDArray(std::move(store_)); + return NDArray(legate::LogicalStore(store_)); } if (static_cast(axes.size()) != dim()) { throw std::invalid_argument("axes must be the same size as ndim for transpose"); @@ -1367,7 +1367,7 @@ NDArray NDArray::diagonal(int32_t offset, auto runtime = CuNumericRuntime::get_runtime(); auto m = shape()[0] + std::abs(offset); auto res = runtime->create_array({m, m}, store_.type()); - res.diag_task(NDArray(std::move(store_)), offset, 0, false, false); + res.diag_task(*this, offset, 0, false, false); return res; } else { if (!axis1) { From b18c9799c4fdcaac366e4439258c0896bb7899e0 Mon Sep 17 00:00:00 2001 From: Manolis Papadakis Date: Wed, 7 Feb 2024 21:35:18 -0800 Subject: [PATCH 148/462] Don't create intermediate Store for scalar fills (#118) --- cunumeric/deferred.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/cunumeric/deferred.py b/cunumeric/deferred.py index 03d673cf07..c6e4e2df15 100644 --- a/cunumeric/deferred.py +++ b/cunumeric/deferred.py @@ -1388,13 +1388,15 @@ def fft( task.execute() # Fill the cuNumeric array with the value in the numpy array - def _fill(self, value: Any) -> None: + def _fill(self, value: LogicalStore | Scalar) -> None: assert self.base is not None if not self.base.transformed: # Emit a Legate fill legate_runtime.issue_fill(self.base, value) else: + if isinstance(value, Scalar): + value = legate_runtime.create_store_from_scalar(value) # Arg reductions would never fill transformed stores assert self.dtype.kind != "V" # Perform the fill using a task @@ -1415,8 +1417,7 @@ def fill(self, numpy_array: Any) -> None: # Have to copy the numpy array because this launch is asynchronous # and we need to make sure the application doesn't mutate the value # so make a future result, this is immediate so no dependence - value = Scalar(numpy_array.tobytes(), self.base.type) - self._fill(legate_runtime.create_store_from_scalar(value)) + self._fill(Scalar(numpy_array.tobytes(), self.base.type)) @auto_convert("rhs1_thunk", "rhs2_thunk") def contract( From 65e22cbd9b2713530d7028ee0b9fb35eb34b773d Mon Sep 17 00:00:00 2001 From: Robin Wang <104830875+robinwnv@users.noreply.github.com> Date: Thu, 8 Feb 2024 14:23:29 +0800 Subject: [PATCH 149/462] Fix bug in test_logic.py. (#115) * Fix bug in test_logic.py. * Address comments. --- cunumeric/eager.py | 2 +- tests/integration/test_logic.py | 5 ----- 2 files changed, 1 insertion(+), 6 deletions(-) diff --git a/cunumeric/eager.py b/cunumeric/eager.py index 60341a97e6..d5b03a8ff3 100644 --- a/cunumeric/eager.py +++ b/cunumeric/eager.py @@ -1531,7 +1531,7 @@ def isclose( if self.deferred is not None: self.deferred.isclose(rhs1, rhs2, rtol, atol, equal_nan) else: - self.array[:] = np.isclose( + self.array[...] = np.isclose( rhs1.array, rhs2.array, rtol=rtol, diff --git a/tests/integration/test_logic.py b/tests/integration/test_logic.py index f969eb1680..749f65859b 100644 --- a/tests/integration/test_logic.py +++ b/tests/integration/test_logic.py @@ -141,16 +141,11 @@ def test_isscalar_array(): ) -@pytest.mark.xfail @pytest.mark.parametrize( ("a", "b"), SCALAR_PAIRS, ) def test_isclose_scalars(a, b): - # for all cases, - # In Numpy, it pass - # In cuNumeric, it raises IndexError: too many indices for array: - # array is 0-dimensional, but 1 were indexed out_np = np.isclose(a, b) out_num = num.isclose(a, b) assert np.array_equal(out_np, out_num) From 6ab7274e607fd7e3ca9203d143a3f30388ca52f2 Mon Sep 17 00:00:00 2001 From: Bryan Van de Ven Date: Fri, 9 Feb 2024 10:10:10 -0800 Subject: [PATCH 150/462] Trim README and consolidate docss (#116) * Trim README and consolidate docss * Update README.md Co-authored-by: Manolis Papadakis --------- Co-authored-by: Manolis Papadakis --- README.md | 112 +++----------------- docs/cunumeric/source/user/installation.rst | 14 ++- 2 files changed, 19 insertions(+), 107 deletions(-) diff --git a/README.md b/README.md index 3ed163b571..d64061fcda 100644 --- a/README.md +++ b/README.md @@ -27,8 +27,7 @@ completely unmodified on 2048 A100 GPUs in a [DGX SuperPOD](https://www.nvidia.c drawing cuNumeric works best for programs that have very large arrays of data -that cannot fit in the memory of a single GPU or a single node and need -to span multiple nodes and GPUs. While our implementation of the current +that need to span multiple nodes and GPUs. While our implementation of the current NumPy API is still incomplete, programs that use unimplemented features will still work (assuming enough memory) by falling back to the canonical NumPy implementation. @@ -37,123 +36,38 @@ If you have questions, please contact us at legate(at)nvidia.com. ## Installation -cuNumeric is available [on conda](https://anaconda.org/legate/cunumeric): +Linux-64 packages for cuNumeric are available [via conda](https://anaconda.org/legate/cunumeric): -``` +```sh conda install -c nvidia -c conda-forge -c legate cunumeric ``` -Only linux-64 packages are available at the moment. - The default package contains GPU support, and is compatible with CUDA >= 11.8 (CUDA driver version >= r520), and Volta or later GPU architectures. There are also CPU-only packages available, and will be automatically selected by `conda` when installing on a machine without GPUs. -See the build instructions at https://nv-legate.github.io/cunumeric for details -about building cuNumeric from source. +For details about building cuNumeric from source, +see the [Legate Core](https://github.com/nv-legate/legate.core) documentation ## Usage and Execution -Using cuNumeric as a replacement for NumPy is easy. Users only need -to replace: - -``` +Using cuNumeric as a replacement for NumPy is as simple as replacing: +```python import numpy as np ``` - with: -``` +```python import cunumeric as np ``` -These programs can then be run by the Legate driver script described in the -[Legate Core](https://github.com/nv-legate/legate.core) documentation. - -``` -legate cunumeric_program.py -``` - -For execution with multiple nodes (assuming Legate Core is installed with networking support) -users can supply the `--nodes` option. For execution with GPUs, users can use the -`--gpus` flags to specify the number of GPUs to use per node. We encourage all users -to familiarize themselves with these resource flags as described in the Legate Core -documentation or simply by passing `--help` to the `legate` driver script. - -You can use `test.py` to run the test suite. Invoke the script directly or through -standard `python`; the script will invoke the `legate` driver script internally. -Check out `test.py --help` for further options. - -## Supported and Planned Features - -cuNumeric is currently a work in progress and we are gradually adding support for -additional NumPy operators. Unsupported NumPy operations will provide a -warning that we are falling back to canonical NumPy. Please report unimplemented -features that are necessary for attaining good performance so that we can triage -them and prioritize implementation appropriately. The more users that report an -unimplemented feature, the more we will prioritize it. Please include a pointer -to your code if possible too so we can see how you are using the feature in context. - -## Supported Types and Dimensions - -cuNumeric currently supports the following NumPy types: `float16`, `float32`, -`float64`, `int16`, `int32`, `int64`, `uint16`, `uint32`, `uint64`, `bool`, -`complex64`, and `complex128`. - -cuNumeric supports up to 4D arrays by default, you can adjust this setting by -installing legate.core with a larger `--max-dim`. - -## Documentation - -The cuNumeric documentation can be found -[here](https://nv-legate.github.io/cunumeric). - -## Future Directions - -There are three primary directions that we plan to investigate -with cuNumeric going forward: - -* More features: we plan to identify a few key lighthouse applications - and use the demands of these applications to drive the addition of - new features to cuNumeric. -* We plan to add support for sharded file I/O for loading and - storing large data sets that could never be loaded on a single node. - Initially this will begin with native support for [h5py](https://www.h5py.org/) - but will grow to accommodate other formats needed by our lighthouse - applications. -* Strong scaling: while cuNumeric is currently implemented in a way that - enables weak scaling of codes on larger data sets, we would also like - to make it possible to strong-scale Legate applications for a single - problem size. This will require leveraging some of the more advanced - features of Legion from inside the Python interpreter. +Scripts can be run using the standard `python` interpreter, or using the `legate` +driver script to assist with launching more advanced configurations. +For information about execution with multiple nodes and with GPUs, +see the [Legate Core](https://github.com/nv-legate/legate.core) documentation. -We are open to comments, suggestions, and ideas. ## Contributing -See the discussion of contributing in [CONTRIBUTING.md](CONTRIBUTING.md). - -## Known Issues - - * When using certain operations with high scratch space requirements (e.g. - `einsum` or `convolve`) you might run into the following error: - ``` - LEGION ERROR: Failed to allocate DeferredBuffer/Value/Reduction in task [some task] because [some memory] is full. This is an eager allocation ... - ``` - Currently, Legion splits its memory reservations between two pools: the - "deferred" pool, used for allocating cuNumeric `ndarray`s, and the "eager" - pool, used for allocating scratch memory for operations. The above error - message signifies that not enough memory was available for an operation's - scratch space requirements. You can work around this by allocating more - memory overall to cuNumeric (e.g. adjusting `--sysmem`, `--numamem` or - `--fbmem`), and/or by adjusting the split between the two pools (e.g. by - passing `-lg:eager_alloc_percentage 60` on the command line to allocate 60% - of memory to the eager pool, up from the default of 50%). - * cuNumeric can exercise a bug in OpenBLAS when it is run with - [multiple OpenMP processors](https://github.com/xianyi/OpenBLAS/issues/2146) - * On Mac OSX, cuNumeric can trigger a bug in Apple's implementation of libc++. - The [bug](https://bugs.llvm.org/show_bug.cgi?id=43764) has since been fixed but - likely will not show up on most Apple machines for quite some time. You may have - to manually patch your implementation of libc++. If you have trouble doing this - please contact us and we will be able to help you. +See the discussion in [CONTRIBUTING.md](CONTRIBUTING.md). diff --git a/docs/cunumeric/source/user/installation.rst b/docs/cunumeric/source/user/installation.rst index 8c496c84f2..6f22dcd4c3 100644 --- a/docs/cunumeric/source/user/installation.rst +++ b/docs/cunumeric/source/user/installation.rst @@ -1,19 +1,17 @@ Installation ============ -cuNumeric is available `from conda`_: +Linux-64 packages for cuNumeric are available `via conda`_: .. code-block:: sh conda install -c nvidia -c conda-forge -c legate cunumeric -Only linux-64 packages are available at the moment. - -The default package contains GPU support, and is compatible with CUDA >= 11.4 -(CUDA driver version >= r470), and Volta or later GPU architectures. There are -also CPU-only packages available, and will be automatically selected by -``conda`` when installing on a machine without GPUs. +The default package contains GPU support, and is compatible with CUDA >= 11.8 +(CUDA driver version >= r520), and Volta or later GPU architectures. There are +also CPU-only packages available, and will be automatically selected by conda +when installing on a machine without GPUs. See :ref:`building cunumeric from source` for instructions on building cuNumeric manually. -.. _from conda: https://anaconda.org/legate/cunumeric \ No newline at end of file +.. _via conda: https://anaconda.org/legate/cunumeric \ No newline at end of file From c3ca656f10b374237d6406f41fff579737c6474c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Malte=20F=C3=B6rster?= <97973773+mfoerste4@users.noreply.github.com> Date: Thu, 15 Feb 2024 09:48:17 +0100 Subject: [PATCH 151/462] Add cusolverMP support (#62) * initial cusolvermp commit for cpp core * bump core version, add new task ids to config test * Pre-commit fixes * review suggestions and small fixes * add larger testcases to trigger cusolverMP paths when run with multiple GPUs * review suggestions repartition * review suggestions repartition * some more suggestions * some more suggestions * fix repartitioning for edge cases with odd coloring * add test for repartitioning * refactoring large kernels * extracting duplicated logic * review comments * review suggestions * relax correctness check for large testcase * disable large test_solve.py testcase until test launcher can handle larger memory consumption --------- Co-authored-by: Manolis Papadakis --- cunumeric/config.py | 8 + cunumeric/linalg/cholesky.py | 71 +- cunumeric/linalg/linalg.py | 6 +- cunumeric/linalg/solve.py | 42 + cunumeric/runtime.py | 3 + cunumeric_cpp.cmake | 18 + examples/cholesky.py | 72 +- examples/solve.py | 67 +- install.py | 16 + src/cunumeric/cuda_help.h | 54 + src/cunumeric/cudalibs.cu | 46 +- src/cunumeric/cudalibs.h | 9 + src/cunumeric/cunumeric.cc | 9 + src/cunumeric/cunumeric_c.h | 4 + src/cunumeric/mapper.cc | 4 +- src/cunumeric/matrix/mp_potrf.cu | 146 +++ src/cunumeric/matrix/mp_potrf.h | 33 + src/cunumeric/matrix/mp_potrf_template.inl | 167 +++ src/cunumeric/matrix/mp_solve.cu | 247 ++++ src/cunumeric/matrix/mp_solve.h | 33 + src/cunumeric/matrix/mp_solve_template.inl | 195 +++ src/cunumeric/sort/sort.cu | 2 + src/cunumeric/utilities/repartition.cc | 71 + src/cunumeric/utilities/repartition.cu | 1357 ++++++++++++++++++++ src/cunumeric/utilities/repartition.h | 95 ++ tests/cpp/integration/test_repartition.cc | 453 +++++++ tests/cpp/integration/util.inl | 44 +- tests/integration/test_cholesky.py | 2 +- tests/integration/test_solve.py | 6 + tests/unit/cunumeric/test_config.py | 2 + 30 files changed, 3222 insertions(+), 60 deletions(-) create mode 100644 src/cunumeric/matrix/mp_potrf.cu create mode 100644 src/cunumeric/matrix/mp_potrf.h create mode 100644 src/cunumeric/matrix/mp_potrf_template.inl create mode 100644 src/cunumeric/matrix/mp_solve.cu create mode 100644 src/cunumeric/matrix/mp_solve.h create mode 100644 src/cunumeric/matrix/mp_solve_template.inl create mode 100644 src/cunumeric/utilities/repartition.cc create mode 100644 src/cunumeric/utilities/repartition.cu create mode 100644 src/cunumeric/utilities/repartition.h create mode 100644 tests/cpp/integration/test_repartition.cc diff --git a/cunumeric/config.py b/cunumeric/config.py index 498ee5b3ec..c27983a05d 100644 --- a/cunumeric/config.py +++ b/cunumeric/config.py @@ -171,6 +171,8 @@ class _CunumericSharedLib: CUNUMERIC_MAX_MAPPERS: int CUNUMERIC_MAX_REDOPS: int CUNUMERIC_MAX_TASKS: int + CUNUMERIC_MP_POTRF: int + CUNUMERIC_MP_SOLVE: int CUNUMERIC_NONZERO: int CUNUMERIC_PACKBITS: int CUNUMERIC_POTRF: int @@ -281,6 +283,10 @@ class _CunumericSharedLib: def cunumeric_has_curand(self) -> int: ... + @abstractmethod + def cunumeric_has_cusolvermp(self) -> int: + ... + @abstractmethod def cunumeric_register_reduction_ops(self, code: int) -> _ReductionOpIds: ... @@ -374,6 +380,8 @@ class CuNumericOpCode(IntEnum): LOAD_CUDALIBS = _cunumeric.CUNUMERIC_LOAD_CUDALIBS MATMUL = _cunumeric.CUNUMERIC_MATMUL MATVECMUL = _cunumeric.CUNUMERIC_MATVECMUL + MP_POTRF = _cunumeric.CUNUMERIC_MP_POTRF + MP_SOLVE = _cunumeric.CUNUMERIC_MP_SOLVE NONZERO = _cunumeric.CUNUMERIC_NONZERO PACKBITS = _cunumeric.CUNUMERIC_PACKBITS POTRF = _cunumeric.CUNUMERIC_POTRF diff --git a/cunumeric/linalg/cholesky.py b/cunumeric/linalg/cholesky.py index 82a3e895c0..a8037457b7 100644 --- a/cunumeric/linalg/cholesky.py +++ b/cunumeric/linalg/cholesky.py @@ -38,9 +38,6 @@ from ..runtime import Runtime -legate_runtime = get_legate_runtime() - - def transpose_copy_single( library: Library, input: LogicalStore, output: LogicalStore ) -> None: @@ -85,6 +82,25 @@ def potrf_single(library: Library, output: LogicalStore) -> None: task.execute() +def mp_potrf( + library: Library, + n: int, + nb: int, + input: LogicalStore, + output: LogicalStore, +) -> None: + task = legate_runtime.create_auto_task(library, CuNumericOpCode.MP_POTRF) + task.throws_exception(LinAlgError) + task.add_input(input) + task.add_output(output) + task.add_alignment(output, input) + task.add_scalar_arg(n, ty.int64) + task.add_scalar_arg(nb, ty.int64) + task.add_nccl_communicator() # for repartitioning + task.add_cal_communicator() + task.execute() + + def potrf(library: Library, p_output: LogicalStorePartition, i: int) -> None: task = legate_runtime.create_manual_task( library, CuNumericOpCode.POTRF, (i + 1, i + 1), lower_bounds=(i, i) @@ -270,23 +286,34 @@ def cholesky( return shape = tuple(output.base.shape) - initial_color_shape = choose_color_shape(runtime, shape) - tile_shape = _rounding_divide(shape, initial_color_shape) - color_shape = _rounding_divide(shape, tile_shape) - n = color_shape[0] - - p_input = input.base.partition_by_tiling(tile_shape) - p_output = output.base.partition_by_tiling(tile_shape) - transpose_copy(library, color_shape, p_input, p_output) - - for i in range(n): - potrf(library, p_output, i) - trsm(library, p_output, i, i + 1, n) - for k in range(i + 1, n): - syrk(library, p_output, k, i) - gemm(library, p_output, k, i, k + 1, n) - - if no_tril: - return + tile_shape: tuple[int, ...] + if ( + runtime.has_cusolvermp + and runtime.num_gpus > 1 + and shape[0] >= MIN_CHOLESKY_MATRIX_SIZE + ): + mp_potrf( + library, shape[0], MIN_CHOLESKY_TILE_SIZE, input.base, output.base + ) - tril(library, p_output, n) + if not no_tril: + tril_single(library, output.base) + else: + initial_color_shape = choose_color_shape(runtime, shape) + tile_shape = _rounding_divide(shape, initial_color_shape) + color_shape = _rounding_divide(shape, tile_shape) + n = color_shape[0] + + p_input = input.base.partition_by_tiling(tile_shape) + p_output = output.base.partition_by_tiling(tile_shape) + transpose_copy(library, color_shape, p_input, p_output) + + for i in range(n): + potrf(library, p_output, i) + trsm(library, p_output, i, i + 1, n) + for k in range(i + 1, n): + syrk(library, p_output, k, i) + gemm(library, p_output, k, i, k + 1, n) + + if not no_tril: + tril(library, p_output, n) diff --git a/cunumeric/linalg/linalg.py b/cunumeric/linalg/linalg.py index d1c0498b2e..726a7a0386 100644 --- a/cunumeric/linalg/linalg.py +++ b/cunumeric/linalg/linalg.py @@ -112,13 +112,17 @@ def solve(a: ndarray, b: ndarray, out: Optional[ndarray] = None) -> ndarray: LinAlgError If `a` is singular or not square. + Notes + ------ + Multi-GPU usage is only available when compiled with cusolverMP. + See Also -------- numpy.linalg.solve Availability -------- - Single GPU, Single CPU + Multiple GPUs, Single CPU """ if a.ndim < 2: raise LinAlgError( diff --git a/cunumeric/linalg/solve.py b/cunumeric/linalg/solve.py index c716f90056..ddcfb2816e 100644 --- a/cunumeric/linalg/solve.py +++ b/cunumeric/linalg/solve.py @@ -16,6 +16,7 @@ from typing import TYPE_CHECKING, cast +import legate.core.types as ty from legate.core import broadcast, get_legate_runtime from cunumeric.config import CuNumericOpCode @@ -45,12 +46,53 @@ def solve_single(library: Library, a: LogicalStore, b: LogicalStore) -> None: task.execute() +MIN_SOLVE_TILE_SIZE = 512 +MIN_SOLVE_MATRIX_SIZE = 2048 + + +def mp_solve( + library: Library, + n: int, + nrhs: int, + nb: int, + a: LogicalStore, + b: LogicalStore, + output: LogicalStore, +) -> None: + task = get_legate_runtime().create_auto_task( + library, CuNumericOpCode.MP_SOLVE + ) + task.throws_exception(LinAlgError) + task.add_input(a) + task.add_input(b) + task.add_output(output) + task.add_alignment(output, b) + task.add_scalar_arg(n, ty.int64) + task.add_scalar_arg(nrhs, ty.int64) + task.add_scalar_arg(nb, ty.int64) + task.add_nccl_communicator() # for repartitioning + task.add_cal_communicator() + task.execute() + + def solve(output: DeferredArray, a: DeferredArray, b: DeferredArray) -> None: from ..deferred import DeferredArray runtime = output.runtime library = output.library + if ( + runtime.has_cusolvermp + and runtime.num_gpus > 1 + and a.base.shape[0] >= MIN_SOLVE_MATRIX_SIZE + ): + n = a.base.shape[0] + nrhs = b.base.shape[1] + mp_solve( + library, n, nrhs, MIN_SOLVE_TILE_SIZE, a.base, b.base, output.base + ) + return + a_copy = cast( DeferredArray, runtime.create_empty_thunk(a.shape, dtype=a.base.type, inputs=(a,)), diff --git a/cunumeric/runtime.py b/cunumeric/runtime.py index 3cfa16ce67..1bb3cb6bf2 100644 --- a/cunumeric/runtime.py +++ b/cunumeric/runtime.py @@ -67,6 +67,9 @@ def __init__(self) -> None: assert cunumeric_lib.shared_object is not None self.cunumeric_lib = cunumeric_lib.shared_object self.has_curand = cunumeric_lib.shared_object.cunumeric_has_curand() + self.has_cusolvermp = ( + cunumeric_lib.shared_object.cunumeric_has_cusolvermp() + ) settings.warn = settings.warn() or legate_settings.test() diff --git a/cunumeric_cpp.cmake b/cunumeric_cpp.cmake index c32afa4999..14a2abb06d 100644 --- a/cunumeric_cpp.cmake +++ b/cunumeric_cpp.cmake @@ -167,6 +167,7 @@ list(APPEND cunumeric_SOURCES src/cunumeric/stat/bincount.cc src/cunumeric/convolution/convolve.cc src/cunumeric/transform/flip.cc + src/cunumeric/utilities/repartition.cc src/cunumeric/arg_redop_register.cc src/cunumeric/mapper.cc src/cunumeric/ndarray.cc @@ -275,6 +276,7 @@ if(Legion_USE_CUDA) src/cunumeric/convolution/convolve.cu src/cunumeric/fft/fft.cu src/cunumeric/transform/flip.cu + src/cunumeric/utilities/repartition.cu src/cunumeric/arg_redop_register.cu src/cunumeric/cudalibs.cu src/cunumeric/stat/histogram.cu @@ -345,6 +347,14 @@ if(Legion_USE_CUDA OR cunumeric_cuRAND_INCLUDE_DIR) endif() endif() +# add sources for cusolverMp +if(Legion_USE_CUDA AND CUSOLVERMP_DIR) + list(APPEND cunumeric_SOURCES + src/cunumeric/matrix/mp_potrf.cu + src/cunumeric/matrix/mp_solve.cu + ) +endif() + list(APPEND cunumeric_SOURCES # This must always be the last file! # It guarantees we do our registration callback @@ -406,6 +416,14 @@ if(NOT Legion_USE_CUDA AND cunumeric_cuRAND_INCLUDE_DIR) target_include_directories(cunumeric PRIVATE ${cunumeric_cuRAND_INCLUDE_DIR}) endif() +if(Legion_USE_CUDA AND CUSOLVERMP_DIR) + message(VERBOSE "cunumeric: CUSOLVERMP_DIR ${CUSOLVERMP_DIR}") + list(APPEND cunumeric_CXX_DEFS CUNUMERIC_USE_CUSOLVERMP) + list(APPEND cunumeric_CUDA_DEFS CUNUMERIC_USE_CUSOLVERMP) + target_include_directories(cunumeric PRIVATE ${CUSOLVERMP_DIR}/include) + target_link_libraries(cunumeric PRIVATE ${CUSOLVERMP_DIR}/lib/libcusolverMp.so) +endif() + # Change THRUST_DEVICE_SYSTEM for `.cpp` files if(Legion_USE_OpenMP) list(APPEND cunumeric_CXX_OPTIONS -UTHRUST_DEVICE_SYSTEM) diff --git a/examples/cholesky.py b/examples/cholesky.py index 92c357d60d..cabe10a4f0 100644 --- a/examples/cholesky.py +++ b/examples/cholesky.py @@ -15,40 +15,80 @@ import argparse -from legate.timing import time +import numpy +from benchmark import parse_args, run_benchmark -import cunumeric as np +def check_equal_numpy(input, output, package): + if package == "cupy": + out2 = numpy.linalg.cholesky(input.get()) + else: + out2 = numpy.linalg.cholesky(input.__array__()) -def cholesky(n, dtype): - input = np.eye(n, dtype=dtype) + print("Checking result...") + if numpy.allclose(output, out2): + print("PASS!") + else: + print("FAIL!") + print("numpy : " + str(out2)) + print(f"{package} : " + str(output)) + assert False - start = time() - np.linalg.cholesky(input) - stop = time() - flops = (n**3) / 3 + 2 * n / 3 - total = (stop - start) / 1000.0 - print(f"Elapsed Time: {total} ms") - print(f"{flops / total} GOP/s") + +def cholesky(n, dtype, perform_check, timing, package): + input = num.eye(n, dtype=dtype) + + timer.start() + out1 = num.linalg.cholesky(input) + total = timer.stop() + + if perform_check: + check_equal_numpy(input, out1, package) + + if timing: + print(f"Elapsed Time: {total} ms") + flops = (n**3) / 3 + 2 * n / 3 + print(f"{flops / total / 1000000} GOP/s") + + return total if __name__ == "__main__": parser = argparse.ArgumentParser() + parser.add_argument( + "-t", + "--time", + dest="timing", + action="store_true", + help="perform timing", + ) parser.add_argument( "-n", "--num", type=int, default=10, dest="n", - help="number of rows in the matrix", + help="number of rows/cols in the matrix", ) parser.add_argument( - "-t", - "--type", + "-d", + "--dtype", default="float64", choices=["float32", "float64", "complex64", "complex128"], dest="dtype", help="data type", ) - args, unknown = parser.parse_known_args() - cholesky(args.n, args.dtype) + parser.add_argument( + "--check", + dest="check", + action="store_true", + help="compare result to numpy", + ) + args, num, timer = parse_args(parser) + + run_benchmark( + cholesky, + args.benchmark, + "Cholesky", + (args.n, args.dtype, args.check, args.timing, args.package), + ) diff --git a/examples/solve.py b/examples/solve.py index 91f92c6dd7..d87b74e51f 100644 --- a/examples/solve.py +++ b/examples/solve.py @@ -17,37 +17,64 @@ import argparse +import numpy from benchmark import parse_args, run_benchmark -def solve(m, n, nrhs, dtype): - a = np.random.rand(m, n).astype(dtype=dtype) - b = np.random.rand(n, nrhs).astype(dtype=dtype) +def check_result(a, b, x): + print("Checking result...") + + res = b - num.matmul(a, x) + max_res = num.linalg.norm(res, numpy.inf) + if max_res < 1e-04: + print(f"PASS! max-res = {max_res}") + else: + print(f"FAIL! max-res = {max_res}") + assert False + + +def solve(n, nrhs, dtype, perform_check, timing): + a = num.random.rand(n, n).astype(dtype=dtype) + b = num.random.rand(n, nrhs).astype(dtype=dtype) timer.start() - np.linalg.solve(a, b) + x = num.linalg.solve(a, b) total = timer.stop() - print(f"Elapsed Time: {total} ms") + if perform_check: + check_result(a, b, x) + + if timing: + print(f"Elapsed Time: {total} ms") + + if dtype in ["complex64", "complex128"]: + getrf_flops = (n**3) * 8 / 3 + getrs_flops = (n**2) * nrhs * 8 / 3 + else: + getrf_flops = (n**3) * 2 / 3 + getrs_flops = (n**2) * nrhs * 2 / 3 + flops = getrf_flops + getrs_flops + print(f"{flops / total / 1000000} GOP/s") + + return total if __name__ == "__main__": parser = argparse.ArgumentParser() parser.add_argument( - "-m", - "--num_rows", - type=int, - default=10, - dest="m", - help="number of rows in the matrix", + "-t", + "--time", + dest="timing", + action="store_true", + help="perform timing", ) parser.add_argument( "-n", - "--num_cols", + "--num", type=int, default=10, dest="n", - help="number of columns in the matrix", + help="number of rows/columns in the matrix", ) parser.add_argument( "-s", @@ -58,18 +85,24 @@ def solve(m, n, nrhs, dtype): help="number of right hand sides", ) parser.add_argument( - "-t", - "--type", + "-d", + "--dtype", default="float64", choices=["float32", "float64", "complex64", "complex128"], dest="dtype", help="data type", ) - args, np, timer = parse_args(parser) + parser.add_argument( + "--check", + dest="check", + action="store_true", + help="compare result to numpy", + ) + args, num, timer = parse_args(parser) run_benchmark( solve, args.benchmark, "Solve", - (args.m, args.n, args.nrhs, args.dtype), + (args.n, args.nrhs, args.dtype, args.check, args.timing), ) diff --git a/install.py b/install.py index 92d16ad55f..a532c988cd 100755 --- a/install.py +++ b/install.py @@ -134,6 +134,7 @@ def install_cunumeric( cuda_dir, cuda, curand_dir, + cusolvermp_dir, cutensor_dir, debug_release, debug, @@ -178,6 +179,7 @@ def install_cunumeric( print("cuda_dir: ", cuda_dir) print("cuda: ", cuda) print("curand_dir: ", curand_dir) + print("cusolvermp_dir: ", cusolvermp_dir) print("cutensor_dir: ", cutensor_dir) print("debug_release: ", debug_release) print("debug: ", debug) @@ -226,6 +228,7 @@ def validate_path(path): thrust_dir = validate_path(thrust_dir) curand_dir = validate_path(curand_dir) gasnet_dir = validate_path(gasnet_dir) + cusolvermp_dir = validate_path(cusolvermp_dir) cutensor_dir = validate_path(cutensor_dir) openblas_dir = validate_path(openblas_dir) @@ -247,6 +250,7 @@ def validate_path(path): print("thrust_dir: ", thrust_dir) print("curand_dir: ", curand_dir) print("gasnet_dir: ", gasnet_dir) + print("cusolvermp_dir: ", cusolvermp_dir) print("cutensor_dir: ", cutensor_dir) print("openblas_dir: ", openblas_dir) @@ -369,6 +373,8 @@ def validate_path(path): cmake_flags += ["-DThrust_ROOT=%s" % thrust_dir] if openblas_dir: cmake_flags += ["-DBLAS_DIR=%s" % openblas_dir] + if cusolvermp_dir: + cmake_flags += ["-DCUSOLVERMP_DIR=%s" % cusolvermp_dir] if cutensor_dir: cmake_flags += ["-Dcutensor_DIR=%s" % cutensor_dir] # A custom path to cuRAND is ignored when CUDA support is available @@ -483,6 +489,16 @@ def driver(): help="Path to cuRAND installation directory. This flag is ignored " "if Legate Core was built with CUDA support.", ) + # TODO(jfaibussowit) maybe split to --with-cusolvermp [bool] + # and a --with-cusolvermp-dir [dir] + parser.add_argument( + "--with-cusolvermp", + dest="cusolvermp_dir", + metavar="DIR", + required=False, + default=os.environ.get("CUSOLVERMP_PATH"), + help="Path to cuSolverMp installation directory.", + ) parser.add_argument( "--with-cutensor", dest="cutensor_dir", diff --git a/src/cunumeric/cuda_help.h b/src/cunumeric/cuda_help.h index a711ded9ed..b92201c1bc 100644 --- a/src/cunumeric/cuda_help.h +++ b/src/cunumeric/cuda_help.h @@ -23,6 +23,10 @@ #include "cunumeric/device_scalar_reduction_buffer.h" #include #include +#if LegateDefined(CUNUMERIC_USE_CUSOLVERMP) +#include +#include +#endif #include #include #include @@ -53,6 +57,12 @@ check_cusolver(__result__, __FILE__, __LINE__); \ } while (false) +#define CHECK_CAL(expr) \ + do { \ + calError_t __result__ = (expr); \ + check_cal(__result__, __FILE__, __LINE__); \ + } while (false) + #define CHECK_CUTENSOR(expr) \ do { \ cutensorStatus_t __result__ = (expr); \ @@ -74,6 +84,29 @@ namespace cunumeric { +template +struct cudaTypeToDataType; + +template <> +struct cudaTypeToDataType { + static constexpr cudaDataType type = CUDA_R_32F; +}; + +template <> +struct cudaTypeToDataType { + static constexpr cudaDataType type = CUDA_R_64F; +}; + +template <> +struct cudaTypeToDataType { + static constexpr cudaDataType type = CUDA_C_32F; +}; + +template <> +struct cudaTypeToDataType { + static constexpr cudaDataType type = CUDA_C_64F; +}; + __device__ inline size_t global_tid_1d() { return static_cast(blockIdx.x) * blockDim.x + threadIdx.x; @@ -139,6 +172,9 @@ struct cufftPlanParams { legate::cuda::StreamView get_cached_stream(); cublasHandle_t get_cublas(); cusolverDnHandle_t get_cusolver(); +#if LegateDefined(CUNUMERIC_USE_CUSOLVERMP) +cusolverMpHandle_t get_cusolvermp(); +#endif cutensorHandle_t* get_cutensor(); cufftContext get_cufft_plan(cufftType type, const cufftPlanParams& params); @@ -190,6 +226,24 @@ __host__ inline void check_cusolver(cusolverStatus_t status, const char* file, i } } +#if LegateDefined(CUNUMERIC_USE_CUSOLVERMP) +__host__ inline void check_cal(calError_t status, const char* file, int line) +{ + if (status != CAL_OK) { + fprintf(stderr, + "Internal libcal failure with error code %d in file %s at line %d\n", + status, + file, + line); +#ifdef DEBUG_CUNUMERIC + assert(false); +#else + exit(status); +#endif + } +} +#endif + __host__ inline void check_cutensor(cutensorStatus_t result, const char* file, int line) { if (result != CUTENSOR_STATUS_SUCCESS) { diff --git a/src/cunumeric/cudalibs.cu b/src/cunumeric/cudalibs.cu index bf17ad35e1..4a16ae7f22 100644 --- a/src/cunumeric/cudalibs.cu +++ b/src/cunumeric/cudalibs.cu @@ -268,7 +268,14 @@ cufftPlan* cufftPlanCache::get_cufft_plan(const cufftPlanParams& params) } CUDALibraries::CUDALibraries() - : finalized_(false), cublas_(nullptr), cusolver_(nullptr), cutensor_(nullptr), plan_caches_() + : finalized_(false), + cublas_(nullptr), + cusolver_(nullptr), +#if LegateDefined(CUNUMERIC_USE_CUSOLVERMP) + cusolvermp_(nullptr), +#endif + cutensor_(nullptr), + plan_caches_() { } @@ -285,6 +292,11 @@ void CUDALibraries::finalize() if (cusolver_ != nullptr) { finalize_cusolver(); } +#if LegateDefined(CUNUMERIC_USE_CUSOLVERMP) + if (cusolvermp_ != nullptr) { + finalize_cusolvermp(); + } +#endif if (cutensor_ != nullptr) { finalize_cutensor(); } @@ -306,6 +318,14 @@ void CUDALibraries::finalize_cusolver() cusolver_ = nullptr; } +#if LegateDefined(CUNUMERIC_USE_CUSOLVERMP) +void CUDALibraries::finalize_cusolvermp() +{ + CHECK_CUSOLVER(cusolverMpDestroy(cusolvermp_)); + cusolvermp_ = nullptr; +} +#endif + void CUDALibraries::finalize_cutensor() { delete cutensor_; @@ -336,6 +356,18 @@ cusolverDnHandle_t CUDALibraries::get_cusolver() return cusolver_; } +#if LegateDefined(CUNUMERIC_USE_CUSOLVERMP) +cusolverMpHandle_t CUDALibraries::get_cusolvermp() +{ + if (nullptr == cusolvermp_) { + int device = -1; + CHECK_CUDA(cudaGetDevice(&device)); + CHECK_CUSOLVER(cusolverMpCreate(&cusolvermp_, device, get_cached_stream())); + } + return cusolvermp_; +} +#endif + cutensorHandle_t* CUDALibraries::get_cutensor() { if (nullptr == cutensor_) { @@ -389,6 +421,15 @@ cusolverDnContext* get_cusolver() return lib.get_cusolver(); } +#if LegateDefined(CUNUMERIC_USE_CUSOLVERMP) +cusolverMpHandle* get_cusolvermp() +{ + const auto proc = legate::Processor::get_executing_processor(); + auto& lib = get_cuda_libraries(proc); + return lib.get_cusolvermp(); +} +#endif + cutensorHandle_t* get_cutensor() { const auto proc = legate::Processor::get_executing_processor(); @@ -414,6 +455,9 @@ class LoadCUDALibsTask : public CuNumericTask { auto& lib = get_cuda_libraries(proc); lib.get_cublas(); lib.get_cusolver(); +#if LegateDefined(CUNUMERIC_USE_CUSOLVERMP) + lib.get_cusolvermp(); +#endif lib.get_cutensor(); } }; diff --git a/src/cunumeric/cudalibs.h b/src/cunumeric/cudalibs.h index 37de6d8695..269bc09f6c 100644 --- a/src/cunumeric/cudalibs.h +++ b/src/cunumeric/cudalibs.h @@ -36,18 +36,27 @@ struct CUDALibraries { void finalize(); cublasHandle_t get_cublas(); cusolverDnHandle_t get_cusolver(); +#if LegateDefined(CUNUMERIC_USE_CUSOLVERMP) + cusolverMpHandle_t get_cusolvermp(); +#endif cutensorHandle_t* get_cutensor(); cufftContext get_cufft_plan(cufftType type, const cufftPlanParams& params); private: void finalize_cublas(); void finalize_cusolver(); +#if LegateDefined(CUNUMERIC_USE_CUSOLVERMP) + void finalize_cusolvermp(); +#endif void finalize_cutensor(); private: bool finalized_; cublasContext* cublas_; cusolverDnContext* cusolver_; +#if LegateDefined(CUNUMERIC_USE_CUSOLVERMP) + cusolverMpHandle* cusolvermp_; +#endif cutensorHandle_t* cutensor_; std::map plan_caches_; }; diff --git a/src/cunumeric/cunumeric.cc b/src/cunumeric/cunumeric.cc index ddb2153c37..e9a407812f 100644 --- a/src/cunumeric/cunumeric.cc +++ b/src/cunumeric/cunumeric.cc @@ -78,4 +78,13 @@ bool cunumeric_has_curand() return false; #endif } + +bool cunumeric_has_cusolvermp() +{ +#if LegateDefined(LEGATE_USE_CUDA) && LegateDefined(CUNUMERIC_USE_CUSOLVERMP) + return true; +#else + return false; +#endif +} } diff --git a/src/cunumeric/cunumeric_c.h b/src/cunumeric/cunumeric_c.h index f8839f4402..07e8fc6f72 100644 --- a/src/cunumeric/cunumeric_c.h +++ b/src/cunumeric/cunumeric_c.h @@ -51,6 +51,8 @@ enum CuNumericOpCode { CUNUMERIC_LOAD_CUDALIBS, CUNUMERIC_MATMUL, CUNUMERIC_MATVECMUL, + CUNUMERIC_MP_POTRF, + CUNUMERIC_MP_SOLVE, CUNUMERIC_NONZERO, CUNUMERIC_PACKBITS, CUNUMERIC_POTRF, @@ -341,6 +343,8 @@ typedef struct ReductionOpIds { void cunumeric_perform_registration(); bool cunumeric_has_curand(); +bool cunumeric_has_cusolvermp(); + struct ReductionOpIds cunumeric_register_reduction_ops(int32_t code); #ifdef __cplusplus diff --git a/src/cunumeric/mapper.cc b/src/cunumeric/mapper.cc index e260dd165c..c3ac9a99d5 100644 --- a/src/cunumeric/mapper.cc +++ b/src/cunumeric/mapper.cc @@ -128,7 +128,9 @@ std::vector CuNumericMapper::store_mappings( case CUNUMERIC_TRSM: case CUNUMERIC_SOLVE: case CUNUMERIC_SYRK: - case CUNUMERIC_GEMM: { + case CUNUMERIC_GEMM: + case CUNUMERIC_MP_POTRF: + case CUNUMERIC_MP_SOLVE: { std::vector mappings; auto inputs = task.inputs(); auto outputs = task.outputs(); diff --git a/src/cunumeric/matrix/mp_potrf.cu b/src/cunumeric/matrix/mp_potrf.cu new file mode 100644 index 0000000000..84a66a62b9 --- /dev/null +++ b/src/cunumeric/matrix/mp_potrf.cu @@ -0,0 +1,146 @@ +/* Copyright 2023 NVIDIA Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +#include "cunumeric/matrix/mp_potrf.h" +#include "cunumeric/matrix/mp_potrf_template.inl" + +#include "cunumeric/cuda_help.h" + +namespace cunumeric { + +using namespace Legion; +using namespace legate; + +template +static inline void mp_potrf_template( + cal_comm_t comm, int nprow, int npcol, int64_t n, int64_t nb, VAL* array, int64_t lld) +{ + const auto uplo = CUBLAS_FILL_MODE_LOWER; + + auto context = get_cusolvermp(); + auto stream = get_cached_stream(); + + cusolverMpGrid_t grid = nullptr; + CHECK_CUSOLVER(cusolverMpCreateDeviceGrid( + context, &grid, comm, nprow, npcol, CUSOLVERMP_GRID_MAPPING_COL_MAJOR)); + + cusolverMpMatrixDescriptor_t desc = nullptr; + CHECK_CUSOLVER(cusolverMpCreateMatrixDesc( + &desc, grid, cudaTypeToDataType::type, n, n, nb, nb, 0, 0, lld)); + + size_t device_buffer_size = 0; + size_t host_buffer_size = 0; + CHECK_CUSOLVER(cusolverMpPotrf_bufferSize(context, + uplo, + n, + array, + 1, + 1, + desc, + cudaTypeToDataType::type, + &device_buffer_size, + &host_buffer_size)); + + auto device_buffer = create_buffer(device_buffer_size, Memory::Kind::GPU_FB_MEM); + auto host_buffer = create_buffer(host_buffer_size, Memory::Kind::Z_COPY_MEM); + auto info = create_buffer(1, Memory::Kind::Z_COPY_MEM); + + // initialize to zero + info[0] = 0; + + CHECK_CUSOLVER(cusolverMpPotrf(context, + uplo, + n, + array, + 1, + 1, + desc, + cudaTypeToDataType::type, + device_buffer.ptr(0), + device_buffer_size, + host_buffer.ptr(0), + host_buffer_size, + info.ptr(0))); + + // TODO: We need a deferred exception to avoid this synchronization + CHECK_CAL(cal_stream_sync(comm, stream)); + CHECK_CUDA_STREAM(stream); + + CHECK_CUSOLVER(cusolverMpDestroyMatrixDesc(desc)); + CHECK_CUSOLVER(cusolverMpDestroyGrid(grid)); + + if (info[0] != 0) { + throw legate::TaskException("Matrix is not positive definite"); + } +} + +template <> +struct MpPotrfImplBody { + void operator()( + cal_comm_t comm, int nprow, int npcol, int64_t n, int64_t nb, float* array, int64_t lld) + { + mp_potrf_template(comm, nprow, npcol, n, nb, array, lld); + } +}; + +template <> +struct MpPotrfImplBody { + void operator()( + cal_comm_t comm, int nprow, int npcol, int64_t n, int64_t nb, double* array, int64_t lld) + { + mp_potrf_template(comm, nprow, npcol, n, nb, array, lld); + } +}; + +template <> +struct MpPotrfImplBody { + void operator()(cal_comm_t comm, + int nprow, + int npcol, + int64_t n, + int64_t nb, + complex* array, + int64_t lld) + { + mp_potrf_template(comm, nprow, npcol, n, nb, reinterpret_cast(array), lld); + } +}; + +template <> +struct MpPotrfImplBody { + void operator()(cal_comm_t comm, + int nprow, + int npcol, + int64_t n, + int64_t nb, + complex* array, + int64_t lld) + { + mp_potrf_template(comm, nprow, npcol, n, nb, reinterpret_cast(array), lld); + } +}; + +/*static*/ void MpPotrfTask::gpu_variant(TaskContext context) +{ + mp_potrf_template(context); +} + +namespace // unnamed +{ +static void __attribute__((constructor)) register_tasks(void) { MpPotrfTask::register_variants(); } +} // namespace + +} // namespace cunumeric \ No newline at end of file diff --git a/src/cunumeric/matrix/mp_potrf.h b/src/cunumeric/matrix/mp_potrf.h new file mode 100644 index 0000000000..11a625b679 --- /dev/null +++ b/src/cunumeric/matrix/mp_potrf.h @@ -0,0 +1,33 @@ +/* Copyright 2023 NVIDIA Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +#pragma once + +#include "cunumeric/cunumeric_task.h" + +namespace cunumeric { + +class MpPotrfTask : public CuNumericTask { + public: + static const int TASK_ID = CUNUMERIC_MP_POTRF; + + public: +#if LegateDefined(LEGATE_USE_CUDA) + static void gpu_variant(legate::TaskContext context); +#endif +}; + +} // namespace cunumeric \ No newline at end of file diff --git a/src/cunumeric/matrix/mp_potrf_template.inl b/src/cunumeric/matrix/mp_potrf_template.inl new file mode 100644 index 0000000000..8a016de2e7 --- /dev/null +++ b/src/cunumeric/matrix/mp_potrf_template.inl @@ -0,0 +1,167 @@ +/* Copyright 2023 NVIDIA Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +#pragma once + +#include + +#include "core/comm/coll.h" + +// Useful for IDEs +#include "cunumeric/matrix/mp_potrf.h" +#include "cunumeric/cuda_help.h" +#include "cunumeric/utilities/repartition.h" + +#include + +namespace cunumeric { + +using namespace Legion; +using namespace legate; + +template +struct MpPotrfImplBody; + +template +struct support_mp_potrf : std::false_type {}; +template <> +struct support_mp_potrf : std::true_type {}; +template <> +struct support_mp_potrf : std::true_type {}; +template <> +struct support_mp_potrf : std::true_type {}; +template <> +struct support_mp_potrf : std::true_type {}; + +template +struct MpPotrfImpl { + template ::value>* = nullptr> + void operator()(int64_t n, + int64_t nb, + legate::PhysicalStore input_array, + legate::PhysicalStore output_array, + std::vector comms, + const Domain& launch_domain) const + { + using VAL = type_of; + + auto input_shape = input_array.shape<2>(); + auto output_shape = output_array.shape<2>(); + + int rank, num_ranks; + auto nccl_comm = comms[0]; + auto cal_comm = comms[1].get(); + assert(cal_comm); + CHECK_CAL(cal_comm_get_rank(cal_comm, &rank)); + CHECK_CAL(cal_comm_get_size(cal_comm, &num_ranks)); + assert(launch_domain.get_volume() == num_ranks); + assert(launch_domain.get_dim() <= 2); + + assert(input_shape == output_shape); + + bool input_col_major = + input_shape.empty() || + input_array.read_accessor(input_shape).accessor.is_dense_col_major(input_shape); + bool output_col_major = + output_shape.empty() || + output_array.write_accessor(output_shape).accessor.is_dense_col_major(output_shape); + assert(input_col_major); + assert(output_col_major); + + size_t strides[2]; + + auto input_arr = input_shape.empty() + ? nullptr + : input_array.read_accessor(input_shape).ptr(input_shape, strides); + auto output_arr = + output_shape.empty() + ? nullptr + : output_array.write_accessor(output_shape).ptr(output_shape, strides); + auto num_rows = + input_shape.hi[0] < input_shape.lo[0] ? 0 : input_shape.hi[0] - input_shape.lo[0] + 1; + auto num_cols = + input_shape.hi[1] < input_shape.lo[1] ? 0 : input_shape.hi[1] - input_shape.lo[1] + 1; + auto lld = input_shape.empty() ? 1 : (input_col_major ? num_rows : num_cols); + + // the 2dbc process domain should go in both dimensions (8x1) -> (4x2) + size_t nprow = num_ranks; + size_t npcol = 1; + while (npcol * 2 <= nprow && nprow % 2 == 0) { + npcol *= 2; + nprow /= 2; + } + + assert(nprow * npcol == num_ranks); + assert(n > 0 && nb > 0 && lld > 0 && nprow > 0 && npcol > 0); + + auto offset_r = input_shape.lo[0]; + auto offset_c = input_shape.lo[1]; + auto volume = num_rows * num_cols; + + auto [buffer_2dbc, volume_2dbc, lld_2dbc] = repartition_matrix_2dbc( + input_arr, volume, false, offset_r, offset_c, lld, nprow, npcol, nb, nb, nccl_comm); + + MpPotrfImplBody()(cal_comm, nprow, npcol, n, nb, buffer_2dbc.ptr(0), lld_2dbc); + + repartition_matrix_block(buffer_2dbc, + volume_2dbc, + lld_2dbc, + rank, + nprow, + npcol, + nb, + nb, + output_arr, + volume, + lld, + num_rows, + num_cols, + false, + offset_r, + offset_c, + nccl_comm); + } + + template ::value>* = nullptr> + void operator()(int64_t n, + int64_t nb, + legate::PhysicalStore input_array, + legate::PhysicalStore output_array, + std::vector comms, + const Domain& launch_domain) const + { + assert(false); + } +}; + +template +static void mp_potrf_template(TaskContext& context) +{ + legate::PhysicalStore input_array = context.inputs()[0]; + legate::PhysicalStore output_array = context.outputs()[0]; + auto n = context.scalars()[0].value(); + auto nb = context.scalars()[1].value(); + type_dispatch(input_array.code(), + MpPotrfImpl{}, + n, + nb, + input_array, + output_array, + context.communicators(), + context.get_launch_domain()); +} + +} // namespace cunumeric \ No newline at end of file diff --git a/src/cunumeric/matrix/mp_solve.cu b/src/cunumeric/matrix/mp_solve.cu new file mode 100644 index 0000000000..a8b0b37af3 --- /dev/null +++ b/src/cunumeric/matrix/mp_solve.cu @@ -0,0 +1,247 @@ +/* Copyright 2023 NVIDIA Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +#include "cunumeric/matrix/mp_solve.h" +#include "cunumeric/matrix/mp_solve_template.inl" + +#include "cunumeric/cuda_help.h" + +namespace cunumeric { + +using namespace Legion; +using namespace legate; + +template +static inline void mp_solve_template(cal_comm_t comm, + int nprow, + int npcol, + int64_t n, + int64_t nrhs, + int64_t nb, + VAL* a_array, + int64_t llda, + VAL* b_array, + int64_t lldb) +{ + const auto trans = CUBLAS_OP_N; + + auto context = get_cusolvermp(); + auto stream = get_cached_stream(); + + cusolverMpGrid_t grid = nullptr; + CHECK_CUSOLVER(cusolverMpCreateDeviceGrid( + context, &grid, comm, nprow, npcol, CUSOLVERMP_GRID_MAPPING_COL_MAJOR)); + + cusolverMpMatrixDescriptor_t a_desc = nullptr; + CHECK_CUSOLVER(cusolverMpCreateMatrixDesc( + &a_desc, grid, cudaTypeToDataType::type, n, n, nb, nb, 0, 0, llda)); + + cusolverMpMatrixDescriptor_t b_desc = nullptr; + CHECK_CUSOLVER(cusolverMpCreateMatrixDesc( + &b_desc, grid, cudaTypeToDataType::type, n, nrhs, nb, nb, 0, 0, lldb)); + + size_t getrf_device_buffer_size = 0; + size_t getrf_host_buffer_size = 0; + CHECK_CUSOLVER(cusolverMpGetrf_bufferSize(context, + n, + n, + a_array, + 1, + 1, + a_desc, + nullptr, + cudaTypeToDataType::type, + &getrf_device_buffer_size, + &getrf_host_buffer_size)); + + size_t getrs_device_buffer_size = 0; + size_t getrs_host_buffer_size = 0; + CHECK_CUSOLVER(cusolverMpGetrs_bufferSize(context, + trans, + n, + nrhs, + a_array, + 1, + 1, + a_desc, + nullptr, + b_array, + 1, + 1, + b_desc, + cudaTypeToDataType::type, + &getrs_device_buffer_size, + &getrs_host_buffer_size)); + + auto device_buffer = create_buffer( + std::max(getrf_device_buffer_size, getrs_device_buffer_size), Memory::Kind::GPU_FB_MEM); + auto host_buffer = create_buffer(std::max(getrf_host_buffer_size, getrs_host_buffer_size), + Memory::Kind::Z_COPY_MEM); + auto info = create_buffer(1, Memory::Kind::Z_COPY_MEM); + + // initialize to zero + info[0] = 0; + + CHECK_CUSOLVER(cusolverMpGetrf(context, + n, + n, + a_array, + 1, + 1, + a_desc, + nullptr, + cudaTypeToDataType::type, + device_buffer.ptr(0), + getrf_device_buffer_size, + host_buffer.ptr(0), + getrf_host_buffer_size, + info.ptr(0))); + + if (info[0] != 0) { + throw legate::TaskException("Matrix is singular"); + } + + CHECK_CUSOLVER(cusolverMpGetrs(context, + trans, + n, + nrhs, + a_array, + 1, + 1, + a_desc, + nullptr, + b_array, + 1, + 1, + b_desc, + cudaTypeToDataType::type, + device_buffer.ptr(0), + getrs_device_buffer_size, + host_buffer.ptr(0), + getrs_host_buffer_size, + info.ptr(0))); + + // TODO: We need a deferred exception to avoid this synchronization + CHECK_CAL(cal_stream_sync(comm, stream)); + CHECK_CUDA_STREAM(stream); + + CHECK_CUSOLVER(cusolverMpDestroyMatrixDesc(a_desc)); + CHECK_CUSOLVER(cusolverMpDestroyMatrixDesc(b_desc)); + CHECK_CUSOLVER(cusolverMpDestroyGrid(grid)); + + // FIXME: this should be synchronized with all participating tasks in order to quit gracefully + if (info[0] != 0) { + throw legate::TaskException("Matrix is singular"); + } +} + +template <> +struct MpSolveImplBody { + void operator()(cal_comm_t comm, + int nprow, + int npcol, + int64_t n, + int64_t nrhs, + int64_t nb, + float* a_array, + int64_t llda, + float* b_array, + int64_t lldb) + { + mp_solve_template(comm, nprow, npcol, n, nrhs, nb, a_array, llda, b_array, lldb); + } +}; + +template <> +struct MpSolveImplBody { + void operator()(cal_comm_t comm, + int nprow, + int npcol, + int64_t n, + int64_t nrhs, + int64_t nb, + double* a_array, + int64_t llda, + double* b_array, + int64_t lldb) + { + mp_solve_template(comm, nprow, npcol, n, nrhs, nb, a_array, llda, b_array, lldb); + } +}; + +template <> +struct MpSolveImplBody { + void operator()(cal_comm_t comm, + int nprow, + int npcol, + int64_t n, + int64_t nrhs, + int64_t nb, + complex* a_array, + int64_t llda, + complex* b_array, + int64_t lldb) + { + mp_solve_template(comm, + nprow, + npcol, + n, + nrhs, + nb, + reinterpret_cast(a_array), + llda, + reinterpret_cast(b_array), + lldb); + } +}; + +template <> +struct MpSolveImplBody { + void operator()(cal_comm_t comm, + int nprow, + int npcol, + int64_t n, + int64_t nrhs, + int64_t nb, + complex* a_array, + int64_t llda, + complex* b_array, + int64_t lldb) + { + mp_solve_template(comm, + nprow, + npcol, + n, + nrhs, + nb, + reinterpret_cast(a_array), + llda, + reinterpret_cast(b_array), + lldb); + } +}; + +/*static*/ void MpSolveTask::gpu_variant(TaskContext context) +{ + mp_solve_template(context); +} + +namespace // unnamed +{ +static void __attribute__((constructor)) register_tasks(void) { MpSolveTask::register_variants(); } +} // namespace + +} // namespace cunumeric \ No newline at end of file diff --git a/src/cunumeric/matrix/mp_solve.h b/src/cunumeric/matrix/mp_solve.h new file mode 100644 index 0000000000..48cd9ecc6a --- /dev/null +++ b/src/cunumeric/matrix/mp_solve.h @@ -0,0 +1,33 @@ +/* Copyright 2021-2022 NVIDIA Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +#pragma once + +#include "cunumeric/cunumeric_task.h" + +namespace cunumeric { + +class MpSolveTask : public CuNumericTask { + public: + static const int TASK_ID = CUNUMERIC_MP_SOLVE; + + public: +#if LegateDefined(LEGATE_USE_CUDA) + static void gpu_variant(legate::TaskContext context); +#endif +}; + +} // namespace cunumeric \ No newline at end of file diff --git a/src/cunumeric/matrix/mp_solve_template.inl b/src/cunumeric/matrix/mp_solve_template.inl new file mode 100644 index 0000000000..3489d13574 --- /dev/null +++ b/src/cunumeric/matrix/mp_solve_template.inl @@ -0,0 +1,195 @@ +/* Copyright 2023 NVIDIA Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +#pragma once + +#include + +#include "core/comm/coll.h" + +// Useful for IDEs +#include "cunumeric/matrix/mp_solve.h" +#include "cunumeric/cuda_help.h" +#include "cunumeric/utilities/repartition.h" + +#include + +namespace cunumeric { + +using namespace Legion; +using namespace legate; + +template +struct MpSolveImplBody; + +template +struct support_mp_solve : std::false_type {}; +template <> +struct support_mp_solve : std::true_type {}; +template <> +struct support_mp_solve : std::true_type {}; +template <> +struct support_mp_solve : std::true_type {}; +template <> +struct support_mp_solve : std::true_type {}; + +template +struct MpSolveImpl { + template ::value>* = nullptr> + void operator()(int64_t n, + int64_t nrhs, + int64_t nb, + legate::PhysicalStore a_array, + legate::PhysicalStore b_array, + legate::PhysicalStore x_array, + std::vector comms, + const Domain& launch_domain) const + { + using VAL = type_of; + + auto nccl_comm = comms[0]; + auto cal_comm = comms[1].get(); + + int rank, num_ranks; + assert(cal_comm); + CHECK_CAL(cal_comm_get_rank(cal_comm, &rank)); + CHECK_CAL(cal_comm_get_size(cal_comm, &num_ranks)); + assert(launch_domain.get_volume() == num_ranks); + assert(launch_domain.get_dim() <= 2); + + // enforce 2D shape for simplicity -- although b/x might be 1D + auto a_shape = a_array.shape<2>(); + auto b_shape = b_array.shape<2>(); + auto x_shape = x_array.shape<2>(); + + assert(x_shape == b_shape); + + auto a_arr = a_shape.empty() ? nullptr : a_array.read_accessor(a_shape).ptr(a_shape.lo); + auto b_arr = b_shape.empty() ? nullptr : b_array.read_accessor(b_shape).ptr(b_shape.lo); + auto x_arr = + x_shape.empty() ? nullptr : x_array.write_accessor(x_shape).ptr(x_shape.lo); + + // assume col-major input + auto llda = a_shape.empty() ? 1 : (a_shape.hi[0] - a_shape.lo[0] + 1); + auto lldb = b_shape.empty() ? 1 : (b_shape.hi[0] - b_shape.lo[0] + 1); + + // the 2dbc process domain should go in both dimensions (8x1) -> (4x2) + size_t nprow = num_ranks; + size_t npcol = 1; + while (npcol * 2 <= nprow && nprow % 2 == 0) { + npcol *= 2; + nprow /= 2; + } + + assert(nprow * npcol == num_ranks); + assert(n > 0 && nrhs > 0 && nb > 0 && llda > 0 && lldb > 0 && nprow > 0 && npcol > 0); + + // the 2dbc conversion seems to have issues for row-wise ordered data, therefore we enforce + // col-based in the mapper + bool a_col_major = a_shape.empty() || + a_array.read_accessor(a_shape).accessor.is_dense_col_major(a_shape); + bool b_col_major = b_shape.empty() || + b_array.read_accessor(b_shape).accessor.is_dense_col_major(b_shape); + bool x_col_major = x_shape.empty() || + x_array.write_accessor(x_shape).accessor.is_dense_col_major(x_shape); + + assert(a_col_major); + assert(b_col_major); + assert(x_col_major); + + auto a_offset_r = a_shape.lo[0]; + auto a_offset_c = a_shape.lo[1]; + auto a_volume = a_shape.empty() ? 0 : llda * (a_shape.hi[1] - a_shape.lo[1] + 1); + + auto [a_buffer_2dbc, a_volume_2dbc, a_lld_2dbc] = repartition_matrix_2dbc( + a_arr, a_volume, false, a_offset_r, a_offset_c, llda, nprow, npcol, nb, nb, nccl_comm); + + auto b_offset_r = b_shape.lo[0]; + auto b_offset_c = b_shape.lo[1]; + auto b_volume = b_shape.empty() ? 0 : lldb * (b_shape.hi[1] - b_shape.lo[1] + 1); + + auto [b_buffer_2dbc, b_volume_2dbc, b_lld_2dbc] = repartition_matrix_2dbc( + b_arr, b_volume, false, b_offset_r, b_offset_c, lldb, nprow, npcol, nb, nb, nccl_comm); + + MpSolveImplBody()(cal_comm, + nprow, + npcol, + n, + nrhs, + nb, + a_buffer_2dbc.ptr(0), + a_lld_2dbc, + b_buffer_2dbc.ptr(0), + b_lld_2dbc); + + auto b_num_rows = b_shape.hi[0] < b_shape.lo[0] ? 0 : b_shape.hi[0] - b_shape.lo[0] + 1; + auto b_num_cols = b_shape.hi[1] < b_shape.lo[1] ? 0 : b_shape.hi[1] - b_shape.lo[1] + 1; + + repartition_matrix_block(b_buffer_2dbc, + b_volume_2dbc, + b_lld_2dbc, + rank, + nprow, + npcol, + nb, + nb, + x_arr, + b_volume, + lldb, + b_num_rows, + b_num_cols, + false, // x_shape is enforced col-major + b_offset_r, + b_offset_c, + nccl_comm); + } + + template ::value>* = nullptr> + void operator()(int64_t n, + int64_t nrhs, + int64_t nb, + legate::PhysicalStore a_array, + legate::PhysicalStore b_array, + legate::PhysicalStore x_array, + std::vector comms, + const Domain& launch_domain) const + { + assert(false); + } +}; + +template +static void mp_solve_template(TaskContext& context) +{ + legate::PhysicalStore a_array = context.inputs()[0]; + legate::PhysicalStore b_array = context.inputs()[1]; + legate::PhysicalStore x_array = context.outputs()[0]; + auto n = context.scalars()[0].value(); + auto nrhs = context.scalars()[1].value(); + auto nb = context.scalars()[2].value(); + type_dispatch(a_array.code(), + MpSolveImpl{}, + n, + nrhs, + nb, + a_array, + b_array, + x_array, + context.communicators(), + context.get_launch_domain()); +} + +} // namespace cunumeric \ No newline at end of file diff --git a/src/cunumeric/sort/sort.cu b/src/cunumeric/sort/sort.cu index 6281c2cf1f..7193e11984 100644 --- a/src/cunumeric/sort/sort.cu +++ b/src/cunumeric/sort/sort.cu @@ -84,11 +84,13 @@ void local_sort(const type_of* values_in, values_in, values_out, indices_in, indices_out, volume, sort_dim_size, stable, stream); } +namespace { // auto align to multiples of 16 bytes auto get_16b_aligned = [](auto bytes) { return std::max(16, (bytes + 15) / 16 * 16); }; auto get_16b_aligned_count = [](auto count, auto element_bytes) { return (get_16b_aligned(count * element_bytes) + element_bytes - 1) / element_bytes; }; +} // namespace // increase number of columns computed per block as long as either // 1. we have more threads in block than elements in row diff --git a/src/cunumeric/utilities/repartition.cc b/src/cunumeric/utilities/repartition.cc new file mode 100644 index 0000000000..59223a81d0 --- /dev/null +++ b/src/cunumeric/utilities/repartition.cc @@ -0,0 +1,71 @@ +/* Copyright 2022 NVIDIA Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +#include "repartition.h" + +namespace cunumeric { + +std::tuple elements_for_rank_in_dimension( + size_t dim_length, size_t offset_id, size_t proc_id, size_t num_dim_procs, size_t tilesize) +{ + size_t start_tile_idx = offset_id / tilesize; + size_t start_tile_proc_id = start_tile_idx % num_dim_procs; + size_t start_pos_offset = proc_id >= start_tile_proc_id + ? (proc_id - start_tile_proc_id) * tilesize + : (num_dim_procs + proc_id - start_tile_proc_id) * tilesize; + size_t start_tile_offset = offset_id % tilesize; + + if (start_tile_offset > 0 && start_pos_offset > 0) { + // we can move the start position left to the start of the tile + start_pos_offset -= start_tile_offset; + } + + // calc global offset for procId + size_t offset_tiles = (start_tile_idx + num_dim_procs - proc_id - 1) / num_dim_procs; + + if (start_pos_offset > dim_length) { + return {0ul, offset_tiles}; + } + + size_t full_cycles = (dim_length - start_pos_offset) / (tilesize * num_dim_procs); + size_t num_elements = full_cycles * tilesize; + size_t remainder = dim_length - start_pos_offset - num_elements * num_dim_procs; + if (start_pos_offset > 0 || start_tile_offset == 0) { + // we have a clean start + if (remainder > 0) { + num_elements += std::min(tilesize, remainder); + } + } else { + // we start with a partial tile + size_t tile_remainder = tilesize - start_tile_offset; + if (remainder <= tile_remainder) { + num_elements += remainder; + } else { + remainder -= tile_remainder; + num_elements += tile_remainder; + if (remainder > (num_dim_procs - 1) * tilesize) { + num_elements += std::min(tilesize, remainder - (num_dim_procs - 1) * tilesize); + } + } + } + + size_t offset_elements = + offset_tiles * tilesize + (start_pos_offset == 0 ? start_tile_offset : 0); + + return {num_elements, offset_elements}; +} + +} // namespace cunumeric \ No newline at end of file diff --git a/src/cunumeric/utilities/repartition.cu b/src/cunumeric/utilities/repartition.cu new file mode 100644 index 0000000000..4355d7a4df --- /dev/null +++ b/src/cunumeric/utilities/repartition.cu @@ -0,0 +1,1357 @@ +/* Copyright 2022 NVIDIA Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +#include "repartition.h" + +#include "cunumeric/cuda_help.h" + +namespace cunumeric { + +using namespace Legion; +using namespace legate; + +namespace { +// auto align to multiples of 16 bytes +constexpr auto get_16b_aligned = [](auto bytes) { + return std::max(16, (bytes + 15) / 16 * 16); +}; +constexpr auto get_16b_aligned_count = [](auto count, auto element_bytes) { + return (get_16b_aligned(count * element_bytes) + element_bytes - 1) / element_bytes; +}; + +const auto is_device_only_ptr = [](const void* ptr) { + cudaPointerAttributes attrs; + auto res = cudaPointerGetAttributes(&attrs, ptr); + if (res == cudaSuccess) { + return attrs.type == cudaMemoryTypeDevice; + } else { + cudaGetLastError(); + return false; + } +}; +} // namespace + +template +__global__ static void __launch_bounds__(THREADS_PER_BLOCK, MIN_CTAS_PER_SM) + split_data_to_send_buffers(const VAL* input_2dbc, + size_t input_volume, + size_t input_lld, + Buffer send_info, + size_t stored_size_per_rank, + Buffer send_buffers_ptr, + size_t p_r, + size_t p_c, + size_t tile_r, + size_t tile_c) +{ + size_t thread_offset = blockIdx.x * blockDim.x + threadIdx.x; + size_t threadgroup_size = blockDim.x * gridDim.x; + size_t rank_id = blockIdx.y * blockDim.y + threadIdx.y; + + if (rank_id >= p_r * p_c) { + return; + } + + size_t source_size = send_info[rank_id * stored_size_per_rank + BlockInfo::TOTAL_SIZE]; + size_t source_lld = send_info[rank_id * stored_size_per_rank + BlockInfo::LLD]; + size_t source_offset_row = send_info[rank_id * stored_size_per_rank + BlockInfo::OFFSET_ROW]; + size_t source_offset_col = send_info[rank_id * stored_size_per_rank + BlockInfo::OFFSET_COL]; + + // copy large block from input with all elements for target rank_id + for (size_t pos = thread_offset; pos < source_size; pos += threadgroup_size) { + size_t source_row_id = source_offset_row + pos % source_lld; + size_t source_col_id = source_offset_col + pos / source_lld; + size_t index_in = source_col_id * input_lld + source_row_id; + + assert(index_in < input_volume); + send_buffers_ptr[rank_id][pos] = input_2dbc[index_in]; + } +} + +template +__global__ static void __launch_bounds__(THREADS_PER_BLOCK, MIN_CTAS_PER_SM) + merge_data_to_result(Buffer result_2dbc, + size_t volume, + Buffer recv_info, + size_t stored_size_per_rank, + Buffer merge_buffers, + size_t target_lld, + size_t tile_r, + size_t tile_c, + size_t my_rank, + size_t num_ranks) +{ + size_t thread_offset = blockIdx.x * blockDim.x + threadIdx.x; + size_t threadgroup_size = blockDim.x * gridDim.x; + size_t rank_id = blockIdx.y * blockDim.y + threadIdx.y; + + if (rank_id >= num_ranks) { + return; + } + + size_t source_size = recv_info[rank_id * stored_size_per_rank + BlockInfo::TOTAL_SIZE]; + size_t source_lld = recv_info[rank_id * stored_size_per_rank + BlockInfo::LLD]; + size_t source_offset_row = recv_info[rank_id * stored_size_per_rank + BlockInfo::OFFSET_ROW]; + size_t source_offset_col = recv_info[rank_id * stored_size_per_rank + BlockInfo::OFFSET_COL]; + + for (size_t pos = thread_offset; pos < source_size; pos += threadgroup_size) { + size_t target_col_id = source_offset_col + pos / source_lld; + size_t target_row_id = source_offset_row + pos % source_lld; + + // store elementwise + size_t index_out = target_col_id * target_lld + target_row_id; + + assert(index_out < volume); + result_2dbc[index_out] = merge_buffers[rank_id][pos]; + } +} + +__device__ __inline__ std::tuple compute_tile_info(size_t num_rows, + size_t num_cols, + size_t row_major, + size_t lld, + size_t offset_r, + size_t offset_c, + size_t tile_r, + size_t tile_c) +{ + // position info + // get local tile size and start position + size_t tile_r_size = tile_r; + size_t tile_c_size = tile_c; + size_t start_pos; + // special cases for first/last tile + { + size_t start_r_offset = offset_r % tile_r; + size_t start_c_offset = offset_c % tile_c; + size_t start_pos_r = blockIdx.x * tile_r; + size_t start_pos_c = blockIdx.y * tile_c; + + // rows + if (start_r_offset > 0) { + if (blockIdx.x == 0) { + tile_r_size -= start_r_offset; + } else { + start_pos_r -= start_r_offset; + } + } + if (blockIdx.x == gridDim.x - 1) { + size_t last_element_offset = (num_rows + start_r_offset) % tile_r; + if (last_element_offset > 0) { + tile_r_size -= (tile_r - last_element_offset); + } + } + // cols + if (start_c_offset > 0) { + if (blockIdx.y == 0) { + tile_c_size -= start_c_offset; + } else { + start_pos_c -= start_c_offset; + } + } + if (blockIdx.y == gridDim.y - 1) { + size_t last_element_offset = (num_cols + start_c_offset) % tile_c; + if (last_element_offset > 0) { + tile_c_size -= (tile_c - last_element_offset); + } + } + + start_pos = row_major ? start_pos_r * lld + start_pos_c : start_pos_c * lld + start_pos_r; + } + + return {tile_r_size, tile_c_size, start_pos}; +} + +__device__ __inline__ std::tuple compute_2dbc_info( + Buffer info, + size_t stored_size_per_rank, + size_t p_r, + size_t p_c, + size_t tile_idx_row, + size_t tile_idx_col, + size_t tile_r, + size_t tile_c) +{ + size_t rank_r = tile_idx_row % p_r; + size_t rank_c = tile_idx_col % p_c; + size_t rank_id = rank_r + rank_c * p_r; // tile ranks are col major + size_t size = info[rank_id * stored_size_per_rank + BlockInfo::TOTAL_SIZE]; + size_t lld = info[rank_id * stored_size_per_rank + BlockInfo::LLD]; + size_t start_pos = 0; + // compute start position of tile (tile_idx_row/tile_idx_col) within source + { + // this is where the OUR part of the whole 2dbc dist of the target rank resides + size_t offset_row = info[rank_id * stored_size_per_rank + BlockInfo::OFFSET_ROW]; + size_t offset_col = info[rank_id * stored_size_per_rank + BlockInfo::OFFSET_COL]; + + // this is where the tile starts / it does not have to be where our PART of the tile starts + size_t tile_pos_row = (tile_idx_row - rank_r) / p_r * tile_r; + size_t tile_pos_col = (tile_idx_col - rank_c) / p_c * tile_c; + + // shift to the positions where our PART of the tile starts + if (tile_pos_row > offset_row) { + tile_pos_row -= offset_row; + } else { + tile_pos_row = 0; + } + if (tile_pos_col > offset_col) { + tile_pos_col -= offset_col; + } else { + tile_pos_col = 0; + } + + start_pos = tile_pos_col * lld + tile_pos_row; // always col major + } + return {rank_id, size, lld, start_pos}; +} + +#define BLOCK_DIM 16 +template +__device__ __inline__ void transfer_data_src_tgt(const VAL* source, + size_t source_size, + size_t source_lld, + bool source_row_major, + size_t source_start_pos, + VAL* target, + size_t target_size, + size_t target_lld, + size_t target_row_major, + size_t target_start_pos, + size_t tile_r_size, + size_t tile_c_size, + VAL block[BLOCK_DIM][BLOCK_DIM + 1]) +{ + if (source_row_major != target_row_major) { + for (size_t tile_r_pos = 0; tile_r_pos < tile_r_size; tile_r_pos += BLOCK_DIM) { + for (size_t tile_c_pos = 0; tile_c_pos < tile_c_size; tile_c_pos += BLOCK_DIM) { + // we are at offset tile_r_pos/tile_c_pos within our tile (start of block) + // blocks are square, tiles don't need to be! + size_t tile_r_pos_t = tile_r_pos + threadIdx.y; + size_t tile_c_pos_t = tile_c_pos + threadIdx.x; + if (tile_r_pos_t < tile_r_size && tile_c_pos_t < tile_c_size) { + size_t index_in = + source_start_pos + (source_row_major ? tile_c_pos_t + tile_r_pos_t * source_lld + : tile_c_pos_t * source_lld + tile_r_pos_t); + assert(index_in < source_size); + block[threadIdx.y][threadIdx.x] = source[index_in]; + } + + __syncthreads(); + + // write back data to target (row major OR column major) + if (tile_r_pos + threadIdx.x < tile_r_size && tile_c_pos + threadIdx.y < tile_c_size) { + size_t index_out = + target_start_pos + + (target_row_major ? (tile_r_pos + threadIdx.x) * target_lld + tile_c_pos + threadIdx.y + : tile_r_pos + threadIdx.x + (tile_c_pos + threadIdx.y) * target_lld); + assert(index_out < target_size); + target[index_out] = block[threadIdx.x][threadIdx.y]; + } + } + } + } else { + for (size_t tile_r_pos = threadIdx.x; tile_r_pos < tile_r_size; tile_r_pos += BLOCK_DIM) { + for (size_t tile_c_pos = threadIdx.y; tile_c_pos < tile_c_size; tile_c_pos += BLOCK_DIM) { + size_t index_in = source_start_pos + tile_r_pos + tile_c_pos * source_lld; + size_t index_out = target_start_pos + tile_r_pos + tile_c_pos * target_lld; + assert(index_in < source_size); + assert(index_out < target_size); + target[index_out] = source[index_in]; + } + } + } +} + +template +__global__ void assemble_tiles_to_block_result(VAL* target, + size_t target_volume, + size_t target_lld, + size_t target_offset_r, + size_t target_offset_c, + bool target_row_major, + Buffer recv_info, + size_t stored_size_per_rank, + Buffer recv_buffers_ptr, + size_t p_r, + size_t p_c, + size_t tile_r, + size_t tile_c) +{ + __shared__ VAL block[BLOCK_DIM][BLOCK_DIM + 1]; + + size_t num_target_cols = target_row_major ? target_lld : target_volume / target_lld; + size_t num_target_rows = target_row_major ? target_volume / target_lld : target_lld; + + size_t tile_idx_row = blockIdx.x + target_offset_r / tile_r; + size_t tile_idx_col = blockIdx.y + target_offset_c / tile_c; + + auto [tile_r_size, tile_c_size, target_start_pos] = compute_tile_info(num_target_rows, + num_target_cols, + target_row_major, + target_lld, + target_offset_r, + target_offset_c, + tile_r, + tile_c); + + auto [source_rank_id, source_size, source_lld, source_start_pos] = compute_2dbc_info( + recv_info, stored_size_per_rank, p_r, p_c, tile_idx_row, tile_idx_col, tile_r, tile_c); + + transfer_data_src_tgt(recv_buffers_ptr[source_rank_id], + source_size, + source_lld, + false, + source_start_pos, + target, + target_volume, + target_lld, + target_row_major, + target_start_pos, + tile_r_size, + tile_c_size, + block); +} + +template +__global__ void copy_to_send_buffer(const VAL* input, + size_t volume, + Buffer send_info, + size_t stored_size_per_rank, + Buffer send_buffers_ptr, + bool row_major, + size_t offset_r, + size_t offset_c, + size_t lld, + size_t p_r, + size_t p_c, + size_t tile_r, + size_t tile_c) +{ + __shared__ VAL block[BLOCK_DIM][BLOCK_DIM + 1]; + + size_t num_input_cols = row_major ? lld : volume / lld; + size_t num_input_rows = row_major ? volume / lld : lld; + + size_t tile_idx_row = blockIdx.x + offset_r / tile_r; + size_t tile_idx_col = blockIdx.y + offset_c / tile_c; + + auto [tile_r_size, tile_c_size, source_start_pos] = compute_tile_info( + num_input_rows, num_input_cols, row_major, lld, offset_r, offset_c, tile_r, tile_c); + + auto [target_rank_id, target_size, target_lld, target_start_pos] = compute_2dbc_info( + send_info, stored_size_per_rank, p_r, p_c, tile_idx_row, tile_idx_col, tile_r, tile_c); + + transfer_data_src_tgt(input, + volume, + lld, + row_major, + source_start_pos, + send_buffers_ptr[target_rank_id], + target_size, + target_lld, + false, + target_start_pos, + tile_r_size, + tile_c_size, + block); +} + +template +std::tuple, size_t, size_t> repartition_matrix_2dbc(const VAL* input, + size_t volume, + bool row_major, + size_t offset_r, + size_t offset_c, + size_t lld, + size_t p_r, + size_t p_c, + size_t tile_r, + size_t tile_c, + comm::Communicator comm_wrapper) +{ + assert(volume == 0 || is_device_only_ptr(input)); + + auto num_ranks = p_r * p_c; + size_t num_cols = row_major ? lld : volume / lld; + size_t num_rows = row_major ? volume / lld : lld; + + auto comm = comm_wrapper.get(); + auto stream = get_cached_stream(); + + int nccl_rank = -1; + int nccl_ranks = -1; + CHECK_NCCL(ncclCommUserRank(*comm, &nccl_rank)); + CHECK_NCCL(ncclCommCount(*comm, &nccl_ranks)); + assert(num_ranks == nccl_ranks); + + // compute sizes/lld/offset for each target rank + size_t stored_size_per_rank = get_16b_aligned_count(BlockInfo::LAST, sizeof(size_t)); + size_t total_send_elements = 0; + Buffer send_info = + create_buffer(num_ranks * stored_size_per_rank, Memory::Z_COPY_MEM); + Buffer recv_info = + create_buffer(num_ranks * stored_size_per_rank, Memory::Z_COPY_MEM); + for (size_t rank_c = 0; rank_c < p_c; ++rank_c) { + auto [active_columns, offset_columns] = + elements_for_rank_in_dimension(num_cols, offset_c, rank_c, p_c, tile_c); + for (size_t rank_r = 0; rank_r < p_r; ++rank_r) { + auto glob_rank = rank_r + rank_c * p_r; // target ranks are col major + auto [active_rows, offset_rows] = + elements_for_rank_in_dimension(num_rows, offset_r, rank_r, p_r, tile_r); + + auto elements_for_rank = active_columns * active_rows; + total_send_elements += elements_for_rank; + + send_info[glob_rank * stored_size_per_rank + BlockInfo::TOTAL_SIZE] = elements_for_rank; + send_info[glob_rank * stored_size_per_rank + BlockInfo::LLD] = + active_rows; // col-major send data + send_info[glob_rank * stored_size_per_rank + BlockInfo::OFFSET_ROW] = offset_rows; + send_info[glob_rank * stored_size_per_rank + BlockInfo::OFFSET_COL] = offset_columns; + } + } + + assert(total_send_elements == volume); + + // TODO / OPTIMIZE + // in case we have the global partition information of the cuNumeric block partition + // we can compute receive buffers instead and skip this all2all + // same applies for inverse operation + + // all2all send_info/recv_info + CHECK_NCCL(ncclGroupStart()); + for (size_t r = 0; r < num_ranks; r++) { + CHECK_NCCL(ncclSend( + send_info.ptr(r * stored_size_per_rank), stored_size_per_rank, ncclUint64, r, *comm, stream)); + CHECK_NCCL(ncclRecv( + recv_info.ptr(r * stored_size_per_rank), stored_size_per_rank, ncclUint64, r, *comm, stream)); + } + CHECK_NCCL(ncclGroupEnd()); + CHECK_CUDA(cudaStreamSynchronize(stream)); // need Z-copy synchronized to Host + + // allocate send/recv buffer + std::vector> send_buffers; + send_buffers.reserve(num_ranks); + std::vector> recv_buffers; + recv_buffers.reserve(num_ranks); + size_t total_receive = 0; + size_t target_lld = 0; + for (size_t rank_c = 0; rank_c < p_c; ++rank_c) { + for (size_t rank_r = 0; rank_r < p_r; ++rank_r) { + auto glob_rank = rank_r + rank_c * p_r; // target ranks are col major + assert(send_buffers.size() == glob_rank); + send_buffers.emplace_back(create_buffer( + send_info[glob_rank * stored_size_per_rank + BlockInfo::TOTAL_SIZE], Memory::GPU_FB_MEM)); + auto receive_size = recv_info[glob_rank * stored_size_per_rank + BlockInfo::TOTAL_SIZE]; + if (receive_size > 0) { + target_lld = + std::max(target_lld, + recv_info[glob_rank * stored_size_per_rank + BlockInfo::LLD] + + recv_info[glob_rank * stored_size_per_rank + BlockInfo::OFFSET_ROW]); + } + total_receive += receive_size; + assert(recv_buffers.size() == glob_rank); + recv_buffers.emplace_back(create_buffer(receive_size, Memory::GPU_FB_MEM)); + } + } + + // and package data for each target rank + if (volume > 0) { + Buffer send_buffers_ptr = create_buffer(num_ranks, Memory::Z_COPY_MEM); + for (size_t r = 0; r < num_ranks; r++) { + send_buffers_ptr[r] = send_buffers[r].ptr(0); + } + + size_t first_tile_r = offset_r / tile_r; + size_t last_tile_r = (offset_r + num_rows - 1) / tile_r; + size_t num_tiles_r = last_tile_r - first_tile_r + 1; + size_t first_tile_c = offset_c / tile_c; + size_t last_tile_c = (offset_c + num_cols - 1) / tile_c; + size_t num_tiles_c = last_tile_c - first_tile_c + 1; + + // simplify - every tile handled by individual block (especially helpful for row/col transpose) + dim3 grid = dim3(num_tiles_r, num_tiles_c); + dim3 block(BLOCK_DIM, BLOCK_DIM); + // row based needs shared mem for coalesced read/write + // col based can access directly? maybe also use shared mem to unify + copy_to_send_buffer<<>>(input, + volume, + send_info, + stored_size_per_rank, + send_buffers_ptr, + row_major, + offset_r, + offset_c, + lld, + p_r, + p_c, + tile_r, + tile_c); + CHECK_CUDA(cudaStreamSynchronize(stream)); + send_buffers_ptr.destroy(); + } + + CHECK_CUDA_STREAM(stream); + + // all2all data + CHECK_NCCL(ncclGroupStart()); + for (size_t r = 0; r < num_ranks; r++) { + CHECK_NCCL(ncclSend(send_buffers[r].ptr(0), + send_info[r * stored_size_per_rank + BlockInfo::TOTAL_SIZE] * sizeof(VAL), + ncclInt8, + r, + *comm, + stream)); + CHECK_NCCL(ncclRecv(recv_buffers[r].ptr(0), + recv_info[r * stored_size_per_rank + BlockInfo::TOTAL_SIZE] * sizeof(VAL), + ncclInt8, + r, + *comm, + stream)); + } + CHECK_NCCL(ncclGroupEnd()); + send_info.destroy(); + for (auto&& buf : send_buffers) { + buf.destroy(); + } + + // combine data from all buffers + Buffer result_2dbc = create_buffer(total_receive, Memory::GPU_FB_MEM); + if (total_receive > 0) { + Buffer recv_buffers_ptr = create_buffer(num_ranks, Memory::Z_COPY_MEM); + for (size_t r = 0; r < num_ranks; r++) { + recv_buffers_ptr[r] = recv_buffers[r].ptr(0); + } + + size_t avr_elements_per_rank = (total_receive + num_ranks - 1) / num_ranks; + // this roughly ensures ~32 elements per thread to copy - not optimized yet + size_t num_blocks_per_rank = + ((avr_elements_per_rank + 31) / 32 + THREADS_PER_BLOCK - 1) / THREADS_PER_BLOCK; + dim3 grid_shape = dim3(num_blocks_per_rank, num_ranks); + merge_data_to_result<<>>(result_2dbc, + total_receive, + recv_info, + stored_size_per_rank, + recv_buffers_ptr, + target_lld, + tile_r, + tile_c, + (size_t)nccl_rank, + num_ranks); + CHECK_CUDA(cudaStreamSynchronize(stream)); + recv_buffers_ptr.destroy(); + } + + CHECK_CUDA_STREAM(stream); + + recv_info.destroy(); + for (auto&& buf : recv_buffers) { + buf.destroy(); + } + + // returns the buffer/size/lld + return {result_2dbc, total_receive, target_lld}; +} + +template +void repartition_matrix_block( + Buffer input_2dbc_buffer, + size_t input_volume, + size_t input_lld, + size_t local_rank, // NOTE: this needs to correspond to communicator rank! + size_t p_r, + size_t p_c, + size_t tile_r, + size_t tile_c, + VAL* target, + size_t target_volume, + size_t target_lld, + size_t num_target_rows, + size_t num_target_cols, + bool target_row_major, + // TODO optimize -- we would like to provide a global mapping to skip additional communication + size_t target_offset_r, + size_t target_offset_c, + comm::Communicator comm_wrapper) +{ + auto num_ranks = p_r * p_c; + + auto comm = comm_wrapper.get(); + auto stream = get_cached_stream(); + + size_t num_input_rows = input_volume > 0 ? input_lld : 0; + size_t num_input_cols = input_volume > 0 ? input_volume / input_lld : 0; + + // will be computed from offset exchange + size_t target_p_r = 0; + size_t target_p_c = 0; + size_t target_p_r_valid = 0; + size_t target_p_c_valid = 0; + + // 1. communicate global offsets + auto offsets_r = create_buffer(num_ranks, Memory::Z_COPY_MEM); + auto offsets_c = create_buffer(num_ranks, Memory::Z_COPY_MEM); + // for now we need to exchange all offsets + { + auto offsets = create_buffer(2 * num_ranks, Memory::Z_COPY_MEM); + offsets[2 * local_rank] = num_target_rows > 0 ? target_offset_r + num_target_rows : 0; + offsets[2 * local_rank + 1] = num_target_cols > 0 ? target_offset_c + num_target_cols : 0; + CHECK_NCCL( + ncclAllGather(offsets.ptr(2 * local_rank), offsets.ptr(0), 2, ncclUint64, *comm, stream)); + CHECK_CUDA(cudaStreamSynchronize(stream)); + + // re-arrange so that all row offsets come first + for (size_t i = 1; i < num_ranks; i += 2) { + size_t tmp = offsets[i]; + size_t idx2 = 2 * num_ranks - 1 - i; + offsets[i] = offsets[idx2]; + offsets[idx2] = tmp; + } + // sort col/row offsets independently + std::sort(offsets.ptr(0), offsets.ptr(num_ranks)); + std::sort(offsets.ptr(num_ranks), offsets.ptr(2 * num_ranks)); + // store offsets (we know that we can skip duplicate information) + + size_t last_offset_r = 0; + size_t empty_p_r = 0; + size_t equals_r = 1; + for (size_t r = 0; r < num_ranks; r++) { + if (offsets[r] > last_offset_r) { + offsets_r[target_p_r_valid++] = offsets[r]; + last_offset_r = offsets[r]; + } else if (target_p_r_valid == 1) { + assert(offsets[r] == last_offset_r); + equals_r++; + } else if (target_p_r_valid == 0) { + empty_p_r++; + } + } + size_t last_offset_c = 0; + size_t empty_p_c = 0; + size_t equals_c = 1; + for (size_t c = num_ranks; c < 2 * num_ranks; c++) { + if (offsets[c] > last_offset_c) { + offsets_c[target_p_c_valid++] = offsets[c]; + last_offset_c = offsets[c]; + } else if (target_p_c_valid == 1) { + assert(offsets[c] == last_offset_c); + equals_c++; + } else if (target_p_c_valid == 0) { + empty_p_c++; + } + } + + // edge-case -- empty in 2D + // x x x x 0 0 + // x x x x 0 0 + // 0 0 0 0 0 0 + // 0 0 0 0 0 0 + // 0 0 0 0 0 0 + // target_p_r_valid = 2 target_p_c_valid = 4 + // empty_p_r = 18 empty_p_c = 10 + // equals_r = 4 equals_c = 2 + if (empty_p_r > 0 && empty_p_c > 0) { + size_t empty_prod = empty_p_r * empty_p_c; + assert(empty_prod % num_ranks == 0); + empty_prod /= num_ranks; + bool found_match = false; + for (size_t r = 1; r <= empty_p_r && !found_match; r++) { + for (size_t c = 1; r <= empty_p_c; r++) { + if (r * c == empty_prod && (r + target_p_r_valid) * (c + target_p_c_valid) == num_ranks) { + found_match = true; + empty_p_r = r; + empty_p_c = c; + break; + } + } + } + assert(found_match); + } + + target_p_r = target_p_r_valid + empty_p_r; + target_p_c = target_p_c_valid + empty_p_c; + + // update offsets for invalid ranks + for (int r = target_p_r_valid; r < target_p_r; ++r) { + offsets_r[r] = offsets_r[r - 1]; + } + for (int c = target_p_c_valid; c < target_p_c; ++c) { + offsets_c[c] = offsets_c[c - 1]; + } + + offsets.destroy(); + assert(num_ranks == target_p_r * target_p_c); + } + + // Assumptions: + // a. local_rank == nccl_rank == 2dbc-id (col-major) + // b. local_rank interpreted row-major (cuNumeric) should match offsets in offset mappings + // c. offsets for ranks outside valid bounds are not considered + size_t rank_r_rm = local_rank / target_p_c; + size_t rank_c_rm = local_rank % target_p_c; + size_t rank_r_cm = local_rank % p_r; + size_t rank_c_cm = local_rank / p_r; + { + assert(rank_r_rm >= target_p_r_valid || + offsets_r[rank_r_rm] == target_offset_r + num_target_rows); + assert(rank_c_rm >= target_p_c_valid || + offsets_c[rank_c_rm] == target_offset_c + num_target_cols); + } + + // 2. compute expected send/receive sizes locally + // first convert global element offsets to local tile offsets + auto glob2loc = + [](size_t glob_elem, size_t first_tile_offset, size_t proc_dim, size_t tilesize) -> size_t { + size_t local_element = 0; + if (glob_elem > first_tile_offset) { + size_t remainder = glob_elem - first_tile_offset; + // full cycles + size_t cycle_length = proc_dim * tilesize; + size_t full_cycles = remainder / cycle_length; + local_element += tilesize * full_cycles; + remainder = remainder % cycle_length; + local_element += min(remainder, tilesize); + } + + return local_element; + }; + + size_t first_tile_offset_r = rank_r_cm * tile_r; + size_t first_tile_offset_c = rank_c_cm * tile_c; + size_t stored_size_per_rank = get_16b_aligned_count(BlockInfo::LAST, sizeof(size_t)); + size_t total_send_elements = 0; + size_t total_recv_elements = 0; + Buffer send_info = + create_buffer(num_ranks * stored_size_per_rank, Memory::Z_COPY_MEM); + Buffer recv_info = + create_buffer(num_ranks * stored_size_per_rank, Memory::Z_COPY_MEM); + + // send/recv buffer + + std::vector> send_buffers; + send_buffers.reserve(num_ranks); + std::vector> recv_buffers; + recv_buffers.reserve(num_ranks); + + size_t active_send_row_end = 0; + for (size_t rank_r = 0; rank_r < target_p_r; ++rank_r) { + size_t active_send_row_start = active_send_row_end; + active_send_row_end = glob2loc(offsets_r[rank_r], first_tile_offset_r, p_r, tile_r); + active_send_row_end = std::min(active_send_row_end, num_input_rows); // limited by local rows! + size_t active_send_column_end = 0; + for (size_t rank_c = 0; rank_c < target_p_c; ++rank_c) { + size_t active_send_column_start = active_send_column_end; + auto other_rank = rank_r * target_p_c + rank_c; // target ranks are row major!!! + active_send_column_end = glob2loc(offsets_c[rank_c], first_tile_offset_c, p_c, tile_c); + active_send_column_end = + std::min(active_send_column_end, num_input_cols); // limited by local cols! + + // send information from local_rank to other_rank + { + size_t active_send_rows = active_send_row_end - active_send_row_start; + size_t active_send_columns = active_send_column_end - active_send_column_start; + auto send_elements_for_rank = active_send_columns * active_send_rows; + total_send_elements += send_elements_for_rank; + + send_info[other_rank * stored_size_per_rank + BlockInfo::TOTAL_SIZE] = + send_elements_for_rank; + send_info[other_rank * stored_size_per_rank + BlockInfo::LLD] = + active_send_rows; // col-major send data + send_info[other_rank * stored_size_per_rank + BlockInfo::OFFSET_ROW] = + active_send_row_start; + send_info[other_rank * stored_size_per_rank + BlockInfo::OFFSET_COL] = + active_send_column_start; + assert(send_buffers.size() == other_rank); + send_buffers.emplace_back(create_buffer(send_elements_for_rank, Memory::GPU_FB_MEM)); + } + } + } + + assert(total_send_elements == input_volume); + + // 3. package send data (should be blocks of data) + if (total_send_elements > 0) { + VAL* input_2dbc = input_2dbc_buffer.ptr(0); + Buffer send_buffers_ptr = create_buffer(num_ranks, Memory::Z_COPY_MEM); + for (size_t r = 0; r < num_ranks; r++) { + send_buffers_ptr[r] = send_buffers[r].ptr(0); + } + + size_t avr_elements_per_rank = (total_send_elements + num_ranks - 1) / num_ranks; + // this roughly ensures ~32 elements per thread to copy - not optimized yet + size_t num_blocks_per_rank = + ((avr_elements_per_rank + 31) / 32 + THREADS_PER_BLOCK - 1) / THREADS_PER_BLOCK; + dim3 grid_shape = dim3(num_blocks_per_rank, num_ranks); + split_data_to_send_buffers<<>>(input_2dbc, + input_volume, + input_lld, + send_info, + stored_size_per_rank, + send_buffers_ptr, + p_r, + p_c, + tile_r, + tile_c); + + CHECK_CUDA(cudaStreamSynchronize(stream)); + send_buffers_ptr.destroy(); + } + // we can destroy the input once we distributed data into the buffers + input_2dbc_buffer.destroy(); + + // compute and allocate receive buffers + for (size_t rank_r = 0; rank_r < target_p_r; ++rank_r) { + for (size_t rank_c = 0; rank_c < target_p_c; ++rank_c) { + auto other_rank = rank_r * target_p_c + rank_c; // target ranks are row major!!! + + // recv information from other_rank to local_rank + // other rank sends info for tile based on col-major ordering + size_t other_rank_r_cm = other_rank % p_r; + size_t other_rank_c_cm = other_rank / p_r; + size_t other_first_tile_offset_r = other_rank_r_cm * tile_r; + size_t other_first_tile_offset_c = other_rank_c_cm * tile_c; + + // locate other active rows/cols w.r.t. local target offsets + size_t active_recv_row_end = + glob2loc(target_offset_r + num_target_rows, other_first_tile_offset_r, p_r, tile_r); + size_t active_recv_column_end = + glob2loc(target_offset_c + num_target_cols, other_first_tile_offset_c, p_c, tile_c); + size_t active_recv_row_start = + glob2loc(target_offset_r, other_first_tile_offset_r, p_r, tile_r); + size_t active_recv_column_start = + glob2loc(target_offset_c, other_first_tile_offset_c, p_c, tile_c); + + size_t active_recv_rows = active_recv_row_end - active_recv_row_start; + size_t active_recv_columns = active_recv_column_end - active_recv_column_start; + auto recv_elements_for_rank = active_recv_columns * active_recv_rows; + total_recv_elements += recv_elements_for_rank; + + recv_info[other_rank * stored_size_per_rank + BlockInfo::TOTAL_SIZE] = recv_elements_for_rank; + recv_info[other_rank * stored_size_per_rank + BlockInfo::LLD] = + active_recv_rows; // col-major recv data + recv_info[other_rank * stored_size_per_rank + BlockInfo::OFFSET_ROW] = active_recv_row_start; + recv_info[other_rank * stored_size_per_rank + BlockInfo::OFFSET_COL] = + active_recv_column_start; + assert(other_rank == recv_buffers.size()); + recv_buffers.emplace_back(create_buffer(recv_elements_for_rank, Memory::GPU_FB_MEM)); + } + } + + assert(total_recv_elements == target_volume); + + // 4. communicate data + // all2all data + CHECK_NCCL(ncclGroupStart()); + for (size_t r = 0; r < num_ranks; r++) { + CHECK_NCCL(ncclSend(send_buffers[r].ptr(0), + send_info[r * stored_size_per_rank + BlockInfo::TOTAL_SIZE] * sizeof(VAL), + ncclInt8, + r, + *comm, + stream)); + CHECK_NCCL(ncclRecv(recv_buffers[r].ptr(0), + recv_info[r * stored_size_per_rank + BlockInfo::TOTAL_SIZE] * sizeof(VAL), + ncclInt8, + r, + *comm, + stream)); + } + CHECK_NCCL(ncclGroupEnd()); + send_info.destroy(); + for (auto&& buf : send_buffers) { + buf.destroy(); + } + + // 5. merge data from recv_buffers + if (total_recv_elements > 0) { + Buffer recv_buffers_ptr = create_buffer(num_ranks, Memory::Z_COPY_MEM); + for (size_t r = 0; r < num_ranks; r++) { + recv_buffers_ptr[r] = recv_buffers[r].ptr(0); + } + + size_t first_tile_r = target_offset_r / tile_r; + size_t last_tile_r = (target_offset_r + num_target_rows - 1) / tile_r; + size_t num_tiles_r = last_tile_r - first_tile_r + 1; + size_t first_tile_c = target_offset_c / tile_c; + size_t last_tile_c = (target_offset_c + num_target_cols - 1) / tile_c; + size_t num_tiles_c = last_tile_c - first_tile_c + 1; + + // simplify - every tile handled by individual block (especially helpful for row/col transpose) + dim3 grid = dim3(num_tiles_r, num_tiles_c); + dim3 block(BLOCK_DIM, BLOCK_DIM); + assemble_tiles_to_block_result<<>>(target, + target_volume, + target_lld, + target_offset_r, + target_offset_c, + target_row_major, + recv_info, + stored_size_per_rank, + recv_buffers_ptr, + p_r, + p_c, + tile_r, + tile_c); + CHECK_CUDA(cudaStreamSynchronize(stream)); + recv_buffers_ptr.destroy(); + } + + CHECK_CUDA_STREAM(stream); + + // cleanup + offsets_r.destroy(); + offsets_c.destroy(); + recv_info.destroy(); + for (auto&& buf : recv_buffers) { + buf.destroy(); + } +} + +/* + BOOL = LEGION_TYPE_BOOL, + INT8 = LEGION_TYPE_INT8, + INT16 = LEGION_TYPE_INT16, + INT32 = LEGION_TYPE_INT32, + INT64 = LEGION_TYPE_INT64, + UINT8 = LEGION_TYPE_UINT8, + UINT16 = LEGION_TYPE_UINT16, + UINT32 = LEGION_TYPE_UINT32, + UINT64 = LEGION_TYPE_UINT64, + FLOAT16 = LEGION_TYPE_FLOAT16, + FLOAT32 = LEGION_TYPE_FLOAT32, + FLOAT64 = LEGION_TYPE_FLOAT64, + COMPLEX64 = LEGION_TYPE_COMPLEX64, + COMPLEX128 = LEGION_TYPE_COMPLEX128 + */ +template std::tuple>, size_t, size_t> +repartition_matrix_2dbc>(const type_of*, + size_t, + bool, + size_t, + size_t, + size_t, + size_t, + size_t, + size_t, + size_t, + comm::Communicator); +template void repartition_matrix_block>(Buffer>, + size_t, + size_t, + size_t, + size_t, + size_t, + size_t, + size_t, + type_of*, + size_t, + size_t, + size_t, + size_t, + bool, + size_t, + size_t, + comm::Communicator); +template std::tuple>, size_t, size_t> +repartition_matrix_2dbc>(const type_of*, + size_t, + bool, + size_t, + size_t, + size_t, + size_t, + size_t, + size_t, + size_t, + comm::Communicator); +template void repartition_matrix_block>(Buffer>, + size_t, + size_t, + size_t, + size_t, + size_t, + size_t, + size_t, + type_of*, + size_t, + size_t, + size_t, + size_t, + bool, + size_t, + size_t, + comm::Communicator); +template std::tuple>, size_t, size_t> +repartition_matrix_2dbc>(const type_of*, + size_t, + bool, + size_t, + size_t, + size_t, + size_t, + size_t, + size_t, + size_t, + comm::Communicator); +template void repartition_matrix_block>( + Buffer>, + size_t, + size_t, + size_t, + size_t, + size_t, + size_t, + size_t, + type_of*, + size_t, + size_t, + size_t, + size_t, + bool, + size_t, + size_t, + comm::Communicator); +template std::tuple>, size_t, size_t> +repartition_matrix_2dbc>(const type_of*, + size_t, + bool, + size_t, + size_t, + size_t, + size_t, + size_t, + size_t, + size_t, + comm::Communicator); +template void repartition_matrix_block>( + Buffer>, + size_t, + size_t, + size_t, + size_t, + size_t, + size_t, + size_t, + type_of*, + size_t, + size_t, + size_t, + size_t, + bool, + size_t, + size_t, + comm::Communicator); +template std::tuple>, size_t, size_t> +repartition_matrix_2dbc>(const type_of*, + size_t, + bool, + size_t, + size_t, + size_t, + size_t, + size_t, + size_t, + size_t, + comm::Communicator); +template void repartition_matrix_block>( + Buffer>, + size_t, + size_t, + size_t, + size_t, + size_t, + size_t, + size_t, + type_of*, + size_t, + size_t, + size_t, + size_t, + bool, + size_t, + size_t, + comm::Communicator); +template std::tuple>, size_t, size_t> +repartition_matrix_2dbc>(const type_of*, + size_t, + bool, + size_t, + size_t, + size_t, + size_t, + size_t, + size_t, + size_t, + comm::Communicator); +template void repartition_matrix_block>( + Buffer>, + size_t, + size_t, + size_t, + size_t, + size_t, + size_t, + size_t, + type_of*, + size_t, + size_t, + size_t, + size_t, + bool, + size_t, + size_t, + comm::Communicator); +template std::tuple>, size_t, size_t> +repartition_matrix_2dbc>(const type_of*, + size_t, + bool, + size_t, + size_t, + size_t, + size_t, + size_t, + size_t, + size_t, + comm::Communicator); +template void repartition_matrix_block>( + Buffer>, + size_t, + size_t, + size_t, + size_t, + size_t, + size_t, + size_t, + type_of*, + size_t, + size_t, + size_t, + size_t, + bool, + size_t, + size_t, + comm::Communicator); +template std::tuple>, size_t, size_t> +repartition_matrix_2dbc>(const type_of*, + size_t, + bool, + size_t, + size_t, + size_t, + size_t, + size_t, + size_t, + size_t, + comm::Communicator); +template void repartition_matrix_block>( + Buffer>, + size_t, + size_t, + size_t, + size_t, + size_t, + size_t, + size_t, + type_of*, + size_t, + size_t, + size_t, + size_t, + bool, + size_t, + size_t, + comm::Communicator); +template std::tuple>, size_t, size_t> +repartition_matrix_2dbc>(const type_of*, + size_t, + bool, + size_t, + size_t, + size_t, + size_t, + size_t, + size_t, + size_t, + comm::Communicator); +template void repartition_matrix_block>( + Buffer>, + size_t, + size_t, + size_t, + size_t, + size_t, + size_t, + size_t, + type_of*, + size_t, + size_t, + size_t, + size_t, + bool, + size_t, + size_t, + comm::Communicator); +template std::tuple>, size_t, size_t> +repartition_matrix_2dbc>(const type_of*, + size_t, + bool, + size_t, + size_t, + size_t, + size_t, + size_t, + size_t, + size_t, + comm::Communicator); +template void repartition_matrix_block>( + Buffer>, + size_t, + size_t, + size_t, + size_t, + size_t, + size_t, + size_t, + type_of*, + size_t, + size_t, + size_t, + size_t, + bool, + size_t, + size_t, + comm::Communicator); +template std::tuple>, size_t, size_t> +repartition_matrix_2dbc>(const type_of*, + size_t, + bool, + size_t, + size_t, + size_t, + size_t, + size_t, + size_t, + size_t, + comm::Communicator); +template void repartition_matrix_block>( + Buffer>, + size_t, + size_t, + size_t, + size_t, + size_t, + size_t, + size_t, + type_of*, + size_t, + size_t, + size_t, + size_t, + bool, + size_t, + size_t, + comm::Communicator); +template std::tuple>, size_t, size_t> +repartition_matrix_2dbc>(const type_of*, + size_t, + bool, + size_t, + size_t, + size_t, + size_t, + size_t, + size_t, + size_t, + comm::Communicator); +template void repartition_matrix_block>( + Buffer>, + size_t, + size_t, + size_t, + size_t, + size_t, + size_t, + size_t, + type_of*, + size_t, + size_t, + size_t, + size_t, + bool, + size_t, + size_t, + comm::Communicator); +template std::tuple>, size_t, size_t> +repartition_matrix_2dbc>(const type_of*, + size_t, + bool, + size_t, + size_t, + size_t, + size_t, + size_t, + size_t, + size_t, + comm::Communicator); +template void repartition_matrix_block>( + Buffer>, + size_t, + size_t, + size_t, + size_t, + size_t, + size_t, + size_t, + type_of*, + size_t, + size_t, + size_t, + size_t, + bool, + size_t, + size_t, + comm::Communicator); +template std::tuple>, size_t, size_t> +repartition_matrix_2dbc>(const type_of*, + size_t, + bool, + size_t, + size_t, + size_t, + size_t, + size_t, + size_t, + size_t, + comm::Communicator); +template void repartition_matrix_block>( + Buffer>, + size_t, + size_t, + size_t, + size_t, + size_t, + size_t, + size_t, + type_of*, + size_t, + size_t, + size_t, + size_t, + bool, + size_t, + size_t, + comm::Communicator); + +} // namespace cunumeric \ No newline at end of file diff --git a/src/cunumeric/utilities/repartition.h b/src/cunumeric/utilities/repartition.h new file mode 100644 index 0000000000..7f651dcf40 --- /dev/null +++ b/src/cunumeric/utilities/repartition.h @@ -0,0 +1,95 @@ +/* Copyright 2022 NVIDIA Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +#pragma once + +#include "legate.h" +#include "cunumeric/cunumeric_task.h" + +namespace cunumeric { + +enum BlockInfo { + TOTAL_SIZE, // # values send + LLD, // local leading dimension ( num rows for col-based) + OFFSET_ROW, // global row offset w.r.t. elements of proc id + OFFSET_COL, // global col offset w.r.t. elements of proc id + LAST // keep as last element +}; + +// TODO(mförster) optimize -- we would like to provide a global mapping to skip additional +// communication + +/* + * performs collective repartition to 2d block cyclic pattern + * returns tuple(buffer, volume, lld) + */ +template +[[nodiscard]] std::tuple, size_t, size_t> repartition_matrix_2dbc( + // dense input data block (only GPU mem supported) + const VAL* input, + size_t volume, + bool row_major, + // offset of local block w.r.t. global dimensions + size_t offset_r, + size_t offset_c, + // lld of input data, corresponds to numRows/numCols + size_t lld, + // target process grid layout (p_r*p_c need to match communicator size) + size_t p_r, + size_t p_c, + // tile layout + size_t tile_r, + size_t tile_c, + // communicator + legate::comm::Communicator comm); + +/* + * performs collective repartition from 2d block cyclic pattern + * back to block + */ +template +void repartition_matrix_block( + // dense input data block (only GPU mem supported) + // will be released as soon as consumed + legate::Buffer input_2dbc, + size_t input_volume, + size_t input_lld, + // should match NCCL rank and 2dbc ID column major + size_t local_rank, + // 2dbc process grid layout (p_r*p_c need to match communicator size) + size_t p_r, + size_t p_c, + // tile layout + size_t tile_r, + size_t tile_c, + // dense output data pointer (only GPU mem supported) + VAL* target, + size_t target_volume, + size_t target_lld, + // cuNumeric process grid layout (needs to match communicator size) + size_t num_target_rows, + size_t num_target_cols, + bool target_row_major, + // offset of local block w.r.t. global dimensions + size_t target_offset_r, + size_t target_offset_c, + // communicator + legate::comm::Communicator comm); + +[[nodiscard]] std::tuple elements_for_rank_in_dimension( + size_t dim_length, size_t offset_id, size_t proc_id, size_t num_dim_procs, size_t tilesize); + +} // namespace cunumeric \ No newline at end of file diff --git a/tests/cpp/integration/test_repartition.cc b/tests/cpp/integration/test_repartition.cc new file mode 100644 index 0000000000..ec69f20b9d --- /dev/null +++ b/tests/cpp/integration/test_repartition.cc @@ -0,0 +1,453 @@ +/* Copyright 2023 NVIDIA Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +#include +#include +#include +#include "legate.h" +#include "cunumeric.h" +#include "util.inl" +#include "cunumeric/utilities/repartition.h" + +namespace repartition_test { + +constexpr const char* library_name = "test_repartition"; + +constexpr bool debug = false; + +enum TaskIDs { + CHECK_REPARTITION_TASK = 0, +}; + +template +struct CheckRepartitionTask + : public legate::LegateTask> { + static const std::int32_t TASK_ID = CHECK_REPARTITION_TASK + I_ROW_MAJOR * 2 + O_ROW_MAJOR; + static void gpu_variant(legate::TaskContext context); +}; + +class RepartitionLayoutMapper : public legate::mapping::Mapper { + void set_machine(const legate::mapping::MachineQueryInterface* /*machine*/) override {} + legate::mapping::TaskTarget task_target( + const legate::mapping::Task& /*task*/, + const std::vector& options) override + { + return options.front(); + } + std::vector store_mappings( + const legate::mapping::Task& task, + const std::vector& options) override + { + auto task_id = task.task_id(); + bool out_row_major = task_id % 2 == 1; + bool in_row_major = task_id > 1; + + std::vector mappings; + auto inputs = task.inputs(); + auto outputs = task.outputs(); + for (auto& input : inputs) { + mappings.push_back(legate::mapping::StoreMapping::default_mapping( + input.data(), options.front(), true /*exact*/)); + if (in_row_major) { + mappings.back().policy().ordering.set_c_order(); + } else { + mappings.back().policy().ordering.set_fortran_order(); + } + } + for (auto& output : outputs) { + mappings.push_back(legate::mapping::StoreMapping::default_mapping( + output.data(), options.front(), true /*exact*/)); + if (out_row_major) { + mappings.back().policy().ordering.set_c_order(); + } else { + mappings.back().policy().ordering.set_fortran_order(); + } + } + return mappings; + } + legate::Scalar tunable_value(legate::TunableID /*tunable_id*/) override + { + return legate::Scalar{}; + } +}; + +int get_rank_row_major(legate::Domain domain, legate::DomainPoint index_point) +{ + int domain_index = 0; + auto hi = domain.hi(); + auto lo = domain.lo(); + for (int i = 0; i < domain.get_dim(); ++i) { + if (i > 0) { + domain_index *= hi[i] - lo[i] + 1; + } + domain_index += index_point[i]; + } + return domain_index; +} + +void repartition_2dbc_test(legate::AccessorRO input, + legate::Rect<2> in_rect, + bool in_row_major, + legate::AccessorWO output, + legate::Rect<2> out_rect, + bool out_row_major, + int32_t proc_r, + int32_t proc_c, + int32_t tile_r, + int32_t tile_c, + int32_t local_rank, + legate::comm::Communicator comm) +{ + const int32_t* input_ptr = input.ptr(in_rect.lo); + size_t input_volume = in_rect.volume(); + size_t input_offset_r = in_rect.lo[0]; + size_t input_offset_c = in_rect.lo[1]; + size_t input_lld = + in_rect.empty() ? 1 : (in_rect.hi[in_row_major ? 1 : 0] - in_rect.lo[in_row_major ? 1 : 0] + 1); + + auto [buffer_2dbc, volume_2dbc, lld_2dbc] = cunumeric::repartition_matrix_2dbc(input_ptr, + input_volume, + in_row_major, + input_offset_r, + input_offset_c, + input_lld, + proc_r, + proc_c, + tile_r, + tile_c, + comm); + + int32_t* output_ptr = output.ptr(out_rect.lo); + size_t output_volume = out_rect.volume(); + size_t output_offset_r = out_rect.lo[0]; + size_t output_offset_c = out_rect.lo[1]; + size_t num_rows = out_rect.hi[0] < out_rect.lo[0] ? 0 : out_rect.hi[0] - out_rect.lo[0] + 1; + size_t num_cols = out_rect.hi[1] < out_rect.lo[1] ? 0 : out_rect.hi[1] - out_rect.lo[1] + 1; + size_t output_lld = out_rect.empty() ? 1 : (out_row_major ? num_cols : num_rows); + + if (debug) { + std::ostringstream stringStream; + stringStream << "DEBUG: volume_2dbc = " << volume_2dbc << ", lld_2dbc = " << lld_2dbc + << ", in_row_major = " << in_row_major << ", out_row_major = " << out_row_major + << ", num_rows = " << num_rows << ", num_cols = " << num_cols + << ", output_offset_r = " << output_offset_r + << ", output_offset_c = " << output_offset_c << ", output_lld = " << output_lld + << ", rank = " << local_rank << std::endl; + std::cerr << stringStream.str(); + } + + cunumeric::repartition_matrix_block(buffer_2dbc, + volume_2dbc, + lld_2dbc, + local_rank, + proc_r, + proc_c, + tile_r, + tile_c, + output_ptr, + output_volume, + output_lld, + num_rows, + num_cols, + out_row_major, + output_offset_r, + output_offset_c, + comm); +} + +void register_tasks() +{ + static bool prepared = false; + if (prepared) { + return; + } + prepared = true; + auto runtime = legate::Runtime::get_runtime(); + auto library = runtime->create_library( + library_name, legate::ResourceConfig{}, std::make_unique()); + + CheckRepartitionTask::register_variants(library); + CheckRepartitionTask::register_variants(library); + CheckRepartitionTask::register_variants(library); + CheckRepartitionTask::register_variants(library); +} + +template +/*static*/ void CheckRepartitionTask::gpu_variant( + legate::TaskContext context) +{ + auto input = context.input(0); + auto output = context.output(0); + auto shape_in = input.shape<2>(); + auto shape_out = output.shape<2>(); + + size_t tile_r = context.scalar(0).value(); + size_t tile_c = context.scalar(1).value(); + + auto total_ranks = context.get_launch_domain().get_volume(); + auto local_rank = get_rank_row_major(context.get_launch_domain(), context.get_task_index()); + + if (total_ranks == 1) { + std::cerr << "Error: aborting due to single task launch. Ensure LEGATE_TEST=1 to force " + "parallel execution for small test dimensions." + << std::endl; + return; + } + + int32_t pr = total_ranks; + int32_t pc = 1; + while (pc * 2 <= pr && pr % 2 == 0) { + pr /= 2; + pc *= 2; + } + + auto input_acc = input.data().read_accessor(shape_in); + auto output_acc = output.data().write_accessor(shape_out); + + bool in_row_major = shape_in.empty() || input_acc.accessor.is_dense_row_major(shape_in); + bool in_col_major = shape_in.empty() || input_acc.accessor.is_dense_col_major(shape_in); + bool out_row_major = shape_out.empty() || output_acc.accessor.is_dense_row_major(shape_out); + bool out_col_major = shape_out.empty() || output_acc.accessor.is_dense_col_major(shape_out); + + if (debug) { + std::ostringstream stringStream; + stringStream << "DEBUG: Domain = " << context.get_launch_domain() + << ", index = " << context.get_task_index() << ", I_ROW_MAJOR = " << I_ROW_MAJOR + << ", O_ROW_MAJOR = " << O_ROW_MAJOR << ", shape_in = " << shape_in + << "(order=" << in_row_major << "," << in_col_major << ")" + << ", shape_out = " << shape_out << "(order=" << out_row_major << ", " + << out_col_major << ")" + << ", communicators = " << context.num_communicators() << ", rank = " << local_rank + << ", tile = (" << tile_r << "," << tile_c << ")" + << ", procs_2dbc = (" << pr << "," << pc << ")" << std::endl; + std::cerr << stringStream.str(); + } + + EXPECT_EQ(true, I_ROW_MAJOR ? in_row_major : in_col_major); + EXPECT_EQ(true, O_ROW_MAJOR ? out_row_major : out_col_major); + + repartition_2dbc_test(input_acc, + shape_in, + I_ROW_MAJOR, + output_acc, + shape_out, + O_ROW_MAJOR, + pr, + pc, + tile_r, + tile_c, + local_rank, + context.communicator(0)); +} + +template +void run_test_aligned_default_launch(std::vector& data_shape, + std::vector& tile_shape) +{ + auto runtime = legate::Runtime::get_runtime(); + auto library = runtime->find_library(library_name); + auto machine = runtime->get_machine(); + auto num_gpus = machine.count(legate::mapping::TaskTarget::GPU); + if (num_gpus < 2) { + GTEST_SKIP(); + } + + // generate data + size_t volume = data_shape[0] * data_shape[1]; + auto data_input = cunumeric::zeros(data_shape, legate::int32()); + auto data_output = cunumeric::zeros(data_shape, legate::int32()); + if (volume != 0) { + if (volume == 1) { + data_input.fill(legate::Scalar(0)); + } else { + std::vector numbers(volume); + std::iota(numbers.data(), numbers.data() + volume, 0); + assign_values_to_array(data_input, numbers.data(), numbers.size()); + } + } + + // start custom test-task with aligned in/out + auto task = runtime->create_task(library, CHECK_REPARTITION_TASK + I_ROW_MAJOR * 2 + O_ROW_MAJOR); + auto part_in = task.add_input(data_input.get_store()); + auto part_out = task.add_output(data_output.get_store()); + task.add_scalar_arg(legate::Scalar{tile_shape[0]}); + task.add_scalar_arg(legate::Scalar{tile_shape[1]}); + task.add_constraint(legate::align(part_in, part_out)); + task.add_communicator("nccl"); + runtime->submit(std::move(task)); + + check_array_eq(data_input, data_output); +} + +void run_tests_with_shape(std::vector& data_shape, std::vector& tile_shape) +{ + auto machine = legate::Runtime::get_runtime()->get_machine(); + auto num_gpus = machine.count(legate::mapping::TaskTarget::GPU); + if (num_gpus < 2) { + GTEST_SKIP(); + } + + run_test_aligned_default_launch(data_shape, tile_shape); + run_test_aligned_default_launch(data_shape, tile_shape); + run_test_aligned_default_launch(data_shape, tile_shape); + run_test_aligned_default_launch(data_shape, tile_shape); +} + +std::vector> NICE_SHAPES = {{64, 64}, {64, 32}, {256, 256}, {512, 1}}; +std::vector> NICE_TILESIZE = {{4, 4}, {32, 32}, {64, 64}, {256, 256}}; + +TEST(Repartition, NiceValues_C_C) +{ + register_tasks(); + + for (size_t shape_idx = 0; shape_idx < NICE_SHAPES.size(); ++shape_idx) { + for (size_t tile_idx = 0; tile_idx < NICE_TILESIZE.size(); ++tile_idx) { + run_test_aligned_default_launch(NICE_SHAPES[shape_idx], NICE_TILESIZE[tile_idx]); + } + } +} + +TEST(Repartition, NiceValues_F_F) +{ + register_tasks(); + + for (size_t shape_idx = 0; shape_idx < NICE_SHAPES.size(); ++shape_idx) { + for (size_t tile_idx = 0; tile_idx < NICE_TILESIZE.size(); ++tile_idx) { + run_test_aligned_default_launch(NICE_SHAPES[shape_idx], + NICE_TILESIZE[tile_idx]); + } + } +} + +TEST(Repartition, NiceValues_C_F) +{ + register_tasks(); + + for (size_t shape_idx = 0; shape_idx < NICE_SHAPES.size(); ++shape_idx) { + for (size_t tile_idx = 0; tile_idx < NICE_TILESIZE.size(); ++tile_idx) { + run_test_aligned_default_launch(NICE_SHAPES[shape_idx], NICE_TILESIZE[tile_idx]); + } + } +} + +TEST(Repartition, NiceValues_F_C) +{ + register_tasks(); + + for (size_t shape_idx = 0; shape_idx < NICE_SHAPES.size(); ++shape_idx) { + for (size_t tile_idx = 0; tile_idx < NICE_TILESIZE.size(); ++tile_idx) { + run_test_aligned_default_launch(NICE_SHAPES[shape_idx], NICE_TILESIZE[tile_idx]); + } + } +} + +std::vector> ODD_SHAPES = { + {120, 257}, {148, 12}, {12, 2325}, {1112, 31}, {256, 256}, {12, 1}}; + +std::vector> ODD_TILESIZE = { + {2, 2}, {64, 32}, {255, 256}, {16, 5}, {1, 1}, {4, 4}}; + +TEST(Repartition, OddValues_C_C) +{ + register_tasks(); + for (size_t shape_idx = 0; shape_idx < ODD_SHAPES.size(); ++shape_idx) { + for (size_t tile_idx = 0; tile_idx < ODD_TILESIZE.size(); ++tile_idx) { + run_test_aligned_default_launch(ODD_SHAPES[shape_idx], ODD_TILESIZE[tile_idx]); + } + } +} + +TEST(Repartition, OddValues_F_F) +{ + register_tasks(); + for (size_t shape_idx = 0; shape_idx < ODD_SHAPES.size(); ++shape_idx) { + for (size_t tile_idx = 0; tile_idx < ODD_TILESIZE.size(); ++tile_idx) { + run_test_aligned_default_launch(ODD_SHAPES[shape_idx], ODD_TILESIZE[tile_idx]); + } + } +} + +TEST(Repartition, OddValues_C_F) +{ + register_tasks(); + for (size_t shape_idx = 0; shape_idx < ODD_SHAPES.size(); ++shape_idx) { + for (size_t tile_idx = 0; tile_idx < ODD_TILESIZE.size(); ++tile_idx) { + run_test_aligned_default_launch(ODD_SHAPES[shape_idx], ODD_TILESIZE[tile_idx]); + } + } +} + +TEST(Repartition, OddValues_F_C) +{ + register_tasks(); + for (size_t shape_idx = 0; shape_idx < ODD_SHAPES.size(); ++shape_idx) { + for (size_t tile_idx = 0; tile_idx < ODD_TILESIZE.size(); ++tile_idx) { + run_test_aligned_default_launch(ODD_SHAPES[shape_idx], ODD_TILESIZE[tile_idx]); + } + } +} + +std::vector> STRANGE_SHAPES = { + {120, 257}, {148, 12}, {12, 2325}, {1112, 31}, {256, 256}, {12, 1}}; + +std::vector> STRANGE_TILESIZE = { + {2, 2}, {64, 32}, {255, 256}, {16, 5}, {1, 1}, {4, 4}}; + +TEST(Repartition, StrangeValues_C_C) +{ + register_tasks(); + for (size_t shape_idx = 0; shape_idx < STRANGE_SHAPES.size(); ++shape_idx) { + for (size_t tile_idx = 0; tile_idx < STRANGE_TILESIZE.size(); ++tile_idx) { + run_test_aligned_default_launch(STRANGE_SHAPES[shape_idx], + STRANGE_TILESIZE[tile_idx]); + } + } +} + +TEST(Repartition, StrangeValues_F_F) +{ + register_tasks(); + for (size_t shape_idx = 0; shape_idx < STRANGE_SHAPES.size(); ++shape_idx) { + for (size_t tile_idx = 0; tile_idx < STRANGE_TILESIZE.size(); ++tile_idx) { + run_test_aligned_default_launch(STRANGE_SHAPES[shape_idx], + STRANGE_TILESIZE[tile_idx]); + } + } +} + +TEST(Repartition, StrangeValues_C_F) +{ + register_tasks(); + for (size_t shape_idx = 0; shape_idx < STRANGE_SHAPES.size(); ++shape_idx) { + for (size_t tile_idx = 0; tile_idx < STRANGE_TILESIZE.size(); ++tile_idx) { + run_test_aligned_default_launch(STRANGE_SHAPES[shape_idx], + STRANGE_TILESIZE[tile_idx]); + } + } +} + +TEST(Repartition, StrangeValues_F_C) +{ + register_tasks(); + for (size_t shape_idx = 0; shape_idx < STRANGE_SHAPES.size(); ++shape_idx) { + for (size_t tile_idx = 0; tile_idx < STRANGE_TILESIZE.size(); ++tile_idx) { + run_test_aligned_default_launch(STRANGE_SHAPES[shape_idx], + STRANGE_TILESIZE[tile_idx]); + } + } +} + +} // namespace repartition_test diff --git a/tests/cpp/integration/util.inl b/tests/cpp/integration/util.inl index 51e35371c5..6d08f36e8e 100644 --- a/tests/cpp/integration/util.inl +++ b/tests/cpp/integration/util.inl @@ -120,7 +120,10 @@ struct check_array_eq_fn { const std::vector& shape, legate::Rect rect) { - std::cerr << check_array_eq(acc, values_ptr, shape, rect) << std::endl; + auto string_result = check_array_eq(acc, values_ptr, shape, rect); + if (rect.volume() <= 256) { + std::cerr << string_result << std::endl; + } } }; @@ -135,6 +138,17 @@ struct assign_array_fn { } }; +template +struct copy_array_fn { + void operator()(legate::AccessorRO acc, T* values_ptr, legate::Rect rect) + { + auto index = 0; + for (legate::PointInRectIterator itr(rect, false); itr.valid(); ++itr) { + values_ptr[index++] = acc[*itr]; + } + } +}; + template void print_array(cunumeric::NDArray array) { @@ -177,4 +191,32 @@ void assign_values_to_array(cunumeric::NDArray array, T* values_ptr, size_t leng assign_array_fn()(acc, values_ptr, rect); } +template +std::vector assign_array_to_values(cunumeric::NDArray array) +{ + std::vector result(array.size()); + if (array.size() > 0) { + T* values_ptr = result.data(); + assert(values_ptr != nullptr); + auto acc = array.get_read_accessor(); + auto logical_store = array.get_store(); + auto physical_store = logical_store.get_physical_store(); + auto rect = physical_store.shape(); + copy_array_fn()(acc, values_ptr, rect); + } + return std::move(result); +} + +template +void check_array_eq(cunumeric::NDArray array1, cunumeric::NDArray array2) +{ + assert(array1.size() == array2.size()); + if (array1.size() == 0) { + return; + } + + std::vector data2 = assign_array_to_values(array2); + check_array_eq(array1, data2.data(), data2.size()); +} + } // namespace diff --git a/tests/integration/test_cholesky.py b/tests/integration/test_cholesky.py index c4b52754b0..a935796f3a 100644 --- a/tests/integration/test_cholesky.py +++ b/tests/integration/test_cholesky.py @@ -19,7 +19,7 @@ import cunumeric as num -SIZES = [8, 9, 255, 512] +SIZES = [8, 9, 255, 512, 1024] def test_matrix(): diff --git a/tests/integration/test_solve.py b/tests/integration/test_solve.py index c8d11032c6..6109e09be3 100644 --- a/tests/integration/test_solve.py +++ b/tests/integration/test_solve.py @@ -51,6 +51,9 @@ def test_solve_1d(n, a_dtype, b_dtype): rtol = RTOL[out.dtype] atol = ATOL[out.dtype] + if n > 1024: + atol *= 20.0 + assert allclose( b, num.matmul(a, out), rtol=rtol, atol=atol, check_dtype=False ) @@ -71,6 +74,9 @@ def test_solve_2d(n, a_dtype, b_dtype): rtol = RTOL[out.dtype] atol = ATOL[out.dtype] + if n > 1024: + atol *= 20.0 + assert allclose( b, num.matmul(a, out), rtol=rtol, atol=atol, check_dtype=False ) diff --git a/tests/unit/cunumeric/test_config.py b/tests/unit/cunumeric/test_config.py index 66e5600fde..85d04efdc1 100644 --- a/tests/unit/cunumeric/test_config.py +++ b/tests/unit/cunumeric/test_config.py @@ -77,6 +77,8 @@ def test_CuNumericOpCode() -> None: "LOAD_CUDALIBS", "MATMUL", "MATVECMUL", + "MP_POTRF", + "MP_SOLVE", "NONZERO", "PACKBITS", "POTRF", From 3321cb4c3073900c16b6b34d409bf293d625bcf6 Mon Sep 17 00:00:00 2001 From: Sandeep Datta <128171450+sandeepd-nv@users.noreply.github.com> Date: Thu, 15 Feb 2024 16:30:43 +0530 Subject: [PATCH 152/462] Move reusable CI components into nv-legate/legate-gh-ci. (#104) --- .github/actions/download-artifacts/action.yml | 46 --- .github/workflows/ci-gh.yml | 1 + .github/workflows/gh-build-and-test.yml | 19 +- .github/workflows/gh-build.yml | 122 -------- .../workflows/gh-test-within-container.yml | 85 ------ cmake/versions.json | 4 +- continuous_integration/dot-gitconfig | 3 - .../scripts/{build-cunumeric => build} | 51 ++-- .../scripts/build-cunumeric-conda | 7 +- continuous_integration/scripts/conda-utils | 99 ------- continuous_integration/scripts/entrypoint | 26 -- continuous_integration/scripts/make-conda-env | 8 +- continuous_integration/scripts/setup-utils | 278 ------------------ .../scripts/{test-cunumeric => test} | 6 +- continuous_integration/scripts/vault-s3-init | 109 ------- 15 files changed, 47 insertions(+), 817 deletions(-) delete mode 100644 .github/actions/download-artifacts/action.yml delete mode 100644 .github/workflows/gh-build.yml delete mode 100644 .github/workflows/gh-test-within-container.yml delete mode 100644 continuous_integration/dot-gitconfig rename continuous_integration/scripts/{build-cunumeric => build} (68%) delete mode 100755 continuous_integration/scripts/conda-utils delete mode 100755 continuous_integration/scripts/entrypoint delete mode 100755 continuous_integration/scripts/setup-utils rename continuous_integration/scripts/{test-cunumeric => test} (97%) delete mode 100755 continuous_integration/scripts/vault-s3-init diff --git a/.github/actions/download-artifacts/action.yml b/.github/actions/download-artifacts/action.yml deleted file mode 100644 index d9929e02c2..0000000000 --- a/.github/actions/download-artifacts/action.yml +++ /dev/null @@ -1,46 +0,0 @@ -name: download-artifacts - -description: Download dependencies (artifacts) - -inputs: - target-device: {type: string, required: true} - git_sha: {type: string, required: true} - inter_repos_ro_access_token: {required: true} - platform: {type: string, required: true} - dest-dir: {type: string, required: true} - -runs: - using: composite - steps: - - - id: cache - name: Cache conda artifacts - uses: actions/cache@v3 - with: - key: "nv-legate/${{ inputs.platform }}-legate.core.internal@${{ inputs.git_sha }}-${{ inputs.target-device }}" - path: ${{ inputs.dest-dir }} - - - if: steps.cache.outputs.cache-hit != 'true' - name: Download conda artifacts - uses: dawidd6/action-download-artifact@v2 - with: - github_token: ${{ inputs.inter_repos_ro_access_token }} - path: ${{ inputs.dest-dir }} - repo: nv-legate/legate.core.internal - check_artifacts: true - # search_artifacts: true - commit: ${{ inputs.git_sha }} - workflow_conclusion: success - workflow: "ci-gh.yml" - name: "${{ inputs.platform }}-legate.core.internal-${{ inputs.target-device }}-release-gcc-${{ inputs.git_sha }}" - # name_is_regexp: true - skip_unpack: true - if_no_artifact_found: fail - allow_forks: false - - - if: steps.cache.outputs.cache-hit != 'true' - name: Unpack artifact - shell: bash --noprofile --norc -xeuo pipefail {0} - run: | - cd ${{ inputs.dest-dir }} - unzip *.zip diff --git a/.github/workflows/ci-gh.yml b/.github/workflows/ci-gh.yml index a8e8a6bef3..5c2a7a2330 100644 --- a/.github/workflows/ci-gh.yml +++ b/.github/workflows/ci-gh.yml @@ -5,6 +5,7 @@ concurrency: cancel-in-progress: true on: + workflow_dispatch: push: branches: - "pull-request/[0-9]+" diff --git a/.github/workflows/gh-build-and-test.yml b/.github/workflows/gh-build-and-test.yml index 88653f194f..2c4ba2c6cc 100644 --- a/.github/workflows/gh-build-and-test.yml +++ b/.github/workflows/gh-build-and-test.yml @@ -31,13 +31,18 @@ jobs: needs: setup-build name: "Build (${{ inputs.platform }}, ${{ inputs.target-device }})" uses: - ./.github/workflows/gh-build.yml + nv-legate/legate-gh-ci/.github/workflows/gh-build.yml@v23.11.00 with: + client-repo: ${{ github.event.repository.name }} target-device: ${{ inputs.target-device }} runs-on: ${{ needs.setup-build.outputs.runner_type }} build-type: ci use-container: ${{ inputs.platform == 'linux' }} platform: ${{ inputs.platform }} + dependencies-file: "cmake/versions.json" + legate-gh-ci-tag: "v23.11.00" + cmake-preset: "" + ucx-enabled: false secrets: inherit setup-test: @@ -53,7 +58,9 @@ jobs: set -xeuo pipefail MATRIX_JSON='{"include": [' RUNNERS=( - 'linux-amd64-gpu-v100-latest-1:gpu:gpu:linux' 'linux-amd64-2gpu:gpu:2gpu:linux' 'linux-amd64-cpu32:cpu:cpu:linux' 'macos-latest:cpu:cpu:mac') + 'linux-amd64-gpu-v100-latest-1:gpu:gpu:linux' 'linux-amd64-2gpu:gpu:2gpu:linux' + 'linux-amd64-cpu32:cpu:cpu:linux' + 'macos-latest:cpu:cpu:mac') TEST_CONFIGS=( '1 CPU test:test --cpus 1 --unit --debug:cpu' '1 CPU test:test --cpus 1 --unit --debug:gpu' @@ -102,11 +109,17 @@ jobs: matrix: ${{fromJson(needs.setup-test.outputs.matrix)}} uses: - ./.github/workflows/gh-test-within-container.yml + nv-legate/legate-gh-ci/.github/workflows/gh-test-within-container.yml@v23.11.00 with: + client-repo: ${{ github.event.repository.name }} + build-type: ci name: ${{ matrix.test-config.name }} target-device: ${{ inputs.target-device }} runs-on: ${{ matrix.runner.name }} has-gpu: ${{ matrix.runner.type == 'gpu' }} test-options: ${{ matrix.test-config.test-options }} platform: ${{ inputs.platform }} + legate-gh-ci-tag: "v23.11.00" + cmake-preset: "" + ucx-enabled: false + secrets: inherit diff --git a/.github/workflows/gh-build.yml b/.github/workflows/gh-build.yml deleted file mode 100644 index 2f4e8cf224..0000000000 --- a/.github/workflows/gh-build.yml +++ /dev/null @@ -1,122 +0,0 @@ -name: Build - -on: - workflow_call: - inputs: - target-device: - required: true - type: string - runs-on: - required: true - type: string - build-type: - required: true - type: string - description: One of ci / release - use-container: - required: true - type: boolean - platform: - required: true - type: string -env: - ARTIFACTS_DIR: "${{ github.workspace }}/artifacts" - ARTIFACT_NAME: "${{ inputs.platform }}-${{ github.event.repository.name }}-${{ inputs.target-device }}-${{ github.sha }}" - USE_CUDA: ${{ (inputs.target-device == 'cpu' && 'OFF') || 'ON' }} - DOCKER_IMAGE: "condaforge/miniforge3:latest" -jobs: - build: - name: Build (${{ inputs.platform }}, ${{ inputs.target-device }}, ${{ inputs.build-type }}, Use container=${{ inputs.use-container }} ) - - permissions: - id-token: write # This is required for configure-aws-credentials - contents: read # This is required for actions/checkout - - runs-on: ${{ inputs.runs-on }} - - defaults: - run: - shell: bash --noprofile --norc -xeuo pipefail {0} - - steps: - - name: Checkout ${{ github.event.repository.name }} - uses: actions/checkout@v3 - with: - fetch-depth: 0 - path: cunumeric.internal - - - name: Dump environment - run: | - env - - - name: Dump CPU info - run: | - cat /proc/cpuinfo - - - name: Copy .gitconfig - run: cp cunumeric.internal/continuous_integration/dot-gitconfig ~/.gitconfig - - - id: legate_core_info - name: Read legate.core SHA - shell: bash --noprofile --norc -xeo pipefail {0} - run: | - git_tag="$(jq -r '.packages.legate_core_internal.git_tag' cunumeric.internal/cmake/versions.json)"; - echo "git_tag=$git_tag" | tee -a "${GITHUB_OUTPUT}"; - - - name: Download dependencies (artifacts) - uses: ./cunumeric.internal/.github/actions/download-artifacts - with: - target-device: "${{ inputs.target-device }}" - git_sha: "${{ steps.legate_core_info.outputs.git_tag }}" - inter_repos_ro_access_token: ${{ secrets.NV_LEGATE_INTER_REPOS_ACCESS_RO }} - platform: ${{ inputs.platform }} - dest-dir: ${{ env.ARTIFACTS_DIR }} - - - name: Display structure of downloaded artifacts - run: | - pwd - ls -lahR $ARTIFACTS_DIR - - - if: github.repository_owner == 'nv-legate' - name: Get AWS credentials for sccache bucket - uses: aws-actions/configure-aws-credentials@v2 - with: - aws-region: us-east-2 - role-duration-seconds: 28800 # 8 hours - role-to-assume: arn:aws:iam::279114543810:role/gha-oidc-nv-legate - - - if: ${{ inputs.use-container }} - name: Build (in container) - run: | - cd cunumeric.internal - - docker run \ - -e AWS_REGION \ - -e AWS_SESSION_TOKEN \ - -e AWS_ACCESS_KEY_ID \ - -e AWS_SECRET_ACCESS_KEY \ - -e GITHUB_TOKEN \ - -e ARTIFACTS_DIR \ - -e USE_CUDA \ - -v "$(pwd):$(pwd)" \ - -v "$ARTIFACTS_DIR:$(pwd)/../artifacts" \ - --rm "${{ env.DOCKER_IMAGE }}" \ - /bin/bash -c "cd $(pwd) && continuous_integration/scripts/entrypoint build-cunumeric ${{ inputs.build-type}} ${{ inputs.target-device }}" - - - if: ${{ !inputs.use-container }} - name: Build (without container) - run: | - cd cunumeric.internal - - continuous_integration/scripts/entrypoint build-cunumeric ${{ inputs.build-type}} - - - name: Display structure of the artifacts folder (post build) - run: | - sudo chown -R $(whoami) ${{ env.ARTIFACTS_DIR }} - ls -lahR ${{ env.ARTIFACTS_DIR }} - - - name: Upload build artifacts - uses: actions/upload-artifact@v3 - with: - name: ${{ env.ARTIFACT_NAME }} - path: ${{ env.ARTIFACTS_DIR }} diff --git a/.github/workflows/gh-test-within-container.yml b/.github/workflows/gh-test-within-container.yml deleted file mode 100644 index ea6057ea40..0000000000 --- a/.github/workflows/gh-test-within-container.yml +++ /dev/null @@ -1,85 +0,0 @@ -name: Test - -on: - workflow_call: - inputs: - name: - required: true - type: string - target-device: - required: true - type: string - runs-on: - required: true - type: string - has-gpu: - required: true - type: boolean - description: "The runner has GPU(s)." - test-options: - required: true - type: string - platform: - required: true - type: string - -env: - ARTIFACT_NAME: "${{ inputs.platform }}-${{ github.event.repository.name }}-${{ inputs.target-device }}-${{ github.sha }}" - ARTIFACTS_DIR: "${{ github.workspace }}/artifacts" - -jobs: - test: - name: ${{ inputs.name }} - if: github.repository_owner == 'nv-legate' - runs-on: ${{ inputs.runs-on }} - - container: - options: -u root --security-opt seccomp=unconfined - image: condaforge/miniforge3:latest - env: - NVIDIA_VISIBLE_DEVICES: ${{ env.NVIDIA_VISIBLE_DEVICES }} - - defaults: - run: - shell: bash --noprofile --norc -xeuo pipefail {0} - - steps: - - if: inputs.has-gpu - name: Run nvidia-smi to make sure GPU is working - run: nvidia-smi - - # This step is needed to work around a github runner bug which causes the value of github.workspace to change over time. - - name: Dump relevant enviornment variables - run: | - pwd - echo "ARTIFACTS_DIR=$ARTIFACTS_DIR" >> $GITHUB_ENV - echo ARTIFACT_NAME=$ARTIFACT_NAME - echo Platform: ${{ inputs.platform }} - - - name: Dump CPU info - run: | - cat /proc/cpuinfo - - - name: Checkout ${{ github.event.repository.name }} - uses: actions/checkout@v3 - with: - fetch-depth: 0 - path: "${{ github.workspace }}/cunumeric.internal" - persist-credentials: false - - - name: Download build artifacts - uses: actions/download-artifact@v3 - with: - name: ${{ env.ARTIFACT_NAME }} - path: ${{ env.ARTIFACTS_DIR }} - - - name: Display structure of downloaded artifacts - run: | - pwd - ls -lahR $ARTIFACTS_DIR - - - name: Run ${{ github.event.repository.name }} test / analysis - run: | - cd cunumeric.internal - - continuous_integration/scripts/entrypoint test-cunumeric ${{ inputs.test-options }} diff --git a/cmake/versions.json b/cmake/versions.json index 77f817b7d1..af4e833a7d 100644 --- a/cmake/versions.json +++ b/cmake/versions.json @@ -1,11 +1,13 @@ { "packages" : { "legate_core_internal" : { + "repo": "legate.core.internal", + "artifact_name": "${{ inputs.platform }}-${{ inputs.build-type }}-<>-${{ inputs.target-device }}-release-gcc-<>", "version": "24.01.00", "git_url" : "https://github.com/nv-legate/legate.core.internal.git", "git_shallow": false, "always_download": false, - "git_tag" : "190263e60f603660fd998223e39775f2f2bafa64" + "git_tag" : "68db26d3593253317ff6e4fb310fb42804fd2dac" } } } diff --git a/continuous_integration/dot-gitconfig b/continuous_integration/dot-gitconfig deleted file mode 100644 index 91ac79c701..0000000000 --- a/continuous_integration/dot-gitconfig +++ /dev/null @@ -1,3 +0,0 @@ -[user] - email = users.noreply.github.com - name = anon \ No newline at end of file diff --git a/continuous_integration/scripts/build-cunumeric b/continuous_integration/scripts/build similarity index 68% rename from continuous_integration/scripts/build-cunumeric rename to continuous_integration/scripts/build index 6a908c2a18..9321f04e32 100755 --- a/continuous_integration/scripts/build-cunumeric +++ b/continuous_integration/scripts/build @@ -1,4 +1,7 @@ #!/usr/bin/env bash +set -x + +# copy_ci_artifacts() { set -xeuo pipefail; @@ -10,7 +13,7 @@ copy_ci_artifacts() { cp -r /tmp/conda-build "$ARTIFACTS_DIR" } -build_cunumeric_ci() { +build_ci_product() { set -xeuo pipefail; printf "\n\n\n\n********* BUILDING CUNUMERIC CPP *********\n" @@ -25,38 +28,6 @@ build_cunumeric_ci() { copy_ci_artifacts; } -build_cunumeric() { - set -x; - - . conda-utils; - . setup-utils; - - export BUILD_TYPE=$1 - - set -xeuo pipefail; - - set_base_defs; - - cd "$PREBUILD_DIR" - - setup_build_env; - init_sccache; - cd "$REPO_DIR"; - - rm -rf "$REPO_DIR/build"; - - make-conda-env "$BUILD_TYPE"; - - activate_conda_env; - conda_info; - - case "$BUILD_TYPE" in - ci) build_cunumeric_ci;; - # release) build_cunumeric_release;; - *) return 1;; - esac -} - build_cunumeric_fake() { set -xeuo pipefail; @@ -68,5 +39,17 @@ build_cunumeric_fake() { copy_ci_artifacts } +build_project() { + . setup-utils; + + init_build_env "$@"; + + case "$BUILD_TYPE" in + ci) build_ci_product;; + release) build_release;; + *) return 1;; + esac +} + -(build_cunumeric "$@"); +(build_project "$@"); diff --git a/continuous_integration/scripts/build-cunumeric-conda b/continuous_integration/scripts/build-cunumeric-conda index c26c82543d..5544dbf15b 100755 --- a/continuous_integration/scripts/build-cunumeric-conda +++ b/continuous_integration/scripts/build-cunumeric-conda @@ -28,7 +28,7 @@ build_cunumeric_conda_package() { conda_build_args+=(--no-anaconda-upload); GPU_ENABLED=true - [ "${USE_CUDA:-}" = "OFF" ] && GPU_ENABLED=false + [ "${USE_CUDA}" = "OFF" ] && GPU_ENABLED=false conda_build_args+=(--variants "{gpu_enabled:${GPU_ENABLED},python:${python_version}}"); @@ -56,9 +56,6 @@ use_local_path: numpy: - 1.22 -gpu_enabled: - - ${GPU_ENABLED} - package_version: - "$(git -C "${REPO_DIR}" describe --abbrev=0 --tags | sed 's/[a-zA-Z]//g' | cut -d '.' -f -2).00" EOF @@ -75,7 +72,7 @@ EOF git -C "${REPO_DIR}" commit --allow-empty --allow-empty-message -n -m ""; # Build cuNumeric conda package - CUDA=${CUDA_VERSION_MAJOR}.${CUDA_VERSION_MINOR} \ + CUDA=${CUDA_VERSION} \ conda mambabuild ${conda_build_args[@]} "${REPO_DIR}/conda/conda-build"; git -C "${REPO_DIR}" reset --hard HEAD~1; diff --git a/continuous_integration/scripts/conda-utils b/continuous_integration/scripts/conda-utils deleted file mode 100755 index c5f411a668..0000000000 --- a/continuous_integration/scripts/conda-utils +++ /dev/null @@ -1,99 +0,0 @@ -#!/usr/bin/env bash - -make_conda_env_from_yaml() { - mamba env create -n "${CONDA_ENV}" -f "${yaml_file}" --force; -} - -generate_yaml_file() { - UCX_PKG=ucx - [[ "${UCX_ENABLED:-}" = "OFF" ]] && UCX_PKG=no-ucx - - if [[ "$USE_CUDA" == "OFF" ]]; then - yaml_file="$(\ - $REPO_DIR/scripts/generate-conda-envs.py \ - --os "$OS_SHORT_NAME" \ - --compilers \ - --python ${PYTHON_VERSION} \ - --openmpi \ - --${UCX_PKG} \ - | head -n1 | cut -d' ' -f3 \ - )" - else - local cuda_version="${CUDA_VERSION:-${CUDA_VERSION_MAJOR}.${CUDA_VERSION_MINOR}}"; - cuda_version="$(echo "${cuda_version}" | cut -d'.' -f3 --complement)"; - - yaml_file="$( \ - $REPO_DIR/scripts/generate-conda-envs.py \ - --os "$OS_SHORT_NAME" \ - --compilers \ - --ctk ${cuda_version} \ - --python ${PYTHON_VERSION} \ - --openmpi \ - --${UCX_PKG} \ - | head -n1 | cut -d' ' -f3 \ - )" - fi - - $SED -i -re "s/legate-test/${CONDA_ENV}/g" "${yaml_file}"; - echo " - boa" >> "${yaml_file}"; - - if [[ "$LEGATE_CORE_CMAKE_PRESET" == "debug-sanitizer-gcc" ]]; then - echo " - libsanitizer <=${MAX_LIBSANITIZER_VERSION}" >> "${yaml_file}"; - fi - - mkdir -p /tmp/out/ - cp "${yaml_file}" /tmp/out/ - mkdir -p /tmp/env_yaml - cp "${yaml_file}" /tmp/env_yaml -} - -find_yaml_file() { - pattern="/tmp/env_yaml/*.yaml"; - files=( ${pattern} ); - yaml_file="${files[0]}"; - - if [[ -z "${yaml_file:-}" ]] || [[ ! -f "${yaml_file}" ]]; then - return 1; - fi - - return 0; -} - -get_yaml_and_make_conda_env() { - set -xe; - - local yaml_file=""; - - generate_yaml_file; - - echo YAML file: "${yaml_file}" - cat "${yaml_file}"; - - make_conda_env_from_yaml; -} - -install_legate_core_with_war() { - # WAR: legate-core depends on a different version of numpy than what is already installed. - # The correct version will be installed when legate-core is installed below. - # See github issue: https://github.com/nv-legate/legate.core/issues/812 - mamba uninstall -y -n "${CONDA_ENV}" numpy; - - mamba install -y -n "${CONDA_ENV}" -c nvidia -c conda-forge -c $ARTIFACTS_DIR/conda-build/legate_core legate-core; -} - -activate_conda_env() { - set +xu - eval "$(conda shell.bash hook)" - conda activate ${CONDA_ENV}; - set -xu -} - -conda_info() { - set +x - conda info - set -x -} - -make_release_env() { - mamba create -y -n "${CONDA_ENV}" -c conda-forge boa -} diff --git a/continuous_integration/scripts/entrypoint b/continuous_integration/scripts/entrypoint deleted file mode 100755 index 621e7b13ca..0000000000 --- a/continuous_integration/scripts/entrypoint +++ /dev/null @@ -1,26 +0,0 @@ -#!/usr/bin/env bash - -set_repo_dir() { - set -xeuo pipefail - - # Resolve the directory of the script - SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )" - - # Navigate to the parent of the parent of SCRIPT_DIR, then get the full path - REPO_DIR="$(cd "$SCRIPT_DIR/../.." && pwd)" - - export REPO_DIR - - export PATH="${PATH}:${REPO_DIR}/continuous_integration/scripts" - - # export ARTIFACTS_DIR=${ARTIFACTS_DIR:-/tmp/artifacts} -} - -entrypoint() { - set -xeuo pipefail - set_repo_dir; - - exec "$@"; -} - -entrypoint "$@"; diff --git a/continuous_integration/scripts/make-conda-env b/continuous_integration/scripts/make-conda-env index b2c0c9eec7..17ca35e418 100755 --- a/continuous_integration/scripts/make-conda-env +++ b/continuous_integration/scripts/make-conda-env @@ -1,10 +1,10 @@ #!/usr/bin/env bash -# . conda-utils +set -x -setup_env() { +make_ci_env() { set -xeuo pipefail - yaml_file=$(find "$ARTIFACTS_DIR" -name "environment*.yaml" | head -n 1) + yaml_file=$(find "${ARTIFACTS_DIR}" -name "environment*.yaml" | head -n 1) [ "${USE_CUDA}" = "ON" ] && echo " - libcublas-dev" >> "${yaml_file}" && @@ -30,7 +30,7 @@ make_conda_env() { set -xeuo pipefail case "$1" in - ci) setup_env;; + ci) make_ci_env;; # release) make_release_env;; *) return 1;; esac diff --git a/continuous_integration/scripts/setup-utils b/continuous_integration/scripts/setup-utils deleted file mode 100755 index 9f1708eedd..0000000000 --- a/continuous_integration/scripts/setup-utils +++ /dev/null @@ -1,278 +0,0 @@ -#!/usr/bin/env bash - -set_darwin_build_env() { - set -xeuo pipefail - - export USE_CUDA=OFF - export UCX_ENABLED=OFF - export OS_SHORT_NAME=osx - export PATH="/usr/local/opt/coreutils/libexec/gnubin:${PATH}" -} - -install_darwin_mamba() { - set -xeuo pipefail - - if [ "${GITHUB_ACTIONS:-}" == "true" ]; then - conda install -y -n base anaconda-clean - conda run -n base anaconda-clean --yes - sudo rm -rf /usr/local/miniconda - fi - - brew install --cask mambaforge -} - -install_darwin_tools() { - set -xeuo pipefail - - export SED=gsed - export READLINK=greadlink - - brew update - brew install cmake coreutils git gnu-getopt gnu-sed jq ninja wget sccache - install_darwin_mamba; -} - -install_darwin_test_tools() { - set -xeuo pipefail - - export SED=gsed - export READLINK=greadlink - - brew update - brew install coreutils git gnu-getopt gnu-sed jq wget - install_darwin_mamba; -} - -# Function to compare version numbers -version_greater_equal() { - set -xeuo pipefail - - set +x - IFS='.' read -ra ver1 <<< "$1" - IFS='.' read -ra ver2 <<< "$2" - - for i in "${!ver1[@]}"; do - if [[ -z ${ver2[i]} ]]; then - # ver1 has more segments and is greater - set -x - return 0 - fi - - if ((10#${ver1[i]} > 10#${ver2[i]})); then - set -x - return 0 - elif ((10#${ver1[i]} < 10#${ver2[i]})); then - set -x - return 1 - fi - done - - return 0 -} - -install_from_apt() { - set -xeuo pipefail - - export DEBIAN_FRONTEND=non-interactive - - # Run package updates and install packages - apt-get update - apt-get install -y wget curl jq sudo ninja-build vim numactl rsync -} - -install_sccache_linux() { - set -xeuo pipefail - - wget https://github.com/mozilla/sccache/releases/download/v0.5.4/sccache-v0.5.4-x86_64-unknown-linux-musl.tar.gz && \ - tar -xf sccache-v0.5.4-x86_64-unknown-linux-musl.tar.gz && \ - sudo mv sccache-v0.5.4-x86_64-unknown-linux-musl/sccache /usr/bin/sccache -} - -maybe_install_sccache_linux() { - set -xeuo pipefail - - if ! command -v sccache &> /dev/null; then - echo "sccache not found, proceeding with installation." - install_sccache_linux - else - sccache_version=$(sccache --version 2>&1 | awk '/sccache/ {print $2}') - if [[ -z "$sccache_version" ]] || ! version_greater_equal "$sccache_version" "0.5.4"; then - echo "sccache version less than 0.5.4, proceeding with installation." - install_sccache_linux - else - echo "sccache version is 0.5.4 or greater, no need to install." - fi - fi -} - - -install_cmake() { - set -xeuo pipefail - - wget https://github.com/Kitware/CMake/releases/download/v3.26.5/cmake-3.26.5-linux-x86_64.tar.gz - - tar -xzf cmake-3.26.5-linux-x86_64.tar.gz -} - -setup_linux_build_env() { - set -xeuo pipefail - export OS_SHORT_NAME=linux - export PATH="${PATH}:${PREBUILD_DIR}/cmake-3.26.5-linux-x86_64/bin" - - mkdir -p /tmp/out /tmp/env_yaml -} - -install_linux_tools() { - set -xeuo pipefail - - export SED=sed - export READLINK=readlink - - install_from_apt; - maybe_install_sccache_linux; - install_cmake; - - mkdir -p /tmp/out /tmp/env_yaml -} - -install_linux_test_tools() { - set -xeuo pipefail - - export SED=sed - export READLINK=readlink - - # Run package updates and install packages - apt-get update - apt-get install -y numactl -} - -set_base_defs() { - set -xeuo pipefail - - export USE_CUDA=${USE_CUDA:-OFF} - - export CONDA_ENV=legate - - CONDA_PLATFORM=$(conda info | grep 'platform' | awk -F ' : ' '{print $2}') - export CONDA_PLATFORM - - export PROJECT=cunumeric.internal - export PREBUILD_DIR=/tmp/prebuild - mkdir -p "$PREBUILD_DIR" - - export BUILD_MARCH=$(uname -m | tr '_' '-') - - export CUDA_VERSION=12.2 - export CUDA_VERSION_MAJOR=12 - export CUDA_VERSION_MINOR=2 - - export PYTHON_VERSION=3.11 - - export MAX_LIBSANITIZER_VERSION=11.4 - - export USE_OPENMP=ON - export LEGATE_CORE_CMAKE_PRESET=${LEGATE_CORE_CMAKE_PRESET:-release-gcc} -} - -# ----------------------------------------------------------------------------- - -prep_git() { - local current_email=$(git config --global user.email) - local current_name=$(git config --global user.name) - - if [ -z "$current_email" ]; then - git config --global --add user.email "users.noreply.github.com" - else - echo "Note: git user.email is already set to $current_email" - fi - - if [ -z "$current_name" ]; then - git config --global --add user.name "anon" - else - echo "Note: git user.name is already set to $current_name" - fi - - # Fix "fatal: detected dubious ownership in repository at '/tmp/legate.core'" - # during local builds. - git config --global --add safe.directory "$REPO_DIR" -} - -install_tools() { - if [[ "$(uname)" == "Darwin" ]]; then - install_darwin_tools; - elif [[ "$(uname)" == "Linux" ]]; then - install_linux_tools; - else - echo "Unknown OS" - exit 1 - fi -} - -install_test_tools() { - if [[ "$(uname)" == "Darwin" ]]; then - install_darwin_test_tools; - elif [[ "$(uname)" == "Linux" ]]; then - install_linux_test_tools; - else - echo "Unknown OS" - exit 1 - fi -} - -setup_os_specific_env() { - if [[ "$(uname)" == "Darwin" ]]; then - set_darwin_build_env; - elif [[ "$(uname)" == "Linux" ]]; then - setup_linux_build_env; - else - echo "Unknown OS" - exit 1 - fi -} - -setup_build_env() { - set -xeuo pipefail - - install_tools; - - setup_os_specific_env; - - rm -rf "$PREBUILD_DIR" - mkdir -p "$PREBUILD_DIR" - cd $PREBUILD_DIR - - prep_git; -} - -sccache_stop_server_and_show_stats() { - set -xeuo pipefail - sccache --stop-server || true && sccache --show-stats; -} - -init_sccache() { - set -xeuo pipefail - - export SCCACHE_REGION="us-east-2" - export SCCACHE_BUCKET="rapids-sccache-east" - export SCCACHE_S3_KEY_PREFIX=legate-cunumeric-dev - export VAULT_HOST=https://vault.ops.k8s.rapids.ai - CMAKE_C_COMPILER_LAUNCHER=$(which sccache) - export CMAKE_C_COMPILER_LAUNCHER - export CMAKE_CXX_COMPILER_LAUNCHER=${CMAKE_C_COMPILER_LAUNCHER} - export CMAKE_CUDA_COMPILER_LAUNCHER=${CMAKE_C_COMPILER_LAUNCHER} - - echo AWS_REGION="${AWS_REGION:-}" - echo AWS_SESSION_TOKEN="${AWS_SESSION_TOKEN:-}" - echo AWS_ACCESS_KEY_ID="${AWS_ACCESS_KEY_ID:-}" - echo AWS_SECRET_ACCESS_KEY="${AWS_SECRET_ACCESS_KEY:-}" - - mkdir -p ~/.cache; - - local secrets_dir="$REPO_DIR/.creds" - - if [ -d "$secrets_dir" ] && [ "$(ls -A "$secrets_dir")" ]; then - vault-s3-init; - else - sccache_stop_server_and_show_stats - fi -} diff --git a/continuous_integration/scripts/test-cunumeric b/continuous_integration/scripts/test similarity index 97% rename from continuous_integration/scripts/test-cunumeric rename to continuous_integration/scripts/test index eca73a5b63..a23b758d67 100755 --- a/continuous_integration/scripts/test-cunumeric +++ b/continuous_integration/scripts/test @@ -1,5 +1,7 @@ #!/usr/bin/env bash +set -x + setup_env() { set -xeuo pipefail @@ -27,7 +29,7 @@ setup_mypy_env() { mamba install -y "mypy>=0.961" jinja2 nbsphinx sphinx-copybutton "sphinx>=4.4.0" types-docutils } -test-cunumeric() { +test_cunumeric() { set -xeo pipefail . conda-utils; @@ -67,4 +69,4 @@ test-cunumeric() { esac } -(test-cunumeric "$@"); +(test_cunumeric "$@"); diff --git a/continuous_integration/scripts/vault-s3-init b/continuous_integration/scripts/vault-s3-init deleted file mode 100755 index a72af04cae..0000000000 --- a/continuous_integration/scripts/vault-s3-init +++ /dev/null @@ -1,109 +0,0 @@ -#! /usr/bin/env bash - -set -xeuo pipefail; - -get_vault_token() { - set -eo pipefail - local VAULT_HOST="$1"; - local user_org="$2"; - local gh_token="$3"; - - local vault_token=null; - - vault_token="$( \ - curl -s \ - -X POST \ - -H "Content-Type: application/json" \ - -d "{\"token\": \"$gh_token\"}" \ - "$VAULT_HOST/v1/auth/github-${user_org}/login" \ - | jq -r '.auth.client_token' \ - )"; - - echo "vault_token='$vault_token'"; -} - -vault_s3_init() { - set -eo pipefail - # Attempt to retrieve temporary AWS credentials from a vault - # instance using GitHub OAuth. - - eval "export $(find $REPO_DIR/.creds -type f -exec bash -c 'echo $(basename $0)=$(<$0)' {} \;)"; - - if [[ -z "${VAULT_HOST:-}" ]]; then return; fi - if [[ -z "${SCCACHE_BUCKET:-}" ]]; then return; fi - if [[ -z "${GH_TOKEN:-}" ]]; then return; fi - - echo "" - echo "Attempting to use your GitHub account to authenticate"; - echo "with vault at '${VAULT_HOST}'."; - echo "" - - local vault_token=null; - local user_orgs=nv-legate; - - # Attempt to authenticate with GitHub - eval "$(get_vault_token "${VAULT_HOST}" ${user_orgs} $GH_TOKEN)"; - - if [[ "${vault_token:-null}" == null ]]; then - echo "Your GitHub user was not recognized by vault. Exiting." >&2; - return; - fi - - echo "Successfully authenticated with vault!"; - - local ttl="${VAULT_S3_TTL:-"43200s"}"; - local uri="${VAULT_S3_URI:-"v1/aws/creds/devs"}"; - - # Generate temporary AWS creds - local aws_creds="$( \ - curl -s \ - -X GET \ - -H "X-Vault-Token: $vault_token" \ - -H "Content-Type: application/json" \ - "${VAULT_HOST}/$uri?ttl=$ttl" \ - | jq -r '.data' \ - )"; - - export AWS_ACCESS_KEY_ID="$(echo "$aws_creds" | jq -r '.access_key')"; - export AWS_SECRET_ACCESS_KEY="$(echo "$aws_creds" | jq -r '.secret_key')"; - - if [[ "${AWS_ACCESS_KEY_ID:-null}" == null ]]; then - echo "Failed to generate temporary AWS S3 credentials. Exiting." >&2; - return; - fi - - if [[ "${AWS_SECRET_ACCESS_KEY:-null}" == null ]]; then - echo "Failed to generate temporary AWS S3 credentials. Exiting." >&2; - return; - fi - - # Generate AWS config files - mkdir -p ~/.aws; - - echo "$(date '+%s')" > ~/.aws/stamp; - - cat < ~/.aws/config -[default] -${SCCACHE_BUCKET:+bucket=$SCCACHE_BUCKET} -${SCCACHE_REGION:+region=$SCCACHE_REGION} -EOF - - cat < ~/.aws/credentials -[default] -aws_access_key_id=$AWS_ACCESS_KEY_ID -aws_secret_access_key=$AWS_SECRET_ACCESS_KEY -EOF - - chmod 0600 ~/.aws/{config,credentials}; - - echo "Successfully generated temporary AWS S3 credentials!"; - - # Stop server and reset sccache stats. - sccache --stop-server || true - - # Wait for AWS credentials to propagate - sleep 10 - sccache --show-stats -} - -(vault_s3_init "$@"); From 9d6c02c7d25458caeaf60822105dfde3b4bcf997 Mon Sep 17 00:00:00 2001 From: Bryan Van de Ven Date: Thu, 15 Feb 2024 10:47:40 -0800 Subject: [PATCH 153/462] Switch to global Runtime instance (#123) * clean up linalg shadowing * clean up ma imports * clean up random * clean up sort shadow * use relative imports consistently * more specific name for legate runtimes * don't use runtime attributes * remove runtime argument to thunks * Update cunumeric/runtime.py Co-authored-by: Manolis Papadakis --------- Co-authored-by: Manolis Papadakis --- cunumeric/__init__.py | 16 +- cunumeric/{sort.py => _sort.py} | 31 ++-- cunumeric/_ufunc/bit_twiddling.py | 3 +- cunumeric/_ufunc/comparison.py | 3 +- cunumeric/_ufunc/floating.py | 3 +- cunumeric/_ufunc/math.py | 3 +- cunumeric/_ufunc/trigonometric.py | 3 +- cunumeric/config.py | 4 +- cunumeric/deferred.py | 163 ++++++++---------- cunumeric/eager.py | 60 +++---- cunumeric/fft/__init__.py | 6 +- cunumeric/linalg/__init__.py | 7 +- .../linalg/{cholesky.py => _cholesky.py} | 9 +- .../linalg/{exception.py => _exception.py} | 0 cunumeric/linalg/{solve.py => _solve.py} | 13 +- cunumeric/linalg/linalg.py | 17 +- cunumeric/ma/__init__.py | 6 +- cunumeric/module.py | 3 +- cunumeric/random/__init__.py | 15 +- .../{bitgenerator.py => _bitgenerator.py} | 0 .../random/{generator.py => _generator.py} | 2 +- cunumeric/random/{legacy.py => _legacy.py} | 4 +- cunumeric/random/{random.py => _random.py} | 87 ++++------ cunumeric/runtime.py | 65 +++++-- cunumeric/thunk.py | 5 +- tests/integration/utils/random.py | 4 +- .../cunumeric/random/test_bitgenerator.py | 2 +- 27 files changed, 259 insertions(+), 275 deletions(-) rename cunumeric/{sort.py => _sort.py} (82%) rename cunumeric/linalg/{cholesky.py => _cholesky.py} (98%) rename cunumeric/linalg/{exception.py => _exception.py} (100%) rename cunumeric/linalg/{solve.py => _solve.py} (91%) rename cunumeric/random/{bitgenerator.py => _bitgenerator.py} (100%) rename cunumeric/random/{generator.py => _generator.py} (99%) rename cunumeric/random/{legacy.py => _legacy.py} (98%) rename cunumeric/random/{random.py => _random.py} (94%) diff --git a/cunumeric/__init__.py b/cunumeric/__init__.py index 3ad86dd9fa..5e9b079fa6 100644 --- a/cunumeric/__init__.py +++ b/cunumeric/__init__.py @@ -28,14 +28,14 @@ import numpy as _np -from cunumeric import linalg, random, fft, ma -from cunumeric.array import maybe_convert_to_np_ndarray, ndarray -from cunumeric.bits import packbits, unpackbits -from cunumeric.module import * -from cunumeric._ufunc import * -from cunumeric.logic import * -from cunumeric.window import bartlett, blackman, hamming, hanning, kaiser -from cunumeric.coverage import clone_module +from . import linalg, random, fft, ma +from .array import maybe_convert_to_np_ndarray, ndarray +from .bits import packbits, unpackbits +from .module import * +from ._ufunc import * +from .logic import * +from .window import bartlett, blackman, hamming, hanning, kaiser +from .coverage import clone_module clone_module(_np, globals(), maybe_convert_to_np_ndarray) diff --git a/cunumeric/sort.py b/cunumeric/_sort.py similarity index 82% rename from cunumeric/sort.py rename to cunumeric/_sort.py index fe3ef3b93e..055ec69838 100644 --- a/cunumeric/sort.py +++ b/cunumeric/_sort.py @@ -22,6 +22,7 @@ ) from .config import CuNumericOpCode +from .runtime import runtime if TYPE_CHECKING: from .deferred import DeferredArray @@ -35,11 +36,11 @@ def sort_flattened( # run sort flattened -- return 1D solution sort_result = cast( "DeferredArray", - output.runtime.create_empty_thunk( + runtime.create_empty_thunk( flattened.shape, dtype=output.base.type, inputs=(flattened,) ), ) - sort(sort_result, flattened, argsort, stable=stable) + sort_deferred(sort_result, flattened, argsort, stable=stable) output.base = sort_result.base output.numpy_array = None @@ -58,7 +59,7 @@ def sort_swapped( swapped_copy = cast( "DeferredArray", - output.runtime.create_empty_thunk( + runtime.create_empty_thunk( swapped.shape, dtype=input.base.type, inputs=(input, swapped) ), ) @@ -68,17 +69,17 @@ def sort_swapped( if argsort is True: sort_result = cast( "DeferredArray", - output.runtime.create_empty_thunk( + runtime.create_empty_thunk( swapped_copy.shape, dtype=output.base.type, inputs=(swapped_copy,), ), ) - sort(sort_result, swapped_copy, argsort, stable=stable) + sort_deferred(sort_result, swapped_copy, argsort, stable=stable) output.base = sort_result.swapaxes(input.ndim - 1, sort_axis).base output.numpy_array = None else: - sort(swapped_copy, swapped_copy, argsort, stable=stable) + sort_deferred(swapped_copy, swapped_copy, argsort, stable=stable) output.base = swapped_copy.swapaxes(input.ndim - 1, sort_axis).base output.numpy_array = None @@ -86,24 +87,24 @@ def sort_swapped( def sort_task( output: DeferredArray, input: DeferredArray, argsort: bool, stable: bool ) -> None: - runtime = get_legate_runtime() - task = runtime.create_auto_task(output.library, CuNumericOpCode.SORT) + legate_runtime = get_legate_runtime() + task = legate_runtime.create_auto_task( + output.library, CuNumericOpCode.SORT + ) - uses_unbound_output = output.runtime.num_procs > 1 and input.ndim == 1 + uses_unbound_output = runtime.num_procs > 1 and input.ndim == 1 task.add_input(input.base) if uses_unbound_output: - unbound = output.runtime.create_unbound_thunk( - dtype=output.base.type, ndim=1 - ) + unbound = runtime.create_unbound_thunk(dtype=output.base.type, ndim=1) task.add_output(unbound.base) else: task.add_output(output.base) task.add_alignment(output.base, input.base) - if output.runtime.num_gpus > 1: + if runtime.num_gpus > 1: task.add_nccl_communicator() - elif output.runtime.num_gpus == 0 and output.runtime.num_procs > 1: + elif runtime.num_gpus == 0 and runtime.num_procs > 1: task.add_cpu_communicator() task.add_scalar_arg(argsort, ty.bool_) # return indices flag @@ -116,7 +117,7 @@ def sort_task( output.numpy_array = None -def sort( +def sort_deferred( output: DeferredArray, input: DeferredArray, argsort: bool, diff --git a/cunumeric/_ufunc/bit_twiddling.py b/cunumeric/_ufunc/bit_twiddling.py index dc70fc2ad6..1a5cc80618 100644 --- a/cunumeric/_ufunc/bit_twiddling.py +++ b/cunumeric/_ufunc/bit_twiddling.py @@ -14,8 +14,7 @@ # from __future__ import annotations -from cunumeric.config import BinaryOpCode, UnaryOpCode - +from ..config import BinaryOpCode, UnaryOpCode from .ufunc import create_binary_ufunc, create_unary_ufunc, integer_dtypes bitwise_and = create_binary_ufunc( diff --git a/cunumeric/_ufunc/comparison.py b/cunumeric/_ufunc/comparison.py index d1df4e8dea..ef9d9fcc26 100644 --- a/cunumeric/_ufunc/comparison.py +++ b/cunumeric/_ufunc/comparison.py @@ -14,8 +14,7 @@ # from __future__ import annotations -from cunumeric.config import BinaryOpCode, UnaryOpCode, UnaryRedCode - +from ..config import BinaryOpCode, UnaryOpCode, UnaryRedCode from .ufunc import ( all_dtypes, create_binary_ufunc, diff --git a/cunumeric/_ufunc/floating.py b/cunumeric/_ufunc/floating.py index 0dceb691f4..0de6ed5000 100644 --- a/cunumeric/_ufunc/floating.py +++ b/cunumeric/_ufunc/floating.py @@ -14,8 +14,7 @@ # from __future__ import annotations -from cunumeric.config import BinaryOpCode, UnaryOpCode - +from ..config import BinaryOpCode, UnaryOpCode from .ufunc import ( create_binary_ufunc, create_multiout_unary_ufunc, diff --git a/cunumeric/_ufunc/math.py b/cunumeric/_ufunc/math.py index 3f79c84ed2..0d8b5c60ac 100644 --- a/cunumeric/_ufunc/math.py +++ b/cunumeric/_ufunc/math.py @@ -14,8 +14,7 @@ # from __future__ import annotations -from cunumeric.config import BinaryOpCode, UnaryOpCode, UnaryRedCode - +from ..config import BinaryOpCode, UnaryOpCode, UnaryRedCode from .ufunc import ( all_but_boolean, all_dtypes, diff --git a/cunumeric/_ufunc/trigonometric.py b/cunumeric/_ufunc/trigonometric.py index 0687cc13eb..7df81ded43 100644 --- a/cunumeric/_ufunc/trigonometric.py +++ b/cunumeric/_ufunc/trigonometric.py @@ -14,8 +14,7 @@ # from __future__ import annotations -from cunumeric.config import BinaryOpCode, UnaryOpCode - +from ..config import BinaryOpCode, UnaryOpCode from .ufunc import ( create_binary_ufunc, create_unary_ufunc, diff --git a/cunumeric/config.py b/cunumeric/config.py index c27983a05d..734972f8a8 100644 --- a/cunumeric/config.py +++ b/cunumeric/config.py @@ -327,14 +327,14 @@ def register(self) -> None: callback() def get_shared_library(self) -> str: - from cunumeric.install_info import libpath + from .install_info import libpath return os.path.join( libpath, "libcunumeric" + self.get_library_extension() ) def get_c_header(self) -> str: - from cunumeric.install_info import header + from .install_info import header return header diff --git a/cunumeric/deferred.py b/cunumeric/deferred.py index 33ee98f9b9..9d122308ed 100644 --- a/cunumeric/deferred.py +++ b/cunumeric/deferred.py @@ -52,6 +52,7 @@ ) from typing_extensions import ParamSpec +from ._sort import sort_deferred from .config import ( BinaryOpCode, BitGeneratorDistribution, @@ -63,9 +64,9 @@ UnaryOpCode, UnaryRedCode, ) -from .linalg.cholesky import cholesky -from .linalg.solve import solve -from .sort import sort +from .linalg._cholesky import cholesky_deferred +from .linalg._solve import solve_deferred +from .runtime import runtime from .thunk import NumPyThunk from .utils import is_advanced_indexing, to_core_dtype @@ -73,7 +74,6 @@ import numpy.typing as npt from .config import BitGeneratorType, FFTDirection, FFTType, WindowOpCode - from .runtime import Runtime from .types import ( BitOrder, ConvolveMode, @@ -125,16 +125,15 @@ def decorator(func: Callable[P, R]) -> Callable[P, R]: @wraps(func) def wrapper(*args: Any, **kwargs: Any) -> R: # Convert relevant arguments to DeferredArrays - self = args[0] args = tuple( - self.runtime.to_deferred_array(arg) + runtime.to_deferred_array(arg) if idx in indices and arg is not None else arg for (idx, arg) in enumerate(args) ) for k, v in kwargs.items(): if k in keys and v is not None: - kwargs[k] = self.runtime.to_deferred_array(v) + kwargs[k] = runtime.to_deferred_array(v) return func(*args, **kwargs) @@ -240,11 +239,10 @@ class DeferredArray(NumPyThunk): def __init__( self, - runtime: Runtime, base: LogicalStore, numpy_array: Optional[npt.NDArray[Any]] = None, ) -> None: - super().__init__(runtime, base.type.to_numpy_dtype()) + super().__init__(base.type.to_numpy_dtype()) assert base is not None assert isinstance(base, LogicalStore) self.base: LogicalStore = base # a Legate Store @@ -268,7 +266,7 @@ def _copy_if_overlapping(self, other: DeferredArray) -> DeferredArray: return self copy = cast( DeferredArray, - self.runtime.create_empty_thunk( + runtime.create_empty_thunk( self.shape, self.base.type, inputs=[self], @@ -293,7 +291,7 @@ def __numpy_array__(self) -> npt.NDArray[Any]: # TODO: We should return a view of the field instead of a copy def imag(self) -> NumPyThunk: - result = self.runtime.create_empty_thunk( + result = runtime.create_empty_thunk( self.shape, dtype=_COMPLEX_FIELD_DTYPES[self.base.type], inputs=[self], @@ -309,7 +307,7 @@ def imag(self) -> NumPyThunk: # TODO: We should return a view of the field instead of a copy def real(self) -> NumPyThunk: - result = self.runtime.create_empty_thunk( + result = runtime.create_empty_thunk( self.shape, dtype=_COMPLEX_FIELD_DTYPES[self.base.type], inputs=[self], @@ -324,7 +322,7 @@ def real(self) -> NumPyThunk: return result def conj(self) -> NumPyThunk: - result = self.runtime.create_empty_thunk( + result = runtime.create_empty_thunk( self.shape, dtype=self.base.type, inputs=[self], @@ -367,7 +365,7 @@ def _zip_indices( new_arrays: tuple[Any, ...] = tuple() # check array's type and convert them to deferred arrays for a in arrays: - a = self.runtime.to_deferred_array(a) + a = runtime.to_deferred_array(a) data_type = a.dtype if data_type != np.int64: raise TypeError("index arrays should be int64 type") @@ -431,7 +429,7 @@ def _zip_indices( pointN_dtype = ty.point_type(N) output_arr = cast( DeferredArray, - self.runtime.create_empty_thunk( + runtime.create_empty_thunk( shape=out_shape, dtype=pointN_dtype, inputs=[self], @@ -456,11 +454,8 @@ def _zip_indices( return output_arr def _copy_store(self, store: Any) -> DeferredArray: - store_to_copy = DeferredArray( - self.runtime, - base=store, - ) - store_copy = self.runtime.create_empty_thunk( + store_to_copy = DeferredArray(base=store) + store_copy = runtime.create_empty_thunk( store_to_copy.shape, self.base.type, inputs=[store_to_copy], @@ -575,7 +570,7 @@ def _has_single_boolean_array( "Unsupported entry type passed to advanced ", "indexing operation", ) - lhs = DeferredArray(self.runtime, store) + lhs = DeferredArray(store) return True, lhs, key[transpose_index] @@ -591,7 +586,7 @@ def _advanced_indexing_with_boolean_array( ) -> tuple[bool, Any, Any, Any]: rhs = self if not isinstance(key, DeferredArray): - key = self.runtime.to_deferred_array(key) + key = runtime.to_deferred_array(key) # in case when boolean array is passed as an index, shape for all # its dimensions should be the same as the shape of @@ -620,7 +615,7 @@ def _advanced_indexing_with_boolean_array( out = cast( DeferredArray, - self.runtime.create_empty_thunk( + runtime.create_empty_thunk( out_shape, rhs.base.type, inputs=[rhs], @@ -640,10 +635,7 @@ def _advanced_indexing_with_boolean_array( # and avoid calling Copy has_set_value = set_value is not None and set_value.size == 1 if has_set_value: - mask = DeferredArray( - self.runtime, - base=key_store, - ) + mask = DeferredArray(base=key_store) rhs.putmask(mask, set_value) return False, rhs, rhs, self else: @@ -658,7 +650,7 @@ def _advanced_indexing_with_boolean_array( # TODO : current implementation of the ND output regions # requires out.ndim == rhs.ndim. This will be fixed in the # future - out = rhs.runtime.create_unbound_thunk(out_dtype, ndim=rhs.ndim) + out = runtime.create_unbound_thunk(out_dtype, ndim=rhs.ndim) key_dims = key.ndim # dimension of the original key task = legate_runtime.create_auto_task( @@ -688,7 +680,7 @@ def _advanced_indexing_with_boolean_array( out_shape = tuple(out.shape[i] for i in range(0, out_dim)) out = cast( DeferredArray, - self.runtime.create_empty_thunk( + runtime.create_empty_thunk( out_shape, out_dtype, inputs=[out], @@ -792,7 +784,7 @@ def _create_indexing_array( k, store = self._slice_store(k, store, dim + shift) elif isinstance(k, NumPyThunk): if not isinstance(computed_key, DeferredArray): - k = self.runtime.to_deferred_array(k) + k = runtime.to_deferred_array(k) if k.dtype == bool: for i in range(k.ndim): if k.shape[i] != store.shape[dim + i + shift]: @@ -817,7 +809,7 @@ def _create_indexing_array( # to apply all the transformations done to `store` to `self` # as well before creating a copy if is_set: - self = DeferredArray(self.runtime, store) + self = DeferredArray(store) # after store is transformed we need to to return a copy of # the store since Copy operation can't be done on # the store with transformation @@ -866,10 +858,7 @@ def _get_view(self, key: Any) -> DeferredArray: else: assert False - return DeferredArray( - self.runtime, - base=store, - ) + return DeferredArray(base=store) def _broadcast(self, shape: NdShape) -> Any: result = self.base @@ -900,10 +889,7 @@ def _convert_future_to_regionfield( shape=shape, optimize_scalar=False, ) - thunk_copy = DeferredArray( - self.runtime, - base=store, - ) + thunk_copy = DeferredArray(base=store) thunk_copy.copy(self, deep=True) return thunk_copy @@ -929,13 +915,10 @@ def get_item(self, key: Any) -> NumPyThunk: shape=index_array.shape, optimize_scalar=False, ) - result = DeferredArray( - self.runtime, - base=result_store, - ) + result = DeferredArray(base=result_store) else: - result = self.runtime.create_empty_thunk( + result = runtime.create_empty_thunk( index_array.base.shape, self.base.type, inputs=[self], @@ -953,7 +936,7 @@ def get_item(self, key: Any) -> NumPyThunk: if result.shape == (): input = result - result = self.runtime.create_empty_thunk( + result = runtime.create_empty_thunk( (), self.base.type, inputs=[self] ) @@ -996,10 +979,7 @@ def set_item(self, key: Any, rhs: Any) -> None: # a single value # TODO this logic should be removed when copy accepts Futures if rhs_store.has_scalar_storage: - rhs_tmp = DeferredArray( - self.runtime, - base=rhs_store, - ) + rhs_tmp = DeferredArray(base=rhs_store) rhs_tmp2 = rhs_tmp._convert_future_to_regionfield() rhs_store = rhs_tmp2.base @@ -1057,7 +1037,7 @@ def set_item(self, key: Any, rhs: Any) -> None: view.copy(rhs, deep=False) def broadcast_to(self, shape: NdShape) -> NumPyThunk: - return DeferredArray(self.runtime, base=self._broadcast(shape)) + return DeferredArray(base=self._broadcast(shape)) def reshape(self, newshape: NdShape, order: OrderType) -> NumPyThunk: assert isinstance(newshape, Iterable) @@ -1066,7 +1046,7 @@ def reshape(self, newshape: NdShape, order: OrderType) -> NumPyThunk: if order != "C": # If we don't have a transform then we need to make a copy - self.runtime.warn( + runtime.warn( "cuNumeric has not implemented reshape using Fortran-like " "index order and is falling back to canonical numpy. You may " "notice significantly decreased performance for this " @@ -1076,9 +1056,9 @@ def reshape(self, newshape: NdShape, order: OrderType) -> NumPyThunk: numpy_array = self.__numpy_array__() # Force a copy here because we know we can't build a view result_array = numpy_array.reshape(newshape, order=order).copy() - result = self.runtime.get_numpy_thunk(result_array) + result = runtime.get_numpy_thunk(result_array) - return self.runtime.to_deferred_array(result) + return runtime.to_deferred_array(result) if self.shape == newshape: return self @@ -1172,7 +1152,7 @@ def reshape(self, newshape: NdShape, order: OrderType) -> NumPyThunk: else: tmp_shape += tgt_g - result = self.runtime.create_empty_thunk( + result = runtime.create_empty_thunk( tmp_shape, dtype=self.base.type, inputs=[self] ) @@ -1203,8 +1183,8 @@ def reshape(self, newshape: NdShape, order: OrderType) -> NumPyThunk: assert src.shape == tgt.shape - src_array = DeferredArray(self.runtime, src) - tgt_array = DeferredArray(self.runtime, tgt) + src_array = DeferredArray(src) + tgt_array = DeferredArray(tgt) tgt_array.copy(src_array, deep=True) if needs_delinearization and needs_linearization: @@ -1216,8 +1196,8 @@ def reshape(self, newshape: NdShape, order: OrderType) -> NumPyThunk: src_dim += len(tgt_g) assert src.shape == newshape - src_array = DeferredArray(self.runtime, src) - result = self.runtime.create_empty_thunk( + src_array = DeferredArray(src) + result = runtime.create_empty_thunk( newshape, dtype=self.base.type, inputs=[self] ) result.copy(src_array, deep=True) @@ -1242,7 +1222,7 @@ def reshape(self, newshape: NdShape, order: OrderType) -> NumPyThunk: src_dim += diff - result = DeferredArray(self.runtime, src) + result = DeferredArray(src) return result @@ -1269,7 +1249,7 @@ def squeeze( ) if result is self.base: return self - return DeferredArray(self.runtime, result) + return DeferredArray(result) def swapaxes(self, axis1: int, axis2: int) -> DeferredArray: if self.size == 1 or axis1 == axis2: @@ -1281,7 +1261,7 @@ def swapaxes(self, axis1: int, axis2: int) -> DeferredArray: dims[axis1], dims[axis2] = dims[axis2], dims[axis1] result = self.base.transpose(tuple(dims)) - return DeferredArray(self.runtime, result) + return DeferredArray(result) # Convert the source array to the destination array @auto_convert("rhs") @@ -1297,7 +1277,7 @@ def convert( assert lhs_array.dtype != rhs_array.dtype if warn: - self.runtime.warn( + runtime.warn( "cuNumeric performing implicit type conversion from " + str(rhs_array.dtype) + " to " @@ -1354,11 +1334,11 @@ def fft( ) -> None: lhs = self # For now, deferred only supported with GPU, use eager / numpy for CPU - if self.runtime.num_gpus == 0: - lhs_eager = lhs.runtime.to_eager_array(lhs) - rhs_eager = rhs.runtime.to_eager_array(rhs) + if runtime.num_gpus == 0: + lhs_eager = runtime.to_eager_array(lhs) + rhs_eager = runtime.to_eager_array(rhs) lhs_eager.fft(rhs_eager, axes, kind, direction) - lhs.base = lhs.runtime.to_deferred_array(lhs_eager).base + lhs.base = runtime.to_deferred_array(lhs_eager).base else: input = rhs.base output = lhs.base @@ -1514,7 +1494,7 @@ def contract( # of tensor cores. In the general-purpose tensor contraction case # below the tasks do this adjustment internally. if blas_op is not None and lhs_thunk.dtype == np.float16: - lhs_thunk = self.runtime.create_empty_thunk( + lhs_thunk = runtime.create_empty_thunk( lhs_thunk.shape, ty.float32, inputs=[lhs_thunk] ) @@ -1682,8 +1662,8 @@ def add_mode( # Create array from input array and indices def choose(self, rhs: Any, *args: Any) -> None: # convert all arrays to deferred - index_arr = self.runtime.to_deferred_array(rhs) - ch_def = tuple(self.runtime.to_deferred_array(c) for c in args) + index_arr = runtime.to_deferred_array(rhs) + ch_def = tuple(runtime.to_deferred_array(c) for c in args) out_arr = self.base # broadcast input array and all choices arrays to the same shape @@ -1707,10 +1687,8 @@ def select( choicelist: Iterable[Any], default: npt.NDArray[Any], ) -> None: - condlist_ = tuple(self.runtime.to_deferred_array(c) for c in condlist) - choicelist_ = tuple( - self.runtime.to_deferred_array(c) for c in choicelist - ) + condlist_ = tuple(runtime.to_deferred_array(c) for c in condlist) + choicelist_ = tuple(runtime.to_deferred_array(c) for c in choicelist) task = legate_runtime.create_auto_task( self.library, CuNumericOpCode.SELECT @@ -1823,7 +1801,7 @@ def put(self, indices: Any, values: Any, check_bounds: bool) -> None: pointN_dtype = ty.point_type(N) indirect = cast( DeferredArray, - self.runtime.create_empty_thunk( + runtime.create_empty_thunk( shape=indices.shape, dtype=pointN_dtype, inputs=[indices], @@ -1937,7 +1915,7 @@ def transpose( ) -> DeferredArray: computed_axes = tuple(axes) if axes is not None else () result = self.base.transpose(computed_axes) - return DeferredArray(self.runtime, result) + return DeferredArray(result) @auto_convert("rhs") def trilu(self, rhs: Any, k: int, lower: bool) -> None: @@ -1961,7 +1939,7 @@ def trilu(self, rhs: Any, k: int, lower: bool) -> None: def repeat( self, repeats: Any, axis: int, scalar_repeats: bool ) -> DeferredArray: - out = self.runtime.create_unbound_thunk(self.base.type, ndim=self.ndim) + out = runtime.create_unbound_thunk(self.base.type, ndim=self.ndim) task = legate_runtime.create_auto_task( self.library, CuNumericOpCode.REPEAT ) @@ -1974,7 +1952,7 @@ def repeat( task.add_scalar_arg(repeats, ty.int64) else: shape = self.shape - repeats = self.runtime.to_deferred_array(repeats).base + repeats = runtime.to_deferred_array(repeats).base for dim, extent in enumerate(shape): if dim == axis: continue @@ -2036,8 +2014,7 @@ def bincount(self, rhs: Any, weights: Optional[NumPyThunk] = None) -> None: def nonzero(self) -> tuple[NumPyThunk, ...]: results = tuple( - self.runtime.create_unbound_thunk(ty.int64) - for _ in range(self.ndim) + runtime.create_unbound_thunk(ty.int64) for _ in range(self.ndim) ) task = legate_runtime.create_auto_task( @@ -3056,7 +3033,7 @@ def random(self, gen_code: Any, args: tuple[Scalar, ...] = ()) -> None: task.add_output(self.base) task.add_scalar_arg(gen_code.value, ty.int32) - epoch = self.runtime.get_next_random_epoch() + epoch = runtime.get_next_random_epoch() task.add_scalar_arg(epoch, ty.uint32) task.add_scalar_arg(self.compute_strides(self.shape), (ty.int64,)) for arg in args: @@ -3140,8 +3117,8 @@ def unary_reduction( ) if argred: - argred_dtype = self.runtime.get_argred_type(rhs_array.base.type) - lhs_array = self.runtime.create_empty_thunk( + argred_dtype = runtime.get_argred_type(rhs_array.base.type) + lhs_array = runtime.create_empty_thunk( lhs_array.shape, dtype=argred_dtype, inputs=[self], @@ -3347,7 +3324,7 @@ def where(self, src1: Any, src2: Any, src3: Any) -> None: task.execute() def argwhere(self) -> NumPyThunk: - result = self.runtime.create_unbound_thunk(ty.int64, ndim=2) + result = runtime.create_unbound_thunk(ty.int64, ndim=2) task = legate_runtime.create_auto_task( self.library, CuNumericOpCode.ARGWHERE @@ -3373,11 +3350,11 @@ def compute_strides(shape: NdShape) -> tuple[int, ...]: @auto_convert("src") def cholesky(self, src: Any, no_tril: bool = False) -> None: - cholesky(self, src, no_tril) + cholesky_deferred(self, src, no_tril) @auto_convert("a", "b") def solve(self, a: Any, b: Any) -> None: - solve(self, a, b) + solve_deferred(self, a, b) @auto_convert("rhs") def scan( @@ -3390,7 +3367,7 @@ def scan( ) -> None: # local sum # storage for local sums accessible - temp = self.runtime.create_unbound_thunk( + temp = runtime.create_unbound_thunk( dtype=self.base.type, ndim=self.ndim ) @@ -3400,7 +3377,7 @@ def scan( else: # swap axes, always performing scan along last axis swapped = rhs.swapaxes(axis, rhs.ndim - 1) - input = self.runtime.create_empty_thunk( + input = runtime.create_empty_thunk( swapped.shape, dtype=rhs.base.type, inputs=(rhs, swapped) ) input.copy(swapped, deep=True) @@ -3441,7 +3418,7 @@ def scan( self.copy(swapped, deep=True) def unique(self) -> NumPyThunk: - result = self.runtime.create_unbound_thunk(self.base.type) + result = runtime.create_unbound_thunk(self.base.type) task = legate_runtime.create_auto_task( self.library, CuNumericOpCode.UNIQUE @@ -3450,12 +3427,12 @@ def unique(self) -> NumPyThunk: task.add_output(result.base) task.add_input(self.base) - if self.runtime.num_gpus > 0: + if runtime.num_gpus > 0: task.add_nccl_communicator() task.execute() - if self.runtime.num_gpus == 0 and self.runtime.num_procs > 1: + if runtime.num_gpus == 0 and runtime.num_procs > 1: result.base = legate_runtime.tree_reduce( self.library, CuNumericOpCode.UNIQUE_REDUCE, result.base ) @@ -3511,7 +3488,7 @@ def sort( if axis is not None and (axis >= rhs.ndim or axis < -rhs.ndim): raise ValueError("invalid axis") - sort(self, rhs, argsort, axis, stable) + sort_deferred(self, rhs, argsort, axis, stable) @auto_convert("rhs") def partition( @@ -3532,7 +3509,7 @@ def partition( raise ValueError("invalid axis") # fallback to sort for now - sort(self, rhs, argpartition, axis, False) + sort_deferred(self, rhs, argpartition, axis, False) def create_window(self, op_code: WindowOpCode, M: int, *args: Any) -> None: task = legate_runtime.create_auto_task( @@ -3594,7 +3571,7 @@ def _wrap(self, src: Any, new_len: int) -> None: pointN_dtype = ty.point_type(N) indirect = cast( DeferredArray, - self.runtime.create_empty_thunk( + runtime.create_empty_thunk( shape=(new_len,), dtype=pointN_dtype, inputs=[src], diff --git a/cunumeric/eager.py b/cunumeric/eager.py index 350db6262e..eb97e2a2dc 100644 --- a/cunumeric/eager.py +++ b/cunumeric/eager.py @@ -43,6 +43,7 @@ WindowOpCode, ) from .deferred import DeferredArray +from .runtime import runtime from .thunk import NumPyThunk from .utils import is_advanced_indexing, is_supported_type, to_core_dtype @@ -50,7 +51,6 @@ import numpy.typing as npt from .config import BitGeneratorType, FFTType - from .runtime import Runtime from .types import ( BitOrder, ConvolveMode, @@ -219,12 +219,11 @@ class EagerArray(NumPyThunk): def __init__( self, - runtime: Runtime, array: npt.NDArray[Any], parent: Optional[EagerArray] = None, key: Optional[tuple[Any, ...]] = None, ) -> None: - super().__init__(runtime, array.dtype) + super().__init__(array.dtype) self.array: npt.NDArray[Any] = array self.parent: Optional[EagerArray] = parent self.children: list[EagerArray] = [] @@ -256,11 +255,11 @@ def check_eager_args(self, *args: Any) -> None: if self.deferred is not None: return for arg in args: - if self.runtime.is_eager_array(arg): + if runtime.is_eager_array(arg): if arg.deferred is not None: self.to_deferred_array() break - elif self.runtime.is_deferred_array(arg): + elif runtime.is_deferred_array(arg): self.to_deferred_array() break elif arg is None or not isinstance(arg, NumPyThunk): @@ -272,7 +271,7 @@ def _convert_children(self) -> None: """ Traverse down our children and convert them to deferred arrays. """ - assert self.runtime.is_deferred_array(self.deferred) + assert runtime.is_deferred_array(self.deferred) for child in self.children: if child.deferred is None: assert child.key is not None @@ -301,17 +300,17 @@ def to_deferred_array(self) -> DeferredArray: # We are at the root of the tree so we need to # actually make a DeferredArray to use if self.array.size == 1: - runtime = get_legate_runtime() - store = runtime.create_store_from_scalar( + legate_runtime = get_legate_runtime() + store = legate_runtime.create_store_from_scalar( Scalar( self.array.tobytes(), to_core_dtype(self.array.dtype), ), shape=self.shape, ) - self.deferred = DeferredArray(self.runtime, store) + self.deferred = DeferredArray(store) else: - self.deferred = self.runtime.find_or_create_array_thunk( + self.deferred = runtime.find_or_create_array_thunk( self.array, share=self.escaped, defer=True, @@ -326,18 +325,18 @@ def to_deferred_array(self) -> DeferredArray: def imag(self) -> NumPyThunk: if self.deferred is not None: return self.deferred.imag() - return EagerArray(self.runtime, self.array.imag) + return EagerArray(self.array.imag) def real(self) -> NumPyThunk: if self.deferred is not None: return self.deferred.real() - return EagerArray(self.runtime, self.array.real) + return EagerArray(self.array.real) def conj(self) -> NumPyThunk: if self.deferred is not None: return self.deferred.conj() - return EagerArray(self.runtime, self.array.conj()) + return EagerArray(self.array.conj()) def convolve(self, v: Any, out: Any, mode: ConvolveMode) -> None: self.check_eager_args(v, out) @@ -414,7 +413,7 @@ def _create_indexing_key(self, key: Any) -> Any: result += (self._create_indexing_key(k),) return result assert isinstance(key, NumPyThunk) - return self.runtime.to_eager_array(key).array + return runtime.to_eager_array(key).array def get_item(self, key: Any) -> NumPyThunk: if self.deferred is not None: @@ -422,12 +421,10 @@ def get_item(self, key: Any) -> NumPyThunk: if is_advanced_indexing(key): index_key = self._create_indexing_key(key) out = self.array[index_key] - result = EagerArray(self.runtime, out) + result = EagerArray(out) else: child = self.array[key] - result = EagerArray( - self.runtime, child, parent=self, key=("get_item", key) - ) + result = EagerArray(child, parent=self, key=("get_item", key)) self.children.append(result) return result @@ -454,10 +451,9 @@ def reshape(self, newshape: NdShape, order: OrderType) -> NumPyThunk: child = self.array.reshape(newshape, order=order) # See if we are aliased or not if child.base is None: - result = EagerArray(self.runtime, child) + result = EagerArray(child) else: result = EagerArray( - self.runtime, child, parent=self, key=("reshape", newshape, order), @@ -475,9 +471,7 @@ def squeeze(self, axis: Optional[int]) -> NumPyThunk: return self # Should be aliased with parent region assert child.base is not None - result = EagerArray( - self.runtime, child, parent=self, key=("squeeze", axis) - ) + result = EagerArray(child, parent=self, key=("squeeze", axis)) self.children.append(result) return result @@ -487,9 +481,7 @@ def swapaxes(self, axis1: int, axis2: int) -> NumPyThunk: child = self.array.swapaxes(axis1, axis2) # Should be aliased with parent region assert child.base is not None - result = EagerArray( - self.runtime, child, parent=self, key=("swapaxes", axis1, axis2) - ) + result = EagerArray(child, parent=self, key=("swapaxes", axis1, axis2)) self.children.append(result) return result @@ -532,9 +524,7 @@ def transpose(self, axes: Union[tuple[int, ...], list[int]]) -> NumPyThunk: child = self.array.transpose(cast(Any, axes)) # Should be aliased with parent region assert child.base is not None - result = EagerArray( - self.runtime, child, parent=self, key=("transpose", axes) - ) + result = EagerArray(child, parent=self, key=("transpose", axes)) self.children.append(result) return result @@ -554,7 +544,7 @@ def repeat( array = np.repeat(self.array, repeats.array, axis) else: array = np.repeat(self.array, repeats, axis) - return EagerArray(self.runtime, array) + return EagerArray(array) def flip(self, rhs: Any, axes: Union[None, int, tuple[int, ...]]) -> None: self.check_eager_args(rhs) @@ -573,9 +563,7 @@ def broadcast_to(self, shape: NdShape) -> NumPyThunk: child = np.broadcast_to(self.array, shape) # Should be aliased with parent region assert child.base is not None - result = EagerArray( - self.runtime, child, parent=self, key=("broadcast_to", shape) - ) + result = EagerArray(child, parent=self, key=("broadcast_to", shape)) self.children.append(result) return result @@ -718,7 +706,7 @@ def nonzero(self) -> tuple[NumPyThunk, ...]: arrays = self.array.nonzero() result: tuple[NumPyThunk, ...] = () for array in arrays: - result += (EagerArray(self.runtime, array),) + result += (EagerArray(array),) return result def searchsorted(self, rhs: Any, v: Any, side: SortSide = "left") -> None: @@ -1649,7 +1637,7 @@ def argwhere(self) -> NumPyThunk: if self.deferred is not None: return self.deferred.argwhere() else: - return EagerArray(self.runtime, np.argwhere(self.array)) + return EagerArray(np.argwhere(self.array)) def trilu(self, rhs: Any, k: int, lower: bool) -> None: self.check_eager_args(rhs) @@ -1718,7 +1706,7 @@ def unique(self) -> NumPyThunk: if self.deferred is not None: return self.deferred.unique() else: - return EagerArray(self.runtime, np.unique(self.array)) + return EagerArray(np.unique(self.array)) def create_window(self, op_code: WindowOpCode, M: int, *args: Any) -> None: if self.deferred is not None: diff --git a/cunumeric/fft/__init__.py b/cunumeric/fft/__init__.py index d0d3eb28d8..e541941cf1 100644 --- a/cunumeric/fft/__init__.py +++ b/cunumeric/fft/__init__.py @@ -16,9 +16,9 @@ import numpy.fft as _npfft -from cunumeric.array import maybe_convert_to_np_ndarray -from cunumeric.fft.fft import * -from cunumeric.coverage import clone_module +from ..array import maybe_convert_to_np_ndarray +from ..coverage import clone_module +from .fft import * clone_module(_npfft, globals(), maybe_convert_to_np_ndarray) diff --git a/cunumeric/linalg/__init__.py b/cunumeric/linalg/__init__.py index 6904bf72cd..18542b95da 100644 --- a/cunumeric/linalg/__init__.py +++ b/cunumeric/linalg/__init__.py @@ -16,10 +16,9 @@ import numpy.linalg as _nplinalg -from cunumeric.array import maybe_convert_to_np_ndarray -from cunumeric.linalg.linalg import * -from cunumeric.linalg.exception import * -from cunumeric.coverage import clone_module +from ..array import maybe_convert_to_np_ndarray +from ..coverage import clone_module +from .linalg import * clone_module(_nplinalg, globals(), maybe_convert_to_np_ndarray) diff --git a/cunumeric/linalg/cholesky.py b/cunumeric/linalg/_cholesky.py similarity index 98% rename from cunumeric/linalg/cholesky.py rename to cunumeric/linalg/_cholesky.py index a8037457b7..d0b29f1b61 100644 --- a/cunumeric/linalg/cholesky.py +++ b/cunumeric/linalg/_cholesky.py @@ -25,9 +25,9 @@ ) from legate.settings import settings -from cunumeric.config import CuNumericOpCode - -from .exception import LinAlgError +from ..config import CuNumericOpCode +from ..runtime import runtime +from ._exception import LinAlgError legate_runtime = get_legate_runtime() @@ -254,10 +254,9 @@ def _batched_cholesky( task.execute() -def cholesky( +def cholesky_deferred( output: DeferredArray, input: DeferredArray, no_tril: bool ) -> None: - runtime = output.runtime library = runtime.library if len(input.base.shape) > 2: if no_tril: diff --git a/cunumeric/linalg/exception.py b/cunumeric/linalg/_exception.py similarity index 100% rename from cunumeric/linalg/exception.py rename to cunumeric/linalg/_exception.py diff --git a/cunumeric/linalg/solve.py b/cunumeric/linalg/_solve.py similarity index 91% rename from cunumeric/linalg/solve.py rename to cunumeric/linalg/_solve.py index ddcfb2816e..b543eb28ab 100644 --- a/cunumeric/linalg/solve.py +++ b/cunumeric/linalg/_solve.py @@ -19,10 +19,10 @@ import legate.core.types as ty from legate.core import broadcast, get_legate_runtime -from cunumeric.config import CuNumericOpCode - -from .cholesky import transpose_copy_single -from .exception import LinAlgError +from ..config import CuNumericOpCode +from ..runtime import runtime +from ._cholesky import transpose_copy_single +from ._exception import LinAlgError if TYPE_CHECKING: from legate.core import Library, LogicalStore @@ -75,10 +75,11 @@ def mp_solve( task.execute() -def solve(output: DeferredArray, a: DeferredArray, b: DeferredArray) -> None: +def solve_deferred( + output: DeferredArray, a: DeferredArray, b: DeferredArray +) -> None: from ..deferred import DeferredArray - runtime = output.runtime library = output.library if ( diff --git a/cunumeric/linalg/linalg.py b/cunumeric/linalg/linalg.py index 726a7a0386..094d0732a9 100644 --- a/cunumeric/linalg/linalg.py +++ b/cunumeric/linalg/linalg.py @@ -24,11 +24,10 @@ normalize_axis_tuple, ) -from cunumeric._ufunc.math import add, sqrt as _sqrt -from cunumeric.array import add_boilerplate, convert_to_cunumeric_ndarray -from cunumeric.module import dot, empty_like, eye, matmul, ndarray - -from .exception import LinAlgError +from .._ufunc.math import add, sqrt as _sqrt +from ..array import add_boilerplate, convert_to_cunumeric_ndarray +from ..module import dot, empty_like, eye, matmul, ndarray +from ._exception import LinAlgError if TYPE_CHECKING: from typing import Optional @@ -82,7 +81,7 @@ def cholesky(a: ndarray) -> ndarray: elif shape[-1] != shape[-2]: raise ValueError("Last 2 dimensions of the array must be square") - return _cholesky(a) + return _thunk_cholesky(a) @add_boilerplate("a", "b") @@ -158,7 +157,7 @@ def solve(a: ndarray, b: ndarray, out: Optional[ndarray] = None) -> ndarray: if a.size == 0 or b.size == 0: return empty_like(b) - return _solve(a, b, out) + return _thunk_solve(a, b, out) # This implementation is adapted closely from NumPy @@ -587,7 +586,7 @@ def norm( raise ValueError("Improper number of dimensions to norm") -def _cholesky(a: ndarray, no_tril: bool = False) -> ndarray: +def _thunk_cholesky(a: ndarray, no_tril: bool = False) -> ndarray: """Cholesky decomposition. Return the Cholesky decomposition, `L * L.H`, of the square matrix `a`, @@ -635,7 +634,7 @@ def _cholesky(a: ndarray, no_tril: bool = False) -> ndarray: return output -def _solve( +def _thunk_solve( a: ndarray, b: ndarray, output: Optional[ndarray] = None ) -> ndarray: if a.dtype.kind not in ("f", "c"): diff --git a/cunumeric/ma/__init__.py b/cunumeric/ma/__init__.py index 14a9e0d467..6e8f14bf4d 100644 --- a/cunumeric/ma/__init__.py +++ b/cunumeric/ma/__init__.py @@ -16,9 +16,9 @@ import numpy.ma as _ma -from cunumeric.array import maybe_convert_to_np_ndarray -from cunumeric.coverage import clone_module -from cunumeric.ma._masked_array import MaskedArray +from ..array import maybe_convert_to_np_ndarray +from ..coverage import clone_module +from ._masked_array import MaskedArray masked_array = MaskedArray diff --git a/cunumeric/module.py b/cunumeric/module.py index c346321c80..cbe5c2eff5 100644 --- a/cunumeric/module.py +++ b/cunumeric/module.py @@ -41,8 +41,6 @@ normalize_axis_tuple, ) -from cunumeric.coverage import is_implemented - from ._ufunc.comparison import maximum, minimum from ._ufunc.floating import floor, isnan from ._ufunc.math import add, multiply @@ -54,6 +52,7 @@ ndarray, ) from .config import BinaryOpCode, ScanCode, UnaryRedCode +from .coverage import is_implemented from .runtime import runtime from .settings import settings as cunumeric_settings from .types import NdShape, NdShapeLike, OrderType, SortSide diff --git a/cunumeric/random/__init__.py b/cunumeric/random/__init__.py index 0f397a0c5f..2214b99ab1 100644 --- a/cunumeric/random/__init__.py +++ b/cunumeric/random/__init__.py @@ -16,16 +16,16 @@ import numpy.random as _nprandom -from cunumeric.array import maybe_convert_to_np_ndarray -from cunumeric.coverage import clone_module -from cunumeric.runtime import runtime +from ..array import maybe_convert_to_np_ndarray +from ..coverage import clone_module +from ..runtime import runtime if runtime.has_curand: - from cunumeric.random.random import * - from cunumeric.random.bitgenerator import * - from cunumeric.random.generator import * + from ._random import * + from ._bitgenerator import * + from ._generator import * else: - from cunumeric.random.legacy import * + from ._legacy import * clone_module( _nprandom, @@ -36,4 +36,5 @@ del maybe_convert_to_np_ndarray del clone_module +del runtime del _nprandom diff --git a/cunumeric/random/bitgenerator.py b/cunumeric/random/_bitgenerator.py similarity index 100% rename from cunumeric/random/bitgenerator.py rename to cunumeric/random/_bitgenerator.py diff --git a/cunumeric/random/generator.py b/cunumeric/random/_generator.py similarity index 99% rename from cunumeric/random/generator.py rename to cunumeric/random/_generator.py index 7145de662c..ed8b9a6675 100644 --- a/cunumeric/random/generator.py +++ b/cunumeric/random/_generator.py @@ -18,7 +18,7 @@ import numpy as np -from cunumeric.random.bitgenerator import XORWOW, BitGenerator +from ._bitgenerator import XORWOW, BitGenerator if TYPE_CHECKING: import numpy.typing as npt diff --git a/cunumeric/random/legacy.py b/cunumeric/random/_legacy.py similarity index 98% rename from cunumeric/random/legacy.py rename to cunumeric/random/_legacy.py index 4070653e8a..cab8939237 100644 --- a/cunumeric/random/legacy.py +++ b/cunumeric/random/_legacy.py @@ -19,8 +19,8 @@ import numpy as np import numpy.random as nprandom -from cunumeric.array import ndarray -from cunumeric.runtime import runtime +from ..array import ndarray +from ..runtime import runtime if TYPE_CHECKING: import numpy.typing as npt diff --git a/cunumeric/random/random.py b/cunumeric/random/_random.py similarity index 94% rename from cunumeric/random/random.py rename to cunumeric/random/_random.py index d877d391d2..d3dc144b5a 100644 --- a/cunumeric/random/random.py +++ b/cunumeric/random/_random.py @@ -18,10 +18,10 @@ import numpy as np -from cunumeric.array import ndarray -from cunumeric.coverage import clone_class -from cunumeric.random import generator -from cunumeric.runtime import runtime +from ..array import ndarray +from ..coverage import clone_class +from ..runtime import runtime +from ._generator import default_rng, get_static_generator # NOQA if TYPE_CHECKING: import numpy.typing as npt @@ -29,9 +29,6 @@ from ..types import NdShapeLike -default_rng = generator.default_rng - - def seed(init: Union[int, None] = None) -> None: """ Reseed the legacy random number generator. @@ -100,7 +97,7 @@ def beta( -------- Multiple GPUs, Multiple CPUs """ - return generator.get_static_generator().beta(a, b, size, dtype) + return get_static_generator().beta(a, b, size, dtype) def binomial( @@ -145,7 +142,7 @@ def binomial( -------- Multiple GPUs, Multiple CPUs """ - return generator.get_static_generator().binomial(ntrials, p, size, dtype) + return get_static_generator().binomial(ntrials, p, size, dtype) def bytes(length: int) -> ndarray: @@ -172,7 +169,7 @@ def bytes(length: int) -> ndarray: -------- Multiple GPUs, Multiple CPUs """ - return generator.get_static_generator().bytes(length) + return get_static_generator().bytes(length) def chisquare( @@ -218,7 +215,7 @@ def chisquare( -------- Multiple GPUs, Multiple CPUs """ - return generator.get_static_generator().chisquare(df, size, dtype) + return get_static_generator().chisquare(df, size, dtype) def exponential( @@ -278,7 +275,7 @@ def exponential( -------- Multiple GPUs, Multiple CPUs """ - return generator.get_static_generator().exponential(scale, size, dtype) + return get_static_generator().exponential(scale, size, dtype) def f( @@ -326,7 +323,7 @@ def f( -------- Multiple GPUs, Multiple CPUs """ - return generator.get_static_generator().f(dfnum, dfden, size, dtype) + return get_static_generator().f(dfnum, dfden, size, dtype) def gamma( @@ -369,7 +366,7 @@ def gamma( -------- Multiple GPUs, Multiple CPUs """ - return generator.get_static_generator().gamma(shape, scale, size, dtype) + return get_static_generator().gamma(shape, scale, size, dtype) def geometric( @@ -416,7 +413,7 @@ def geometric( -------- Multiple GPUs, Multiple CPUs """ - return generator.get_static_generator().geometric(p, size, dtype) + return get_static_generator().geometric(p, size, dtype) def gumbel( @@ -458,7 +455,7 @@ def gumbel( -------- Multiple GPUs, Multiple CPUs """ - return generator.get_static_generator().gumbel(loc, scale, size, dtype) + return get_static_generator().gumbel(loc, scale, size, dtype) def hypergeometric( @@ -508,7 +505,7 @@ def hypergeometric( -------- Multiple GPUs, Multiple CPUs """ - return generator.get_static_generator().hypergeometric( + return get_static_generator().hypergeometric( ngood, nbad, nsample, size, dtype ) @@ -555,7 +552,7 @@ def laplace( -------- Multiple GPUs, Multiple CPUs """ - return generator.get_static_generator().laplace(loc, scale, size, dtype) + return get_static_generator().laplace(loc, scale, size, dtype) def logistic( @@ -597,7 +594,7 @@ def logistic( -------- Multiple GPUs, Multiple CPUs """ - return generator.get_static_generator().logistic(loc, scale, size, dtype) + return get_static_generator().logistic(loc, scale, size, dtype) def lognormal( @@ -641,7 +638,7 @@ def lognormal( -------- Multiple GPUs, Multiple CPUs """ - return generator.get_static_generator().lognormal(mean, sigma, size, dtype) + return get_static_generator().lognormal(mean, sigma, size, dtype) def logseries( @@ -679,7 +676,7 @@ def logseries( -------- Multiple GPUs, Multiple CPUs """ - return generator.get_static_generator().logseries(p, size, dtype) + return get_static_generator().logseries(p, size, dtype) def negative_binomial( @@ -723,9 +720,7 @@ def negative_binomial( -------- Multiple GPUs, Multiple CPUs """ - return generator.get_static_generator().negative_binomial( - n, p, size, dtype - ) + return get_static_generator().negative_binomial(n, p, size, dtype) def noncentral_chisquare( @@ -767,9 +762,7 @@ def noncentral_chisquare( -------- Multiple GPUs, Multiple CPUs """ - return generator.get_static_generator().noncentral_chisquare( - df, nonc, size, dtype - ) + return get_static_generator().noncentral_chisquare(df, nonc, size, dtype) def noncentral_f( @@ -816,9 +809,7 @@ def noncentral_f( -------- Multiple GPUs, Multiple CPUs """ - return generator.get_static_generator().noncentral_f( - dfnum, dfden, nonc, size, dtype - ) + return get_static_generator().noncentral_f(dfnum, dfden, nonc, size, dtype) def normal( @@ -873,7 +864,7 @@ def normal( -------- Multiple GPUs, Multiple CPUs """ - return generator.get_static_generator().normal(loc, scale, size, dtype) + return get_static_generator().normal(loc, scale, size, dtype) def pareto( @@ -926,7 +917,7 @@ def pareto( -------- Multiple GPUs, Multiple CPUs """ - return generator.get_static_generator().pareto(a, size, dtype) + return get_static_generator().pareto(a, size, dtype) def poisson( @@ -964,7 +955,7 @@ def poisson( -------- Multiple GPUs, Multiple CPUs """ - return generator.get_static_generator().poisson(lam, size) + return get_static_generator().poisson(lam, size) def power( @@ -1007,7 +998,7 @@ def power( -------- Multiple GPUs, Multiple CPUs """ - return generator.get_static_generator().power(a, size, dtype) + return get_static_generator().power(a, size, dtype) def rand(*shapeargs: int) -> Union[float, ndarray]: @@ -1100,7 +1091,7 @@ def randint( elif low >= high: raise ValueError("low >= high") - return generator.get_static_generator().integers(low, high, size, dtype) + return get_static_generator().integers(low, high, size, dtype) def randn(*shapeargs: int) -> Union[float, ndarray]: @@ -1156,7 +1147,7 @@ def random( -------- Multiple GPUs, Multiple CPUs """ - return generator.get_static_generator().random(size) + return get_static_generator().random(size) # deprecated in numpy from version 1.11.0 @@ -1289,7 +1280,7 @@ def rayleigh( -------- Multiple GPUs, Multiple CPUs """ - return generator.get_static_generator().rayleigh(scale, size, dtype) + return get_static_generator().rayleigh(scale, size, dtype) sample = random_sample @@ -1326,7 +1317,7 @@ def standard_cauchy( -------- Multiple GPUs, Multiple CPUs """ - return generator.get_static_generator().standard_cauchy(size, dtype) + return get_static_generator().standard_cauchy(size, dtype) def standard_exponential( @@ -1361,7 +1352,7 @@ def standard_exponential( -------- Multiple GPUs, Multiple CPUs """ - return generator.get_static_generator().standard_exponential(size, dtype) + return get_static_generator().standard_exponential(size, dtype) def standard_gamma( @@ -1399,7 +1390,7 @@ def standard_gamma( -------- Multiple GPUs, Multiple CPUs """ - return generator.get_static_generator().standard_gamma(shape, size, dtype) + return get_static_generator().standard_gamma(shape, size, dtype) def standard_t( @@ -1439,7 +1430,7 @@ def standard_t( -------- Multiple GPUs, Multiple CPUs """ - return generator.get_static_generator().standard_t(df, size, dtype) + return get_static_generator().standard_t(df, size, dtype) def triangular( @@ -1487,9 +1478,7 @@ def triangular( -------- Multiple GPUs, Multiple CPUs """ - return generator.get_static_generator().triangular( - left, mode, right, size, dtype - ) + return get_static_generator().triangular(left, mode, right, size, dtype) def uniform( @@ -1537,7 +1526,7 @@ def uniform( -------- Multiple GPUs, Multiple CPUs """ - return generator.get_static_generator().uniform(low, high, size, dtype) + return get_static_generator().uniform(low, high, size, dtype) def vonmises( @@ -1583,7 +1572,7 @@ def vonmises( -------- Multiple GPUs, Multiple CPUs """ - return generator.get_static_generator().vonmises(mu, kappa, size, dtype) + return get_static_generator().vonmises(mu, kappa, size, dtype) def wald( @@ -1630,7 +1619,7 @@ def wald( -------- Multiple GPUs, Multiple CPUs """ - return generator.get_static_generator().wald(mean, scale, size, dtype) + return get_static_generator().wald(mean, scale, size, dtype) def weibull( @@ -1675,7 +1664,7 @@ def weibull( -------- Multiple GPUs, Multiple CPUs """ - return generator.get_static_generator().weibull(a, size, dtype) + return get_static_generator().weibull(a, size, dtype) def zipf( @@ -1718,7 +1707,7 @@ def zipf( -------- Multiple GPUs, Multiple CPUs """ - return generator.get_static_generator().zipf(a, size, dtype) + return get_static_generator().zipf(a, size, dtype) def _random_state_fallback(obj: Any) -> Any: diff --git a/cunumeric/runtime.py b/cunumeric/runtime.py index 1bb3cb6bf2..7366828dc5 100644 --- a/cunumeric/runtime.py +++ b/cunumeric/runtime.py @@ -30,19 +30,23 @@ CuNumericTunable, cunumeric_lib, ) -from .deferred import DeferredArray -from .eager import EagerArray -from .settings import settings -from .thunk import NumPyThunk -from .types import NdShape from .utils import calculate_volume, find_last_user_stacklevel, to_core_dtype +# We need to be careful about importing from other cunumeric modules here. The +# runtime is global and used in many places, but also depends on many of the +# other modules. Things like config and utils are OK, but imports for thunks, +# array types, etc. need to be deferred in order to avoid circular imports. + + if TYPE_CHECKING: import numpy.typing as npt from legate.core import AutoTask, ManualTask from .array import ndarray - + from .deferred import DeferredArray + from .eager import EagerArray + from .thunk import NumPyThunk + from .types import NdShape DIMENSION = int @@ -71,6 +75,8 @@ def __init__(self) -> None: cunumeric_lib.shared_object.cunumeric_has_cusolvermp() ) + from .settings import settings + settings.warn = settings.warn() or legate_settings.test() if self.num_gpus > 0 and settings.preload_cudalibs(): @@ -90,6 +96,8 @@ def num_gpus(self) -> int: def record_api_call( self, name: str, location: str, implemented: bool ) -> None: + from .settings import settings + assert settings.report_coverage() self.api_calls.append((name, location, implemented)) @@ -131,6 +139,8 @@ def _report_coverage(self) -> None: f"({implemented / total * 100}%)" ) + from .settings import settings + if (dump_csv := settings.report_dump_csv()) is not None: with open(dump_csv, "w") as f: print("function_name,location,implemented", file=f) @@ -138,6 +148,8 @@ def _report_coverage(self) -> None: print(f"{func_name},{loc},{impl}", file=f) def destroy(self) -> None: + from .settings import settings + assert not self.destroyed if settings.report_coverage(): self._report_coverage() @@ -241,7 +253,10 @@ def get_numpy_thunk( raise NotImplementedError( "Array must be non-nullable and not nested" ) - return DeferredArray(self, array.data) + + from .deferred import DeferredArray + + return DeferredArray(array.data) # See if this is a normal numpy array # Make sure to convert numpy matrices to numpy arrays here # as the former doesn't behave quite like the latter @@ -334,6 +349,8 @@ def compute_parent_child_mapping( def find_or_create_array_thunk( self, array: npt.NDArray[Any], share: bool = False, defer: bool = False ) -> NumPyThunk: + from .deferred import DeferredArray + assert isinstance(array, np.ndarray) # We have to be really careful here to handle the case of # aliased numpy arrays that are passed in from the application @@ -377,7 +394,7 @@ def find_or_create_array_thunk( Scalar(array.tobytes(), dtype), shape=array.shape, ) - return DeferredArray(self, store) + return DeferredArray(store) # This is not a scalar so make a field store = legate_runtime.create_store_from_buffer( @@ -387,13 +404,14 @@ def find_or_create_array_thunk( not share, # read_only ) return DeferredArray( - self, store, numpy_array=array if share else None, ) + from .eager import EagerArray + # Make this into an eager evaluated thunk - return EagerArray(self, array) + return EagerArray(array) def create_empty_thunk( self, @@ -401,26 +419,32 @@ def create_empty_thunk( dtype: ty.Type, inputs: Optional[Sequence[NumPyThunk]] = None, ) -> NumPyThunk: + from .deferred import DeferredArray + if self.is_eager_shape(shape) and self.are_all_eager_inputs(inputs): return self.create_eager_thunk(shape, dtype.to_numpy_dtype()) store = legate_runtime.create_store( dtype, shape=shape, optimize_scalar=True ) - return DeferredArray(self, store) + return DeferredArray(store) def create_eager_thunk( self, shape: NdShape, dtype: np.dtype[Any], ) -> NumPyThunk: - return EagerArray(self, np.empty(shape, dtype=dtype)) + from .eager import EagerArray + + return EagerArray(np.empty(shape, dtype=dtype)) def create_unbound_thunk( self, dtype: ty.Type, ndim: int = 1 ) -> DeferredArray: + from .deferred import DeferredArray + store = legate_runtime.create_store(dtype, ndim=ndim) - return DeferredArray(self, store) + return DeferredArray(store) def is_eager_shape(self, shape: NdShape) -> bool: volume = calculate_volume(shape) @@ -435,6 +459,8 @@ def is_eager_shape(self, shape: NdShape) -> bool: if len(shape) > LEGATE_MAX_DIM: return True + from .settings import settings + # CUNUMERIC_FORCE_THUNK == "eager" if settings.force_thunk() == "eager": return True @@ -451,6 +477,9 @@ def is_eager_shape(self, shape: NdShape) -> bool: @staticmethod def are_all_eager_inputs(inputs: Optional[Sequence[NumPyThunk]]) -> bool: + from .eager import EagerArray + from .thunk import NumPyThunk + if inputs is None: return True for inp in inputs: @@ -461,19 +490,25 @@ def are_all_eager_inputs(inputs: Optional[Sequence[NumPyThunk]]) -> bool: @staticmethod def is_eager_array(array: NumPyThunk) -> TypeGuard[EagerArray]: + from .eager import EagerArray + return isinstance(array, EagerArray) @staticmethod def is_deferred_array( array: Optional[NumPyThunk], ) -> TypeGuard[DeferredArray]: + from .deferred import DeferredArray + return isinstance(array, DeferredArray) def to_eager_array(self, array: NumPyThunk) -> EagerArray: + from .eager import EagerArray + if self.is_eager_array(array): return array elif self.is_deferred_array(array): - return EagerArray(self, array.__numpy_array__()) + return EagerArray(array.__numpy_array__()) else: raise RuntimeError("invalid array type") @@ -486,6 +521,8 @@ def to_deferred_array(self, array: NumPyThunk) -> DeferredArray: raise RuntimeError("invalid array type") def warn(self, msg: str, category: type = UserWarning) -> None: + from .settings import settings + if not settings.warn(): return stacklevel = find_last_user_stacklevel() diff --git a/cunumeric/thunk.py b/cunumeric/thunk.py index a822a043d4..c99c7b0a4b 100644 --- a/cunumeric/thunk.py +++ b/cunumeric/thunk.py @@ -18,6 +18,7 @@ from typing import TYPE_CHECKING, Any, Iterable, Optional, Sequence, Union from .config import ConvertCode +from .runtime import runtime if TYPE_CHECKING: import numpy as np @@ -33,7 +34,6 @@ UnaryRedCode, WindowOpCode, ) - from .runtime import Runtime from .types import ( BitOrder, ConvolveMode, @@ -53,8 +53,7 @@ class NumPyThunk(ABC): :meta private: """ - def __init__(self, runtime: Runtime, dtype: np.dtype[Any]) -> None: - self.runtime = runtime + def __init__(self, dtype: np.dtype[Any]) -> None: self.library = runtime.library self.dtype = dtype diff --git a/tests/integration/utils/random.py b/tests/integration/utils/random.py index 89d21c20d0..b2422193a6 100644 --- a/tests/integration/utils/random.py +++ b/tests/integration/utils/random.py @@ -26,11 +26,11 @@ def __init__( self.bit_generator = num.random.XORWOW(seed) def random_raw(self, shape): - gen = num.random.generator.get_static_generator() + gen = num.random._generator.get_static_generator() return gen.bit_generator.random_raw(shape) def integers(self, low, high, size, dtype, endpoint): - return num.random.generator.get_static_generator().integers( + return num.random._generator.get_static_generator().integers( low, high, size, dtype, endpoint ) diff --git a/tests/unit/cunumeric/random/test_bitgenerator.py b/tests/unit/cunumeric/random/test_bitgenerator.py index 895a49ccc4..5dd13392fa 100644 --- a/tests/unit/cunumeric/random/test_bitgenerator.py +++ b/tests/unit/cunumeric/random/test_bitgenerator.py @@ -16,7 +16,7 @@ import pytest from mock import patch -import cunumeric.random.bitgenerator as m # module under test +import cunumeric.random._bitgenerator as m # module under test from cunumeric.config import BitGeneratorType From 116010b3924f66bf615ca47cb981b88a8545b0da Mon Sep 17 00:00:00 2001 From: Manolis Papadakis Date: Thu, 15 Feb 2024 15:52:44 -0800 Subject: [PATCH 154/462] Fix compilation on MacOS (#121) --- cunumeric_cpp.cmake | 1 + src/cunumeric.mk | 179 ------------------------- src/cunumeric/binary/binary_op.cc | 28 ---- src/cunumeric/binary/binary_op_util.cc | 10 +- src/cunumeric/binary/binary_op_util.h | 2 +- src/cunumeric/ndarray.cc | 21 +-- src/cunumeric/ndarray.h | 4 +- src/cunumeric/operators.cc | 12 +- src/cunumeric/operators.h | 8 +- src/cunumeric/operators.inl | 2 +- src/cunumeric/runtime.cc | 2 +- src/cunumeric/runtime.h | 2 +- 12 files changed, 32 insertions(+), 239 deletions(-) delete mode 100644 src/cunumeric.mk diff --git a/cunumeric_cpp.cmake b/cunumeric_cpp.cmake index 14a2abb06d..41dd58682c 100644 --- a/cunumeric_cpp.cmake +++ b/cunumeric_cpp.cmake @@ -124,6 +124,7 @@ list(APPEND cunumeric_SOURCES src/cunumeric/scan/scan_global.cc src/cunumeric/scan/scan_local.cc src/cunumeric/binary/binary_op.cc + src/cunumeric/binary/binary_op_util.cc src/cunumeric/binary/binary_red.cc src/cunumeric/bits/packbits.cc src/cunumeric/bits/unpackbits.cc diff --git a/src/cunumeric.mk b/src/cunumeric.mk deleted file mode 100644 index 1a3b5a4c98..0000000000 --- a/src/cunumeric.mk +++ /dev/null @@ -1,179 +0,0 @@ -# Copyright 2021-2022 NVIDIA Corporation -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# - -# List all the application source files that need OpenMP separately -# since we have to add the -fopenmp flag to CC_FLAGS for them -GEN_CPU_SRC += cunumeric/ternary/where.cc \ - cunumeric/scan/scan_global.cc \ - cunumeric/scan/scan_local.cc \ - cunumeric/binary/binary_op.cc \ - cunumeric/binary/binary_op_util.cc \ - cunumeric/binary/binary_red.cc \ - cunumeric/bits/packbits.cc \ - cunumeric/bits/unpackbits.cc \ - cunumeric/unary/scalar_unary_red.cc \ - cunumeric/unary/unary_op.cc \ - cunumeric/unary/unary_red.cc \ - cunumeric/unary/convert.cc \ - cunumeric/nullary/arange.cc \ - cunumeric/nullary/eye.cc \ - cunumeric/nullary/fill.cc \ - cunumeric/nullary/window.cc \ - cunumeric/index/advanced_indexing.cc \ - cunumeric/index/choose.cc \ - cunumeric/index/repeat.cc \ - cunumeric/index/wrap.cc \ - cunumeric/index/zip.cc \ - cunumeric/item/read.cc \ - cunumeric/item/write.cc \ - cunumeric/matrix/contract.cc \ - cunumeric/matrix/diag.cc \ - cunumeric/matrix/gemm.cc \ - cunumeric/matrix/matmul.cc \ - cunumeric/matrix/matvecmul.cc \ - cunumeric/matrix/dot.cc \ - cunumeric/matrix/potrf.cc \ - cunumeric/matrix/solve.cc \ - cunumeric/matrix/syrk.cc \ - cunumeric/matrix/tile.cc \ - cunumeric/matrix/transpose.cc \ - cunumeric/matrix/trilu.cc \ - cunumeric/matrix/trsm.cc \ - cunumeric/matrix/util.cc \ - cunumeric/random/rand.cc \ - cunumeric/search/argwhere.cc \ - cunumeric/search/nonzero.cc \ - cunumeric/set/unique.cc \ - cunumeric/set/unique_reduce.cc \ - cunumeric/stat/bincount.cc \ - cunumeric/convolution/convolve.cc \ - cunumeric/transform/flip.cc \ - cunumeric/arg.cc \ - cunumeric/mapper.cc \ - cunumeric/ndarray.cc \ - cunumeric/operators.cc \ - cunumeric/runtime.cc - -GEN_CPU_SRC += cunumeric/cephes/chbevl.cc \ - cunumeric/cephes/i0.cc - -ifeq ($(strip $(USE_OPENMP)),1) -GEN_CPU_SRC += cunumeric/ternary/where_omp.cc \ - cunumeric/scan/scan_global_omp.cc \ - cunumeric/scan/scan_local_omp.cc \ - cunumeric/binary/binary_op_omp.cc \ - cunumeric/binary/binary_red_omp.cc \ - cunumeric/bits/packbits_omp.cc \ - cunumeric/bits/unpackbits_omp.cc \ - cunumeric/unary/unary_op_omp.cc \ - cunumeric/unary/scalar_unary_red_omp.cc \ - cunumeric/unary/unary_red_omp.cc \ - cunumeric/unary/convert_omp.cc \ - cunumeric/nullary/arange_omp.cc \ - cunumeric/nullary/eye_omp.cc \ - cunumeric/nullary/fill_omp.cc \ - cunumeric/nullary/window_omp.cc \ - cunumeric/index/advanced_indexing_omp.cc\ - cunumeric/index/choose_omp.cc \ - cunumeric/index/repeat_omp.cc \ - cunumeric/index/wrap_omp.cc \ - cunumeric/index/zip_omp.cc \ - cunumeric/matrix/contract_omp.cc \ - cunumeric/matrix/diag_omp.cc \ - cunumeric/matrix/gemm_omp.cc \ - cunumeric/matrix/matmul_omp.cc \ - cunumeric/matrix/matvecmul_omp.cc \ - cunumeric/matrix/dot_omp.cc \ - cunumeric/matrix/potrf_omp.cc \ - cunumeric/matrix/solve_omp.cc \ - cunumeric/matrix/syrk_omp.cc \ - cunumeric/matrix/tile_omp.cc \ - cunumeric/matrix/transpose_omp.cc \ - cunumeric/matrix/trilu_omp.cc \ - cunumeric/matrix/trsm_omp.cc \ - cunumeric/matrix/util_omp.cc \ - cunumeric/random/rand_omp.cc \ - cunumeric/search/argwhere_omp.cc \ - cunumeric/search/nonzero_omp.cc \ - cunumeric/set/unique_omp.cc \ - cunumeric/stat/bincount_omp.cc \ - cunumeric/convolution/convolve_omp.cc \ - cunumeric/transform/flip_omp.cc -endif - -GEN_GPU_SRC += cunumeric/ternary/where.cu \ - cunumeric/scan/scan_global.cu \ - cunumeric/scan/scan_local.cu \ - cunumeric/binary/binary_op.cu \ - cunumeric/binary/binary_red.cu \ - cunumeric/bits/packbits.cu \ - cunumeric/bits/unpackbits.cu \ - cunumeric/unary/scalar_unary_red.cu \ - cunumeric/unary/unary_red.cu \ - cunumeric/unary/unary_op.cu \ - cunumeric/unary/convert.cu \ - cunumeric/nullary/arange.cu \ - cunumeric/nullary/eye.cu \ - cunumeric/nullary/fill.cu \ - cunumeric/nullary/window.cu \ - cunumeric/index/advanced_indexing.cu \ - cunumeric/index/choose.cu \ - cunumeric/index/repeat.cu \ - cunumeric/index/wrap.cu \ - cunumeric/index/zip.cu \ - cunumeric/item/read.cu \ - cunumeric/item/write.cu \ - cunumeric/matrix/contract.cu \ - cunumeric/matrix/diag.cu \ - cunumeric/matrix/gemm.cu \ - cunumeric/matrix/matmul.cu \ - cunumeric/matrix/matvecmul.cu \ - cunumeric/matrix/dot.cu \ - cunumeric/matrix/potrf.cu \ - cunumeric/matrix/solve.cu \ - cunumeric/matrix/syrk.cu \ - cunumeric/matrix/tile.cu \ - cunumeric/matrix/transpose.cu \ - cunumeric/matrix/trilu.cu \ - cunumeric/matrix/trsm.cu \ - cunumeric/random/rand.cu \ - cunumeric/search/argwhere.cu \ - cunumeric/search/nonzero.cu \ - cunumeric/set/unique.cu \ - cunumeric/stat/bincount.cu \ - cunumeric/convolution/convolve.cu \ - cunumeric/fft/fft.cu \ - cunumeric/transform/flip.cu \ - cunumeric/cudalibs.cu \ - cunumeric/cunumeric.cu - -include cunumeric/sort/sort.mk - -ifeq ($(strip $(BUILD_CURAND_TASKS)),1) -include cunumeric/random/random.mk -endif - -GEN_CPU_SRC += cunumeric/cunumeric.cc # This must always be the last file! - # It guarantees we do our registration callback - # only after all task variants are recorded - -INSTALL_PATHS = cunumeric - -INSTALL_HEADERS = cunumeric/cunumeric_c.h \ - cunumeric/ndarray.h \ - cunumeric/ndarray.inl \ - cunumeric/operators.h \ - cunumeric/typedefs.h \ - cunumeric.h diff --git a/src/cunumeric/binary/binary_op.cc b/src/cunumeric/binary/binary_op.cc index dd53fc4939..2ddd0a018e 100644 --- a/src/cunumeric/binary/binary_op.cc +++ b/src/cunumeric/binary/binary_op.cc @@ -58,34 +58,6 @@ struct BinaryOpImplBody { binary_op_template(context); } -std::vector broadcast_shapes(std::vector arrays) -{ -#ifdef DEBUG_CUNUMERIC - assert(!arrays.empty()); -#endif - int32_t dim = 0; - for (auto& array : arrays) { - dim = std::max(dim, array.dim()); - } - - std::vector result(dim, 1); - - for (auto& array : arrays) { - auto& shape = array.shape(); - - auto in_it = shape.rbegin(); - auto out_it = result.rbegin(); - for (; in_it != shape.rend() && out_it != result.rend(); ++in_it, ++out_it) { - if (1 == *out_it) { - *out_it = *in_it; - } else if (*in_it != 1 && *out_it != *in_it) { - throw std::exception(); - } - } - } - return result; -} - namespace // unnamed { static void __attribute__((constructor)) register_tasks(void) { BinaryOpTask::register_variants(); } diff --git a/src/cunumeric/binary/binary_op_util.cc b/src/cunumeric/binary/binary_op_util.cc index ca9f0e0adb..a843fd3357 100644 --- a/src/cunumeric/binary/binary_op_util.cc +++ b/src/cunumeric/binary/binary_op_util.cc @@ -14,13 +14,11 @@ * */ -namespace cunumeric { - -using namespace legate; - #include "cunumeric/binary/binary_op_util.h" -std::vector broadcast_shapes(std::vector arrays) +namespace cunumeric { + +std::vector broadcast_shapes(std::vector arrays) { #ifdef DEBUG_CUNUMERIC assert(!arrays.empty()); @@ -30,7 +28,7 @@ std::vector broadcast_shapes(std::vector arrays) dim = std::max(dim, array.dim()); } - std::vector result(dim, 1); + std::vector result(dim, 1); for (auto& array : arrays) { auto& shape = array.shape(); diff --git a/src/cunumeric/binary/binary_op_util.h b/src/cunumeric/binary/binary_op_util.h index bc506db85a..14d284a27f 100644 --- a/src/cunumeric/binary/binary_op_util.h +++ b/src/cunumeric/binary/binary_op_util.h @@ -911,6 +911,6 @@ struct RHS2OfBinaryOp { template using rhs2_of_binary_op = typename RHS2OfBinaryOp::type; -std::vector broadcast_shapes(std::vector arrays); +std::vector broadcast_shapes(std::vector arrays); } // namespace cunumeric diff --git a/src/cunumeric/ndarray.cc b/src/cunumeric/ndarray.cc index 72789fb655..52b2d800b2 100644 --- a/src/cunumeric/ndarray.cc +++ b/src/cunumeric/ndarray.cc @@ -117,13 +117,13 @@ NDArray::NDArray(legate::LogicalStore&& store) : store_(std::forward& NDArray::shape() const { return store_.extents().data(); } +const std::vector& NDArray::shape() const { return store_.extents().data(); } size_t NDArray::size() const { return store_.volume(); } legate::Type NDArray::type() const { return store_.type(); } -static std::vector compute_strides(const std::vector& shape) +static std::vector compute_strides(const std::vector& shape) { std::vector strides(shape.size()); if (shape.size() > 0) { @@ -685,7 +685,7 @@ void NDArray::convolve(NDArray input, NDArray filter) auto p_output = task.add_output(store_); task.add_scalar_arg(legate::Scalar(shape())); - auto offsets = (filter.store_.extents() + size_t{1}) / size_t{2}; + auto offsets = (filter.store_.extents() + 1) / 2; task.add_constraint(legate::align(p_input, p_output)); task.add_constraint(legate::bloat(p_input, p_halo, offsets, offsets)); @@ -818,7 +818,7 @@ NDArray NDArray::_perform_unary_reduction(int32_t op, axes = normalize_axis_vector(axis.value(), src.dim()); } - std::vector out_shape; + std::vector out_shape; for (auto i = 0; i < src.dim(); ++i) { if (std::find(axes.begin(), axes.end(), i) == axes.end()) { out_shape.push_back(src.shape()[i]); @@ -1034,7 +1034,7 @@ NDArray NDArray::diag_helper(int32_t offset, NDArray a = runtime->create_array(store_.type()); - size_t diag_size; + uint64_t diag_size; if (N == 2) { if (offset >= 0) { transpose_axes.push_back(axes[0]); @@ -1048,7 +1048,8 @@ NDArray NDArray::diag_helper(int32_t offset, if (offset >= a.shape()[dim() - 1]) { throw std::invalid_argument("'offset' for diag or diagonal must be in range"); } - diag_size = std::max((size_t)0, std::min(a.shape().end()[-2], a.shape().end()[-1] - offset)); + diag_size = std::max(static_cast(0), + std::min(a.shape().end()[-2], a.shape().end()[-1] - offset)); } else if (N > 2) { if (offset != 0) { throw std::invalid_argument("offset supported for number of axes == 2"); @@ -1063,12 +1064,12 @@ NDArray NDArray::diag_helper(int32_t offset, throw std::invalid_argument("number of axes should be more than 1"); } - std::vector tr_shape; + std::vector tr_shape; for (size_t i = 0; i < a.dim() - N; ++i) { tr_shape.push_back(a.shape()[i]); } - std::vector out_shape; + std::vector out_shape; if (trace) { if (N != 2) { throw std::invalid_argument("exactly 2 axes should be passed to trace"); @@ -1397,7 +1398,7 @@ NDArray NDArray::trace(int32_t offset, legate::LogicalStore NDArray::get_store() { return store_; } -legate::LogicalStore NDArray::broadcast(const std::vector& shape, +legate::LogicalStore NDArray::broadcast(const std::vector& shape, legate::LogicalStore& store) { int32_t diff = static_cast(shape.size()) - store.dim(); @@ -1411,7 +1412,7 @@ legate::LogicalStore NDArray::broadcast(const std::vector& shape, result = result.promote(dim, shape[dim]); } - std::vector orig_shape = result.extents().data(); + std::vector orig_shape = result.extents().data(); for (uint32_t dim = 0; dim < shape.size(); ++dim) { if (orig_shape[dim] != shape[dim]) { #ifdef DEBUG_CUNUMERIC diff --git a/src/cunumeric/ndarray.h b/src/cunumeric/ndarray.h index c1a69eaebf..9d0d8bb14a 100644 --- a/src/cunumeric/ndarray.h +++ b/src/cunumeric/ndarray.h @@ -41,7 +41,7 @@ class NDArray { public: int32_t dim() const; - const std::vector& shape() const; + const std::vector& shape() const; size_t size() const; legate::Type type() const; @@ -117,7 +117,7 @@ class NDArray { NDArray clip_indices(Scalar const& min, Scalar const& max); private: - legate::LogicalStore broadcast(const std::vector& shape, legate::LogicalStore& store); + legate::LogicalStore broadcast(const std::vector& shape, legate::LogicalStore& store); legate::LogicalStore broadcast(NDArray rhs1, NDArray rhs2); void sort_task(NDArray rhs, bool argsort, bool stable); void sort_swapped(NDArray rhs, bool argsort, int32_t sort_axis, bool stable); diff --git a/src/cunumeric/operators.cc b/src/cunumeric/operators.cc index da08fe7b78..cc892592e4 100644 --- a/src/cunumeric/operators.cc +++ b/src/cunumeric/operators.cc @@ -29,7 +29,7 @@ static legate::Logger log_cunumeric("cunumeric"); legate::Logger& cunumeric_log() { return log_cunumeric; } -NDArray array(std::vector shape, const legate::Type& type) +NDArray array(std::vector shape, const legate::Type& type) { return CuNumericRuntime::get_runtime()->create_array(std::move(shape), type); } @@ -75,7 +75,7 @@ NDArray multiply(NDArray rhs1, NDArray rhs2, std::optional out) NDArray negative(NDArray input) { return unary_op(UnaryOpCode::NEGATIVE, std::move(input)); } -NDArray random(std::vector shape) +NDArray random(std::vector shape) { auto runtime = CuNumericRuntime::get_runtime(); auto out = runtime->create_array(std::move(shape), legate::float64()); @@ -140,7 +140,7 @@ struct generate_int_value_fn { } // namespace -NDArray zeros(std::vector shape, std::optional type) +NDArray zeros(std::vector shape, std::optional type) { auto code = type.has_value() ? type.value().code() : legate::Type::Code::FLOAT64; if (static_cast(code) >= static_cast(legate::Type::Code::FIXED_ARRAY)) { @@ -150,7 +150,7 @@ NDArray zeros(std::vector shape, std::optional type) return full(shape, zero); } -NDArray full(std::vector shape, const Scalar& value) +NDArray full(std::vector shape, const Scalar& value) { auto runtime = CuNumericRuntime::get_runtime(); auto out = runtime->create_array(std::move(shape), value.type()); @@ -233,7 +233,7 @@ NDArray trilu(NDArray rhs, int32_t k, bool lower) { auto dim = rhs.dim(); auto& shape = rhs.shape(); - std::vector out_shape(shape); + std::vector out_shape(shape); if (dim == 0) { throw std::invalid_argument("Dim of input array must be > 0"); } @@ -266,7 +266,7 @@ NDArray dot(NDArray rhs1, NDArray rhs2) } auto runtime = CuNumericRuntime::get_runtime(); - std::vector shape; + std::vector shape; shape.push_back(rhs1_shape[0]); shape.push_back(rhs2_shape[1]); diff --git a/src/cunumeric/operators.h b/src/cunumeric/operators.h index 14863f1852..a3dbbe9fda 100644 --- a/src/cunumeric/operators.h +++ b/src/cunumeric/operators.h @@ -29,7 +29,7 @@ legate::Logger& cunumeric_log(); void initialize(int32_t argc, char** argv); -NDArray array(std::vector shape, const legate::Type& type); +NDArray array(std::vector shape, const legate::Type& type); NDArray abs(NDArray input); @@ -41,11 +41,11 @@ NDArray dot(NDArray rhs1, NDArray rhs2); NDArray negative(NDArray input); -NDArray random(std::vector shape); +NDArray random(std::vector shape); -NDArray zeros(std::vector shape, std::optional type = std::nullopt); +NDArray zeros(std::vector shape, std::optional type = std::nullopt); -NDArray full(std::vector shape, const Scalar& value); +NDArray full(std::vector shape, const Scalar& value); NDArray all(NDArray input, std::optional> axis = std::nullopt, diff --git a/src/cunumeric/operators.inl b/src/cunumeric/operators.inl index 229972d4fe..b952931ecf 100644 --- a/src/cunumeric/operators.inl +++ b/src/cunumeric/operators.inl @@ -24,7 +24,7 @@ NDArray arange(T start, std::optional stop, T step) start = 0; } - size_t N = ceil((stop.value() - start) / step); + uint64_t N = ceil((stop.value() - start) / step); auto s_start = Scalar(start); auto s_stop = Scalar(stop.value()); auto s_step = Scalar(step); diff --git a/src/cunumeric/runtime.cc b/src/cunumeric/runtime.cc index 0239055c3e..14a0822da8 100644 --- a/src/cunumeric/runtime.cc +++ b/src/cunumeric/runtime.cc @@ -40,7 +40,7 @@ NDArray CuNumericRuntime::create_array(const legate::Type& type) return NDArray(std::move(store)); } -NDArray CuNumericRuntime::create_array(std::vector shape, +NDArray CuNumericRuntime::create_array(std::vector shape, const legate::Type& type, bool optimize_scalar) { diff --git a/src/cunumeric/runtime.h b/src/cunumeric/runtime.h index aea34cc2d4..38f631eb4d 100644 --- a/src/cunumeric/runtime.h +++ b/src/cunumeric/runtime.h @@ -33,7 +33,7 @@ class CuNumericRuntime { public: NDArray create_array(const legate::Type& type); - NDArray create_array(std::vector shape, + NDArray create_array(std::vector shape, const legate::Type& type, bool optimize_scalar = true); NDArray create_array(legate::LogicalStore&& store); From 1718d2cc0b90932414c6f3b313e56867faf42184 Mon Sep 17 00:00:00 2001 From: Shijie Chen Date: Mon, 19 Feb 2024 13:18:02 +0800 Subject: [PATCH 155/462] Added tests for cunumeric.zeros (#94) * Added tests for cunumeric.zeros Update the check_array() utility to only do equal comparisons. * remove a redundant test in test_zeros.cc --- tests/cpp/integration/common_utils.h | 6 +- tests/cpp/integration/test_zeros.cc | 108 +++++++++++++++++++++++++++ 2 files changed, 109 insertions(+), 5 deletions(-) create mode 100644 tests/cpp/integration/test_zeros.cc diff --git a/tests/cpp/integration/common_utils.h b/tests/cpp/integration/common_utils.h index 3b315ddc86..d84d0f69b1 100644 --- a/tests/cpp/integration/common_utils.h +++ b/tests/cpp/integration/common_utils.h @@ -91,11 +91,7 @@ void check_array(NDArray a, std::vector values, std::vector shape = { }; auto acc = a.get_read_accessor(); for (size_t i = 0; i < values.size(); ++i) { - switch (legate::type_code_of) { - case legate::Type::Code::FLOAT32: ASSERT_FLOAT_EQ(acc[i], values[i]) << err_msg(i); break; - case legate::Type::Code::FLOAT64: ASSERT_DOUBLE_EQ(acc[i], values[i]) << err_msg(i); break; - default: ASSERT_EQ(acc[i], values[i]) << err_msg(i); break; - } + ASSERT_EQ(acc[i], values[i]) << err_msg(i); } } diff --git a/tests/cpp/integration/test_zeros.cc b/tests/cpp/integration/test_zeros.cc new file mode 100644 index 0000000000..cb880f85ae --- /dev/null +++ b/tests/cpp/integration/test_zeros.cc @@ -0,0 +1,108 @@ +/* Copyright 2024 NVIDIA Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +#include "common_utils.h" + +using namespace cunumeric; +using Code = legate::Type::Code; + +namespace { + +const size_t DIM = 4; +std::vector> shape_list{{0}, + {1}, + {DIM}, + {0, 1}, + {1, 0}, + {1, 1}, + {1, DIM}, + {DIM, 1}, + {DIM, DIM}, + {1, 0, 0}, + {1, 1, 0}, + {1, 0, 1}, + {1, 1, 1}, + {DIM, 1, 1}, + {1, DIM, 1}, + {1, 1, DIM}, + {DIM, DIM, DIM}}; + +std::vector code_list{Code::BOOL, + Code::INT8, + Code::INT16, + Code::INT32, + Code::INT64, + Code::UINT8, + Code::UINT16, + Code::UINT32, + Code::UINT64, + Code::FLOAT32, + Code::FLOAT64, + Code::COMPLEX64, + Code::COMPLEX128}; + +template +void _test(std::vector shape) +{ + auto x = zeros(shape, legate::primitive_type(CODE)); + using VAL = legate::type_of; + std::vector x_gt(x.size()); + check_array(x, x_gt, shape); + // debug_array(x, false); +} + +TEST(Zeros, test_basic_dtype) +{ + for (auto code : code_list) { + for (auto shape : shape_list) { + switch (code) { + case Code::BOOL: _test(shape); break; + case Code::INT8: _test(shape); break; + case Code::INT16: _test(shape); break; + case Code::INT32: _test(shape); break; + case Code::INT64: _test(shape); break; + case Code::UINT8: _test(shape); break; + case Code::UINT16: _test(shape); break; + case Code::UINT32: _test(shape); break; + case Code::UINT64: _test(shape); break; + case Code::FLOAT32: _test(shape); break; + case Code::FLOAT64: _test(shape); break; + case Code::COMPLEX64: _test(shape); break; + case Code::COMPLEX128: _test(shape); break; + default: FAIL() << "Unsupported data types."; break; + } + } + } +} + +TEST(Zeros, test_ndim) +{ + std::vector shape; + for (int32_t ndim = 1; ndim <= LEGATE_MAX_DIM; ++ndim) { + shape.push_back(ndim); + _test(shape); + _test(shape); + _test(shape); + _test(shape); + } +} + +TEST(Zeros, test_invalid_type) +{ + EXPECT_THROW(zeros({2, 2}, legate::primitive_type(Code::FIXED_ARRAY)), std::invalid_argument); +} + +} // namespace From f49bba4d30c9897c1ede7f0500dc43a1a616871f Mon Sep 17 00:00:00 2001 From: Shijie Chen Date: Mon, 19 Feb 2024 15:15:44 +0800 Subject: [PATCH 156/462] Fix 0-dim scalar error in cunumeric.put (#114) * Fix 0-dim scalar error in cunumeric.put * refactor the code for assigning scalar array Co-authored-by: Manolis Papadakis --- src/cunumeric/ndarray.cc | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/cunumeric/ndarray.cc b/src/cunumeric/ndarray.cc index 52b2d800b2..a33985d1da 100644 --- a/src/cunumeric/ndarray.cc +++ b/src/cunumeric/ndarray.cc @@ -1258,6 +1258,9 @@ void NDArray::put(NDArray indices, NDArray values, std::string mode) legate_runtime->issue_scatter(self_tmp.store_, indirect.store_, values.store_); if (need_copy) { + if (store_.has_scalar_storage()) { + self_tmp = runtime->create_array(std::move(self_tmp.store_.project(0, 0))); + } assign(self_tmp); } } @@ -1330,10 +1333,9 @@ NDArray NDArray::_warn_and_convert(legate::Type const& type) NDArray NDArray::wrap_indices(Scalar const& n) { - auto runtime = CuNumericRuntime::get_runtime(); - auto out = runtime->create_array(shape(), type()); - auto divisor_store = runtime->create_scalar_store(n); - auto divisor = runtime->create_array(std::move(divisor_store)); + auto runtime = CuNumericRuntime::get_runtime(); + auto out = runtime->create_array(shape(), type()); + auto divisor = cunumeric::full({}, n); out.binary_op(static_cast(cunumeric::BinaryOpCode::MOD), *this, divisor); return out; } From a8766469c239e7434a58e4acc7492a3fd147ab8b Mon Sep 17 00:00:00 2001 From: Mona Han <129724851+mona-nv@users.noreply.github.com> Date: Thu, 22 Feb 2024 10:40:32 +0800 Subject: [PATCH 157/462] Add tests for nonzero API (#93) * Add tests for nonzero API * Update code based on review comments --- src/cunumeric/ndarray.cc | 4 +- tests/cpp/integration/common_utils.h | 30 +++ tests/cpp/integration/test_nonzero.cc | 273 ++++++++++++++++++++++++++ 3 files changed, 306 insertions(+), 1 deletion(-) create mode 100644 tests/cpp/integration/test_nonzero.cc diff --git a/src/cunumeric/ndarray.cc b/src/cunumeric/ndarray.cc index a33985d1da..0f3317015b 100644 --- a/src/cunumeric/ndarray.cc +++ b/src/cunumeric/ndarray.cc @@ -581,7 +581,9 @@ std::vector NDArray::nonzero() } auto p_rhs = task.add_input(store_); - task.add_constraint(legate::broadcast(p_rhs, legate::from_range(1, ndim))); + if (ndim > 1) { + task.add_constraint(legate::broadcast(p_rhs, legate::from_range(1, ndim))); + } runtime->submit(std::move(task)); diff --git a/tests/cpp/integration/common_utils.h b/tests/cpp/integration/common_utils.h index d84d0f69b1..4f5b81d91d 100644 --- a/tests/cpp/integration/common_utils.h +++ b/tests/cpp/integration/common_utils.h @@ -29,6 +29,7 @@ #include "legate.h" #include "cunumeric.h" #include "cunumeric/runtime.h" +#include "util.inl" namespace cunumeric { @@ -95,6 +96,35 @@ void check_array(NDArray a, std::vector values, std::vector shape = { } } +template +struct PrintArray { + template + void operator()(cunumeric::NDArray array) + { + auto acc = array.get_read_accessor(); + auto& shape = array.shape(); + auto logical_store = array.get_store(); + auto physical_store = logical_store.get_physical_store(); + auto rect = physical_store.shape(); + std::cerr << to_string(acc, shape, rect) << std::endl; + } +}; + +template +void print_array(NDArray array) +{ + if (array.size() == 0) { + std::cerr << "[]" << std::endl; + return; + } + if (array.dim() == 0) { + auto acc = array.get_read_accessor(); + std::cerr << "[" << acc[0] << "]" << std::endl; + return; + } + legate::dim_dispatch(array.dim(), PrintArray{}, array); +} + template void debug_vector(const std::vector& vec) { diff --git a/tests/cpp/integration/test_nonzero.cc b/tests/cpp/integration/test_nonzero.cc new file mode 100644 index 0000000000..5dfd238964 --- /dev/null +++ b/tests/cpp/integration/test_nonzero.cc @@ -0,0 +1,273 @@ +/* Copyright 2024 NVIDIA Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +#include "common_utils.h" + +auto get_nonzero_expect_result() +{ + std::vector>> expect_result = { + {{1, 2, 4, 5, 6, 8, 10}}, + {{0, 0, 0, 0, 0, 0, 0}, {1, 2, 4, 5, 6, 8, 10}}, + {{1, 2, 4, 5, 6, 8, 10}, {0, 0, 0, 0, 0, 0, 0}}, + {{0, 0, 1, 1, 1, 2, 2}, {1, 2, 0, 1, 2, 0, 2}}, + {{1, 2, 4, 5, 6, 8, 10}, {0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0}}, + {{0, 0, 0, 0, 0, 0, 0}, {1, 2, 4, 5, 6, 8, 10}, {0, 0, 0, 0, 0, 0, 0}}, + {{0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0}, {1, 2, 4, 5, 6, 8, 10}}, + {{0, 0, 0, 0, 1, 1, 1}, {0, 0, 1, 1, 0, 0, 1}, {1, 2, 1, 2, 0, 2, 1}}}; + return expect_result; +} + +auto get_nonzero_expect_result_4d() +{ + std::vector>> expect_result = {{{0, 0, 0, 0, 0, 0, 0, 0}, + {0, 0, 0, 0, 0, 0, 0, 0}, + {0, 0, 0, 0, 0, 0, 0, 0}, + {0, 2, 3, 5, 7, 9, 11, 14}}, + {{0, 2, 3, 5, 7, 9, 11, 14}, + {0, 0, 0, 0, 0, 0, 0, 0}, + {0, 0, 0, 0, 0, 0, 0, 0}, + {0, 0, 0, 0, 0, 0, 0, 0}}, + {{0, 0, 0, 0, 0, 1, 1, 1}, + {0, 0, 0, 1, 1, 0, 0, 1}, + {0, 0, 0, 0, 0, 0, 0, 0}, + {0, 2, 3, 1, 3, 1, 3, 2}}}; + return expect_result; +} + +auto get_nonzero_expect_result_5d() +{ + std::vector>> expect_result = {{{0, 0, 0, 0, 0, 0, 0, 0}, + {0, 0, 0, 0, 0, 0, 0, 0}, + {0, 0, 0, 0, 0, 0, 0, 0}, + {0, 2, 3, 5, 7, 9, 11, 14}, + {0, 0, 0, 0, 0, 0, 0, 0}}, + {{0, 0, 0, 0, 0, 0, 0, 0}, + {0, 2, 3, 5, 7, 9, 11, 14}, + {0, 0, 0, 0, 0, 0, 0, 0}, + {0, 0, 0, 0, 0, 0, 0, 0}, + {0, 0, 0, 0, 0, 0, 0, 0}}, + {{0, 0, 0, 0, 0, 0, 0, 0}, + {0, 0, 0, 0, 0, 1, 1, 1}, + {0, 0, 0, 1, 1, 0, 0, 1}, + {0, 0, 0, 0, 0, 0, 0, 0}, + {0, 2, 3, 1, 3, 1, 3, 2}}}; + return expect_result; +} + +auto get_nonzero_expect_result_6d() +{ + std::vector>> expect_result = {{{0, 2, 3, 5, 7, 9, 11, 14}, + {0, 0, 0, 0, 0, 0, 0, 0}, + {0, 0, 0, 0, 0, 0, 0, 0}, + {0, 0, 0, 0, 0, 0, 0, 0}, + {0, 0, 0, 0, 0, 0, 0, 0}, + {0, 0, 0, 0, 0, 0, 0, 0}}, + {{0, 0, 0, 0, 0, 0, 0, 0}, + {0, 0, 0, 0, 0, 0, 0, 0}, + {0, 2, 3, 5, 7, 9, 11, 14}, + {0, 0, 0, 0, 0, 0, 0, 0}, + {0, 0, 0, 0, 0, 0, 0, 0}, + {0, 0, 0, 0, 0, 0, 0, 0}}, + {{0, 0, 0, 0, 0, 0, 0, 0}, + {0, 0, 0, 0, 0, 1, 1, 1}, + {0, 0, 0, 0, 0, 0, 0, 0}, + {0, 0, 0, 1, 1, 0, 0, 1}, + {0, 1, 1, 0, 1, 0, 1, 1}, + {0, 0, 1, 1, 1, 1, 1, 0}}}; + return expect_result; +} + +auto get_nonzero_expect_result_7d() +{ + std::vector>> expect_result = {{{0, 0, 0, 0, 0, 0, 0, 0}, + {0, 2, 3, 5, 7, 9, 11, 14}, + {0, 0, 0, 0, 0, 0, 0, 0}, + {0, 0, 0, 0, 0, 0, 0, 0}, + {0, 0, 0, 0, 0, 0, 0, 0}, + {0, 0, 0, 0, 0, 0, 0, 0}, + {0, 0, 0, 0, 0, 0, 0, 0}}, + {{0, 0, 0, 0, 0, 0, 0, 0}, + {0, 0, 0, 0, 0, 0, 0, 0}, + {0, 0, 0, 0, 0, 1, 1, 1}, + {0, 0, 0, 1, 1, 0, 0, 1}, + {0, 0, 0, 0, 0, 0, 0, 0}, + {0, 2, 3, 1, 3, 1, 3, 2}, + {0, 0, 0, 0, 0, 0, 0, 0}}, + {{0, 0, 0, 0, 0, 1, 1, 1}, + {0, 0, 0, 1, 1, 0, 0, 1}, + {0, 0, 0, 0, 0, 0, 0, 0}, + {0, 0, 0, 0, 0, 0, 0, 0}, + {0, 1, 1, 0, 1, 0, 1, 1}, + {0, 0, 0, 0, 0, 0, 0, 0}, + {0, 0, 1, 1, 1, 1, 1, 0}}}; + return expect_result; +} + +template +void test_nonzero(const std::vector& in_array, + const std::vector>& expect, + const std::vector& shape) +{ + auto array = cunumeric::mk_array(in_array, shape); + auto result_vec = cunumeric::nonzero(array); + size_t result_size = result_vec.size(); + ASSERT_EQ(result_size, expect.size()); + std::vector expect_shape = {}; + if (shape.size() > 0) { + if (result_vec[0].size() == 0) { + expect_shape.push_back(0); + } else if (result_vec[0].size() == 1) { + expect_shape.push_back(1); + } + } + for (size_t i = 0; i < result_size; ++i) { + cunumeric::check_array(result_vec[i], expect[i], expect_shape); + } +} + +template +void nonzero_basic_impl(const std::vector>& test_shapes, + const std::vector& in_array, + const std::vector>>& expect_result) +{ + size_t test_shape_size = test_shapes.size(); + for (size_t i = 0; i < test_shape_size; ++i) { + test_nonzero(in_array, expect_result[i], test_shapes[i]); + } +} + +void nonzero_basic() +{ + std::vector> test_shapes = { + {12}, {1, 12}, {12, 1}, {3, 4}, {12, 1, 1}, {1, 12, 1}, {1, 1, 12}, {2, 2, 3}}; + auto expect_result = get_nonzero_expect_result(); + + // Test int type + std::vector in_array1 = {0, 3, 12, 0, 2, 4, 8, 0, 7, 0, 11, 0}; + nonzero_basic_impl(test_shapes, in_array1, expect_result); + + // Test float type + std::vector in_array2 = {0.0, 3.5, 11.0, 0, 2.2, 6.5, 8, 0.0, 7.9, 0.0, 0.0011, 0}; + nonzero_basic_impl(test_shapes, in_array2, expect_result); + + // Test complex type + std::vector> in_array3 = {complex(0, 0), + complex(2.2, 0), + complex(12, 5), + complex(0), + complex(2, 4), + complex(6, 4), + complex(8, 9), + complex(0, 0), + complex(7.9, 12), + complex(0), + complex(0, 0.001), + complex(0, 0)}; + nonzero_basic_impl>(test_shapes, in_array3, expect_result); +} + +void nonzero_basic_max_dim() +{ + // Only test int type for max dim + std::vector in_array = {14, 0, 3, 12, 0, 13, 0, 4, 0, 8, 0, 7, 0, 0, 1, 0}; + +#if LEGATE_MAX_DIM >= 4 + std::vector> test_shapes_4d = {{1, 1, 1, 16}, {16, 1, 1, 1}, {2, 2, 1, 4}}; + auto expect_result_4d = get_nonzero_expect_result_4d(); + nonzero_basic_impl(test_shapes_4d, in_array, expect_result_4d); +#endif + +#if LEGATE_MAX_DIM >= 5 + std::vector> test_shapes_5d = { + {1, 1, 1, 16, 1}, {1, 16, 1, 1, 1}, {1, 2, 2, 1, 4}}; + auto expect_result_5d = get_nonzero_expect_result_5d(); + nonzero_basic_impl(test_shapes_5d, in_array, expect_result_5d); +#endif + +#if LEGATE_MAX_DIM >= 6 + std::vector> test_shapes_6d = { + {16, 1, 1, 1, 1, 1}, {1, 1, 16, 1, 1, 1}, {1, 2, 1, 2, 2, 2}}; + auto expect_result_6d = get_nonzero_expect_result_6d(); + nonzero_basic_impl(test_shapes_6d, in_array, expect_result_6d); +#endif + +#if LEGATE_MAX_DIM >= 7 + std::vector> test_shapes_7d = { + {1, 16, 1, 1, 1, 1, 1}, {1, 1, 2, 2, 1, 4, 1}, {2, 2, 1, 1, 2, 1, 2}}; + auto expect_result_7d = get_nonzero_expect_result_7d(); + nonzero_basic_impl(test_shapes_7d, in_array, expect_result_7d); +#endif +} + +void nonzero_large_array() +{ + const int32_t count = 10000; + std::vector test_shape = {count}; + std::vector> expect_result = {{0, 9999}}; + + // Test int type for large array + std::vector in_array1(count); + in_array1.assign(count, 0); + in_array1[0] = 1; + in_array1[9999] = 1; + test_nonzero(in_array1, expect_result, test_shape); + + // Test float type for large array + std::vector in_array2(count); + in_array2.assign(count, 0.0); + in_array2[0] = 0.0001; + in_array2[9999] = 0.0001; + test_nonzero(in_array2, expect_result, test_shape); + + // Test complex type for large array + std::vector> in_array3(count); + in_array3.assign(count, complex(0.0)); + in_array3[0] = complex(0.0001, 0.0); + in_array3[9999] = complex(0.0, 0.0001); + test_nonzero>(in_array3, expect_result, test_shape); +} + +void nonzero_empty_array() +{ + std::vector> test_shapes = { + {0}, {0, 1}, {1, 0}, {1, 0, 0}, {1, 1, 0}, {1, 0, 1}}; + + std::vector in_array = {}; + std::vector>> expect_result = { + {{}}, {{}, {}}, {{}, {}}, {{}, {}, {}}, {{}, {}, {}}, {{}, {}, {}}}; + + nonzero_basic_impl(test_shapes, in_array, expect_result); +} + +void single_item_array() +{ + std::vector> test_shapes = {{1}, {1, 1}, {1, 1, 1}}; + + std::vector in_array1 = {1}; + std::vector>> expect_result1 = { + {{0}}, {{0}, {0}}, {{0}, {0}, {0}}}; + nonzero_basic_impl(test_shapes, in_array1, expect_result1); + + std::vector in_array2 = {0}; + std::vector>> expect_result2 = {{{}}, {{}, {}}, {{}, {}, {}}}; + nonzero_basic_impl(test_shapes, in_array2, expect_result2); +} + +// void cpp_test() +TEST(Nonzero, Basic) { nonzero_basic(); } +TEST(Nonzero, BasicMaxDim) { nonzero_basic_max_dim(); } +TEST(Nonzero, LargeArray) { nonzero_large_array(); } +TEST(Nonzero, EmptyArray) { nonzero_empty_array(); } +TEST(Nonzero, SingleItemArray) { single_item_array(); } From d991500937273f15e007eab3d9a34f079b70dd89 Mon Sep 17 00:00:00 2001 From: Bryan Van de Ven Date: Tue, 27 Feb 2024 11:43:54 -0800 Subject: [PATCH 158/462] Refactor numpy module (#126) * preliminary module.py -> _module sub-package * split off creation routines * split off manipulation routines * split off stats routines * split of searching, sorting, counting routines * split off set routines * split off logic and math functions * split off indexing and correct some locations * consistency * remove unnecessary import --- cunumeric/__init__.py | 2 +- cunumeric/_module/__init__.py | 106 + cunumeric/_module/array_basic.py | 81 + cunumeric/_module/array_dimension.py | 401 ++ cunumeric/_module/array_joining.py | 700 ++ cunumeric/_module/array_rearrange.py | 134 + cunumeric/_module/array_shape.py | 119 + cunumeric/_module/array_splitting.py | 247 + cunumeric/_module/array_tiling.py | 224 + cunumeric/_module/array_transpose.py | 133 + cunumeric/_module/creation_data.py | 176 + cunumeric/_module/creation_matrices.py | 191 + cunumeric/_module/creation_ranges.py | 254 + cunumeric/_module/creation_shape.py | 400 ++ cunumeric/_module/indexing.py | 1111 +++ cunumeric/_module/linalg_mvp.py | 924 +++ cunumeric/_module/logic_comparison.py | 197 + cunumeric/_module/logic_truth.py | 140 + cunumeric/_module/math_complex.py | 79 + cunumeric/_module/math_extrema.py | 179 + cunumeric/_module/math_misc.py | 149 + cunumeric/_module/math_sum_prod_diff.py | 940 +++ cunumeric/_module/sets_making.py | 104 + cunumeric/_module/ssc_counting.py | 57 + cunumeric/_module/ssc_searching.py | 339 + cunumeric/_module/ssc_sorting.py | 287 + cunumeric/_module/stats_avgs_vars.py | 236 + cunumeric/_module/stats_histograms.py | 275 + cunumeric/_module/stats_order.py | 700 ++ cunumeric/array.py | 6 +- cunumeric/bits.py | 2 +- cunumeric/deferred.py | 2 +- cunumeric/fft/fft.py | 2 +- cunumeric/linalg/linalg.py | 2 +- cunumeric/logic.py | 2 +- cunumeric/module.py | 8284 ----------------------- cunumeric/window.py | 2 +- 37 files changed, 8894 insertions(+), 8293 deletions(-) create mode 100644 cunumeric/_module/__init__.py create mode 100644 cunumeric/_module/array_basic.py create mode 100644 cunumeric/_module/array_dimension.py create mode 100644 cunumeric/_module/array_joining.py create mode 100644 cunumeric/_module/array_rearrange.py create mode 100644 cunumeric/_module/array_shape.py create mode 100644 cunumeric/_module/array_splitting.py create mode 100644 cunumeric/_module/array_tiling.py create mode 100644 cunumeric/_module/array_transpose.py create mode 100644 cunumeric/_module/creation_data.py create mode 100644 cunumeric/_module/creation_matrices.py create mode 100644 cunumeric/_module/creation_ranges.py create mode 100644 cunumeric/_module/creation_shape.py create mode 100644 cunumeric/_module/indexing.py create mode 100644 cunumeric/_module/linalg_mvp.py create mode 100644 cunumeric/_module/logic_comparison.py create mode 100644 cunumeric/_module/logic_truth.py create mode 100644 cunumeric/_module/math_complex.py create mode 100644 cunumeric/_module/math_extrema.py create mode 100644 cunumeric/_module/math_misc.py create mode 100644 cunumeric/_module/math_sum_prod_diff.py create mode 100644 cunumeric/_module/sets_making.py create mode 100644 cunumeric/_module/ssc_counting.py create mode 100644 cunumeric/_module/ssc_searching.py create mode 100644 cunumeric/_module/ssc_sorting.py create mode 100644 cunumeric/_module/stats_avgs_vars.py create mode 100644 cunumeric/_module/stats_histograms.py create mode 100644 cunumeric/_module/stats_order.py delete mode 100644 cunumeric/module.py diff --git a/cunumeric/__init__.py b/cunumeric/__init__.py index 5e9b079fa6..50d9d74431 100644 --- a/cunumeric/__init__.py +++ b/cunumeric/__init__.py @@ -31,7 +31,7 @@ from . import linalg, random, fft, ma from .array import maybe_convert_to_np_ndarray, ndarray from .bits import packbits, unpackbits -from .module import * +from ._module import * from ._ufunc import * from .logic import * from .window import bartlett, blackman, hamming, hanning, kaiser diff --git a/cunumeric/_module/__init__.py b/cunumeric/_module/__init__.py new file mode 100644 index 0000000000..4909b058b3 --- /dev/null +++ b/cunumeric/_module/__init__.py @@ -0,0 +1,106 @@ +# Copyright 2021-2022 NVIDIA Corporation +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +from __future__ import annotations + +# The CLLR functions for the numpy module are broken up more or less according +# to the docs at https://numpy.org/doc/stable/reference/routines.html +# +# There are some discrepencies since some functions are repeated in multiple +# locations and since indexing routines are listed elsehwere for some reason. +# Sections/sub-modules that are currently missing are noted in comments. + +# --- Array Creation Routines +# https://numpy.org/doc/stable/reference/routines.array-creation.html + +from .creation_shape import * # From shape or value +from .creation_data import * # From existing data +from .creation_ranges import * # Numerical Ranges +from .creation_matrices import * # Building Matrices + +# --- Array manipulation routines +# https://numpy.org/doc/stable/reference/routines.array-manipulation.html +# +# from .array_kind import * # Changing kind of array +# from .array_add_remove import * # Adding and removing elements + +from .array_basic import * # Basic operations +from .array_shape import * # Changing array shape +from .array_transpose import * # Transpose-like operations +from .array_dimension import * # Changing number of dimensions +from .array_joining import * # Joining arrays +from .array_splitting import * # Splitting arrays +from .array_tiling import * # Tiling arrays +from .array_rearrange import * # Rearranging elements + +# --- Indexing routines +# +# These routines in the numpy module are a bit odd, they are documented under +# the array ref: https://numpy.org/doc/stable/reference/arrays.indexing.html + +from .indexing import * + +# --- Linear Algebra +# https://numpy.org/doc/stable/reference/routines.linalg.html + +from .linalg_mvp import * # Matrix and vector products + +# --- Logic functions +# https://numpy.org/doc/stable/reference/routines.logic.html +# +# from .logic_contents import * # Array contents +# from .logic_type import * # Array type testing +# from .logic_ops import * # Logical operations + +from .logic_truth import * # Truth value testing +from .logic_comparison import * # Comparison + +# --- Mathematical functions +# https://numpy.org/doc/stable/reference/routines.math.html +# +# from .math_trig import * # Trigonometric functions +# from .math_hyp import * # Hyperbolic functions +# from .math_round import * # Rounding +# from .math_exp_log import * # Exponents and logarithms +# from .math_other import * # Other special funtions +# from .math_floating import * # Floating point routines +# from .math_arithmetic import * # Arithmetic opertations + +from .math_sum_prod_diff import * # Sums, products, differences +from .math_complex import * # Handling complex numbers +from .math_extrema import * # Extrema finding +from .math_misc import * # Miscellaneous + +# --- Set routines +# https://numpy.org/doc/stable/reference/routines.set.html +# +# from .sets_boolean import * # Boolean operations + +from .sets_making import * # Making proper sets + +# --- Sorting, searching, and counting +# https://numpy.org/doc/stable/reference/routines.sort.html + +from .ssc_sorting import * # Sorting +from .ssc_searching import * # Searching +from .ssc_counting import * # Counting + +# --- Statistics +# https://numpy.org/doc/stable/reference/routines.statistics.html +# +# from .correlating import * # Correlating + +from .stats_order import * # Order statistics +from .stats_avgs_vars import * # Averages and variances +from .stats_histograms import * # Histograms diff --git a/cunumeric/_module/array_basic.py b/cunumeric/_module/array_basic.py new file mode 100644 index 0000000000..a1fed84405 --- /dev/null +++ b/cunumeric/_module/array_basic.py @@ -0,0 +1,81 @@ +# Copyright 2024 NVIDIA Corporation +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +from __future__ import annotations + +from typing import TYPE_CHECKING + +from ..array import add_boilerplate + +if TYPE_CHECKING: + from ..array import ndarray + from ..types import NdShape + + +@add_boilerplate("a") +def ndim(a: ndarray) -> int: + """ + + Return the number of dimensions of an array. + + Parameters + ---------- + a : array_like + Input array. If it is not already an ndarray, a conversion is + attempted. + + Returns + ------- + number_of_dimensions : int + The number of dimensions in `a`. Scalars are zero-dimensional. + + See Also + -------- + ndarray.ndim : equivalent method + shape : dimensions of array + ndarray.shape : dimensions of array + + Availability + -------- + Multiple GPUs, Multiple CPUs + """ + return 0 if a is None else a.ndim + + +@add_boilerplate("a") +def shape(a: ndarray) -> NdShape: + """ + + Return the shape of an array. + + Parameters + ---------- + a : array_like + Input array. + + Returns + ------- + shape : tuple[int, ...] + The elements of the shape tuple give the lengths of the + corresponding array dimensions. + + See Also + -------- + numpy.shape + + Availability + -------- + Multiple GPUs, Multiple CPUs + """ + return a.shape diff --git a/cunumeric/_module/array_dimension.py b/cunumeric/_module/array_dimension.py new file mode 100644 index 0000000000..28447e6aaf --- /dev/null +++ b/cunumeric/_module/array_dimension.py @@ -0,0 +1,401 @@ +# Copyright 2024 NVIDIA Corporation +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +from __future__ import annotations + +from typing import ( + TYPE_CHECKING, + Any, + Iterable, + Optional, + Sequence, + Tuple, + Union, +) + +import numpy as np + +from ..array import add_boilerplate, convert_to_cunumeric_ndarray, ndarray +from .creation_data import array + +if TYPE_CHECKING: + from ..types import NdShape, NdShapeLike + + +def _reshape_recur(ndim: int, arr: ndarray) -> tuple[int, ...]: + if arr.ndim < ndim: + cur_shape: tuple[int, ...] = _reshape_recur(ndim - 1, arr) + if ndim == 2: + cur_shape = (1,) + cur_shape + else: + cur_shape = cur_shape + (1,) + else: + cur_shape = arr.shape + return cur_shape + + +def _atleast_nd( + ndim: int, arys: Sequence[ndarray] +) -> Union[list[ndarray], ndarray]: + inputs = list(convert_to_cunumeric_ndarray(arr) for arr in arys) + # 'reshape' change the shape of arrays + # only when arr.shape != _reshape_recur(ndim,arr) + result = list(arr.reshape(_reshape_recur(ndim, arr)) for arr in inputs) + # if the number of arrays in `arys` is 1, + # the return value is a single array + if len(result) == 1: + return result[0] + return result + + +def atleast_1d(*arys: ndarray) -> Union[list[ndarray], ndarray]: + """ + + Convert inputs to arrays with at least one dimension. + Scalar inputs are converted to 1-dimensional arrays, + whilst higher-dimensional inputs are preserved. + + Parameters + ---------- + *arys : array_like + One or more input arrays. + + Returns + ------- + ret : ndarray + An array, or list of arrays, each with a.ndim >= 1. + Copies are made only if necessary. + + See Also + -------- + numpy.atleast_1d + + Availability + -------- + Multiple GPUs, Multiple CPUs + """ + return _atleast_nd(1, arys) + + +def atleast_2d(*arys: ndarray) -> Union[list[ndarray], ndarray]: + """ + + View inputs as arrays with at least two dimensions. + + Parameters + ---------- + *arys : array_like + One or more array-like sequences. + Non-array inputs are converted to arrays. + Arrays that already have two or more dimensions are preserved. + + Returns + ------- + res, res2, … : ndarray + An array, or list of arrays, each with a.ndim >= 2. + Copies are avoided where possible, and + views with two or more dimensions are returned. + + See Also + -------- + numpy.atleast_2d + + Availability + -------- + Multiple GPUs, Multiple CPUs + """ + return _atleast_nd(2, arys) + + +def atleast_3d(*arys: ndarray) -> Union[list[ndarray], ndarray]: + """ + + View inputs as arrays with at least three dimensions. + + Parameters + ---------- + *arys : array_like + One or more array-like sequences. + Non-array inputs are converted to arrays. + Arrays that already have three or more dimensions are preserved. + + Returns + ------- + res, res2, … : ndarray + An array, or list of arrays, each with a.ndim >= 3. + Copies are avoided where possible, and + views with three or more dimensions are returned. + For example, a 1-D array of shape (N,) becomes + a view of shape (1, N, 1), and a 2-D array of shape (M, N) + becomes a view of shape (M, N, 1). + + See Also + -------- + numpy.atleast_3d + + Availability + -------- + Multiple GPUs, Multiple CPUs + """ + return _atleast_nd(3, arys) + + +@add_boilerplate("a") +def squeeze(a: ndarray, axis: Optional[NdShapeLike] = None) -> ndarray: + """ + + Remove single-dimensional entries from the shape of an array. + + Parameters + ---------- + a : array_like + Input data. + axis : None or int or tuple[int], optional + Selects a subset of the single-dimensional entries in the + shape. If an axis is selected with shape entry greater than + one, an error is raised. + + Returns + ------- + squeezed : ndarray + The input array, but with all or a subset of the + dimensions of length 1 removed. This is always `a` itself + or a view into `a`. + + Raises + ------ + ValueError + If `axis` is not None, and an axis being squeezed is not of length 1 + + See Also + -------- + numpy.squeeze + + Availability + -------- + Multiple GPUs, Multiple CPUs + """ + return a.squeeze(axis=axis) + + +def broadcast_shapes( + *args: Union[NdShapeLike, Sequence[NdShapeLike]] +) -> NdShape: + """ + + Broadcast the input shapes into a single shape. + + Parameters + ---------- + `*args` : tuples of ints, or ints + The shapes to be broadcast against each other. + + Returns + ------- + tuple : Broadcasted shape. + + See Also + -------- + numpy.broadcast_shapes + + Availability + -------- + Multiple GPUs, Multiple CPUs + + """ + # TODO: expected "Union[SupportsIndex, Sequence[SupportsIndex]]" + return np.broadcast_shapes(*args) # type: ignore [arg-type] + + +def _broadcast_to( + arr: ndarray, + shape: NdShapeLike, + subok: bool = False, + broadcasted: bool = False, +) -> ndarray: + # create an array object w/ options passed from 'broadcast' routines + arr = array(arr, copy=False, subok=subok) + # 'broadcast_to' returns a read-only view of the original array + out_shape = broadcast_shapes(arr.shape, shape) + if out_shape != shape: + raise ValueError( + f"cannot broadcast an array of shape {arr.shape} to {shape}" + ) + result = ndarray( + shape=out_shape, + thunk=arr._thunk.broadcast_to(out_shape), + writeable=False, + ) + return result + + +@add_boilerplate("arr") +def broadcast_to( + arr: ndarray, shape: NdShapeLike, subok: bool = False +) -> ndarray: + """ + + Broadcast an array to a new shape. + + Parameters + ---------- + arr : array_like + The array to broadcast. + shape : tuple or int + The shape of the desired array. + A single integer i is interpreted as (i,). + subok : bool, optional + This option is ignored by cuNumeric. + + Returns + ------- + broadcast : array + A readonly view on the original array with the given shape. + It is typically not contiguous. + Furthermore, more than one element of a broadcasted array + may refer to a single memory location. + + See Also + -------- + numpy.broadcast_to + + Availability + -------- + Multiple GPUs, Multiple CPUs + + """ + return _broadcast_to(arr, shape, subok) + + +def _broadcast_arrays( + arrs: list[ndarray], + subok: bool = False, +) -> list[ndarray]: + # create an arry object w/ options passed from 'broadcast' routines + arrays = [array(arr, copy=False, subok=subok) for arr in arrs] + # check if the broadcast can happen in the input list of arrays + shapes = [arr.shape for arr in arrays] + out_shape = broadcast_shapes(*shapes) + # broadcast to the final shape + arrays = [_broadcast_to(arr, out_shape, subok) for arr in arrays] + return arrays + + +def broadcast_arrays( + *args: Sequence[Any], subok: bool = False +) -> list[ndarray]: + """ + + Broadcast any number of arrays against each other. + + Parameters + ---------- + `*args` : array_likes + The arrays to broadcast. + + subok : bool, optional + This option is ignored by cuNumeric + + Returns + ------- + broadcasted : list of arrays + These arrays are views on the original arrays. + They are typically not contiguous. + Furthermore, more than one element of a broadcasted array + may refer to a single memory location. + If you need to write to the arrays, make copies first. + + Availability + -------- + Multiple GPUs, Multiple CPUs + + """ + arrs = [convert_to_cunumeric_ndarray(arr) for arr in args] + return _broadcast_arrays(arrs, subok=subok) + + +class broadcast: + """Produce an object that broadcasts input parameters against one another. + It has shape and nd properties and may be used as an iterator. + + Parameters + ---------- + `*arrays` : array_likes + The arrays to broadcast. + + Returns + ------- + b: broadcast + Broadcast the input parameters against one another, and return an + object that encapsulates the result. Amongst others, it has shape + and nd properties, and may be used as an iterator. + + """ + + def __init__(self, *arrays: Sequence[Any]) -> None: + arrs = [convert_to_cunumeric_ndarray(arr) for arr in arrays] + broadcasted = _broadcast_arrays(arrs) + self._iters = tuple(arr.flat for arr in broadcasted) + self._index = 0 + self._shape = broadcasted[0].shape + self._size = np.prod(self.shape, dtype=int) + + def __iter__(self) -> broadcast: + self._index = 0 + return self + + def __next__(self) -> Any: + if self._index < self.size: + result = tuple(each[self._index] for each in self._iters) + self._index += 1 + return result + + def reset(self) -> None: + """Reset the broadcasted result's iterator(s).""" + self._index = 0 + + @property + def index(self) -> int: + """current index in broadcasted result""" + return self._index + + @property + def iters(self) -> Tuple[Iterable[Any], ...]: + """tuple of iterators along self’s "components." """ + return self._iters + + @property + def numiter(self) -> int: + """Number of iterators possessed by the broadcasted result.""" + return len(self._iters) + + @property + def nd(self) -> int: + """Number of dimensions of broadcasted result.""" + return self.ndim + + @property + def ndim(self) -> int: + """Number of dimensions of broadcasted result.""" + return len(self.shape) + + @property + def shape(self) -> NdShape: + """Shape of broadcasted result.""" + return self._shape + + @property + def size(self) -> int: + """Total size of broadcasted result.""" + return self._size diff --git a/cunumeric/_module/array_joining.py b/cunumeric/_module/array_joining.py new file mode 100644 index 0000000000..3d15c9b8c2 --- /dev/null +++ b/cunumeric/_module/array_joining.py @@ -0,0 +1,700 @@ +# Copyright 2024 NVIDIA Corporation +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +from __future__ import annotations + +from itertools import chain +from typing import TYPE_CHECKING, Any, Optional, Sequence, Union + +import numpy as np +from numpy.core.multiarray import ( # type: ignore [attr-defined] + normalize_axis_index, +) + +from ..array import convert_to_cunumeric_ndarray, ndarray +from .array_dimension import _atleast_nd + +if TYPE_CHECKING: + import numpy.typing as npt + + from .._ufunc.ufunc import CastingKind + from ..types import NdShape + +_builtin_any = any +_builtin_max = max +_builtin_sum = sum + +casting_kinds: tuple[CastingKind, ...] = ( + "no", + "equiv", + "safe", + "same_kind", + "unsafe", +) + + +class ArrayInfo: + def __init__( + self, ndim: int, shape: NdShape, dtype: np.dtype[Any] + ) -> None: + self.ndim = ndim + self.shape = shape + self.dtype = dtype + + +def convert_to_array_form(indices: Sequence[int]) -> str: + return "".join(f"[{coord}]" for coord in indices) + + +def check_list_depth(arr: Any, prefix: NdShape = (0,)) -> int: + if not isinstance(arr, list): + return 0 + elif len(arr) == 0: + raise ValueError( + f"List at arrays{convert_to_array_form(prefix)} cannot be empty" + ) + + depths = list( + check_list_depth(each, prefix + (idx,)) for idx, each in enumerate(arr) + ) + + if len(set(depths)) != 1: # this should be one + # If we're here elements don't have the same depth + first_depth = depths[0] + for idx, other_depth in enumerate(depths[1:]): + if other_depth != first_depth: + raise ValueError( + "List depths are mismatched. First element was at depth " + f"{first_depth}, but there is an element at" + f" depth {other_depth}, " + f"arrays{convert_to_array_form(prefix+(idx+1,))}" + ) + + return depths[0] + 1 + + +def check_shape_with_axis( + inputs: list[ndarray], + func_name: str, + axis: int, +) -> None: + ndim = inputs[0].ndim + shape = inputs[0].shape + + axis = normalize_axis_index(axis, ndim) + if ndim >= 1: + if _builtin_any( + shape[:axis] != inp.shape[:axis] + or shape[axis + 1 :] != inp.shape[axis + 1 :] + for inp in inputs + ): + raise ValueError( + f"All arguments to {func_name} " + "must have the same " + "dimension size in all dimensions " + "except the target axis" + ) + return + + +def check_shape_dtype_without_axis( + inputs: Sequence[ndarray], + func_name: str, + dtype: Optional[npt.DTypeLike] = None, + casting: CastingKind = "same_kind", +) -> tuple[list[ndarray], ArrayInfo]: + if len(inputs) == 0: + raise ValueError("need at least one array to concatenate") + + inputs = list(convert_to_cunumeric_ndarray(inp) for inp in inputs) + ndim = inputs[0].ndim + shape = inputs[0].shape + + if _builtin_any(ndim != inp.ndim for inp in inputs): + raise ValueError( + f"All arguments to {func_name} " + "must have the same number of dimensions" + ) + + # Cast arrays with the passed arguments (dtype, casting) + if dtype is None: + dtype = np.result_type(*[inp.dtype for inp in inputs]) + else: + dtype = np.dtype(dtype) + + converted = list(inp.astype(dtype, casting=casting) for inp in inputs) + return converted, ArrayInfo(ndim, shape, dtype) + + +def _block_collect_slices( + arr: Union[ndarray, Sequence[ndarray]], cur_depth: int, depth: int +) -> tuple[list[Any], list[tuple[slice, ...]], Sequence[ndarray]]: + # collects slices for each array in `arr` + # the outcome will be slices on every dimension of the output array + # for each array in `arr` + if cur_depth < depth: + sublist_results = list( + _block_collect_slices(each, cur_depth + 1, depth) for each in arr + ) + # 'sublist_results' contains a list of 3-way tuples, + # for arrays, out_shape of the sublist, and slices + arrays, outshape_list, slices = zip(*sublist_results) + max_ndim = _builtin_max( + 1 + (depth - cur_depth), *(len(each) for each in outshape_list) + ) + outshape_list = list( + ((1,) * (max_ndim - len(each)) + tuple(each)) + for each in outshape_list + ) + leading_dim = _builtin_sum( + each[-1 + (cur_depth - depth)] for each in outshape_list + ) + # flatten array lists from sublists into a single list + arrays = list(chain(*arrays)) + # prepares the out_shape of the current list + out_shape = list(outshape_list[0]) + out_shape[-1 + cur_depth - depth] = leading_dim + offset = 0 + updated_slices = [] + # update the dimension in each slice for the current axis + for shape, slice_list in zip(outshape_list, slices): + cur_dim = shape[-1 + cur_depth - depth] + updated_slices.append( + list( + (slice(offset, offset + cur_dim),) + each + for each in slice_list + ) + ) + offset += cur_dim + # flatten lists of slices into a single list + slices = list(chain(*updated_slices)) + else: + arrays = list(convert_to_cunumeric_ndarray(inp) for inp in arr) + common_shape = arrays[0].shape + if len(arr) > 1: + arrays, common_info = check_shape_dtype_without_axis( + arrays, block.__name__ + ) + common_shape = common_info.shape + check_shape_with_axis(arrays, block.__name__, axis=-1) + # the initial slices for each arr on arr.shape[-1] + out_shape, slices, arrays = _collect_outshape_slices( + arrays, common_shape, axis=-1 + len(common_shape) + ) + + return arrays, out_shape, slices + + +def _block_slicing(arrays: Sequence[ndarray], depth: int) -> ndarray: + # collects the final slices of input arrays and assign them at once + arrays, out_shape, slices = _block_collect_slices(arrays, 1, depth) + out_array = ndarray(shape=out_shape, inputs=arrays) + + for dest, inp in zip(slices, arrays): + out_array[(Ellipsis,) + tuple(dest)] = inp + + return out_array + + +def _collect_outshape_slices( + inputs: Sequence[ndarray], common_shape: NdShape, axis: int +) -> tuple[list[Any], list[tuple[slice, ...]], Sequence[ndarray]]: + leading_dim = _builtin_sum(arr.shape[axis] for arr in inputs) + out_shape = list(common_shape) + out_shape[axis] = leading_dim + post_idx = (slice(None),) * len(out_shape[axis + 1 :]) + slices = [] + offset = 0 + # collect slices for arrays in `inputs` + inputs = list(inp for inp in inputs if inp.size > 0) + for inp in inputs: + slices.append((slice(offset, offset + inp.shape[axis]),) + post_idx) + offset += inp.shape[axis] + + return out_shape, slices, inputs + + +def _concatenate( + inputs: Sequence[ndarray], + common_info: ArrayInfo, + axis: int = 0, + out: Optional[ndarray] = None, + dtype: Optional[npt.DTypeLike] = None, + casting: CastingKind = "same_kind", +) -> ndarray: + if axis < 0: + axis += len(common_info.shape) + out_shape, slices, inputs = _collect_outshape_slices( + inputs, common_info.shape, axis + ) + + if out is None: + out_array = ndarray( + shape=out_shape, dtype=common_info.dtype, inputs=inputs + ) + else: + out = convert_to_cunumeric_ndarray(out) + if not isinstance(out, ndarray): + raise TypeError("out should be ndarray") + elif list(out.shape) != out_shape: + raise ValueError( + f"out.shape({out.shape}) is not matched " + f"to the result shape of concatenation ({out_shape})" + ) + out_array = out + + for dest, src in zip(slices, inputs): + out_array[(Ellipsis,) + dest] = src + + return out_array + + +def append( + arr: ndarray, values: ndarray, axis: Optional[int] = None +) -> ndarray: + """ + + Append values to the end of an array. + + Parameters + ---------- + arr : array_like + Values are appended to a copy of this array. + values : array_like + These values are appended to a copy of arr. It must be of the correct + shape (the same shape as arr, excluding axis). If axis is not + specified, values can be any shape and will be flattened before use. + axis : int, optional + The axis along which values are appended. If axis is not given, both + `arr` and `values` are flattened before use. + + Returns + ------- + res : ndarray + A copy of arr with values appended to axis. + + See Also + -------- + numpy.append + + Availability + -------- + Multiple GPUs, Multiple CPUs + + """ + # Check to see if we can build a new tuple of cuNumeric arrays + inputs = list(convert_to_cunumeric_ndarray(inp) for inp in [arr, values]) + return concatenate(inputs, axis) + + +def block(arrays: Sequence[Any]) -> ndarray: + """ + Assemble an nd-array from nested lists of blocks. + + Blocks in the innermost lists are concatenated (see concatenate) + along the last dimension (-1), then these are concatenated along + the second-last dimension (-2), and so on until the outermost + list is reached. + + Blocks can be of any dimension, but will not be broadcasted using + the normal rules. Instead, leading axes of size 1 are inserted, + to make block.ndim the same for all blocks. This is primarily useful + for working with scalars, and means that code like np.block([v, 1]) + is valid, where v.ndim == 1. + + When the nested list is two levels deep, this allows block matrices + to be constructed from their components. + + Parameters + ---------- + arrays : nested list of array_like or scalars + If passed a single ndarray or scalar (a nested list of depth 0), + this is returned unmodified (and not copied). + + Elements shapes must match along the appropriate axes (without + broadcasting), but leading 1s will be prepended to the shape as + necessary to make the dimensions match. + + Returns + ------- + block_array : ndarray + The array assembled from the given blocks. + The dimensionality of the output is equal to the greatest of: * the + dimensionality of all the inputs * the depth to which the input list + is nested + + Raises + ------ + ValueError + If list depths are mismatched - for instance, [[a, b], c] is + illegal, and should be spelt [[a, b], [c]] + If lists are empty - for instance, [[a, b], []] + + See Also + -------- + numpy.block + + Availability + -------- + Multiple GPUs, Multiple CPUs + + """ + # arrays should concatenate from innermost subarrays + # the 'arrays' should be balanced tree + # check if the 'arrays' is a balanced tree + depth = check_list_depth(arrays) + + result = _block_slicing(arrays, depth) + return result + + +def concatenate( + inputs: Sequence[ndarray], + axis: Union[int, None] = 0, + out: Optional[ndarray] = None, + dtype: Optional[npt.DTypeLike] = None, + casting: CastingKind = "same_kind", +) -> ndarray: + """ + + concatenate((a1, a2, ...), axis=0, out=None, dtype=None, + casting="same_kind") + + Join a sequence of arrays along an existing axis. + + Parameters + ---------- + a1, a2, ... : Sequence[array_like] + The arrays must have the same shape, except in the dimension + corresponding to `axis` (the first, by default). + axis : int, optional + The axis along which the arrays will be joined. If axis is None, + arrays are flattened before use. Default is 0. + out : ndarray, optional + If provided, the destination to place the result. The shape must be + correct, matching that of what concatenate would have returned if no + out argument were specified. + dtype : str or data-type + If provided, the destination array will have this dtype. Cannot be + provided together with `out`. + casting : ``{'no', 'equiv', 'safe', 'same_kind', 'unsafe'}``, optional + Controls what kind of data casting may occur. Defaults to 'same_kind'. + + Returns + ------- + res : ndarray + The concatenated array. + + See Also + -------- + numpy.concatenate + + Availability + -------- + Multiple GPUs, Multiple CPUs + """ + if dtype is not None and out is not None: + raise TypeError( + "concatenate() only takes `out` or `dtype` as an argument," + "but both were provided." + ) + + if casting not in casting_kinds: + raise ValueError( + "casting must be one of 'no', 'equiv', " + "'safe', 'same_kind', or 'unsafe'" + ) + + # flatten arrays if axis == None and concatenate arrays on the first axis + if axis is None: + # Reshape arrays in the `array_list` to handle scalars + reshaped = _atleast_nd(1, inputs) + if not isinstance(reshaped, list): + reshaped = [reshaped] + inputs = list(inp.ravel() for inp in reshaped) + axis = 0 + + # Check to see if we can build a new tuple of cuNumeric arrays + cunumeric_inputs, common_info = check_shape_dtype_without_axis( + inputs, concatenate.__name__, dtype, casting + ) + check_shape_with_axis(cunumeric_inputs, concatenate.__name__, axis) + + return _concatenate( + cunumeric_inputs, + common_info, + axis, + out, + dtype, + casting, + ) + + +def stack( + arrays: Sequence[ndarray], axis: int = 0, out: Optional[ndarray] = None +) -> ndarray: + """ + + Join a sequence of arrays along a new axis. + + The ``axis`` parameter specifies the index of the new axis in the + dimensions of the result. For example, if ``axis=0`` it will be the first + dimension and if ``axis=-1`` it will be the last dimension. + + Parameters + ---------- + arrays : Sequence[array_like] + Each array must have the same shape. + + axis : int, optional + The axis in the result array along which the input arrays are stacked. + + out : ndarray, optional + If provided, the destination to place the result. The shape must be + correct, matching that of what stack would have returned if no + out argument were specified. + + Returns + ------- + stacked : ndarray + The stacked array has one more dimension than the input arrays. + + See Also + -------- + numpy.stack + + Availability + -------- + Multiple GPUs, Multiple CPUs + """ + if type(axis) is not int: + raise TypeError("The target axis should be an integer") + + arrays, common_info = check_shape_dtype_without_axis( + arrays, stack.__name__ + ) + shapes = {inp.shape for inp in arrays} + if len(shapes) != 1: + raise ValueError("all input arrays must have the same shape for stack") + + axis = normalize_axis_index(axis, common_info.ndim + 1) + shape = common_info.shape[:axis] + (1,) + common_info.shape[axis:] + arrays = [arr.reshape(shape) for arr in arrays] + common_info.shape = tuple(shape) + return _concatenate(arrays, common_info, axis, out=out) + + +def vstack(tup: Sequence[ndarray]) -> ndarray: + """ + + Stack arrays in sequence vertically (row wise). + + This is equivalent to concatenation along the first axis after 1-D arrays + of shape `(N,)` have been reshaped to `(1,N)`. Rebuilds arrays divided by + `vsplit`. + + This function makes most sense for arrays with up to 3 dimensions. For + instance, for pixel-data with a height (first axis), width (second axis), + and r/g/b channels (third axis). The functions `concatenate`, `stack` and + `block` provide more general stacking and concatenation operations. + + Parameters + ---------- + tup : Sequence[ndarray] + The arrays must have the same shape along all but the first axis. + 1-D arrays must have the same length. + + Returns + ------- + stacked : ndarray + The array formed by stacking the given arrays, will be at least 2-D. + + See Also + -------- + numpy.vstack + + Availability + -------- + Multiple GPUs, Multiple CPUs + """ + # Reshape arrays in the `array_list` if needed before concatenation + reshaped = _atleast_nd(2, tup) + if not isinstance(reshaped, list): + reshaped = [reshaped] + tup, common_info = check_shape_dtype_without_axis( + reshaped, vstack.__name__ + ) + check_shape_with_axis(tup, vstack.__name__, 0) + return _concatenate( + tup, + common_info, + axis=0, + dtype=common_info.dtype, + ) + + +def hstack(tup: Sequence[ndarray]) -> ndarray: + """ + + Stack arrays in sequence horizontally (column wise). + + This is equivalent to concatenation along the second axis, except for 1-D + arrays where it concatenates along the first axis. Rebuilds arrays divided + by `hsplit`. + + This function makes most sense for arrays with up to 3 dimensions. For + instance, for pixel-data with a height (first axis), width (second axis), + and r/g/b channels (third axis). The functions `concatenate`, `stack` and + `block` provide more general stacking and concatenation operations. + + Parameters + ---------- + tup : Sequence[ndarray] + The arrays must have the same shape along all but the second axis, + except 1-D arrays which can be any length. + + Returns + ------- + stacked : ndarray + The array formed by stacking the given arrays. + + See Also + -------- + numpy.hstack + + Availability + -------- + Multiple GPUs, Multiple CPUs + """ + # Reshape arrays in the `array_list` to handle scalars + reshaped = _atleast_nd(1, tup) + if not isinstance(reshaped, list): + reshaped = [reshaped] + + tup, common_info = check_shape_dtype_without_axis( + reshaped, hstack.__name__ + ) + check_shape_with_axis( + tup, hstack.__name__, axis=(0 if common_info.ndim == 1 else 1) + ) + # When ndim == 1, hstack concatenates arrays along the first axis + return _concatenate( + tup, + common_info, + axis=(0 if common_info.ndim == 1 else 1), + dtype=common_info.dtype, + ) + + +def dstack(tup: Sequence[ndarray]) -> ndarray: + """ + + Stack arrays in sequence depth wise (along third axis). + + This is equivalent to concatenation along the third axis after 2-D arrays + of shape `(M,N)` have been reshaped to `(M,N,1)` and 1-D arrays of shape + `(N,)` have been reshaped to `(1,N,1)`. Rebuilds arrays divided by + `dsplit`. + + This function makes most sense for arrays with up to 3 dimensions. For + instance, for pixel-data with a height (first axis), width (second axis), + and r/g/b channels (third axis). The functions `concatenate`, `stack` and + `block` provide more general stacking and concatenation operations. + + Parameters + ---------- + tup : Sequence[ndarray] + The arrays must have the same shape along all but the third axis. + 1-D or 2-D arrays must have the same shape. + + Returns + ------- + stacked : ndarray + The array formed by stacking the given arrays, will be at least 3-D. + + See Also + -------- + numpy.dstack + + Availability + -------- + Multiple GPUs, Multiple CPUs + """ + # Reshape arrays to (1,N,1) for ndim ==1 or (M,N,1) for ndim == 2: + reshaped = _atleast_nd(3, tup) + if not isinstance(reshaped, list): + reshaped = [reshaped] + tup, common_info = check_shape_dtype_without_axis( + reshaped, dstack.__name__ + ) + check_shape_with_axis(tup, dstack.__name__, 2) + return _concatenate( + tup, + common_info, + axis=2, + dtype=common_info.dtype, + ) + + +def column_stack(tup: Sequence[ndarray]) -> ndarray: + """ + + Stack 1-D arrays as columns into a 2-D array. + + Take a sequence of 1-D arrays and stack them as columns + to make a single 2-D array. 2-D arrays are stacked as-is, + just like with `hstack`. 1-D arrays are turned into 2-D columns + first. + + Parameters + ---------- + tup : Sequence[ndarray] + 1-D or 2-D arrays to stack. All of them must have the same + first dimension. + + Returns + ------- + stacked : ndarray + The 2-D array formed by stacking the given arrays. + + See Also + -------- + numpy.column_stack + + Availability + -------- + Multiple GPUs, Multiple CPUs + """ + # Reshape arrays in the `array_list` to handle scalars + reshaped = _atleast_nd(1, tup) + if not isinstance(reshaped, list): + reshaped = [reshaped] + + tup, common_info = check_shape_dtype_without_axis( + reshaped, column_stack.__name__ + ) + + if common_info.ndim == 1: + tup = list(inp.reshape((inp.shape[0], 1)) for inp in tup) + common_info.shape = tup[0].shape + check_shape_with_axis(tup, column_stack.__name__, 1) + return _concatenate( + tup, + common_info, + axis=1, + dtype=common_info.dtype, + ) + + +row_stack = vstack diff --git a/cunumeric/_module/array_rearrange.py b/cunumeric/_module/array_rearrange.py new file mode 100644 index 0000000000..f0e43be4c1 --- /dev/null +++ b/cunumeric/_module/array_rearrange.py @@ -0,0 +1,134 @@ +# Copyright 2024 NVIDIA Corporation +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +from __future__ import annotations + +from typing import TYPE_CHECKING, Optional + +from ..array import add_boilerplate + +if TYPE_CHECKING: + from ..array import ndarray + from ..types import NdShapeLike + + +@add_boilerplate("m") +def flip(m: ndarray, axis: Optional[NdShapeLike] = None) -> ndarray: + """ + Reverse the order of elements in an array along the given axis. + + The shape of the array is preserved, but the elements are reordered. + + Parameters + ---------- + m : array_like + Input array. + axis : None or int or tuple[int], optional + Axis or axes along which to flip over. The default, axis=None, will + flip over all of the axes of the input array. If axis is negative it + counts from the last to the first axis. + + If axis is a tuple of ints, flipping is performed on all of the axes + specified in the tuple. + + Returns + ------- + out : array_like + A new array that is constructed from `m` with the entries of axis + reversed. + + See Also + -------- + numpy.flip + + Availability + -------- + Single GPU, Single CPU + + Notes + ----- + cuNumeric implementation doesn't return a view, it returns a new array + """ + return m.flip(axis=axis) + + +@add_boilerplate("m") +def flipud(m: ndarray) -> ndarray: + """ + Reverse the order of elements along axis 0 (up/down). + + For a 2-D array, this flips the entries in each column in the up/down + direction. Rows are preserved, but appear in a different order than before. + + Parameters + ---------- + m : array_like + Input array. + + Returns + ------- + out : array_like + A new array that is constructed from `m` with rows reversed. + + See Also + -------- + numpy.flipud + + Availability + -------- + Single GPU, Single CPU + + Notes + ----- + cuNumeric implementation doesn't return a view, it returns a new array + """ + if m.ndim < 1: + raise ValueError("Input must be >= 1-d.") + return flip(m, axis=0) + + +@add_boilerplate("m") +def fliplr(m: ndarray) -> ndarray: + """ + Reverse the order of elements along axis 1 (left/right). + + For a 2-D array, this flips the entries in each row in the left/right + direction. Columns are preserved, but appear in a different order than + before. + + Parameters + ---------- + m : array_like + Input array, must be at least 2-D. + + Returns + ------- + f : ndarray + A new array that is constructed from `m` with the columns reversed. + + See Also + -------- + numpy.fliplr + + Availability + -------- + Single GPU, Single CPU + + Notes + ----- + cuNumeric implementation doesn't return a view, it returns a new array + """ + if m.ndim < 2: + raise ValueError("Input must be >= 2-d.") + return flip(m, axis=1) diff --git a/cunumeric/_module/array_shape.py b/cunumeric/_module/array_shape.py new file mode 100644 index 0000000000..105bc9f619 --- /dev/null +++ b/cunumeric/_module/array_shape.py @@ -0,0 +1,119 @@ +# Copyright 2024 NVIDIA Corporation +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +from __future__ import annotations + +from typing import TYPE_CHECKING + +from ..array import add_boilerplate + +if TYPE_CHECKING: + from ..array import ndarray + from ..types import NdShapeLike, OrderType + + +@add_boilerplate("a") +def ravel(a: ndarray, order: OrderType = "C") -> ndarray: + """ + Return a contiguous flattened array. + + A 1-D array, containing the elements of the input, is returned. A copy is + made only if needed. + + Parameters + ---------- + a : array_like + Input array. The elements in `a` are read in the order specified by + `order`, and packed as a 1-D array. + order : ``{'C','F', 'A', 'K'}``, optional + The elements of `a` are read using this index order. 'C' means + to index the elements in row-major, C-style order, + with the last axis index changing fastest, back to the first + axis index changing slowest. 'F' means to index the elements + in column-major, Fortran-style order, with the + first index changing fastest, and the last index changing + slowest. Note that the 'C' and 'F' options take no account of + the memory layout of the underlying array, and only refer to + the order of axis indexing. 'A' means to read the elements in + Fortran-like index order if `a` is Fortran *contiguous* in + memory, C-like order otherwise. 'K' means to read the + elements in the order they occur in memory, except for + reversing the data when strides are negative. By default, 'C' + index order is used. + + Returns + ------- + y : array_like + y is an array of the same subtype as `a`, with shape ``(a.size,)``. + Note that matrices are special cased for backward compatibility, if `a` + is a matrix, then y is a 1-D ndarray. + + See Also + -------- + numpy.ravel + + Availability + -------- + Multiple GPUs, Multiple CPUs + """ + return a.ravel(order=order) + + +@add_boilerplate("a") +def reshape( + a: ndarray, newshape: NdShapeLike, order: OrderType = "C" +) -> ndarray: + """ + + Gives a new shape to an array without changing its data. + + Parameters + ---------- + a : array_like + Array to be reshaped. + newshape : int or tuple[int] + The new shape should be compatible with the original shape. If + an integer, then the result will be a 1-D array of that length. + One shape dimension can be -1. In this case, the value is + inferred from the length of the array and remaining dimensions. + order : ``{'C', 'F', 'A'}``, optional + Read the elements of `a` using this index order, and place the + elements into the reshaped array using this index order. 'C' + means to read / write the elements using C-like index order, + with the last axis index changing fastest, back to the first + axis index changing slowest. 'F' means to read / write the + elements using Fortran-like index order, with the first index + changing fastest, and the last index changing slowest. Note that + the 'C' and 'F' options take no account of the memory layout of + the underlying array, and only refer to the order of indexing. + 'A' means to read / write the elements in Fortran-like index + order if `a` is Fortran *contiguous* in memory, C-like order + otherwise. + + Returns + ------- + reshaped_array : ndarray + This will be a new view object if possible; otherwise, it will + be a copy. Note there is no guarantee of the *memory layout* (C- or + Fortran- contiguous) of the returned array. + + See Also + -------- + numpy.reshape + + Availability + -------- + Multiple GPUs, Multiple CPUs + """ + return a.reshape(newshape, order=order) diff --git a/cunumeric/_module/array_splitting.py b/cunumeric/_module/array_splitting.py new file mode 100644 index 0000000000..0fb7f34dc6 --- /dev/null +++ b/cunumeric/_module/array_splitting.py @@ -0,0 +1,247 @@ +# Copyright 2024 NVIDIA Corporation +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +from __future__ import annotations + +from typing import TYPE_CHECKING, Any, Union + +import numpy as np + +from ..array import convert_to_cunumeric_ndarray, ndarray + +if TYPE_CHECKING: + import numpy.typing as npt + + +def split( + a: ndarray, indices: Union[int, ndarray], axis: int = 0 +) -> list[ndarray]: + """ + + Split an array into multiple sub-arrays as views into `ary`. + + Parameters + ---------- + ary : ndarray + Array to be divided into sub-arrays. + indices_or_sections : int or ndarray + If `indices_or_sections` is an integer, N, the array will be divided + into N equal arrays along `axis`. If such a split is not possible, + an error is raised. + + If `indices_or_sections` is a 1-D array of sorted integers, the entries + indicate where along `axis` the array is split. For example, + ``[2, 3]`` would, for ``axis=0``, result in + + - ary[:2] + - ary[2:3] + - ary[3:] + + If an index exceeds the dimension of the array along `axis`, + an empty sub-array is returned correspondingly. + axis : int, optional + The axis along which to split, default is 0. + + Returns + ------- + sub-arrays : list[ndarray] + A list of sub-arrays as views into `ary`. + + Raises + ------ + ValueError + If `indices_or_sections` is given as an integer, but + a split does not result in equal division. + + See Also + -------- + numpy.split + + Availability + -------- + Multiple GPUs, Multiple CPUs + """ + return array_split(a, indices, axis, equal=True) + + +def array_split( + a: ndarray, + indices: Union[int, tuple[int], ndarray, npt.NDArray[Any]], + axis: int = 0, + equal: bool = False, +) -> list[ndarray]: + """ + + Split an array into multiple sub-arrays. + + Please refer to the ``split`` documentation. The only difference + between these functions is that ``array_split`` allows + `indices_or_sections` to be an integer that does *not* equally + divide the axis. For an array of length l that should be split + into n sections, it returns l % n sub-arrays of size l//n + 1 + and the rest of size l//n. + + See Also + -------- + numpy.array_split + + Availability + -------- + Multiple GPUs, Multiple CPUs + """ + array = convert_to_cunumeric_ndarray(a) + split_pts = [] + if axis >= array.ndim: + raise ValueError( + f"array({array.shape}) has less dimensions than axis({axis})" + ) + + if isinstance(indices, int): + if indices <= 0: + raise ValueError("number sections must be larger than 0.") + res = array.shape[axis] % indices + if equal and res != 0: + raise ValueError("array split does not result in an equal divison") + + len_subarr = array.shape[axis] // indices + end_idx = array.shape[axis] + first_idx = len_subarr + + # the requested # of subarray is larger than the size of array + # -> size of 1 subarrays + empty subarrays + if len_subarr == 0: + len_subarr = 1 + first_idx = len_subarr + end_idx = indices + else: + if res != 0: + # The first 'res' groups have len_subarr+1 elements + split_pts = list( + range( + len_subarr + 1, (len_subarr + 1) * res, len_subarr + 1 + ) + ) + first_idx = (len_subarr + 1) * res + split_pts.extend(range(first_idx, end_idx + 1, len_subarr)) + + elif isinstance(indices, (list, tuple)) or ( + isinstance(indices, (ndarray, np.ndarray)) and indices.dtype == int + ): + split_pts = list(indices) + # adding the size of the target dimension. + # This helps create dummy or last subarray correctly + split_pts.append(array.shape[axis]) + + else: + raise ValueError("Integer or array for split should be provided") + + result = [] + start_idx = 0 + end_idx = 0 + out_shape = [] + in_shape: list[Union[int, slice]] = [] + + for i in range(array.ndim): + if i != axis: + in_shape.append(slice(array.shape[i])) + out_shape.append(array.shape[i]) + else: + in_shape.append(1) + out_shape.append(1) + + for pts in split_pts: + if type(pts) is not int: + raise ValueError( + "Split points in the passed `indices` should be integer" + ) + end_idx = pts + # For a split point, which is larger than the dimension for splitting, + # The last non-empty subarray should be copied from + # array[last_elem:array.shape[axis]] + if pts > array.shape[axis]: + end_idx = array.shape[axis] + out_shape[axis] = (end_idx - start_idx) + 1 + in_shape[axis] = slice(start_idx, end_idx) + new_subarray = None + if start_idx < array.shape[axis] and start_idx < end_idx: + new_subarray = array[tuple(in_shape)].view() + else: + out_shape[axis] = 0 + new_subarray = ndarray( + tuple(out_shape), dtype=array.dtype, writeable=array._writeable + ) + result.append(new_subarray) + start_idx = pts + + return result + + +def dsplit(a: ndarray, indices: Union[int, ndarray]) -> list[ndarray]: + """ + + Split array into multiple sub-arrays along the 3rd axis (depth). + + Please refer to the `split` documentation. `dsplit` is equivalent + to `split` with ``axis=2``, the array is always split along the third + axis provided the array dimension is greater than or equal to 3. + + See Also + -------- + numpy.dsplit + + Availability + -------- + Multiple GPUs, Multiple CPUs + """ + return split(a, indices, axis=2) + + +def hsplit(a: ndarray, indices: Union[int, ndarray]) -> list[ndarray]: + """ + + Split an array into multiple sub-arrays horizontally (column-wise). + + Please refer to the `split` documentation. `hsplit` is equivalent + to `split` with ``axis=1``, the array is always split along the second + axis regardless of the array dimension. + + See Also + -------- + numpy.hsplit + + Availability + -------- + Multiple GPUs, Multiple CPUs + """ + return split(a, indices, axis=1) + + +def vsplit(a: ndarray, indices: Union[int, ndarray]) -> list[ndarray]: + """ + + Split an array into multiple sub-arrays vertically (row-wise). + + Please refer to the ``split`` documentation. ``vsplit`` is equivalent + to ``split`` with `axis=0` (default), the array is always split along the + first axis regardless of the array dimension. + + See Also + -------- + numpy.vsplit + + Availability + -------- + Multiple GPUs, Multiple CPUs + """ + return split(a, indices, axis=0) diff --git a/cunumeric/_module/array_tiling.py b/cunumeric/_module/array_tiling.py new file mode 100644 index 0000000000..7a32d7edab --- /dev/null +++ b/cunumeric/_module/array_tiling.py @@ -0,0 +1,224 @@ +# Copyright 2024 NVIDIA Corporation +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +from __future__ import annotations + +from typing import TYPE_CHECKING, Any, Optional, Sequence, Union, cast + +import numpy as np +from numpy.core.multiarray import ( # type: ignore [attr-defined] + normalize_axis_index, +) + +from ..array import add_boilerplate, convert_to_cunumeric_ndarray, ndarray +from ..runtime import runtime +from .creation_shape import full + +if TYPE_CHECKING: + import numpy.typing as npt + + from ..types import NdShape + + +_builtin_max = max + + +@add_boilerplate("A") +def tile( + A: ndarray, reps: Union[int, Sequence[int], npt.NDArray[np.int_]] +) -> ndarray: + """ + Construct an array by repeating A the number of times given by reps. + + If `reps` has length ``d``, the result will have dimension of ``max(d, + A.ndim)``. + + If ``A.ndim < d``, `A` is promoted to be d-dimensional by prepending new + axes. So a shape (3,) array is promoted to (1, 3) for 2-D replication, + or shape (1, 1, 3) for 3-D replication. If this is not the desired + behavior, promote `A` to d-dimensions manually before calling this + function. + + If ``A.ndim > d``, `reps` is promoted to `A`.ndim by pre-pending 1's to it. + Thus for an `A` of shape (2, 3, 4, 5), a `reps` of (2, 2) is treated as + (1, 1, 2, 2). + + Parameters + ---------- + A : array_like + The input array. + reps : 1d array_like + The number of repetitions of `A` along each axis. + + Returns + ------- + c : ndarray + The tiled output array. + + See Also + -------- + numpy.tile + + Availability + -------- + Multiple GPUs, Multiple CPUs + """ + computed_reps: tuple[int, ...] + if isinstance(reps, int): + computed_reps = (reps,) + else: + if np.ndim(reps) > 1: + raise TypeError("`reps` must be a 1d sequence") + computed_reps = tuple(reps) + # Figure out the shape of the destination array + out_dims = _builtin_max(A.ndim, len(computed_reps)) + # Prepend ones until the dimensions match + while len(computed_reps) < out_dims: + computed_reps = (1,) + computed_reps + out_shape: NdShape = () + # Prepend dimensions if necessary + for dim in range(out_dims - A.ndim): + out_shape += (computed_reps[dim],) + offset = len(out_shape) + for dim in range(A.ndim): + out_shape += (A.shape[dim] * computed_reps[offset + dim],) + assert len(out_shape) == out_dims + result = ndarray(out_shape, dtype=A.dtype, inputs=(A,)) + result._thunk.tile(A._thunk, computed_reps) + return result + + +def repeat(a: ndarray, repeats: Any, axis: Optional[int] = None) -> ndarray: + """ + Repeat elements of an array. + + Parameters + ---------- + a : array_like + Input array. + repeats : int or ndarray[int] + The number of repetitions for each element. repeats is + broadcasted to fit the shape of the given axis. + axis : int, optional + The axis along which to repeat values. By default, use the + flattened input array, and return a flat output array. + + Returns + ------- + repeated_array : ndarray + Output array which has the same shape as a, except along the + given axis. + + Notes + ----- + Currently, repeat operations supports only 1D arrays + + See Also + -------- + numpy.repeat + + Availability + -------- + Multiple GPUs, Multiple CPUs + """ + + if repeats is None: + raise TypeError( + "int() argument must be a string, a bytes-like object or a number," + " not 'NoneType'" + ) + + if np.ndim(repeats) > 1: + raise ValueError("`repeats` should be scalar or 1D array") + + # axes should be integer type + if axis is not None and not isinstance(axis, int): + raise TypeError("Axis should be of integer type") + + # when array is a scalar + if np.ndim(a) == 0: + if axis is not None and axis != 0 and axis != -1: + raise np.AxisError( + f"axis {axis} is out of bounds for array of dimension 0" + ) + if np.ndim(repeats) == 0: + if not isinstance(repeats, int): + runtime.warn( + "converting repeats to an integer type", + category=UserWarning, + ) + repeats = np.int64(repeats) + return full((repeats,), cast(Union[int, float], a)) + elif np.ndim(repeats) == 1 and len(repeats) == 1: + if not isinstance(repeats, int): + runtime.warn( + "converting repeats to an integer type", + category=UserWarning, + ) + repeats = np.int64(repeats) + return full((repeats[0],), cast(Union[int, float], a)) + else: + raise ValueError( + "`repeat` with a scalar parameter `a` is only " + "implemented for scalar values of the parameter `repeats`." + ) + + # array is an array + array = convert_to_cunumeric_ndarray(a) + if np.ndim(repeats) == 1: + repeats = convert_to_cunumeric_ndarray(repeats) + + # if no axes specified, flatten array + if axis is None: + array = array.ravel() + axis = 0 + + axis_int: int = normalize_axis_index(axis, array.ndim) + + # If repeats is on a zero sized axis_int, then return the array. + if array.shape[axis_int] == 0: + return array.copy() + + if np.ndim(repeats) == 1: + if repeats.shape[0] == 1 and repeats.shape[0] != array.shape[axis_int]: + repeats = repeats[0] + + # repeats is a scalar. + if np.ndim(repeats) == 0: + # repeats is 0 + if repeats == 0: + empty_shape = list(array.shape) + empty_shape[axis_int] = 0 + return ndarray(shape=tuple(empty_shape), dtype=array.dtype) + # repeats should be integer type + if not isinstance(repeats, int): + runtime.warn( + "converting repeats to an integer type", + category=UserWarning, + ) + result = array._thunk.repeat( + repeats=np.int64(repeats), + axis=axis_int, + scalar_repeats=True, + ) + # repeats is an array + else: + # repeats should be integer type + repeats = repeats._warn_and_convert(np.int64) + if repeats.shape[0] != array.shape[axis_int]: + raise ValueError("incorrect shape of repeats array") + result = array._thunk.repeat( + repeats=repeats._thunk, axis=axis_int, scalar_repeats=False + ) + return ndarray(shape=result.shape, thunk=result) diff --git a/cunumeric/_module/array_transpose.py b/cunumeric/_module/array_transpose.py new file mode 100644 index 0000000000..0d386a8886 --- /dev/null +++ b/cunumeric/_module/array_transpose.py @@ -0,0 +1,133 @@ +# Copyright 2024 NVIDIA Corporation +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +from __future__ import annotations + +from typing import TYPE_CHECKING, Optional, Sequence + +from numpy.core.numeric import ( # type: ignore [attr-defined] + normalize_axis_tuple, +) + +from ..array import add_boilerplate + +if TYPE_CHECKING: + from ..array import ndarray + + +@add_boilerplate("a") +def swapaxes(a: ndarray, axis1: int, axis2: int) -> ndarray: + """ + + Interchange two axes of an array. + + Parameters + ---------- + a : array_like + Input array. + axis1 : int + First axis. + axis2 : int + Second axis. + + Returns + ------- + a_swapped : ndarray + If `a` is an ndarray, then a view of `a` is returned; otherwise a new + array is created. + + See Also + -------- + numpy.swapaxes + + Availability + -------- + Multiple GPUs, Multiple CPUs + """ + return a.swapaxes(axis1, axis2) + + +@add_boilerplate("a") +def transpose(a: ndarray, axes: Optional[list[int]] = None) -> ndarray: + """ + + Permute the dimensions of an array. + + Parameters + ---------- + a : array_like + Input array. + axes : list[int], optional + By default, reverse the dimensions, otherwise permute the axes + according to the values given. + + Returns + ------- + p : ndarray + `a` with its axes permuted. A view is returned whenever + possible. + + See Also + -------- + numpy.transpose + + Availability + -------- + Multiple GPUs, Multiple CPUs + """ + return a.transpose(axes=axes) + + +@add_boilerplate("a") +def moveaxis( + a: ndarray, source: Sequence[int], destination: Sequence[int] +) -> ndarray: + """ + Move axes of an array to new positions. + Other axes remain in their original order. + + Parameters + ---------- + a : ndarray + The array whose axes should be reordered. + source : int or Sequence[int] + Original positions of the axes to move. These must be unique. + destination : int or Sequence[int] + Destination positions for each of the original axes. These must also be + unique. + + Returns + ------- + result : ndarray + Array with moved axes. This array is a view of the input array. + + See Also + -------- + numpy.moveaxis + + Availability + -------- + Multiple GPUs, Multiple CPUs + """ + source = normalize_axis_tuple(source, a.ndim, "source") + destination = normalize_axis_tuple(destination, a.ndim, "destination") + if len(source) != len(destination): + raise ValueError( + "`source` and `destination` arguments must have the same number " + "of elements" + ) + order = [n for n in range(a.ndim) if n not in source] + for dest, src in sorted(zip(destination, source)): + order.insert(dest, src) + return a.transpose(order) diff --git a/cunumeric/_module/creation_data.py b/cunumeric/_module/creation_data.py new file mode 100644 index 0000000000..a8e8248d3d --- /dev/null +++ b/cunumeric/_module/creation_data.py @@ -0,0 +1,176 @@ +# Copyright 2024 NVIDIA Corporation +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +from __future__ import annotations + +from typing import TYPE_CHECKING, Any, Literal, Optional, Union + +import numpy as np + +from ..array import add_boilerplate, ndarray +from ..runtime import runtime +from .creation_shape import empty_like + +if TYPE_CHECKING: + from ..types import OrderType + + +def array( + obj: Any, + dtype: Optional[np.dtype[Any]] = None, + copy: bool = True, + order: Union[OrderType, Literal["K"]] = "K", + subok: bool = False, + ndmin: int = 0, +) -> ndarray: + """ + array(object, dtype=None, copy=True) + + Create an array. + + Parameters + ---------- + object : array_like + An array, any object exposing the array interface, an object whose + __array__ method returns an array, or any (nested) sequence. + dtype : data-type, optional + The desired data-type for the array. If not given, then the type will + be determined as the minimum type required to hold the objects in the + sequence. + copy : bool, optional + If true (default), then the object is copied. Otherwise, a copy will + only be made if __array__ returns a copy, if obj is a nested sequence, + or if a copy is needed to satisfy any of the other requirements + (`dtype`, `order`, etc.). + order : ``{'K', 'A', 'C', 'F'}``, optional + Specify the memory layout of the array. If object is not an array, the + newly created array will be in C order (row major) unless 'F' is + specified, in which case it will be in Fortran order (column major). + If object is an array the following holds. + + ===== ========= =================================================== + order no copy copy=True + ===== ========= =================================================== + 'K' unchanged F & C order preserved, otherwise most similar order + 'A' unchanged F order if input is F and not C, otherwise C order + 'C' C order C order + 'F' F order F order + ===== ========= =================================================== + + When ``copy=False`` and a copy is made for other reasons, the result is + the same as if ``copy=True``, with some exceptions for 'A', see the + Notes section. The default order is 'K'. + subok : bool, optional + If True, then sub-classes will be passed-through, otherwise + the returned array will be forced to be a base-class array (default). + ndmin : int, optional + Specifies the minimum number of dimensions that the resulting + array should have. Ones will be pre-pended to the shape as + needed to meet this requirement. + + Returns + ------- + out : ndarray + An array object satisfying the specified requirements. + + See Also + -------- + numpy.array + + Availability + -------- + Multiple GPUs, Multiple CPUs + """ + + if not isinstance(obj, ndarray): + thunk = runtime.get_numpy_thunk(obj, share=(not copy), dtype=dtype) + result = ndarray(shape=None, thunk=thunk) + else: + result = obj + if dtype is not None and result.dtype != dtype: + result = result.astype(dtype) + elif copy and obj is result: + result = result.copy() + if result.ndim < ndmin: + shape = (1,) * (ndmin - result.ndim) + result.shape + result = result.reshape(shape) + return result + + +def asarray(a: Any, dtype: Optional[np.dtype[Any]] = None) -> ndarray: + """ + Convert the input to an array. + + Parameters + ---------- + a : array_like + Input data, in any form that can be converted to an array. This + includes lists, lists of tuples, tuples, tuples of tuples, tuples + of lists and ndarrays. + dtype : data-type, optional + By default, the data-type is inferred from the input data. + + Returns + ------- + out : ndarray + Array interpretation of `a`. No copy is performed if the input is + already an ndarray with matching dtype. If `a` is a subclass of + ndarray, a base class ndarray is returned. + + See Also + -------- + numpy.asarray + + Availability + -------- + Multiple GPUs, Multiple CPUs + """ + if not isinstance(a, ndarray): + thunk = runtime.get_numpy_thunk(a, share=True, dtype=dtype) + writeable = a.flags.writeable if isinstance(a, np.ndarray) else True + array = ndarray(shape=None, thunk=thunk, writeable=writeable) + else: + array = a + if dtype is not None and array.dtype != dtype: + array = array.astype(dtype) + return array + + +@add_boilerplate("a") +def copy(a: ndarray) -> ndarray: + """ + + Return an array copy of the given object. + + Parameters + ---------- + a : array_like + Input data. + + Returns + ------- + arr : ndarray + Array interpretation of `a`. + + See Also + -------- + numpy.copy + + Availability + -------- + Multiple GPUs, Multiple CPUs + """ + result = empty_like(a, dtype=a.dtype) + result._thunk.copy(a._thunk, deep=True) + return result diff --git a/cunumeric/_module/creation_matrices.py b/cunumeric/_module/creation_matrices.py new file mode 100644 index 0000000000..8d1f69f99e --- /dev/null +++ b/cunumeric/_module/creation_matrices.py @@ -0,0 +1,191 @@ +# Copyright 2024 NVIDIA Corporation +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +from __future__ import annotations + +from typing import TYPE_CHECKING, Optional + +from ..array import add_boilerplate, ndarray +from .creation_shape import ones + +if TYPE_CHECKING: + import numpy.typing as npt + + +@add_boilerplate("v") +def diag(v: ndarray, k: int = 0) -> ndarray: + """ + + Extract a diagonal or construct a diagonal array. + + See the more detailed documentation for ``cunumeric.diagonal`` if you use + this function to extract a diagonal and wish to write to the resulting + array; whether it returns a copy or a view depends on what version of numpy + you are using. + + Parameters + ---------- + v : array_like + If `v` is a 2-D array, return a copy of its `k`-th diagonal. + If `v` is a 1-D array, return a 2-D array with `v` on the `k`-th + diagonal. + k : int, optional + Diagonal in question. The default is 0. Use `k>0` for diagonals + above the main diagonal, and `k<0` for diagonals below the main + diagonal. + + Returns + ------- + out : ndarray + The extracted diagonal or constructed diagonal array. + + See Also + -------- + numpy.diag + + Availability + -------- + Multiple GPUs, Multiple CPUs + """ + if v.ndim == 0: + raise ValueError("Input must be 1- or 2-d") + elif v.ndim == 1: + return v.diagonal(offset=k, axis1=0, axis2=1, extract=False) + elif v.ndim == 2: + return v.diagonal(offset=k, axis1=0, axis2=1, extract=True) + else: + raise ValueError("diag requires 1- or 2-D array, use diagonal instead") + + +def tri( + N: int, + M: Optional[int] = None, + k: int = 0, + dtype: npt.DTypeLike = float, + *, + like: Optional[ndarray] = None, +) -> ndarray: + """ + An array with ones at and below the given diagonal and zeros elsewhere. + + Parameters + ---------- + N : int + Number of rows in the array. + M : int, optional + Number of columns in the array. + By default, `M` is taken equal to `N`. + k : int, optional + The sub-diagonal at and below which the array is filled. + `k` = 0 is the main diagonal, while `k` < 0 is below it, + and `k` > 0 is above. The default is 0. + dtype : dtype, optional + Data type of the returned array. The default is float. + like : array_like + Reference object to allow the creation of arrays which are not NumPy + arrays. If an array-like passed in as `like` supports the + `__array_function__` protocol, the result will be defined by it. In + this case it ensures the creation of an array object compatible with + that passed in via this argument. + + Returns + ------- + tri : ndarray of shape (N, M) + Array with its lower triangle filled with ones and zero elsewhere; + in other words ``T[i,j] == 1`` for ``j <= i + k``, 0 otherwise. + + See Also + -------- + numpy.tri + + Notes + ----- + `like` argument is currently not supported + + Availability + -------- + Multiple GPUs, Multiple CPUs + + """ + # TODO: add support for `like` (see issue #418) + if like is not None: + raise ValueError("like parameter is currently not supported") + + if M is None: + M = N + + out = ones((N, M), dtype=dtype) + return tril(out, k) + + +@add_boilerplate("m") +def trilu(m: ndarray, k: int, lower: bool) -> ndarray: + if m.ndim < 1: + raise TypeError("Array must be at least 1-D") + shape = m.shape if m.ndim >= 2 else m.shape * 2 + result = ndarray(shape, dtype=m.dtype, inputs=(m,)) + result._thunk.trilu(m._thunk, k, lower) + return result + + +def tril(m: ndarray, k: int = 0) -> ndarray: + """ + + Lower triangle of an array. + + Return a copy of an array with elements above the `k`-th diagonal zeroed. + + Parameters + ---------- + m : array_like + Input array of shape (M, N). + k : int, optional + Diagonal above which to zero elements. `k = 0` (the default) is the + main diagonal, `k < 0` is below it and `k > 0` is above. + + Returns + ------- + tril : ndarray + Lower triangle of `m`, of same shape and data-type as `m`. + + See Also + -------- + numpy.tril + + Availability + -------- + Multiple GPUs, Multiple CPUs + """ + return trilu(m, k, True) + + +def triu(m: ndarray, k: int = 0) -> ndarray: + """ + + Upper triangle of an array. + + Return a copy of a matrix with the elements below the `k`-th diagonal + zeroed. + + Please refer to the documentation for `tril` for further details. + + See Also + -------- + numpy.triu + + Availability + -------- + Multiple GPUs, Multiple CPUs + """ + return trilu(m, k, False) diff --git a/cunumeric/_module/creation_ranges.py b/cunumeric/_module/creation_ranges.py new file mode 100644 index 0000000000..dd67ce2d74 --- /dev/null +++ b/cunumeric/_module/creation_ranges.py @@ -0,0 +1,254 @@ +# Copyright 2024 NVIDIA Corporation +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +from __future__ import annotations + +import math +from typing import TYPE_CHECKING, Any, Optional, Union + +import numpy as np + +from .._ufunc.floating import floor +from ..array import add_boilerplate, ndarray + +if TYPE_CHECKING: + import numpy.typing as npt + + +_builtin_max = max + + +def arange( + start: Union[int, float] = 0, + stop: Optional[Union[int, float]] = None, + step: Optional[Union[int, float]] = 1, + dtype: Optional[npt.DTypeLike] = None, +) -> ndarray: + """ + arange([start,] stop[, step,], dtype=None) + + Return evenly spaced values within a given interval. + + Values are generated within the half-open interval ``[start, stop)`` + (in other words, the interval including `start` but excluding `stop`). + For integer arguments the function is equivalent to the Python built-in + `range` function, but returns an ndarray rather than a list. + + When using a non-integer step, such as 0.1, the results will often not + be consistent. It is better to use `cunumeric.linspace` for these cases. + + Parameters + ---------- + start : int or float, optional + Start of interval. The interval includes this value. The default + start value is 0. + stop : int or float + End of interval. The interval does not include this value, except + in some cases where `step` is not an integer and floating point + round-off affects the length of `out`. + step : int or float, optional + Spacing between values. For any output `out`, this is the distance + between two adjacent values, ``out[i+1] - out[i]``. The default + step size is 1. If `step` is specified as a position argument, + `start` must also be given. + dtype : data-type + The type of the output array. If `dtype` is not given, infer the data + type from the other input arguments. + + Returns + ------- + arange : ndarray + Array of evenly spaced values. + + For floating point arguments, the length of the result is + ``ceil((stop - start)/step)``. Because of floating point overflow, + this rule may result in the last element of `out` being greater + than `stop`. + + See Also + -------- + numpy.arange + + Availability + -------- + Multiple GPUs, Multiple CPUs + """ + if stop is None: + stop = start + start = 0 + + if step is None: + step = 1 + + if dtype is None: + dtype = np.result_type(start, stop, step) + else: + dtype = np.dtype(dtype) + + N = math.ceil((stop - start) / step) + result = ndarray((_builtin_max(0, N),), dtype) + result._thunk.arange(start, stop, step) + return result + + +@add_boilerplate("start", "stop") +def linspace( + start: ndarray, + stop: ndarray, + num: int = 50, + endpoint: bool = True, + retstep: bool = False, + dtype: Optional[npt.DTypeLike] = None, + axis: int = 0, +) -> Union[ndarray, tuple[ndarray, Union[float, ndarray]]]: + """ + + Return evenly spaced numbers over a specified interval. + + Returns `num` evenly spaced samples, calculated over the + interval [`start`, `stop`]. + + The endpoint of the interval can optionally be excluded. + + Parameters + ---------- + start : array_like + The starting value of the sequence. + stop : array_like + The end value of the sequence, unless `endpoint` is set to False. + In that case, the sequence consists of all but the last of ``num + 1`` + evenly spaced samples, so that `stop` is excluded. Note that the step + size changes when `endpoint` is False. + num : int, optional + Number of samples to generate. Default is 50. Must be non-negative. + endpoint : bool, optional + If True, `stop` is the last sample. Otherwise, it is not included. + Default is True. + retstep : bool, optional + If True, return (`samples`, `step`), where `step` is the spacing + between samples. + dtype : data-type, optional + The type of the output array. If `dtype` is not given, infer the data + type from the other input arguments. + axis : int, optional + The axis in the result to store the samples. Relevant only if start + or stop are array-like. By default (0), the samples will be along a + new axis inserted at the beginning. Use -1 to get an axis at the end. + + Returns + ------- + samples : ndarray + There are `num` equally spaced samples in the closed interval + ``[start, stop]`` or the half-open interval ``[start, stop)`` + (depending on whether `endpoint` is True or False). + step : float or ndarray, optional + Only returned if `retstep` is True + + Size of spacing between samples. + + See Also + -------- + numpy.linspace + + Availability + -------- + Multiple GPUs, Multiple CPUs + """ + if num < 0: + raise ValueError("Number of samples, %s, must be non-negative." % num) + div = (num - 1) if endpoint else num + + common_kind = np.result_type(start.dtype, stop.dtype).kind + dt = np.complex128 if common_kind == "c" else np.float64 + if dtype is None: + dtype = dt + + delta = stop - start + y = arange(0, num, dtype=dt) + + out: tuple[Any, ...] # EllipsisType not even in typing_extensions yet + + # Reshape these arrays into dimensions that allow them to broadcast + if delta.ndim > 0: + if axis is None or axis == 0: + # First dimension + y = y.reshape((-1,) + (1,) * delta.ndim) + # Nothing else needs to be reshaped here because + # they should all broadcast correctly with y + if endpoint and num > 1: + out = (-1,) + elif axis == -1 or axis == delta.ndim: + # Last dimension + y = y.reshape((1,) * delta.ndim + (-1,)) + if endpoint and num > 1: + out = (Ellipsis, -1) + # Extend everything else with extra dimensions of 1 at the end + # so that they can broadcast with y + delta = delta.reshape(delta.shape + (1,)) + start = start.reshape(start.shape + (1,)) + elif axis < delta.ndim: + # Somewhere in the middle + y = y.reshape((1,) * axis + (-1,) + (1,) * (delta.ndim - axis)) + # Start array might be smaller than delta because of broadcast + startax = start.ndim - len(delta.shape[axis:]) + start = start.reshape( + start.shape[0:startax] + (1,) + start.shape[startax:] + ) + if endpoint and num > 1: + out = (Ellipsis, -1) + (slice(None, None, None),) * len( + delta.shape[axis:] + ) + delta = delta.reshape( + delta.shape[0:axis] + (1,) + delta.shape[axis:] + ) + else: + raise ValueError( + "axis " + + str(axis) + + " is out of bounds for array of dimension " + + str(delta.ndim + 1) + ) + else: + out = (-1,) + # else delta is a scalar so start must be also + # therefore it will trivially broadcast correctly + + step: Union[float, ndarray] + if div > 0: + step = delta / div + if delta.ndim == 0: + y *= step + else: + y = y * step + else: + # sequences with 0 items or 1 item with endpoint=True (i.e. div <= 0) + # have an undefined step + step = np.NaN + if delta.ndim == 0: + y *= delta + else: + y = y * delta + + y += start.astype(y.dtype, copy=False) + + if endpoint and num > 1: + y[out] = stop.astype(y.dtype, copy=False) + + if np.issubdtype(dtype, np.integer): + floor(y, out=y) + + if retstep: + return y.astype(dtype, copy=False), step + else: + return y.astype(dtype, copy=False) diff --git a/cunumeric/_module/creation_shape.py b/cunumeric/_module/creation_shape.py new file mode 100644 index 0000000000..a8fa9c9e70 --- /dev/null +++ b/cunumeric/_module/creation_shape.py @@ -0,0 +1,400 @@ +# Copyright 2024 NVIDIA Corporation +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +from __future__ import annotations + +import operator +from typing import TYPE_CHECKING, Any, Optional, Union + +import numpy as np + +from ..array import add_boilerplate, ndarray +from ..types import NdShapeLike + +if TYPE_CHECKING: + import numpy.typing as npt + + +def empty(shape: NdShapeLike, dtype: npt.DTypeLike = np.float64) -> ndarray: + """ + empty(shape, dtype=float) + + Return a new array of given shape and type, without initializing entries. + + Parameters + ---------- + shape : int or tuple[int] + Shape of the empty array. + dtype : data-type, optional + Desired output data-type for the array. Default is `cunumeric.float64`. + + Returns + ------- + out : ndarray + Array of uninitialized (arbitrary) data of the given shape and dtype. + + See Also + -------- + numpy.empty + + Availability + -------- + Multiple GPUs, Multiple CPUs + """ + return ndarray(shape=shape, dtype=dtype) + + +@add_boilerplate("a") +def empty_like( + a: ndarray, + dtype: Optional[npt.DTypeLike] = None, + shape: Optional[NdShapeLike] = None, +) -> ndarray: + """ + + empty_like(prototype, dtype=None) + + Return a new array with the same shape and type as a given array. + + Parameters + ---------- + prototype : array_like + The shape and data-type of `prototype` define these same attributes + of the returned array. + dtype : data-type, optional + Overrides the data type of the result. + shape : int or tuple[int], optional + Overrides the shape of the result. + + Returns + ------- + out : ndarray + Array of uninitialized (arbitrary) data with the same shape and type as + `prototype`. + + See Also + -------- + numpy.empty_like + + Availability + -------- + Multiple GPUs, Multiple CPUs + """ + shape = a.shape if shape is None else shape + if dtype is not None: + dtype = np.dtype(dtype) + else: + dtype = a.dtype + return ndarray(shape, dtype=dtype, inputs=(a,)) + + +def eye( + N: int, + M: Optional[int] = None, + k: int = 0, + dtype: Optional[npt.DTypeLike] = np.float64, +) -> ndarray: + """ + + Return a 2-D array with ones on the diagonal and zeros elsewhere. + + Parameters + ---------- + N : int + Number of rows in the output. + M : int, optional + Number of columns in the output. If None, defaults to `N`. + k : int, optional + Index of the diagonal: 0 (the default) refers to the main diagonal, + a positive value refers to an upper diagonal, and a negative value + to a lower diagonal. + dtype : data-type, optional + Data-type of the returned array. + + Returns + ------- + I : ndarray + An array of shape (N, M) where all elements are equal to zero, except + for the `k`-th diagonal, whose values are equal to one. + + See Also + -------- + numpy.eye + + Availability + -------- + Multiple GPUs, Multiple CPUs + """ + if dtype is not None: + dtype = np.dtype(dtype) + if M is None: + M = N + k = operator.index(k) + result = ndarray((N, M), dtype) + result._thunk.eye(k) + return result + + +def identity(n: int, dtype: npt.DTypeLike = float) -> ndarray: + """ + + Return the identity array. + + The identity array is a square array with ones on + the main diagonal. + + Parameters + ---------- + n : int + Number of rows (and columns) in `n` x `n` output. + dtype : data-type, optional + Data-type of the output. Defaults to ``float``. + + Returns + ------- + out : ndarray + `n` x `n` array with its main diagonal set to one, and all other + elements 0. + + See Also + -------- + numpy.identity + + Availability + -------- + Multiple GPUs, Multiple CPUs + """ + return eye(N=n, M=n, dtype=dtype) + + +def ones(shape: NdShapeLike, dtype: npt.DTypeLike = np.float64) -> ndarray: + """ + + Return a new array of given shape and type, filled with ones. + + Parameters + ---------- + shape : int or tuple[int] + Shape of the new array. + dtype : data-type, optional + The desired data-type for the array. Default is `cunumeric.float64`. + + Returns + ------- + out : ndarray + Array of ones with the given shape and dtype. + + See Also + -------- + numpy.ones + + Availability + -------- + Multiple GPUs, Multiple CPUs + """ + return full(shape, 1, dtype=dtype) + + +def ones_like( + a: ndarray, + dtype: Optional[npt.DTypeLike] = None, + shape: Optional[NdShapeLike] = None, +) -> ndarray: + """ + + Return an array of ones with the same shape and type as a given array. + + Parameters + ---------- + a : array_like + The shape and data-type of `a` define these same attributes of the + returned array. + dtype : data-type, optional + Overrides the data type of the result. + shape : int or tuple[int], optional + Overrides the shape of the result. + + Returns + ------- + out : ndarray + Array of ones with the same shape and type as `a`. + + See Also + -------- + numpy.ones_like + + Availability + -------- + Multiple GPUs, Multiple CPUs + """ + usedtype = a.dtype + if dtype is not None: + usedtype = np.dtype(dtype) + return full_like(a, 1, dtype=usedtype, shape=shape) + + +def zeros(shape: NdShapeLike, dtype: npt.DTypeLike = np.float64) -> ndarray: + """ + zeros(shape, dtype=float) + + Return a new array of given shape and type, filled with zeros. + + Parameters + ---------- + shape : int or tuple[int] + Shape of the new array. + dtype : data-type, optional + The desired data-type for the array. Default is `cunumeric.float64`. + + Returns + ------- + out : ndarray + Array of zeros with the given shape and dtype. + + See Also + -------- + numpy.zeros + + Availability + -------- + Multiple GPUs, Multiple CPUs + """ + if dtype is not None: + dtype = np.dtype(dtype) + return full(shape, 0, dtype=dtype) + + +def zeros_like( + a: ndarray, + dtype: Optional[npt.DTypeLike] = None, + shape: Optional[NdShapeLike] = None, +) -> ndarray: + """ + + Return an array of zeros with the same shape and type as a given array. + + Parameters + ---------- + a : array_like + The shape and data-type of `a` define these same attributes of + the returned array. + dtype : data-type, optional + Overrides the data type of the result. + shape : int or tuple[int], optional + Overrides the shape of the result. + + Returns + ------- + out : ndarray + Array of zeros with the same shape and type as `a`. + + See Also + -------- + numpy.zeros_like + + Availability + -------- + Multiple GPUs, Multiple CPUs + """ + usedtype = a.dtype + if dtype is not None: + usedtype = np.dtype(dtype) + return full_like(a, 0, dtype=usedtype, shape=shape) + + +def full( + shape: NdShapeLike, + value: Any, + dtype: Optional[npt.DTypeLike] = None, +) -> ndarray: + """ + + Return a new array of given shape and type, filled with `fill_value`. + + Parameters + ---------- + shape : int or tuple[int] + Shape of the new array. + fill_value : scalar + Fill value. + dtype : data-type, optional + The desired data-type for the array The default, None, means + `cunumeric.array(fill_value).dtype`. + + Returns + ------- + out : ndarray + Array of `fill_value` with the given shape and dtype. + + See Also + -------- + numpy.full + + Availability + -------- + Multiple GPUs, Multiple CPUs + """ + if dtype is None: + val = np.array(value) + else: + dtype = np.dtype(dtype) + val = np.array(value, dtype=dtype) + result = empty(shape, dtype=val.dtype) + result._thunk.fill(val) + return result + + +def full_like( + a: ndarray, + value: Union[int, float], + dtype: Optional[npt.DTypeLike] = None, + shape: Optional[NdShapeLike] = None, +) -> ndarray: + """ + + Return a full array with the same shape and type as a given array. + + Parameters + ---------- + a : array_like + The shape and data-type of `a` define these same attributes of + the returned array. + fill_value : scalar + Fill value. + dtype : data-type, optional + Overrides the data type of the result. + shape : int or tuple[int], optional + Overrides the shape of the result. + + Returns + ------- + out : ndarray + Array of `fill_value` with the same shape and type as `a`. + + See Also + -------- + numpy.full_like + + Availability + -------- + Multiple GPUs, Multiple CPUs + """ + if dtype is not None: + dtype = np.dtype(dtype) + else: + dtype = a.dtype + result = empty_like(a, dtype=dtype, shape=shape) + val = np.array(value, dtype=result.dtype) + result._thunk.fill(val) + return result diff --git a/cunumeric/_module/indexing.py b/cunumeric/_module/indexing.py new file mode 100644 index 0000000000..9f97875169 --- /dev/null +++ b/cunumeric/_module/indexing.py @@ -0,0 +1,1111 @@ +# Copyright 2024 NVIDIA Corporation +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +from __future__ import annotations + +from typing import TYPE_CHECKING, Any, Optional, Sequence, Union + +import numpy as np +from numpy.core.multiarray import ( # type: ignore [attr-defined] + normalize_axis_index, +) + +from ..array import ( + add_boilerplate, + check_writeable, + convert_to_cunumeric_ndarray, + ndarray, +) +from ..coverage import is_implemented +from ..runtime import runtime +from ..types import NdShape +from .array_joining import hstack +from .array_shape import reshape +from .array_tiling import tile +from .creation_matrices import tri +from .creation_ranges import arange +from .creation_shape import empty, ones +from .ssc_counting import count_nonzero +from .ssc_searching import nonzero + +if TYPE_CHECKING: + from typing import Callable + + import numpy.typing as npt + + from ..types import BoundsMode + +_builtin_min = min + + +@add_boilerplate("arr", "mask", "vals") +def place(arr: ndarray, mask: ndarray, vals: ndarray) -> None: + """ + Change elements of an array based on conditional and input values. + + Parameters + ---------- + arr : array_like + Array to put data into. + mask : array_like + Mask array. Must have the same size as `arr`. + vals : 1-D sequence + Values to put into `arr`. Only the first N elements are used, + where N is the number of True values in mask. If vals is smaller + than N, it will be repeated, and if elements of a are to be masked, + this sequence must be non-empty. + + See Also + -------- + numpy.copyto, numpy.put, numpy.take, numpy.extract + + Availability + -------- + Multiple GPUs, Multiple CPUs + """ + if arr.size == 0: + return + + check_writeable(arr) + + if mask.size != arr.size: + raise ValueError("arr array and condition array must be of same size") + + if vals.ndim != 1: + raise ValueError("vals array has to be 1-dimensional") + + if mask.shape != arr.shape: + mask_reshape = reshape(mask, arr.shape) + else: + mask_reshape = mask + + num_values = int(count_nonzero(mask_reshape)) + if num_values == 0: + return + + if vals.size == 0: + raise ValueError("vals array cannot be empty") + + if num_values != vals.size: + reps = (num_values + vals.size - 1) // vals.size + vals_resized = tile(A=vals, reps=reps) if reps > 1 else vals + vals_resized = vals_resized[:num_values] + else: + vals_resized = vals + + if mask_reshape.dtype == bool: + arr._thunk.set_item(mask_reshape._thunk, vals_resized._thunk) + else: + bool_mask = mask_reshape.astype(bool) + arr._thunk.set_item(bool_mask._thunk, vals_resized._thunk) + + +# Indexing-like operations +def indices( + dimensions: Sequence[int], dtype: npt.DTypeLike = int, sparse: bool = False +) -> Union[ndarray, tuple[ndarray, ...]]: + """ + Return an array representing the indices of a grid. + Compute an array where the subarrays contain index values 0, 1, ... + varying only along the corresponding axis. + + Parameters + ---------- + dimensions : Sequence[int] + The shape of the grid. + dtype : data-type, optional + Data type of the result. + sparse : bool, optional + Return a sparse representation of the grid instead of a dense + representation. Default is False. + + Returns + ------- + grid : ndarray or Tuple[ndarray, ...] + If sparse is False returns one array of grid indices, + ``grid.shape = (len(dimensions),) + tuple(dimensions)``. + If sparse is True returns a tuple of arrays, with + ``grid[i].shape = (1, ..., 1, dimensions[i], 1, ..., 1)`` with + dimensions[i] in the ith place + + See Also + -------- + numpy.indices + + Notes + ----- + The output shape in the dense case is obtained by prepending the number + of dimensions in front of the tuple of dimensions, i.e. if `dimensions` + is a tuple ``(r0, ..., rN-1)`` of length ``N``, the output shape is + ``(N, r0, ..., rN-1)``. + The subarrays ``grid[k]`` contains the N-D array of indices along the + ``k-th`` axis. Explicitly: + + grid[k, i0, i1, ..., iN-1] = ik + + Availability + -------- + Multiple GPUs, Multiple CPUs + """ + # implementation of indices routine is adapted from NumPy + dimensions = tuple(dimensions) + N = len(dimensions) + shape = (1,) * N + if sparse: + res_tuple: tuple[ndarray, ...] = () + for i, dim in enumerate(dimensions): + idx = arange(dim, dtype=dtype).reshape( + shape[:i] + (dim,) + shape[i + 1 :] + ) + res_tuple += (idx,) + return res_tuple + else: + out_shape = (N,) + dimensions + res_array: ndarray = empty(out_shape, dtype=dtype) + for i, dim in enumerate(dimensions): + idx = arange(dim, dtype=dtype).reshape( + shape[:i] + (dim,) + shape[i + 1 :] + ) + res_array[i] = idx + return res_array + + +def mask_indices( + n: int, mask_func: Callable[[ndarray, int], ndarray], k: int = 0 +) -> tuple[ndarray, ...]: + """ + Return the indices to access (n, n) arrays, given a masking function. + + Assume `mask_func` is a function that, for a square array a of size + ``(n, n)`` with a possible offset argument `k`, when called as + ``mask_func(a, k)`` returns a new array with zeros in certain locations + (functions like :func:`cunumeric.triu` or :func:`cunumeric.tril` + do precisely this). Then this function returns the indices where + the non-zero values would be located. + + Parameters + ---------- + n : int + The returned indices will be valid to access arrays of shape (n, n). + mask_func : callable + A function whose call signature is similar to that of + :func:`cunumeric.triu`, :func:`cunumeric.tril`. + That is, ``mask_func(x, k)`` returns a boolean array, shaped like `x`. + `k` is an optional argument to the function. + k : scalar + An optional argument which is passed through to `mask_func`. Functions + like :func:`cunumeric.triu`, :func:`cunumeric,tril` + take a second argument that is interpreted as an offset. + + Returns + ------- + indices : tuple of arrays. + The `n` arrays of indices corresponding to the locations where + ``mask_func(np.ones((n, n)), k)`` is True. + + See Also + -------- + numpy.mask_indices + + Notes + ----- + WARNING: `mask_indices` expects `mask_function` to call cuNumeric functions + for good performance. In case non-cuNumeric functions are called by + `mask_function`, cuNumeric will have to materialize all data on the host + which might result in running out of system memory. + + Availability + -------- + Multiple GPUs, Multiple CPUs + """ + # this implementation is based on the Cupy + a = ones((n, n), dtype=bool) + if not is_implemented(mask_func): + runtime.warn( + "Calling non-cuNumeric functions in mask_func can result in bad " + "performance", + category=UserWarning, + ) + return mask_func(a, k).nonzero() + + +def diag_indices(n: int, ndim: int = 2) -> tuple[ndarray, ...]: + """ + Return the indices to access the main diagonal of an array. + + This returns a tuple of indices that can be used to access the main + diagonal of an array a with a.ndim >= 2 dimensions and + shape (n, n, …, n). For a.ndim = 2 this is the usual diagonal, + for a.ndim > 2 this is the set of indices to + access a[i, i, ..., i] for i = [0..n-1]. + + Parameters + ---------- + n : int + The size, along each dimension, of the arrays for which the + returned indices can be used. + ndim : int, optional + The number of dimensions. + + See Also + -------- + numpy.diag_indices + + Availability + -------- + Multiple GPUs, Multiple CPUs + """ + idx = arange(n, dtype=int) + return (idx,) * ndim + + +@add_boilerplate("arr") +def diag_indices_from(arr: ndarray) -> tuple[ndarray, ...]: + """ + Return the indices to access the main diagonal of an n-dimensional array. + + See diag_indices for full details. + + Parameters + ---------- + arr : array_like + at least 2-D + + See Also + -------- + numpy.diag_indices_from, numpy.diag_indices + + Availability + -------- + Multiple GPUs, Multiple CPUs + """ + if not arr.ndim >= 2: + raise ValueError("input array must be at least 2-d") + # For more than d=2, the strided formula is only valid for arrays with + # all dimensions equal, so we check first. + for i in range(1, arr.ndim): + if arr.shape[i] != arr.shape[0]: + raise ValueError("All dimensions of input must be of equal length") + + return diag_indices(arr.shape[0], arr.ndim) + + +def tril_indices( + n: int, k: int = 0, m: Optional[int] = None +) -> tuple[ndarray, ...]: + """ + Return the indices for the lower-triangle of an (n, m) array. + + Parameters + ---------- + n : int + The row dimension of the arrays for which the returned + indices will be valid. + k : int, optional + Diagonal offset (see :func:`cunumeric.tril` for details). + m : int, optional + The column dimension of the arrays for which the returned + indices will be valid. + By default `m` is taken equal to `n`. + + Returns + ------- + inds : tuple of arrays + The indices for the lower-triangle. The returned tuple contains two + arrays, each with the indices along one dimension of the array. + + See also + -------- + numpy.tril_indices + + Notes + ----- + + Availability + ------------ + Multiple GPUs, Multiple CPUs + """ + + tri_ = tri(n, m, k=k, dtype=bool) + return nonzero(tri_) + + +@add_boilerplate("arr") +def tril_indices_from(arr: ndarray, k: int = 0) -> tuple[ndarray, ...]: + """ + Return the indices for the lower-triangle of arr. + + See :func:`cunumeric.tril_indices` for full details. + + Parameters + ---------- + arr : array_like + The indices will be valid for arrays whose dimensions are + the same as arr. + k : int, optional + Diagonal offset (see :func:`cunumeric.tril` for details). + + Returns + ------- + inds : tuple of arrays + The indices for the lower-triangle. The returned tuple contains two + arrays, each with the indices along one dimension of the array. + + See Also + -------- + numpy.tril_indices_from + + Notes + ----- + + Availability + -------- + Multiple GPUs, Multiple CPUs + + """ + # this implementation is taken from numpy + if arr.ndim != 2: + raise ValueError("input array must be 2-d") + return tril_indices(arr.shape[-2], k=k, m=arr.shape[-1]) + + +def triu_indices( + n: int, k: int = 0, m: Optional[int] = None +) -> tuple[ndarray, ...]: + """ + Return the indices for the upper-triangle of an (n, m) array. + + Parameters + ---------- + n : int + The size of the arrays for which the returned indices will + be valid. + k : int, optional + Diagonal offset (see :func:`cunumeric.triu` for details). + m : int, optional + The column dimension of the arrays for which the returned + arrays will be valid. + By default `m` is taken equal to `n`. + + Returns + ------- + inds : tuple of arrays + The indices for the upper-triangle. The returned tuple contains two + arrays, each with the indices along one dimension of the array. + + See also + -------- + numpy.triu_indices + + Notes + ----- + + Availability + -------- + Multiple GPUs, Multiple CPUs + """ + + tri_ = ~tri(n, m, k=k - 1, dtype=bool) + return nonzero(tri_) + + +@add_boilerplate("arr") +def triu_indices_from(arr: ndarray, k: int = 0) -> tuple[ndarray, ...]: + """ + Return the indices for the upper-triangle of arr. + + See :func:`cunumeric.triu_indices` for full details. + + Parameters + ---------- + arr : ndarray, shape(N, N) + The indices will be valid for arrays whose dimensions are + the same as arr. + k : int, optional + Diagonal offset (see :func:`cunumeric.triu` for details). + + Returns + ------- + inds : tuple of arrays + The indices for the upper-triangle. The returned tuple contains two + arrays, each with the indices along one dimension of the array. + + See Also + -------- + numpy.triu_indices_from + + Notes + ----- + + Availability + -------- + Multiple GPUs, Multiple CPUs + """ + # this implementation is taken from numpy + if arr.ndim != 2: + raise ValueError("input array must be 2-d") + return triu_indices(arr.shape[-2], k=k, m=arr.shape[-1]) + + +@add_boilerplate("a") +def take( + a: ndarray, + indices: ndarray, + axis: Optional[int] = None, + out: Optional[ndarray] = None, + mode: BoundsMode = "raise", +) -> ndarray: + """ + Take elements from an array along an axis. + When axis is not None, this function does the same thing as “fancy” + indexing (indexing arrays using arrays); however, it can be easier + to use if you need elements along a given axis. A call such as + `np.take(arr, indices, axis=3)` is equivalent to `arr[:,:,:,indices,...]`. + + Parameters + ---------- + a : array_like `(Ni…, M, Nk…)` + The source array. + indices : array_like `(Nj…)` + The indices of the values to extract. + Also allow scalars for indices. + axis : int, optional + The axis over which to select values. By default, the flattened input + array is used. + out : ndarray, optional `(Ni…, Nj…, Nk…)` + If provided, the result will be placed in this array. It should be of + the appropriate shape and dtype. + mode : ``{'raise', 'wrap', 'clip'}``, optional + Specifies how out-of-bounds indices will behave. + 'raise' - raise an error (default) + 'wrap' - wrap around + 'clip' - clip to the range + 'clip' mode means that all indices that are too large are replaced by + the index that addresses the last element along that axis. + Note that this disables indexing with negative numbers. + + Returns + ------- + out : ndarray `(Ni…, Nj…, Nk…)` + The returned array has the same type as a. + + Raises + ------ + + See Also + -------- + numpy.take + + Availability + -------- + Multiple GPUs, Multiple CPUs + """ + return a.take(indices=indices, axis=axis, out=out, mode=mode) + + +def _fill_fancy_index_for_along_axis_routines( + a_shape: NdShape, axis: int, indices: ndarray +) -> tuple[ndarray, ...]: + # the logic below is base on the cupy implementation of + # the *_along_axis routines + ndim = len(a_shape) + fancy_index = [] + for i, n in enumerate(a_shape): + if i == axis: + fancy_index.append(indices) + else: + ind_shape = (1,) * i + (-1,) + (1,) * (ndim - i - 1) + fancy_index.append(arange(n).reshape(ind_shape)) + return tuple(fancy_index) + + +@add_boilerplate("a", "indices") +def take_along_axis( + a: ndarray, indices: ndarray, axis: Union[int, None] +) -> ndarray: + """ + Take values from the input array by matching 1d index and data slices. + + This iterates over matching 1d slices oriented along the specified axis in + the index and data arrays, and uses the former to look up values in the + latter. These slices can be different lengths. + + Functions returning an index along an axis, like + :func:`cunumeric.argsort` and :func:`cunumeric.argpartition`, + produce suitable indices for this function. + + Parameters + ---------- + arr : ndarray (Ni..., M, Nk...) + Source array + indices : ndarray (Ni..., J, Nk...) + Indices to take along each 1d slice of `arr`. This must match the + dimension of arr, but dimensions Ni and Nj only need to broadcast + against `arr`. + axis : int + The axis to take 1d slices along. If axis is None, the input array is + treated as if it had first been flattened to 1d, for consistency with + :func:`cunumeric.sort` and :func:`cunumeric.argsort`. + + Returns + ------- + out: ndarray (Ni..., J, Nk...) + The indexed result. It is going to be a view to `arr` for most cases, + except the case when `axis=Null` and `arr.ndim>1`. + + See Also + -------- + numpy.take_along_axis + + Availability + -------- + Multiple GPUs, Multiple CPUs + """ + if not np.issubdtype(indices.dtype, np.integer): + raise TypeError("`indices` must be an integer array") + + computed_axis = 0 + if axis is None: + if indices.ndim != 1: + raise ValueError("indices must be 1D if axis=None") + if a.ndim > 1: + a = a.ravel() + else: + computed_axis = normalize_axis_index(axis, a.ndim) + + if a.ndim != indices.ndim: + raise ValueError( + "`indices` and `a` must have the same number of dimensions" + ) + return a[ + _fill_fancy_index_for_along_axis_routines( + a.shape, computed_axis, indices + ) + ] + + +@add_boilerplate("a", "indices", "values") +def put_along_axis( + a: ndarray, indices: ndarray, values: ndarray, axis: Union[int, None] +) -> None: + """ + Put values into the destination array by matching 1d index and data slices. + + This iterates over matching 1d slices oriented along the specified axis in + the index and data arrays, and uses the former to place values into the + latter. These slices can be different lengths. + + Functions returning an index along an axis, like :func:`cunumeric.argsort` + and :func:`cunumeric.argpartition`, produce suitable indices for + this function. + + Parameters + ---------- + a : ndarray (Ni..., M, Nk...) + Destination array. + indices : ndarray (Ni..., J, Nk...) + Indices to change along each 1d slice of `arr`. This must match the + dimension of arr, but dimensions in Ni and Nj may be 1 to broadcast + against `arr`. + values : array_like (Ni..., J, Nk...) + values to insert at those indices. Its shape and dimension are + broadcast to match that of `indices`. + axis : int + The axis to take 1d slices along. If axis is None, the destination + array is treated as if a flattened 1d view had been created of it. + `axis=None` case is currently supported only for 1D input arrays. + + Note + ---- + Having duplicate entries in `indices` will result in undefined behavior + since operation performs asynchronous update of the `arr` entries. + + See Also + -------- + numpy.put_along_axis + + Availability + -------- + Multiple GPUs, Multiple CPUs + + """ + + if a.size == 0: + return + + check_writeable(a) + + if not np.issubdtype(indices.dtype, np.integer): + raise TypeError("`indices` must be an integer array") + + computed_axis = 0 + if axis is None: + if indices.ndim != 1: + raise ValueError("indices must be 1D if axis=None") + if a.ndim > 1: + # TODO call a=a.flat when flat is implemented + raise ValueError("a.ndim>1 case is not supported when axis=None") + if (indices.size == 0) or (values.size == 0): + return + if values.shape != indices.shape: + values = values._wrap(indices.size) + else: + computed_axis = normalize_axis_index(axis, a.ndim) + + if a.ndim != indices.ndim: + raise ValueError( + "`indices` and `a` must have the same number of dimensions" + ) + ind = _fill_fancy_index_for_along_axis_routines( + a.shape, computed_axis, indices + ) + a[ind] = values + + +@add_boilerplate("a") +def choose( + a: ndarray, + choices: Sequence[ndarray], + out: Optional[ndarray] = None, + mode: BoundsMode = "raise", +) -> ndarray: + """ + Construct an array from an index array and a list of arrays to choose from. + + Given an "index" array (`a`) of integers and a sequence of ``n`` arrays + (`choices`), `a` and each choice array are first broadcast, as necessary, + to arrays of a common shape; calling these *Ba* and *Bchoices[i], i = + 0,...,n-1* we have that, necessarily, ``Ba.shape == Bchoices[i].shape`` + for each ``i``. Then, a new array with shape ``Ba.shape`` is created as + follows: + + * if ``mode='raise'`` (the default), then, first of all, each element of + ``a`` (and thus ``Ba``) must be in the range ``[0, n-1]``; now, suppose + that ``i`` (in that range) is the value at the ``(j0, j1, ..., jm)`` + position in ``Ba`` - then the value at the same position in the new array + is the value in ``Bchoices[i]`` at that same position; + + * if ``mode='wrap'``, values in `a` (and thus `Ba`) may be any (signed) + integer; modular arithmetic is used to map integers outside the range + `[0, n-1]` back into that range; and then the new array is constructed + as above; + + * if ``mode='clip'``, values in `a` (and thus ``Ba``) may be any (signed) + integer; negative integers are mapped to 0; values greater than ``n-1`` + are mapped to ``n-1``; and then the new array is constructed as above. + + Parameters + ---------- + a : ndarray[int] + This array must contain integers in ``[0, n-1]``, where ``n`` is the + number of choices, unless ``mode=wrap`` or ``mode=clip``, in which + cases any integers are permissible. + choices : Sequence[ndarray] + Choice arrays. `a` and all of the choices must be broadcastable to the + same shape. If `choices` is itself an array (not recommended), then + its outermost dimension (i.e., the one corresponding to + ``choices.shape[0]``) is taken as defining the "sequence". + out : ndarray, optional + If provided, the result will be inserted into this array. It should + be of the appropriate shape and dtype. Note that `out` is always + buffered if ``mode='raise'``; use other modes for better performance. + mode : ``{'raise', 'wrap', 'clip'}``, optional + Specifies how indices outside ``[0, n-1]`` will be treated: + + * 'raise' : an exception is raised (default) + * 'wrap' : value becomes value mod ``n`` + * 'clip' : values < 0 are mapped to 0, values > n-1 are mapped to n-1 + + Returns + ------- + merged_array : ndarray + The merged result. + + Raises + ------ + ValueError: shape mismatch + If `a` and each choice array are not all broadcastable to the same + shape. + + See Also + -------- + numpy.choose + + Availability + -------- + Multiple GPUs, Multiple CPUs + """ + return a.choose(choices=choices, out=out, mode=mode) + + +def select( + condlist: Sequence[npt.ArrayLike | ndarray], + choicelist: Sequence[npt.ArrayLike | ndarray], + default: Any = 0, +) -> ndarray: + """ + Return an array drawn from elements in choicelist, depending on conditions. + + Parameters + ---------- + condlist : list of bool ndarrays + The list of conditions which determine from which array in `choicelist` + the output elements are taken. When multiple conditions are satisfied, + the first one encountered in `condlist` is used. + choicelist : list of ndarrays + The list of arrays from which the output elements are taken. It has + to be of the same length as `condlist`. + default : scalar, optional + The element inserted in `output` when all conditions evaluate to False. + + Returns + ------- + output : ndarray + The output at position m is the m-th element of the array in + `choicelist` where the m-th element of the corresponding array in + `condlist` is True. + + See Also + -------- + numpy.select + + Availability + -------- + Multiple GPUs, Multiple CPUs + """ + + if len(condlist) != len(choicelist): + raise ValueError( + "list of cases must be same length as list of conditions" + ) + if len(condlist) == 0: + raise ValueError("select with an empty condition list is not possible") + + condlist_ = tuple(convert_to_cunumeric_ndarray(c) for c in condlist) + for i, c in enumerate(condlist_): + if c.dtype != bool: + raise TypeError( + f"invalid entry {i} in condlist: should be boolean ndarray" + ) + + choicelist_ = tuple(convert_to_cunumeric_ndarray(c) for c in choicelist) + common_type = np.result_type(*choicelist_, default) + args = condlist_ + choicelist_ + choicelist_ = tuple( + c._maybe_convert(common_type, args) for c in choicelist_ + ) + default_ = np.array(default, dtype=common_type) + + out_shape = np.broadcast_shapes( + *(c.shape for c in condlist_), + *(c.shape for c in choicelist_), + ) + out = ndarray(shape=out_shape, dtype=common_type, inputs=args) + out._thunk.select( + tuple(c._thunk for c in condlist_), + tuple(c._thunk for c in choicelist_), + default_, + ) + return out + + +@add_boilerplate("condition", "a") +def compress( + condition: ndarray, + a: ndarray, + axis: Optional[int] = None, + out: Optional[ndarray] = None, +) -> ndarray: + """ + Return selected slices of an array along given axis. + + When working along a given axis, a slice along that axis is returned + in output for each index where condition evaluates to True. + When working on a 1-D array, compress is equivalent to numpy.extract. + + Parameters + ---------- + condition, 1-D array of bools + Array that selects which entries to return. If `len(c)` is less than + the size of a along the given axis, then output is truncated to the + length of the condition array. + + a : array_like + Array from which to extract a part. + + axis: int, optional + Axis along which to take slices. If None (default), + work on the flattened array. + + out : ndarray, optional + Output array. Its type is preserved and it must be of the right + shape to hold the output. + + Returns + ------- + compressed_array : ndarray + A copy of `a` without the slices along `axis` for which condition + is false. + + Raises + ------ + ValueError : dimension mismatch + If condition is not 1D array + ValueError : shape mismatch + If condition contains entries that are out of bounds of array + ValueError : shape mismatch + If output array has a wrong shape + + See Also + -------- + numpy.compress, numpy.extract + + Availability + -------- + Multiple GPUs, Multiple CPUs + + """ + return a.compress(condition, axis=axis, out=out) + + +@add_boilerplate("a") +def diagonal( + a: ndarray, + offset: int = 0, + axis1: int = 0, + axis2: int = 1, + extract: bool = True, +) -> ndarray: + """ + diagonal(a: ndarray, offset=0, axis1=None, axis2=None) + + Return specified diagonals. + + If `a` is 2-D, returns the diagonal of `a` with the given offset, + i.e., the collection of elements of the form ``a[i, i+offset]``. If + `a` has more than two dimensions, then the axes specified by `axis1` + and `axis2` are used to determine the 2-D sub-array whose diagonal is + returned. The shape of the resulting array can be determined by + removing `axis1` and `axis2` and appending an index to the right equal + to the size of the resulting diagonals. + + Parameters + ---------- + a : array_like + Array from which the diagonals are taken. + offset : int, optional + Offset of the diagonal from the main diagonal. Can be positive or + negative. Defaults to main diagonal (0). + axis1 : int, optional + Axis to be used as the first axis of the 2-D sub-arrays from which + the diagonals should be taken. Defaults to first axis (0). + axis2 : int, optional + Axis to be used as the second axis of the 2-D sub-arrays from + which the diagonals should be taken. Defaults to second axis (1). + + Returns + ------- + array_of_diagonals : ndarray + If `a` is 2-D, then a 1-D array containing the diagonal and of the + same type as `a` is returned unless `a` is a `matrix`, in which case + a 1-D array rather than a (2-D) `matrix` is returned in order to + maintain backward compatibility. + + If ``a.ndim > 2``, then the dimensions specified by `axis1` and `axis2` + are removed, and a new axis inserted at the end corresponding to the + diagonal. + + Raises + ------ + ValueError + If the dimension of `a` is less than 2. + + Notes + ----- + Unlike NumPy's, the cuNumeric implementation always returns a copy + + See Also + -------- + numpy.diagonal + + Availability + -------- + Multiple GPUs, Multiple CPUs + + """ + return a.diagonal(offset=offset, axis1=axis1, axis2=axis2, extract=extract) + + +@add_boilerplate("a", "indices", "values") +def put( + a: ndarray, indices: ndarray, values: ndarray, mode: str = "raise" +) -> None: + """ + Replaces specified elements of an array with given values. + The indexing works as if the target array is first flattened. + + Parameters + ---------- + a : array_like + Array to put data into + indices : array_like + Target indices, interpreted as integers. + WARNING: In case there are repeated entries in the + indices array, Legate doesn't guarantee the order in + which values are updated. + + values : array_like + Values to place in `a` at target indices. If values array is shorter + than indices, it will be repeated as necessary. + mode : {'raise', 'wrap', 'clip'}, optional + Specifies how out-of-bounds indices will behave. + 'raise' : raise an error. + 'wrap' : wrap around. + 'clip' : clip to the range. + + See Also + -------- + numpy.put + + Availability + -------- + Multiple GPUs, Multiple CPUs + """ + a.put(indices=indices, values=values, mode=mode) + + +@add_boilerplate("a", "mask", "values") +def putmask(a: ndarray, mask: ndarray, values: ndarray) -> None: + """ + putmask(a, mask, values) + Changes elements of an array based on conditional and input values. + Sets ``a.flat[n] = values[n]`` for each n where ``mask.flat[n]==True``. + If `values` is not the same size as `a` and `mask` then it will repeat. + This gives behavior different from ``a[mask] = values``. + + Parameters + ---------- + a : ndarray + Target array. + mask : array_like + Boolean mask array. It has to be the same shape as `a`. + values : array_like + Values to put into `a` where `mask` is True. If `values` is smaller + than `a` it will be repeated. + + See Also + -------- + numpy.putmask + + Availability + ------------ + Multiple GPUs, Multiple CPUs + """ + if not a.shape == mask.shape: + raise ValueError("mask and data must be the same size") + + check_writeable(a) + + mask = mask._warn_and_convert(np.dtype(bool)) + + if a.dtype != values.dtype: + values = values._warn_and_convert(a.dtype) + + try: + np.broadcast_shapes(values.shape, a.shape) + except ValueError: + values = values._wrap(a.size) + values = values.reshape(a.shape) + + a._thunk.putmask(mask._thunk, values._thunk) + + +@add_boilerplate("a", "val") +def fill_diagonal(a: ndarray, val: ndarray, wrap: bool = False) -> None: + """ + Fill the main diagonal of the given array of any dimensionality. + + For an array a with a.ndim >= 2, the diagonal is the list of locations with + indices a[i, ..., i] all identical. This function modifies the input + array in-place, it does not return a value. + + Parameters + ---------- + + a : array, at least 2-D. + Array whose diagonal is to be filled, it gets modified in-place. + val : scalar or array_like + Value(s) to write on the diagonal. If val is scalar, the value is + written along the diagonal. + If array-like, the flattened val is written along + the diagonal, repeating if necessary to fill all diagonal entries. + wrap : bool + If true, the diagonal "wraps" after N columns, for tall 2d matrices. + + Raises + ------ + ValueError + If the dimension of `a` is less than 2. + + Notes + ----- + + See Also + -------- + numpy.fill_diagonal + + Availability + -------- + Multiple GPUs, Multiple CPUs + + """ + if val.size == 0 or a.size == 0: + return + + check_writeable(a) + + if a.ndim < 2: + raise ValueError("array must be at least 2-d") + + n = _builtin_min(a.shape) + + if a.ndim > 2: + for s in a.shape: + if s != n: + raise ValueError( + "All dimensions of input must be of equal length" + ) + + len_val = n + + if a.ndim == 2 and wrap and a.shape[0] > a.shape[1]: + len_val = a.shape[0] - (a.shape[0] // (a.shape[1] + 1)) + + if (val.size != len_val and val.ndim > 0) or val.ndim > 1: + val = val._wrap(len_val) + + if a.ndim == 2 and wrap and a.shape[0] > a.shape[1]: + idx0_tmp = arange(a.shape[1], dtype=int) + idx0 = idx0_tmp.copy() + while idx0.size < len_val: + idx0_tmp = idx0_tmp + (a.shape[1] + 1) + idx0 = hstack((idx0, idx0_tmp)) + idx0 = idx0[0:len_val] + idx1 = arange(len_val, dtype=int) % a.shape[1] + a[idx0, idx1] = val + else: + idx = arange(n, dtype=int) + indices = (idx,) * a.ndim + + a[indices] = val diff --git a/cunumeric/_module/linalg_mvp.py b/cunumeric/_module/linalg_mvp.py new file mode 100644 index 0000000000..bd9cf6a0fc --- /dev/null +++ b/cunumeric/_module/linalg_mvp.py @@ -0,0 +1,924 @@ +# Copyright 2024 NVIDIA Corporation +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +from __future__ import annotations + +import re +from collections import Counter +from itertools import chain +from typing import TYPE_CHECKING, Any, Literal, Optional, Union + +import numpy as np +import opt_einsum as oe # type: ignore [import] + +from .._ufunc.math import multiply +from ..array import add_boilerplate, convert_to_cunumeric_ndarray, ndarray +from ..types import NdShape +from ..utils import AxesPairLike, inner_modes, matmul_modes, tensordot_modes +from .creation_data import copy + +if TYPE_CHECKING: + from .._ufunc.ufunc import CastingKind + +_builtin_all = all +_builtin_max = max + + +@add_boilerplate("a", "b") +def inner(a: ndarray, b: ndarray, out: Optional[ndarray] = None) -> ndarray: + """ + Inner product of two arrays. + + Ordinary inner product of vectors for 1-D arrays (without complex + conjugation), in higher dimensions a sum product over the last axes. + + Parameters + ---------- + a, b : array_like + out : ndarray, optional + Output argument. This must have the exact shape that would be returned + if it was not present. If its dtype is not what would be expected from + this operation, then the result will be (unsafely) cast to `out`. + + Returns + ------- + output : ndarray + If `a` and `b` are both + scalars or both 1-D arrays then a scalar is returned; otherwise + an array is returned. + ``output.shape = (*a.shape[:-1], *b.shape[:-1])`` + If `out` is given, then it is returned. + + Notes + ----- + The cuNumeric implementation is a little more liberal than NumPy in terms + of allowed broadcasting, e.g. ``inner(ones((1,)), ones((4,)))`` is allowed. + + See Also + -------- + numpy.inner + + Availability + -------- + Multiple GPUs, Multiple CPUs + """ + if a.ndim == 0 or b.ndim == 0: + return multiply(a, b, out=out) + (a_modes, b_modes, out_modes) = inner_modes(a.ndim, b.ndim) + return _contract( + a_modes, + b_modes, + out_modes, + a, + b, + out=out, + casting="unsafe", + ) + + +@add_boilerplate("a", "b") +def dot(a: ndarray, b: ndarray, out: Optional[ndarray] = None) -> ndarray: + """ + Dot product of two arrays. Specifically, + + - If both `a` and `b` are 1-D arrays, it is inner product of vectors + (without complex conjugation). + + - If both `a` and `b` are 2-D arrays, it is matrix multiplication, + but using ``a @ b`` is preferred. + + - If either `a` or `b` is 0-D (scalar), it is equivalent to + :func:`multiply` and using ``cunumeric.multiply(a, b)`` or ``a * b`` is + preferred. + + - If `a` is an N-D array and `b` is a 1-D array, it is a sum product over + the last axis of `a` and `b`. + + - If `a` is an N-D array and `b` is an M-D array (where ``M>=2``), it is a + sum product over the last axis of `a` and the second-to-last axis of + `b`:: + + dot(a: ndarray, b)[i,j,k,m] = sum(a[i,j,:] * b[k,:,m]) + + Parameters + ---------- + a : array_like + First argument. + b : array_like + Second argument. + out : ndarray, optional + Output argument. This must have the exact shape and dtype that would be + returned if it was not present. + + Returns + ------- + output : ndarray + Returns the dot product of `a` and `b`. If `out` is given, then it is + returned. + + Notes + ----- + The cuNumeric implementation is a little more liberal than NumPy in terms + of allowed broadcasting, e.g. ``dot(ones((3,1)), ones((4,5)))`` is allowed. + + Except for the inner-product case, only floating-point types are supported. + + See Also + -------- + numpy.dot + + Availability + -------- + Multiple GPUs, Multiple CPUs + """ + return a.dot(b, out=out) + + +@add_boilerplate("a", "b") +def matmul( + a: ndarray, + b: ndarray, + /, + out: Optional[ndarray] = None, + *, + casting: CastingKind = "same_kind", + dtype: Optional[np.dtype[Any]] = None, +) -> ndarray: + """ + Matrix product of two arrays. + + Parameters + ---------- + a, b : array_like + Input arrays, scalars not allowed. + out : ndarray, optional + A location into which the result is stored. If provided, it must have + a shape that matches the signature `(n,k),(k,m)->(n,m)`. + casting : ``{'no', 'equiv', 'safe', 'same_kind', 'unsafe'}``, optional + Controls what kind of data casting may occur. + + * 'no' means the data types should not be cast at all. + * 'equiv' means only byte-order changes are allowed. + * 'safe' means only casts which can preserve values are allowed. + * 'same_kind' means only safe casts or casts within a kind, + like float64 to float32, are allowed. + * 'unsafe' means any data conversions may be done. + + Default is 'same_kind'. + dtype : data-type, optional + If provided, forces the calculation to use the data type specified. + Note that you may have to also give a more liberal `casting` + parameter to allow the conversions. Default is None. + + Returns + ------- + output : ndarray + The matrix product of the inputs. + This is a scalar only when both a, b are 1-d vectors. + If `out` is given, then it is returned. + + Notes + ----- + The behavior depends on the arguments in the following way. + + - If both arguments are 2-D they are multiplied like conventional + matrices. + - If either argument is N-D, N > 2, it is treated as a stack of + matrices residing in the last two indexes and broadcast accordingly. + - If the first argument is 1-D, it is promoted to a matrix by + prepending a 1 to its dimensions. After matrix multiplication + the prepended 1 is removed. + - If the second argument is 1-D, it is promoted to a matrix by + appending a 1 to its dimensions. After matrix multiplication + the appended 1 is removed. + + ``matmul`` differs from ``dot`` in two important ways: + + - Multiplication by scalars is not allowed, use ``*`` instead. + - Stacks of matrices are broadcast together as if the matrices + were elements, respecting the signature ``(n,k),(k,m)->(n,m)``: + + >>> a = ones([9, 5, 7, 4]) + >>> c = ones([9, 5, 4, 3]) + >>> dot(a: ndarray, c).shape + (9, 5, 7, 9, 5, 3) + >>> matmul(a: ndarray, c).shape + (9, 5, 7, 3) + >>> # n is 7, k is 4, m is 3 + + The cuNumeric implementation is a little more liberal than NumPy in terms + of allowed broadcasting, e.g. ``matmul(ones((3,1)), ones((4,5)))`` is + allowed. + + Only floating-point types are supported. + + See Also + -------- + numpy.matmul + + Availability + -------- + Multiple GPUs, Multiple CPUs + """ + if a.ndim == 0 or b.ndim == 0: + raise ValueError("Scalars not allowed in matmul") + + (a_modes, b_modes, out_modes) = matmul_modes(a.ndim, b.ndim) + + return _contract( + a_modes, + b_modes, + out_modes, + a, + b, + out=out, + casting=casting, + dtype=dtype, + ) + + +@add_boilerplate("a", "b") +def vdot(a: ndarray, b: ndarray, out: Optional[ndarray] = None) -> ndarray: + """ + Return the dot product of two vectors. + + The vdot(`a`, `b`) function handles complex numbers differently than + dot(`a`, `b`). If the first argument is complex the complex conjugate + of the first argument is used for the calculation of the dot product. + + Note that `vdot` handles multidimensional arrays differently than `dot`: + it does *not* perform a matrix product, but flattens input arguments + to 1-D vectors first. Consequently, it should only be used for vectors. + + Parameters + ---------- + a : array_like + If `a` is complex the complex conjugate is taken before calculation + of the dot product. + b : array_like + Second argument to the dot product. + out : ndarray, optional + Output argument. This must have the exact shape that would be returned + if it was not present. If its dtype is not what would be expected from + this operation, then the result will be (unsafely) cast to `out`. + + Returns + ------- + output : ndarray + Dot product of `a` and `b`. If `out` is given, then it is returned. + + Notes + ----- + The cuNumeric implementation is a little more liberal than NumPy in terms + of allowed broadcasting, e.g. ``vdot(ones((1,)), ones((4,)))`` is allowed. + + See Also + -------- + numpy.vdot + + Availability + -------- + Multiple GPUs, Multiple CPUs + """ + return inner(a.ravel().conj(), b.ravel(), out=out) + + +@add_boilerplate("a", "b") +def outer(a: ndarray, b: ndarray, out: Optional[ndarray] = None) -> ndarray: + """ + Compute the outer product of two vectors. + + Given two vectors, ``a = [a0, a1, ..., aM]`` and ``b = [b0, b1, ..., bN]``, + the outer product is:: + + [[a0*b0 a0*b1 ... a0*bN ] + [a1*b0 . + [ ... . + [aM*b0 aM*bN ]] + + Parameters + ---------- + a : (M,) array_like + First input vector. Input is flattened if not already 1-dimensional. + b : (N,) array_like + Second input vector. Input is flattened if not already 1-dimensional. + out : (M, N) ndarray, optional + A location where the result is stored. If its dtype is not what would + be expected from this operation, then the result will be (unsafely) + cast to `out`. + + Returns + ------- + output : (M, N) ndarray + ``output[i, j] = a[i] * b[j]`` + If `out` is given, then it is returned. + + See Also + -------- + numpy.outer + + Availability + -------- + Multiple GPUs, Multiple CPUs + """ + return multiply( + a.ravel()[:, np.newaxis], b.ravel()[np.newaxis, :], out=out + ) + + +@add_boilerplate("a", "b") +def tensordot( + a: ndarray, + b: ndarray, + axes: AxesPairLike = 2, + out: Optional[ndarray] = None, +) -> ndarray: + """ + Compute tensor dot product along specified axes. + + Given two tensors, `a` and `b`, and an array_like object containing + two array_like objects, ``(a_axes, b_axes)``, sum the products of + `a`'s and `b`'s elements (components) over the axes specified by + ``a_axes`` and ``b_axes``. The third argument can be a single non-negative + integer_like scalar, ``N``; if it is such, then the last ``N`` dimensions + of `a` and the first ``N`` dimensions of `b` are summed over. + + Parameters + ---------- + a, b : array_like + Tensors to "dot". + + axes : int or array_like + * integer_like + If an int N, sum over the last N axes of `a` and the first N axes + of `b` in order. + * (2,) array_like + Or, a list of axes to be summed over, first sequence applying to `a`, + second to `b`. Both elements array_like must be of the same length. + out : ndarray, optional + Output argument. This must have the exact shape that would be returned + if it was not present. If its dtype is not what would be expected from + this operation, then the result will be (unsafely) cast to `out`. + + Returns + ------- + output : ndarray + The tensor dot product of the inputs. If `out` is given, then it is + returned. + + Notes + ----- + The cuNumeric implementation is a little more liberal than NumPy in terms + of allowed broadcasting, e.g. ``tensordot(ones((3,1)), ones((1,4)))`` is + allowed. + + Except for the inner-product case, only floating-point types are supported. + + See Also + -------- + numpy.tensordot + + Availability + -------- + Multiple GPUs, Multiple CPUs + """ + (a_modes, b_modes, out_modes) = tensordot_modes(a.ndim, b.ndim, axes) + + return _contract( + a_modes, + b_modes, + out_modes, + a, + b, + out=out, + casting="unsafe", + ) + + +# Trivial multi-tensor contraction strategy: contract in input order +class NullOptimizer(oe.paths.PathOptimizer): # type: ignore [misc,no-any-unimported] # noqa + def __call__( + self, + inputs: list[set[str]], + outputs: set[str], + size_dict: dict[str, int], + memory_limit: Union[int, None] = None, + ) -> list[tuple[int, int]]: + return [(0, 1)] + [(0, -1)] * (len(inputs) - 2) + + +def _maybe_cast_input( + arr: ndarray, to_dtype: np.dtype[Any], casting: CastingKind +) -> ndarray: + if arr.dtype == to_dtype: + return arr + if not np.can_cast(arr.dtype, to_dtype, casting=casting): + raise TypeError( + f"Cannot cast input array of type {arr.dtype} to {to_dtype} with " + f"casting rule '{casting}'" + ) + return arr.astype(to_dtype) + + +# Generalized tensor contraction +def _contract( + a_modes: list[str], + b_modes: list[str], + out_modes: list[str], + a: ndarray, + b: Optional[ndarray] = None, + out: Optional[ndarray] = None, + casting: CastingKind = "same_kind", + dtype: Optional[np.dtype[Any]] = None, +) -> ndarray: + # Sanity checks + if len(a_modes) != a.ndim: + raise ValueError( + f"Expected {len(a_modes)}-d input array but got {a.ndim}-d" + ) + + if b is None: + if len(b_modes) != 0: + raise ValueError("Missing input array") + elif len(b_modes) != b.ndim: + raise ValueError( + f"Expected {len(b_modes)}-d input array but got {b.ndim}-d" + ) + + if out is not None and len(out_modes) != out.ndim: + raise ValueError( + f"Expected {len(out_modes)}-d output array but got {out.ndim}-d" + ) + + if len(set(out_modes)) != len(out_modes): + raise ValueError("Duplicate mode labels on output") + + if len(set(out_modes) - set(a_modes) - set(b_modes)) > 0: + raise ValueError("Unknown mode labels on output") + + makes_view = b is None and len(a_modes) == len(out_modes) + if dtype is not None and not makes_view: + c_dtype = dtype + elif out is not None: + c_dtype = out.dtype + elif b is None: + c_dtype = a.dtype + else: + c_dtype = ndarray.find_common_type(a, b) + + a = _maybe_cast_input(a, c_dtype, casting) + + if b is not None: + b = _maybe_cast_input(b, c_dtype, casting) + + out_dtype = out.dtype if out is not None else c_dtype + + # Handle duplicate modes on inputs + c_a_modes = Counter(a_modes) + for mode, count in c_a_modes.items(): + if count > 1: + axes = [i for (i, m) in enumerate(a_modes) if m == mode] + a = a._diag_helper(axes=axes) + # diagonal is stored on last axis + a_modes = [m for m in a_modes if m != mode] + [mode] + c_b_modes = Counter(b_modes) + for mode, count in c_b_modes.items(): + if count > 1: + axes = [i for (i, m) in enumerate(b_modes) if m == mode] + b = b._diag_helper(axes=axes) # type: ignore [union-attr] + # diagonal is stored on last axis + b_modes = [m for m in b_modes if m != mode] + [mode] + + # Drop modes corresponding to singleton dimensions. This handles cases of + # broadcasting. + for dim in reversed(range(a.ndim)): + if a.shape[dim] == 1: + a = a.squeeze(dim) + a_modes.pop(dim) + if b is not None: + for dim in reversed(range(b.ndim)): + if b.shape[dim] == 1: + b = b.squeeze(dim) + b_modes.pop(dim) + + # Sum-out modes appearing on one argument, and missing from the result + # TODO: If we supported sum on multiple axes we could do the full sum in a + # single operation, and avoid intermediates. + for dim, mode in reversed(list(enumerate(a_modes))): + if mode not in b_modes and mode not in out_modes: + a_modes.pop(dim) + a = a.sum(axis=dim) + + for dim, mode in reversed(list(enumerate(b_modes))): + if mode not in a_modes and mode not in out_modes: + b_modes.pop(dim) + b = b.sum(axis=dim) # type: ignore [union-attr] + + # Compute extent per mode. No need to consider broadcasting at this stage, + # since it has been handled above. + mode2extent: dict[str, int] = {} + for mode, extent in chain( + zip(a_modes, a.shape), zip(b_modes, b.shape) if b is not None else [] + ): + prev_extent = mode2extent.get(mode) + if prev_extent is not None and extent != prev_extent: + raise ValueError( + f"Incompatible sizes between matched dimensions: {extent} vs " + f"{prev_extent}" + ) + mode2extent[mode] = extent + + # Any modes appearing only on the result must have originally been present + # on one of the operands, but got dropped by the broadcast-handling code. + out_shape = ( + out.shape + if out is not None + else tuple(mode2extent.get(mode, 1) for mode in out_modes) + ) + c_modes = [] + c_shape: NdShape = () + c_bloated_shape: NdShape = () + for mode, extent in zip(out_modes, out_shape): + if mode not in a_modes and mode not in b_modes: + c_bloated_shape += (1,) + else: + assert extent > 1 + c_modes.append(mode) + c_shape += (extent,) + c_bloated_shape += (extent,) + + # Verify output array has the right shape (input arrays can be broadcasted + # up to match the output, but not the other way around). There should be no + # unknown or singleton modes on the result at this point. + for mode, extent in zip(c_modes, c_shape): + prev_extent = mode2extent[mode] + assert prev_extent != 1 + if extent != prev_extent: + raise ValueError("Wrong shape on output array") + + # Test for fallback to unary case + if b is not None: + if len(a_modes) == 0: + a = a * b + a_modes = b_modes + b = None + b_modes = [] + elif len(b_modes) == 0: + a = a * b + b = None + + if b is None: + # Unary contraction case + assert len(a_modes) == len(c_modes) and set(a_modes) == set(c_modes) + if len(a_modes) == 0: + # NumPy doesn't return a view in this case + c = copy(a) + elif a_modes == c_modes: + c = a + else: + # Shuffle input array according to mode labels + axes = [a_modes.index(mode) for mode in c_modes] + assert _builtin_all(ax >= 0 for ax in axes) + c = a.transpose(axes) + + else: + # Binary contraction case + # Create result array, if output array can't be directly targeted + if out is not None and out_dtype == c_dtype and out_shape == c_shape: + c = out + else: + c = ndarray( + shape=c_shape, + dtype=c_dtype, + inputs=(a, b), + ) + # Perform operation + c._thunk.contract( + c_modes, + a._thunk, + a_modes, + b._thunk, + b_modes, + mode2extent, + ) + + # Postprocess result before returning + if out is c: + # We already decided above to use the output array directly + return out + if out_dtype != c_dtype or out_shape != c_bloated_shape: + # We need to broadcast the result of the contraction or switch types + # before returning + if not np.can_cast(c_dtype, out_dtype, casting=casting): + raise TypeError( + f"Cannot cast intermediate result array of type {c_dtype} " + f"into output array of type {out_dtype} with casting rule " + f"'{casting}'" + ) + if out is None: + out = ndarray( + shape=out_shape, + dtype=out_dtype, + inputs=(c,), + ) + out[...] = c.reshape(c_bloated_shape) + return out + if out_shape != c_shape: + # We need to add missing dimensions, but they are all of size 1, so + # we don't need to broadcast + assert c_bloated_shape == out_shape + if out is None: + return c.reshape(out_shape) + else: + out[...] = c.reshape(out_shape) + return out + if out is not None: + # The output and result arrays are fully compatible, but we still + # need to copy + out[...] = c + return out + return c + + +def einsum( + expr: str, + *operands: ndarray, + out: Optional[ndarray] = None, + dtype: Optional[np.dtype[Any]] = None, + casting: CastingKind = "safe", + optimize: Union[bool, Literal["greedy", "optimal"]] = True, +) -> ndarray: + """ + Evaluates the Einstein summation convention on the operands. + + Using the Einstein summation convention, many common multi-dimensional, + linear algebraic array operations can be represented in a simple fashion. + In *implicit* mode `einsum` computes these values. + + In *explicit* mode, `einsum` provides further flexibility to compute + other array operations that might not be considered classical Einstein + summation operations, by disabling, or forcing summation over specified + subscript labels. + + Parameters + ---------- + subscripts : str + Specifies the subscripts for summation as comma separated list of + subscript labels. An implicit (classical Einstein summation) + calculation is performed unless the explicit indicator '->' is + included as well as subscript labels of the precise output form. + operands : list[array_like] + These are the arrays for the operation. + out : ndarray, optional + If provided, the calculation is done into this array. + dtype : data-type, optional + If provided, forces the calculation to use the data type specified. + Note that you may have to also give a more liberal `casting` + parameter to allow the conversions. Default is None. + casting : ``{'no', 'equiv', 'safe', 'same_kind', 'unsafe'}``, optional + Controls what kind of data casting may occur. + + * 'no' means the data types should not be cast at all. + * 'equiv' means only byte-order changes are allowed. + * 'safe' means only casts which can preserve values are allowed. + * 'same_kind' means only safe casts or casts within a kind, + like float64 to float32, are allowed. + * 'unsafe' means any data conversions may be done. + + Default is 'safe'. + optimize : ``{False, True, 'greedy', 'optimal'}``, optional + Controls if intermediate optimization should occur. If False then + arrays will be contracted in input order, one at a time. True (the + default) will use the 'greedy' algorithm. See ``cunumeric.einsum_path`` + for more information on the available optimization algorithms. + + Returns + ------- + output : ndarray + The calculation based on the Einstein summation convention. + + Notes + ----- + For most expressions, only floating-point types are supported. + + See Also + -------- + numpy.einsum + + Availability + -------- + Multiple GPUs, Multiple CPUs + """ + operands_list = [convert_to_cunumeric_ndarray(op) for op in operands] + + if out is not None: + out = convert_to_cunumeric_ndarray(out, share=True) + + if optimize is True: + optimize = "greedy" + elif optimize is False: + optimize = NullOptimizer() + + # This call normalizes the expression (adds the output part if it's + # missing, expands '...') and checks for some errors (mismatch on number + # of dimensions between operand and expression, wrong number of operands, + # unknown modes on output, a mode appearing under two different + # non-singleton extents). + computed_operands, contractions = oe.contract_path( + expr, *operands_list, einsum_call=True, optimize=optimize + ) + for indices, _, sub_expr, _, _ in contractions: + assert len(indices) == 1 or len(indices) == 2 + a = computed_operands.pop(indices[0]) + b = computed_operands.pop(indices[1]) if len(indices) == 2 else None + if b is None: + m = re.match(r"([a-zA-Z]*)->([a-zA-Z]*)", sub_expr) + if m is None: + raise NotImplementedError("Non-alphabetic mode labels") + a_modes = list(m.group(1)) + b_modes = [] + out_modes = list(m.group(2)) + else: + m = re.match(r"([a-zA-Z]*),([a-zA-Z]*)->([a-zA-Z]*)", sub_expr) + if m is None: + raise NotImplementedError("Non-alphabetic mode labels") + a_modes = list(m.group(1)) + b_modes = list(m.group(2)) + out_modes = list(m.group(3)) + sub_result = _contract( + a_modes, + b_modes, + out_modes, + a, + b, + out=(out if len(computed_operands) == 0 else None), + casting=casting, + dtype=dtype, + ) + computed_operands.append(sub_result) + + assert len(computed_operands) == 1 + return computed_operands[0] + + +def einsum_path( + expr: str, + *operands: ndarray, + optimize: Union[bool, list[Any], tuple[Any, ...], str] = "greedy", +) -> tuple[list[Union[str, int]], str]: + """ + Evaluates the lowest cost contraction order for an einsum expression by + considering the creation of intermediate arrays. + + Parameters + ---------- + expr : str + Specifies the subscripts for summation. + *operands : Sequence[array_like] + These are the arrays for the operation. + optimize : ``{bool, list, tuple, 'greedy', 'optimal'}`` + Choose the type of path. If a tuple is provided, the second argument is + assumed to be the maximum intermediate size created. If only a single + argument is provided the largest input or output array size is used + as a maximum intermediate size. + + * if a list is given that starts with ``einsum_path``, uses this as the + contraction path + * if False no optimization is taken + * if True defaults to the 'greedy' algorithm + * 'optimal' An algorithm that combinatorially explores all possible + ways of contracting the listed tensors and chooses the least costly + path. Scales exponentially with the number of terms in the + contraction. + * 'greedy' An algorithm that chooses the best pair contraction + at each step. Effectively, this algorithm searches the largest inner, + Hadamard, and then outer products at each step. Scales cubically with + the number of terms in the contraction. Equivalent to the 'optimal' + path for most contractions. + + Default is 'greedy'. + + Returns + ------- + path : list[Tuple[int,...]] + A list representation of the einsum path. + string_repr : str + A printable representation of the einsum path. + + Notes + ----- + The resulting path indicates which terms of the input contraction should be + contracted first, the result of this contraction is then appended to the + end of the contraction list. This list can then be iterated over until all + intermediate contractions are complete. + + See Also + -------- + numpy.einsum_path + + Availability + -------- + Multiple GPUs, Multiple CPUs + """ + computed_operands = [convert_to_cunumeric_ndarray(op) for op in operands] + memory_limit = _builtin_max(op.size for op in computed_operands) + if isinstance(optimize, tuple): + if len(optimize) != 2: + raise ValueError("einsum_path expects optimize tuples of size 2") + optimize, memory_limit = optimize + if optimize is True: + optimize = "greedy" + elif optimize is False: + optimize = [tuple(range(len(computed_operands)))] + elif optimize in ["greedy", "optimal"]: + pass + elif ( + isinstance(optimize, list) + and len(optimize) > 1 + and optimize[0] == "einsum_path" + ): + optimize = optimize[1:] + else: + raise ValueError( + f"einsum_path: unexpected value for optimize: {optimize}" + ) + path, info = oe.contract_path( + expr, *computed_operands, optimize=optimize, memory_limit=memory_limit + ) + return ["einsum_path"] + path, info + + +@add_boilerplate("a") +def trace( + a: ndarray, + offset: int = 0, + axis1: Optional[int] = None, + axis2: Optional[int] = None, + dtype: Optional[np.dtype[Any]] = None, + out: Optional[ndarray] = None, +) -> ndarray: + """ + Return the sum along diagonals of the array. + + If a is 2-D, the sum along its diagonal with the given offset is + returned, i.e., the sum of elements a[i,i+offset] for all i. + If a has more than two dimensions, then the axes specified by axis1 + and axis2 are used to determine the 2-D sub-arrays whose traces + are returned. The shape of the resulting array is the same as that + of a with axis1 and axis2 removed. + + Parameters + ---------- + a : array_like + Input array, from which the diagonals are taken. + offset : int, optional + Offset of the diagonal from the main diagonal. Can be both + positive and negative. Defaults to 0. + axis1, axis2 : int, optional + Axes to be used as the first and second axis of the 2-D sub-arrays + from which the diagonals should be taken. Defaults are the + first two axes of a. + dtype : data-type, optional + Determines the data-type of the returned array and of the + accumulator where the elements are summed. If dtype has the value + None and a is of integer type of precision less than the default + integer precision, then the default integer precision is used. + Otherwise, the precision is the same as that of a. + + out : ndarray, optional + Array into which the output is placed. Its type is preserved and + it must be of the right shape to hold the output. + + Returns + ------- + sum_along_diagonals : ndarray + If a is 2-D, the sum along the diagonal is returned. If a has + larger dimensions, then an array of sums along diagonals is returned. + + Raises + ------ + ValueError + If the dimension of `a` is less than 2. + + See Also + -------- + numpy.diagonal + + Availability + -------- + Multiple GPUs, Multiple CPUs + """ + return a.trace( + offset=offset, axis1=axis1, axis2=axis2, dtype=dtype, out=out + ) diff --git a/cunumeric/_module/logic_comparison.py b/cunumeric/_module/logic_comparison.py new file mode 100644 index 0000000000..5243a99a15 --- /dev/null +++ b/cunumeric/_module/logic_comparison.py @@ -0,0 +1,197 @@ +# Copyright 2024 NVIDIA Corporation +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +from __future__ import annotations + +from typing import Union + +import numpy as np +from legate.core import Scalar, types as ty + +from ..array import add_boilerplate, ndarray +from ..config import BinaryOpCode +from .creation_shape import empty + + +@add_boilerplate("a", "b") +def allclose( + a: ndarray, + b: ndarray, + rtol: float = 1e-5, + atol: float = 1e-8, + equal_nan: bool = False, +) -> ndarray: + """ + + Returns True if two arrays are element-wise equal within a tolerance. + + The tolerance values are positive, typically very small numbers. The + relative difference (`rtol` * abs(`b`)) and the absolute difference + `atol` are added together to compare against the absolute difference + between `a` and `b`. + + NaNs are treated as equal if they are in the same place and if + ``equal_nan=True``. Infs are treated as equal if they are in the same + place and of the same sign in both arrays. + + Parameters + ---------- + a, b : array_like + Input arrays to compare. + rtol : float + The relative tolerance parameter (see Notes). + atol : float + The absolute tolerance parameter (see Notes). + equal_nan : bool + Whether to compare NaN's as equal. If True, NaN's in `a` will be + considered equal to NaN's in `b` in the output array. + + Returns + ------- + allclose : ndarray scalar + Returns True if the two arrays are equal within the given + tolerance; False otherwise. + + Notes + ----- + If the following equation is element-wise True, then allclose returns + True. + + absolute(`a` - `b`) <= (`atol` + `rtol` * absolute(`b`)) + + See Also + -------- + numpy.allclose + + Availability + -------- + Multiple GPUs, Multiple CPUs + """ + if equal_nan: + raise NotImplementedError( + "cuNumeric does not support `equal_nan` yet for allclose" + ) + args = (Scalar(rtol, ty.float64), Scalar(atol, ty.float64)) + return ndarray._perform_binary_reduction( + BinaryOpCode.ISCLOSE, + a, + b, + dtype=np.dtype(bool), + extra_args=args, + ) + + +@add_boilerplate("a", "b") +def isclose( + a: ndarray, + b: ndarray, + rtol: float = 1e-5, + atol: float = 1e-8, + equal_nan: bool = False, +) -> ndarray: + """ + + Returns a boolean array where two arrays are element-wise equal within a + tolerance. + + Parameters + ---------- + a, b : array_like + Input arrays to compare. + rtol : float + The relative tolerance parameter (see Notes). + atol : float + The absolute tolerance parameter (see Notes). + equal_nan : bool + Whether to compare NaN's as equal. If True, NaN's in `a` will be + considered equal to NaN's in `b` in the output array. + + Returns + ------- + y : array_like + Returns a boolean array of where `a` and `b` are equal within the + given tolerance. If both `a` and `b` are scalars, returns a single + boolean value. + + Notes + ----- + For finite values, isclose uses the following equation to test whether + two floating point values are equivalent. + + absolute(`a` - `b`) <= (`atol` + `rtol` * absolute(`b`)) + + See Also + -------- + numpy.isclose + + Availability + -------- + Multiple GPUs, Multiple CPUs + """ + if equal_nan: + raise NotImplementedError( + "cuNumeric does not support `equal_nan` yet for isclose" + ) + + out_shape = np.broadcast_shapes(a.shape, b.shape) + out = empty(out_shape, dtype=bool) + + common_type = ndarray.find_common_type(a, b) + a = a.astype(common_type) + b = b.astype(common_type) + + out._thunk.isclose(a._thunk, b._thunk, rtol, atol, equal_nan) + return out + + +@add_boilerplate("a1", "a2") +def array_equal( + a1: ndarray, a2: ndarray, equal_nan: bool = False +) -> Union[bool, ndarray]: + """ + + True if two arrays have the same shape and elements, False otherwise. + + Parameters + ---------- + a1, a2 : array_like + Input arrays. + equal_nan : bool + Whether to compare NaN's as equal. If the dtype of a1 and a2 is + complex, values will be considered equal if either the real or the + imaginary component of a given value is ``nan``. + + Returns + ------- + b : ndarray scalar + Returns True if the arrays are equal. + + See Also + -------- + numpy.array_equal + + Availability + -------- + Multiple GPUs, Multiple CPUs + """ + if equal_nan: + raise NotImplementedError( + "cuNumeric does not support `equal_nan` yet for `array_equal`" + ) + + if a1.shape != a2.shape: + return False + return ndarray._perform_binary_reduction( + BinaryOpCode.EQUAL, a1, a2, dtype=np.dtype(np.bool_) + ) diff --git a/cunumeric/_module/logic_truth.py b/cunumeric/_module/logic_truth.py new file mode 100644 index 0000000000..1f4f891af7 --- /dev/null +++ b/cunumeric/_module/logic_truth.py @@ -0,0 +1,140 @@ +# Copyright 2024 NVIDIA Corporation +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +from __future__ import annotations + +from typing import TYPE_CHECKING, Optional, Union + +from ..array import add_boilerplate + +if TYPE_CHECKING: + from ..array import ndarray + + +@add_boilerplate("a") +def all( + a: ndarray, + axis: Optional[Union[int, tuple[int, ...]]] = None, + out: Optional[ndarray] = None, + keepdims: bool = False, + where: Optional[ndarray] = None, +) -> ndarray: + """ + Test whether all array elements along a given axis evaluate to True. + + Parameters + ---------- + a : array_like + Input array or object that can be converted to an array. + axis : None or int or tuple[int], optional + Axis or axes along which a logical AND reduction is performed. + The default (``axis=None``) is to perform a logical AND over all + the dimensions of the input array. `axis` may be negative, in + which case it counts from the last to the first axis. + + If this is a tuple of ints, a reduction is performed on multiple + axes, instead of a single axis or all the axes as before. + out : ndarray, optional + Alternate output array in which to place the result. + It must have the same shape as the expected output and its + type is preserved (e.g., if ``dtype(out)`` is float, the result + will consist of 0.0's and 1.0's). See `ufuncs-output-type` for more + details. + + keepdims : bool, optional + If this is set to True, the axes which are reduced are left + in the result as dimensions with size one. With this option, + the result will broadcast correctly against the input array. + + If the default value is passed, then `keepdims` will not be + passed through to the `all` method of sub-classes of + `ndarray`, however any non-default value will be. If the + sub-class' method does not implement `keepdims` any + exceptions will be raised. + + Returns + ------- + all : ndarray, bool + A new boolean or array is returned unless `out` is specified, + in which case a reference to `out` is returned. + + See Also + -------- + numpy.all + + Availability + -------- + Multiple GPUs, Multiple CPUs + """ + return a.all(axis=axis, out=out, keepdims=keepdims, where=where) + + +@add_boilerplate("a") +def any( + a: ndarray, + axis: Optional[Union[int, tuple[int, ...]]] = None, + out: Optional[ndarray] = None, + keepdims: bool = False, + where: Optional[ndarray] = None, +) -> ndarray: + """ + Test whether any array element along a given axis evaluates to True. + + Returns single boolean unless `axis` is not ``None`` + + Parameters + ---------- + a : array_like + Input array or object that can be converted to an array. + axis : None or int or tuple[int], optional + Axis or axes along which a logical OR reduction is performed. + The default (``axis=None``) is to perform a logical OR over all + the dimensions of the input array. `axis` may be negative, in + which case it counts from the last to the first axis. + + If this is a tuple of ints, a reduction is performed on multiple + axes, instead of a single axis or all the axes as before. + out : ndarray, optional + Alternate output array in which to place the result. It must have + the same shape as the expected output and its type is preserved + (e.g., if it is of type float, then it will remain so, returning + 1.0 for True and 0.0 for False, regardless of the type of `a`). + See `ufuncs-output-type` for more details. + + keepdims : bool, optional + If this is set to True, the axes which are reduced are left + in the result as dimensions with size one. With this option, + the result will broadcast correctly against the input array. + + If the default value is passed, then `keepdims` will not be + passed through to the `any` method of sub-classes of + `ndarray`, however any non-default value will be. If the + sub-class' method does not implement `keepdims` any + exceptions will be raised. + + Returns + ------- + any : bool or ndarray + A new boolean or `ndarray` is returned unless `out` is specified, + in which case a reference to `out` is returned. + + See Also + -------- + numpy.any + + Availability + -------- + Multiple GPUs, Multiple CPUs + """ + return a.any(axis=axis, out=out, keepdims=keepdims, where=where) diff --git a/cunumeric/_module/math_complex.py b/cunumeric/_module/math_complex.py new file mode 100644 index 0000000000..e7f71156a3 --- /dev/null +++ b/cunumeric/_module/math_complex.py @@ -0,0 +1,79 @@ +# Copyright 2024 NVIDIA Corporation +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +from __future__ import annotations + +from typing import TYPE_CHECKING + +from ..array import add_boilerplate + +if TYPE_CHECKING: + from ..array import ndarray + + +@add_boilerplate("val") +def real(val: ndarray) -> ndarray: + """ + Return the real part of the complex argument. + + Parameters + ---------- + val : array_like + Input array. + + Returns + ------- + out : ndarray or scalar + The real component of the complex argument. If `val` is real, the type + of `val` is used for the output. If `val` has complex elements, the + returned type is float. + + See Also + -------- + numpy.real + + Availability + -------- + Multiple GPUs, Multiple CPUs + """ + return val.real + + +@add_boilerplate("val") +def imag(val: ndarray) -> ndarray: + """ + + Return the imaginary part of the complex argument. + + Parameters + ---------- + val : array_like + Input array. + + Returns + ------- + out : ndarray or scalar + The imaginary component of the complex argument. If `val` is real, + the type of `val` is used for the output. If `val` has complex + elements, the returned type is float. + + See Also + -------- + numpy.imag + + Availability + -------- + Multiple GPUs, Multiple CPUs + """ + return val.imag diff --git a/cunumeric/_module/math_extrema.py b/cunumeric/_module/math_extrema.py new file mode 100644 index 0000000000..e2cf7d05c0 --- /dev/null +++ b/cunumeric/_module/math_extrema.py @@ -0,0 +1,179 @@ +# Copyright 2024 NVIDIA Corporation +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +from __future__ import annotations + +from typing import TYPE_CHECKING, Any, Optional, Union + +import numpy as np + +from .._ufunc.comparison import maximum, minimum +from ..array import add_boilerplate + +if TYPE_CHECKING: + from ..array import ndarray + + +@add_boilerplate("a") +def amax( + a: ndarray, + axis: Optional[Union[int, tuple[int, ...]]] = None, + dtype: Optional[np.dtype[Any]] = None, + out: Optional[ndarray] = None, + keepdims: bool = False, + initial: Optional[Union[int, float]] = None, + where: Optional[ndarray] = None, +) -> ndarray: + """ + + Return the maximum of an array or maximum along an axis. + + Parameters + ---------- + a : array_like + Input data. + axis : None or int or tuple[int], optional + Axis or axes along which to operate. By default, flattened input is + used. + + If this is a tuple of ints, the maximum is selected over multiple axes, + instead of a single axis or all the axes as before. + out : ndarray, optional + Alternative output array in which to place the result. Must + be of the same shape and buffer length as the expected output. + See `ufuncs-output-type` for more details. + + keepdims : bool, optional + If this is set to True, the axes which are reduced are left + in the result as dimensions with size one. With this option, + the result will broadcast correctly against the input array. + + If the default value is passed, then `keepdims` will not be + passed through to the `amax` method of sub-classes of + `ndarray`, however any non-default value will be. If the + sub-class' method does not implement `keepdims` any + exceptions will be raised. + + initial : scalar, optional + The minimum value of an output element. Must be present to allow + computation on empty slice. See `~cunumeric.ufunc.reduce` for details. + + where : array_like[bool], optional + Elements to compare for the maximum. See `~cunumeric.ufunc.reduce` + for details. + + Returns + ------- + amax : ndarray or scalar + Maximum of `a`. If `axis` is None, the result is a scalar value. + If `axis` is given, the result is an array of dimension + ``a.ndim - 1``. + + See Also + -------- + numpy.amax + + Availability + -------- + Multiple GPUs, Multiple CPUs + """ + return maximum.reduce( + a, + axis=axis, + dtype=dtype, + out=out, + keepdims=keepdims, + initial=initial, + where=where, + ) + + +max = amax + + +@add_boilerplate("a") +def amin( + a: ndarray, + axis: Optional[Union[int, tuple[int, ...]]] = None, + dtype: Optional[np.dtype[Any]] = None, + out: Optional[ndarray] = None, + keepdims: bool = False, + initial: Optional[Union[int, float]] = None, + where: Optional[ndarray] = None, +) -> ndarray: + """ + + Return the minimum of an array or minimum along an axis. + + Parameters + ---------- + a : array_like + Input data. + axis : None or int or tuple[int], optional + Axis or axes along which to operate. By default, flattened input is + used. + + If this is a tuple of ints, the minimum is selected over multiple axes, + instead of a single axis or all the axes as before. + out : ndarray, optional + Alternative output array in which to place the result. Must + be of the same shape and buffer length as the expected output. + See `ufuncs-output-type` for more details. + + keepdims : bool, optional + If this is set to True, the axes which are reduced are left + in the result as dimensions with size one. With this option, + the result will broadcast correctly against the input array. + + If the default value is passed, then `keepdims` will not be + passed through to the `amin` method of sub-classes of + `ndarray`, however any non-default value will be. If the + sub-class' method does not implement `keepdims` any + exceptions will be raised. + + initial : scalar, optional + The maximum value of an output element. Must be present to allow + computation on empty slice. See `~cunumeric.ufunc.reduce` for details. + + where : array_like[bool], optional + Elements to compare for the minimum. See `~cunumeric.ufunc.reduce` + for details. + + Returns + ------- + amin : ndarray or scalar + Minimum of `a`. If `axis` is None, the result is a scalar value. + If `axis` is given, the result is an array of dimension + ``a.ndim - 1``. + + See Also + -------- + numpy.amin + + Availability + -------- + Multiple GPUs, Multiple CPUs + """ + return minimum.reduce( + a, + axis=axis, + dtype=dtype, + out=out, + keepdims=keepdims, + initial=initial, + where=where, + ) + + +min = amin diff --git a/cunumeric/_module/math_misc.py b/cunumeric/_module/math_misc.py new file mode 100644 index 0000000000..47a72bf5d9 --- /dev/null +++ b/cunumeric/_module/math_misc.py @@ -0,0 +1,149 @@ +# Copyright 2024 NVIDIA Corporation +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +from __future__ import annotations + +from typing import TYPE_CHECKING, Any, Union + +from ..array import add_boilerplate, ndarray + +if TYPE_CHECKING: + import numpy.typing as npt + + from ..types import ConvolveMode + + +@add_boilerplate("a", "v") +def convolve(a: ndarray, v: ndarray, mode: ConvolveMode = "full") -> ndarray: + """ + + Returns the discrete, linear convolution of two ndarrays. + + If `a` and `v` are both 1-D and `v` is longer than `a`, the two are + swapped before computation. For N-D cases, the arguments are never swapped. + + Parameters + ---------- + a : (N,) array_like + First input ndarray. + v : (M,) array_like + Second input ndarray. + mode : ``{'full', 'valid', 'same'}``, optional + 'same': + The output is the same size as `a`, centered with respect to + the 'full' output. (default) + + 'full': + The output is the full discrete linear convolution of the inputs. + + 'valid': + The output consists only of those elements that do not + rely on the zero-padding. In 'valid' mode, either `a` or `v` + must be at least as large as the other in every dimension. + + Returns + ------- + out : ndarray + Discrete, linear convolution of `a` and `v`. + + See Also + -------- + numpy.convolve + + Notes + ----- + The current implementation only supports the 'same' mode. + + Unlike `numpy.convolve`, `cunumeric.convolve` supports N-dimensional + inputs, but it follows NumPy's behavior for 1-D inputs. + + Availability + -------- + Multiple GPUs, Multiple CPUs + """ + if mode != "same": + raise NotImplementedError("Need to implement other convolution modes") + + if a.ndim != v.ndim: + raise RuntimeError("Arrays should have the same dimensions") + elif a.ndim > 3: + raise NotImplementedError(f"{a.ndim}-D arrays are not yet supported") + + if a.ndim == 1 and a.size < v.size: + v, a = a, v + + if a.dtype != v.dtype: + v = v.astype(a.dtype) + out = ndarray( + shape=a.shape, + dtype=a.dtype, + inputs=(a, v), + ) + a._thunk.convolve(v._thunk, out._thunk, mode) + return out + + +@add_boilerplate("a") +def clip( + a: ndarray, + a_min: Union[int, float, npt.ArrayLike, None], + a_max: Union[int, float, npt.ArrayLike, None], + out: Union[npt.NDArray[Any], ndarray, None] = None, +) -> ndarray: + """ + + Clip (limit) the values in an array. + + Given an interval, values outside the interval are clipped to + the interval edges. For example, if an interval of ``[0, 1]`` + is specified, values smaller than 0 become 0, and values larger + than 1 become 1. + + Parameters + ---------- + a : array_like + Array containing elements to clip. + a_min : scalar or array_like or None + Minimum value. If None, clipping is not performed on lower + interval edge. Not more than one of `a_min` and `a_max` may be + None. + a_max : scalar or array_like or None + Maximum value. If None, clipping is not performed on upper + interval edge. Not more than one of `a_min` and `a_max` may be + None. If `a_min` or `a_max` are array_like, then the three + arrays will be broadcasted to match their shapes. + out : ndarray, optional + The results will be placed in this array. It may be the input + array for in-place clipping. `out` must be of the right shape + to hold the output. Its type is preserved. + **kwargs + For other keyword-only arguments, see the + :ref:`ufunc docs `. + + Returns + ------- + clipped_array : ndarray + An array with the elements of `a`, but where values + < `a_min` are replaced with `a_min`, and those > `a_max` + with `a_max`. + + See Also + -------- + numpy.clip + + Availability + -------- + Multiple GPUs, Multiple CPUs + """ + return a.clip(a_min, a_max, out=out) diff --git a/cunumeric/_module/math_sum_prod_diff.py b/cunumeric/_module/math_sum_prod_diff.py new file mode 100644 index 0000000000..bd75c73631 --- /dev/null +++ b/cunumeric/_module/math_sum_prod_diff.py @@ -0,0 +1,940 @@ +# Copyright 2024 NVIDIA Corporation +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +from __future__ import annotations + +from typing import Any, Optional, Union + +import numpy as np + +from .._ufunc.floating import isnan +from .._ufunc.math import add, multiply +from .._unary_red_utils import get_non_nan_unary_red_code +from ..array import add_boilerplate, ndarray +from ..config import ScanCode, UnaryRedCode +from ..settings import settings as cunumeric_settings +from .indexing import putmask +from .logic_truth import all, any + + +@add_boilerplate("a") +def prod( + a: ndarray, + axis: Optional[Union[int, tuple[int, ...]]] = None, + dtype: Optional[np.dtype[Any]] = None, + out: Optional[ndarray] = None, + keepdims: bool = False, + initial: Optional[Union[int, float]] = None, + where: Optional[ndarray] = None, +) -> ndarray: + """ + + Return the product of array elements over a given axis. + + Parameters + ---------- + a : array_like + Input data. + axis : None or int or tuple[int], optional + Axis or axes along which a product is performed. The default, + axis=None, will calculate the product of all the elements in the + input array. If axis is negative it counts from the last to the + first axis. + + If axis is a tuple of ints, a product is performed on all of the + axes specified in the tuple instead of a single axis or all the + axes as before. + dtype : data-type, optional + The type of the returned array, as well as of the accumulator in + which the elements are multiplied. The dtype of `a` is used by + default unless `a` has an integer dtype of less precision than the + default platform integer. In that case, if `a` is signed then the + platform integer is used while if `a` is unsigned then an unsigned + integer of the same precision as the platform integer is used. + out : ndarray, optional + Alternative output array in which to place the result. It must have + the same shape as the expected output, but the type of the output + values will be cast if necessary. + keepdims : bool, optional + If this is set to True, the axes which are reduced are left in the + result as dimensions with size one. With this option, the result + will broadcast correctly against the input array. + + If the default value is passed, then `keepdims` will not be + passed through to the `prod` method of sub-classes of + `ndarray`, however any non-default value will be. If the + sub-class' method does not implement `keepdims` any + exceptions will be raised. + initial : scalar, optional + The starting value for this product. See `~cunumeric.ufunc.reduce` for + details. + + where : array_like[bool], optional + Elements to include in the product. See `~cunumeric.ufunc.reduce` for + details. + + Returns + ------- + product_along_axis : ndarray, see `dtype` parameter above. + An array shaped as `a` but with the specified axis removed. + Returns a reference to `out` if specified. + + See Also + -------- + numpy.prod + + Availability + -------- + Multiple GPUs, Multiple CPUs + """ + return multiply.reduce( + a, + axis=axis, + dtype=dtype, + out=out, + keepdims=keepdims, + initial=initial, + where=where, + ) + + +@add_boilerplate("a") +def sum( + a: ndarray, + axis: Optional[Union[int, tuple[int, ...]]] = None, + dtype: Optional[np.dtype[Any]] = None, + out: Optional[ndarray] = None, + keepdims: bool = False, + initial: Optional[Union[int, float]] = None, + where: Optional[ndarray] = None, +) -> ndarray: + """ + + Sum of array elements over a given axis. + + Parameters + ---------- + a : array_like + Elements to sum. + axis : None or int or tuple[int], optional + Axis or axes along which a sum is performed. The default, + axis=None, will sum all of the elements of the input array. If + axis is negative it counts from the last to the first axis. + + If axis is a tuple of ints, a sum is performed on all of the axes + specified in the tuple instead of a single axis or all the axes as + before. + dtype : data-type, optional + The type of the returned array and of the accumulator in which the + elements are summed. The dtype of `a` is used by default unless `a` + has an integer dtype of less precision than the default platform + integer. In that case, if `a` is signed then the platform integer + is used while if `a` is unsigned then an unsigned integer of the + same precision as the platform integer is used. + out : ndarray, optional + Alternative output array in which to place the result. It must have + the same shape as the expected output, but the type of the output + values will be cast if necessary. + keepdims : bool, optional + If this is set to True, the axes which are reduced are left + in the result as dimensions with size one. With this option, + the result will broadcast correctly against the input array. + + If the default value is passed, then `keepdims` will not be + passed through to the `sum` method of sub-classes of + `ndarray`, however any non-default value will be. If the + sub-class' method does not implement `keepdims` any + exceptions will be raised. + initial : scalar, optional + Starting value for the sum. See `~cunumeric.ufunc.reduce` for details. + + where : array_like[bool], optional + Elements to include in the sum. See `~cunumeric.ufunc.reduce` for + details. + + Returns + ------- + sum_along_axis : ndarray + An array with the same shape as `a`, with the specified + axis removed. If `a` is a 0-d array, or if `axis` is None, a scalar + is returned. If an output array is specified, a reference to + `out` is returned. + + See Also + -------- + numpy.sum + + Availability + -------- + Multiple GPUs, Multiple CPUs + """ + return add.reduce( + a, + axis=axis, + dtype=dtype, + out=out, + keepdims=keepdims, + initial=initial, + where=where, + ) + + +@add_boilerplate("a") +def cumprod( + a: ndarray, + axis: Optional[int] = None, + dtype: Optional[np.dtype[Any]] = None, + out: Optional[ndarray] = None, +) -> ndarray: + """ + Return the cumulative product of the elements along a given axis. + + Parameters + ---------- + a : array_like + Input array. + + axis : int, optional + Axis along which the cumulative product is computed. The default (None) + is to compute the cumprod over the flattened array. + + dtype : dtype, optional + Type of the returned array and of the accumulator in which the elements + are multiplied. If dtype is not specified, it defaults to the dtype of + a, unless a has an integer dtype with a precision less than that of the + default platform integer. In that case, the default platform integer is + used. + out : ndarray, optional + Alternative output array in which to place the result. It must have the + same shape and buffer length as the expected output but the type will + be cast if necessary. See Output type determination for more details. + + Returns + ------- + cumprod : ndarray + A new array holding the result is returned unless out is specified, in + which case a reference to out is returned. The result has the same size + as a, and the same shape as a if axis is not None or a is a 1-d array. + + See Also + -------- + numpy.cumprod + + Notes + ----- + CuNumeric's parallel implementation may yield different results from NumPy + with floating point and complex types. For example, when boundary values + such as inf occur they may not propagate as expected. Consider the float32 + array ``[3e+37, 1, 100, 0.01]``. NumPy's cumprod will return a result of + ``[3e+37, 3e+37, inf, inf]``. However, cuNumeric might internally partition + the array such that partition 0 has ``[3e+37, 1]`` and partition 1 has + ``[100, 0.01]``, returning the result ``[3e+37, 3e+37, inf, 3e+37]``. + + Availability + -------- + Multiple GPUs, Multiple CPUs + """ + return ndarray._perform_scan( + ScanCode.PROD, + a, + axis=axis, + dtype=dtype, + out=out, + nan_to_identity=False, + ) + + +@add_boilerplate("a") +def cumsum( + a: ndarray, + axis: Optional[int] = None, + dtype: Optional[np.dtype[Any]] = None, + out: Optional[ndarray] = None, +) -> ndarray: + """ + Return the cumulative sum of the elements along a given axis. + + Parameters + ---------- + a : array_like + Input array. + + axis : int, optional + Axis along which the cumulative sum is computed. The default (None) is + to compute the cumsum over the flattened array. + + dtype : dtype, optional + Type of the returned array and of the accumulator in which the elements + are summed. If dtype is not specified, it defaults to the dtype of a, + unless a has an integer dtype with a precision less than that of the + default platform integer. In that case, the default platform integer is + used. + out : ndarray, optional + Alternative output array in which to place the result. It must have the + same shape and buffer length as the expected output but the type will + be cast if necessary. See Output type determination for more details. + + Returns + ------- + cumsum : ndarray. + A new array holding the result is returned unless out is specified, in + which case a reference to out is returned. The result has the same size + as a, and the same shape as a if axis is not None or a is a 1-d array. + + See Also + -------- + numpy.cumsum + + Notes + ----- + CuNumeric's parallel implementation may yield different results from NumPy + with floating point and complex types. For example, when boundary values + such as inf occur they may not propagate as expected. For more explanation + check cunumeric.cumprod. + + Availability + -------- + Multiple GPUs, Multiple CPUs + """ + return ndarray._perform_scan( + ScanCode.SUM, a, axis=axis, dtype=dtype, out=out, nan_to_identity=False + ) + + +@add_boilerplate("a") +def nancumprod( + a: ndarray, + axis: Optional[int] = None, + dtype: Optional[np.dtype[Any]] = None, + out: Optional[ndarray] = None, +) -> ndarray: + """ + Return the cumulative product of the elements along a given axis treating + Not a Numbers (NaNs) as one. The cumulative product does not change when + NaNs are encountered and leading NaNs are replaced by ones. + + Ones are returned for slices that are all-NaN or empty. + + Parameters + ---------- + a : array_like + Input array. + + axis : int, optional + Axis along which the cumulative product is computed. The default (None) + is to compute the nancumprod over the flattened array. + + dtype : dtype, optional + Type of the returned array and of the accumulator in which the elements + are multiplied. If dtype is not specified, it defaults to the dtype of + a, unless a has an integer dtype with a precision less than that of the + default platform integer. In that case, the default platform integer is + used. + out : ndarray, optional + Alternative output array in which to place the result. It must have the + same shape and buffer length as the expected output but the type will + be cast if necessary. See Output type determination for more details. + + Returns + ------- + nancumprod : ndarray. + A new array holding the result is returned unless out is specified, in + which case a reference to out is returned. The result has the same size + as a, and the same shape as a if axis is not None or a is a 1-d array. + + See Also + -------- + numpy.nancumprod + + Notes + ----- + CuNumeric's parallel implementation may yield different results from NumPy + with floating point and complex types. For example, when boundary values + such as inf occur they may not propagate as expected. For more explanation + check cunumeric.cumprod. + + Availability + -------- + Multiple GPUs, Multiple CPUs + """ + return ndarray._perform_scan( + ScanCode.PROD, a, axis=axis, dtype=dtype, out=out, nan_to_identity=True + ) + + +@add_boilerplate("a") +def nancumsum( + a: ndarray, + axis: Optional[int] = None, + dtype: Optional[np.dtype[Any]] = None, + out: Optional[ndarray] = None, +) -> ndarray: + """ + Return the cumulative sum of the elements along a given axis treating Not a + Numbers (NaNs) as zero. The cumulative sum does not change when NaNs are + encountered and leading NaNs are replaced by zeros. + + Zeros are returned for slices that are all-NaN or empty. + + Parameters + ---------- + a : array_like + Input array. + + axis : int, optional + Axis along which the cumulative sum is computed. The default (None) is + to compute the nancumsum over the flattened array. + + dtype : dtype, optional + Type of the returned array and of the accumulator in which the elements + are summed. If dtype is not specified, it defaults to the dtype of a, + unless a has an integer dtype with a precision less than that of the + default platform integer. In that case, the default platform integer is + used. + out : ndarray, optional + Alternative output array in which to place the result. It must have the + same shape and buffer length as the expected output but the type will + be cast if necessary. See Output type determination for more details. + + Returns + ------- + nancumsum : ndarray. + A new array holding the result is returned unless out is specified, in + which case a reference to out is returned. The result has the same size + as a, and the same shape as a if axis is not None or a is a 1-d array. + + See Also + -------- + numpy.nancumsum + + Notes + ----- + CuNumeric's parallel implementation may yield different results from NumPy + with floating point and complex types. For example, when boundary values + such as inf occur they may not propagate as expected. For more explanation + check cunumeric.cumprod. + + Availability + -------- + Multiple GPUs, Multiple CPUs + """ + return ndarray._perform_scan( + ScanCode.SUM, a, axis=axis, dtype=dtype, out=out, nan_to_identity=True + ) + + +@add_boilerplate("a") +def nanargmax( + a: ndarray, + axis: Any = None, + out: Union[ndarray, None] = None, + *, + keepdims: bool = False, +) -> ndarray: + """ + Return the indices of the maximum values in the specified axis ignoring + NaNs. For empty arrays, ValueError is raised. For all-NaN slices, + ValueError is raised only when CUNUMERIC_NUMPY_COMPATIBILITY + environment variable is set, otherwise identity is returned. + + Warning: results cannot be trusted if a slice contains only NaNs + and -Infs. + + Parameters + ---------- + a : array_like + Input array. + axis : int, optional + By default, the index corresponds to the flattened array, otherwise + along the specified axis. + out : ndarray, optional + If provided, the result will be inserted into this array. It should + be of the appropriate shape and dtype. + keepdims : bool, optional + If this is set to True, the axes which are reduced are left + in the result as dimensions with size one. With this option, + the result will broadcast correctly against the array. + + Returns + ------- + index_array : ndarray[int] + Array of indices into the array. It has the same shape as `a.shape` + with the dimension along `axis` removed. + + See Also + -------- + numpy.nanargmin, numpy.nanargmax + + Availability + -------- + Multiple GPUs, Multiple CPUs + """ + + if a.size == 0: + raise ValueError("attempt to get nanargmax of an empty sequence") + + if cunumeric_settings.numpy_compat() and a.dtype.kind == "f": + if any(all(isnan(a), axis=axis)): + raise ValueError("Array/Slice contains only NaNs") + + unary_red_code = get_non_nan_unary_red_code( + a.dtype.kind, UnaryRedCode.NANARGMAX + ) + + return a._perform_unary_reduction( + unary_red_code, + a, + axis=axis, + out=out, + keepdims=keepdims, + res_dtype=np.dtype(np.int64), + ) + + +@add_boilerplate("a") +def nanargmin( + a: ndarray, + axis: Any = None, + out: Union[ndarray, None] = None, + *, + keepdims: bool = False, +) -> ndarray: + """ + Return the indices of the minimum values in the specified axis ignoring + NaNs. For empty arrays, ValueError is raised. For all-NaN slices, + ValueError is raised only when CUNUMERIC_NUMPY_COMPATIBILITY + environment variable is set, otherwise identity is returned. + + Warning: results cannot be trusted if a slice contains only NaNs + and -Infs. + + Parameters + ---------- + a : array_like + Input array. + axis : int, optional + By default, the index corresponds to the flattened array, otherwise + along the specified axis. + out : ndarray, optional + If provided, the result will be inserted into this array. It should + be of the appropriate shape and dtype. + keepdims : bool, optional + If this is set to True, the axes which are reduced are left + in the result as dimensions with size one. With this option, + the result will broadcast correctly against the array. + + Returns + ------- + index_array : ndarray[int] + Array of indices into the array. It has the same shape as `a.shape` + with the dimension along `axis` removed. + + See Also + -------- + numpy.nanargmin, numpy.nanargmax + + Availability + -------- + Multiple GPUs, Multiple CPUs + """ + + if a.size == 0: + raise ValueError("attempt to get nanargmin of an empty sequence") + + if cunumeric_settings.numpy_compat() and a.dtype.kind == "f": + if any(all(isnan(a), axis=axis)): + raise ValueError("Array/Slice contains only NaNs") + + unary_red_code = get_non_nan_unary_red_code( + a.dtype.kind, UnaryRedCode.NANARGMIN + ) + + return a._perform_unary_reduction( + unary_red_code, + a, + axis=axis, + out=out, + keepdims=keepdims, + res_dtype=np.dtype(np.int64), + ) + + +@add_boilerplate("a") +def nanmin( + a: ndarray, + axis: Any = None, + out: Union[ndarray, None] = None, + keepdims: bool = False, + initial: Optional[Union[int, float]] = None, + where: Optional[ndarray] = None, +) -> ndarray: + """ + Return minimum of an array or minimum along an axis, ignoring any + NaNs. When all-NaN slices are encountered, a NaN is returned + for that slice only when CUNUMERIC_NUMPY_COMPATIBILITY environment + variable is set, otherwise identity is returned. + Empty slices will raise a ValueError + + Parameters + ---------- + a : array_like + Array containing numbers whose minimum is desired. If a is not an + array, a conversion is attempted. + + axis : {int, tuple of int, None}, optional + Axis or axes along which the minimum is computed. The default is to + compute the minimum of the flattened array. + + out : ndarray, optional + Alternative output array in which to place the result. Must + be of the same shape and buffer length as the expected output. + See `ufuncs-output-type` for more details. + + keepdims : bool, Optional + If this is set to True, the axes which are reduced are left + in the result as dimensions with size one. With this option, + the result will broadcast correctly against the input array. + + If the default value is passed, then `keepdims` will not be + passed through to the `amin` method of sub-classes of + `ndarray`, however any non-default value will be. If the + sub-class' method does not implement `keepdims` any + exceptions will be raised. + + initial : scalar, optional + The maximum value of an output element. Must be present to allow + computation on empty slice. See `~cunumeric.ufunc.reduce` for details. + + where : array_like[bool], optional + Elements to compare for the minimum. See `~cunumeric.ufunc.reduce` + for details. + + Returns + ------- + nanmin : ndarray or scalar + Minimum of `a`. If `axis` is None, the result is a scalar value. + If `axis` is given, the result is an array of dimension + ``a.ndim - 1``. + + Notes + ----- + CuNumeric's implementation will not raise a Runtime Warning for + slices with all-NaNs + + See Also + -------- + numpy.nanmin, numpy.nanmax, numpy.min, numpy.max, numpy.isnan, + numpy.maximum + + Availability + -------- + Multiple GPUs, Multiple CPUs + """ + + unary_red_code = get_non_nan_unary_red_code( + a.dtype.kind, UnaryRedCode.NANMIN + ) + + out_array = a._perform_unary_reduction( + unary_red_code, + a, + axis=axis, + out=out, + keepdims=keepdims, + initial=initial, + where=where, + ) + + if cunumeric_settings.numpy_compat() and a.dtype.kind == "f": + all_nan = all(isnan(a), axis=axis, keepdims=keepdims, where=where) + putmask(out_array, all_nan, np.nan) # type: ignore + + return out_array + + +@add_boilerplate("a") +def nanmax( + a: ndarray, + axis: Any = None, + out: Union[ndarray, None] = None, + keepdims: bool = False, + initial: Optional[Union[int, float]] = None, + where: Optional[ndarray] = None, +) -> ndarray: + """ + Return the maximum of an array or maximum along an axis, ignoring any + NaNs. When all-NaN slices are encountered, a NaN is returned + for that slice only when CUNUMERIC_NUMPY_COMPATIBILITY environment + variable is set, otherwise identity is returned. + Empty slices will raise a ValueError + + Parameters + ---------- + a : array_like + Array containing numbers whose maximum is desired. If a is not + an array, a conversion is attempted. + + axis : None or int or tuple[int], optional + Axis or axes along which to operate. By default, flattened input is + used. + + If this is a tuple of ints, the maximum is selected over multiple axes, + instead of a single axis or all the axes as before. + + out : ndarray, optional + Alternative output array in which to place the result. Must + be of the same shape and buffer length as the expected output. + See `ufuncs-output-type` for more details. + + keepdims : bool, optional + If this is set to True, the axes which are reduced are left + in the result as dimensions with size one. With this option, + the result will broadcast correctly against the input array. + + If the default value is passed, then `keepdims` will not be + passed through to the `amax` method of sub-classes of + `ndarray`, however any non-default value will be. If the + sub-class' method does not implement `keepdims` any + exceptions will be raised. + + initial : scalar, optional + The minimum value of an output element. Must be present to allow + computation on empty slice. See `~cunumeric.ufunc.reduce` for details. + + where : array_like[bool], optional + Elements to compare for the maximum. See `~cunumeric.ufunc.reduce` + for details. + + Returns + ------- + nanmax : ndarray or scalar + An array with the same shape as `a`, with the specified axis + removed. If `a` is 0-d array, of if axis is None, an ndarray + scalar is returned. The same dtype as `a` is returned. + + Notes + ----- + CuNumeric's implementation will not raise a Runtime Warning for + slices with all-NaNs + + See Also + -------- + numpy.nanmin, numpy.amax, numpy.isnan, numpy.fmax, numpy.maximum, + numpy.isfinite + + Availability + -------- + Multiple GPUs, Multiple CPUs + """ + + unary_red_code = get_non_nan_unary_red_code( + a.dtype.kind, UnaryRedCode.NANMAX + ) + + out_array = a._perform_unary_reduction( + unary_red_code, + a, + axis=axis, + out=out, + keepdims=keepdims, + initial=initial, + where=where, + ) + + if cunumeric_settings.numpy_compat() and a.dtype.kind == "f": + all_nan = all(isnan(a), axis=axis, keepdims=keepdims, where=where) + putmask(out_array, all_nan, np.nan) # type: ignore + + return out_array + + +@add_boilerplate("a") +def nanprod( + a: ndarray, + axis: Any = None, + dtype: Any = None, + out: Union[ndarray, None] = None, + keepdims: bool = False, + initial: Optional[Union[int, float]] = None, + where: Optional[ndarray] = None, +) -> ndarray: + """ + Return the product of array elements over a given axis treating + Not a Numbers (NaNs) as ones. + + One is returned for slices that are all-NaN or empty. + + Parameters + ---------- + a : array_like + Input array. + axis : int, optional + Axis or axes along which the product is computed. The + default is to compute the product of the flattened array. + dtype : data-type, optional + The type of the returned array and of the accumulator in + which the elements are summed. By default, the dtype of a + is used. An exception is when a has an integer type with + less precision than the platform (u)intp. In that case, + the default will be either (u)int32 or (u)int64 depending + on whether the platform is 32 or 64 bits. For inexact + inputs, dtype must be inexact. + out : ndarray, optional + Alternate output array in which to place the result. The + default is None. If provided, it must have the same shape as + the expected output, but the type will be cast if necessary. + See Output type determination for more details. The casting of + NaN to integer can yield unexpected results. + keepdims : bool, optional + If this is set to True, the axes which are reduced are left in the + result as dimensions with size one. With this option, the result + will broadcast correctly against the input array. + + If the default value is passed, then `keepdims` will not be + passed through to the `prod` method of sub-classes of + `ndarray`, however any non-default value will be. If the + sub-class' method does not implement `keepdims` any + exceptions will be raised. + initial : scalar, optional + The starting value for this product. See `~cunumeric.ufunc.reduce` for + details. + where : array_like[bool], optional + Elements to include in the product. See `~cunumeric.ufunc.reduce` for + details. + + Returns + ------- + nanprod: ndarray, see `dtype` parameter above. + A new array holding the result is returned unless out is + specified, in which case it is returned. + + See Also + -------- + numpy.prod, numpy.isnan + + Availability + -------- + Multiple GPUs, Multiple CPUs + + """ + + # Note: if the datatype of the input array is int and less + # than that of the platform int, then a convert task is launched + # in np.prod to take care of the type casting + + if a.dtype == np.complex128: + raise NotImplementedError( + "operation is not supported for complex128 arrays" + ) + + if a.dtype.kind in ("f", "c"): + unary_red_code = UnaryRedCode.NANPROD + else: + unary_red_code = UnaryRedCode.PROD + + return a._perform_unary_reduction( + unary_red_code, + a, + axis=axis, + dtype=dtype, + out=out, + keepdims=keepdims, + initial=initial, + where=where, + ) + + +@add_boilerplate("a") +def nansum( + a: ndarray, + axis: Any = None, + dtype: Any = None, + out: Union[ndarray, None] = None, + keepdims: bool = False, + initial: Optional[Union[int, float]] = None, + where: Optional[ndarray] = None, +) -> ndarray: + """ + Return the sum of array elements over a given axis treating + Not a Numbers (NaNs) as ones. + + Zero is returned for slices that are all-NaN or empty. + + Parameters + ---------- + a : array_like + Array containing numbers whose product is desired. If a is not + an array, a conversion is attempted. + + axis : None or int or tuple[int], optional + Axis or axes along which a sum is performed. The default, + axis=None, will sum all of the elements of the input array. + If axis is negative it counts from the last to the first axis. + + If axis is a tuple of ints, a sum is performed on all of the + axes specified in the tuple instead of a single axis or all + the axes as before. + + dtype : data-type, optional + The type of the returned array and of the accumulator in which + the elements are summed. The dtype of `a` is used by default + unless `a` has an integer dtype of less precision than the + default platform integer. In that case, if `a` is signed then + the platform integer is used while if `a` is unsigned then an + unsigned integer of the same precision as the platform integer + is used. + + out : ndarray, optional + Alternative output array in which to place the result. It must + have the same shape as the expected output, but the type of + the output values will be cast if necessary. + + keepdims : bool, optional + If this is set to True, the axes which are reduced are left + in the result as dimensions with size one. With this option, + the result will broadcast correctly against the input array. + + initial : scalar, optional + Starting value for the sum. See `~cunumeric.ufunc.reduce` for + details. + + where : array_like[bool], optional + Elements to include in the sum. See `~cunumeric.ufunc.reduce` for + details. + + Returns + ------- + nansum : ndarray, see `dtype` parameter above. + A new array holding the result is returned unless out is + specified, in which case it is returned. The result has the + same size as a, and the same shape as a if axis is not None or + a is a 1-d array. + + See Also + -------- + numpy.nansum, numpy.isnan, numpy.isfinite + + Availability + -------- + Multiple GPUs, Multiple CPUs + """ + + return a._nansum( + axis=axis, + dtype=dtype, + out=out, + keepdims=keepdims, + initial=initial, + where=where, + ) diff --git a/cunumeric/_module/sets_making.py b/cunumeric/_module/sets_making.py new file mode 100644 index 0000000000..72e77af710 --- /dev/null +++ b/cunumeric/_module/sets_making.py @@ -0,0 +1,104 @@ +# Copyright 2024 NVIDIA Corporation +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +from __future__ import annotations + +from typing import TYPE_CHECKING, Optional + +from ..array import add_boilerplate + +if TYPE_CHECKING: + from ..array import ndarray + +_builtin_any = any + + +@add_boilerplate("ar") +def unique( + ar: ndarray, + return_index: bool = False, + return_inverse: bool = False, + return_counts: bool = False, + axis: Optional[int] = None, +) -> ndarray: + """ + + Find the unique elements of an array. + Returns the sorted unique elements of an array. There are three optional + outputs in addition to the unique elements: + * the indices of the input array that give the unique values + * the indices of the unique array that reconstruct the input array + * the number of times each unique value comes up in the input array + + Parameters + ---------- + ar : array_like + Input array. Unless `axis` is specified, this will be flattened if it + is not already 1-D. + return_index : bool, optional + If True, also return the indices of `ar` (along the specified axis, + if provided, or in the flattened array) that result in the unique + array. + Currently not supported. + return_inverse : bool, optional + If True, also return the indices of the unique array (for the specified + axis, if provided) that can be used to reconstruct `ar`. + Currently not supported. + return_counts : bool, optional + If True, also return the number of times each unique item appears + in `ar`. + Currently not supported. + axis : int or None, optional + The axis to operate on. If None, `ar` will be flattened. If an integer, + the subarrays indexed by the given axis will be flattened and treated + as the elements of a 1-D array with the dimension of the given axis, + see the notes for more details. Object arrays or structured arrays + that contain objects are not supported if the `axis` kwarg is used. The + default is None. + Currently not supported. + + Returns + ------- + unique : ndarray + The sorted unique values. + unique_indices : ndarray, optional + The indices of the first occurrences of the unique values in the + original array. Only provided if `return_index` is True. + unique_inverse : ndarray, optional + The indices to reconstruct the original array from the + unique array. Only provided if `return_inverse` is True. + unique_counts : ndarray, optional + The number of times each of the unique values comes up in the + original array. Only provided if `return_counts` is True. + + See Also + -------- + numpy.unique + + Availability + -------- + Multiple GPUs, Multiple CPUs + + Notes + -------- + Keyword arguments for optional outputs are not yet supported. + `axis` is also not handled currently. + + """ + if _builtin_any((return_index, return_inverse, return_counts, axis)): + raise NotImplementedError( + "Keyword arguments for `unique` are not yet supported" + ) + + return ar.unique() diff --git a/cunumeric/_module/ssc_counting.py b/cunumeric/_module/ssc_counting.py new file mode 100644 index 0000000000..e46f61d012 --- /dev/null +++ b/cunumeric/_module/ssc_counting.py @@ -0,0 +1,57 @@ +# Copyright 2024 NVIDIA Corporation +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +from __future__ import annotations + +from typing import TYPE_CHECKING, Optional, Union + +from ..array import add_boilerplate + +if TYPE_CHECKING: + from ..array import ndarray + + +@add_boilerplate("a") +def count_nonzero( + a: ndarray, axis: Optional[Union[int, tuple[int, ...]]] = None +) -> Union[int, ndarray]: + """ + + Counts the number of non-zero values in the array ``a``. + + Parameters + ---------- + a : array_like + The array for which to count non-zeros. + axis : int or tuple, optional + Axis or tuple of axes along which to count non-zeros. + Default is None, meaning that non-zeros will be counted + along a flattened version of ``a``. + + Returns + ------- + count : int or ndarray[int] + Number of non-zero values in the array along a given axis. + Otherwise, the total number of non-zero values in the array + is returned. + + See Also + -------- + numpy.count_nonzero + + Availability + -------- + Multiple GPUs, Multiple CPUs + """ + return a._count_nonzero(axis) diff --git a/cunumeric/_module/ssc_searching.py b/cunumeric/_module/ssc_searching.py new file mode 100644 index 0000000000..040db11b9b --- /dev/null +++ b/cunumeric/_module/ssc_searching.py @@ -0,0 +1,339 @@ +# Copyright 2024 NVIDIA Corporation +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +from __future__ import annotations + +from typing import TYPE_CHECKING, Optional, Union + +from ..array import add_boilerplate, ndarray +from .array_shape import ravel, reshape + +if TYPE_CHECKING: + from ..types import SortSide + + +@add_boilerplate("a") +def searchsorted( + a: ndarray, + v: Union[int, float, ndarray], + side: SortSide = "left", + sorter: Optional[ndarray] = None, +) -> Union[int, ndarray]: + """ + + Find the indices into a sorted array a such that, if the corresponding + elements in v were inserted before the indices, the order of a would be + preserved. + + Parameters + ---------- + a : 1-D array_like + Input array. If sorter is None, then it must be sorted in ascending + order, otherwise sorter must be an array of indices that sort it. + v : scalar or array_like + Values to insert into a. + side : ``{'left', 'right'}``, optional + If 'left', the index of the first suitable location found is given. + If 'right', return the last such index. If there is no suitable index, + return either 0 or N (where N is the length of a). + sorter : 1-D array_like, optional + Optional array of integer indices that sort array a into ascending + order. They are typically the result of argsort. + + Returns + ------- + indices : int or array_like[int] + Array of insertion points with the same shape as v, or an integer + if v is a scalar. + + Availability + -------- + Multiple GPUs, Multiple CPUs + """ + return a.searchsorted(v, side, sorter) + + +@add_boilerplate("a") +def argmax( + a: ndarray, + axis: Optional[int] = None, + out: Optional[ndarray] = None, + *, + keepdims: bool = False, +) -> ndarray: + """ + + Returns the indices of the maximum values along an axis. + + Parameters + ---------- + a : array_like + Input array. + axis : int, optional + By default, the index is into the flattened array, otherwise + along the specified axis. + out : ndarray, optional + If provided, the result will be inserted into this array. It should + be of the appropriate shape and dtype. + keepdims : bool, optional + If this is set to True, the axes which are reduced are left + in the result as dimensions with size one. With this option, + the result will broadcast correctly against the array. + + Returns + ------- + index_array : ndarray[int] + Array of indices into the array. It has the same shape as `a.shape` + with the dimension along `axis` removed. + + See Also + -------- + numpy.argmax + + Notes + ----- + CuNumeric's parallel implementation may yield different results from NumPy + when the array contains NaN(s). + + Availability + -------- + Multiple GPUs, Multiple CPUs + """ + return a.argmax(axis=axis, out=out, keepdims=keepdims) + + +@add_boilerplate("a") +def argmin( + a: ndarray, + axis: Optional[int] = None, + out: Optional[ndarray] = None, + *, + keepdims: bool = False, +) -> ndarray: + """ + + Returns the indices of the minimum values along an axis. + + Parameters + ---------- + a : array_like + Input array. + axis : int, optional + By default, the index is into the flattened array, otherwise + along the specified axis. + out : ndarray, optional + If provided, the result will be inserted into this array. It should + be of the appropriate shape and dtype. + keepdims : bool, optional + If this is set to True, the axes which are reduced are left + in the result as dimensions with size one. With this option, + the result will broadcast correctly against the array. + + Returns + ------- + index_array : ndarray[int] + Array of indices into the array. It has the same shape as `a.shape` + with the dimension along `axis` removed. + + See Also + -------- + numpy.argmin + + Notes + ----- + CuNumeric's parallel implementation may yield different results from NumPy + when the array contains NaN(s). + + Availability + -------- + Multiple GPUs, Multiple CPUs + """ + return a.argmin(axis=axis, out=out, keepdims=keepdims) + + +@add_boilerplate("a") +def flatnonzero(a: ndarray) -> ndarray: + """ + + Return indices that are non-zero in the flattened version of a. + + This is equivalent to `np.nonzero(np.ravel(a))[0]`. + + Parameters + ---------- + a : array_like + Input array. + + Returns + ------- + res : ndarray + Output array, containing the indices of the elements of + `a.ravel()` that are non-zero. + + See Also + -------- + numpy.flatnonzero + + Availability + -------- + Multiple GPUs, Multiple CPUs + """ + return nonzero(ravel(a))[0] + + +@add_boilerplate("a", "x", "y") +def where( + a: ndarray, x: Optional[ndarray] = None, y: Optional[ndarray] = None +) -> Union[ndarray, tuple[ndarray, ...]]: + """ + where(condition, [x, y]) + + Return elements chosen from `x` or `y` depending on `condition`. + + Parameters + ---------- + condition : array_like, bool + Where True, yield `x`, otherwise yield `y`. + x, y : array_like + Values from which to choose. `x`, `y` and `condition` need to be + broadcastable to some shape. + + Returns + ------- + out : ndarray + An array with elements from `x` where `condition` is True, and elements + from `y` elsewhere. + + See Also + -------- + numpy.where + + Availability + -------- + Multiple GPUs, Multiple CPUs + """ + if x is None or y is None: + if x is not None or y is not None: + raise ValueError( + "both 'x' and 'y' parameters must be specified together for" + " 'where'" + ) + return nonzero(a) + return ndarray._perform_where(a, x, y) + + +@add_boilerplate("a") +def argwhere(a: ndarray) -> ndarray: + """ + argwhere(a) + + Find the indices of array elements that are non-zero, grouped by element. + + Parameters + ---------- + a : array_like + Input data. + + Returns + ------- + index_array : ndarray + Indices of elements that are non-zero. Indices are grouped by element. + This array will have shape (N, a.ndim) where N is the number of + non-zero items. + + See Also + -------- + numpy.argwhere + + Availability + -------- + Multiple GPUs, Multiple CPUs + """ + thunk = a._thunk.argwhere() + return ndarray(shape=thunk.shape, thunk=thunk) + + +@add_boilerplate("condition", "arr") +def extract(condition: ndarray, arr: ndarray) -> ndarray: + """ + + Return the elements of an array that satisfy some condition. + + Parameters + ---------- + condition : array_like + An array whose nonzero or True entries indicate the elements + of `arr` to extract. + arr : array_like + Input array of the same size as `condition`. + + Returns + ------- + result : ndarray + Rank 1 array of values from arr where `condition` is True. + + See Also + -------- + numpy.extract + + Availability + -------- + Multiple GPUs, Multiple CPUs + """ + if condition.size != arr.size: + raise ValueError("arr array and condition array must be of same size") + + if condition.shape != arr.shape: + condition_reshape = reshape(condition, arr.shape) + else: + condition_reshape = condition + + if condition_reshape.dtype == bool: + thunk = arr._thunk.get_item(condition_reshape._thunk) + else: + bool_condition = condition_reshape.astype(bool) + thunk = arr._thunk.get_item(bool_condition._thunk) + + return ndarray(shape=thunk.shape, thunk=thunk) + + +@add_boilerplate("a") +def nonzero(a: ndarray) -> tuple[ndarray, ...]: + """ + + Return the indices of the elements that are non-zero. + + Returns a tuple of arrays, one for each dimension of `a`, + containing the indices of the non-zero elements in that + dimension. + + Parameters + ---------- + a : array_like + Input array. + + Returns + ------- + tuple_of_arrays : tuple + Indices of elements that are non-zero. + + See Also + -------- + numpy.nonzero + + Availability + -------- + Multiple GPUs, Multiple CPUs + """ + return a.nonzero() diff --git a/cunumeric/_module/ssc_sorting.py b/cunumeric/_module/ssc_sorting.py new file mode 100644 index 0000000000..ed456d4950 --- /dev/null +++ b/cunumeric/_module/ssc_sorting.py @@ -0,0 +1,287 @@ +# Copyright 2024 NVIDIA Corporation +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +from __future__ import annotations + +from typing import TYPE_CHECKING, Optional, Sequence, Union + +import numpy as np + +from ..array import add_boilerplate, ndarray + +if TYPE_CHECKING: + from ..types import SelectKind, SortType + + +@add_boilerplate("a") +def argsort( + a: ndarray, + axis: Union[int, None] = -1, + kind: SortType = "quicksort", + order: Optional[Union[str, list[str]]] = None, +) -> ndarray: + """ + + Returns the indices that would sort an array. + + Parameters + ---------- + a : array_like + Input array. + axis : int or None, optional + Axis to sort. By default, the index -1 (the last axis) is used. If + None, the flattened array is used. + kind : ``{'quicksort', 'mergesort', 'heapsort', 'stable'}``, optional + Default is 'quicksort'. The underlying sort algorithm might vary. + The code basically supports 'stable' or *not* 'stable'. + order : str or list[str], optional + Currently not supported + + Returns + ------- + index_array : ndarray[int] + Array of indices that sort a along the specified axis. It has the + same shape as `a.shape` or is flattened in case of `axis` is None. + + See Also + -------- + numpy.argsort + + Availability + -------- + Multiple GPUs, Multiple CPUs + """ + + result = ndarray(a.shape, np.int64) + result._thunk.sort( + rhs=a._thunk, argsort=True, axis=axis, kind=kind, order=order + ) + return result + + +def msort(a: ndarray) -> ndarray: + """ + + Returns a sorted copy of an array sorted along the first axis. + + Parameters + ---------- + a : array_like + Input array. + + Returns + ------- + out : ndarray + Sorted array with same dtype and shape as `a`. + + See Also + -------- + numpy.msort + + Availability + -------- + Multiple GPUs, Multiple CPUs + """ + return sort(a, axis=0) + + +@add_boilerplate("a") +def sort( + a: ndarray, + axis: Union[int, None] = -1, + kind: SortType = "quicksort", + order: Optional[Union[str, list[str]]] = None, +) -> ndarray: + """ + + Returns a sorted copy of an array. + + Parameters + ---------- + a : array_like + Input array. + axis : int or None, optional + Axis to sort. By default, the index -1 (the last axis) is used. If + None, the flattened array is used. + kind : ``{'quicksort', 'mergesort', 'heapsort', 'stable'}``, optional + Default is 'quicksort'. The underlying sort algorithm might vary. + The code basically supports 'stable' or *not* 'stable'. + order : str or list[str], optional + Currently not supported + + Returns + ------- + out : ndarray + Sorted array with same dtype and shape as `a`. In case `axis` is + None the result is flattened. + + + See Also + -------- + numpy.sort + + Availability + -------- + Multiple GPUs, Multiple CPUs + """ + result = ndarray(a.shape, a.dtype) + result._thunk.sort(rhs=a._thunk, axis=axis, kind=kind, order=order) + return result + + +@add_boilerplate("a") +def sort_complex(a: ndarray) -> ndarray: + """ + + Returns a sorted copy of an array sorted along the last axis. Sorts the + real part first, the imaginary part second. + + Parameters + ---------- + a : array_like + Input array. + + Returns + ------- + out : ndarray, complex + Sorted array with same shape as `a`. + + See Also + -------- + numpy.sort_complex + + Availability + -------- + Multiple GPUs, Multiple CPUs + """ + + result = sort(a) + # force complex result upon return + if np.issubdtype(result.dtype, np.complexfloating): + return result + elif ( + np.issubdtype(result.dtype, np.integer) and result.dtype.itemsize <= 2 + ): + return result.astype(np.complex64, copy=True) + else: + return result.astype(np.complex128, copy=True) + + +# partition + + +@add_boilerplate("a") +def argpartition( + a: ndarray, + kth: Union[int, Sequence[int]], + axis: Union[int, None] = -1, + kind: SelectKind = "introselect", + order: Optional[Union[str, list[str]]] = None, +) -> ndarray: + """ + + Perform an indirect partition along the given axis. + + Parameters + ---------- + a : array_like + Input array. + kth : int or Sequence[int] + axis : int or None, optional + Axis to partition. By default, the index -1 (the last axis) is used. If + None, the flattened array is used. + kind : ``{'introselect'}``, optional + Currently not supported. + order : str or list[str], optional + Currently not supported. + + Returns + ------- + out : ndarray[int] + Array of indices that partitions a along the specified axis. It has the + same shape as `a.shape` or is flattened in case of `axis` is None. + + + Notes + ----- + The current implementation falls back to `cunumeric.argsort`. + + See Also + -------- + numpy.argpartition + + Availability + -------- + Multiple GPUs, Multiple CPUs + """ + result = ndarray(a.shape, np.int64) + result._thunk.partition( + rhs=a._thunk, + argpartition=True, + kth=kth, + axis=axis, + kind=kind, + order=order, + ) + return result + + +@add_boilerplate("a") +def partition( + a: ndarray, + kth: Union[int, Sequence[int]], + axis: Union[int, None] = -1, + kind: SelectKind = "introselect", + order: Optional[Union[str, list[str]]] = None, +) -> ndarray: + """ + + Returns a partitioned copy of an array. + + Parameters + ---------- + a : array_like + Input array. + kth : int or Sequence[int] + axis : int or None, optional + Axis to partition. By default, the index -1 (the last axis) is used. If + None, the flattened array is used. + kind : ``{'introselect'}``, optional + Currently not supported. + order : str or list[str], optional + Currently not supported. + + Returns + ------- + out : ndarray + Partitioned array with same dtype and shape as `a`. In case `axis` is + None the result is flattened. + + Notes + ----- + The current implementation falls back to `cunumeric.sort`. + + See Also + -------- + numpy.partition + + Availability + -------- + Multiple GPUs, Multiple CPUs + """ + result = ndarray(a.shape, a.dtype) + result._thunk.partition( + rhs=a._thunk, kth=kth, axis=axis, kind=kind, order=order + ) + return result diff --git a/cunumeric/_module/stats_avgs_vars.py b/cunumeric/_module/stats_avgs_vars.py new file mode 100644 index 0000000000..212c358a39 --- /dev/null +++ b/cunumeric/_module/stats_avgs_vars.py @@ -0,0 +1,236 @@ +# Copyright 2024 NVIDIA Corporation +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +from __future__ import annotations + +from typing import TYPE_CHECKING, Any, Optional, Union + +import numpy as np + +from ..array import add_boilerplate + +if TYPE_CHECKING: + from ..array import ndarray + + +@add_boilerplate("a") +def mean( + a: ndarray, + axis: Optional[Union[int, tuple[int, ...]]] = None, + dtype: Optional[np.dtype[Any]] = None, + out: Optional[ndarray] = None, + keepdims: bool = False, + where: Optional[ndarray] = None, +) -> ndarray: + """ + + Compute the arithmetic mean along the specified axis. + + Returns the average of the array elements. The average is taken over + the flattened array by default, otherwise over the specified axis. + `float64` intermediate and return values are used for integer inputs. + + Parameters + ---------- + a : array_like + Array containing numbers whose mean is desired. If `a` is not an + array, a conversion is attempted. + axis : None or int or tuple[int], optional + Axis or axes along which the means are computed. The default is to + compute the mean of the flattened array. + + If this is a tuple of ints, a mean is performed over multiple axes, + instead of a single axis or all the axes as before. + dtype : data-type, optional + Type to use in computing the mean. For integer inputs, the default + is `float64`; for floating point inputs, it is the same as the + input dtype. + out : ndarray, optional + Alternate output array in which to place the result. The default + is ``None``; if provided, it must have the same shape as the + expected output, but the type will be cast if necessary. + See `ufuncs-output-type` for more details. + + keepdims : bool, optional + If this is set to True, the axes which are reduced are left + in the result as dimensions with size one. With this option, + the result will broadcast correctly against the input array. + + If the default value is passed, then `keepdims` will not be + passed through to the `mean` method of sub-classes of + `ndarray`, however any non-default value will be. If the + sub-class' method does not implement `keepdims` any + exceptions will be raised. + + where : array_like of bool, optional + Elements to include in the mean. + + Returns + ------- + m : ndarray + If `out is None`, returns a new array of the same dtype a above + containing the mean values, otherwise a reference to the output + array is returned. + + See Also + -------- + numpy.mean + + Availability + -------- + Multiple GPUs, Multiple CPUs + """ + return a.mean( + axis=axis, dtype=dtype, out=out, keepdims=keepdims, where=where + ) + + +@add_boilerplate("a") +def nanmean( + a: ndarray, + axis: Optional[Union[int, tuple[int, ...]]] = None, + dtype: Optional[np.dtype[Any]] = None, + out: Optional[ndarray] = None, + keepdims: bool = False, + where: Optional[ndarray] = None, +) -> ndarray: + """ + + Compute the arithmetic mean along the specified axis, ignoring NaNs. + + Returns the average of the array elements. The average is taken over + the flattened array by default, otherwise over the specified axis. + `float64` intermediate and return values are used for integer inputs. + + Parameters + ---------- + a : array_like + Array containing numbers whose mean is desired. If `a` is not an + array, a conversion is attempted. + axis : None or int or tuple[int], optional + Axis or axes along which the means are computed. The default is to + compute the mean of the flattened array. + + If this is a tuple of ints, a mean is performed over multiple axes, + instead of a single axis or all the axes as before. + dtype : data-type, optional + Type to use in computing the mean. For integer inputs, the default + is `float64`; for floating point inputs, it is the same as the + input dtype. + out : ndarray, optional + Alternate output array in which to place the result. The default + is ``None``; if provided, it must have the same shape as the + expected output, but the type will be cast if necessary. + See `ufuncs-output-type` for more details. + + keepdims : bool, optional + If this is set to True, the axes which are reduced are left + in the result as dimensions with size one. With this option, + the result will broadcast correctly against the input array. + + + where : array_like of bool, optional + Elements to include in the mean. + + Returns + ------- + m : ndarray + If `out is None`, returns a new array of the same dtype as a above + containing the mean values, otherwise a reference to the output + array is returned. + + See Also + -------- + numpy.nanmean + + Availability + -------- + Multiple GPUs, Multiple CPUs + """ + return a._nanmean( + axis=axis, dtype=dtype, out=out, keepdims=keepdims, where=where + ) + + +@add_boilerplate("a") +def var( + a: ndarray, + axis: Optional[Union[int, tuple[int, ...]]] = None, + dtype: Optional[np.dtype[Any]] = None, + out: Optional[ndarray] = None, + ddof: int = 0, + keepdims: bool = False, + *, + where: Union[ndarray, None] = None, +) -> ndarray: + """ + Compute the variance along the specified axis. + + Returns the variance of the array elements, a measure of the spread of + a distribution. The variance is computed for the flattened array + by default, otherwise over the specified axis. + + Parameters + ---------- + a : array_like + Array containing numbers whose variance is desired. If `a` is not an + array, a conversion is attempted. + axis : None or int or tuple[int], optional + Axis or axes along which the variance is computed. The default is to + compute the variance of the flattened array. + + If this is a tuple of ints, a variance is performed over multiple axes, + instead of a single axis or all the axes as before. + dtype : data-type, optional + Type to use in computing the variance. For arrays of integer type + the default is float64; for arrays of float types + it is the same as the array type. + out : ndarray, optional + Alternate output array in which to place the result. It must have the + same shape as the expected output, but the type is cast if necessary. + ddof : int, optional + “Delta Degrees of Freedom”: the divisor used in the calculation is + N - ddof, where N represents the number of elements. By default + ddof is zero. + keepdims : bool, optional + If this is set to True, the axes which are reduced are left + in the result as dimensions with size one. With this option, + the result will broadcast correctly against the input array. + where : array_like of bool, optional + A boolean array which is broadcasted to match the dimensions of array, + and selects elements to include in the reduction. + + Returns + ------- + m : ndarray, see dtype parameter above + If `out=None`, returns a new array of the same dtype as above + containing the variance values, otherwise a reference to the output + array is returned. + + See Also + -------- + numpy.var + + Availability + -------- + Multiple GPUs, Multiple CPUs + """ + return a.var( + axis=axis, + dtype=dtype, + out=out, + ddof=ddof, + keepdims=keepdims, + where=where, + ) diff --git a/cunumeric/_module/stats_histograms.py b/cunumeric/_module/stats_histograms.py new file mode 100644 index 0000000000..8318951fbf --- /dev/null +++ b/cunumeric/_module/stats_histograms.py @@ -0,0 +1,275 @@ +# Copyright 2024 NVIDIA Corporation +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +from __future__ import annotations + +from typing import TYPE_CHECKING, Any, Optional, Union + +import numpy as np + +from ..array import add_boilerplate, ndarray +from .creation_data import asarray +from .creation_shape import ones, zeros +from .math_extrema import amax, amin + +if TYPE_CHECKING: + import numpy.typing as npt + +_builtin_max = max +_builtin_range = range + + +@add_boilerplate("x", "weights") +def bincount( + x: ndarray, weights: Optional[ndarray] = None, minlength: int = 0 +) -> ndarray: + """ + bincount(x, weights=None, minlength=0) + + Count number of occurrences of each value in array of non-negative ints. + + The number of bins (of size 1) is one larger than the largest value in + `x`. If `minlength` is specified, there will be at least this number + of bins in the output array (though it will be longer if necessary, + depending on the contents of `x`). + Each bin gives the number of occurrences of its index value in `x`. + If `weights` is specified the input array is weighted by it, i.e. if a + value ``n`` is found at position ``i``, ``out[n] += weight[i]`` instead + of ``out[n] += 1``. + + Parameters + ---------- + x : array_like + 1-D input array of non-negative ints. + weights : array_like, optional + Weights, array of the same shape as `x`. + minlength : int, optional + A minimum number of bins for the output array. + + Returns + ------- + out : ndarray[int] + The result of binning the input array. + The length of `out` is equal to ``cunumeric.amax(x)+1``. + + Raises + ------ + ValueError + If the input is not 1-dimensional, or contains elements with negative + values, or if `minlength` is negative. + TypeError + If the type of the input is float or complex. + + See Also + -------- + numpy.bincount + + Availability + -------- + Multiple GPUs, Multiple CPUs + """ + if x.ndim != 1: + raise ValueError("the input array must be 1-dimensional") + if weights is not None: + if weights.shape != x.shape: + raise ValueError("weights array must be same shape for bincount") + if weights.dtype.kind == "c": + raise ValueError("weights must be convertible to float64") + # Make sure the weights are float64 + weights = weights.astype(np.float64) + if not np.issubdtype(x.dtype, np.integer): + raise TypeError("input array for bincount must be integer type") + if minlength < 0: + raise ValueError("'minlength' must not be negative") + # Note that the following are non-blocking operations, + # though passing their results to `int` is blocking + max_val, min_val = amax(x), amin(x) + if int(min_val) < 0: + raise ValueError("the input array must have no negative elements") + minlength = _builtin_max(minlength, int(max_val) + 1) + if x.size == 1: + # Handle the special case of 0-D array + if weights is None: + out = zeros((minlength,), dtype=np.dtype(np.int64)) + # TODO: Remove this "type: ignore" once @add_boilerplate can + # propagate "ndarray -> ndarray | npt.ArrayLike" in wrapped sigs + out[x[0]] = 1 # type: ignore [assignment] + else: + out = zeros((minlength,), dtype=weights.dtype) + index = x[0] + out[index] = weights[0] + else: + # Normal case of bincount + if weights is None: + out = ndarray( + (minlength,), + dtype=np.dtype(np.int64), + inputs=(x, weights), + ) + out._thunk.bincount(x._thunk) + else: + out = ndarray( + (minlength,), + dtype=weights.dtype, + inputs=(x, weights), + ) + out._thunk.bincount(x._thunk, weights=weights._thunk) + return out + + +@add_boilerplate("x", "weights") +def histogram( + x: ndarray, + bins: Union[ndarray, npt.ArrayLike, int] = 10, + range: Optional[Union[tuple[int, int], tuple[float, float]]] = None, + weights: Optional[ndarray] = None, + density: bool = False, +) -> tuple[ndarray, ndarray]: + """ + Compute the histogram of a dataset. + + Parameters + ---------- + a : array_like + Input data. The histogram is computed over the flattened array. + bins : int or sequence of scalars, optional + If `bins` is an int, it defines the number of equal-width bins in the + given range (10, by default). If `bins` is a sequence, it defines a + monotonically increasing array of bin edges, including the rightmost + edge, allowing for non-uniform bin widths. + range : (float, float), optional + The lower and upper range of the bins. If not provided, range is simply + ``(a.min(), a.max())``. Values outside the range are ignored. The first + element of the range must be smaller than the second. This argument is + ignored when bin edges are provided explicitly. + weights : array_like, optional + An array of weights, of the same shape as `a`. Each value in `a` only + contributes its associated weight towards the bin count (instead of 1). + If `density` is True, the weights are normalized, so that the integral + of the density over the range remains 1. + density : bool, optional + If ``False``, the result will contain the number of samples in each + bin. If ``True``, the result is the value of the probability *density* + function at the bin, normalized such that the *integral* over the range + is 1. Note that the sum of the histogram values will not be equal to 1 + unless bins of unity width are chosen; it is not a probability *mass* + function. + + Returns + ------- + hist : array + The values of the histogram. See `density` and `weights` for a + description of the possible semantics. + bin_edges : array + Return the bin edges ``(length(hist)+1)``. + + See Also + -------- + numpy.histogram + + Availability + -------- + Multiple GPUs, Multiple CPUs + """ + result_type: np.dtype[Any] = np.dtype(np.int64) + + if np.ndim(bins) > 1: + raise ValueError("`bins` must be 1d, when an array") + + # check isscalar(bins): + # + if np.ndim(bins) == 0: + if not isinstance(bins, int): + raise TypeError("`bins` must be array or integer type") + + num_intervals = bins + + if range is not None: + assert isinstance(range, tuple) and len(range) == 2 + if range[0] >= range[1]: + raise ValueError( + "`range` must be a pair of increasing values." + ) + + lower_b = range[0] + higher_b = range[1] + elif x.size == 0: + lower_b = 0.0 + higher_b = 1.0 + else: + lower_b = float(min(x)) + higher_b = float(max(x)) + + step = (higher_b - lower_b) / num_intervals + + bins_array = asarray( + [lower_b + k * step for k in _builtin_range(0, num_intervals)] + + [higher_b], + dtype=np.dtype(np.float64), + ) + + bins_orig_type = bins_array.dtype + else: + bins_as_arr = asarray(bins) + bins_orig_type = bins_as_arr.dtype + + bins_array = bins_as_arr.astype(np.dtype(np.float64)) + num_intervals = bins_array.shape[0] - 1 + + if not all((bins_array[1:] - bins_array[:-1]) >= 0): + raise ValueError( + "`bins` must increase monotonically, when an array" + ) + + if x.ndim != 1: + x = x.flatten() + + if weights is not None: + if weights.shape != x.shape: + raise ValueError( + "`weights` array must be same shape for histogram" + ) + + result_type = weights.dtype + weights_array = weights.astype(np.dtype(np.float64)) + else: + # case weights == None cannot be handled inside _thunk.histogram, + # bc/ of hist ndarray inputs(), below; + # needs to be handled here: + # + weights_array = ones(x.shape, dtype=np.dtype(np.float64)) + + if x.size == 0: + return ( + zeros((num_intervals,), dtype=result_type), + bins_array.astype(bins_orig_type), + ) + + hist = ndarray( + (num_intervals,), + dtype=weights_array.dtype, + inputs=(x, bins_array, weights_array), + ) + hist._thunk.histogram( + x._thunk, bins_array._thunk, weights=weights_array._thunk + ) + + # handle (density = True): + # + if density: + result_type = np.dtype(np.float64) + hist /= sum(hist) + hist /= bins_array[1:] - bins_array[:-1] + + return hist.astype(result_type), bins_array.astype(bins_orig_type) diff --git a/cunumeric/_module/stats_order.py b/cunumeric/_module/stats_order.py new file mode 100644 index 0000000000..e3dd6b9880 --- /dev/null +++ b/cunumeric/_module/stats_order.py @@ -0,0 +1,700 @@ +# Copyright 2024 NVIDIA Corporation +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +from __future__ import annotations + +import math +from typing import TYPE_CHECKING, Any, Iterable, Optional, Union + +import numpy as np + +from ..array import add_boilerplate +from .array_transpose import moveaxis +from .creation_shape import zeros +from .ssc_sorting import sort + +if TYPE_CHECKING: + from typing import Callable + + import numpy.typing as npt + + from ..array import ndarray + + +# for the case when axis = tuple (non-singleton) +# reshuffling might have to be done (if tuple is non-consecutive) +# and the src array must be collapsed along that set of axes +# +# args: +# +# arr: [in] source nd-array on which quantiles are calculated; +# axes_set: [in] tuple or list of axes (indices less than arr dimension); +# +# return: pair: (minimal_index, reshuffled_and_collapsed source array) +def _reshuffle_reshape( + arr: ndarray, axes_set: Iterable[int] +) -> tuple[int, ndarray]: + ndim = len(arr.shape) + + sorted_axes = tuple(sorted(axes_set)) + + min_dim_index = sorted_axes[0] + num_axes = len(sorted_axes) + reshuffled_axes = tuple(range(min_dim_index, min_dim_index + num_axes)) + + non_consecutive = sorted_axes != reshuffled_axes + if non_consecutive: + arr_shuffled = moveaxis(arr, sorted_axes, reshuffled_axes) + else: + arr_shuffled = arr + + # shape_reshuffled = arr_shuffled.shape # debug + collapsed_shape = np.prod([arr_shuffled.shape[i] for i in reshuffled_axes]) + + redimed = tuple(range(0, min_dim_index + 1)) + tuple( + range(min_dim_index + num_axes, ndim) + ) + reshaped = tuple( + [ + collapsed_shape if k == min_dim_index else arr_shuffled.shape[k] + for k in redimed + ] + ) + + arr_reshaped = arr_shuffled.reshape(reshaped) + return (min_dim_index, arr_reshaped) + + +# account for 0-based indexing +# there's no negative numbers +# arithmetic at this level, +# (pos, k) are always positive! +# +def _floor_i(k: int | float) -> int: + j = k - 1 if k > 0 else 0 + return int(j) + + +# Generic rule: if `q` input value falls onto a node, then return that node + +# Discontinuous methods: + + +# q = quantile input \in [0, 1] +# n = sizeof(array) +def _inverted_cdf(q: float, n: int) -> tuple[float, int]: + pos = q * n + k = math.floor(pos) + + g = pos - k + gamma = 1.0 if g > 0 else 0.0 + + j = int(k) - 1 + if j < 0: + return (0.0, 0) + else: + return (gamma, j) + + +def _averaged_inverted_cdf(q: float, n: int) -> tuple[float, int]: + pos = q * n + k = math.floor(pos) + + g = pos - k + gamma = 1.0 if g > 0 else 0.5 + + j = int(k) - 1 + if j < 0: + return (0.0, 0) + elif j >= n - 1: + return (1.0, n - 2) + else: + return (gamma, j) + + +def _closest_observation(q: float, n: int) -> tuple[float, int]: + # p = q*n - 0.5 + # pos = 0 if p < 0 else p + + # weird departure from paper + # (bug?), but this fixes it: + # also, j even in original paper + # applied to 1-based indexing; we have 0-based! + # numpy impl. doesn't account that the original paper used + # 1-based indexing, 0-based j is still checked for evennes! + # (see proof in quantile_policies.py) + # + p0 = q * n - 0.5 + p = p0 - 1.0 + + pos = 0 if p < 0 else p0 + k = math.floor(pos) + + j = _floor_i(k) + gamma = 1 if k < pos else (0 if j % 2 == 0 else 1) + + return (gamma, j) + + +# Continuous methods: + + +# Parzen method +def _interpolated_inverted_cdf(q: float, n: int) -> tuple[float, int]: + pos = q * n + k = math.floor(pos) + # gamma = pos-k + # this fixes it: + # + gamma = 0.0 if k == 0 else pos - k + j = _floor_i(k) + return (gamma, j) + + +# Hazen method +def _hazen(q: float, n: int) -> tuple[float, int]: + pos = q * n + 0.5 + k = math.floor(pos) + # gamma = pos-k + # + # this fixes it: + # (when pos > n: this actually selects the right point, + # which is the correct choice, because right = arr[n] + # gets invalidated) + # + gamma = 0.0 if (pos < 1 or pos > n) else pos - k + + j = _floor_i(k) + return (gamma, j) + + +# Weibull method +def _weibull(q: float, n: int) -> tuple[float, int]: + pos = q * (n + 1) + + k = math.floor(pos) + # gamma = pos-k + # + # this fixes it: + # (when pos > n: this actually selects the right point, + # which is the correct choice, because right = arr[n] + # gets invalidated) + # + gamma = 0.0 if (pos < 1 or pos > n) else pos - k + + j = _floor_i(k) + + if j >= n: + j = n - 1 + + return (gamma, j) + + +# Gumbel method +def _linear(q: float, n: int) -> tuple[float, int]: + pos = q * (n - 1) + 1 + k = math.floor(pos) + # gamma = pos-k + # + # this fixes it: + # (when pos > n: this actually selects the right point, + # which is the correct choice, because right = arr[n] + # gets invalidated) + # + gamma = 0.0 if (pos < 1 or pos > n) else pos - k + + j = _floor_i(k) + return (gamma, j) + + +# Johnson & Kotz method +def _median_unbiased(q: float, n: int) -> tuple[float, int]: + fract = 1.0 / 3.0 + pos = q * (n + fract) + fract + k = math.floor(pos) + + # gamma = pos-k + # + # this fixes it: + # (when pos > n: this actually selects the right point, + # which is the correct choice, because right = arr[n] + # gets invalidated) + # + gamma = 0.0 if (pos < 1 or pos > n) else pos - k + + j = _floor_i(k) + return (gamma, j) + + +# Blom method +def _normal_unbiased(q: float, n: int) -> tuple[float, int]: + fract1 = 0.25 + fract2 = 3.0 / 8.0 + pos = q * (n + fract1) + fract2 + k = math.floor(pos) + + # gamma = pos-k + # + # this fixes it: + # (when pos > n: this actually selects the right point, + # which is the correct choice, because right = arr[n] + # gets invalidated) + # + gamma = 0.0 if (pos < 1 or pos > n) else pos - k + + j = _floor_i(k) + return (gamma, j) + + +def _lower(q: float, n: int) -> tuple[float, int]: + gamma = 0.0 + pos = q * (n - 1) + k = math.floor(pos) + + j = int(k) + return (gamma, j) + + +def _higher(q: float, n: int) -> tuple[float, int]: + pos = q * (n - 1) + k = math.floor(pos) + + # Generic rule: (k == pos) + gamma = 0.0 if (pos == 0 or k == pos) else 1.0 + + j = int(k) + return (gamma, j) + + +def _midpoint(q: float, n: int) -> tuple[float, int]: + pos = q * (n - 1) + k = math.floor(pos) + + # Generic rule: (k == pos) + gamma = 0.0 if (pos == 0 or k == pos) else 0.5 + + j = int(k) + return (gamma, j) + + +def _nearest(q: float, n: int) -> tuple[float, int]: + pos = q * (n - 1) + + # k = floor(pos) + # gamma = 1.0 if pos - k >= 0.5 else 0.0 + + k = np.round(pos) + gamma = 0.0 + + j = int(k) + return (gamma, j) + + +# args: +# +# arr: [in] source nd-array on which quantiles are calculated; +# preccondition: assumed sorted! +# q_arr: [in] quantile input values nd-array; +# axis: [in] axis along which quantiles are calculated; +# method: [in] func(q, n) returning (gamma, j), +# where = array1D.size; +# keepdims: [in] boolean flag specifying whether collapsed axis +# should be kept as dim=1; +# to_dtype: [in] dtype to convert the result to; +# qs_all: [in/out] result pass through or created (returned) +# +def _quantile_impl( + arr: ndarray, + q_arr: npt.NDArray[Any], + axis: Optional[int], + axes_set: Iterable[int], + original_shape: tuple[int, ...], + method: Callable[[float, int], tuple[float, int]], + keepdims: bool, + to_dtype: np.dtype[Any], + qs_all: Optional[ndarray], +) -> ndarray: + ndims = len(arr.shape) + + if axis is None: + n = arr.size + + if keepdims: + remaining_shape = (1,) * len(original_shape) + else: + remaining_shape = () # only `q_arr` dictates shape; + # quantile applied to `arr` seen as 1D; + else: + n = arr.shape[axis] + + # arr.shape -{axis}; if keepdims use 1 for arr.shape[axis]: + # (can be empty []) + # + if keepdims: + remaining_shape = tuple( + 1 if k in axes_set else original_shape[k] + for k in range(0, len(original_shape)) + ) + else: + remaining_shape = tuple( + arr.shape[k] for k in range(0, ndims) if k != axis + ) + + # compose qarr.shape with arr.shape: + # + # result.shape = (q_arr.shape, arr.shape -{axis}): + # + qresult_shape = (*q_arr.shape, *remaining_shape) + + # construct result NdArray, non-flattening approach: + # + if qs_all is None: + qs_all = zeros(qresult_shape, dtype=to_dtype) + else: + # implicit conversion from to_dtype to qs_all.dtype assumed + # + if qs_all.shape != qresult_shape: + raise ValueError("wrong shape on output array") + + for index, q in np.ndenumerate(q_arr): + (gamma, j) = method(q, n) + (left_pos, right_pos) = (j, j + 1) + + # (N-1) dimensional ndarray of left, right + # neighbor values: + # + # non-flattening approach: + # + # extract values at index=left_pos; + arr_1D_lvals = arr.take(left_pos, axis) + arr_vals_shape = arr_1D_lvals.shape + + if right_pos >= n: + # some quantile methods may result in j==(n-1), + # hence (j+1) could surpass array boundary; + # + arr_1D_rvals = zeros(arr_vals_shape, dtype=arr_1D_lvals.dtype) + else: + # extract values at index=right_pos; + arr_1D_rvals = arr.take(right_pos, axis) + + # vectorized for axis != None; + # (non-flattening approach) + # + if len(index) == 0: + left = (1.0 - gamma) * arr_1D_lvals.reshape(qs_all.shape) + right = gamma * arr_1D_rvals.reshape(qs_all.shape) + qs_all[...] = left + right + else: + left = (1.0 - gamma) * arr_1D_lvals.reshape(qs_all[index].shape) + right = gamma * arr_1D_rvals.reshape(qs_all[index].shape) + qs_all[index] = left + right + + return qs_all + + +@add_boilerplate("a") +def quantile( + a: ndarray, + q: Union[float, Iterable[float], ndarray], + axis: Union[None, int, tuple[int, ...]] = None, + out: Optional[ndarray] = None, + overwrite_input: bool = False, + method: str = "linear", + keepdims: bool = False, +) -> ndarray: + """ + Compute the q-th quantile of the data along the specified axis. + + Parameters + ---------- + a : array_like + Input array or object that can be converted to an array. + q : array_like of float + Quantile or sequence of quantiles to compute, which must be between + 0 and 1 inclusive. + axis : {int, tuple of int, None}, optional + Axis or axes along which the quantiles are computed. The default is + to compute the quantile(s) along a flattened version of the array. + out : ndarray, optional + Alternative output array in which to place the result. It must have + the same shape as the expected output. + overwrite_input : bool, optional + If True, then allow the input array `a` to be modified by + intermediate calculations, to save memory. In this case, the + contents of the input `a` after this function completes is + undefined. + method : str, optional + This parameter specifies the method to use for estimating the + quantile. The options sorted by their R type + as summarized in the H&F paper [1]_ are: + 1. 'inverted_cdf' + 2. 'averaged_inverted_cdf' + 3. 'closest_observation' + 4. 'interpolated_inverted_cdf' + 5. 'hazen' + 6. 'weibull' + 7. 'linear' (default) + 8. 'median_unbiased' + 9. 'normal_unbiased' + The first three methods are discontinuous. NumPy further defines the + following discontinuous variations of the default 'linear' (7.) option: + * 'lower' + * 'higher', + * 'midpoint' + * 'nearest' + keepdims : bool, optional + If this is set to True, the axes which are reduced are left in + the result as dimensions with size one. With this option, the + result will broadcast correctly against the original array `a`. + + Returns + ------- + quantile : scalar or ndarray + If `q` is a single quantile and `axis=None`, then the result + is a scalar. If multiple quantiles are given, first axis of + the result corresponds to the quantiles. The other axes are + the axes that remain after the reduction of `a`. If the input + contains integers or floats smaller than ``float64``, the output + data-type is ``float64``. Otherwise, the output data-type is the + same as that of the input. If `out` is specified, that array is + returned instead. + + Raises + ------ + TypeError + If the type of the input is complex. + + See Also + -------- + numpy.quantile + + Availability + -------- + Multiple GPUs, Multiple CPUs + + References + ---------- + .. [1] R. J. Hyndman and Y. Fan, + "Sample quantiles in statistical packages," + The American Statistician, 50(4), pp. 361-365, 1996 + """ + + dict_methods = { + "inverted_cdf": _inverted_cdf, + "averaged_inverted_cdf": _averaged_inverted_cdf, + "closest_observation": _closest_observation, + "interpolated_inverted_cdf": _interpolated_inverted_cdf, + "hazen": _hazen, + "weibull": _weibull, + "linear": _linear, + "median_unbiased": _median_unbiased, + "normal_unbiased": _normal_unbiased, + "lower": _lower, + "higher": _higher, + "midpoint": _midpoint, + "nearest": _nearest, + } + + real_axis: Optional[int] + axes_set: Iterable[int] = [] + original_shape = a.shape + + if axis is not None and isinstance(axis, Iterable): + if len(axis) == 1: + real_axis = axis[0] + a_rr = a + else: + (real_axis, a_rr) = _reshuffle_reshape(a, axis) + # What happens with multiple axes and overwrite_input = True ? + # It seems overwrite_input is reset to False; + overwrite_input = False + axes_set = axis + else: + real_axis = axis + a_rr = a + if real_axis is not None: + axes_set = [real_axis] + + # covers both array-like and scalar cases: + # + q_arr = np.asarray(q) + + # in the future k-sort (partition) + # might be faster, for now it uses sort + # arr = partition(arr, k = floor(nq), axis = real_axis) + # but that would require a k-sort call for each `q`! + # too expensive for many `q` values... + # if no axis given then elements are sorted as a 1D array + # + if overwrite_input: + a_rr.sort(axis=real_axis) + arr = a_rr + else: + arr = sort(a_rr, axis=real_axis) + + if arr.dtype.kind == "c": + raise TypeError("input array cannot be of complex type") + + # return type dependency on arr.dtype: + # + # it depends on interpolation method; + # For discontinuous methods returning either end of the interval within + # which the quantile falls, or the other; arr.dtype is returned; + # else, logic below: + # + # if is_float(arr_dtype) && (arr.dtype >= dtype('float64')) then + # arr.dtype + # else + # dtype('float64') + # + # see https://github.com/numpy/numpy/issues/22323 + # + if method in [ + "inverted_cdf", + "closest_observation", + "lower", + "higher", + "nearest", + ]: + to_dtype = arr.dtype + else: + to_dtype = np.dtype("float64") + + # in case dtype("float128") becomes supported: + # + # to_dtype = ( + # arr.dtype + # if (arr.dtype == np.dtype("float128")) + # else np.dtype("float64") + # ) + + res = _quantile_impl( + arr, + q_arr, + real_axis, + axes_set, + original_shape, + dict_methods[method], + keepdims, + to_dtype, + out, + ) + + if out is not None: + # out = res.astype(out.dtype) -- conversion done inside impl + return out + else: + return res + + +@add_boilerplate("a") +def percentile( + a: ndarray, + q: Union[float, Iterable[float], ndarray], + axis: Union[None, int, tuple[int, ...]] = None, + out: Optional[ndarray] = None, + overwrite_input: bool = False, + method: str = "linear", + keepdims: bool = False, +) -> ndarray: + """ + Compute the q-th percentile of the data along the specified axis. + + Parameters + ---------- + a : array_like + Input array or object that can be converted to an array. + q : array_like of float + Percentile or sequence of percentiles to compute, which must be between + 0 and 100 inclusive. + axis : {int, tuple of int, None}, optional + Axis or axes along which the percentiles are computed. The default is + to compute the percentile(s) along a flattened version of the array. + out : ndarray, optional + Alternative output array in which to place the result. It must have + the same shape as the expected output. + overwrite_input : bool, optional + If True, then allow the input array `a` to be modified by + intermediate calculations, to save memory. In this case, the + contents of the input `a` after this function completes is + undefined. + method : str, optional + This parameter specifies the method to use for estimating the + percentile. The options sorted by their R type + as summarized in the H&F paper [1]_ are: + 1. 'inverted_cdf' + 2. 'averaged_inverted_cdf' + 3. 'closest_observation' + 4. 'interpolated_inverted_cdf' + 5. 'hazen' + 6. 'weibull' + 7. 'linear' (default) + 8. 'median_unbiased' + 9. 'normal_unbiased' + The first three methods are discontinuous. NumPy further defines the + following discontinuous variations of the default 'linear' (7.) option: + * 'lower' + * 'higher', + * 'midpoint' + * 'nearest' + keepdims : bool, optional + If this is set to True, the axes which are reduced are left in + the result as dimensions with size one. With this option, the + result will broadcast correctly against the original array `a`. + + Returns + ------- + percentile : scalar or ndarray + If `q` is a single percentile and `axis=None`, then the result + is a scalar. If multiple percentiles are given, first axis of + the result corresponds to the percentiles. The other axes are + the axes that remain after the reduction of `a`. If the input + contains integers or floats smaller than ``float64``, the output + data-type is ``float64``. Otherwise, the output data-type is the + same as that of the input. If `out` is specified, that array is + returned instead. + + Raises + ------ + TypeError + If the type of the input is complex. + + See Also + -------- + numpy.percentile + + Availability + -------- + Multiple GPUs, Multiple CPUs + + References + ---------- + .. [1] R. J. Hyndman and Y. Fan, + "Sample quantiles in statistical packages," + The American Statistician, 50(4), pp. 361-365, 1996 + """ + + q_arr = np.asarray(q) + q01 = q_arr / 100.0 + + return quantile( + a, + q01, + axis, + out=out, + overwrite_input=overwrite_input, + method=method, + keepdims=keepdims, + ) diff --git a/cunumeric/array.py b/cunumeric/array.py index 12012ee6ea..2c77fd441a 100644 --- a/cunumeric/array.py +++ b/cunumeric/array.py @@ -189,7 +189,7 @@ def broadcast_where( where: Union[ndarray, None], shape: NdShape ) -> Union[ndarray, None]: if where is not None and where.shape != shape: - from .module import broadcast_to + from ._module import broadcast_to where = broadcast_to(where, shape) return where @@ -2684,7 +2684,9 @@ def dot(self, rhs: ndarray, out: Union[ndarray, None] = None) -> ndarray: Multiple GPUs, Multiple CPUs """ - from .module import _contract # work around circular import + from ._module.linalg_mvp import ( # work around circular import + _contract, + ) if self.ndim == 0 or rhs.ndim == 0: from ._ufunc import multiply diff --git a/cunumeric/bits.py b/cunumeric/bits.py index 1cb3995c03..9adb4dc102 100644 --- a/cunumeric/bits.py +++ b/cunumeric/bits.py @@ -16,8 +16,8 @@ from typing import TYPE_CHECKING, Optional, Tuple +from ._module import empty from .array import add_boilerplate -from .module import empty if TYPE_CHECKING: from .array import ndarray diff --git a/cunumeric/deferred.py b/cunumeric/deferred.py index 9d122308ed..cd22fae8f9 100644 --- a/cunumeric/deferred.py +++ b/cunumeric/deferred.py @@ -375,7 +375,7 @@ def _zip_indices( # find a broadcasted shape for all arrays passed as indices shapes = tuple(a.shape for a in arrays) if len(arrays) > 1: - from .module import broadcast_shapes + from ._module import broadcast_shapes b_shape = broadcast_shapes(*shapes) else: diff --git a/cunumeric/fft/fft.py b/cunumeric/fft/fft.py index b10ce8c98c..dd58934b23 100644 --- a/cunumeric/fft/fft.py +++ b/cunumeric/fft/fft.py @@ -18,8 +18,8 @@ import numpy as np +from .._module import add_boilerplate from ..config import FFT_C2C, FFT_Z2Z, FFTCode, FFTDirection, FFTNormalization -from ..module import add_boilerplate if TYPE_CHECKING: from ..array import ndarray diff --git a/cunumeric/linalg/linalg.py b/cunumeric/linalg/linalg.py index 094d0732a9..fd6c85795b 100644 --- a/cunumeric/linalg/linalg.py +++ b/cunumeric/linalg/linalg.py @@ -24,9 +24,9 @@ normalize_axis_tuple, ) +from .._module import dot, empty_like, eye, matmul, ndarray from .._ufunc.math import add, sqrt as _sqrt from ..array import add_boilerplate, convert_to_cunumeric_ndarray -from ..module import dot, empty_like, eye, matmul, ndarray from ._exception import LinAlgError if TYPE_CHECKING: diff --git a/cunumeric/logic.py b/cunumeric/logic.py index 667ae1d135..25f3090282 100644 --- a/cunumeric/logic.py +++ b/cunumeric/logic.py @@ -18,10 +18,10 @@ import numpy as np +from ._module import full from ._ufunc.comparison import logical_and from ._ufunc.floating import isinf, signbit from .array import convert_to_cunumeric_ndarray, ndarray -from .module import full if TYPE_CHECKING: import numpy.typing as npt diff --git a/cunumeric/module.py b/cunumeric/module.py deleted file mode 100644 index cbe5c2eff5..0000000000 --- a/cunumeric/module.py +++ /dev/null @@ -1,8284 +0,0 @@ -# Copyright 2021-2023 NVIDIA Corporation -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# -from __future__ import annotations - -import math -import operator -import re -from collections import Counter -from itertools import chain -from typing import ( - TYPE_CHECKING, - Any, - Iterable, - Literal, - Optional, - Sequence, - Tuple, - Union, - cast, -) - -import numpy as np -import opt_einsum as oe # type: ignore [import] -from legate.core import Scalar, types as ty -from numpy.core.multiarray import ( # type: ignore [attr-defined] - normalize_axis_index, -) -from numpy.core.numeric import ( # type: ignore [attr-defined] - normalize_axis_tuple, -) - -from ._ufunc.comparison import maximum, minimum -from ._ufunc.floating import floor, isnan -from ._ufunc.math import add, multiply -from ._unary_red_utils import get_non_nan_unary_red_code -from .array import ( - add_boilerplate, - check_writeable, - convert_to_cunumeric_ndarray, - ndarray, -) -from .config import BinaryOpCode, ScanCode, UnaryRedCode -from .coverage import is_implemented -from .runtime import runtime -from .settings import settings as cunumeric_settings -from .types import NdShape, NdShapeLike, OrderType, SortSide -from .utils import AxesPairLike, inner_modes, matmul_modes, tensordot_modes - -if TYPE_CHECKING: - from typing import Callable - - import numpy.typing as npt - - from ._ufunc.ufunc import CastingKind - from .types import BoundsMode, ConvolveMode, SelectKind, SortType - -_builtin_abs = abs -_builtin_all = all -_builtin_any = any -_builtin_max = max -_builtin_min = min -_builtin_sum = sum -_builtin_range = range - -casting_kinds: tuple[CastingKind, ...] = ( - "no", - "equiv", - "safe", - "same_kind", - "unsafe", -) - -######################### -# Array creation routines -######################### - -# From shape or value - - -def empty(shape: NdShapeLike, dtype: npt.DTypeLike = np.float64) -> ndarray: - """ - empty(shape, dtype=float) - - Return a new array of given shape and type, without initializing entries. - - Parameters - ---------- - shape : int or tuple[int] - Shape of the empty array. - dtype : data-type, optional - Desired output data-type for the array. Default is `cunumeric.float64`. - - Returns - ------- - out : ndarray - Array of uninitialized (arbitrary) data of the given shape and dtype. - - See Also - -------- - numpy.empty - - Availability - -------- - Multiple GPUs, Multiple CPUs - """ - return ndarray(shape=shape, dtype=dtype) - - -@add_boilerplate("a") -def empty_like( - a: ndarray, - dtype: Optional[npt.DTypeLike] = None, - shape: Optional[NdShapeLike] = None, -) -> ndarray: - """ - - empty_like(prototype, dtype=None) - - Return a new array with the same shape and type as a given array. - - Parameters - ---------- - prototype : array_like - The shape and data-type of `prototype` define these same attributes - of the returned array. - dtype : data-type, optional - Overrides the data type of the result. - shape : int or tuple[int], optional - Overrides the shape of the result. - - Returns - ------- - out : ndarray - Array of uninitialized (arbitrary) data with the same shape and type as - `prototype`. - - See Also - -------- - numpy.empty_like - - Availability - -------- - Multiple GPUs, Multiple CPUs - """ - shape = a.shape if shape is None else shape - if dtype is not None: - dtype = np.dtype(dtype) - else: - dtype = a.dtype - return ndarray(shape, dtype=dtype, inputs=(a,)) - - -def eye( - N: int, - M: Optional[int] = None, - k: int = 0, - dtype: Optional[npt.DTypeLike] = np.float64, -) -> ndarray: - """ - - Return a 2-D array with ones on the diagonal and zeros elsewhere. - - Parameters - ---------- - N : int - Number of rows in the output. - M : int, optional - Number of columns in the output. If None, defaults to `N`. - k : int, optional - Index of the diagonal: 0 (the default) refers to the main diagonal, - a positive value refers to an upper diagonal, and a negative value - to a lower diagonal. - dtype : data-type, optional - Data-type of the returned array. - - Returns - ------- - I : ndarray - An array of shape (N, M) where all elements are equal to zero, except - for the `k`-th diagonal, whose values are equal to one. - - See Also - -------- - numpy.eye - - Availability - -------- - Multiple GPUs, Multiple CPUs - """ - if dtype is not None: - dtype = np.dtype(dtype) - if M is None: - M = N - k = operator.index(k) - result = ndarray((N, M), dtype) - result._thunk.eye(k) - return result - - -def identity(n: int, dtype: npt.DTypeLike = float) -> ndarray: - """ - - Return the identity array. - - The identity array is a square array with ones on - the main diagonal. - - Parameters - ---------- - n : int - Number of rows (and columns) in `n` x `n` output. - dtype : data-type, optional - Data-type of the output. Defaults to ``float``. - - Returns - ------- - out : ndarray - `n` x `n` array with its main diagonal set to one, and all other - elements 0. - - See Also - -------- - numpy.identity - - Availability - -------- - Multiple GPUs, Multiple CPUs - """ - return eye(N=n, M=n, dtype=dtype) - - -def ones(shape: NdShapeLike, dtype: npt.DTypeLike = np.float64) -> ndarray: - """ - - Return a new array of given shape and type, filled with ones. - - Parameters - ---------- - shape : int or tuple[int] - Shape of the new array. - dtype : data-type, optional - The desired data-type for the array. Default is `cunumeric.float64`. - - Returns - ------- - out : ndarray - Array of ones with the given shape and dtype. - - See Also - -------- - numpy.ones - - Availability - -------- - Multiple GPUs, Multiple CPUs - """ - return full(shape, 1, dtype=dtype) - - -def ones_like( - a: ndarray, - dtype: Optional[npt.DTypeLike] = None, - shape: Optional[NdShapeLike] = None, -) -> ndarray: - """ - - Return an array of ones with the same shape and type as a given array. - - Parameters - ---------- - a : array_like - The shape and data-type of `a` define these same attributes of the - returned array. - dtype : data-type, optional - Overrides the data type of the result. - shape : int or tuple[int], optional - Overrides the shape of the result. - - Returns - ------- - out : ndarray - Array of ones with the same shape and type as `a`. - - See Also - -------- - numpy.ones_like - - Availability - -------- - Multiple GPUs, Multiple CPUs - """ - usedtype = a.dtype - if dtype is not None: - usedtype = np.dtype(dtype) - return full_like(a, 1, dtype=usedtype, shape=shape) - - -def zeros(shape: NdShapeLike, dtype: npt.DTypeLike = np.float64) -> ndarray: - """ - zeros(shape, dtype=float) - - Return a new array of given shape and type, filled with zeros. - - Parameters - ---------- - shape : int or tuple[int] - Shape of the new array. - dtype : data-type, optional - The desired data-type for the array. Default is `cunumeric.float64`. - - Returns - ------- - out : ndarray - Array of zeros with the given shape and dtype. - - See Also - -------- - numpy.zeros - - Availability - -------- - Multiple GPUs, Multiple CPUs - """ - if dtype is not None: - dtype = np.dtype(dtype) - return full(shape, 0, dtype=dtype) - - -def zeros_like( - a: ndarray, - dtype: Optional[npt.DTypeLike] = None, - shape: Optional[NdShapeLike] = None, -) -> ndarray: - """ - - Return an array of zeros with the same shape and type as a given array. - - Parameters - ---------- - a : array_like - The shape and data-type of `a` define these same attributes of - the returned array. - dtype : data-type, optional - Overrides the data type of the result. - shape : int or tuple[int], optional - Overrides the shape of the result. - - Returns - ------- - out : ndarray - Array of zeros with the same shape and type as `a`. - - See Also - -------- - numpy.zeros_like - - Availability - -------- - Multiple GPUs, Multiple CPUs - """ - usedtype = a.dtype - if dtype is not None: - usedtype = np.dtype(dtype) - return full_like(a, 0, dtype=usedtype, shape=shape) - - -def full( - shape: NdShapeLike, - value: Any, - dtype: Optional[npt.DTypeLike] = None, -) -> ndarray: - """ - - Return a new array of given shape and type, filled with `fill_value`. - - Parameters - ---------- - shape : int or tuple[int] - Shape of the new array. - fill_value : scalar - Fill value. - dtype : data-type, optional - The desired data-type for the array The default, None, means - `cunumeric.array(fill_value).dtype`. - - Returns - ------- - out : ndarray - Array of `fill_value` with the given shape and dtype. - - See Also - -------- - numpy.full - - Availability - -------- - Multiple GPUs, Multiple CPUs - """ - if dtype is None: - val = np.array(value) - else: - dtype = np.dtype(dtype) - val = np.array(value, dtype=dtype) - result = empty(shape, dtype=val.dtype) - result._thunk.fill(val) - return result - - -def full_like( - a: ndarray, - value: Union[int, float], - dtype: Optional[npt.DTypeLike] = None, - shape: Optional[NdShapeLike] = None, -) -> ndarray: - """ - - Return a full array with the same shape and type as a given array. - - Parameters - ---------- - a : array_like - The shape and data-type of `a` define these same attributes of - the returned array. - fill_value : scalar - Fill value. - dtype : data-type, optional - Overrides the data type of the result. - shape : int or tuple[int], optional - Overrides the shape of the result. - - Returns - ------- - out : ndarray - Array of `fill_value` with the same shape and type as `a`. - - See Also - -------- - numpy.full_like - - Availability - -------- - Multiple GPUs, Multiple CPUs - """ - if dtype is not None: - dtype = np.dtype(dtype) - else: - dtype = a.dtype - result = empty_like(a, dtype=dtype, shape=shape) - val = np.array(value, dtype=result.dtype) - result._thunk.fill(val) - return result - - -# From existing data - - -def array( - obj: Any, - dtype: Optional[np.dtype[Any]] = None, - copy: bool = True, - order: Union[OrderType, Literal["K"]] = "K", - subok: bool = False, - ndmin: int = 0, -) -> ndarray: - """ - array(object, dtype=None, copy=True) - - Create an array. - - Parameters - ---------- - object : array_like - An array, any object exposing the array interface, an object whose - __array__ method returns an array, or any (nested) sequence. - dtype : data-type, optional - The desired data-type for the array. If not given, then the type will - be determined as the minimum type required to hold the objects in the - sequence. - copy : bool, optional - If true (default), then the object is copied. Otherwise, a copy will - only be made if __array__ returns a copy, if obj is a nested sequence, - or if a copy is needed to satisfy any of the other requirements - (`dtype`, `order`, etc.). - order : ``{'K', 'A', 'C', 'F'}``, optional - Specify the memory layout of the array. If object is not an array, the - newly created array will be in C order (row major) unless 'F' is - specified, in which case it will be in Fortran order (column major). - If object is an array the following holds. - - ===== ========= =================================================== - order no copy copy=True - ===== ========= =================================================== - 'K' unchanged F & C order preserved, otherwise most similar order - 'A' unchanged F order if input is F and not C, otherwise C order - 'C' C order C order - 'F' F order F order - ===== ========= =================================================== - - When ``copy=False`` and a copy is made for other reasons, the result is - the same as if ``copy=True``, with some exceptions for 'A', see the - Notes section. The default order is 'K'. - subok : bool, optional - If True, then sub-classes will be passed-through, otherwise - the returned array will be forced to be a base-class array (default). - ndmin : int, optional - Specifies the minimum number of dimensions that the resulting - array should have. Ones will be pre-pended to the shape as - needed to meet this requirement. - - Returns - ------- - out : ndarray - An array object satisfying the specified requirements. - - See Also - -------- - numpy.array - - Availability - -------- - Multiple GPUs, Multiple CPUs - """ - - if not isinstance(obj, ndarray): - thunk = runtime.get_numpy_thunk(obj, share=(not copy), dtype=dtype) - result = ndarray(shape=None, thunk=thunk) - else: - result = obj - if dtype is not None and result.dtype != dtype: - result = result.astype(dtype) - elif copy and obj is result: - result = result.copy() - if result.ndim < ndmin: - shape = (1,) * (ndmin - result.ndim) + result.shape - result = result.reshape(shape) - return result - - -def asarray(a: Any, dtype: Optional[np.dtype[Any]] = None) -> ndarray: - """ - Convert the input to an array. - - Parameters - ---------- - a : array_like - Input data, in any form that can be converted to an array. This - includes lists, lists of tuples, tuples, tuples of tuples, tuples - of lists and ndarrays. - dtype : data-type, optional - By default, the data-type is inferred from the input data. - - Returns - ------- - out : ndarray - Array interpretation of `a`. No copy is performed if the input is - already an ndarray with matching dtype. If `a` is a subclass of - ndarray, a base class ndarray is returned. - - See Also - -------- - numpy.asarray - - Availability - -------- - Multiple GPUs, Multiple CPUs - """ - if not isinstance(a, ndarray): - thunk = runtime.get_numpy_thunk(a, share=True, dtype=dtype) - writeable = a.flags.writeable if isinstance(a, np.ndarray) else True - array = ndarray(shape=None, thunk=thunk, writeable=writeable) - else: - array = a - if dtype is not None and array.dtype != dtype: - array = array.astype(dtype) - return array - - -@add_boilerplate("a") -def copy(a: ndarray) -> ndarray: - """ - - Return an array copy of the given object. - - Parameters - ---------- - a : array_like - Input data. - - Returns - ------- - arr : ndarray - Array interpretation of `a`. - - See Also - -------- - numpy.copy - - Availability - -------- - Multiple GPUs, Multiple CPUs - """ - result = empty_like(a, dtype=a.dtype) - result._thunk.copy(a._thunk, deep=True) - return result - - -# Numerical ranges - - -def arange( - start: Union[int, float] = 0, - stop: Optional[Union[int, float]] = None, - step: Optional[Union[int, float]] = 1, - dtype: Optional[npt.DTypeLike] = None, -) -> ndarray: - """ - arange([start,] stop[, step,], dtype=None) - - Return evenly spaced values within a given interval. - - Values are generated within the half-open interval ``[start, stop)`` - (in other words, the interval including `start` but excluding `stop`). - For integer arguments the function is equivalent to the Python built-in - `range` function, but returns an ndarray rather than a list. - - When using a non-integer step, such as 0.1, the results will often not - be consistent. It is better to use `cunumeric.linspace` for these cases. - - Parameters - ---------- - start : int or float, optional - Start of interval. The interval includes this value. The default - start value is 0. - stop : int or float - End of interval. The interval does not include this value, except - in some cases where `step` is not an integer and floating point - round-off affects the length of `out`. - step : int or float, optional - Spacing between values. For any output `out`, this is the distance - between two adjacent values, ``out[i+1] - out[i]``. The default - step size is 1. If `step` is specified as a position argument, - `start` must also be given. - dtype : data-type - The type of the output array. If `dtype` is not given, infer the data - type from the other input arguments. - - Returns - ------- - arange : ndarray - Array of evenly spaced values. - - For floating point arguments, the length of the result is - ``ceil((stop - start)/step)``. Because of floating point overflow, - this rule may result in the last element of `out` being greater - than `stop`. - - See Also - -------- - numpy.arange - - Availability - -------- - Multiple GPUs, Multiple CPUs - """ - if stop is None: - stop = start - start = 0 - - if step is None: - step = 1 - - if dtype is None: - dtype = np.result_type(start, stop, step) - else: - dtype = np.dtype(dtype) - - N = math.ceil((stop - start) / step) - result = ndarray((_builtin_max(0, N),), dtype) - result._thunk.arange(start, stop, step) - return result - - -@add_boilerplate("start", "stop") -def linspace( - start: ndarray, - stop: ndarray, - num: int = 50, - endpoint: bool = True, - retstep: bool = False, - dtype: Optional[npt.DTypeLike] = None, - axis: int = 0, -) -> Union[ndarray, tuple[ndarray, Union[float, ndarray]]]: - """ - - Return evenly spaced numbers over a specified interval. - - Returns `num` evenly spaced samples, calculated over the - interval [`start`, `stop`]. - - The endpoint of the interval can optionally be excluded. - - Parameters - ---------- - start : array_like - The starting value of the sequence. - stop : array_like - The end value of the sequence, unless `endpoint` is set to False. - In that case, the sequence consists of all but the last of ``num + 1`` - evenly spaced samples, so that `stop` is excluded. Note that the step - size changes when `endpoint` is False. - num : int, optional - Number of samples to generate. Default is 50. Must be non-negative. - endpoint : bool, optional - If True, `stop` is the last sample. Otherwise, it is not included. - Default is True. - retstep : bool, optional - If True, return (`samples`, `step`), where `step` is the spacing - between samples. - dtype : data-type, optional - The type of the output array. If `dtype` is not given, infer the data - type from the other input arguments. - axis : int, optional - The axis in the result to store the samples. Relevant only if start - or stop are array-like. By default (0), the samples will be along a - new axis inserted at the beginning. Use -1 to get an axis at the end. - - Returns - ------- - samples : ndarray - There are `num` equally spaced samples in the closed interval - ``[start, stop]`` or the half-open interval ``[start, stop)`` - (depending on whether `endpoint` is True or False). - step : float or ndarray, optional - Only returned if `retstep` is True - - Size of spacing between samples. - - See Also - -------- - numpy.linspace - - Availability - -------- - Multiple GPUs, Multiple CPUs - """ - if num < 0: - raise ValueError("Number of samples, %s, must be non-negative." % num) - div = (num - 1) if endpoint else num - - common_kind = np.result_type(start.dtype, stop.dtype).kind - dt = np.complex128 if common_kind == "c" else np.float64 - if dtype is None: - dtype = dt - - delta = stop - start - y = arange(0, num, dtype=dt) - - out: tuple[Any, ...] # EllipsisType not even in typing_extensions yet - - # Reshape these arrays into dimensions that allow them to broadcast - if delta.ndim > 0: - if axis is None or axis == 0: - # First dimension - y = y.reshape((-1,) + (1,) * delta.ndim) - # Nothing else needs to be reshaped here because - # they should all broadcast correctly with y - if endpoint and num > 1: - out = (-1,) - elif axis == -1 or axis == delta.ndim: - # Last dimension - y = y.reshape((1,) * delta.ndim + (-1,)) - if endpoint and num > 1: - out = (Ellipsis, -1) - # Extend everything else with extra dimensions of 1 at the end - # so that they can broadcast with y - delta = delta.reshape(delta.shape + (1,)) - start = start.reshape(start.shape + (1,)) - elif axis < delta.ndim: - # Somewhere in the middle - y = y.reshape((1,) * axis + (-1,) + (1,) * (delta.ndim - axis)) - # Start array might be smaller than delta because of broadcast - startax = start.ndim - len(delta.shape[axis:]) - start = start.reshape( - start.shape[0:startax] + (1,) + start.shape[startax:] - ) - if endpoint and num > 1: - out = (Ellipsis, -1) + (slice(None, None, None),) * len( - delta.shape[axis:] - ) - delta = delta.reshape( - delta.shape[0:axis] + (1,) + delta.shape[axis:] - ) - else: - raise ValueError( - "axis " - + str(axis) - + " is out of bounds for array of dimension " - + str(delta.ndim + 1) - ) - else: - out = (-1,) - # else delta is a scalar so start must be also - # therefore it will trivially broadcast correctly - - step: Union[float, ndarray] - if div > 0: - step = delta / div - if delta.ndim == 0: - y *= step - else: - y = y * step - else: - # sequences with 0 items or 1 item with endpoint=True (i.e. div <= 0) - # have an undefined step - step = np.NaN - if delta.ndim == 0: - y *= delta - else: - y = y * delta - - y += start.astype(y.dtype, copy=False) - - if endpoint and num > 1: - y[out] = stop.astype(y.dtype, copy=False) - - if np.issubdtype(dtype, np.integer): - floor(y, out=y) - - if retstep: - return y.astype(dtype, copy=False), step - else: - return y.astype(dtype, copy=False) - - -# Building matrices - - -@add_boilerplate("v") -def diag(v: ndarray, k: int = 0) -> ndarray: - """ - - Extract a diagonal or construct a diagonal array. - - See the more detailed documentation for ``cunumeric.diagonal`` if you use - this function to extract a diagonal and wish to write to the resulting - array; whether it returns a copy or a view depends on what version of numpy - you are using. - - Parameters - ---------- - v : array_like - If `v` is a 2-D array, return a copy of its `k`-th diagonal. - If `v` is a 1-D array, return a 2-D array with `v` on the `k`-th - diagonal. - k : int, optional - Diagonal in question. The default is 0. Use `k>0` for diagonals - above the main diagonal, and `k<0` for diagonals below the main - diagonal. - - Returns - ------- - out : ndarray - The extracted diagonal or constructed diagonal array. - - See Also - -------- - numpy.diag - - Availability - -------- - Multiple GPUs, Multiple CPUs - """ - if v.ndim == 0: - raise ValueError("Input must be 1- or 2-d") - elif v.ndim == 1: - return v.diagonal(offset=k, axis1=0, axis2=1, extract=False) - elif v.ndim == 2: - return v.diagonal(offset=k, axis1=0, axis2=1, extract=True) - else: - raise ValueError("diag requires 1- or 2-D array, use diagonal instead") - - -def tri( - N: int, - M: Optional[int] = None, - k: int = 0, - dtype: npt.DTypeLike = float, - *, - like: Optional[ndarray] = None, -) -> ndarray: - """ - An array with ones at and below the given diagonal and zeros elsewhere. - - Parameters - ---------- - N : int - Number of rows in the array. - M : int, optional - Number of columns in the array. - By default, `M` is taken equal to `N`. - k : int, optional - The sub-diagonal at and below which the array is filled. - `k` = 0 is the main diagonal, while `k` < 0 is below it, - and `k` > 0 is above. The default is 0. - dtype : dtype, optional - Data type of the returned array. The default is float. - like : array_like - Reference object to allow the creation of arrays which are not NumPy - arrays. If an array-like passed in as `like` supports the - `__array_function__` protocol, the result will be defined by it. In - this case it ensures the creation of an array object compatible with - that passed in via this argument. - - Returns - ------- - tri : ndarray of shape (N, M) - Array with its lower triangle filled with ones and zero elsewhere; - in other words ``T[i,j] == 1`` for ``j <= i + k``, 0 otherwise. - - See Also - -------- - numpy.tri - - Notes - ----- - `like` argument is currently not supported - - Availability - -------- - Multiple GPUs, Multiple CPUs - - """ - # TODO: add support for `like` (see issue #418) - if like is not None: - raise ValueError("like parameter is currently not supported") - - if M is None: - M = N - - out = ones((N, M), dtype=dtype) - return tril(out, k) - - -@add_boilerplate("m") -def trilu(m: ndarray, k: int, lower: bool) -> ndarray: - if m.ndim < 1: - raise TypeError("Array must be at least 1-D") - shape = m.shape if m.ndim >= 2 else m.shape * 2 - result = ndarray(shape, dtype=m.dtype, inputs=(m,)) - result._thunk.trilu(m._thunk, k, lower) - return result - - -def tril(m: ndarray, k: int = 0) -> ndarray: - """ - - Lower triangle of an array. - - Return a copy of an array with elements above the `k`-th diagonal zeroed. - - Parameters - ---------- - m : array_like - Input array of shape (M, N). - k : int, optional - Diagonal above which to zero elements. `k = 0` (the default) is the - main diagonal, `k < 0` is below it and `k > 0` is above. - - Returns - ------- - tril : ndarray - Lower triangle of `m`, of same shape and data-type as `m`. - - See Also - -------- - numpy.tril - - Availability - -------- - Multiple GPUs, Multiple CPUs - """ - return trilu(m, k, True) - - -def triu(m: ndarray, k: int = 0) -> ndarray: - """ - - Upper triangle of an array. - - Return a copy of a matrix with the elements below the `k`-th diagonal - zeroed. - - Please refer to the documentation for `tril` for further details. - - See Also - -------- - numpy.triu - - Availability - -------- - Multiple GPUs, Multiple CPUs - """ - return trilu(m, k, False) - - -############################# -# Array manipulation routines -############################# - -# Basic operations - - -@add_boilerplate("a") -def ndim(a: ndarray) -> int: - """ - - Return the number of dimensions of an array. - - Parameters - ---------- - a : array_like - Input array. If it is not already an ndarray, a conversion is - attempted. - - Returns - ------- - number_of_dimensions : int - The number of dimensions in `a`. Scalars are zero-dimensional. - - See Also - -------- - ndarray.ndim : equivalent method - shape : dimensions of array - ndarray.shape : dimensions of array - - Availability - -------- - Multiple GPUs, Multiple CPUs - """ - return 0 if a is None else a.ndim - - -@add_boilerplate("a") -def shape(a: ndarray) -> NdShape: - """ - - Return the shape of an array. - - Parameters - ---------- - a : array_like - Input array. - - Returns - ------- - shape : tuple[int, ...] - The elements of the shape tuple give the lengths of the - corresponding array dimensions. - - See Also - -------- - numpy.shape - - Availability - -------- - Multiple GPUs, Multiple CPUs - """ - return a.shape - - -# Changing array shape - - -@add_boilerplate("a") -def ravel(a: ndarray, order: OrderType = "C") -> ndarray: - """ - Return a contiguous flattened array. - - A 1-D array, containing the elements of the input, is returned. A copy is - made only if needed. - - Parameters - ---------- - a : array_like - Input array. The elements in `a` are read in the order specified by - `order`, and packed as a 1-D array. - order : ``{'C','F', 'A', 'K'}``, optional - The elements of `a` are read using this index order. 'C' means - to index the elements in row-major, C-style order, - with the last axis index changing fastest, back to the first - axis index changing slowest. 'F' means to index the elements - in column-major, Fortran-style order, with the - first index changing fastest, and the last index changing - slowest. Note that the 'C' and 'F' options take no account of - the memory layout of the underlying array, and only refer to - the order of axis indexing. 'A' means to read the elements in - Fortran-like index order if `a` is Fortran *contiguous* in - memory, C-like order otherwise. 'K' means to read the - elements in the order they occur in memory, except for - reversing the data when strides are negative. By default, 'C' - index order is used. - - Returns - ------- - y : array_like - y is an array of the same subtype as `a`, with shape ``(a.size,)``. - Note that matrices are special cased for backward compatibility, if `a` - is a matrix, then y is a 1-D ndarray. - - See Also - -------- - numpy.ravel - - Availability - -------- - Multiple GPUs, Multiple CPUs - """ - return a.ravel(order=order) - - -@add_boilerplate("a") -def reshape( - a: ndarray, newshape: NdShapeLike, order: OrderType = "C" -) -> ndarray: - """ - - Gives a new shape to an array without changing its data. - - Parameters - ---------- - a : array_like - Array to be reshaped. - newshape : int or tuple[int] - The new shape should be compatible with the original shape. If - an integer, then the result will be a 1-D array of that length. - One shape dimension can be -1. In this case, the value is - inferred from the length of the array and remaining dimensions. - order : ``{'C', 'F', 'A'}``, optional - Read the elements of `a` using this index order, and place the - elements into the reshaped array using this index order. 'C' - means to read / write the elements using C-like index order, - with the last axis index changing fastest, back to the first - axis index changing slowest. 'F' means to read / write the - elements using Fortran-like index order, with the first index - changing fastest, and the last index changing slowest. Note that - the 'C' and 'F' options take no account of the memory layout of - the underlying array, and only refer to the order of indexing. - 'A' means to read / write the elements in Fortran-like index - order if `a` is Fortran *contiguous* in memory, C-like order - otherwise. - - Returns - ------- - reshaped_array : ndarray - This will be a new view object if possible; otherwise, it will - be a copy. Note there is no guarantee of the *memory layout* (C- or - Fortran- contiguous) of the returned array. - - See Also - -------- - numpy.reshape - - Availability - -------- - Multiple GPUs, Multiple CPUs - """ - return a.reshape(newshape, order=order) - - -# Transpose-like operations - - -@add_boilerplate("a") -def swapaxes(a: ndarray, axis1: int, axis2: int) -> ndarray: - """ - - Interchange two axes of an array. - - Parameters - ---------- - a : array_like - Input array. - axis1 : int - First axis. - axis2 : int - Second axis. - - Returns - ------- - a_swapped : ndarray - If `a` is an ndarray, then a view of `a` is returned; otherwise a new - array is created. - - See Also - -------- - numpy.swapaxes - - Availability - -------- - Multiple GPUs, Multiple CPUs - """ - return a.swapaxes(axis1, axis2) - - -@add_boilerplate("a") -def transpose(a: ndarray, axes: Optional[list[int]] = None) -> ndarray: - """ - - Permute the dimensions of an array. - - Parameters - ---------- - a : array_like - Input array. - axes : list[int], optional - By default, reverse the dimensions, otherwise permute the axes - according to the values given. - - Returns - ------- - p : ndarray - `a` with its axes permuted. A view is returned whenever - possible. - - See Also - -------- - numpy.transpose - - Availability - -------- - Multiple GPUs, Multiple CPUs - """ - return a.transpose(axes=axes) - - -@add_boilerplate("a") -def moveaxis( - a: ndarray, source: Sequence[int], destination: Sequence[int] -) -> ndarray: - """ - Move axes of an array to new positions. - Other axes remain in their original order. - - Parameters - ---------- - a : ndarray - The array whose axes should be reordered. - source : int or Sequence[int] - Original positions of the axes to move. These must be unique. - destination : int or Sequence[int] - Destination positions for each of the original axes. These must also be - unique. - - Returns - ------- - result : ndarray - Array with moved axes. This array is a view of the input array. - - See Also - -------- - numpy.moveaxis - - Availability - -------- - Multiple GPUs, Multiple CPUs - """ - source = normalize_axis_tuple(source, a.ndim, "source") - destination = normalize_axis_tuple(destination, a.ndim, "destination") - if len(source) != len(destination): - raise ValueError( - "`source` and `destination` arguments must have the same number " - "of elements" - ) - order = [n for n in range(a.ndim) if n not in source] - for dest, src in sorted(zip(destination, source)): - order.insert(dest, src) - return a.transpose(order) - - -# Changing number of dimensions - - -def _reshape_recur(ndim: int, arr: ndarray) -> tuple[int, ...]: - if arr.ndim < ndim: - cur_shape: tuple[int, ...] = _reshape_recur(ndim - 1, arr) - if ndim == 2: - cur_shape = (1,) + cur_shape - else: - cur_shape = cur_shape + (1,) - else: - cur_shape = arr.shape - return cur_shape - - -def _atleast_nd( - ndim: int, arys: Sequence[ndarray] -) -> Union[list[ndarray], ndarray]: - inputs = list(convert_to_cunumeric_ndarray(arr) for arr in arys) - # 'reshape' change the shape of arrays - # only when arr.shape != _reshape_recur(ndim,arr) - result = list(arr.reshape(_reshape_recur(ndim, arr)) for arr in inputs) - # if the number of arrays in `arys` is 1, - # the return value is a single array - if len(result) == 1: - return result[0] - return result - - -def atleast_1d(*arys: ndarray) -> Union[list[ndarray], ndarray]: - """ - - Convert inputs to arrays with at least one dimension. - Scalar inputs are converted to 1-dimensional arrays, - whilst higher-dimensional inputs are preserved. - - Parameters - ---------- - *arys : array_like - One or more input arrays. - - Returns - ------- - ret : ndarray - An array, or list of arrays, each with a.ndim >= 1. - Copies are made only if necessary. - - See Also - -------- - numpy.atleast_1d - - Availability - -------- - Multiple GPUs, Multiple CPUs - """ - return _atleast_nd(1, arys) - - -def atleast_2d(*arys: ndarray) -> Union[list[ndarray], ndarray]: - """ - - View inputs as arrays with at least two dimensions. - - Parameters - ---------- - *arys : array_like - One or more array-like sequences. - Non-array inputs are converted to arrays. - Arrays that already have two or more dimensions are preserved. - - Returns - ------- - res, res2, … : ndarray - An array, or list of arrays, each with a.ndim >= 2. - Copies are avoided where possible, and - views with two or more dimensions are returned. - - See Also - -------- - numpy.atleast_2d - - Availability - -------- - Multiple GPUs, Multiple CPUs - """ - return _atleast_nd(2, arys) - - -def atleast_3d(*arys: ndarray) -> Union[list[ndarray], ndarray]: - """ - - View inputs as arrays with at least three dimensions. - - Parameters - ---------- - *arys : array_like - One or more array-like sequences. - Non-array inputs are converted to arrays. - Arrays that already have three or more dimensions are preserved. - - Returns - ------- - res, res2, … : ndarray - An array, or list of arrays, each with a.ndim >= 3. - Copies are avoided where possible, and - views with three or more dimensions are returned. - For example, a 1-D array of shape (N,) becomes - a view of shape (1, N, 1), and a 2-D array of shape (M, N) - becomes a view of shape (M, N, 1). - - See Also - -------- - numpy.atleast_3d - - Availability - -------- - Multiple GPUs, Multiple CPUs - """ - return _atleast_nd(3, arys) - - -@add_boilerplate("a") -def squeeze(a: ndarray, axis: Optional[NdShapeLike] = None) -> ndarray: - """ - - Remove single-dimensional entries from the shape of an array. - - Parameters - ---------- - a : array_like - Input data. - axis : None or int or tuple[int], optional - Selects a subset of the single-dimensional entries in the - shape. If an axis is selected with shape entry greater than - one, an error is raised. - - Returns - ------- - squeezed : ndarray - The input array, but with all or a subset of the - dimensions of length 1 removed. This is always `a` itself - or a view into `a`. - - Raises - ------ - ValueError - If `axis` is not None, and an axis being squeezed is not of length 1 - - See Also - -------- - numpy.squeeze - - Availability - -------- - Multiple GPUs, Multiple CPUs - """ - return a.squeeze(axis=axis) - - -def broadcast_shapes( - *args: Union[NdShapeLike, Sequence[NdShapeLike]] -) -> NdShape: - """ - - Broadcast the input shapes into a single shape. - - Parameters - ---------- - `*args` : tuples of ints, or ints - The shapes to be broadcast against each other. - - Returns - ------- - tuple : Broadcasted shape. - - See Also - -------- - numpy.broadcast_shapes - - Availability - -------- - Multiple GPUs, Multiple CPUs - - """ - # TODO: expected "Union[SupportsIndex, Sequence[SupportsIndex]]" - return np.broadcast_shapes(*args) # type: ignore [arg-type] - - -def _broadcast_to( - arr: ndarray, - shape: NdShapeLike, - subok: bool = False, - broadcasted: bool = False, -) -> ndarray: - # create an array object w/ options passed from 'broadcast' routines - arr = array(arr, copy=False, subok=subok) - # 'broadcast_to' returns a read-only view of the original array - out_shape = broadcast_shapes(arr.shape, shape) - if out_shape != shape: - raise ValueError( - f"cannot broadcast an array of shape {arr.shape} to {shape}" - ) - result = ndarray( - shape=out_shape, - thunk=arr._thunk.broadcast_to(out_shape), - writeable=False, - ) - return result - - -@add_boilerplate("arr") -def broadcast_to( - arr: ndarray, shape: NdShapeLike, subok: bool = False -) -> ndarray: - """ - - Broadcast an array to a new shape. - - Parameters - ---------- - arr : array_like - The array to broadcast. - shape : tuple or int - The shape of the desired array. - A single integer i is interpreted as (i,). - subok : bool, optional - This option is ignored by cuNumeric. - - Returns - ------- - broadcast : array - A readonly view on the original array with the given shape. - It is typically not contiguous. - Furthermore, more than one element of a broadcasted array - may refer to a single memory location. - - See Also - -------- - numpy.broadcast_to - - Availability - -------- - Multiple GPUs, Multiple CPUs - - """ - return _broadcast_to(arr, shape, subok) - - -def _broadcast_arrays( - arrs: list[ndarray], - subok: bool = False, -) -> list[ndarray]: - # create an arry object w/ options passed from 'broadcast' routines - arrays = [array(arr, copy=False, subok=subok) for arr in arrs] - # check if the broadcast can happen in the input list of arrays - shapes = [arr.shape for arr in arrays] - out_shape = broadcast_shapes(*shapes) - # broadcast to the final shape - arrays = [_broadcast_to(arr, out_shape, subok) for arr in arrays] - return arrays - - -def broadcast_arrays( - *args: Sequence[Any], subok: bool = False -) -> list[ndarray]: - """ - - Broadcast any number of arrays against each other. - - Parameters - ---------- - `*args` : array_likes - The arrays to broadcast. - - subok : bool, optional - This option is ignored by cuNumeric - - Returns - ------- - broadcasted : list of arrays - These arrays are views on the original arrays. - They are typically not contiguous. - Furthermore, more than one element of a broadcasted array - may refer to a single memory location. - If you need to write to the arrays, make copies first. - - Availability - -------- - Multiple GPUs, Multiple CPUs - - """ - arrs = [convert_to_cunumeric_ndarray(arr) for arr in args] - return _broadcast_arrays(arrs, subok=subok) - - -class broadcast: - """Produce an object that broadcasts input parameters against one another. - It has shape and nd properties and may be used as an iterator. - - Parameters - ---------- - `*arrays` : array_likes - The arrays to broadcast. - - Returns - ------- - b: broadcast - Broadcast the input parameters against one another, and return an - object that encapsulates the result. Amongst others, it has shape - and nd properties, and may be used as an iterator. - - """ - - def __init__(self, *arrays: Sequence[Any]) -> None: - arrs = [convert_to_cunumeric_ndarray(arr) for arr in arrays] - broadcasted = _broadcast_arrays(arrs) - self._iters = tuple(arr.flat for arr in broadcasted) - self._index = 0 - self._shape = broadcasted[0].shape - self._size = np.prod(self.shape, dtype=int) - - def __iter__(self) -> broadcast: - self._index = 0 - return self - - def __next__(self) -> Any: - if self._index < self.size: - result = tuple(each[self._index] for each in self._iters) - self._index += 1 - return result - - def reset(self) -> None: - """Reset the broadcasted result's iterator(s).""" - self._index = 0 - - @property - def index(self) -> int: - """current index in broadcasted result""" - return self._index - - @property - def iters(self) -> Tuple[Iterable[Any], ...]: - """tuple of iterators along self’s "components." """ - return self._iters - - @property - def numiter(self) -> int: - """Number of iterators possessed by the broadcasted result.""" - return len(self._iters) - - @property - def nd(self) -> int: - """Number of dimensions of broadcasted result.""" - return self.ndim - - @property - def ndim(self) -> int: - """Number of dimensions of broadcasted result.""" - return len(self.shape) - - @property - def shape(self) -> NdShape: - """Shape of broadcasted result.""" - return self._shape - - @property - def size(self) -> int: - """Total size of broadcasted result.""" - return self._size - - -# Joining arrays - - -class ArrayInfo: - def __init__( - self, ndim: int, shape: NdShape, dtype: np.dtype[Any] - ) -> None: - self.ndim = ndim - self.shape = shape - self.dtype = dtype - - -def convert_to_array_form(indices: Sequence[int]) -> str: - return "".join(f"[{coord}]" for coord in indices) - - -def check_list_depth(arr: Any, prefix: NdShape = (0,)) -> int: - if not isinstance(arr, list): - return 0 - elif len(arr) == 0: - raise ValueError( - f"List at arrays{convert_to_array_form(prefix)} cannot be empty" - ) - - depths = list( - check_list_depth(each, prefix + (idx,)) for idx, each in enumerate(arr) - ) - - if len(set(depths)) != 1: # this should be one - # If we're here elements don't have the same depth - first_depth = depths[0] - for idx, other_depth in enumerate(depths[1:]): - if other_depth != first_depth: - raise ValueError( - "List depths are mismatched. First element was at depth " - f"{first_depth}, but there is an element at" - f" depth {other_depth}, " - f"arrays{convert_to_array_form(prefix+(idx+1,))}" - ) - - return depths[0] + 1 - - -def check_shape_with_axis( - inputs: list[ndarray], - func_name: str, - axis: int, -) -> None: - ndim = inputs[0].ndim - shape = inputs[0].shape - - axis = normalize_axis_index(axis, ndim) - if ndim >= 1: - if _builtin_any( - shape[:axis] != inp.shape[:axis] - or shape[axis + 1 :] != inp.shape[axis + 1 :] - for inp in inputs - ): - raise ValueError( - f"All arguments to {func_name} " - "must have the same " - "dimension size in all dimensions " - "except the target axis" - ) - return - - -def check_shape_dtype_without_axis( - inputs: Sequence[ndarray], - func_name: str, - dtype: Optional[npt.DTypeLike] = None, - casting: CastingKind = "same_kind", -) -> tuple[list[ndarray], ArrayInfo]: - if len(inputs) == 0: - raise ValueError("need at least one array to concatenate") - - inputs = list(convert_to_cunumeric_ndarray(inp) for inp in inputs) - ndim = inputs[0].ndim - shape = inputs[0].shape - - if _builtin_any(ndim != inp.ndim for inp in inputs): - raise ValueError( - f"All arguments to {func_name} " - "must have the same number of dimensions" - ) - - # Cast arrays with the passed arguments (dtype, casting) - if dtype is None: - dtype = np.result_type(*[inp.dtype for inp in inputs]) - else: - dtype = np.dtype(dtype) - - converted = list(inp.astype(dtype, casting=casting) for inp in inputs) - return converted, ArrayInfo(ndim, shape, dtype) - - -def _block_collect_slices( - arr: Union[ndarray, Sequence[ndarray]], cur_depth: int, depth: int -) -> tuple[list[Any], list[tuple[slice, ...]], Sequence[ndarray]]: - # collects slices for each array in `arr` - # the outcome will be slices on every dimension of the output array - # for each array in `arr` - if cur_depth < depth: - sublist_results = list( - _block_collect_slices(each, cur_depth + 1, depth) for each in arr - ) - # 'sublist_results' contains a list of 3-way tuples, - # for arrays, out_shape of the sublist, and slices - arrays, outshape_list, slices = zip(*sublist_results) - max_ndim = _builtin_max( - 1 + (depth - cur_depth), *(len(each) for each in outshape_list) - ) - outshape_list = list( - ((1,) * (max_ndim - len(each)) + tuple(each)) - for each in outshape_list - ) - leading_dim = _builtin_sum( - each[-1 + (cur_depth - depth)] for each in outshape_list - ) - # flatten array lists from sublists into a single list - arrays = list(chain(*arrays)) - # prepares the out_shape of the current list - out_shape = list(outshape_list[0]) - out_shape[-1 + cur_depth - depth] = leading_dim - offset = 0 - updated_slices = [] - # update the dimension in each slice for the current axis - for shape, slice_list in zip(outshape_list, slices): - cur_dim = shape[-1 + cur_depth - depth] - updated_slices.append( - list( - (slice(offset, offset + cur_dim),) + each - for each in slice_list - ) - ) - offset += cur_dim - # flatten lists of slices into a single list - slices = list(chain(*updated_slices)) - else: - arrays = list(convert_to_cunumeric_ndarray(inp) for inp in arr) - common_shape = arrays[0].shape - if len(arr) > 1: - arrays, common_info = check_shape_dtype_without_axis( - arrays, block.__name__ - ) - common_shape = common_info.shape - check_shape_with_axis(arrays, block.__name__, axis=-1) - # the initial slices for each arr on arr.shape[-1] - out_shape, slices, arrays = _collect_outshape_slices( - arrays, common_shape, axis=-1 + len(common_shape) - ) - - return arrays, out_shape, slices - - -def _block_slicing(arrays: Sequence[ndarray], depth: int) -> ndarray: - # collects the final slices of input arrays and assign them at once - arrays, out_shape, slices = _block_collect_slices(arrays, 1, depth) - out_array = ndarray(shape=out_shape, inputs=arrays) - - for dest, inp in zip(slices, arrays): - out_array[(Ellipsis,) + tuple(dest)] = inp - - return out_array - - -def _collect_outshape_slices( - inputs: Sequence[ndarray], common_shape: NdShape, axis: int -) -> tuple[list[Any], list[tuple[slice, ...]], Sequence[ndarray]]: - leading_dim = _builtin_sum(arr.shape[axis] for arr in inputs) - out_shape = list(common_shape) - out_shape[axis] = leading_dim - post_idx = (slice(None),) * len(out_shape[axis + 1 :]) - slices = [] - offset = 0 - # collect slices for arrays in `inputs` - inputs = list(inp for inp in inputs if inp.size > 0) - for inp in inputs: - slices.append((slice(offset, offset + inp.shape[axis]),) + post_idx) - offset += inp.shape[axis] - - return out_shape, slices, inputs - - -def _concatenate( - inputs: Sequence[ndarray], - common_info: ArrayInfo, - axis: int = 0, - out: Optional[ndarray] = None, - dtype: Optional[npt.DTypeLike] = None, - casting: CastingKind = "same_kind", -) -> ndarray: - if axis < 0: - axis += len(common_info.shape) - out_shape, slices, inputs = _collect_outshape_slices( - inputs, common_info.shape, axis - ) - - if out is None: - out_array = ndarray( - shape=out_shape, dtype=common_info.dtype, inputs=inputs - ) - else: - out = convert_to_cunumeric_ndarray(out) - if not isinstance(out, ndarray): - raise TypeError("out should be ndarray") - elif list(out.shape) != out_shape: - raise ValueError( - f"out.shape({out.shape}) is not matched " - f"to the result shape of concatenation ({out_shape})" - ) - out_array = out - - for dest, src in zip(slices, inputs): - out_array[(Ellipsis,) + dest] = src - - return out_array - - -def append( - arr: ndarray, values: ndarray, axis: Optional[int] = None -) -> ndarray: - """ - - Append values to the end of an array. - - Parameters - ---------- - arr : array_like - Values are appended to a copy of this array. - values : array_like - These values are appended to a copy of arr. It must be of the correct - shape (the same shape as arr, excluding axis). If axis is not - specified, values can be any shape and will be flattened before use. - axis : int, optional - The axis along which values are appended. If axis is not given, both - `arr` and `values` are flattened before use. - - Returns - ------- - res : ndarray - A copy of arr with values appended to axis. - - See Also - -------- - numpy.append - - Availability - -------- - Multiple GPUs, Multiple CPUs - - """ - # Check to see if we can build a new tuple of cuNumeric arrays - inputs = list(convert_to_cunumeric_ndarray(inp) for inp in [arr, values]) - return concatenate(inputs, axis) - - -def block(arrays: Sequence[Any]) -> ndarray: - """ - Assemble an nd-array from nested lists of blocks. - - Blocks in the innermost lists are concatenated (see concatenate) - along the last dimension (-1), then these are concatenated along - the second-last dimension (-2), and so on until the outermost - list is reached. - - Blocks can be of any dimension, but will not be broadcasted using - the normal rules. Instead, leading axes of size 1 are inserted, - to make block.ndim the same for all blocks. This is primarily useful - for working with scalars, and means that code like np.block([v, 1]) - is valid, where v.ndim == 1. - - When the nested list is two levels deep, this allows block matrices - to be constructed from their components. - - Parameters - ---------- - arrays : nested list of array_like or scalars - If passed a single ndarray or scalar (a nested list of depth 0), - this is returned unmodified (and not copied). - - Elements shapes must match along the appropriate axes (without - broadcasting), but leading 1s will be prepended to the shape as - necessary to make the dimensions match. - - Returns - ------- - block_array : ndarray - The array assembled from the given blocks. - The dimensionality of the output is equal to the greatest of: * the - dimensionality of all the inputs * the depth to which the input list - is nested - - Raises - ------ - ValueError - If list depths are mismatched - for instance, [[a, b], c] is - illegal, and should be spelt [[a, b], [c]] - If lists are empty - for instance, [[a, b], []] - - See Also - -------- - numpy.block - - Availability - -------- - Multiple GPUs, Multiple CPUs - - """ - # arrays should concatenate from innermost subarrays - # the 'arrays' should be balanced tree - # check if the 'arrays' is a balanced tree - depth = check_list_depth(arrays) - - result = _block_slicing(arrays, depth) - return result - - -def concatenate( - inputs: Sequence[ndarray], - axis: Union[int, None] = 0, - out: Optional[ndarray] = None, - dtype: Optional[npt.DTypeLike] = None, - casting: CastingKind = "same_kind", -) -> ndarray: - """ - - concatenate((a1, a2, ...), axis=0, out=None, dtype=None, - casting="same_kind") - - Join a sequence of arrays along an existing axis. - - Parameters - ---------- - a1, a2, ... : Sequence[array_like] - The arrays must have the same shape, except in the dimension - corresponding to `axis` (the first, by default). - axis : int, optional - The axis along which the arrays will be joined. If axis is None, - arrays are flattened before use. Default is 0. - out : ndarray, optional - If provided, the destination to place the result. The shape must be - correct, matching that of what concatenate would have returned if no - out argument were specified. - dtype : str or data-type - If provided, the destination array will have this dtype. Cannot be - provided together with `out`. - casting : ``{'no', 'equiv', 'safe', 'same_kind', 'unsafe'}``, optional - Controls what kind of data casting may occur. Defaults to 'same_kind'. - - Returns - ------- - res : ndarray - The concatenated array. - - See Also - -------- - numpy.concatenate - - Availability - -------- - Multiple GPUs, Multiple CPUs - """ - if dtype is not None and out is not None: - raise TypeError( - "concatenate() only takes `out` or `dtype` as an argument," - "but both were provided." - ) - - if casting not in casting_kinds: - raise ValueError( - "casting must be one of 'no', 'equiv', " - "'safe', 'same_kind', or 'unsafe'" - ) - - # flatten arrays if axis == None and concatenate arrays on the first axis - if axis is None: - # Reshape arrays in the `array_list` to handle scalars - reshaped = _atleast_nd(1, inputs) - if not isinstance(reshaped, list): - reshaped = [reshaped] - inputs = list(inp.ravel() for inp in reshaped) - axis = 0 - - # Check to see if we can build a new tuple of cuNumeric arrays - cunumeric_inputs, common_info = check_shape_dtype_without_axis( - inputs, concatenate.__name__, dtype, casting - ) - check_shape_with_axis(cunumeric_inputs, concatenate.__name__, axis) - - return _concatenate( - cunumeric_inputs, - common_info, - axis, - out, - dtype, - casting, - ) - - -def stack( - arrays: Sequence[ndarray], axis: int = 0, out: Optional[ndarray] = None -) -> ndarray: - """ - - Join a sequence of arrays along a new axis. - - The ``axis`` parameter specifies the index of the new axis in the - dimensions of the result. For example, if ``axis=0`` it will be the first - dimension and if ``axis=-1`` it will be the last dimension. - - Parameters - ---------- - arrays : Sequence[array_like] - Each array must have the same shape. - - axis : int, optional - The axis in the result array along which the input arrays are stacked. - - out : ndarray, optional - If provided, the destination to place the result. The shape must be - correct, matching that of what stack would have returned if no - out argument were specified. - - Returns - ------- - stacked : ndarray - The stacked array has one more dimension than the input arrays. - - See Also - -------- - numpy.stack - - Availability - -------- - Multiple GPUs, Multiple CPUs - """ - if type(axis) is not int: - raise TypeError("The target axis should be an integer") - - arrays, common_info = check_shape_dtype_without_axis( - arrays, stack.__name__ - ) - shapes = {inp.shape for inp in arrays} - if len(shapes) != 1: - raise ValueError("all input arrays must have the same shape for stack") - - axis = normalize_axis_index(axis, common_info.ndim + 1) - shape = common_info.shape[:axis] + (1,) + common_info.shape[axis:] - arrays = [arr.reshape(shape) for arr in arrays] - common_info.shape = tuple(shape) - return _concatenate(arrays, common_info, axis, out=out) - - -def vstack(tup: Sequence[ndarray]) -> ndarray: - """ - - Stack arrays in sequence vertically (row wise). - - This is equivalent to concatenation along the first axis after 1-D arrays - of shape `(N,)` have been reshaped to `(1,N)`. Rebuilds arrays divided by - `vsplit`. - - This function makes most sense for arrays with up to 3 dimensions. For - instance, for pixel-data with a height (first axis), width (second axis), - and r/g/b channels (third axis). The functions `concatenate`, `stack` and - `block` provide more general stacking and concatenation operations. - - Parameters - ---------- - tup : Sequence[ndarray] - The arrays must have the same shape along all but the first axis. - 1-D arrays must have the same length. - - Returns - ------- - stacked : ndarray - The array formed by stacking the given arrays, will be at least 2-D. - - See Also - -------- - numpy.vstack - - Availability - -------- - Multiple GPUs, Multiple CPUs - """ - # Reshape arrays in the `array_list` if needed before concatenation - reshaped = _atleast_nd(2, tup) - if not isinstance(reshaped, list): - reshaped = [reshaped] - tup, common_info = check_shape_dtype_without_axis( - reshaped, vstack.__name__ - ) - check_shape_with_axis(tup, vstack.__name__, 0) - return _concatenate( - tup, - common_info, - axis=0, - dtype=common_info.dtype, - ) - - -def hstack(tup: Sequence[ndarray]) -> ndarray: - """ - - Stack arrays in sequence horizontally (column wise). - - This is equivalent to concatenation along the second axis, except for 1-D - arrays where it concatenates along the first axis. Rebuilds arrays divided - by `hsplit`. - - This function makes most sense for arrays with up to 3 dimensions. For - instance, for pixel-data with a height (first axis), width (second axis), - and r/g/b channels (third axis). The functions `concatenate`, `stack` and - `block` provide more general stacking and concatenation operations. - - Parameters - ---------- - tup : Sequence[ndarray] - The arrays must have the same shape along all but the second axis, - except 1-D arrays which can be any length. - - Returns - ------- - stacked : ndarray - The array formed by stacking the given arrays. - - See Also - -------- - numpy.hstack - - Availability - -------- - Multiple GPUs, Multiple CPUs - """ - # Reshape arrays in the `array_list` to handle scalars - reshaped = _atleast_nd(1, tup) - if not isinstance(reshaped, list): - reshaped = [reshaped] - - tup, common_info = check_shape_dtype_without_axis( - reshaped, hstack.__name__ - ) - check_shape_with_axis( - tup, hstack.__name__, axis=(0 if common_info.ndim == 1 else 1) - ) - # When ndim == 1, hstack concatenates arrays along the first axis - return _concatenate( - tup, - common_info, - axis=(0 if common_info.ndim == 1 else 1), - dtype=common_info.dtype, - ) - - -def dstack(tup: Sequence[ndarray]) -> ndarray: - """ - - Stack arrays in sequence depth wise (along third axis). - - This is equivalent to concatenation along the third axis after 2-D arrays - of shape `(M,N)` have been reshaped to `(M,N,1)` and 1-D arrays of shape - `(N,)` have been reshaped to `(1,N,1)`. Rebuilds arrays divided by - `dsplit`. - - This function makes most sense for arrays with up to 3 dimensions. For - instance, for pixel-data with a height (first axis), width (second axis), - and r/g/b channels (third axis). The functions `concatenate`, `stack` and - `block` provide more general stacking and concatenation operations. - - Parameters - ---------- - tup : Sequence[ndarray] - The arrays must have the same shape along all but the third axis. - 1-D or 2-D arrays must have the same shape. - - Returns - ------- - stacked : ndarray - The array formed by stacking the given arrays, will be at least 3-D. - - See Also - -------- - numpy.dstack - - Availability - -------- - Multiple GPUs, Multiple CPUs - """ - # Reshape arrays to (1,N,1) for ndim ==1 or (M,N,1) for ndim == 2: - reshaped = _atleast_nd(3, tup) - if not isinstance(reshaped, list): - reshaped = [reshaped] - tup, common_info = check_shape_dtype_without_axis( - reshaped, dstack.__name__ - ) - check_shape_with_axis(tup, dstack.__name__, 2) - return _concatenate( - tup, - common_info, - axis=2, - dtype=common_info.dtype, - ) - - -def column_stack(tup: Sequence[ndarray]) -> ndarray: - """ - - Stack 1-D arrays as columns into a 2-D array. - - Take a sequence of 1-D arrays and stack them as columns - to make a single 2-D array. 2-D arrays are stacked as-is, - just like with `hstack`. 1-D arrays are turned into 2-D columns - first. - - Parameters - ---------- - tup : Sequence[ndarray] - 1-D or 2-D arrays to stack. All of them must have the same - first dimension. - - Returns - ------- - stacked : ndarray - The 2-D array formed by stacking the given arrays. - - See Also - -------- - numpy.column_stack - - Availability - -------- - Multiple GPUs, Multiple CPUs - """ - # Reshape arrays in the `array_list` to handle scalars - reshaped = _atleast_nd(1, tup) - if not isinstance(reshaped, list): - reshaped = [reshaped] - - tup, common_info = check_shape_dtype_without_axis( - reshaped, column_stack.__name__ - ) - - if common_info.ndim == 1: - tup = list(inp.reshape((inp.shape[0], 1)) for inp in tup) - common_info.shape = tup[0].shape - check_shape_with_axis(tup, column_stack.__name__, 1) - return _concatenate( - tup, - common_info, - axis=1, - dtype=common_info.dtype, - ) - - -row_stack = vstack - - -# Splitting arrays - - -def split( - a: ndarray, indices: Union[int, ndarray], axis: int = 0 -) -> list[ndarray]: - """ - - Split an array into multiple sub-arrays as views into `ary`. - - Parameters - ---------- - ary : ndarray - Array to be divided into sub-arrays. - indices_or_sections : int or ndarray - If `indices_or_sections` is an integer, N, the array will be divided - into N equal arrays along `axis`. If such a split is not possible, - an error is raised. - - If `indices_or_sections` is a 1-D array of sorted integers, the entries - indicate where along `axis` the array is split. For example, - ``[2, 3]`` would, for ``axis=0``, result in - - - ary[:2] - - ary[2:3] - - ary[3:] - - If an index exceeds the dimension of the array along `axis`, - an empty sub-array is returned correspondingly. - axis : int, optional - The axis along which to split, default is 0. - - Returns - ------- - sub-arrays : list[ndarray] - A list of sub-arrays as views into `ary`. - - Raises - ------ - ValueError - If `indices_or_sections` is given as an integer, but - a split does not result in equal division. - - See Also - -------- - numpy.split - - Availability - -------- - Multiple GPUs, Multiple CPUs - """ - return array_split(a, indices, axis, equal=True) - - -def array_split( - a: ndarray, - indices: Union[int, tuple[int], ndarray, npt.NDArray[Any]], - axis: int = 0, - equal: bool = False, -) -> list[ndarray]: - """ - - Split an array into multiple sub-arrays. - - Please refer to the ``split`` documentation. The only difference - between these functions is that ``array_split`` allows - `indices_or_sections` to be an integer that does *not* equally - divide the axis. For an array of length l that should be split - into n sections, it returns l % n sub-arrays of size l//n + 1 - and the rest of size l//n. - - See Also - -------- - numpy.array_split - - Availability - -------- - Multiple GPUs, Multiple CPUs - """ - array = convert_to_cunumeric_ndarray(a) - split_pts = [] - if axis >= array.ndim: - raise ValueError( - f"array({array.shape}) has less dimensions than axis({axis})" - ) - - if isinstance(indices, int): - if indices <= 0: - raise ValueError("number sections must be larger than 0.") - res = array.shape[axis] % indices - if equal and res != 0: - raise ValueError("array split does not result in an equal divison") - - len_subarr = array.shape[axis] // indices - end_idx = array.shape[axis] - first_idx = len_subarr - - # the requested # of subarray is larger than the size of array - # -> size of 1 subarrays + empty subarrays - if len_subarr == 0: - len_subarr = 1 - first_idx = len_subarr - end_idx = indices - else: - if res != 0: - # The first 'res' groups have len_subarr+1 elements - split_pts = list( - range( - len_subarr + 1, (len_subarr + 1) * res, len_subarr + 1 - ) - ) - first_idx = (len_subarr + 1) * res - split_pts.extend(range(first_idx, end_idx + 1, len_subarr)) - - elif isinstance(indices, (list, tuple)) or ( - isinstance(indices, (ndarray, np.ndarray)) and indices.dtype == int - ): - split_pts = list(indices) - # adding the size of the target dimension. - # This helps create dummy or last subarray correctly - split_pts.append(array.shape[axis]) - - else: - raise ValueError("Integer or array for split should be provided") - - result = [] - start_idx = 0 - end_idx = 0 - out_shape = [] - in_shape: list[Union[int, slice]] = [] - - for i in range(array.ndim): - if i != axis: - in_shape.append(slice(array.shape[i])) - out_shape.append(array.shape[i]) - else: - in_shape.append(1) - out_shape.append(1) - - for pts in split_pts: - if type(pts) is not int: - raise ValueError( - "Split points in the passed `indices` should be integer" - ) - end_idx = pts - # For a split point, which is larger than the dimension for splitting, - # The last non-empty subarray should be copied from - # array[last_elem:array.shape[axis]] - if pts > array.shape[axis]: - end_idx = array.shape[axis] - out_shape[axis] = (end_idx - start_idx) + 1 - in_shape[axis] = slice(start_idx, end_idx) - new_subarray = None - if start_idx < array.shape[axis] and start_idx < end_idx: - new_subarray = array[tuple(in_shape)].view() - else: - out_shape[axis] = 0 - new_subarray = ndarray( - tuple(out_shape), dtype=array.dtype, writeable=array._writeable - ) - result.append(new_subarray) - start_idx = pts - - return result - - -def dsplit(a: ndarray, indices: Union[int, ndarray]) -> list[ndarray]: - """ - - Split array into multiple sub-arrays along the 3rd axis (depth). - - Please refer to the `split` documentation. `dsplit` is equivalent - to `split` with ``axis=2``, the array is always split along the third - axis provided the array dimension is greater than or equal to 3. - - See Also - -------- - numpy.dsplit - - Availability - -------- - Multiple GPUs, Multiple CPUs - """ - return split(a, indices, axis=2) - - -def hsplit(a: ndarray, indices: Union[int, ndarray]) -> list[ndarray]: - """ - - Split an array into multiple sub-arrays horizontally (column-wise). - - Please refer to the `split` documentation. `hsplit` is equivalent - to `split` with ``axis=1``, the array is always split along the second - axis regardless of the array dimension. - - See Also - -------- - numpy.hsplit - - Availability - -------- - Multiple GPUs, Multiple CPUs - """ - return split(a, indices, axis=1) - - -def vsplit(a: ndarray, indices: Union[int, ndarray]) -> list[ndarray]: - """ - - Split an array into multiple sub-arrays vertically (row-wise). - - Please refer to the ``split`` documentation. ``vsplit`` is equivalent - to ``split`` with `axis=0` (default), the array is always split along the - first axis regardless of the array dimension. - - See Also - -------- - numpy.vsplit - - Availability - -------- - Multiple GPUs, Multiple CPUs - """ - return split(a, indices, axis=0) - - -# Tiling arrays - - -@add_boilerplate("A") -def tile( - A: ndarray, reps: Union[int, Sequence[int], npt.NDArray[np.int_]] -) -> ndarray: - """ - Construct an array by repeating A the number of times given by reps. - - If `reps` has length ``d``, the result will have dimension of ``max(d, - A.ndim)``. - - If ``A.ndim < d``, `A` is promoted to be d-dimensional by prepending new - axes. So a shape (3,) array is promoted to (1, 3) for 2-D replication, - or shape (1, 1, 3) for 3-D replication. If this is not the desired - behavior, promote `A` to d-dimensions manually before calling this - function. - - If ``A.ndim > d``, `reps` is promoted to `A`.ndim by pre-pending 1's to it. - Thus for an `A` of shape (2, 3, 4, 5), a `reps` of (2, 2) is treated as - (1, 1, 2, 2). - - Parameters - ---------- - A : array_like - The input array. - reps : 1d array_like - The number of repetitions of `A` along each axis. - - Returns - ------- - c : ndarray - The tiled output array. - - See Also - -------- - numpy.tile - - Availability - -------- - Multiple GPUs, Multiple CPUs - """ - computed_reps: tuple[int, ...] - if isinstance(reps, int): - computed_reps = (reps,) - else: - if np.ndim(reps) > 1: - raise TypeError("`reps` must be a 1d sequence") - computed_reps = tuple(reps) - # Figure out the shape of the destination array - out_dims = _builtin_max(A.ndim, len(computed_reps)) - # Prepend ones until the dimensions match - while len(computed_reps) < out_dims: - computed_reps = (1,) + computed_reps - out_shape: NdShape = () - # Prepend dimensions if necessary - for dim in range(out_dims - A.ndim): - out_shape += (computed_reps[dim],) - offset = len(out_shape) - for dim in range(A.ndim): - out_shape += (A.shape[dim] * computed_reps[offset + dim],) - assert len(out_shape) == out_dims - result = ndarray(out_shape, dtype=A.dtype, inputs=(A,)) - result._thunk.tile(A._thunk, computed_reps) - return result - - -def repeat(a: ndarray, repeats: Any, axis: Optional[int] = None) -> ndarray: - """ - Repeat elements of an array. - - Parameters - ---------- - a : array_like - Input array. - repeats : int or ndarray[int] - The number of repetitions for each element. repeats is - broadcasted to fit the shape of the given axis. - axis : int, optional - The axis along which to repeat values. By default, use the - flattened input array, and return a flat output array. - - Returns - ------- - repeated_array : ndarray - Output array which has the same shape as a, except along the - given axis. - - Notes - ----- - Currently, repeat operations supports only 1D arrays - - See Also - -------- - numpy.repeat - - Availability - -------- - Multiple GPUs, Multiple CPUs - """ - - if repeats is None: - raise TypeError( - "int() argument must be a string, a bytes-like object or a number," - " not 'NoneType'" - ) - - if np.ndim(repeats) > 1: - raise ValueError("`repeats` should be scalar or 1D array") - - # axes should be integer type - if axis is not None and not isinstance(axis, int): - raise TypeError("Axis should be of integer type") - - # when array is a scalar - if np.ndim(a) == 0: - if axis is not None and axis != 0 and axis != -1: - raise np.AxisError( - f"axis {axis} is out of bounds for array of dimension 0" - ) - if np.ndim(repeats) == 0: - if not isinstance(repeats, int): - runtime.warn( - "converting repeats to an integer type", - category=UserWarning, - ) - repeats = np.int64(repeats) - return full((repeats,), cast(Union[int, float], a)) - elif np.ndim(repeats) == 1 and len(repeats) == 1: - if not isinstance(repeats, int): - runtime.warn( - "converting repeats to an integer type", - category=UserWarning, - ) - repeats = np.int64(repeats) - return full((repeats[0],), cast(Union[int, float], a)) - else: - raise ValueError( - "`repeat` with a scalar parameter `a` is only " - "implemented for scalar values of the parameter `repeats`." - ) - - # array is an array - array = convert_to_cunumeric_ndarray(a) - if np.ndim(repeats) == 1: - repeats = convert_to_cunumeric_ndarray(repeats) - - # if no axes specified, flatten array - if axis is None: - array = array.ravel() - axis = 0 - - axis_int: int = normalize_axis_index(axis, array.ndim) - - # If repeats is on a zero sized axis_int, then return the array. - if array.shape[axis_int] == 0: - return array.copy() - - if np.ndim(repeats) == 1: - if repeats.shape[0] == 1 and repeats.shape[0] != array.shape[axis_int]: - repeats = repeats[0] - - # repeats is a scalar. - if np.ndim(repeats) == 0: - # repeats is 0 - if repeats == 0: - empty_shape = list(array.shape) - empty_shape[axis_int] = 0 - return ndarray(shape=tuple(empty_shape), dtype=array.dtype) - # repeats should be integer type - if not isinstance(repeats, int): - runtime.warn( - "converting repeats to an integer type", - category=UserWarning, - ) - result = array._thunk.repeat( - repeats=np.int64(repeats), - axis=axis_int, - scalar_repeats=True, - ) - # repeats is an array - else: - # repeats should be integer type - repeats = repeats._warn_and_convert(np.int64) - if repeats.shape[0] != array.shape[axis_int]: - raise ValueError("incorrect shape of repeats array") - result = array._thunk.repeat( - repeats=repeats._thunk, axis=axis_int, scalar_repeats=False - ) - return ndarray(shape=result.shape, thunk=result) - - -# Rearranging elements - - -@add_boilerplate("m") -def flip(m: ndarray, axis: Optional[NdShapeLike] = None) -> ndarray: - """ - Reverse the order of elements in an array along the given axis. - - The shape of the array is preserved, but the elements are reordered. - - Parameters - ---------- - m : array_like - Input array. - axis : None or int or tuple[int], optional - Axis or axes along which to flip over. The default, axis=None, will - flip over all of the axes of the input array. If axis is negative it - counts from the last to the first axis. - - If axis is a tuple of ints, flipping is performed on all of the axes - specified in the tuple. - - Returns - ------- - out : array_like - A new array that is constructed from `m` with the entries of axis - reversed. - - See Also - -------- - numpy.flip - - Availability - -------- - Single GPU, Single CPU - - Notes - ----- - cuNumeric implementation doesn't return a view, it returns a new array - """ - return m.flip(axis=axis) - - -@add_boilerplate("m") -def flipud(m: ndarray) -> ndarray: - """ - Reverse the order of elements along axis 0 (up/down). - - For a 2-D array, this flips the entries in each column in the up/down - direction. Rows are preserved, but appear in a different order than before. - - Parameters - ---------- - m : array_like - Input array. - - Returns - ------- - out : array_like - A new array that is constructed from `m` with rows reversed. - - See Also - -------- - numpy.flipud - - Availability - -------- - Single GPU, Single CPU - - Notes - ----- - cuNumeric implementation doesn't return a view, it returns a new array - """ - if m.ndim < 1: - raise ValueError("Input must be >= 1-d.") - return flip(m, axis=0) - - -@add_boilerplate("m") -def fliplr(m: ndarray) -> ndarray: - """ - Reverse the order of elements along axis 1 (left/right). - - For a 2-D array, this flips the entries in each row in the left/right - direction. Columns are preserved, but appear in a different order than - before. - - Parameters - ---------- - m : array_like - Input array, must be at least 2-D. - - Returns - ------- - f : ndarray - A new array that is constructed from `m` with the columns reversed. - - See Also - -------- - numpy.fliplr - - Availability - -------- - Single GPU, Single CPU - - Notes - ----- - cuNumeric implementation doesn't return a view, it returns a new array - """ - if m.ndim < 2: - raise ValueError("Input must be >= 2-d.") - return flip(m, axis=1) - - -################### -# Binary operations -################### - -# Elementwise bit operations - - -################### -# Indexing routines -################### - -# Generating index arrays - - -@add_boilerplate("arr", "mask", "vals") -def place(arr: ndarray, mask: ndarray, vals: ndarray) -> None: - """ - Change elements of an array based on conditional and input values. - - Parameters - ---------- - arr : array_like - Array to put data into. - mask : array_like - Mask array. Must have the same size as `arr`. - vals : 1-D sequence - Values to put into `arr`. Only the first N elements are used, - where N is the number of True values in mask. If vals is smaller - than N, it will be repeated, and if elements of a are to be masked, - this sequence must be non-empty. - - See Also - -------- - numpy.copyto, numpy.put, numpy.take, numpy.extract - - Availability - -------- - Multiple GPUs, Multiple CPUs - """ - if arr.size == 0: - return - - check_writeable(arr) - - if mask.size != arr.size: - raise ValueError("arr array and condition array must be of same size") - - if vals.ndim != 1: - raise ValueError("vals array has to be 1-dimensional") - - if mask.shape != arr.shape: - mask_reshape = reshape(mask, arr.shape) - else: - mask_reshape = mask - - num_values = int(count_nonzero(mask_reshape)) - if num_values == 0: - return - - if vals.size == 0: - raise ValueError("vals array cannot be empty") - - if num_values != vals.size: - reps = (num_values + vals.size - 1) // vals.size - vals_resized = tile(A=vals, reps=reps) if reps > 1 else vals - vals_resized = vals_resized[:num_values] - else: - vals_resized = vals - - if mask_reshape.dtype == bool: - arr._thunk.set_item(mask_reshape._thunk, vals_resized._thunk) - else: - bool_mask = mask_reshape.astype(bool) - arr._thunk.set_item(bool_mask._thunk, vals_resized._thunk) - - -@add_boilerplate("condition", "arr") -def extract(condition: ndarray, arr: ndarray) -> ndarray: - """ - - Return the elements of an array that satisfy some condition. - - Parameters - ---------- - condition : array_like - An array whose nonzero or True entries indicate the elements - of `arr` to extract. - arr : array_like - Input array of the same size as `condition`. - - Returns - ------- - result : ndarray - Rank 1 array of values from arr where `condition` is True. - - See Also - -------- - numpy.extract - - Availability - -------- - Multiple GPUs, Multiple CPUs - """ - if condition.size != arr.size: - raise ValueError("arr array and condition array must be of same size") - - if condition.shape != arr.shape: - condition_reshape = reshape(condition, arr.shape) - else: - condition_reshape = condition - - if condition_reshape.dtype == bool: - thunk = arr._thunk.get_item(condition_reshape._thunk) - else: - bool_condition = condition_reshape.astype(bool) - thunk = arr._thunk.get_item(bool_condition._thunk) - - return ndarray(shape=thunk.shape, thunk=thunk) - - -@add_boilerplate("a") -def nonzero(a: ndarray) -> tuple[ndarray, ...]: - """ - - Return the indices of the elements that are non-zero. - - Returns a tuple of arrays, one for each dimension of `a`, - containing the indices of the non-zero elements in that - dimension. - - Parameters - ---------- - a : array_like - Input array. - - Returns - ------- - tuple_of_arrays : tuple - Indices of elements that are non-zero. - - See Also - -------- - numpy.nonzero - - Availability - -------- - Multiple GPUs, Multiple CPUs - """ - return a.nonzero() - - -@add_boilerplate("a") -def flatnonzero(a: ndarray) -> ndarray: - """ - - Return indices that are non-zero in the flattened version of a. - - This is equivalent to `np.nonzero(np.ravel(a))[0]`. - - Parameters - ---------- - a : array_like - Input array. - - Returns - ------- - res : ndarray - Output array, containing the indices of the elements of - `a.ravel()` that are non-zero. - - See Also - -------- - numpy.flatnonzero - - Availability - -------- - Multiple GPUs, Multiple CPUs - """ - return nonzero(ravel(a))[0] - - -@add_boilerplate("a", "x", "y") -def where( - a: ndarray, x: Optional[ndarray] = None, y: Optional[ndarray] = None -) -> Union[ndarray, tuple[ndarray, ...]]: - """ - where(condition, [x, y]) - - Return elements chosen from `x` or `y` depending on `condition`. - - Parameters - ---------- - condition : array_like, bool - Where True, yield `x`, otherwise yield `y`. - x, y : array_like - Values from which to choose. `x`, `y` and `condition` need to be - broadcastable to some shape. - - Returns - ------- - out : ndarray - An array with elements from `x` where `condition` is True, and elements - from `y` elsewhere. - - See Also - -------- - numpy.where - - Availability - -------- - Multiple GPUs, Multiple CPUs - """ - if x is None or y is None: - if x is not None or y is not None: - raise ValueError( - "both 'x' and 'y' parameters must be specified together for" - " 'where'" - ) - return nonzero(a) - return ndarray._perform_where(a, x, y) - - -@add_boilerplate("a") -def argwhere(a: ndarray) -> ndarray: - """ - argwhere(a) - - Find the indices of array elements that are non-zero, grouped by element. - - Parameters - ---------- - a : array_like - Input data. - - Returns - ------- - index_array : ndarray - Indices of elements that are non-zero. Indices are grouped by element. - This array will have shape (N, a.ndim) where N is the number of - non-zero items. - - See Also - -------- - numpy.argwhere - - Availability - -------- - Multiple GPUs, Multiple CPUs - """ - thunk = a._thunk.argwhere() - return ndarray(shape=thunk.shape, thunk=thunk) - - -# Indexing-like operations -def indices( - dimensions: Sequence[int], dtype: npt.DTypeLike = int, sparse: bool = False -) -> Union[ndarray, tuple[ndarray, ...]]: - """ - Return an array representing the indices of a grid. - Compute an array where the subarrays contain index values 0, 1, ... - varying only along the corresponding axis. - - Parameters - ---------- - dimensions : Sequence[int] - The shape of the grid. - dtype : data-type, optional - Data type of the result. - sparse : bool, optional - Return a sparse representation of the grid instead of a dense - representation. Default is False. - - Returns - ------- - grid : ndarray or Tuple[ndarray, ...] - If sparse is False returns one array of grid indices, - ``grid.shape = (len(dimensions),) + tuple(dimensions)``. - If sparse is True returns a tuple of arrays, with - ``grid[i].shape = (1, ..., 1, dimensions[i], 1, ..., 1)`` with - dimensions[i] in the ith place - - See Also - -------- - numpy.indices - - Notes - ----- - The output shape in the dense case is obtained by prepending the number - of dimensions in front of the tuple of dimensions, i.e. if `dimensions` - is a tuple ``(r0, ..., rN-1)`` of length ``N``, the output shape is - ``(N, r0, ..., rN-1)``. - The subarrays ``grid[k]`` contains the N-D array of indices along the - ``k-th`` axis. Explicitly: - - grid[k, i0, i1, ..., iN-1] = ik - - Availability - -------- - Multiple GPUs, Multiple CPUs - """ - # implementation of indices routine is adapted from NumPy - dimensions = tuple(dimensions) - N = len(dimensions) - shape = (1,) * N - if sparse: - res_tuple: tuple[ndarray, ...] = () - for i, dim in enumerate(dimensions): - idx = arange(dim, dtype=dtype).reshape( - shape[:i] + (dim,) + shape[i + 1 :] - ) - res_tuple += (idx,) - return res_tuple - else: - out_shape = (N,) + dimensions - res_array: ndarray = empty(out_shape, dtype=dtype) - for i, dim in enumerate(dimensions): - idx = arange(dim, dtype=dtype).reshape( - shape[:i] + (dim,) + shape[i + 1 :] - ) - res_array[i] = idx - return res_array - - -def mask_indices( - n: int, mask_func: Callable[[ndarray, int], ndarray], k: int = 0 -) -> tuple[ndarray, ...]: - """ - Return the indices to access (n, n) arrays, given a masking function. - - Assume `mask_func` is a function that, for a square array a of size - ``(n, n)`` with a possible offset argument `k`, when called as - ``mask_func(a, k)`` returns a new array with zeros in certain locations - (functions like :func:`cunumeric.triu` or :func:`cunumeric.tril` - do precisely this). Then this function returns the indices where - the non-zero values would be located. - - Parameters - ---------- - n : int - The returned indices will be valid to access arrays of shape (n, n). - mask_func : callable - A function whose call signature is similar to that of - :func:`cunumeric.triu`, :func:`cunumeric.tril`. - That is, ``mask_func(x, k)`` returns a boolean array, shaped like `x`. - `k` is an optional argument to the function. - k : scalar - An optional argument which is passed through to `mask_func`. Functions - like :func:`cunumeric.triu`, :func:`cunumeric,tril` - take a second argument that is interpreted as an offset. - - Returns - ------- - indices : tuple of arrays. - The `n` arrays of indices corresponding to the locations where - ``mask_func(np.ones((n, n)), k)`` is True. - - See Also - -------- - numpy.mask_indices - - Notes - ----- - WARNING: `mask_indices` expects `mask_function` to call cuNumeric functions - for good performance. In case non-cuNumeric functions are called by - `mask_function`, cuNumeric will have to materialize all data on the host - which might result in running out of system memory. - - Availability - -------- - Multiple GPUs, Multiple CPUs - """ - # this implementation is based on the Cupy - a = ones((n, n), dtype=bool) - if not is_implemented(mask_func): - runtime.warn( - "Calling non-cuNumeric functions in mask_func can result in bad " - "performance", - category=UserWarning, - ) - return mask_func(a, k).nonzero() - - -def diag_indices(n: int, ndim: int = 2) -> tuple[ndarray, ...]: - """ - Return the indices to access the main diagonal of an array. - - This returns a tuple of indices that can be used to access the main - diagonal of an array a with a.ndim >= 2 dimensions and - shape (n, n, …, n). For a.ndim = 2 this is the usual diagonal, - for a.ndim > 2 this is the set of indices to - access a[i, i, ..., i] for i = [0..n-1]. - - Parameters - ---------- - n : int - The size, along each dimension, of the arrays for which the - returned indices can be used. - ndim : int, optional - The number of dimensions. - - See Also - -------- - numpy.diag_indices - - Availability - -------- - Multiple GPUs, Multiple CPUs - """ - idx = arange(n, dtype=int) - return (idx,) * ndim - - -@add_boilerplate("arr") -def diag_indices_from(arr: ndarray) -> tuple[ndarray, ...]: - """ - Return the indices to access the main diagonal of an n-dimensional array. - - See diag_indices for full details. - - Parameters - ---------- - arr : array_like - at least 2-D - - See Also - -------- - numpy.diag_indices_from, numpy.diag_indices - - Availability - -------- - Multiple GPUs, Multiple CPUs - """ - if not arr.ndim >= 2: - raise ValueError("input array must be at least 2-d") - # For more than d=2, the strided formula is only valid for arrays with - # all dimensions equal, so we check first. - for i in range(1, arr.ndim): - if arr.shape[i] != arr.shape[0]: - raise ValueError("All dimensions of input must be of equal length") - - return diag_indices(arr.shape[0], arr.ndim) - - -def tril_indices( - n: int, k: int = 0, m: Optional[int] = None -) -> tuple[ndarray, ...]: - """ - Return the indices for the lower-triangle of an (n, m) array. - - Parameters - ---------- - n : int - The row dimension of the arrays for which the returned - indices will be valid. - k : int, optional - Diagonal offset (see :func:`cunumeric.tril` for details). - m : int, optional - The column dimension of the arrays for which the returned - indices will be valid. - By default `m` is taken equal to `n`. - - Returns - ------- - inds : tuple of arrays - The indices for the lower-triangle. The returned tuple contains two - arrays, each with the indices along one dimension of the array. - - See also - -------- - numpy.tril_indices - - Notes - ----- - - Availability - ------------ - Multiple GPUs, Multiple CPUs - """ - - tri_ = tri(n, m, k=k, dtype=bool) - return nonzero(tri_) - - -@add_boilerplate("arr") -def tril_indices_from(arr: ndarray, k: int = 0) -> tuple[ndarray, ...]: - """ - Return the indices for the lower-triangle of arr. - - See :func:`cunumeric.tril_indices` for full details. - - Parameters - ---------- - arr : array_like - The indices will be valid for arrays whose dimensions are - the same as arr. - k : int, optional - Diagonal offset (see :func:`cunumeric.tril` for details). - - Returns - ------- - inds : tuple of arrays - The indices for the lower-triangle. The returned tuple contains two - arrays, each with the indices along one dimension of the array. - - See Also - -------- - numpy.tril_indices_from - - Notes - ----- - - Availability - -------- - Multiple GPUs, Multiple CPUs - - """ - # this implementation is taken from numpy - if arr.ndim != 2: - raise ValueError("input array must be 2-d") - return tril_indices(arr.shape[-2], k=k, m=arr.shape[-1]) - - -def triu_indices( - n: int, k: int = 0, m: Optional[int] = None -) -> tuple[ndarray, ...]: - """ - Return the indices for the upper-triangle of an (n, m) array. - - Parameters - ---------- - n : int - The size of the arrays for which the returned indices will - be valid. - k : int, optional - Diagonal offset (see :func:`cunumeric.triu` for details). - m : int, optional - The column dimension of the arrays for which the returned - arrays will be valid. - By default `m` is taken equal to `n`. - - Returns - ------- - inds : tuple of arrays - The indices for the upper-triangle. The returned tuple contains two - arrays, each with the indices along one dimension of the array. - - See also - -------- - numpy.triu_indices - - Notes - ----- - - Availability - -------- - Multiple GPUs, Multiple CPUs - """ - - tri_ = ~tri(n, m, k=k - 1, dtype=bool) - return nonzero(tri_) - - -@add_boilerplate("arr") -def triu_indices_from(arr: ndarray, k: int = 0) -> tuple[ndarray, ...]: - """ - Return the indices for the upper-triangle of arr. - - See :func:`cunumeric.triu_indices` for full details. - - Parameters - ---------- - arr : ndarray, shape(N, N) - The indices will be valid for arrays whose dimensions are - the same as arr. - k : int, optional - Diagonal offset (see :func:`cunumeric.triu` for details). - - Returns - ------- - inds : tuple of arrays - The indices for the upper-triangle. The returned tuple contains two - arrays, each with the indices along one dimension of the array. - - See Also - -------- - numpy.triu_indices_from - - Notes - ----- - - Availability - -------- - Multiple GPUs, Multiple CPUs - """ - # this implementation is taken from numpy - if arr.ndim != 2: - raise ValueError("input array must be 2-d") - return triu_indices(arr.shape[-2], k=k, m=arr.shape[-1]) - - -@add_boilerplate("a") -def take( - a: ndarray, - indices: ndarray, - axis: Optional[int] = None, - out: Optional[ndarray] = None, - mode: BoundsMode = "raise", -) -> ndarray: - """ - Take elements from an array along an axis. - When axis is not None, this function does the same thing as “fancy” - indexing (indexing arrays using arrays); however, it can be easier - to use if you need elements along a given axis. A call such as - `np.take(arr, indices, axis=3)` is equivalent to `arr[:,:,:,indices,...]`. - - Parameters - ---------- - a : array_like `(Ni…, M, Nk…)` - The source array. - indices : array_like `(Nj…)` - The indices of the values to extract. - Also allow scalars for indices. - axis : int, optional - The axis over which to select values. By default, the flattened input - array is used. - out : ndarray, optional `(Ni…, Nj…, Nk…)` - If provided, the result will be placed in this array. It should be of - the appropriate shape and dtype. - mode : ``{'raise', 'wrap', 'clip'}``, optional - Specifies how out-of-bounds indices will behave. - 'raise' - raise an error (default) - 'wrap' - wrap around - 'clip' - clip to the range - 'clip' mode means that all indices that are too large are replaced by - the index that addresses the last element along that axis. - Note that this disables indexing with negative numbers. - - Returns - ------- - out : ndarray `(Ni…, Nj…, Nk…)` - The returned array has the same type as a. - - Raises - ------ - - See Also - -------- - numpy.take - - Availability - -------- - Multiple GPUs, Multiple CPUs - """ - return a.take(indices=indices, axis=axis, out=out, mode=mode) - - -def _fill_fancy_index_for_along_axis_routines( - a_shape: NdShape, axis: int, indices: ndarray -) -> tuple[ndarray, ...]: - # the logic below is base on the cupy implementation of - # the *_along_axis routines - ndim = len(a_shape) - fancy_index = [] - for i, n in enumerate(a_shape): - if i == axis: - fancy_index.append(indices) - else: - ind_shape = (1,) * i + (-1,) + (1,) * (ndim - i - 1) - fancy_index.append(arange(n).reshape(ind_shape)) - return tuple(fancy_index) - - -@add_boilerplate("a", "indices") -def take_along_axis( - a: ndarray, indices: ndarray, axis: Union[int, None] -) -> ndarray: - """ - Take values from the input array by matching 1d index and data slices. - - This iterates over matching 1d slices oriented along the specified axis in - the index and data arrays, and uses the former to look up values in the - latter. These slices can be different lengths. - - Functions returning an index along an axis, like - :func:`cunumeric.argsort` and :func:`cunumeric.argpartition`, - produce suitable indices for this function. - - Parameters - ---------- - arr : ndarray (Ni..., M, Nk...) - Source array - indices : ndarray (Ni..., J, Nk...) - Indices to take along each 1d slice of `arr`. This must match the - dimension of arr, but dimensions Ni and Nj only need to broadcast - against `arr`. - axis : int - The axis to take 1d slices along. If axis is None, the input array is - treated as if it had first been flattened to 1d, for consistency with - :func:`cunumeric.sort` and :func:`cunumeric.argsort`. - - Returns - ------- - out: ndarray (Ni..., J, Nk...) - The indexed result. It is going to be a view to `arr` for most cases, - except the case when `axis=Null` and `arr.ndim>1`. - - See Also - -------- - numpy.take_along_axis - - Availability - -------- - Multiple GPUs, Multiple CPUs - """ - if not np.issubdtype(indices.dtype, np.integer): - raise TypeError("`indices` must be an integer array") - - computed_axis = 0 - if axis is None: - if indices.ndim != 1: - raise ValueError("indices must be 1D if axis=None") - if a.ndim > 1: - a = a.ravel() - else: - computed_axis = normalize_axis_index(axis, a.ndim) - - if a.ndim != indices.ndim: - raise ValueError( - "`indices` and `a` must have the same number of dimensions" - ) - return a[ - _fill_fancy_index_for_along_axis_routines( - a.shape, computed_axis, indices - ) - ] - - -@add_boilerplate("a", "indices", "values") -def put_along_axis( - a: ndarray, indices: ndarray, values: ndarray, axis: Union[int, None] -) -> None: - """ - Put values into the destination array by matching 1d index and data slices. - - This iterates over matching 1d slices oriented along the specified axis in - the index and data arrays, and uses the former to place values into the - latter. These slices can be different lengths. - - Functions returning an index along an axis, like :func:`cunumeric.argsort` - and :func:`cunumeric.argpartition`, produce suitable indices for - this function. - - Parameters - ---------- - a : ndarray (Ni..., M, Nk...) - Destination array. - indices : ndarray (Ni..., J, Nk...) - Indices to change along each 1d slice of `arr`. This must match the - dimension of arr, but dimensions in Ni and Nj may be 1 to broadcast - against `arr`. - values : array_like (Ni..., J, Nk...) - values to insert at those indices. Its shape and dimension are - broadcast to match that of `indices`. - axis : int - The axis to take 1d slices along. If axis is None, the destination - array is treated as if a flattened 1d view had been created of it. - `axis=None` case is currently supported only for 1D input arrays. - - Note - ---- - Having duplicate entries in `indices` will result in undefined behavior - since operation performs asynchronous update of the `arr` entries. - - See Also - -------- - numpy.put_along_axis - - Availability - -------- - Multiple GPUs, Multiple CPUs - - """ - - if a.size == 0: - return - - check_writeable(a) - - if not np.issubdtype(indices.dtype, np.integer): - raise TypeError("`indices` must be an integer array") - - computed_axis = 0 - if axis is None: - if indices.ndim != 1: - raise ValueError("indices must be 1D if axis=None") - if a.ndim > 1: - # TODO call a=a.flat when flat is implemented - raise ValueError("a.ndim>1 case is not supported when axis=None") - if (indices.size == 0) or (values.size == 0): - return - if values.shape != indices.shape: - values = values._wrap(indices.size) - else: - computed_axis = normalize_axis_index(axis, a.ndim) - - if a.ndim != indices.ndim: - raise ValueError( - "`indices` and `a` must have the same number of dimensions" - ) - ind = _fill_fancy_index_for_along_axis_routines( - a.shape, computed_axis, indices - ) - a[ind] = values - - -@add_boilerplate("a") -def choose( - a: ndarray, - choices: Sequence[ndarray], - out: Optional[ndarray] = None, - mode: BoundsMode = "raise", -) -> ndarray: - """ - Construct an array from an index array and a list of arrays to choose from. - - Given an "index" array (`a`) of integers and a sequence of ``n`` arrays - (`choices`), `a` and each choice array are first broadcast, as necessary, - to arrays of a common shape; calling these *Ba* and *Bchoices[i], i = - 0,...,n-1* we have that, necessarily, ``Ba.shape == Bchoices[i].shape`` - for each ``i``. Then, a new array with shape ``Ba.shape`` is created as - follows: - - * if ``mode='raise'`` (the default), then, first of all, each element of - ``a`` (and thus ``Ba``) must be in the range ``[0, n-1]``; now, suppose - that ``i`` (in that range) is the value at the ``(j0, j1, ..., jm)`` - position in ``Ba`` - then the value at the same position in the new array - is the value in ``Bchoices[i]`` at that same position; - - * if ``mode='wrap'``, values in `a` (and thus `Ba`) may be any (signed) - integer; modular arithmetic is used to map integers outside the range - `[0, n-1]` back into that range; and then the new array is constructed - as above; - - * if ``mode='clip'``, values in `a` (and thus ``Ba``) may be any (signed) - integer; negative integers are mapped to 0; values greater than ``n-1`` - are mapped to ``n-1``; and then the new array is constructed as above. - - Parameters - ---------- - a : ndarray[int] - This array must contain integers in ``[0, n-1]``, where ``n`` is the - number of choices, unless ``mode=wrap`` or ``mode=clip``, in which - cases any integers are permissible. - choices : Sequence[ndarray] - Choice arrays. `a` and all of the choices must be broadcastable to the - same shape. If `choices` is itself an array (not recommended), then - its outermost dimension (i.e., the one corresponding to - ``choices.shape[0]``) is taken as defining the "sequence". - out : ndarray, optional - If provided, the result will be inserted into this array. It should - be of the appropriate shape and dtype. Note that `out` is always - buffered if ``mode='raise'``; use other modes for better performance. - mode : ``{'raise', 'wrap', 'clip'}``, optional - Specifies how indices outside ``[0, n-1]`` will be treated: - - * 'raise' : an exception is raised (default) - * 'wrap' : value becomes value mod ``n`` - * 'clip' : values < 0 are mapped to 0, values > n-1 are mapped to n-1 - - Returns - ------- - merged_array : ndarray - The merged result. - - Raises - ------ - ValueError: shape mismatch - If `a` and each choice array are not all broadcastable to the same - shape. - - See Also - -------- - numpy.choose - - Availability - -------- - Multiple GPUs, Multiple CPUs - """ - return a.choose(choices=choices, out=out, mode=mode) - - -def select( - condlist: Sequence[npt.ArrayLike | ndarray], - choicelist: Sequence[npt.ArrayLike | ndarray], - default: Any = 0, -) -> ndarray: - """ - Return an array drawn from elements in choicelist, depending on conditions. - - Parameters - ---------- - condlist : list of bool ndarrays - The list of conditions which determine from which array in `choicelist` - the output elements are taken. When multiple conditions are satisfied, - the first one encountered in `condlist` is used. - choicelist : list of ndarrays - The list of arrays from which the output elements are taken. It has - to be of the same length as `condlist`. - default : scalar, optional - The element inserted in `output` when all conditions evaluate to False. - - Returns - ------- - output : ndarray - The output at position m is the m-th element of the array in - `choicelist` where the m-th element of the corresponding array in - `condlist` is True. - - See Also - -------- - numpy.select - - Availability - -------- - Multiple GPUs, Multiple CPUs - """ - - if len(condlist) != len(choicelist): - raise ValueError( - "list of cases must be same length as list of conditions" - ) - if len(condlist) == 0: - raise ValueError("select with an empty condition list is not possible") - - condlist_ = tuple(convert_to_cunumeric_ndarray(c) for c in condlist) - for i, c in enumerate(condlist_): - if c.dtype != bool: - raise TypeError( - f"invalid entry {i} in condlist: should be boolean ndarray" - ) - - choicelist_ = tuple(convert_to_cunumeric_ndarray(c) for c in choicelist) - common_type = np.result_type(*choicelist_, default) - args = condlist_ + choicelist_ - choicelist_ = tuple( - c._maybe_convert(common_type, args) for c in choicelist_ - ) - default_ = np.array(default, dtype=common_type) - - out_shape = np.broadcast_shapes( - *(c.shape for c in condlist_), - *(c.shape for c in choicelist_), - ) - out = ndarray(shape=out_shape, dtype=common_type, inputs=args) - out._thunk.select( - tuple(c._thunk for c in condlist_), - tuple(c._thunk for c in choicelist_), - default_, - ) - return out - - -@add_boilerplate("condition", "a") -def compress( - condition: ndarray, - a: ndarray, - axis: Optional[int] = None, - out: Optional[ndarray] = None, -) -> ndarray: - """ - Return selected slices of an array along given axis. - - When working along a given axis, a slice along that axis is returned - in output for each index where condition evaluates to True. - When working on a 1-D array, compress is equivalent to numpy.extract. - - Parameters - ---------- - condition, 1-D array of bools - Array that selects which entries to return. If `len(c)` is less than - the size of a along the given axis, then output is truncated to the - length of the condition array. - - a : array_like - Array from which to extract a part. - - axis: int, optional - Axis along which to take slices. If None (default), - work on the flattened array. - - out : ndarray, optional - Output array. Its type is preserved and it must be of the right - shape to hold the output. - - Returns - ------- - compressed_array : ndarray - A copy of `a` without the slices along `axis` for which condition - is false. - - Raises - ------ - ValueError : dimension mismatch - If condition is not 1D array - ValueError : shape mismatch - If condition contains entries that are out of bounds of array - ValueError : shape mismatch - If output array has a wrong shape - - See Also - -------- - numpy.compress, numpy.extract - - Availability - -------- - Multiple GPUs, Multiple CPUs - - """ - return a.compress(condition, axis=axis, out=out) - - -@add_boilerplate("a") -def diagonal( - a: ndarray, - offset: int = 0, - axis1: int = 0, - axis2: int = 1, - extract: bool = True, -) -> ndarray: - """ - diagonal(a: ndarray, offset=0, axis1=None, axis2=None) - - Return specified diagonals. - - If `a` is 2-D, returns the diagonal of `a` with the given offset, - i.e., the collection of elements of the form ``a[i, i+offset]``. If - `a` has more than two dimensions, then the axes specified by `axis1` - and `axis2` are used to determine the 2-D sub-array whose diagonal is - returned. The shape of the resulting array can be determined by - removing `axis1` and `axis2` and appending an index to the right equal - to the size of the resulting diagonals. - - Parameters - ---------- - a : array_like - Array from which the diagonals are taken. - offset : int, optional - Offset of the diagonal from the main diagonal. Can be positive or - negative. Defaults to main diagonal (0). - axis1 : int, optional - Axis to be used as the first axis of the 2-D sub-arrays from which - the diagonals should be taken. Defaults to first axis (0). - axis2 : int, optional - Axis to be used as the second axis of the 2-D sub-arrays from - which the diagonals should be taken. Defaults to second axis (1). - - Returns - ------- - array_of_diagonals : ndarray - If `a` is 2-D, then a 1-D array containing the diagonal and of the - same type as `a` is returned unless `a` is a `matrix`, in which case - a 1-D array rather than a (2-D) `matrix` is returned in order to - maintain backward compatibility. - - If ``a.ndim > 2``, then the dimensions specified by `axis1` and `axis2` - are removed, and a new axis inserted at the end corresponding to the - diagonal. - - Raises - ------ - ValueError - If the dimension of `a` is less than 2. - - Notes - ----- - Unlike NumPy's, the cuNumeric implementation always returns a copy - - See Also - -------- - numpy.diagonal - - Availability - -------- - Multiple GPUs, Multiple CPUs - - """ - return a.diagonal(offset=offset, axis1=axis1, axis2=axis2, extract=extract) - - -@add_boilerplate("a", "indices", "values") -def put( - a: ndarray, indices: ndarray, values: ndarray, mode: str = "raise" -) -> None: - """ - Replaces specified elements of an array with given values. - The indexing works as if the target array is first flattened. - - Parameters - ---------- - a : array_like - Array to put data into - indices : array_like - Target indices, interpreted as integers. - WARNING: In case there are repeated entries in the - indices array, Legate doesn't guarantee the order in - which values are updated. - - values : array_like - Values to place in `a` at target indices. If values array is shorter - than indices, it will be repeated as necessary. - mode : {'raise', 'wrap', 'clip'}, optional - Specifies how out-of-bounds indices will behave. - 'raise' : raise an error. - 'wrap' : wrap around. - 'clip' : clip to the range. - - See Also - -------- - numpy.put - - Availability - -------- - Multiple GPUs, Multiple CPUs - """ - a.put(indices=indices, values=values, mode=mode) - - -@add_boilerplate("a", "mask", "values") -def putmask(a: ndarray, mask: ndarray, values: ndarray) -> None: - """ - putmask(a, mask, values) - Changes elements of an array based on conditional and input values. - Sets ``a.flat[n] = values[n]`` for each n where ``mask.flat[n]==True``. - If `values` is not the same size as `a` and `mask` then it will repeat. - This gives behavior different from ``a[mask] = values``. - - Parameters - ---------- - a : ndarray - Target array. - mask : array_like - Boolean mask array. It has to be the same shape as `a`. - values : array_like - Values to put into `a` where `mask` is True. If `values` is smaller - than `a` it will be repeated. - - See Also - -------- - numpy.putmask - - Availability - ------------ - Multiple GPUs, Multiple CPUs - """ - if not a.shape == mask.shape: - raise ValueError("mask and data must be the same size") - - check_writeable(a) - - mask = mask._warn_and_convert(np.dtype(bool)) - - if a.dtype != values.dtype: - values = values._warn_and_convert(a.dtype) - - try: - np.broadcast_shapes(values.shape, a.shape) - except ValueError: - values = values._wrap(a.size) - values = values.reshape(a.shape) - - a._thunk.putmask(mask._thunk, values._thunk) - - -@add_boilerplate("a", "val") -def fill_diagonal(a: ndarray, val: ndarray, wrap: bool = False) -> None: - """ - Fill the main diagonal of the given array of any dimensionality. - - For an array a with a.ndim >= 2, the diagonal is the list of locations with - indices a[i, ..., i] all identical. This function modifies the input - array in-place, it does not return a value. - - Parameters - ---------- - - a : array, at least 2-D. - Array whose diagonal is to be filled, it gets modified in-place. - val : scalar or array_like - Value(s) to write on the diagonal. If val is scalar, the value is - written along the diagonal. - If array-like, the flattened val is written along - the diagonal, repeating if necessary to fill all diagonal entries. - wrap : bool - If true, the diagonal "wraps" after N columns, for tall 2d matrices. - - Raises - ------ - ValueError - If the dimension of `a` is less than 2. - - Notes - ----- - - See Also - -------- - numpy.fill_diagonal - - Availability - -------- - Multiple GPUs, Multiple CPUs - - """ - if val.size == 0 or a.size == 0: - return - - check_writeable(a) - - if a.ndim < 2: - raise ValueError("array must be at least 2-d") - - n = _builtin_min(a.shape) - - if a.ndim > 2: - for s in a.shape: - if s != n: - raise ValueError( - "All dimensions of input must be of equal length" - ) - - len_val = n - - if a.ndim == 2 and wrap and a.shape[0] > a.shape[1]: - len_val = a.shape[0] - (a.shape[0] // (a.shape[1] + 1)) - - if (val.size != len_val and val.ndim > 0) or val.ndim > 1: - val = val._wrap(len_val) - - if a.ndim == 2 and wrap and a.shape[0] > a.shape[1]: - idx0_tmp = arange(a.shape[1], dtype=int) - idx0 = idx0_tmp.copy() - while idx0.size < len_val: - idx0_tmp = idx0_tmp + (a.shape[1] + 1) - idx0 = hstack((idx0, idx0_tmp)) - idx0 = idx0[0:len_val] - idx1 = arange(len_val, dtype=int) % a.shape[1] - a[idx0, idx1] = val - else: - idx = arange(n, dtype=int) - indices = (idx,) * a.ndim - - a[indices] = val - - -################ -# Linear algebra -################ - -# Matrix and vector products - - -@add_boilerplate("a", "b") -def inner(a: ndarray, b: ndarray, out: Optional[ndarray] = None) -> ndarray: - """ - Inner product of two arrays. - - Ordinary inner product of vectors for 1-D arrays (without complex - conjugation), in higher dimensions a sum product over the last axes. - - Parameters - ---------- - a, b : array_like - out : ndarray, optional - Output argument. This must have the exact shape that would be returned - if it was not present. If its dtype is not what would be expected from - this operation, then the result will be (unsafely) cast to `out`. - - Returns - ------- - output : ndarray - If `a` and `b` are both - scalars or both 1-D arrays then a scalar is returned; otherwise - an array is returned. - ``output.shape = (*a.shape[:-1], *b.shape[:-1])`` - If `out` is given, then it is returned. - - Notes - ----- - The cuNumeric implementation is a little more liberal than NumPy in terms - of allowed broadcasting, e.g. ``inner(ones((1,)), ones((4,)))`` is allowed. - - See Also - -------- - numpy.inner - - Availability - -------- - Multiple GPUs, Multiple CPUs - """ - if a.ndim == 0 or b.ndim == 0: - return multiply(a, b, out=out) - (a_modes, b_modes, out_modes) = inner_modes(a.ndim, b.ndim) - return _contract( - a_modes, - b_modes, - out_modes, - a, - b, - out=out, - casting="unsafe", - ) - - -@add_boilerplate("a", "b") -def dot(a: ndarray, b: ndarray, out: Optional[ndarray] = None) -> ndarray: - """ - Dot product of two arrays. Specifically, - - - If both `a` and `b` are 1-D arrays, it is inner product of vectors - (without complex conjugation). - - - If both `a` and `b` are 2-D arrays, it is matrix multiplication, - but using ``a @ b`` is preferred. - - - If either `a` or `b` is 0-D (scalar), it is equivalent to - :func:`multiply` and using ``cunumeric.multiply(a, b)`` or ``a * b`` is - preferred. - - - If `a` is an N-D array and `b` is a 1-D array, it is a sum product over - the last axis of `a` and `b`. - - - If `a` is an N-D array and `b` is an M-D array (where ``M>=2``), it is a - sum product over the last axis of `a` and the second-to-last axis of - `b`:: - - dot(a: ndarray, b)[i,j,k,m] = sum(a[i,j,:] * b[k,:,m]) - - Parameters - ---------- - a : array_like - First argument. - b : array_like - Second argument. - out : ndarray, optional - Output argument. This must have the exact shape and dtype that would be - returned if it was not present. - - Returns - ------- - output : ndarray - Returns the dot product of `a` and `b`. If `out` is given, then it is - returned. - - Notes - ----- - The cuNumeric implementation is a little more liberal than NumPy in terms - of allowed broadcasting, e.g. ``dot(ones((3,1)), ones((4,5)))`` is allowed. - - Except for the inner-product case, only floating-point types are supported. - - See Also - -------- - numpy.dot - - Availability - -------- - Multiple GPUs, Multiple CPUs - """ - return a.dot(b, out=out) - - -@add_boilerplate("a", "b") -def matmul( - a: ndarray, - b: ndarray, - /, - out: Optional[ndarray] = None, - *, - casting: CastingKind = "same_kind", - dtype: Optional[np.dtype[Any]] = None, -) -> ndarray: - """ - Matrix product of two arrays. - - Parameters - ---------- - a, b : array_like - Input arrays, scalars not allowed. - out : ndarray, optional - A location into which the result is stored. If provided, it must have - a shape that matches the signature `(n,k),(k,m)->(n,m)`. - casting : ``{'no', 'equiv', 'safe', 'same_kind', 'unsafe'}``, optional - Controls what kind of data casting may occur. - - * 'no' means the data types should not be cast at all. - * 'equiv' means only byte-order changes are allowed. - * 'safe' means only casts which can preserve values are allowed. - * 'same_kind' means only safe casts or casts within a kind, - like float64 to float32, are allowed. - * 'unsafe' means any data conversions may be done. - - Default is 'same_kind'. - dtype : data-type, optional - If provided, forces the calculation to use the data type specified. - Note that you may have to also give a more liberal `casting` - parameter to allow the conversions. Default is None. - - Returns - ------- - output : ndarray - The matrix product of the inputs. - This is a scalar only when both a, b are 1-d vectors. - If `out` is given, then it is returned. - - Notes - ----- - The behavior depends on the arguments in the following way. - - - If both arguments are 2-D they are multiplied like conventional - matrices. - - If either argument is N-D, N > 2, it is treated as a stack of - matrices residing in the last two indexes and broadcast accordingly. - - If the first argument is 1-D, it is promoted to a matrix by - prepending a 1 to its dimensions. After matrix multiplication - the prepended 1 is removed. - - If the second argument is 1-D, it is promoted to a matrix by - appending a 1 to its dimensions. After matrix multiplication - the appended 1 is removed. - - ``matmul`` differs from ``dot`` in two important ways: - - - Multiplication by scalars is not allowed, use ``*`` instead. - - Stacks of matrices are broadcast together as if the matrices - were elements, respecting the signature ``(n,k),(k,m)->(n,m)``: - - >>> a = ones([9, 5, 7, 4]) - >>> c = ones([9, 5, 4, 3]) - >>> dot(a: ndarray, c).shape - (9, 5, 7, 9, 5, 3) - >>> matmul(a: ndarray, c).shape - (9, 5, 7, 3) - >>> # n is 7, k is 4, m is 3 - - The cuNumeric implementation is a little more liberal than NumPy in terms - of allowed broadcasting, e.g. ``matmul(ones((3,1)), ones((4,5)))`` is - allowed. - - Only floating-point types are supported. - - See Also - -------- - numpy.matmul - - Availability - -------- - Multiple GPUs, Multiple CPUs - """ - if a.ndim == 0 or b.ndim == 0: - raise ValueError("Scalars not allowed in matmul") - - (a_modes, b_modes, out_modes) = matmul_modes(a.ndim, b.ndim) - - return _contract( - a_modes, - b_modes, - out_modes, - a, - b, - out=out, - casting=casting, - dtype=dtype, - ) - - -@add_boilerplate("a", "b") -def vdot(a: ndarray, b: ndarray, out: Optional[ndarray] = None) -> ndarray: - """ - Return the dot product of two vectors. - - The vdot(`a`, `b`) function handles complex numbers differently than - dot(`a`, `b`). If the first argument is complex the complex conjugate - of the first argument is used for the calculation of the dot product. - - Note that `vdot` handles multidimensional arrays differently than `dot`: - it does *not* perform a matrix product, but flattens input arguments - to 1-D vectors first. Consequently, it should only be used for vectors. - - Parameters - ---------- - a : array_like - If `a` is complex the complex conjugate is taken before calculation - of the dot product. - b : array_like - Second argument to the dot product. - out : ndarray, optional - Output argument. This must have the exact shape that would be returned - if it was not present. If its dtype is not what would be expected from - this operation, then the result will be (unsafely) cast to `out`. - - Returns - ------- - output : ndarray - Dot product of `a` and `b`. If `out` is given, then it is returned. - - Notes - ----- - The cuNumeric implementation is a little more liberal than NumPy in terms - of allowed broadcasting, e.g. ``vdot(ones((1,)), ones((4,)))`` is allowed. - - See Also - -------- - numpy.vdot - - Availability - -------- - Multiple GPUs, Multiple CPUs - """ - return inner(a.ravel().conj(), b.ravel(), out=out) - - -@add_boilerplate("a", "b") -def outer(a: ndarray, b: ndarray, out: Optional[ndarray] = None) -> ndarray: - """ - Compute the outer product of two vectors. - - Given two vectors, ``a = [a0, a1, ..., aM]`` and ``b = [b0, b1, ..., bN]``, - the outer product is:: - - [[a0*b0 a0*b1 ... a0*bN ] - [a1*b0 . - [ ... . - [aM*b0 aM*bN ]] - - Parameters - ---------- - a : (M,) array_like - First input vector. Input is flattened if not already 1-dimensional. - b : (N,) array_like - Second input vector. Input is flattened if not already 1-dimensional. - out : (M, N) ndarray, optional - A location where the result is stored. If its dtype is not what would - be expected from this operation, then the result will be (unsafely) - cast to `out`. - - Returns - ------- - output : (M, N) ndarray - ``output[i, j] = a[i] * b[j]`` - If `out` is given, then it is returned. - - See Also - -------- - numpy.outer - - Availability - -------- - Multiple GPUs, Multiple CPUs - """ - return multiply( - a.ravel()[:, np.newaxis], b.ravel()[np.newaxis, :], out=out - ) - - -@add_boilerplate("a", "b") -def tensordot( - a: ndarray, - b: ndarray, - axes: AxesPairLike = 2, - out: Optional[ndarray] = None, -) -> ndarray: - """ - Compute tensor dot product along specified axes. - - Given two tensors, `a` and `b`, and an array_like object containing - two array_like objects, ``(a_axes, b_axes)``, sum the products of - `a`'s and `b`'s elements (components) over the axes specified by - ``a_axes`` and ``b_axes``. The third argument can be a single non-negative - integer_like scalar, ``N``; if it is such, then the last ``N`` dimensions - of `a` and the first ``N`` dimensions of `b` are summed over. - - Parameters - ---------- - a, b : array_like - Tensors to "dot". - - axes : int or array_like - * integer_like - If an int N, sum over the last N axes of `a` and the first N axes - of `b` in order. - * (2,) array_like - Or, a list of axes to be summed over, first sequence applying to `a`, - second to `b`. Both elements array_like must be of the same length. - out : ndarray, optional - Output argument. This must have the exact shape that would be returned - if it was not present. If its dtype is not what would be expected from - this operation, then the result will be (unsafely) cast to `out`. - - Returns - ------- - output : ndarray - The tensor dot product of the inputs. If `out` is given, then it is - returned. - - Notes - ----- - The cuNumeric implementation is a little more liberal than NumPy in terms - of allowed broadcasting, e.g. ``tensordot(ones((3,1)), ones((1,4)))`` is - allowed. - - Except for the inner-product case, only floating-point types are supported. - - See Also - -------- - numpy.tensordot - - Availability - -------- - Multiple GPUs, Multiple CPUs - """ - (a_modes, b_modes, out_modes) = tensordot_modes(a.ndim, b.ndim, axes) - - return _contract( - a_modes, - b_modes, - out_modes, - a, - b, - out=out, - casting="unsafe", - ) - - -# Trivial multi-tensor contraction strategy: contract in input order -class NullOptimizer(oe.paths.PathOptimizer): # type: ignore [misc,no-any-unimported] # noqa - def __call__( - self, - inputs: list[set[str]], - outputs: set[str], - size_dict: dict[str, int], - memory_limit: Union[int, None] = None, - ) -> list[tuple[int, int]]: - return [(0, 1)] + [(0, -1)] * (len(inputs) - 2) - - -def _maybe_cast_input( - arr: ndarray, to_dtype: np.dtype[Any], casting: CastingKind -) -> ndarray: - if arr.dtype == to_dtype: - return arr - if not np.can_cast(arr.dtype, to_dtype, casting=casting): - raise TypeError( - f"Cannot cast input array of type {arr.dtype} to {to_dtype} with " - f"casting rule '{casting}'" - ) - return arr.astype(to_dtype) - - -# Generalized tensor contraction -def _contract( - a_modes: list[str], - b_modes: list[str], - out_modes: list[str], - a: ndarray, - b: Optional[ndarray] = None, - out: Optional[ndarray] = None, - casting: CastingKind = "same_kind", - dtype: Optional[np.dtype[Any]] = None, -) -> ndarray: - # Sanity checks - if len(a_modes) != a.ndim: - raise ValueError( - f"Expected {len(a_modes)}-d input array but got {a.ndim}-d" - ) - - if b is None: - if len(b_modes) != 0: - raise ValueError("Missing input array") - elif len(b_modes) != b.ndim: - raise ValueError( - f"Expected {len(b_modes)}-d input array but got {b.ndim}-d" - ) - - if out is not None and len(out_modes) != out.ndim: - raise ValueError( - f"Expected {len(out_modes)}-d output array but got {out.ndim}-d" - ) - - if len(set(out_modes)) != len(out_modes): - raise ValueError("Duplicate mode labels on output") - - if len(set(out_modes) - set(a_modes) - set(b_modes)) > 0: - raise ValueError("Unknown mode labels on output") - - makes_view = b is None and len(a_modes) == len(out_modes) - if dtype is not None and not makes_view: - c_dtype = dtype - elif out is not None: - c_dtype = out.dtype - elif b is None: - c_dtype = a.dtype - else: - c_dtype = ndarray.find_common_type(a, b) - - a = _maybe_cast_input(a, c_dtype, casting) - - if b is not None: - b = _maybe_cast_input(b, c_dtype, casting) - - out_dtype = out.dtype if out is not None else c_dtype - - # Handle duplicate modes on inputs - c_a_modes = Counter(a_modes) - for mode, count in c_a_modes.items(): - if count > 1: - axes = [i for (i, m) in enumerate(a_modes) if m == mode] - a = a._diag_helper(axes=axes) - # diagonal is stored on last axis - a_modes = [m for m in a_modes if m != mode] + [mode] - c_b_modes = Counter(b_modes) - for mode, count in c_b_modes.items(): - if count > 1: - axes = [i for (i, m) in enumerate(b_modes) if m == mode] - b = b._diag_helper(axes=axes) # type: ignore [union-attr] - # diagonal is stored on last axis - b_modes = [m for m in b_modes if m != mode] + [mode] - - # Drop modes corresponding to singleton dimensions. This handles cases of - # broadcasting. - for dim in reversed(range(a.ndim)): - if a.shape[dim] == 1: - a = a.squeeze(dim) - a_modes.pop(dim) - if b is not None: - for dim in reversed(range(b.ndim)): - if b.shape[dim] == 1: - b = b.squeeze(dim) - b_modes.pop(dim) - - # Sum-out modes appearing on one argument, and missing from the result - # TODO: If we supported sum on multiple axes we could do the full sum in a - # single operation, and avoid intermediates. - for dim, mode in reversed(list(enumerate(a_modes))): - if mode not in b_modes and mode not in out_modes: - a_modes.pop(dim) - a = a.sum(axis=dim) - - for dim, mode in reversed(list(enumerate(b_modes))): - if mode not in a_modes and mode not in out_modes: - b_modes.pop(dim) - b = b.sum(axis=dim) # type: ignore [union-attr] - - # Compute extent per mode. No need to consider broadcasting at this stage, - # since it has been handled above. - mode2extent: dict[str, int] = {} - for mode, extent in chain( - zip(a_modes, a.shape), zip(b_modes, b.shape) if b is not None else [] - ): - prev_extent = mode2extent.get(mode) - if prev_extent is not None and extent != prev_extent: - raise ValueError( - f"Incompatible sizes between matched dimensions: {extent} vs " - f"{prev_extent}" - ) - mode2extent[mode] = extent - - # Any modes appearing only on the result must have originally been present - # on one of the operands, but got dropped by the broadcast-handling code. - out_shape = ( - out.shape - if out is not None - else tuple(mode2extent.get(mode, 1) for mode in out_modes) - ) - c_modes = [] - c_shape: NdShape = () - c_bloated_shape: NdShape = () - for mode, extent in zip(out_modes, out_shape): - if mode not in a_modes and mode not in b_modes: - c_bloated_shape += (1,) - else: - assert extent > 1 - c_modes.append(mode) - c_shape += (extent,) - c_bloated_shape += (extent,) - - # Verify output array has the right shape (input arrays can be broadcasted - # up to match the output, but not the other way around). There should be no - # unknown or singleton modes on the result at this point. - for mode, extent in zip(c_modes, c_shape): - prev_extent = mode2extent[mode] - assert prev_extent != 1 - if extent != prev_extent: - raise ValueError("Wrong shape on output array") - - # Test for fallback to unary case - if b is not None: - if len(a_modes) == 0: - a = a * b - a_modes = b_modes - b = None - b_modes = [] - elif len(b_modes) == 0: - a = a * b - b = None - - if b is None: - # Unary contraction case - assert len(a_modes) == len(c_modes) and set(a_modes) == set(c_modes) - if len(a_modes) == 0: - # NumPy doesn't return a view in this case - c = copy(a) - elif a_modes == c_modes: - c = a - else: - # Shuffle input array according to mode labels - axes = [a_modes.index(mode) for mode in c_modes] - assert _builtin_all(ax >= 0 for ax in axes) - c = a.transpose(axes) - - else: - # Binary contraction case - # Create result array, if output array can't be directly targeted - if out is not None and out_dtype == c_dtype and out_shape == c_shape: - c = out - else: - c = ndarray( - shape=c_shape, - dtype=c_dtype, - inputs=(a, b), - ) - # Perform operation - c._thunk.contract( - c_modes, - a._thunk, - a_modes, - b._thunk, - b_modes, - mode2extent, - ) - - # Postprocess result before returning - if out is c: - # We already decided above to use the output array directly - return out - if out_dtype != c_dtype or out_shape != c_bloated_shape: - # We need to broadcast the result of the contraction or switch types - # before returning - if not np.can_cast(c_dtype, out_dtype, casting=casting): - raise TypeError( - f"Cannot cast intermediate result array of type {c_dtype} " - f"into output array of type {out_dtype} with casting rule " - f"'{casting}'" - ) - if out is None: - out = ndarray( - shape=out_shape, - dtype=out_dtype, - inputs=(c,), - ) - out[...] = c.reshape(c_bloated_shape) - return out - if out_shape != c_shape: - # We need to add missing dimensions, but they are all of size 1, so - # we don't need to broadcast - assert c_bloated_shape == out_shape - if out is None: - return c.reshape(out_shape) - else: - out[...] = c.reshape(out_shape) - return out - if out is not None: - # The output and result arrays are fully compatible, but we still - # need to copy - out[...] = c - return out - return c - - -def einsum( - expr: str, - *operands: ndarray, - out: Optional[ndarray] = None, - dtype: Optional[np.dtype[Any]] = None, - casting: CastingKind = "safe", - optimize: Union[bool, Literal["greedy", "optimal"]] = True, -) -> ndarray: - """ - Evaluates the Einstein summation convention on the operands. - - Using the Einstein summation convention, many common multi-dimensional, - linear algebraic array operations can be represented in a simple fashion. - In *implicit* mode `einsum` computes these values. - - In *explicit* mode, `einsum` provides further flexibility to compute - other array operations that might not be considered classical Einstein - summation operations, by disabling, or forcing summation over specified - subscript labels. - - Parameters - ---------- - subscripts : str - Specifies the subscripts for summation as comma separated list of - subscript labels. An implicit (classical Einstein summation) - calculation is performed unless the explicit indicator '->' is - included as well as subscript labels of the precise output form. - operands : list[array_like] - These are the arrays for the operation. - out : ndarray, optional - If provided, the calculation is done into this array. - dtype : data-type, optional - If provided, forces the calculation to use the data type specified. - Note that you may have to also give a more liberal `casting` - parameter to allow the conversions. Default is None. - casting : ``{'no', 'equiv', 'safe', 'same_kind', 'unsafe'}``, optional - Controls what kind of data casting may occur. - - * 'no' means the data types should not be cast at all. - * 'equiv' means only byte-order changes are allowed. - * 'safe' means only casts which can preserve values are allowed. - * 'same_kind' means only safe casts or casts within a kind, - like float64 to float32, are allowed. - * 'unsafe' means any data conversions may be done. - - Default is 'safe'. - optimize : ``{False, True, 'greedy', 'optimal'}``, optional - Controls if intermediate optimization should occur. If False then - arrays will be contracted in input order, one at a time. True (the - default) will use the 'greedy' algorithm. See ``cunumeric.einsum_path`` - for more information on the available optimization algorithms. - - Returns - ------- - output : ndarray - The calculation based on the Einstein summation convention. - - Notes - ----- - For most expressions, only floating-point types are supported. - - See Also - -------- - numpy.einsum - - Availability - -------- - Multiple GPUs, Multiple CPUs - """ - operands_list = [convert_to_cunumeric_ndarray(op) for op in operands] - - if out is not None: - out = convert_to_cunumeric_ndarray(out, share=True) - - if optimize is True: - optimize = "greedy" - elif optimize is False: - optimize = NullOptimizer() - - # This call normalizes the expression (adds the output part if it's - # missing, expands '...') and checks for some errors (mismatch on number - # of dimensions between operand and expression, wrong number of operands, - # unknown modes on output, a mode appearing under two different - # non-singleton extents). - computed_operands, contractions = oe.contract_path( - expr, *operands_list, einsum_call=True, optimize=optimize - ) - for indices, _, sub_expr, _, _ in contractions: - assert len(indices) == 1 or len(indices) == 2 - a = computed_operands.pop(indices[0]) - b = computed_operands.pop(indices[1]) if len(indices) == 2 else None - if b is None: - m = re.match(r"([a-zA-Z]*)->([a-zA-Z]*)", sub_expr) - if m is None: - raise NotImplementedError("Non-alphabetic mode labels") - a_modes = list(m.group(1)) - b_modes = [] - out_modes = list(m.group(2)) - else: - m = re.match(r"([a-zA-Z]*),([a-zA-Z]*)->([a-zA-Z]*)", sub_expr) - if m is None: - raise NotImplementedError("Non-alphabetic mode labels") - a_modes = list(m.group(1)) - b_modes = list(m.group(2)) - out_modes = list(m.group(3)) - sub_result = _contract( - a_modes, - b_modes, - out_modes, - a, - b, - out=(out if len(computed_operands) == 0 else None), - casting=casting, - dtype=dtype, - ) - computed_operands.append(sub_result) - - assert len(computed_operands) == 1 - return computed_operands[0] - - -def einsum_path( - expr: str, - *operands: ndarray, - optimize: Union[bool, list[Any], tuple[Any, ...], str] = "greedy", -) -> tuple[list[Union[str, int]], str]: - """ - Evaluates the lowest cost contraction order for an einsum expression by - considering the creation of intermediate arrays. - - Parameters - ---------- - expr : str - Specifies the subscripts for summation. - *operands : Sequence[array_like] - These are the arrays for the operation. - optimize : ``{bool, list, tuple, 'greedy', 'optimal'}`` - Choose the type of path. If a tuple is provided, the second argument is - assumed to be the maximum intermediate size created. If only a single - argument is provided the largest input or output array size is used - as a maximum intermediate size. - - * if a list is given that starts with ``einsum_path``, uses this as the - contraction path - * if False no optimization is taken - * if True defaults to the 'greedy' algorithm - * 'optimal' An algorithm that combinatorially explores all possible - ways of contracting the listed tensors and chooses the least costly - path. Scales exponentially with the number of terms in the - contraction. - * 'greedy' An algorithm that chooses the best pair contraction - at each step. Effectively, this algorithm searches the largest inner, - Hadamard, and then outer products at each step. Scales cubically with - the number of terms in the contraction. Equivalent to the 'optimal' - path for most contractions. - - Default is 'greedy'. - - Returns - ------- - path : list[Tuple[int,...]] - A list representation of the einsum path. - string_repr : str - A printable representation of the einsum path. - - Notes - ----- - The resulting path indicates which terms of the input contraction should be - contracted first, the result of this contraction is then appended to the - end of the contraction list. This list can then be iterated over until all - intermediate contractions are complete. - - See Also - -------- - numpy.einsum_path - - Availability - -------- - Multiple GPUs, Multiple CPUs - """ - computed_operands = [convert_to_cunumeric_ndarray(op) for op in operands] - memory_limit = _builtin_max(op.size for op in computed_operands) - if isinstance(optimize, tuple): - if len(optimize) != 2: - raise ValueError("einsum_path expects optimize tuples of size 2") - optimize, memory_limit = optimize - if optimize is True: - optimize = "greedy" - elif optimize is False: - optimize = [tuple(range(len(computed_operands)))] - elif optimize in ["greedy", "optimal"]: - pass - elif ( - isinstance(optimize, list) - and len(optimize) > 1 - and optimize[0] == "einsum_path" - ): - optimize = optimize[1:] - else: - raise ValueError( - f"einsum_path: unexpected value for optimize: {optimize}" - ) - path, info = oe.contract_path( - expr, *computed_operands, optimize=optimize, memory_limit=memory_limit - ) - return ["einsum_path"] + path, info - - -@add_boilerplate("a") -def trace( - a: ndarray, - offset: int = 0, - axis1: Optional[int] = None, - axis2: Optional[int] = None, - dtype: Optional[np.dtype[Any]] = None, - out: Optional[ndarray] = None, -) -> ndarray: - """ - Return the sum along diagonals of the array. - - If a is 2-D, the sum along its diagonal with the given offset is - returned, i.e., the sum of elements a[i,i+offset] for all i. - If a has more than two dimensions, then the axes specified by axis1 - and axis2 are used to determine the 2-D sub-arrays whose traces - are returned. The shape of the resulting array is the same as that - of a with axis1 and axis2 removed. - - Parameters - ---------- - a : array_like - Input array, from which the diagonals are taken. - offset : int, optional - Offset of the diagonal from the main diagonal. Can be both - positive and negative. Defaults to 0. - axis1, axis2 : int, optional - Axes to be used as the first and second axis of the 2-D sub-arrays - from which the diagonals should be taken. Defaults are the - first two axes of a. - dtype : data-type, optional - Determines the data-type of the returned array and of the - accumulator where the elements are summed. If dtype has the value - None and a is of integer type of precision less than the default - integer precision, then the default integer precision is used. - Otherwise, the precision is the same as that of a. - - out : ndarray, optional - Array into which the output is placed. Its type is preserved and - it must be of the right shape to hold the output. - - Returns - ------- - sum_along_diagonals : ndarray - If a is 2-D, the sum along the diagonal is returned. If a has - larger dimensions, then an array of sums along diagonals is returned. - - Raises - ------ - ValueError - If the dimension of `a` is less than 2. - - See Also - -------- - numpy.diagonal - - Availability - -------- - Multiple GPUs, Multiple CPUs - """ - return a.trace( - offset=offset, axis1=axis1, axis2=axis2, dtype=dtype, out=out - ) - - -################# -# Logic functions -################# - -# Truth value testing - - -@add_boilerplate("a") -def all( - a: ndarray, - axis: Optional[Union[int, tuple[int, ...]]] = None, - out: Optional[ndarray] = None, - keepdims: bool = False, - where: Optional[ndarray] = None, -) -> ndarray: - """ - Test whether all array elements along a given axis evaluate to True. - - Parameters - ---------- - a : array_like - Input array or object that can be converted to an array. - axis : None or int or tuple[int], optional - Axis or axes along which a logical AND reduction is performed. - The default (``axis=None``) is to perform a logical AND over all - the dimensions of the input array. `axis` may be negative, in - which case it counts from the last to the first axis. - - If this is a tuple of ints, a reduction is performed on multiple - axes, instead of a single axis or all the axes as before. - out : ndarray, optional - Alternate output array in which to place the result. - It must have the same shape as the expected output and its - type is preserved (e.g., if ``dtype(out)`` is float, the result - will consist of 0.0's and 1.0's). See `ufuncs-output-type` for more - details. - - keepdims : bool, optional - If this is set to True, the axes which are reduced are left - in the result as dimensions with size one. With this option, - the result will broadcast correctly against the input array. - - If the default value is passed, then `keepdims` will not be - passed through to the `all` method of sub-classes of - `ndarray`, however any non-default value will be. If the - sub-class' method does not implement `keepdims` any - exceptions will be raised. - - Returns - ------- - all : ndarray, bool - A new boolean or array is returned unless `out` is specified, - in which case a reference to `out` is returned. - - See Also - -------- - numpy.all - - Availability - -------- - Multiple GPUs, Multiple CPUs - """ - return a.all(axis=axis, out=out, keepdims=keepdims, where=where) - - -@add_boilerplate("a") -def any( - a: ndarray, - axis: Optional[Union[int, tuple[int, ...]]] = None, - out: Optional[ndarray] = None, - keepdims: bool = False, - where: Optional[ndarray] = None, -) -> ndarray: - """ - Test whether any array element along a given axis evaluates to True. - - Returns single boolean unless `axis` is not ``None`` - - Parameters - ---------- - a : array_like - Input array or object that can be converted to an array. - axis : None or int or tuple[int], optional - Axis or axes along which a logical OR reduction is performed. - The default (``axis=None``) is to perform a logical OR over all - the dimensions of the input array. `axis` may be negative, in - which case it counts from the last to the first axis. - - If this is a tuple of ints, a reduction is performed on multiple - axes, instead of a single axis or all the axes as before. - out : ndarray, optional - Alternate output array in which to place the result. It must have - the same shape as the expected output and its type is preserved - (e.g., if it is of type float, then it will remain so, returning - 1.0 for True and 0.0 for False, regardless of the type of `a`). - See `ufuncs-output-type` for more details. - - keepdims : bool, optional - If this is set to True, the axes which are reduced are left - in the result as dimensions with size one. With this option, - the result will broadcast correctly against the input array. - - If the default value is passed, then `keepdims` will not be - passed through to the `any` method of sub-classes of - `ndarray`, however any non-default value will be. If the - sub-class' method does not implement `keepdims` any - exceptions will be raised. - - Returns - ------- - any : bool or ndarray - A new boolean or `ndarray` is returned unless `out` is specified, - in which case a reference to `out` is returned. - - See Also - -------- - numpy.any - - Availability - -------- - Multiple GPUs, Multiple CPUs - """ - return a.any(axis=axis, out=out, keepdims=keepdims, where=where) - - -# Array contents - - -# Logic operations - - -# Comparison - - -@add_boilerplate("a", "b") -def allclose( - a: ndarray, - b: ndarray, - rtol: float = 1e-5, - atol: float = 1e-8, - equal_nan: bool = False, -) -> ndarray: - """ - - Returns True if two arrays are element-wise equal within a tolerance. - - The tolerance values are positive, typically very small numbers. The - relative difference (`rtol` * abs(`b`)) and the absolute difference - `atol` are added together to compare against the absolute difference - between `a` and `b`. - - NaNs are treated as equal if they are in the same place and if - ``equal_nan=True``. Infs are treated as equal if they are in the same - place and of the same sign in both arrays. - - Parameters - ---------- - a, b : array_like - Input arrays to compare. - rtol : float - The relative tolerance parameter (see Notes). - atol : float - The absolute tolerance parameter (see Notes). - equal_nan : bool - Whether to compare NaN's as equal. If True, NaN's in `a` will be - considered equal to NaN's in `b` in the output array. - - Returns - ------- - allclose : ndarray scalar - Returns True if the two arrays are equal within the given - tolerance; False otherwise. - - Notes - ----- - If the following equation is element-wise True, then allclose returns - True. - - absolute(`a` - `b`) <= (`atol` + `rtol` * absolute(`b`)) - - See Also - -------- - numpy.allclose - - Availability - -------- - Multiple GPUs, Multiple CPUs - """ - if equal_nan: - raise NotImplementedError( - "cuNumeric does not support `equal_nan` yet for allclose" - ) - args = (Scalar(rtol, ty.float64), Scalar(atol, ty.float64)) - return ndarray._perform_binary_reduction( - BinaryOpCode.ISCLOSE, - a, - b, - dtype=np.dtype(bool), - extra_args=args, - ) - - -@add_boilerplate("a", "b") -def isclose( - a: ndarray, - b: ndarray, - rtol: float = 1e-5, - atol: float = 1e-8, - equal_nan: bool = False, -) -> ndarray: - """ - - Returns a boolean array where two arrays are element-wise equal within a - tolerance. - - Parameters - ---------- - a, b : array_like - Input arrays to compare. - rtol : float - The relative tolerance parameter (see Notes). - atol : float - The absolute tolerance parameter (see Notes). - equal_nan : bool - Whether to compare NaN's as equal. If True, NaN's in `a` will be - considered equal to NaN's in `b` in the output array. - - Returns - ------- - y : array_like - Returns a boolean array of where `a` and `b` are equal within the - given tolerance. If both `a` and `b` are scalars, returns a single - boolean value. - - Notes - ----- - For finite values, isclose uses the following equation to test whether - two floating point values are equivalent. - - absolute(`a` - `b`) <= (`atol` + `rtol` * absolute(`b`)) - - See Also - -------- - numpy.isclose - - Availability - -------- - Multiple GPUs, Multiple CPUs - """ - if equal_nan: - raise NotImplementedError( - "cuNumeric does not support `equal_nan` yet for isclose" - ) - - out_shape = np.broadcast_shapes(a.shape, b.shape) - out = empty(out_shape, dtype=bool) - - common_type = ndarray.find_common_type(a, b) - a = a.astype(common_type) - b = b.astype(common_type) - - out._thunk.isclose(a._thunk, b._thunk, rtol, atol, equal_nan) - return out - - -@add_boilerplate("a1", "a2") -def array_equal( - a1: ndarray, a2: ndarray, equal_nan: bool = False -) -> Union[bool, ndarray]: - """ - - True if two arrays have the same shape and elements, False otherwise. - - Parameters - ---------- - a1, a2 : array_like - Input arrays. - equal_nan : bool - Whether to compare NaN's as equal. If the dtype of a1 and a2 is - complex, values will be considered equal if either the real or the - imaginary component of a given value is ``nan``. - - Returns - ------- - b : ndarray scalar - Returns True if the arrays are equal. - - See Also - -------- - numpy.array_equal - - Availability - -------- - Multiple GPUs, Multiple CPUs - """ - if equal_nan: - raise NotImplementedError( - "cuNumeric does not support `equal_nan` yet for `array_equal`" - ) - - if a1.shape != a2.shape: - return False - return ndarray._perform_binary_reduction( - BinaryOpCode.EQUAL, a1, a2, dtype=np.dtype(np.bool_) - ) - - -######################## -# Mathematical functions -######################## - -# Trigonometric functions - - -# Hyperbolic functions - - -# Rounding - - -# Sums, products, differences - - -@add_boilerplate("a") -def prod( - a: ndarray, - axis: Optional[Union[int, tuple[int, ...]]] = None, - dtype: Optional[np.dtype[Any]] = None, - out: Optional[ndarray] = None, - keepdims: bool = False, - initial: Optional[Union[int, float]] = None, - where: Optional[ndarray] = None, -) -> ndarray: - """ - - Return the product of array elements over a given axis. - - Parameters - ---------- - a : array_like - Input data. - axis : None or int or tuple[int], optional - Axis or axes along which a product is performed. The default, - axis=None, will calculate the product of all the elements in the - input array. If axis is negative it counts from the last to the - first axis. - - If axis is a tuple of ints, a product is performed on all of the - axes specified in the tuple instead of a single axis or all the - axes as before. - dtype : data-type, optional - The type of the returned array, as well as of the accumulator in - which the elements are multiplied. The dtype of `a` is used by - default unless `a` has an integer dtype of less precision than the - default platform integer. In that case, if `a` is signed then the - platform integer is used while if `a` is unsigned then an unsigned - integer of the same precision as the platform integer is used. - out : ndarray, optional - Alternative output array in which to place the result. It must have - the same shape as the expected output, but the type of the output - values will be cast if necessary. - keepdims : bool, optional - If this is set to True, the axes which are reduced are left in the - result as dimensions with size one. With this option, the result - will broadcast correctly against the input array. - - If the default value is passed, then `keepdims` will not be - passed through to the `prod` method of sub-classes of - `ndarray`, however any non-default value will be. If the - sub-class' method does not implement `keepdims` any - exceptions will be raised. - initial : scalar, optional - The starting value for this product. See `~cunumeric.ufunc.reduce` for - details. - - where : array_like[bool], optional - Elements to include in the product. See `~cunumeric.ufunc.reduce` for - details. - - Returns - ------- - product_along_axis : ndarray, see `dtype` parameter above. - An array shaped as `a` but with the specified axis removed. - Returns a reference to `out` if specified. - - See Also - -------- - numpy.prod - - Availability - -------- - Multiple GPUs, Multiple CPUs - """ - return multiply.reduce( - a, - axis=axis, - dtype=dtype, - out=out, - keepdims=keepdims, - initial=initial, - where=where, - ) - - -@add_boilerplate("a") -def sum( - a: ndarray, - axis: Optional[Union[int, tuple[int, ...]]] = None, - dtype: Optional[np.dtype[Any]] = None, - out: Optional[ndarray] = None, - keepdims: bool = False, - initial: Optional[Union[int, float]] = None, - where: Optional[ndarray] = None, -) -> ndarray: - """ - - Sum of array elements over a given axis. - - Parameters - ---------- - a : array_like - Elements to sum. - axis : None or int or tuple[int], optional - Axis or axes along which a sum is performed. The default, - axis=None, will sum all of the elements of the input array. If - axis is negative it counts from the last to the first axis. - - If axis is a tuple of ints, a sum is performed on all of the axes - specified in the tuple instead of a single axis or all the axes as - before. - dtype : data-type, optional - The type of the returned array and of the accumulator in which the - elements are summed. The dtype of `a` is used by default unless `a` - has an integer dtype of less precision than the default platform - integer. In that case, if `a` is signed then the platform integer - is used while if `a` is unsigned then an unsigned integer of the - same precision as the platform integer is used. - out : ndarray, optional - Alternative output array in which to place the result. It must have - the same shape as the expected output, but the type of the output - values will be cast if necessary. - keepdims : bool, optional - If this is set to True, the axes which are reduced are left - in the result as dimensions with size one. With this option, - the result will broadcast correctly against the input array. - - If the default value is passed, then `keepdims` will not be - passed through to the `sum` method of sub-classes of - `ndarray`, however any non-default value will be. If the - sub-class' method does not implement `keepdims` any - exceptions will be raised. - initial : scalar, optional - Starting value for the sum. See `~cunumeric.ufunc.reduce` for details. - - where : array_like[bool], optional - Elements to include in the sum. See `~cunumeric.ufunc.reduce` for - details. - - Returns - ------- - sum_along_axis : ndarray - An array with the same shape as `a`, with the specified - axis removed. If `a` is a 0-d array, or if `axis` is None, a scalar - is returned. If an output array is specified, a reference to - `out` is returned. - - See Also - -------- - numpy.sum - - Availability - -------- - Multiple GPUs, Multiple CPUs - """ - return add.reduce( - a, - axis=axis, - dtype=dtype, - out=out, - keepdims=keepdims, - initial=initial, - where=where, - ) - - -@add_boilerplate("a") -def cumprod( - a: ndarray, - axis: Optional[int] = None, - dtype: Optional[np.dtype[Any]] = None, - out: Optional[ndarray] = None, -) -> ndarray: - """ - Return the cumulative product of the elements along a given axis. - - Parameters - ---------- - a : array_like - Input array. - - axis : int, optional - Axis along which the cumulative product is computed. The default (None) - is to compute the cumprod over the flattened array. - - dtype : dtype, optional - Type of the returned array and of the accumulator in which the elements - are multiplied. If dtype is not specified, it defaults to the dtype of - a, unless a has an integer dtype with a precision less than that of the - default platform integer. In that case, the default platform integer is - used. - out : ndarray, optional - Alternative output array in which to place the result. It must have the - same shape and buffer length as the expected output but the type will - be cast if necessary. See Output type determination for more details. - - Returns - ------- - cumprod : ndarray - A new array holding the result is returned unless out is specified, in - which case a reference to out is returned. The result has the same size - as a, and the same shape as a if axis is not None or a is a 1-d array. - - See Also - -------- - numpy.cumprod - - Notes - ----- - CuNumeric's parallel implementation may yield different results from NumPy - with floating point and complex types. For example, when boundary values - such as inf occur they may not propagate as expected. Consider the float32 - array ``[3e+37, 1, 100, 0.01]``. NumPy's cumprod will return a result of - ``[3e+37, 3e+37, inf, inf]``. However, cuNumeric might internally partition - the array such that partition 0 has ``[3e+37, 1]`` and partition 1 has - ``[100, 0.01]``, returning the result ``[3e+37, 3e+37, inf, 3e+37]``. - - Availability - -------- - Multiple GPUs, Multiple CPUs - """ - return ndarray._perform_scan( - ScanCode.PROD, - a, - axis=axis, - dtype=dtype, - out=out, - nan_to_identity=False, - ) - - -@add_boilerplate("a") -def cumsum( - a: ndarray, - axis: Optional[int] = None, - dtype: Optional[np.dtype[Any]] = None, - out: Optional[ndarray] = None, -) -> ndarray: - """ - Return the cumulative sum of the elements along a given axis. - - Parameters - ---------- - a : array_like - Input array. - - axis : int, optional - Axis along which the cumulative sum is computed. The default (None) is - to compute the cumsum over the flattened array. - - dtype : dtype, optional - Type of the returned array and of the accumulator in which the elements - are summed. If dtype is not specified, it defaults to the dtype of a, - unless a has an integer dtype with a precision less than that of the - default platform integer. In that case, the default platform integer is - used. - out : ndarray, optional - Alternative output array in which to place the result. It must have the - same shape and buffer length as the expected output but the type will - be cast if necessary. See Output type determination for more details. - - Returns - ------- - cumsum : ndarray. - A new array holding the result is returned unless out is specified, in - which case a reference to out is returned. The result has the same size - as a, and the same shape as a if axis is not None or a is a 1-d array. - - See Also - -------- - numpy.cumsum - - Notes - ----- - CuNumeric's parallel implementation may yield different results from NumPy - with floating point and complex types. For example, when boundary values - such as inf occur they may not propagate as expected. For more explanation - check cunumeric.cumprod. - - Availability - -------- - Multiple GPUs, Multiple CPUs - """ - return ndarray._perform_scan( - ScanCode.SUM, a, axis=axis, dtype=dtype, out=out, nan_to_identity=False - ) - - -@add_boilerplate("a") -def nancumprod( - a: ndarray, - axis: Optional[int] = None, - dtype: Optional[np.dtype[Any]] = None, - out: Optional[ndarray] = None, -) -> ndarray: - """ - Return the cumulative product of the elements along a given axis treating - Not a Numbers (NaNs) as one. The cumulative product does not change when - NaNs are encountered and leading NaNs are replaced by ones. - - Ones are returned for slices that are all-NaN or empty. - - Parameters - ---------- - a : array_like - Input array. - - axis : int, optional - Axis along which the cumulative product is computed. The default (None) - is to compute the nancumprod over the flattened array. - - dtype : dtype, optional - Type of the returned array and of the accumulator in which the elements - are multiplied. If dtype is not specified, it defaults to the dtype of - a, unless a has an integer dtype with a precision less than that of the - default platform integer. In that case, the default platform integer is - used. - out : ndarray, optional - Alternative output array in which to place the result. It must have the - same shape and buffer length as the expected output but the type will - be cast if necessary. See Output type determination for more details. - - Returns - ------- - nancumprod : ndarray. - A new array holding the result is returned unless out is specified, in - which case a reference to out is returned. The result has the same size - as a, and the same shape as a if axis is not None or a is a 1-d array. - - See Also - -------- - numpy.nancumprod - - Notes - ----- - CuNumeric's parallel implementation may yield different results from NumPy - with floating point and complex types. For example, when boundary values - such as inf occur they may not propagate as expected. For more explanation - check cunumeric.cumprod. - - Availability - -------- - Multiple GPUs, Multiple CPUs - """ - return ndarray._perform_scan( - ScanCode.PROD, a, axis=axis, dtype=dtype, out=out, nan_to_identity=True - ) - - -@add_boilerplate("a") -def nancumsum( - a: ndarray, - axis: Optional[int] = None, - dtype: Optional[np.dtype[Any]] = None, - out: Optional[ndarray] = None, -) -> ndarray: - """ - Return the cumulative sum of the elements along a given axis treating Not a - Numbers (NaNs) as zero. The cumulative sum does not change when NaNs are - encountered and leading NaNs are replaced by zeros. - - Zeros are returned for slices that are all-NaN or empty. - - Parameters - ---------- - a : array_like - Input array. - - axis : int, optional - Axis along which the cumulative sum is computed. The default (None) is - to compute the nancumsum over the flattened array. - - dtype : dtype, optional - Type of the returned array and of the accumulator in which the elements - are summed. If dtype is not specified, it defaults to the dtype of a, - unless a has an integer dtype with a precision less than that of the - default platform integer. In that case, the default platform integer is - used. - out : ndarray, optional - Alternative output array in which to place the result. It must have the - same shape and buffer length as the expected output but the type will - be cast if necessary. See Output type determination for more details. - - Returns - ------- - nancumsum : ndarray. - A new array holding the result is returned unless out is specified, in - which case a reference to out is returned. The result has the same size - as a, and the same shape as a if axis is not None or a is a 1-d array. - - See Also - -------- - numpy.nancumsum - - Notes - ----- - CuNumeric's parallel implementation may yield different results from NumPy - with floating point and complex types. For example, when boundary values - such as inf occur they may not propagate as expected. For more explanation - check cunumeric.cumprod. - - Availability - -------- - Multiple GPUs, Multiple CPUs - """ - return ndarray._perform_scan( - ScanCode.SUM, a, axis=axis, dtype=dtype, out=out, nan_to_identity=True - ) - - -@add_boilerplate("a") -def nanargmax( - a: ndarray, - axis: Any = None, - out: Union[ndarray, None] = None, - *, - keepdims: bool = False, -) -> ndarray: - """ - Return the indices of the maximum values in the specified axis ignoring - NaNs. For empty arrays, ValueError is raised. For all-NaN slices, - ValueError is raised only when CUNUMERIC_NUMPY_COMPATIBILITY - environment variable is set, otherwise identity is returned. - - Warning: results cannot be trusted if a slice contains only NaNs - and -Infs. - - Parameters - ---------- - a : array_like - Input array. - axis : int, optional - By default, the index corresponds to the flattened array, otherwise - along the specified axis. - out : ndarray, optional - If provided, the result will be inserted into this array. It should - be of the appropriate shape and dtype. - keepdims : bool, optional - If this is set to True, the axes which are reduced are left - in the result as dimensions with size one. With this option, - the result will broadcast correctly against the array. - - Returns - ------- - index_array : ndarray[int] - Array of indices into the array. It has the same shape as `a.shape` - with the dimension along `axis` removed. - - See Also - -------- - numpy.nanargmin, numpy.nanargmax - - Availability - -------- - Multiple GPUs, Multiple CPUs - """ - - if a.size == 0: - raise ValueError("attempt to get nanargmax of an empty sequence") - - if cunumeric_settings.numpy_compat() and a.dtype.kind == "f": - if any(all(isnan(a), axis=axis)): - raise ValueError("Array/Slice contains only NaNs") - - unary_red_code = get_non_nan_unary_red_code( - a.dtype.kind, UnaryRedCode.NANARGMAX - ) - - return a._perform_unary_reduction( - unary_red_code, - a, - axis=axis, - out=out, - keepdims=keepdims, - res_dtype=np.dtype(np.int64), - ) - - -@add_boilerplate("a") -def nanargmin( - a: ndarray, - axis: Any = None, - out: Union[ndarray, None] = None, - *, - keepdims: bool = False, -) -> ndarray: - """ - Return the indices of the minimum values in the specified axis ignoring - NaNs. For empty arrays, ValueError is raised. For all-NaN slices, - ValueError is raised only when CUNUMERIC_NUMPY_COMPATIBILITY - environment variable is set, otherwise identity is returned. - - Warning: results cannot be trusted if a slice contains only NaNs - and -Infs. - - Parameters - ---------- - a : array_like - Input array. - axis : int, optional - By default, the index corresponds to the flattened array, otherwise - along the specified axis. - out : ndarray, optional - If provided, the result will be inserted into this array. It should - be of the appropriate shape and dtype. - keepdims : bool, optional - If this is set to True, the axes which are reduced are left - in the result as dimensions with size one. With this option, - the result will broadcast correctly against the array. - - Returns - ------- - index_array : ndarray[int] - Array of indices into the array. It has the same shape as `a.shape` - with the dimension along `axis` removed. - - See Also - -------- - numpy.nanargmin, numpy.nanargmax - - Availability - -------- - Multiple GPUs, Multiple CPUs - """ - - if a.size == 0: - raise ValueError("attempt to get nanargmin of an empty sequence") - - if cunumeric_settings.numpy_compat() and a.dtype.kind == "f": - if any(all(isnan(a), axis=axis)): - raise ValueError("Array/Slice contains only NaNs") - - unary_red_code = get_non_nan_unary_red_code( - a.dtype.kind, UnaryRedCode.NANARGMIN - ) - - return a._perform_unary_reduction( - unary_red_code, - a, - axis=axis, - out=out, - keepdims=keepdims, - res_dtype=np.dtype(np.int64), - ) - - -@add_boilerplate("a") -def nanmin( - a: ndarray, - axis: Any = None, - out: Union[ndarray, None] = None, - keepdims: bool = False, - initial: Optional[Union[int, float]] = None, - where: Optional[ndarray] = None, -) -> ndarray: - """ - Return minimum of an array or minimum along an axis, ignoring any - NaNs. When all-NaN slices are encountered, a NaN is returned - for that slice only when CUNUMERIC_NUMPY_COMPATIBILITY environment - variable is set, otherwise identity is returned. - Empty slices will raise a ValueError - - Parameters - ---------- - a : array_like - Array containing numbers whose minimum is desired. If a is not an - array, a conversion is attempted. - - axis : {int, tuple of int, None}, optional - Axis or axes along which the minimum is computed. The default is to - compute the minimum of the flattened array. - - out : ndarray, optional - Alternative output array in which to place the result. Must - be of the same shape and buffer length as the expected output. - See `ufuncs-output-type` for more details. - - keepdims : bool, Optional - If this is set to True, the axes which are reduced are left - in the result as dimensions with size one. With this option, - the result will broadcast correctly against the input array. - - If the default value is passed, then `keepdims` will not be - passed through to the `amin` method of sub-classes of - `ndarray`, however any non-default value will be. If the - sub-class' method does not implement `keepdims` any - exceptions will be raised. - - initial : scalar, optional - The maximum value of an output element. Must be present to allow - computation on empty slice. See `~cunumeric.ufunc.reduce` for details. - - where : array_like[bool], optional - Elements to compare for the minimum. See `~cunumeric.ufunc.reduce` - for details. - - Returns - ------- - nanmin : ndarray or scalar - Minimum of `a`. If `axis` is None, the result is a scalar value. - If `axis` is given, the result is an array of dimension - ``a.ndim - 1``. - - Notes - ----- - CuNumeric's implementation will not raise a Runtime Warning for - slices with all-NaNs - - See Also - -------- - numpy.nanmin, numpy.nanmax, numpy.min, numpy.max, numpy.isnan, - numpy.maximum - - Availability - -------- - Multiple GPUs, Multiple CPUs - """ - - unary_red_code = get_non_nan_unary_red_code( - a.dtype.kind, UnaryRedCode.NANMIN - ) - - out_array = a._perform_unary_reduction( - unary_red_code, - a, - axis=axis, - out=out, - keepdims=keepdims, - initial=initial, - where=where, - ) - - if cunumeric_settings.numpy_compat() and a.dtype.kind == "f": - all_nan = all(isnan(a), axis=axis, keepdims=keepdims, where=where) - putmask(out_array, all_nan, np.nan) # type: ignore - - return out_array - - -@add_boilerplate("a") -def nanmax( - a: ndarray, - axis: Any = None, - out: Union[ndarray, None] = None, - keepdims: bool = False, - initial: Optional[Union[int, float]] = None, - where: Optional[ndarray] = None, -) -> ndarray: - """ - Return the maximum of an array or maximum along an axis, ignoring any - NaNs. When all-NaN slices are encountered, a NaN is returned - for that slice only when CUNUMERIC_NUMPY_COMPATIBILITY environment - variable is set, otherwise identity is returned. - Empty slices will raise a ValueError - - Parameters - ---------- - a : array_like - Array containing numbers whose maximum is desired. If a is not - an array, a conversion is attempted. - - axis : None or int or tuple[int], optional - Axis or axes along which to operate. By default, flattened input is - used. - - If this is a tuple of ints, the maximum is selected over multiple axes, - instead of a single axis or all the axes as before. - - out : ndarray, optional - Alternative output array in which to place the result. Must - be of the same shape and buffer length as the expected output. - See `ufuncs-output-type` for more details. - - keepdims : bool, optional - If this is set to True, the axes which are reduced are left - in the result as dimensions with size one. With this option, - the result will broadcast correctly against the input array. - - If the default value is passed, then `keepdims` will not be - passed through to the `amax` method of sub-classes of - `ndarray`, however any non-default value will be. If the - sub-class' method does not implement `keepdims` any - exceptions will be raised. - - initial : scalar, optional - The minimum value of an output element. Must be present to allow - computation on empty slice. See `~cunumeric.ufunc.reduce` for details. - - where : array_like[bool], optional - Elements to compare for the maximum. See `~cunumeric.ufunc.reduce` - for details. - - Returns - ------- - nanmax : ndarray or scalar - An array with the same shape as `a`, with the specified axis - removed. If `a` is 0-d array, of if axis is None, an ndarray - scalar is returned. The same dtype as `a` is returned. - - Notes - ----- - CuNumeric's implementation will not raise a Runtime Warning for - slices with all-NaNs - - See Also - -------- - numpy.nanmin, numpy.amax, numpy.isnan, numpy.fmax, numpy.maximum, - numpy.isfinite - - Availability - -------- - Multiple GPUs, Multiple CPUs - """ - - unary_red_code = get_non_nan_unary_red_code( - a.dtype.kind, UnaryRedCode.NANMAX - ) - - out_array = a._perform_unary_reduction( - unary_red_code, - a, - axis=axis, - out=out, - keepdims=keepdims, - initial=initial, - where=where, - ) - - if cunumeric_settings.numpy_compat() and a.dtype.kind == "f": - all_nan = all(isnan(a), axis=axis, keepdims=keepdims, where=where) - putmask(out_array, all_nan, np.nan) # type: ignore - - return out_array - - -@add_boilerplate("a") -def nanprod( - a: ndarray, - axis: Any = None, - dtype: Any = None, - out: Union[ndarray, None] = None, - keepdims: bool = False, - initial: Optional[Union[int, float]] = None, - where: Optional[ndarray] = None, -) -> ndarray: - """ - Return the product of array elements over a given axis treating - Not a Numbers (NaNs) as ones. - - One is returned for slices that are all-NaN or empty. - - Parameters - ---------- - a : array_like - Input array. - axis : int, optional - Axis or axes along which the product is computed. The - default is to compute the product of the flattened array. - dtype : data-type, optional - The type of the returned array and of the accumulator in - which the elements are summed. By default, the dtype of a - is used. An exception is when a has an integer type with - less precision than the platform (u)intp. In that case, - the default will be either (u)int32 or (u)int64 depending - on whether the platform is 32 or 64 bits. For inexact - inputs, dtype must be inexact. - out : ndarray, optional - Alternate output array in which to place the result. The - default is None. If provided, it must have the same shape as - the expected output, but the type will be cast if necessary. - See Output type determination for more details. The casting of - NaN to integer can yield unexpected results. - keepdims : bool, optional - If this is set to True, the axes which are reduced are left in the - result as dimensions with size one. With this option, the result - will broadcast correctly against the input array. - - If the default value is passed, then `keepdims` will not be - passed through to the `prod` method of sub-classes of - `ndarray`, however any non-default value will be. If the - sub-class' method does not implement `keepdims` any - exceptions will be raised. - initial : scalar, optional - The starting value for this product. See `~cunumeric.ufunc.reduce` for - details. - where : array_like[bool], optional - Elements to include in the product. See `~cunumeric.ufunc.reduce` for - details. - - Returns - ------- - nanprod: ndarray, see `dtype` parameter above. - A new array holding the result is returned unless out is - specified, in which case it is returned. - - See Also - -------- - numpy.prod, numpy.isnan - - Availability - -------- - Multiple GPUs, Multiple CPUs - - """ - - # Note: if the datatype of the input array is int and less - # than that of the platform int, then a convert task is launched - # in np.prod to take care of the type casting - - if a.dtype == np.complex128: - raise NotImplementedError( - "operation is not supported for complex128 arrays" - ) - - if a.dtype.kind in ("f", "c"): - unary_red_code = UnaryRedCode.NANPROD - else: - unary_red_code = UnaryRedCode.PROD - - return a._perform_unary_reduction( - unary_red_code, - a, - axis=axis, - dtype=dtype, - out=out, - keepdims=keepdims, - initial=initial, - where=where, - ) - - -@add_boilerplate("a") -def nansum( - a: ndarray, - axis: Any = None, - dtype: Any = None, - out: Union[ndarray, None] = None, - keepdims: bool = False, - initial: Optional[Union[int, float]] = None, - where: Optional[ndarray] = None, -) -> ndarray: - """ - Return the sum of array elements over a given axis treating - Not a Numbers (NaNs) as ones. - - Zero is returned for slices that are all-NaN or empty. - - Parameters - ---------- - a : array_like - Array containing numbers whose product is desired. If a is not - an array, a conversion is attempted. - - axis : None or int or tuple[int], optional - Axis or axes along which a sum is performed. The default, - axis=None, will sum all of the elements of the input array. - If axis is negative it counts from the last to the first axis. - - If axis is a tuple of ints, a sum is performed on all of the - axes specified in the tuple instead of a single axis or all - the axes as before. - - dtype : data-type, optional - The type of the returned array and of the accumulator in which - the elements are summed. The dtype of `a` is used by default - unless `a` has an integer dtype of less precision than the - default platform integer. In that case, if `a` is signed then - the platform integer is used while if `a` is unsigned then an - unsigned integer of the same precision as the platform integer - is used. - - out : ndarray, optional - Alternative output array in which to place the result. It must - have the same shape as the expected output, but the type of - the output values will be cast if necessary. - - keepdims : bool, optional - If this is set to True, the axes which are reduced are left - in the result as dimensions with size one. With this option, - the result will broadcast correctly against the input array. - - initial : scalar, optional - Starting value for the sum. See `~cunumeric.ufunc.reduce` for - details. - - where : array_like[bool], optional - Elements to include in the sum. See `~cunumeric.ufunc.reduce` for - details. - - Returns - ------- - nansum : ndarray, see `dtype` parameter above. - A new array holding the result is returned unless out is - specified, in which case it is returned. The result has the - same size as a, and the same shape as a if axis is not None or - a is a 1-d array. - - See Also - -------- - numpy.nansum, numpy.isnan, numpy.isfinite - - Availability - -------- - Multiple GPUs, Multiple CPUs - """ - - return a._nansum( - axis=axis, - dtype=dtype, - out=out, - keepdims=keepdims, - initial=initial, - where=where, - ) - - -# Exponents and logarithms - - -# Arithmetic operations - - -# Handling complex numbers - - -@add_boilerplate("val") -def real(val: ndarray) -> ndarray: - """ - Return the real part of the complex argument. - - Parameters - ---------- - val : array_like - Input array. - - Returns - ------- - out : ndarray or scalar - The real component of the complex argument. If `val` is real, the type - of `val` is used for the output. If `val` has complex elements, the - returned type is float. - - See Also - -------- - numpy.real - - Availability - -------- - Multiple GPUs, Multiple CPUs - """ - return val.real - - -@add_boilerplate("val") -def imag(val: ndarray) -> ndarray: - """ - - Return the imaginary part of the complex argument. - - Parameters - ---------- - val : array_like - Input array. - - Returns - ------- - out : ndarray or scalar - The imaginary component of the complex argument. If `val` is real, - the type of `val` is used for the output. If `val` has complex - elements, the returned type is float. - - See Also - -------- - numpy.imag - - Availability - -------- - Multiple GPUs, Multiple CPUs - """ - return val.imag - - -# Extrema Finding - - -@add_boilerplate("a") -def amax( - a: ndarray, - axis: Optional[Union[int, tuple[int, ...]]] = None, - dtype: Optional[np.dtype[Any]] = None, - out: Optional[ndarray] = None, - keepdims: bool = False, - initial: Optional[Union[int, float]] = None, - where: Optional[ndarray] = None, -) -> ndarray: - """ - - Return the maximum of an array or maximum along an axis. - - Parameters - ---------- - a : array_like - Input data. - axis : None or int or tuple[int], optional - Axis or axes along which to operate. By default, flattened input is - used. - - If this is a tuple of ints, the maximum is selected over multiple axes, - instead of a single axis or all the axes as before. - out : ndarray, optional - Alternative output array in which to place the result. Must - be of the same shape and buffer length as the expected output. - See `ufuncs-output-type` for more details. - - keepdims : bool, optional - If this is set to True, the axes which are reduced are left - in the result as dimensions with size one. With this option, - the result will broadcast correctly against the input array. - - If the default value is passed, then `keepdims` will not be - passed through to the `amax` method of sub-classes of - `ndarray`, however any non-default value will be. If the - sub-class' method does not implement `keepdims` any - exceptions will be raised. - - initial : scalar, optional - The minimum value of an output element. Must be present to allow - computation on empty slice. See `~cunumeric.ufunc.reduce` for details. - - where : array_like[bool], optional - Elements to compare for the maximum. See `~cunumeric.ufunc.reduce` - for details. - - Returns - ------- - amax : ndarray or scalar - Maximum of `a`. If `axis` is None, the result is a scalar value. - If `axis` is given, the result is an array of dimension - ``a.ndim - 1``. - - See Also - -------- - numpy.amax - - Availability - -------- - Multiple GPUs, Multiple CPUs - """ - return maximum.reduce( - a, - axis=axis, - dtype=dtype, - out=out, - keepdims=keepdims, - initial=initial, - where=where, - ) - - -max = amax - - -@add_boilerplate("a") -def amin( - a: ndarray, - axis: Optional[Union[int, tuple[int, ...]]] = None, - dtype: Optional[np.dtype[Any]] = None, - out: Optional[ndarray] = None, - keepdims: bool = False, - initial: Optional[Union[int, float]] = None, - where: Optional[ndarray] = None, -) -> ndarray: - """ - - Return the minimum of an array or minimum along an axis. - - Parameters - ---------- - a : array_like - Input data. - axis : None or int or tuple[int], optional - Axis or axes along which to operate. By default, flattened input is - used. - - If this is a tuple of ints, the minimum is selected over multiple axes, - instead of a single axis or all the axes as before. - out : ndarray, optional - Alternative output array in which to place the result. Must - be of the same shape and buffer length as the expected output. - See `ufuncs-output-type` for more details. - - keepdims : bool, optional - If this is set to True, the axes which are reduced are left - in the result as dimensions with size one. With this option, - the result will broadcast correctly against the input array. - - If the default value is passed, then `keepdims` will not be - passed through to the `amin` method of sub-classes of - `ndarray`, however any non-default value will be. If the - sub-class' method does not implement `keepdims` any - exceptions will be raised. - - initial : scalar, optional - The maximum value of an output element. Must be present to allow - computation on empty slice. See `~cunumeric.ufunc.reduce` for details. - - where : array_like[bool], optional - Elements to compare for the minimum. See `~cunumeric.ufunc.reduce` - for details. - - Returns - ------- - amin : ndarray or scalar - Minimum of `a`. If `axis` is None, the result is a scalar value. - If `axis` is given, the result is an array of dimension - ``a.ndim - 1``. - - See Also - -------- - numpy.amin - - Availability - -------- - Multiple GPUs, Multiple CPUs - """ - return minimum.reduce( - a, - axis=axis, - dtype=dtype, - out=out, - keepdims=keepdims, - initial=initial, - where=where, - ) - - -min = amin - -# Miscellaneous - - -@add_boilerplate("a", "v") -def convolve(a: ndarray, v: ndarray, mode: ConvolveMode = "full") -> ndarray: - """ - - Returns the discrete, linear convolution of two ndarrays. - - If `a` and `v` are both 1-D and `v` is longer than `a`, the two are - swapped before computation. For N-D cases, the arguments are never swapped. - - Parameters - ---------- - a : (N,) array_like - First input ndarray. - v : (M,) array_like - Second input ndarray. - mode : ``{'full', 'valid', 'same'}``, optional - 'same': - The output is the same size as `a`, centered with respect to - the 'full' output. (default) - - 'full': - The output is the full discrete linear convolution of the inputs. - - 'valid': - The output consists only of those elements that do not - rely on the zero-padding. In 'valid' mode, either `a` or `v` - must be at least as large as the other in every dimension. - - Returns - ------- - out : ndarray - Discrete, linear convolution of `a` and `v`. - - See Also - -------- - numpy.convolve - - Notes - ----- - The current implementation only supports the 'same' mode. - - Unlike `numpy.convolve`, `cunumeric.convolve` supports N-dimensional - inputs, but it follows NumPy's behavior for 1-D inputs. - - Availability - -------- - Multiple GPUs, Multiple CPUs - """ - if mode != "same": - raise NotImplementedError("Need to implement other convolution modes") - - if a.ndim != v.ndim: - raise RuntimeError("Arrays should have the same dimensions") - elif a.ndim > 3: - raise NotImplementedError(f"{a.ndim}-D arrays are not yet supported") - - if a.ndim == 1 and a.size < v.size: - v, a = a, v - - if a.dtype != v.dtype: - v = v.astype(a.dtype) - out = ndarray( - shape=a.shape, - dtype=a.dtype, - inputs=(a, v), - ) - a._thunk.convolve(v._thunk, out._thunk, mode) - return out - - -@add_boilerplate("a") -def clip( - a: ndarray, - a_min: Union[int, float, npt.ArrayLike, None], - a_max: Union[int, float, npt.ArrayLike, None], - out: Union[npt.NDArray[Any], ndarray, None] = None, -) -> ndarray: - """ - - Clip (limit) the values in an array. - - Given an interval, values outside the interval are clipped to - the interval edges. For example, if an interval of ``[0, 1]`` - is specified, values smaller than 0 become 0, and values larger - than 1 become 1. - - Parameters - ---------- - a : array_like - Array containing elements to clip. - a_min : scalar or array_like or None - Minimum value. If None, clipping is not performed on lower - interval edge. Not more than one of `a_min` and `a_max` may be - None. - a_max : scalar or array_like or None - Maximum value. If None, clipping is not performed on upper - interval edge. Not more than one of `a_min` and `a_max` may be - None. If `a_min` or `a_max` are array_like, then the three - arrays will be broadcasted to match their shapes. - out : ndarray, optional - The results will be placed in this array. It may be the input - array for in-place clipping. `out` must be of the right shape - to hold the output. Its type is preserved. - **kwargs - For other keyword-only arguments, see the - :ref:`ufunc docs `. - - Returns - ------- - clipped_array : ndarray - An array with the elements of `a`, but where values - < `a_min` are replaced with `a_min`, and those > `a_max` - with `a_max`. - - See Also - -------- - numpy.clip - - Availability - -------- - Multiple GPUs, Multiple CPUs - """ - return a.clip(a_min, a_max, out=out) - - -################################## -# Set routines -################################## - - -@add_boilerplate("ar") -def unique( - ar: ndarray, - return_index: bool = False, - return_inverse: bool = False, - return_counts: bool = False, - axis: Optional[int] = None, -) -> ndarray: - """ - - Find the unique elements of an array. - Returns the sorted unique elements of an array. There are three optional - outputs in addition to the unique elements: - * the indices of the input array that give the unique values - * the indices of the unique array that reconstruct the input array - * the number of times each unique value comes up in the input array - - Parameters - ---------- - ar : array_like - Input array. Unless `axis` is specified, this will be flattened if it - is not already 1-D. - return_index : bool, optional - If True, also return the indices of `ar` (along the specified axis, - if provided, or in the flattened array) that result in the unique - array. - Currently not supported. - return_inverse : bool, optional - If True, also return the indices of the unique array (for the specified - axis, if provided) that can be used to reconstruct `ar`. - Currently not supported. - return_counts : bool, optional - If True, also return the number of times each unique item appears - in `ar`. - Currently not supported. - axis : int or None, optional - The axis to operate on. If None, `ar` will be flattened. If an integer, - the subarrays indexed by the given axis will be flattened and treated - as the elements of a 1-D array with the dimension of the given axis, - see the notes for more details. Object arrays or structured arrays - that contain objects are not supported if the `axis` kwarg is used. The - default is None. - Currently not supported. - - Returns - ------- - unique : ndarray - The sorted unique values. - unique_indices : ndarray, optional - The indices of the first occurrences of the unique values in the - original array. Only provided if `return_index` is True. - unique_inverse : ndarray, optional - The indices to reconstruct the original array from the - unique array. Only provided if `return_inverse` is True. - unique_counts : ndarray, optional - The number of times each of the unique values comes up in the - original array. Only provided if `return_counts` is True. - - See Also - -------- - numpy.unique - - Availability - -------- - Multiple GPUs, Multiple CPUs - - Notes - -------- - Keyword arguments for optional outputs are not yet supported. - `axis` is also not handled currently. - - """ - if _builtin_any((return_index, return_inverse, return_counts, axis)): - raise NotImplementedError( - "Keyword arguments for `unique` are not yet supported" - ) - - return ar.unique() - - -################################## -# Sorting, searching, and counting -################################## - -# Sorting - - -@add_boilerplate("a") -def argsort( - a: ndarray, - axis: Union[int, None] = -1, - kind: SortType = "quicksort", - order: Optional[Union[str, list[str]]] = None, -) -> ndarray: - """ - - Returns the indices that would sort an array. - - Parameters - ---------- - a : array_like - Input array. - axis : int or None, optional - Axis to sort. By default, the index -1 (the last axis) is used. If - None, the flattened array is used. - kind : ``{'quicksort', 'mergesort', 'heapsort', 'stable'}``, optional - Default is 'quicksort'. The underlying sort algorithm might vary. - The code basically supports 'stable' or *not* 'stable'. - order : str or list[str], optional - Currently not supported - - Returns - ------- - index_array : ndarray[int] - Array of indices that sort a along the specified axis. It has the - same shape as `a.shape` or is flattened in case of `axis` is None. - - See Also - -------- - numpy.argsort - - Availability - -------- - Multiple GPUs, Multiple CPUs - """ - - result = ndarray(a.shape, np.int64) - result._thunk.sort( - rhs=a._thunk, argsort=True, axis=axis, kind=kind, order=order - ) - return result - - -def msort(a: ndarray) -> ndarray: - """ - - Returns a sorted copy of an array sorted along the first axis. - - Parameters - ---------- - a : array_like - Input array. - - Returns - ------- - out : ndarray - Sorted array with same dtype and shape as `a`. - - See Also - -------- - numpy.msort - - Availability - -------- - Multiple GPUs, Multiple CPUs - """ - return sort(a, axis=0) - - -@add_boilerplate("a") -def searchsorted( - a: ndarray, - v: Union[int, float, ndarray], - side: SortSide = "left", - sorter: Optional[ndarray] = None, -) -> Union[int, ndarray]: - """ - - Find the indices into a sorted array a such that, if the corresponding - elements in v were inserted before the indices, the order of a would be - preserved. - - Parameters - ---------- - a : 1-D array_like - Input array. If sorter is None, then it must be sorted in ascending - order, otherwise sorter must be an array of indices that sort it. - v : scalar or array_like - Values to insert into a. - side : ``{'left', 'right'}``, optional - If 'left', the index of the first suitable location found is given. - If 'right', return the last such index. If there is no suitable index, - return either 0 or N (where N is the length of a). - sorter : 1-D array_like, optional - Optional array of integer indices that sort array a into ascending - order. They are typically the result of argsort. - - Returns - ------- - indices : int or array_like[int] - Array of insertion points with the same shape as v, or an integer - if v is a scalar. - - Availability - -------- - Multiple GPUs, Multiple CPUs - """ - return a.searchsorted(v, side, sorter) - - -@add_boilerplate("a") -def sort( - a: ndarray, - axis: Union[int, None] = -1, - kind: SortType = "quicksort", - order: Optional[Union[str, list[str]]] = None, -) -> ndarray: - """ - - Returns a sorted copy of an array. - - Parameters - ---------- - a : array_like - Input array. - axis : int or None, optional - Axis to sort. By default, the index -1 (the last axis) is used. If - None, the flattened array is used. - kind : ``{'quicksort', 'mergesort', 'heapsort', 'stable'}``, optional - Default is 'quicksort'. The underlying sort algorithm might vary. - The code basically supports 'stable' or *not* 'stable'. - order : str or list[str], optional - Currently not supported - - Returns - ------- - out : ndarray - Sorted array with same dtype and shape as `a`. In case `axis` is - None the result is flattened. - - - See Also - -------- - numpy.sort - - Availability - -------- - Multiple GPUs, Multiple CPUs - """ - result = ndarray(a.shape, a.dtype) - result._thunk.sort(rhs=a._thunk, axis=axis, kind=kind, order=order) - return result - - -@add_boilerplate("a") -def sort_complex(a: ndarray) -> ndarray: - """ - - Returns a sorted copy of an array sorted along the last axis. Sorts the - real part first, the imaginary part second. - - Parameters - ---------- - a : array_like - Input array. - - Returns - ------- - out : ndarray, complex - Sorted array with same shape as `a`. - - See Also - -------- - numpy.sort_complex - - Availability - -------- - Multiple GPUs, Multiple CPUs - """ - - result = sort(a) - # force complex result upon return - if np.issubdtype(result.dtype, np.complexfloating): - return result - elif ( - np.issubdtype(result.dtype, np.integer) and result.dtype.itemsize <= 2 - ): - return result.astype(np.complex64, copy=True) - else: - return result.astype(np.complex128, copy=True) - - -# partition - - -@add_boilerplate("a") -def argpartition( - a: ndarray, - kth: Union[int, Sequence[int]], - axis: Union[int, None] = -1, - kind: SelectKind = "introselect", - order: Optional[Union[str, list[str]]] = None, -) -> ndarray: - """ - - Perform an indirect partition along the given axis. - - Parameters - ---------- - a : array_like - Input array. - kth : int or Sequence[int] - axis : int or None, optional - Axis to partition. By default, the index -1 (the last axis) is used. If - None, the flattened array is used. - kind : ``{'introselect'}``, optional - Currently not supported. - order : str or list[str], optional - Currently not supported. - - Returns - ------- - out : ndarray[int] - Array of indices that partitions a along the specified axis. It has the - same shape as `a.shape` or is flattened in case of `axis` is None. - - - Notes - ----- - The current implementation falls back to `cunumeric.argsort`. - - See Also - -------- - numpy.argpartition - - Availability - -------- - Multiple GPUs, Multiple CPUs - """ - result = ndarray(a.shape, np.int64) - result._thunk.partition( - rhs=a._thunk, - argpartition=True, - kth=kth, - axis=axis, - kind=kind, - order=order, - ) - return result - - -@add_boilerplate("a") -def partition( - a: ndarray, - kth: Union[int, Sequence[int]], - axis: Union[int, None] = -1, - kind: SelectKind = "introselect", - order: Optional[Union[str, list[str]]] = None, -) -> ndarray: - """ - - Returns a partitioned copy of an array. - - Parameters - ---------- - a : array_like - Input array. - kth : int or Sequence[int] - axis : int or None, optional - Axis to partition. By default, the index -1 (the last axis) is used. If - None, the flattened array is used. - kind : ``{'introselect'}``, optional - Currently not supported. - order : str or list[str], optional - Currently not supported. - - Returns - ------- - out : ndarray - Partitioned array with same dtype and shape as `a`. In case `axis` is - None the result is flattened. - - Notes - ----- - The current implementation falls back to `cunumeric.sort`. - - See Also - -------- - numpy.partition - - Availability - -------- - Multiple GPUs, Multiple CPUs - """ - result = ndarray(a.shape, a.dtype) - result._thunk.partition( - rhs=a._thunk, kth=kth, axis=axis, kind=kind, order=order - ) - return result - - -# Searching - - -@add_boilerplate("a") -def argmax( - a: ndarray, - axis: Optional[int] = None, - out: Optional[ndarray] = None, - *, - keepdims: bool = False, -) -> ndarray: - """ - - Returns the indices of the maximum values along an axis. - - Parameters - ---------- - a : array_like - Input array. - axis : int, optional - By default, the index is into the flattened array, otherwise - along the specified axis. - out : ndarray, optional - If provided, the result will be inserted into this array. It should - be of the appropriate shape and dtype. - keepdims : bool, optional - If this is set to True, the axes which are reduced are left - in the result as dimensions with size one. With this option, - the result will broadcast correctly against the array. - - Returns - ------- - index_array : ndarray[int] - Array of indices into the array. It has the same shape as `a.shape` - with the dimension along `axis` removed. - - See Also - -------- - numpy.argmax - - Notes - ----- - CuNumeric's parallel implementation may yield different results from NumPy - when the array contains NaN(s). - - Availability - -------- - Multiple GPUs, Multiple CPUs - """ - return a.argmax(axis=axis, out=out, keepdims=keepdims) - - -@add_boilerplate("a") -def argmin( - a: ndarray, - axis: Optional[int] = None, - out: Optional[ndarray] = None, - *, - keepdims: bool = False, -) -> ndarray: - """ - - Returns the indices of the minimum values along an axis. - - Parameters - ---------- - a : array_like - Input array. - axis : int, optional - By default, the index is into the flattened array, otherwise - along the specified axis. - out : ndarray, optional - If provided, the result will be inserted into this array. It should - be of the appropriate shape and dtype. - keepdims : bool, optional - If this is set to True, the axes which are reduced are left - in the result as dimensions with size one. With this option, - the result will broadcast correctly against the array. - - Returns - ------- - index_array : ndarray[int] - Array of indices into the array. It has the same shape as `a.shape` - with the dimension along `axis` removed. - - See Also - -------- - numpy.argmin - - Notes - ----- - CuNumeric's parallel implementation may yield different results from NumPy - when the array contains NaN(s). - - Availability - -------- - Multiple GPUs, Multiple CPUs - """ - return a.argmin(axis=axis, out=out, keepdims=keepdims) - - -# Counting - - -@add_boilerplate("a") -def count_nonzero( - a: ndarray, axis: Optional[Union[int, tuple[int, ...]]] = None -) -> Union[int, ndarray]: - """ - - Counts the number of non-zero values in the array ``a``. - - Parameters - ---------- - a : array_like - The array for which to count non-zeros. - axis : int or tuple, optional - Axis or tuple of axes along which to count non-zeros. - Default is None, meaning that non-zeros will be counted - along a flattened version of ``a``. - - Returns - ------- - count : int or ndarray[int] - Number of non-zero values in the array along a given axis. - Otherwise, the total number of non-zero values in the array - is returned. - - See Also - -------- - numpy.count_nonzero - - Availability - -------- - Multiple GPUs, Multiple CPUs - """ - return a._count_nonzero(axis) - - -############ -# Statistics -############ - -# Averages and variances - - -@add_boilerplate("a") -def mean( - a: ndarray, - axis: Optional[Union[int, tuple[int, ...]]] = None, - dtype: Optional[np.dtype[Any]] = None, - out: Optional[ndarray] = None, - keepdims: bool = False, - where: Optional[ndarray] = None, -) -> ndarray: - """ - - Compute the arithmetic mean along the specified axis. - - Returns the average of the array elements. The average is taken over - the flattened array by default, otherwise over the specified axis. - `float64` intermediate and return values are used for integer inputs. - - Parameters - ---------- - a : array_like - Array containing numbers whose mean is desired. If `a` is not an - array, a conversion is attempted. - axis : None or int or tuple[int], optional - Axis or axes along which the means are computed. The default is to - compute the mean of the flattened array. - - If this is a tuple of ints, a mean is performed over multiple axes, - instead of a single axis or all the axes as before. - dtype : data-type, optional - Type to use in computing the mean. For integer inputs, the default - is `float64`; for floating point inputs, it is the same as the - input dtype. - out : ndarray, optional - Alternate output array in which to place the result. The default - is ``None``; if provided, it must have the same shape as the - expected output, but the type will be cast if necessary. - See `ufuncs-output-type` for more details. - - keepdims : bool, optional - If this is set to True, the axes which are reduced are left - in the result as dimensions with size one. With this option, - the result will broadcast correctly against the input array. - - If the default value is passed, then `keepdims` will not be - passed through to the `mean` method of sub-classes of - `ndarray`, however any non-default value will be. If the - sub-class' method does not implement `keepdims` any - exceptions will be raised. - - where : array_like of bool, optional - Elements to include in the mean. - - Returns - ------- - m : ndarray - If `out is None`, returns a new array of the same dtype a above - containing the mean values, otherwise a reference to the output - array is returned. - - See Also - -------- - numpy.mean - - Availability - -------- - Multiple GPUs, Multiple CPUs - """ - return a.mean( - axis=axis, dtype=dtype, out=out, keepdims=keepdims, where=where - ) - - -@add_boilerplate("a") -def nanmean( - a: ndarray, - axis: Optional[Union[int, tuple[int, ...]]] = None, - dtype: Optional[np.dtype[Any]] = None, - out: Optional[ndarray] = None, - keepdims: bool = False, - where: Optional[ndarray] = None, -) -> ndarray: - """ - - Compute the arithmetic mean along the specified axis, ignoring NaNs. - - Returns the average of the array elements. The average is taken over - the flattened array by default, otherwise over the specified axis. - `float64` intermediate and return values are used for integer inputs. - - Parameters - ---------- - a : array_like - Array containing numbers whose mean is desired. If `a` is not an - array, a conversion is attempted. - axis : None or int or tuple[int], optional - Axis or axes along which the means are computed. The default is to - compute the mean of the flattened array. - - If this is a tuple of ints, a mean is performed over multiple axes, - instead of a single axis or all the axes as before. - dtype : data-type, optional - Type to use in computing the mean. For integer inputs, the default - is `float64`; for floating point inputs, it is the same as the - input dtype. - out : ndarray, optional - Alternate output array in which to place the result. The default - is ``None``; if provided, it must have the same shape as the - expected output, but the type will be cast if necessary. - See `ufuncs-output-type` for more details. - - keepdims : bool, optional - If this is set to True, the axes which are reduced are left - in the result as dimensions with size one. With this option, - the result will broadcast correctly against the input array. - - - where : array_like of bool, optional - Elements to include in the mean. - - Returns - ------- - m : ndarray - If `out is None`, returns a new array of the same dtype as a above - containing the mean values, otherwise a reference to the output - array is returned. - - See Also - -------- - numpy.nanmean - - Availability - -------- - Multiple GPUs, Multiple CPUs - """ - return a._nanmean( - axis=axis, dtype=dtype, out=out, keepdims=keepdims, where=where - ) - - -@add_boilerplate("a") -def var( - a: ndarray, - axis: Optional[Union[int, tuple[int, ...]]] = None, - dtype: Optional[np.dtype[Any]] = None, - out: Optional[ndarray] = None, - ddof: int = 0, - keepdims: bool = False, - *, - where: Union[ndarray, None] = None, -) -> ndarray: - """ - Compute the variance along the specified axis. - - Returns the variance of the array elements, a measure of the spread of - a distribution. The variance is computed for the flattened array - by default, otherwise over the specified axis. - - Parameters - ---------- - a : array_like - Array containing numbers whose variance is desired. If `a` is not an - array, a conversion is attempted. - axis : None or int or tuple[int], optional - Axis or axes along which the variance is computed. The default is to - compute the variance of the flattened array. - - If this is a tuple of ints, a variance is performed over multiple axes, - instead of a single axis or all the axes as before. - dtype : data-type, optional - Type to use in computing the variance. For arrays of integer type - the default is float64; for arrays of float types - it is the same as the array type. - out : ndarray, optional - Alternate output array in which to place the result. It must have the - same shape as the expected output, but the type is cast if necessary. - ddof : int, optional - “Delta Degrees of Freedom”: the divisor used in the calculation is - N - ddof, where N represents the number of elements. By default - ddof is zero. - keepdims : bool, optional - If this is set to True, the axes which are reduced are left - in the result as dimensions with size one. With this option, - the result will broadcast correctly against the input array. - where : array_like of bool, optional - A boolean array which is broadcasted to match the dimensions of array, - and selects elements to include in the reduction. - - Returns - ------- - m : ndarray, see dtype parameter above - If `out=None`, returns a new array of the same dtype as above - containing the variance values, otherwise a reference to the output - array is returned. - - See Also - -------- - numpy.var - - Availability - -------- - Multiple GPUs, Multiple CPUs - """ - return a.var( - axis=axis, - dtype=dtype, - out=out, - ddof=ddof, - keepdims=keepdims, - where=where, - ) - - -# Histograms - - -@add_boilerplate("x", "weights") -def bincount( - x: ndarray, weights: Optional[ndarray] = None, minlength: int = 0 -) -> ndarray: - """ - bincount(x, weights=None, minlength=0) - - Count number of occurrences of each value in array of non-negative ints. - - The number of bins (of size 1) is one larger than the largest value in - `x`. If `minlength` is specified, there will be at least this number - of bins in the output array (though it will be longer if necessary, - depending on the contents of `x`). - Each bin gives the number of occurrences of its index value in `x`. - If `weights` is specified the input array is weighted by it, i.e. if a - value ``n`` is found at position ``i``, ``out[n] += weight[i]`` instead - of ``out[n] += 1``. - - Parameters - ---------- - x : array_like - 1-D input array of non-negative ints. - weights : array_like, optional - Weights, array of the same shape as `x`. - minlength : int, optional - A minimum number of bins for the output array. - - Returns - ------- - out : ndarray[int] - The result of binning the input array. - The length of `out` is equal to ``cunumeric.amax(x)+1``. - - Raises - ------ - ValueError - If the input is not 1-dimensional, or contains elements with negative - values, or if `minlength` is negative. - TypeError - If the type of the input is float or complex. - - See Also - -------- - numpy.bincount - - Availability - -------- - Multiple GPUs, Multiple CPUs - """ - if x.ndim != 1: - raise ValueError("the input array must be 1-dimensional") - if weights is not None: - if weights.shape != x.shape: - raise ValueError("weights array must be same shape for bincount") - if weights.dtype.kind == "c": - raise ValueError("weights must be convertible to float64") - # Make sure the weights are float64 - weights = weights.astype(np.float64) - if not np.issubdtype(x.dtype, np.integer): - raise TypeError("input array for bincount must be integer type") - if minlength < 0: - raise ValueError("'minlength' must not be negative") - # Note that the following are non-blocking operations, - # though passing their results to `int` is blocking - max_val, min_val = amax(x), amin(x) - if int(min_val) < 0: - raise ValueError("the input array must have no negative elements") - minlength = _builtin_max(minlength, int(max_val) + 1) - if x.size == 1: - # Handle the special case of 0-D array - if weights is None: - out = zeros((minlength,), dtype=np.dtype(np.int64)) - # TODO: Remove this "type: ignore" once @add_boilerplate can - # propagate "ndarray -> ndarray | npt.ArrayLike" in wrapped sigs - out[x[0]] = 1 # type: ignore [assignment] - else: - out = zeros((minlength,), dtype=weights.dtype) - index = x[0] - out[index] = weights[0] - else: - # Normal case of bincount - if weights is None: - out = ndarray( - (minlength,), - dtype=np.dtype(np.int64), - inputs=(x, weights), - ) - out._thunk.bincount(x._thunk) - else: - out = ndarray( - (minlength,), - dtype=weights.dtype, - inputs=(x, weights), - ) - out._thunk.bincount(x._thunk, weights=weights._thunk) - return out - - -# Quantiles - - -# account for 0-based indexing -# there's no negative numbers -# arithmetic at this level, -# (pos, k) are always positive! -# -def floor_i(k: int | float) -> int: - j = k - 1 if k > 0 else 0 - return int(j) - - -# Generic rule: if `q` input value falls onto a node, then return that node - - -# Discontinuous methods: -# -# 'inverted_cdf' -# q = quantile input \in [0, 1] -# n = sizeof(array) -# -def inverted_cdf(q: float, n: int) -> tuple[float, int]: - pos = q * n - k = math.floor(pos) - - g = pos - k - gamma = 1.0 if g > 0 else 0.0 - - j = int(k) - 1 - if j < 0: - return (0.0, 0) - else: - return (gamma, j) - - -# 'averaged_inverted_cdf' -# -def averaged_inverted_cdf(q: float, n: int) -> tuple[float, int]: - pos = q * n - k = math.floor(pos) - - g = pos - k - gamma = 1.0 if g > 0 else 0.5 - - j = int(k) - 1 - if j < 0: - return (0.0, 0) - elif j >= n - 1: - return (1.0, n - 2) - else: - return (gamma, j) - - -# 'closest_observation' -# -def closest_observation(q: float, n: int) -> tuple[float, int]: - # p = q*n - 0.5 - # pos = 0 if p < 0 else p - - # weird departure from paper - # (bug?), but this fixes it: - # also, j even in original paper - # applied to 1-based indexing; we have 0-based! - # numpy impl. doesn't account that the original paper used - # 1-based indexing, 0-based j is still checked for evennes! - # (see proof in quantile_policies.py) - # - p0 = q * n - 0.5 - p = p0 - 1.0 - - pos = 0 if p < 0 else p0 - k = math.floor(pos) - - j = floor_i(k) - gamma = 1 if k < pos else (0 if j % 2 == 0 else 1) - - return (gamma, j) - - -# Continuous methods: -# -# Parzen method: -# 'interpolated_inverted_cdf' -# -def interpolated_inverted_cdf(q: float, n: int) -> tuple[float, int]: - pos = q * n - k = math.floor(pos) - # gamma = pos-k - # this fixes it: - # - gamma = 0.0 if k == 0 else pos - k - j = floor_i(k) - return (gamma, j) - - -# Hazen method: -# 'hazen' -# -def hazen(q: float, n: int) -> tuple[float, int]: - pos = q * n + 0.5 - k = math.floor(pos) - # gamma = pos-k - # - # this fixes it: - # (when pos > n: this actually selects the right point, - # which is the correct choice, because right = arr[n] - # gets invalidated) - # - gamma = 0.0 if (pos < 1 or pos > n) else pos - k - - j = floor_i(k) - return (gamma, j) - - -# Weibull method: -# 'weibull' -# -def weibull(q: float, n: int) -> tuple[float, int]: - pos = q * (n + 1) - - k = math.floor(pos) - # gamma = pos-k - # - # this fixes it: - # (when pos > n: this actually selects the right point, - # which is the correct choice, because right = arr[n] - # gets invalidated) - # - gamma = 0.0 if (pos < 1 or pos > n) else pos - k - - j = floor_i(k) - - if j >= n: - j = n - 1 - - return (gamma, j) - - -# Gumbel method: -# 'linear' -# -def linear(q: float, n: int) -> tuple[float, int]: - pos = q * (n - 1) + 1 - k = math.floor(pos) - # gamma = pos-k - # - # this fixes it: - # (when pos > n: this actually selects the right point, - # which is the correct choice, because right = arr[n] - # gets invalidated) - # - gamma = 0.0 if (pos < 1 or pos > n) else pos - k - - j = floor_i(k) - return (gamma, j) - - -# Johnson & Kotz method: -# 'median_unbiased' -# -def median_unbiased(q: float, n: int) -> tuple[float, int]: - fract = 1.0 / 3.0 - pos = q * (n + fract) + fract - k = math.floor(pos) - - # gamma = pos-k - # - # this fixes it: - # (when pos > n: this actually selects the right point, - # which is the correct choice, because right = arr[n] - # gets invalidated) - # - gamma = 0.0 if (pos < 1 or pos > n) else pos - k - - j = floor_i(k) - return (gamma, j) - - -# Blom method: -# 'normal_unbiased' -# -def normal_unbiased(q: float, n: int) -> tuple[float, int]: - fract1 = 0.25 - fract2 = 3.0 / 8.0 - pos = q * (n + fract1) + fract2 - k = math.floor(pos) - - # gamma = pos-k - # - # this fixes it: - # (when pos > n: this actually selects the right point, - # which is the correct choice, because right = arr[n] - # gets invalidated) - # - gamma = 0.0 if (pos < 1 or pos > n) else pos - k - - j = floor_i(k) - return (gamma, j) - - -# `lower` -# -def lower(q: float, n: int) -> tuple[float, int]: - gamma = 0.0 - pos = q * (n - 1) - k = math.floor(pos) - - j = int(k) - return (gamma, j) - - -# `higher` -# -def higher(q: float, n: int) -> tuple[float, int]: - pos = q * (n - 1) - k = math.floor(pos) - - # Generic rule: (k == pos) - gamma = 0.0 if (pos == 0 or k == pos) else 1.0 - - j = int(k) - return (gamma, j) - - -# `midpoint` -# -def midpoint(q: float, n: int) -> tuple[float, int]: - pos = q * (n - 1) - k = math.floor(pos) - - # Generic rule: (k == pos) - gamma = 0.0 if (pos == 0 or k == pos) else 0.5 - - j = int(k) - return (gamma, j) - - -# `nearest` -# -def nearest(q: float, n: int) -> tuple[float, int]: - pos = q * (n - 1) - - # k = floor(pos) - # gamma = 1.0 if pos - k >= 0.5 else 0.0 - - k = np.round(pos) - gamma = 0.0 - - j = int(k) - return (gamma, j) - - -# for the case when axis = tuple (non-singleton) -# reshuffling might have to be done (if tuple is non-consecutive) -# and the src array must be collapsed along that set of axes -# -# args: -# -# arr: [in] source nd-array on which quantiles are calculated; -# axes_set: [in] tuple or list of axes (indices less than arr dimension); -# -# return: pair: (minimal_index, reshuffled_and_collapsed source array) -def reshuffle_reshape( - arr: ndarray, axes_set: Iterable[int] -) -> tuple[int, ndarray]: - ndim = len(arr.shape) - - sorted_axes = tuple(sorted(axes_set)) - - min_dim_index = sorted_axes[0] - num_axes = len(sorted_axes) - reshuffled_axes = tuple(range(min_dim_index, min_dim_index + num_axes)) - - non_consecutive = sorted_axes != reshuffled_axes - if non_consecutive: - arr_shuffled = moveaxis(arr, sorted_axes, reshuffled_axes) - else: - arr_shuffled = arr - - # shape_reshuffled = arr_shuffled.shape # debug - collapsed_shape = np.prod([arr_shuffled.shape[i] for i in reshuffled_axes]) - - redimed = tuple(range(0, min_dim_index + 1)) + tuple( - range(min_dim_index + num_axes, ndim) - ) - reshaped = tuple( - [ - collapsed_shape if k == min_dim_index else arr_shuffled.shape[k] - for k in redimed - ] - ) - - arr_reshaped = arr_shuffled.reshape(reshaped) - return (min_dim_index, arr_reshaped) - - -# args: -# -# arr: [in] source nd-array on which quantiles are calculated; -# preccondition: assumed sorted! -# q_arr: [in] quantile input values nd-array; -# axis: [in] axis along which quantiles are calculated; -# method: [in] func(q, n) returning (gamma, j), -# where = array1D.size; -# keepdims: [in] boolean flag specifying whether collapsed axis -# should be kept as dim=1; -# to_dtype: [in] dtype to convert the result to; -# qs_all: [in/out] result pass through or created (returned) -# -def quantile_impl( - arr: ndarray, - q_arr: npt.NDArray[Any], - axis: Optional[int], - axes_set: Iterable[int], - original_shape: tuple[int, ...], - method: Callable[[float, int], tuple[float, int]], - keepdims: bool, - to_dtype: np.dtype[Any], - qs_all: Optional[ndarray], -) -> ndarray: - ndims = len(arr.shape) - - if axis is None: - n = arr.size - - if keepdims: - remaining_shape = (1,) * len(original_shape) - else: - remaining_shape = () # only `q_arr` dictates shape; - # quantile applied to `arr` seen as 1D; - else: - n = arr.shape[axis] - - # arr.shape -{axis}; if keepdims use 1 for arr.shape[axis]: - # (can be empty []) - # - if keepdims: - remaining_shape = tuple( - 1 if k in axes_set else original_shape[k] - for k in range(0, len(original_shape)) - ) - else: - remaining_shape = tuple( - arr.shape[k] for k in range(0, ndims) if k != axis - ) - - # compose qarr.shape with arr.shape: - # - # result.shape = (q_arr.shape, arr.shape -{axis}): - # - qresult_shape = (*q_arr.shape, *remaining_shape) - - # construct result NdArray, non-flattening approach: - # - if qs_all is None: - qs_all = zeros(qresult_shape, dtype=to_dtype) - else: - # implicit conversion from to_dtype to qs_all.dtype assumed - # - if qs_all.shape != qresult_shape: - raise ValueError("wrong shape on output array") - - for index, q in np.ndenumerate(q_arr): - (gamma, j) = method(q, n) - (left_pos, right_pos) = (j, j + 1) - - # (N-1) dimensional ndarray of left, right - # neighbor values: - # - # non-flattening approach: - # - # extract values at index=left_pos; - arr_1D_lvals = arr.take(left_pos, axis) - arr_vals_shape = arr_1D_lvals.shape - - if right_pos >= n: - # some quantile methods may result in j==(n-1), - # hence (j+1) could surpass array boundary; - # - arr_1D_rvals = zeros(arr_vals_shape, dtype=arr_1D_lvals.dtype) - else: - # extract values at index=right_pos; - arr_1D_rvals = arr.take(right_pos, axis) - - # vectorized for axis != None; - # (non-flattening approach) - # - if len(index) == 0: - left = (1.0 - gamma) * arr_1D_lvals.reshape(qs_all.shape) - right = gamma * arr_1D_rvals.reshape(qs_all.shape) - qs_all[...] = left + right - else: - left = (1.0 - gamma) * arr_1D_lvals.reshape(qs_all[index].shape) - right = gamma * arr_1D_rvals.reshape(qs_all[index].shape) - qs_all[index] = left + right - - return qs_all - - -@add_boilerplate("a") -def quantile( - a: ndarray, - q: Union[float, Iterable[float], ndarray], - axis: Union[None, int, tuple[int, ...]] = None, - out: Optional[ndarray] = None, - overwrite_input: bool = False, - method: str = "linear", - keepdims: bool = False, -) -> ndarray: - """ - Compute the q-th quantile of the data along the specified axis. - - Parameters - ---------- - a : array_like - Input array or object that can be converted to an array. - q : array_like of float - Quantile or sequence of quantiles to compute, which must be between - 0 and 1 inclusive. - axis : {int, tuple of int, None}, optional - Axis or axes along which the quantiles are computed. The default is - to compute the quantile(s) along a flattened version of the array. - out : ndarray, optional - Alternative output array in which to place the result. It must have - the same shape as the expected output. - overwrite_input : bool, optional - If True, then allow the input array `a` to be modified by - intermediate calculations, to save memory. In this case, the - contents of the input `a` after this function completes is - undefined. - method : str, optional - This parameter specifies the method to use for estimating the - quantile. The options sorted by their R type - as summarized in the H&F paper [1]_ are: - 1. 'inverted_cdf' - 2. 'averaged_inverted_cdf' - 3. 'closest_observation' - 4. 'interpolated_inverted_cdf' - 5. 'hazen' - 6. 'weibull' - 7. 'linear' (default) - 8. 'median_unbiased' - 9. 'normal_unbiased' - The first three methods are discontinuous. NumPy further defines the - following discontinuous variations of the default 'linear' (7.) option: - * 'lower' - * 'higher', - * 'midpoint' - * 'nearest' - keepdims : bool, optional - If this is set to True, the axes which are reduced are left in - the result as dimensions with size one. With this option, the - result will broadcast correctly against the original array `a`. - - Returns - ------- - quantile : scalar or ndarray - If `q` is a single quantile and `axis=None`, then the result - is a scalar. If multiple quantiles are given, first axis of - the result corresponds to the quantiles. The other axes are - the axes that remain after the reduction of `a`. If the input - contains integers or floats smaller than ``float64``, the output - data-type is ``float64``. Otherwise, the output data-type is the - same as that of the input. If `out` is specified, that array is - returned instead. - - Raises - ------ - TypeError - If the type of the input is complex. - - See Also - -------- - numpy.quantile - - Availability - -------- - Multiple GPUs, Multiple CPUs - - References - ---------- - .. [1] R. J. Hyndman and Y. Fan, - "Sample quantiles in statistical packages," - The American Statistician, 50(4), pp. 361-365, 1996 - """ - - dict_methods = { - "inverted_cdf": inverted_cdf, - "averaged_inverted_cdf": averaged_inverted_cdf, - "closest_observation": closest_observation, - "interpolated_inverted_cdf": interpolated_inverted_cdf, - "hazen": hazen, - "weibull": weibull, - "linear": linear, - "median_unbiased": median_unbiased, - "normal_unbiased": normal_unbiased, - "lower": lower, - "higher": higher, - "midpoint": midpoint, - "nearest": nearest, - } - - real_axis: Optional[int] - axes_set: Iterable[int] = [] - original_shape = a.shape - - if axis is not None and isinstance(axis, Iterable): - if len(axis) == 1: - real_axis = axis[0] - a_rr = a - else: - (real_axis, a_rr) = reshuffle_reshape(a, axis) - # What happens with multiple axes and overwrite_input = True ? - # It seems overwrite_input is reset to False; - overwrite_input = False - axes_set = axis - else: - real_axis = axis - a_rr = a - if real_axis is not None: - axes_set = [real_axis] - - # covers both array-like and scalar cases: - # - q_arr = np.asarray(q) - - # in the future k-sort (partition) - # might be faster, for now it uses sort - # arr = partition(arr, k = floor(nq), axis = real_axis) - # but that would require a k-sort call for each `q`! - # too expensive for many `q` values... - # if no axis given then elements are sorted as a 1D array - # - if overwrite_input: - a_rr.sort(axis=real_axis) - arr = a_rr - else: - arr = sort(a_rr, axis=real_axis) - - if arr.dtype.kind == "c": - raise TypeError("input array cannot be of complex type") - - # return type dependency on arr.dtype: - # - # it depends on interpolation method; - # For discontinuous methods returning either end of the interval within - # which the quantile falls, or the other; arr.dtype is returned; - # else, logic below: - # - # if is_float(arr_dtype) && (arr.dtype >= dtype('float64')) then - # arr.dtype - # else - # dtype('float64') - # - # see https://github.com/numpy/numpy/issues/22323 - # - if method in [ - "inverted_cdf", - "closest_observation", - "lower", - "higher", - "nearest", - ]: - to_dtype = arr.dtype - else: - to_dtype = np.dtype("float64") - - # in case dtype("float128") becomes supported: - # - # to_dtype = ( - # arr.dtype - # if (arr.dtype == np.dtype("float128")) - # else np.dtype("float64") - # ) - - res = quantile_impl( - arr, - q_arr, - real_axis, - axes_set, - original_shape, - dict_methods[method], - keepdims, - to_dtype, - out, - ) - - if out is not None: - # out = res.astype(out.dtype) -- conversion done inside impl - return out - else: - return res - - -@add_boilerplate("a") -def percentile( - a: ndarray, - q: Union[float, Iterable[float], ndarray], - axis: Union[None, int, tuple[int, ...]] = None, - out: Optional[ndarray] = None, - overwrite_input: bool = False, - method: str = "linear", - keepdims: bool = False, -) -> ndarray: - """ - Compute the q-th percentile of the data along the specified axis. - - Parameters - ---------- - a : array_like - Input array or object that can be converted to an array. - q : array_like of float - Percentile or sequence of percentiles to compute, which must be between - 0 and 100 inclusive. - axis : {int, tuple of int, None}, optional - Axis or axes along which the percentiles are computed. The default is - to compute the percentile(s) along a flattened version of the array. - out : ndarray, optional - Alternative output array in which to place the result. It must have - the same shape as the expected output. - overwrite_input : bool, optional - If True, then allow the input array `a` to be modified by - intermediate calculations, to save memory. In this case, the - contents of the input `a` after this function completes is - undefined. - method : str, optional - This parameter specifies the method to use for estimating the - percentile. The options sorted by their R type - as summarized in the H&F paper [1]_ are: - 1. 'inverted_cdf' - 2. 'averaged_inverted_cdf' - 3. 'closest_observation' - 4. 'interpolated_inverted_cdf' - 5. 'hazen' - 6. 'weibull' - 7. 'linear' (default) - 8. 'median_unbiased' - 9. 'normal_unbiased' - The first three methods are discontinuous. NumPy further defines the - following discontinuous variations of the default 'linear' (7.) option: - * 'lower' - * 'higher', - * 'midpoint' - * 'nearest' - keepdims : bool, optional - If this is set to True, the axes which are reduced are left in - the result as dimensions with size one. With this option, the - result will broadcast correctly against the original array `a`. - - Returns - ------- - percentile : scalar or ndarray - If `q` is a single percentile and `axis=None`, then the result - is a scalar. If multiple percentiles are given, first axis of - the result corresponds to the percentiles. The other axes are - the axes that remain after the reduction of `a`. If the input - contains integers or floats smaller than ``float64``, the output - data-type is ``float64``. Otherwise, the output data-type is the - same as that of the input. If `out` is specified, that array is - returned instead. - - Raises - ------ - TypeError - If the type of the input is complex. - - See Also - -------- - numpy.percentile - - Availability - -------- - Multiple GPUs, Multiple CPUs - - References - ---------- - .. [1] R. J. Hyndman and Y. Fan, - "Sample quantiles in statistical packages," - The American Statistician, 50(4), pp. 361-365, 1996 - """ - - q_arr = np.asarray(q) - q01 = q_arr / 100.0 - - return quantile( - a, - q01, - axis, - out=out, - overwrite_input=overwrite_input, - method=method, - keepdims=keepdims, - ) - - -@add_boilerplate("x", "weights") -def histogram( - x: ndarray, - bins: Union[ndarray, npt.ArrayLike, int] = 10, - range: Optional[Union[tuple[int, int], tuple[float, float]]] = None, - weights: Optional[ndarray] = None, - density: bool = False, -) -> tuple[ndarray, ndarray]: - """ - Compute the histogram of a dataset. - - Parameters - ---------- - a : array_like - Input data. The histogram is computed over the flattened array. - bins : int or sequence of scalars, optional - If `bins` is an int, it defines the number of equal-width bins in the - given range (10, by default). If `bins` is a sequence, it defines a - monotonically increasing array of bin edges, including the rightmost - edge, allowing for non-uniform bin widths. - range : (float, float), optional - The lower and upper range of the bins. If not provided, range is simply - ``(a.min(), a.max())``. Values outside the range are ignored. The first - element of the range must be smaller than the second. This argument is - ignored when bin edges are provided explicitly. - weights : array_like, optional - An array of weights, of the same shape as `a`. Each value in `a` only - contributes its associated weight towards the bin count (instead of 1). - If `density` is True, the weights are normalized, so that the integral - of the density over the range remains 1. - density : bool, optional - If ``False``, the result will contain the number of samples in each - bin. If ``True``, the result is the value of the probability *density* - function at the bin, normalized such that the *integral* over the range - is 1. Note that the sum of the histogram values will not be equal to 1 - unless bins of unity width are chosen; it is not a probability *mass* - function. - - Returns - ------- - hist : array - The values of the histogram. See `density` and `weights` for a - description of the possible semantics. - bin_edges : array - Return the bin edges ``(length(hist)+1)``. - - See Also - -------- - numpy.histogram - - Availability - -------- - Multiple GPUs, Multiple CPUs - """ - result_type: np.dtype[Any] = np.dtype(np.int64) - - if np.ndim(bins) > 1: - raise ValueError("`bins` must be 1d, when an array") - - # check isscalar(bins): - # - if np.ndim(bins) == 0: - if not isinstance(bins, int): - raise TypeError("`bins` must be array or integer type") - - num_intervals = bins - - if range is not None: - assert isinstance(range, tuple) and len(range) == 2 - if range[0] >= range[1]: - raise ValueError( - "`range` must be a pair of increasing values." - ) - - lower_b = range[0] - higher_b = range[1] - elif x.size == 0: - lower_b = 0.0 - higher_b = 1.0 - else: - lower_b = float(min(x)) - higher_b = float(max(x)) - - step = (higher_b - lower_b) / num_intervals - - bins_array = asarray( - [lower_b + k * step for k in _builtin_range(0, num_intervals)] - + [higher_b], - dtype=np.dtype(np.float64), - ) - - bins_orig_type = bins_array.dtype - else: - bins_as_arr = asarray(bins) - bins_orig_type = bins_as_arr.dtype - - bins_array = bins_as_arr.astype(np.dtype(np.float64)) - num_intervals = bins_array.shape[0] - 1 - - if not all((bins_array[1:] - bins_array[:-1]) >= 0): - raise ValueError( - "`bins` must increase monotonically, when an array" - ) - - if x.ndim != 1: - x = x.flatten() - - if weights is not None: - if weights.shape != x.shape: - raise ValueError( - "`weights` array must be same shape for histogram" - ) - - result_type = weights.dtype - weights_array = weights.astype(np.dtype(np.float64)) - else: - # case weights == None cannot be handled inside _thunk.histogram, - # bc/ of hist ndarray inputs(), below; - # needs to be handled here: - # - weights_array = ones(x.shape, dtype=np.dtype(np.float64)) - - if x.size == 0: - return ( - zeros((num_intervals,), dtype=result_type), - bins_array.astype(bins_orig_type), - ) - - hist = ndarray( - (num_intervals,), - dtype=weights_array.dtype, - inputs=(x, bins_array, weights_array), - ) - hist._thunk.histogram( - x._thunk, bins_array._thunk, weights=weights_array._thunk - ) - - # handle (density = True): - # - if density: - result_type = np.dtype(np.float64) - hist /= sum(hist) - hist /= bins_array[1:] - bins_array[:-1] - - return hist.astype(result_type), bins_array.astype(bins_orig_type) diff --git a/cunumeric/window.py b/cunumeric/window.py index 9f25f7e033..9cee6022c7 100644 --- a/cunumeric/window.py +++ b/cunumeric/window.py @@ -18,9 +18,9 @@ import numpy as np +from ._module import empty, ones from .array import ndarray from .config import WindowOpCode -from .module import empty, ones def _create_window(M: int, op_code: WindowOpCode, *args: Any) -> ndarray: From 9d3d5e13df7fcdc841f2ff0ed820805a0958aeff Mon Sep 17 00:00:00 2001 From: Marcin Zalewski Date: Wed, 28 Feb 2024 19:44:07 -0800 Subject: [PATCH 159/462] Update Legion (24.01 -> 24.03) (#125) Update legate.core.internal to a commit with the latest legion. --- cmake/versions.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmake/versions.json b/cmake/versions.json index af4e833a7d..c829cc4f02 100644 --- a/cmake/versions.json +++ b/cmake/versions.json @@ -7,7 +7,7 @@ "git_url" : "https://github.com/nv-legate/legate.core.internal.git", "git_shallow": false, "always_download": false, - "git_tag" : "68db26d3593253317ff6e4fb310fb42804fd2dac" + "git_tag" : "5f3b74fbfa1f3a367badad94e59a6c8a573ba034" } } } From c78cd39df16a681e87d1043dbb5d4c1738872ca1 Mon Sep 17 00:00:00 2001 From: Bryan Van de Ven Date: Thu, 29 Feb 2024 09:01:45 -0800 Subject: [PATCH 160/462] Lightly refactor array module (#127) * split and make array module private * split off flagsobj * split of helper routines * import from correct location * remove no-longer-needed typing extension imports * remove ndarray.find_common_type * add missing docs section --- cunumeric/__init__.py | 9 +- cunumeric/_array/__init__.py | 15 + cunumeric/{ => _array}/array.py | 831 +++--------------------- cunumeric/_array/flags.py | 87 +++ cunumeric/_array/thunk.py | 367 +++++++++++ cunumeric/_array/util.py | 214 ++++++ cunumeric/_module/array_basic.py | 4 +- cunumeric/_module/array_dimension.py | 3 +- cunumeric/_module/array_joining.py | 3 +- cunumeric/_module/array_rearrange.py | 4 +- cunumeric/_module/array_shape.py | 4 +- cunumeric/_module/array_splitting.py | 3 +- cunumeric/_module/array_tiling.py | 3 +- cunumeric/_module/array_transpose.py | 4 +- cunumeric/_module/creation_data.py | 3 +- cunumeric/_module/creation_matrices.py | 3 +- cunumeric/_module/creation_ranges.py | 3 +- cunumeric/_module/creation_shape.py | 3 +- cunumeric/_module/indexing.py | 4 +- cunumeric/_module/linalg_mvp.py | 9 +- cunumeric/_module/logic_comparison.py | 14 +- cunumeric/_module/logic_truth.py | 4 +- cunumeric/_module/math_complex.py | 4 +- cunumeric/_module/math_extrema.py | 4 +- cunumeric/_module/math_misc.py | 3 +- cunumeric/_module/math_sum_prod_diff.py | 26 +- cunumeric/_module/sets_making.py | 4 +- cunumeric/_module/ssc_counting.py | 4 +- cunumeric/_module/ssc_searching.py | 6 +- cunumeric/_module/ssc_sorting.py | 3 +- cunumeric/_module/stats_avgs_vars.py | 4 +- cunumeric/_module/stats_histograms.py | 3 +- cunumeric/_module/stats_order.py | 4 +- cunumeric/_ufunc/ufunc.py | 13 +- cunumeric/bits.py | 4 +- cunumeric/coverage.py | 2 +- cunumeric/deferred.py | 2 +- cunumeric/fft/__init__.py | 2 +- cunumeric/fft/fft.py | 4 +- cunumeric/linalg/__init__.py | 2 +- cunumeric/linalg/linalg.py | 2 +- cunumeric/logic.py | 3 +- cunumeric/ma/__init__.py | 2 +- cunumeric/ma/_masked_array.py | 2 +- cunumeric/random/__init__.py | 2 +- cunumeric/random/_bitgenerator.py | 2 +- cunumeric/random/_generator.py | 2 +- cunumeric/random/_legacy.py | 2 +- cunumeric/random/_random.py | 2 +- cunumeric/runtime.py | 5 +- cunumeric/types.py | 4 +- cunumeric/window.py | 2 +- docs/cunumeric/source/api/_ndarray.rst | 1 - docs/cunumeric/source/api/datatype.rst | 12 + docs/cunumeric/source/api/ndarray.rst | 1 - docs/cunumeric/source/api/routines.rst | 1 + examples/benchmark.py | 3 +- 57 files changed, 903 insertions(+), 833 deletions(-) create mode 100644 cunumeric/_array/__init__.py rename cunumeric/{ => _array}/array.py (82%) create mode 100644 cunumeric/_array/flags.py create mode 100644 cunumeric/_array/thunk.py create mode 100644 cunumeric/_array/util.py create mode 100644 docs/cunumeric/source/api/datatype.rst diff --git a/cunumeric/__init__.py b/cunumeric/__init__.py index 50d9d74431..4301307681 100644 --- a/cunumeric/__init__.py +++ b/cunumeric/__init__.py @@ -24,18 +24,17 @@ """ from __future__ import annotations -import os - import numpy as _np from . import linalg, random, fft, ma -from .array import maybe_convert_to_np_ndarray, ndarray -from .bits import packbits, unpackbits +from ._array.array import ndarray +from ._array.util import maybe_convert_to_np_ndarray from ._module import * from ._ufunc import * +from .bits import packbits, unpackbits +from .coverage import clone_module from .logic import * from .window import bartlett, blackman, hamming, hanning, kaiser -from .coverage import clone_module clone_module(_np, globals(), maybe_convert_to_np_ndarray) diff --git a/cunumeric/_array/__init__.py b/cunumeric/_array/__init__.py new file mode 100644 index 0000000000..31d8d448c4 --- /dev/null +++ b/cunumeric/_array/__init__.py @@ -0,0 +1,15 @@ +# Copyright 2024 NVIDIA Corporation +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +from __future__ import annotations diff --git a/cunumeric/array.py b/cunumeric/_array/array.py similarity index 82% rename from cunumeric/array.py rename to cunumeric/_array/array.py index 2c77fd441a..80efa56c61 100644 --- a/cunumeric/array.py +++ b/cunumeric/_array/array.py @@ -16,18 +16,8 @@ import operator import warnings -from functools import reduce, wraps -from inspect import signature -from typing import ( - TYPE_CHECKING, - Any, - Callable, - Optional, - Sequence, - TypeVar, - Union, - cast, -) +from functools import reduce +from typing import TYPE_CHECKING, Any, Optional, Sequence, Union, cast import numpy as np from legate.core import Field, LogicalArray, Scalar @@ -38,11 +28,9 @@ from numpy.core.numeric import ( # type: ignore [attr-defined] normalize_axis_tuple, ) -from typing_extensions import ParamSpec -from .config import ( - BinaryOpCode, - ConvertCode, +from .. import _ufunc +from ..config import ( FFTDirection, FFTNormalization, FFTType, @@ -50,27 +38,36 @@ UnaryOpCode, UnaryRedCode, ) -from .coverage import FALLBACK_WARNING, clone_class, is_implemented -from .runtime import runtime -from .types import NdShape -from .utils import ( +from ..coverage import FALLBACK_WARNING, clone_class, is_implemented +from ..runtime import runtime +from ..types import NdShape +from ..utils import ( calculate_volume, deep_apply, dot_modes, to_core_dtype, tuple_pop, ) +from .flags import flagsobj +from .thunk import perform_scan, perform_unary_op, perform_unary_reduction +from .util import ( + add_boilerplate, + broadcast_where, + check_writeable, + convert_to_cunumeric_ndarray, + maybe_convert_to_np_ndarray, + sanitize_shape, +) if TYPE_CHECKING: from pathlib import Path import numpy.typing as npt - from .thunk import NumPyThunk - from .types import ( + from ..thunk import NumPyThunk + from ..types import ( BoundsMode, CastingKind, - NdShapeLike, OrderType, SelectKind, SortSide, @@ -79,187 +76,6 @@ from math import prod -R = TypeVar("R") -P = ParamSpec("P") - - -def add_boilerplate( - *array_params: str, -) -> Callable[[Callable[P, R]], Callable[P, R]]: - """ - Adds required boilerplate to the wrapped cunumeric.ndarray or module-level - function. - - Every time the wrapped function is called, this wrapper will: - * Convert all specified array-like parameters, plus the special "out" - parameter (if present), to cuNumeric ndarrays. - * Convert the special "where" parameter (if present) to a valid predicate. - """ - keys = OrderedSet(array_params) - assert len(keys) == len(array_params) - - def decorator(func: Callable[P, R]) -> Callable[P, R]: - assert not hasattr( - func, "__wrapped__" - ), "this decorator must be the innermost" - - # For each parameter specified by name, also consider the case where - # it's passed as a positional parameter. - indices: OrderedSet[int] = OrderedSet() - where_idx: Optional[int] = None - out_idx: Optional[int] = None - params = signature(func).parameters - extra = keys - OrderedSet(params) - assert len(extra) == 0, f"unknown parameter(s): {extra}" - for idx, param in enumerate(params): - if param == "where": - where_idx = idx - elif param == "out": - out_idx = idx - elif param in keys: - indices.add(idx) - - @wraps(func) - def wrapper(*args: Any, **kwargs: Any) -> R: - assert (where_idx is None or len(args) <= where_idx) and ( - out_idx is None or len(args) <= out_idx - ), "'where' and 'out' should be passed as keyword arguments" - - # Convert relevant arguments to cuNumeric ndarrays - args = tuple( - convert_to_cunumeric_ndarray(arg) - if idx in indices and arg is not None - else arg - for (idx, arg) in enumerate(args) - ) - for k, v in kwargs.items(): - if v is None: - continue - elif k == "out": - kwargs[k] = convert_to_cunumeric_ndarray(v, share=True) - if not kwargs[k].flags.writeable: - raise ValueError("out is not writeable") - elif (k in keys) or (k == "where"): - kwargs[k] = convert_to_cunumeric_ndarray(v) - - return func(*args, **kwargs) - - return wrapper - - return decorator - - -def convert_to_cunumeric_ndarray(obj: Any, share: bool = False) -> ndarray: - # If this is an instance of one of our ndarrays then we're done - if isinstance(obj, ndarray): - return obj - # Ask the runtime to make a numpy thunk for this object - thunk = runtime.get_numpy_thunk(obj, share=share) - writeable = ( - obj.flags.writeable if isinstance(obj, np.ndarray) and share else True - ) - return ndarray(shape=None, thunk=thunk, writeable=writeable) - - -def maybe_convert_to_np_ndarray(obj: Any) -> Any: - """ - Converts cuNumeric arrays into NumPy arrays, otherwise has no effect. - """ - from .ma import MaskedArray - - if isinstance(obj, (ndarray, MaskedArray)): - return obj.__array__() - return obj - - -def check_writeable(arr: Union[ndarray, tuple[ndarray, ...], None]) -> None: - """ - Check if the current array is writeable - This check needs to be manually inserted - with consideration on the behavior of the corresponding method - """ - if arr is None: - return - check_list = (arr,) if not isinstance(arr, tuple) else arr - if any(not arr.flags.writeable for arr in check_list): - raise ValueError("array is not writeable") - - -def broadcast_where( - where: Union[ndarray, None], shape: NdShape -) -> Union[ndarray, None]: - if where is not None and where.shape != shape: - from ._module import broadcast_to - - where = broadcast_to(where, shape) - return where - - -class flagsobj: - """ - Information about the memory layout of the array. - - These flags don't reflect the properties of the cuNumeric array, but - rather the NumPy array that will be produced if the cuNumeric array is - materialized on a single node. - """ - - def __init__(self, array: ndarray) -> None: - # prevent infinite __setattr__ recursion - object.__setattr__(self, "_array", array) - - def __repr__(self) -> str: - return f"""\ - C_CONTIGUOUS : {self["C"]} - F_CONTIGUOUS : {self["F"]} - OWNDATA : {self["O"]} - WRITEABLE : {self["W"]} - ALIGNED : {self["A"]} - WRITEBACKIFCOPY : {self["X"]} -""" - - def __eq__(self, other: Any) -> bool: - flags = ("C", "F", "O", "W", "A", "X") - if not isinstance(other, (flagsobj, np.core.multiarray.flagsobj)): - return False - - return all(self[f] == other[f] for f in flags) # type: ignore [index] - - def __getattr__(self, name: str) -> Any: - if name == "writeable": - return self._array._writeable - flags = self._array.__array__().flags - return getattr(flags, name) - - def __setattr__(self, name: str, value: Any) -> None: - if name == "writeable": - self._check_writeable(value) - self._array._writeable = bool(value) - else: - flags = self._array.__array__().flags - setattr(flags, name, value) - - def __getitem__(self, key: Any) -> bool: - if key == "W": - return self._array._writeable - flags = self._array.__array__().flags - return flags[key] - - def __setitem__(self, key: str, value: Any) -> None: - if key == "W": - self._check_writeable(value) - self._array._writeable = bool(value) - else: - flags = self._array.__array__().flags - flags[key] = value - - def _check_writeable(self, value: Any) -> None: - if value and not self._array._writeable: - raise ValueError( - "non-writeable cunumeric arrays cannot be made writeable" - ) - - NDARRAY_INTERNAL = { "__array_finalize__", "__array_function__", @@ -290,7 +106,7 @@ def __init__( assert not isinstance(inputs, ndarray) if thunk is None: assert shape is not None - sanitized_shape = self._sanitize_shape(shape) + sanitized_shape = sanitize_shape(shape) if not isinstance(dtype, np.dtype): dtype = np.dtype(dtype) if buffer is not None: @@ -324,33 +140,6 @@ def __init__( self._writeable = writeable - @staticmethod - def _sanitize_shape( - shape: Union[NdShapeLike, Sequence[Any], npt.NDArray[Any], ndarray] - ) -> NdShape: - seq: tuple[Any, ...] - if isinstance(shape, (ndarray, np.ndarray)): - if shape.ndim == 0: - seq = (shape.__array__().item(),) - else: - seq = tuple(shape.__array__()) - elif np.isscalar(shape): - seq = (shape,) - else: - seq = tuple(cast(NdShape, shape)) - try: - # Unfortunately, we can't do this check using - # 'isinstance(value, int)', as the values in a NumPy ndarray - # don't satisfy the predicate (they have numpy value types, - # such as numpy.int64). - result = tuple(operator.index(value) for value in seq) - except TypeError: - raise TypeError( - "expected a sequence of integers or a single integer, " - f"got {shape!r}" - ) - return result - # Support for the Legate data interface @property def __legate_data_interface__(self) -> dict[str, Any]: @@ -435,7 +224,7 @@ def __array_function__( def __array_ufunc__( self, ufunc: Any, method: str, *inputs: Any, **kwargs: Any ) -> Any: - from . import _ufunc + from .. import _ufunc # Check whether we should handle the arguments array_args = inputs @@ -794,9 +583,7 @@ def __abs__(self) -> ndarray: """ # Handle the nice case of it being unsigned - from ._ufunc import absolute - - return absolute(self) + return _ufunc.absolute(self) def __add__(self, rhs: Any) -> ndarray: """a.__add__(value, /) @@ -808,9 +595,7 @@ def __add__(self, rhs: Any) -> ndarray: Multiple GPUs, Multiple CPUs """ - from ._ufunc import add - - return add(self, rhs) + return _ufunc.add(self, rhs) def __and__(self, rhs: Any) -> ndarray: """a.__and__(value, /) @@ -822,9 +607,7 @@ def __and__(self, rhs: Any) -> ndarray: Multiple GPUs, Multiple CPUs """ - from ._ufunc import bitwise_and - - return bitwise_and(self, rhs) + return _ufunc.bitwise_and(self, rhs) def __array__( self, dtype: Union[np.dtype[Any], None] = None @@ -878,7 +661,7 @@ def __contains__(self, item: Any) -> ndarray: if args[0].size != 1: raise ValueError("contains needs scalar item") core_dtype = to_core_dtype(self.dtype) - return self._perform_unary_reduction( + return perform_unary_reduction( UnaryRedCode.CONTAINS, self, axis=None, @@ -955,9 +738,7 @@ def __eq__(self, rhs: object) -> ndarray: # type: ignore [override] Multiple GPUs, Multiple CPUs """ - from ._ufunc import equal - - return equal(self, rhs) + return _ufunc.equal(self, rhs) def __float__(self) -> float: """a.__float__(/) @@ -977,9 +758,7 @@ def __floordiv__(self, rhs: Any) -> ndarray: Multiple GPUs, Multiple CPUs """ - from ._ufunc import floor_divide - - return floor_divide(self, rhs) + return _ufunc.floor_divide(self, rhs) def __format__(self, *args: Any, **kwargs: Any) -> str: return self.__array__().__format__(*args, **kwargs) @@ -994,9 +773,7 @@ def __ge__(self, rhs: Any) -> ndarray: Multiple GPUs, Multiple CPUs """ - from ._ufunc import greater_equal - - return greater_equal(self, rhs) + return _ufunc.greater_equal(self, rhs) # __getattribute__ @@ -1048,9 +825,7 @@ def __gt__(self, rhs: Any) -> ndarray: Multiple GPUs, Multiple CPUs """ - from ._ufunc import greater - - return greater(self, rhs) + return _ufunc.greater(self, rhs) def __hash__(self) -> int: raise TypeError("unhashable type: cunumeric.ndarray") @@ -1065,9 +840,7 @@ def __iadd__(self, rhs: Any) -> ndarray: Multiple GPUs, Multiple CPUs """ - from ._ufunc import add - - return add(self, rhs, out=self) + return _ufunc.add(self, rhs, out=self) def __iand__(self, rhs: Any) -> ndarray: """a.__iand__(value, /) @@ -1079,9 +852,7 @@ def __iand__(self, rhs: Any) -> ndarray: Multiple GPUs, Multiple CPUs """ - from ._ufunc import bitwise_and - - return bitwise_and(self, rhs, out=self) + return _ufunc.bitwise_and(self, rhs, out=self) def __idiv__(self, rhs: Any) -> ndarray: """a.__idiv__(value, /) @@ -1105,9 +876,7 @@ def __ifloordiv__(self, rhs: Any) -> ndarray: Multiple GPUs, Multiple CPUs """ - from ._ufunc import floor_divide - - return floor_divide(self, rhs, out=self) + return _ufunc.floor_divide(self, rhs, out=self) def __ilshift__(self, rhs: Any) -> ndarray: """a.__ilshift__(value, /) @@ -1119,9 +888,7 @@ def __ilshift__(self, rhs: Any) -> ndarray: Multiple GPUs, Multiple CPUs """ - from ._ufunc import left_shift - - return left_shift(self, rhs, out=self) + return _ufunc.left_shift(self, rhs, out=self) def __imod__(self, rhs: Any) -> ndarray: """a.__imod__(value, /) @@ -1133,9 +900,7 @@ def __imod__(self, rhs: Any) -> ndarray: Multiple GPUs, Multiple CPUs """ - from ._ufunc import remainder - - return remainder(self, rhs, out=self) + return _ufunc.remainder(self, rhs, out=self) def __imul__(self, rhs: Any) -> ndarray: """a.__imul__(value, /) @@ -1147,9 +912,7 @@ def __imul__(self, rhs: Any) -> ndarray: Multiple GPUs, Multiple CPUs """ - from ._ufunc import multiply - - return multiply(self, rhs, out=self) + return _ufunc.multiply(self, rhs, out=self) def __index__(self) -> int: return self.__array__().__index__() @@ -1174,13 +937,9 @@ def __invert__(self) -> ndarray: """ if self.dtype == np.bool_: # Boolean values are special, just do logical NOT - from ._ufunc import logical_not - - return logical_not(self) + return _ufunc.logical_not(self) else: - from ._ufunc import invert - - return invert(self) + return _ufunc.invert(self) def __ior__(self, rhs: Any) -> ndarray: """a.__ior__(/) @@ -1192,9 +951,7 @@ def __ior__(self, rhs: Any) -> ndarray: Multiple GPUs, Multiple CPUs """ - from ._ufunc import bitwise_or - - return bitwise_or(self, rhs, out=self) + return _ufunc.bitwise_or(self, rhs, out=self) def __ipow__(self, rhs: float) -> ndarray: """a.__ipow__(/) @@ -1206,9 +963,7 @@ def __ipow__(self, rhs: float) -> ndarray: Multiple GPUs, Multiple CPUs """ - from ._ufunc import power - - return power(self, rhs, out=self) + return _ufunc.power(self, rhs, out=self) def __irshift__(self, rhs: Any) -> ndarray: """a.__irshift__(/) @@ -1220,9 +975,7 @@ def __irshift__(self, rhs: Any) -> ndarray: Multiple GPUs, Multiple CPUs """ - from ._ufunc import right_shift - - return right_shift(self, rhs, out=self) + return _ufunc.right_shift(self, rhs, out=self) def __iter__(self) -> Any: """a.__iter__(/)""" @@ -1238,9 +991,7 @@ def __isub__(self, rhs: Any) -> ndarray: Multiple GPUs, Multiple CPUs """ - from ._ufunc import subtract - - return subtract(self, rhs, out=self) + return _ufunc.subtract(self, rhs, out=self) def __itruediv__(self, rhs: Any) -> ndarray: """a.__itruediv__(/) @@ -1252,9 +1003,7 @@ def __itruediv__(self, rhs: Any) -> ndarray: Multiple GPUs, Multiple CPUs """ - from ._ufunc import true_divide - - return true_divide(self, rhs, out=self) + return _ufunc.true_divide(self, rhs, out=self) def __ixor__(self, rhs: Any) -> ndarray: """a.__ixor__(/) @@ -1266,9 +1015,7 @@ def __ixor__(self, rhs: Any) -> ndarray: Multiple GPUs, Multiple CPUs """ - from ._ufunc import bitwise_xor - - return bitwise_xor(self, rhs, out=self) + return _ufunc.bitwise_xor(self, rhs, out=self) def __le__(self, rhs: Any) -> ndarray: """a.__le__(value, /) @@ -1280,9 +1027,7 @@ def __le__(self, rhs: Any) -> ndarray: Multiple GPUs, Multiple CPUs """ - from ._ufunc import less_equal - - return less_equal(self, rhs) + return _ufunc.less_equal(self, rhs) def __len__(self) -> int: """a.__len__(/) @@ -1302,9 +1047,7 @@ def __lshift__(self, rhs: Any) -> ndarray: Multiple GPUs, Multiple CPUs """ - from ._ufunc import left_shift - - return left_shift(self, rhs) + return _ufunc.left_shift(self, rhs) def __lt__(self, rhs: Any) -> ndarray: """a.__lt__(value, /) @@ -1316,9 +1059,7 @@ def __lt__(self, rhs: Any) -> ndarray: Multiple GPUs, Multiple CPUs """ - from ._ufunc import less - - return less(self, rhs) + return _ufunc.less(self, rhs) def __matmul__(self, value: Any) -> ndarray: """a.__matmul__(value, /) @@ -1342,9 +1083,7 @@ def __mod__(self, rhs: Any) -> ndarray: Multiple GPUs, Multiple CPUs """ - from ._ufunc import remainder - - return remainder(self, rhs) + return _ufunc.remainder(self, rhs) def __mul__(self, rhs: Any) -> ndarray: """a.__mul__(value, /) @@ -1356,9 +1095,7 @@ def __mul__(self, rhs: Any) -> ndarray: Multiple GPUs, Multiple CPUs """ - from ._ufunc import multiply - - return multiply(self, rhs) + return _ufunc.multiply(self, rhs) def __ne__(self, rhs: object) -> ndarray: # type: ignore [override] """a.__ne__(value, /) @@ -1370,9 +1107,7 @@ def __ne__(self, rhs: object) -> ndarray: # type: ignore [override] Multiple GPUs, Multiple CPUs """ - from ._ufunc import not_equal - - return not_equal(self, rhs) + return _ufunc.not_equal(self, rhs) def __neg__(self) -> ndarray: """a.__neg__(value, /) @@ -1384,9 +1119,7 @@ def __neg__(self) -> ndarray: Multiple GPUs, Multiple CPUs """ - from ._ufunc import negative - - return negative(self) + return _ufunc.negative(self) # __new__ @@ -1422,9 +1155,7 @@ def __or__(self, rhs: Any) -> ndarray: Multiple GPUs, Multiple CPUs """ - from ._ufunc import bitwise_or - - return bitwise_or(self, rhs) + return _ufunc.bitwise_or(self, rhs) def __pos__(self) -> ndarray: """a.__pos__(value, /) @@ -1437,9 +1168,7 @@ def __pos__(self) -> ndarray: """ # the positive opeartor is equivalent to copy - from ._ufunc import positive - - return positive(self) + return _ufunc.positive(self) def __pow__(self, rhs: float) -> ndarray: """a.__pow__(value, /) @@ -1451,9 +1180,7 @@ def __pow__(self, rhs: float) -> ndarray: Multiple GPUs, Multiple CPUs """ - from ._ufunc import power - - return power(self, rhs) + return _ufunc.power(self, rhs) def __radd__(self, lhs: Any) -> ndarray: """a.__radd__(value, /) @@ -1465,9 +1192,7 @@ def __radd__(self, lhs: Any) -> ndarray: Multiple GPUs, Multiple CPUs """ - from ._ufunc import add - - return add(lhs, self) + return _ufunc.add(lhs, self) def __rand__(self, lhs: Any) -> ndarray: """a.__rand__(value, /) @@ -1479,9 +1204,7 @@ def __rand__(self, lhs: Any) -> ndarray: Multiple GPUs, Multiple CPUs """ - from ._ufunc import bitwise_and - - return bitwise_and(lhs, self) + return _ufunc.bitwise_and(lhs, self) def __rdiv__(self, lhs: Any) -> ndarray: """a.__rdiv__(value, /) @@ -1493,9 +1216,7 @@ def __rdiv__(self, lhs: Any) -> ndarray: Multiple GPUs, Multiple CPUs """ - from ._ufunc import true_divide - - return true_divide(lhs, self) + return _ufunc.true_divide(lhs, self) def __rdivmod__(self, lhs: Any) -> ndarray: """a.__rdivmod__(value, /) @@ -1548,9 +1269,7 @@ def __rfloordiv__(self, lhs: Any) -> ndarray: Multiple GPUs, Multiple CPUs """ - from ._ufunc import floor_divide - - return floor_divide(lhs, self) + return _ufunc.floor_divide(lhs, self) def __rmod__(self, lhs: Any) -> ndarray: """a.__rmod__(value, /) @@ -1562,9 +1281,7 @@ def __rmod__(self, lhs: Any) -> ndarray: Multiple GPUs, Multiple CPUs """ - from ._ufunc import remainder - - return remainder(lhs, self) + return _ufunc.remainder(lhs, self) def __rmul__(self, lhs: Any) -> ndarray: """a.__rmul__(value, /) @@ -1576,9 +1293,7 @@ def __rmul__(self, lhs: Any) -> ndarray: Multiple GPUs, Multiple CPUs """ - from ._ufunc import multiply - - return multiply(lhs, self) + return _ufunc.multiply(lhs, self) def __ror__(self, lhs: Any) -> ndarray: """a.__ror__(value, /) @@ -1590,9 +1305,7 @@ def __ror__(self, lhs: Any) -> ndarray: Multiple GPUs, Multiple CPUs """ - from ._ufunc import bitwise_or - - return bitwise_or(lhs, self) + return _ufunc.bitwise_or(lhs, self) def __rpow__(self, lhs: Any) -> ndarray: """__rpow__(value, /) @@ -1604,9 +1317,7 @@ def __rpow__(self, lhs: Any) -> ndarray: Multiple GPUs, Multiple CPUs """ - from ._ufunc import power - - return power(lhs, self) + return _ufunc.power(lhs, self) def __rshift__(self, rhs: Any) -> ndarray: """a.__rshift__(value, /) @@ -1618,9 +1329,7 @@ def __rshift__(self, rhs: Any) -> ndarray: Multiple GPUs, Multiple CPUs """ - from ._ufunc import right_shift - - return right_shift(self, rhs) + return _ufunc.right_shift(self, rhs) def __rsub__(self, lhs: Any) -> ndarray: """a.__rsub__(value, /) @@ -1632,9 +1341,7 @@ def __rsub__(self, lhs: Any) -> ndarray: Multiple GPUs, Multiple CPUs """ - from ._ufunc import subtract - - return subtract(lhs, self) + return _ufunc.subtract(lhs, self) def __rtruediv__(self, lhs: Any) -> ndarray: """a.__rtruediv__(value, /) @@ -1646,9 +1353,7 @@ def __rtruediv__(self, lhs: Any) -> ndarray: Multiple GPUs, Multiple CPUs """ - from ._ufunc import true_divide - - return true_divide(lhs, self) + return _ufunc.true_divide(lhs, self) def __rxor__(self, lhs: Any) -> ndarray: """a.__rxor__(value, /) @@ -1660,9 +1365,7 @@ def __rxor__(self, lhs: Any) -> ndarray: Multiple GPUs, Multiple CPUs """ - from ._ufunc import bitwise_xor - - return bitwise_xor(lhs, self) + return _ufunc.bitwise_xor(lhs, self) # __setattr__ @add_boilerplate("value") @@ -1714,9 +1417,7 @@ def __sub__(self, rhs: Any) -> ndarray: Multiple GPUs, Multiple CPUs """ - from ._ufunc import subtract - - return subtract(self, rhs) + return _ufunc.subtract(self, rhs) def __str__(self) -> str: """a.__str__(/) @@ -1740,9 +1441,7 @@ def __truediv__(self, rhs: Any) -> ndarray: Multiple GPUs, Multiple CPUs """ - from ._ufunc import true_divide - - return true_divide(self, rhs) + return _ufunc.true_divide(self, rhs) def __xor__(self, rhs: Any) -> ndarray: """a.__xor__(value, /) @@ -1754,9 +1453,7 @@ def __xor__(self, rhs: Any) -> ndarray: Multiple GPUs, Multiple CPUs """ - from ._ufunc import bitwise_xor - - return bitwise_xor(self, rhs) + return _ufunc.bitwise_xor(self, rhs) @add_boilerplate() def all( @@ -1782,7 +1479,7 @@ def all( Multiple GPUs, Multiple CPUs """ - return self._perform_unary_reduction( + return perform_unary_reduction( UnaryRedCode.ALL, self, axis=axis, @@ -1817,7 +1514,7 @@ def any( Multiple GPUs, Multiple CPUs """ - return self._perform_unary_reduction( + return perform_unary_reduction( UnaryRedCode.ANY, self, axis=axis, @@ -1854,7 +1551,7 @@ def argmax( raise ValueError("output array must have int64 dtype") if axis is not None and not isinstance(axis, int): raise ValueError("axis must be an integer") - return self._perform_unary_reduction( + return perform_unary_reduction( UnaryRedCode.ARGMAX, self, axis=axis, @@ -1889,7 +1586,7 @@ def argmin( raise ValueError("output array must have int64 dtype") if axis is not None and not isinstance(axis, int): raise ValueError("axis must be an integer") - return self._perform_unary_reduction( + return perform_unary_reduction( UnaryRedCode.ARGMIN, self, axis=axis, @@ -2282,7 +1979,7 @@ def clip( ) core_dtype = to_core_dtype(self.dtype) extra_args = (Scalar(min, core_dtype), Scalar(max, core_dtype)) - return self._perform_unary_op( + return perform_unary_op( UnaryOpCode.CLIP, self, out=out, extra_args=extra_args ) @@ -2346,7 +2043,7 @@ def cumsum( dtype: Union[np.dtype[Any], None] = None, out: Union[ndarray, None] = None, ) -> ndarray: - return self._perform_scan( + return perform_scan( ScanCode.SUM, self, axis=axis, @@ -2362,7 +2059,7 @@ def cumprod( dtype: Union[np.dtype[Any], None] = None, out: Union[ndarray, None] = None, ) -> ndarray: - return self._perform_scan( + return perform_scan( ScanCode.PROD, self, axis=axis, @@ -2378,7 +2075,7 @@ def nancumsum( dtype: Union[np.dtype[Any], None] = None, out: Union[ndarray, None] = None, ) -> ndarray: - return self._perform_scan( + return perform_scan( ScanCode.SUM, self, axis=axis, @@ -2394,7 +2091,7 @@ def nancumprod( dtype: Union[np.dtype[Any], None] = None, out: Union[ndarray, None] = None, ) -> ndarray: - return self._perform_scan( + return perform_scan( ScanCode.PROD, self, axis=axis, @@ -2684,14 +2381,11 @@ def dot(self, rhs: ndarray, out: Union[ndarray, None] = None) -> ndarray: Multiple GPUs, Multiple CPUs """ - from ._module.linalg_mvp import ( # work around circular import - _contract, - ) + # work around circular import + from .._module.linalg_mvp import _contract if self.ndim == 0 or rhs.ndim == 0: - from ._ufunc import multiply - - return multiply(self, rhs, out=out) + return _ufunc.multiply(self, rhs, out=out) (self_modes, rhs_modes, out_modes) = dot_modes(self.ndim, rhs.ndim) return _contract( @@ -3064,7 +2758,7 @@ def max( Multiple GPUs, Multiple CPUs """ - return self._perform_unary_reduction( + return perform_unary_reduction( UnaryRedCode.MAX, self, axis=axis, @@ -3077,7 +2771,7 @@ def max( def _count_nonzero(self, axis: Any = None) -> Union[int, ndarray]: if self.size == 0: return 0 - return ndarray._perform_unary_reduction( + return perform_unary_reduction( UnaryRedCode.COUNT_NONZERO, self, res_dtype=np.dtype(np.uint64), @@ -3201,8 +2895,6 @@ def _nanmean( keepdims: bool = False, where: Union[ndarray, None] = None, ) -> ndarray: - from . import _ufunc - if np.issubdtype(dtype, np.integer) or np.issubdtype(dtype, np.bool_): return self.mean( axis=axis, dtype=dtype, out=out, keepdims=keepdims, where=where @@ -3272,7 +2964,7 @@ def var( if axis is None or calculate_volume(tuple_pop(self.shape, axis)) == 1: # this is a scalar reduction and we can optimize this as a single # pass through a scalar reduction - result = self._perform_unary_reduction( + result = perform_unary_reduction( UnaryRedCode.VARIANCE, self, axis=axis, @@ -3296,7 +2988,7 @@ def var( # delta*delta in second pass delta = self - mu - result = self._perform_unary_reduction( + result = perform_unary_reduction( UnaryRedCode.SUM_SQUARES, delta, axis=axis, @@ -3341,7 +3033,7 @@ def min( Multiple GPUs, Multiple CPUs """ - return self._perform_unary_reduction( + return perform_unary_reduction( UnaryRedCode.MIN, self, axis=axis, @@ -3449,7 +3141,7 @@ def prod( self_array = temp else: self_array = self - return self._perform_unary_reduction( + return perform_unary_reduction( UnaryRedCode.PROD, self_array, axis=axis, @@ -3819,7 +3511,7 @@ def sum( self_array = temp else: self_array = self - return self._perform_unary_reduction( + return perform_unary_reduction( UnaryRedCode.SUM, self_array, axis=axis, @@ -3847,7 +3539,7 @@ def _nansum( else: unary_red_code = UnaryRedCode.SUM - return self._perform_unary_reduction( + return perform_unary_reduction( unary_red_code, self, axis=axis, @@ -4165,44 +3857,6 @@ def unique(self) -> ndarray: thunk = self._thunk.unique() return ndarray(shape=thunk.shape, thunk=thunk) - @classmethod - def _get_where_thunk( - cls, where: Union[None, ndarray], out_shape: NdShape - ) -> Union[None, NumPyThunk]: - if where is None: - return where - if ( - not isinstance(where, ndarray) - or where.dtype != np.bool_ - or where.shape != out_shape - ): - raise RuntimeError("should have converted this earlier") - return where._thunk - - @staticmethod - def find_common_type(*args: ndarray) -> np.dtype[Any]: - """Determine common type following NumPy's coercion rules. - - Parameters - ---------- - *args : ndarray - A list of ndarrays - - Returns - ------- - datatype : data-type - The type that results from applying the NumPy type promotion rules - to the arguments. - """ - array_types = list() - scalars = list() - for array in args: - if array.ndim == 0: - scalars.append(array.dtype.type(0)) - else: - array_types.append(array.dtype) - return np.result_type(*array_types, *scalars) - def _maybe_convert(self, dtype: np.dtype[Any], hints: Any) -> ndarray: if self.dtype == dtype: return self @@ -4220,313 +3874,6 @@ def _warn_and_convert(self, dtype: np.dtype[Any]) -> ndarray: else: return self - # For performing normal/broadcast unary operations - @classmethod - def _perform_unary_op( - cls, - op: UnaryOpCode, - src: ndarray, - out: Union[Any, None] = None, - extra_args: Any = None, - dtype: Union[np.dtype[Any], None] = None, - out_dtype: Union[np.dtype[Any], None] = None, - ) -> ndarray: - if out is not None: - # If the shapes don't match see if we can broadcast - # This will raise an exception if they can't be broadcast together - if np.broadcast_shapes(src.shape, out.shape) != out.shape: - raise ValueError( - f"non-broadcastable output operand with shape {out.shape} " - f"doesn't match the broadcast shape {src.shape}" - ) - else: - # No output yet, so make one - out_shape = src.shape - - if dtype is not None: - out = ndarray( - shape=out_shape, - dtype=dtype, - inputs=(src,), - ) - elif out_dtype is not None: - out = ndarray( - shape=out_shape, - dtype=out_dtype, - inputs=(src,), - ) - else: - out = ndarray( - shape=out_shape, - dtype=src.dtype - if src.dtype.kind != "c" - else np.dtype(np.float32) - if src.dtype == np.dtype(np.complex64) - else np.dtype(np.float64), - inputs=(src,), - ) - - if out_dtype is None: - if out.dtype != src.dtype and not ( - op == UnaryOpCode.ABSOLUTE and src.dtype.kind == "c" - ): - temp = ndarray( - out.shape, - dtype=src.dtype, - inputs=(src,), - ) - temp._thunk.unary_op( - op, - src._thunk, - True, - extra_args, - ) - out._thunk.convert(temp._thunk) - else: - out._thunk.unary_op( - op, - src._thunk, - True, - extra_args, - ) - else: - if out.dtype != out_dtype: - temp = ndarray( - out.shape, - dtype=out_dtype, - inputs=(src,), - ) - temp._thunk.unary_op( - op, - src._thunk, - True, - extra_args, - ) - out._thunk.convert(temp._thunk) - else: - out._thunk.unary_op( - op, - src._thunk, - True, - extra_args, - ) - return out - - # For performing reduction unary operations - @classmethod - def _perform_unary_reduction( - cls, - op: UnaryRedCode, - src: ndarray, - axis: Any = None, - dtype: Union[np.dtype[Any], None] = None, - res_dtype: Union[npt.DTypeLike, None] = None, - out: Union[ndarray, None] = None, - keepdims: bool = False, - args: tuple[Scalar, ...] = (), - initial: Union[int, float, None] = None, - where: Union[ndarray, None] = None, - ) -> ndarray: - # When 'res_dtype' is not None, the input and output of the reduction - # have different types. Such reduction operators don't take a dtype of - # the accumulator - if res_dtype is not None: - assert dtype is None - dtype = src.dtype - else: - # If 'dtype' exists, that determines both the accumulation dtype - # and the output dtype - if dtype is not None: - res_dtype = dtype - elif out is not None: - dtype = out.dtype - res_dtype = out.dtype - else: - dtype = src.dtype - res_dtype = src.dtype - - # TODO: Need to require initial to be given when the array is empty - # or a where mask is given. - if ( - op - in ( - UnaryRedCode.ARGMAX, - UnaryRedCode.ARGMIN, - UnaryRedCode.MAX, - UnaryRedCode.MIN, - ) - and src.dtype.kind == "c" - ): - raise NotImplementedError( - "(arg)max/min not supported for complex-type arrays" - ) - - if axis is None: - axes = tuple(range(src.ndim)) - else: - axes = normalize_axis_tuple(axis, src.ndim) - - out_shape: NdShape = () - for dim in range(src.ndim): - if dim not in axes: - out_shape += (src.shape[dim],) - elif keepdims: - out_shape += (1,) - - if out is None: - out = ndarray( - shape=out_shape, dtype=res_dtype, inputs=(src, where) - ) - elif out.shape != out_shape: - errmsg = ( - f"the output shapes do not match: expected {out_shape} " - f"but got {out.shape}" - ) - raise ValueError(errmsg) - - if dtype != src.dtype: - src = src.astype(dtype) - - if out.dtype == res_dtype: - result = out - else: - result = ndarray( - shape=out_shape, dtype=res_dtype, inputs=(src, where) - ) - - where_array = broadcast_where(where, src.shape) - result._thunk.unary_reduction( - op, - src._thunk, - cls._get_where_thunk(where_array, src.shape), - axis, - axes, - keepdims, - args, - initial, - ) - - if result is not out: - out._thunk.convert(result._thunk) - - return out - - @classmethod - def _perform_binary_reduction( - cls, - op: BinaryOpCode, - one: ndarray, - two: ndarray, - dtype: np.dtype[Any], - extra_args: tuple[Scalar, ...] = (), - ) -> ndarray: - args = (one, two) - - # We only handle bool types here for now - assert dtype is not None and dtype == np.dtype(np.bool_) - # Collapsing down to a single value in this case - # Check to see if we need to broadcast between inputs - if one.shape != two.shape: - broadcast = np.broadcast_shapes(one.shape, two.shape) - else: - broadcast = None - - common_type = cls.find_common_type(one, two) - one_thunk = one._maybe_convert(common_type, args)._thunk - two_thunk = two._maybe_convert(common_type, args)._thunk - - dst = ndarray(shape=(), dtype=dtype, inputs=args) - dst._thunk.binary_reduction( - op, - one_thunk, - two_thunk, - broadcast, - extra_args, - ) - return dst - - @classmethod - def _perform_where( - cls, mask: ndarray, one: ndarray, two: ndarray - ) -> ndarray: - args = (mask, one, two) - - mask = mask._maybe_convert(np.dtype(np.bool_), args) - - common_type = cls.find_common_type(one, two) - one = one._maybe_convert(common_type, args) - two = two._maybe_convert(common_type, args) - - # Compute the output shape - out_shape = np.broadcast_shapes(mask.shape, one.shape, two.shape) - out = ndarray(shape=out_shape, dtype=common_type, inputs=args) - out._thunk.where(mask._thunk, one._thunk, two._thunk) - return out - - @classmethod - def _perform_scan( - cls, - op: ScanCode, - src: ndarray, - axis: Any = None, - dtype: Union[npt.DTypeLike, None] = None, - out: Union[ndarray, None] = None, - nan_to_identity: bool = False, - ) -> ndarray: - if src.dtype.kind != "c" and src.dtype.kind != "f": - nan_to_identity = False - if dtype is None: - if out is None: - if src.dtype.kind == "i": - # Set dtype to default platform integer - dtype = np.int_ - else: - dtype = src.dtype - else: - dtype = out.dtype - # flatten input when axis is None - if axis is None: - axis = 0 - src_arr = src.ravel() - else: - axis = normalize_axis_index(axis, src.ndim) - src_arr = src - if out is not None: - if dtype != out.dtype: - # if out array is specified, its type overrules dtype - dtype = out.dtype - if out.shape != src_arr.shape: - raise NotImplementedError( - "Varried output shape not supported. Output must have " - "same shape as input (same size if no axis is provided" - ) - else: - out = ndarray(shape=src_arr.shape, dtype=dtype) - - if dtype != src_arr.dtype: - if nan_to_identity: - if op is ScanCode.SUM: - nan_op = ConvertCode.SUM - else: - nan_op = ConvertCode.PROD - # If convert is called, it will handle NAN conversion - nan_to_identity = False - else: - nan_op = ConvertCode.NOOP - # convert input to temporary for type conversion - temp = ndarray(shape=src_arr.shape, dtype=dtype) - temp._thunk.convert(src_arr._thunk, nan_op=nan_op) - src_arr = temp - - out._thunk.scan( - op, - src_arr._thunk, - axis=axis, - dtype=dtype, - nan_to_identity=nan_to_identity, - ) - return out - def _wrap(self, new_len: int) -> ndarray: if new_len == 1: idxs = tuple(0 for i in range(self.ndim)) diff --git a/cunumeric/_array/flags.py b/cunumeric/_array/flags.py new file mode 100644 index 0000000000..3dd6be047f --- /dev/null +++ b/cunumeric/_array/flags.py @@ -0,0 +1,87 @@ +# Copyright 2021-2023 NVIDIA Corporation +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +from __future__ import annotations + +from typing import TYPE_CHECKING, Any + +import numpy as np + +if TYPE_CHECKING: + from .array import ndarray + + +class flagsobj: + """ + Information about the memory layout of the array. + + These flags don't reflect the properties of the cuNumeric array, but + rather the NumPy array that will be produced if the cuNumeric array is + materialized on a single node. + """ + + def __init__(self, array: ndarray) -> None: + # prevent infinite __setattr__ recursion + object.__setattr__(self, "_array", array) + + def __repr__(self) -> str: + return f"""\ + C_CONTIGUOUS : {self["C"]} + F_CONTIGUOUS : {self["F"]} + OWNDATA : {self["O"]} + WRITEABLE : {self["W"]} + ALIGNED : {self["A"]} + WRITEBACKIFCOPY : {self["X"]} +""" + + def __eq__(self, other: Any) -> bool: + flags = ("C", "F", "O", "W", "A", "X") + if not isinstance(other, (flagsobj, np.core.multiarray.flagsobj)): + return False + + return all(self[f] == other[f] for f in flags) # type: ignore [index] + + def __getattr__(self, name: str) -> Any: + if name == "writeable": + return self._array._writeable + flags = self._array.__array__().flags + return getattr(flags, name) + + def __setattr__(self, name: str, value: Any) -> None: + if name == "writeable": + self._check_writeable(value) + self._array._writeable = bool(value) + else: + flags = self._array.__array__().flags + setattr(flags, name, value) + + def __getitem__(self, key: Any) -> bool: + if key == "W": + return self._array._writeable + flags = self._array.__array__().flags + return flags[key] + + def __setitem__(self, key: str, value: Any) -> None: + if key == "W": + self._check_writeable(value) + self._array._writeable = bool(value) + else: + flags = self._array.__array__().flags + flags[key] = value + + def _check_writeable(self, value: Any) -> None: + if value and not self._array._writeable: + raise ValueError( + "non-writeable cunumeric arrays cannot be made writeable" + ) diff --git a/cunumeric/_array/thunk.py b/cunumeric/_array/thunk.py new file mode 100644 index 0000000000..b7784b2b1c --- /dev/null +++ b/cunumeric/_array/thunk.py @@ -0,0 +1,367 @@ +# Copyright 2021-2023 NVIDIA Corporation +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +from __future__ import annotations + +from typing import TYPE_CHECKING, Any, Union + +import numpy as np +from legate.core import Scalar +from numpy.core.multiarray import ( # type: ignore [attr-defined] + normalize_axis_index, +) +from numpy.core.numeric import ( # type: ignore [attr-defined] + normalize_axis_tuple, +) + +from ..config import ( + BinaryOpCode, + ConvertCode, + ScanCode, + UnaryOpCode, + UnaryRedCode, +) +from ..types import NdShape +from .util import broadcast_where, find_common_type + +if TYPE_CHECKING: + import numpy.typing as npt + + from ..thunk import NumPyThunk + from .array import ndarray + + +def get_where_thunk( + where: Union[None, ndarray], out_shape: NdShape +) -> Union[None, NumPyThunk]: + from .array import ndarray + + if where is None: + return where + if ( + not isinstance(where, ndarray) + or where.dtype != np.bool_ + or where.shape != out_shape + ): + raise RuntimeError("should have converted this earlier") + return where._thunk + + +def perform_unary_op( + op: UnaryOpCode, + src: ndarray, + out: Union[Any, None] = None, + extra_args: Any = None, + dtype: Union[np.dtype[Any], None] = None, + out_dtype: Union[np.dtype[Any], None] = None, +) -> ndarray: + from .array import ndarray + + if out is not None: + # If the shapes don't match see if we can broadcast + # This will raise an exception if they can't be broadcast together + if np.broadcast_shapes(src.shape, out.shape) != out.shape: + raise ValueError( + f"non-broadcastable output operand with shape {out.shape} " + f"doesn't match the broadcast shape {src.shape}" + ) + else: + # No output yet, so make one + out_shape = src.shape + + if dtype is not None: + out = ndarray( + shape=out_shape, + dtype=dtype, + inputs=(src,), + ) + elif out_dtype is not None: + out = ndarray( + shape=out_shape, + dtype=out_dtype, + inputs=(src,), + ) + else: + out = ndarray( + shape=out_shape, + dtype=src.dtype + if src.dtype.kind != "c" + else np.dtype(np.float32) + if src.dtype == np.dtype(np.complex64) + else np.dtype(np.float64), + inputs=(src,), + ) + + if out_dtype is None: + if out.dtype != src.dtype and not ( + op == UnaryOpCode.ABSOLUTE and src.dtype.kind == "c" + ): + temp = ndarray( + out.shape, + dtype=src.dtype, + inputs=(src,), + ) + temp._thunk.unary_op( + op, + src._thunk, + True, + extra_args, + ) + out._thunk.convert(temp._thunk) + else: + out._thunk.unary_op( + op, + src._thunk, + True, + extra_args, + ) + else: + if out.dtype != out_dtype: + temp = ndarray( + out.shape, + dtype=out_dtype, + inputs=(src,), + ) + temp._thunk.unary_op( + op, + src._thunk, + True, + extra_args, + ) + out._thunk.convert(temp._thunk) + else: + out._thunk.unary_op( + op, + src._thunk, + True, + extra_args, + ) + return out + + +def perform_unary_reduction( + op: UnaryRedCode, + src: ndarray, + axis: Any = None, + dtype: Union[np.dtype[Any], None] = None, + res_dtype: Union[npt.DTypeLike, None] = None, + out: Union[ndarray, None] = None, + keepdims: bool = False, + args: tuple[Scalar, ...] = (), + initial: Union[int, float, None] = None, + where: Union[ndarray, None] = None, +) -> ndarray: + from .array import ndarray + + # When 'res_dtype' is not None, the input and output of the reduction + # have different types. Such reduction operators don't take a dtype of + # the accumulator + if res_dtype is not None: + assert dtype is None + dtype = src.dtype + else: + # If 'dtype' exists, that determines both the accumulation dtype + # and the output dtype + if dtype is not None: + res_dtype = dtype + elif out is not None: + dtype = out.dtype + res_dtype = out.dtype + else: + dtype = src.dtype + res_dtype = src.dtype + + # TODO: Need to require initial to be given when the array is empty + # or a where mask is given. + if ( + op + in ( + UnaryRedCode.ARGMAX, + UnaryRedCode.ARGMIN, + UnaryRedCode.MAX, + UnaryRedCode.MIN, + ) + and src.dtype.kind == "c" + ): + raise NotImplementedError( + "(arg)max/min not supported for complex-type arrays" + ) + + if axis is None: + axes = tuple(range(src.ndim)) + else: + axes = normalize_axis_tuple(axis, src.ndim) + + out_shape: NdShape = () + for dim in range(src.ndim): + if dim not in axes: + out_shape += (src.shape[dim],) + elif keepdims: + out_shape += (1,) + + if out is None: + out = ndarray(shape=out_shape, dtype=res_dtype, inputs=(src, where)) + elif out.shape != out_shape: + errmsg = ( + f"the output shapes do not match: expected {out_shape} " + f"but got {out.shape}" + ) + raise ValueError(errmsg) + + if dtype != src.dtype: + src = src.astype(dtype) + + if out.dtype == res_dtype: + result = out + else: + result = ndarray(shape=out_shape, dtype=res_dtype, inputs=(src, where)) + + where_array = broadcast_where(where, src.shape) + result._thunk.unary_reduction( + op, + src._thunk, + get_where_thunk(where_array, src.shape), + axis, + axes, + keepdims, + args, + initial, + ) + + if result is not out: + out._thunk.convert(result._thunk) + + return out + + +def perform_binary_reduction( + op: BinaryOpCode, + one: ndarray, + two: ndarray, + dtype: np.dtype[Any], + extra_args: tuple[Scalar, ...] = (), +) -> ndarray: + from .array import ndarray + + args = (one, two) + + # We only handle bool types here for now + assert dtype is not None and dtype == np.dtype(np.bool_) + + # Collapsing down to a single value in this case + # Check to see if we need to broadcast between inputs + if one.shape != two.shape: + broadcast = np.broadcast_shapes(one.shape, two.shape) + else: + broadcast = None + + common_type = find_common_type(one, two) + one_thunk = one._maybe_convert(common_type, args)._thunk + two_thunk = two._maybe_convert(common_type, args)._thunk + + dst = ndarray(shape=(), dtype=dtype, inputs=args) + dst._thunk.binary_reduction( + op, + one_thunk, + two_thunk, + broadcast, + extra_args, + ) + return dst + + +def perform_where(mask: ndarray, one: ndarray, two: ndarray) -> ndarray: + from .array import ndarray + + args = (mask, one, two) + + mask = mask._maybe_convert(np.dtype(np.bool_), args) + + common_type = find_common_type(one, two) + one = one._maybe_convert(common_type, args) + two = two._maybe_convert(common_type, args) + + # Compute the output shape + out_shape = np.broadcast_shapes(mask.shape, one.shape, two.shape) + out = ndarray(shape=out_shape, dtype=common_type, inputs=args) + out._thunk.where(mask._thunk, one._thunk, two._thunk) + return out + + +def perform_scan( + op: ScanCode, + src: ndarray, + axis: Any = None, + dtype: Union[npt.DTypeLike, None] = None, + out: Union[ndarray, None] = None, + nan_to_identity: bool = False, +) -> ndarray: + from .array import ndarray + + if src.dtype.kind != "c" and src.dtype.kind != "f": + nan_to_identity = False + + if dtype is None: + if out is None: + if src.dtype.kind == "i": + # Set dtype to default platform integer + dtype = np.int_ + else: + dtype = src.dtype + else: + dtype = out.dtype + + # flatten input when axis is None + if axis is None: + axis = 0 + src_arr = src.ravel() + else: + axis = normalize_axis_index(axis, src.ndim) + src_arr = src + + if out is not None: + if dtype != out.dtype: + # if out array is specified, its type overrules dtype + dtype = out.dtype + if out.shape != src_arr.shape: + raise NotImplementedError( + "Varried output shape not supported. Output must have " + "same shape as input (same size if no axis is provided" + ) + else: + out = ndarray(shape=src_arr.shape, dtype=dtype) + + if dtype != src_arr.dtype: + if nan_to_identity: + if op is ScanCode.SUM: + nan_op = ConvertCode.SUM + else: + nan_op = ConvertCode.PROD + # If convert is called, it will handle NAN conversion + nan_to_identity = False + else: + nan_op = ConvertCode.NOOP + # convert input to temporary for type conversion + temp = ndarray(shape=src_arr.shape, dtype=dtype) + temp._thunk.convert(src_arr._thunk, nan_op=nan_op) + src_arr = temp + + out._thunk.scan( + op, + src_arr._thunk, + axis=axis, + dtype=dtype, + nan_to_identity=nan_to_identity, + ) + return out diff --git a/cunumeric/_array/util.py b/cunumeric/_array/util.py new file mode 100644 index 0000000000..c129e3f6cf --- /dev/null +++ b/cunumeric/_array/util.py @@ -0,0 +1,214 @@ +# Copyright 2021-2023 NVIDIA Corporation +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +from __future__ import annotations + +import operator +from functools import wraps +from inspect import signature +from typing import ( + TYPE_CHECKING, + Any, + Callable, + Optional, + ParamSpec, + Sequence, + TypeVar, + Union, + cast, +) + +import numpy as np +from legate.core.utils import OrderedSet + +from ..runtime import runtime +from ..types import NdShape + +if TYPE_CHECKING: + import numpy.typing as npt + + from ..types import NdShapeLike + from .array import ndarray + + +R = TypeVar("R") +P = ParamSpec("P") + + +def add_boilerplate( + *array_params: str, +) -> Callable[[Callable[P, R]], Callable[P, R]]: + """ + Adds required boilerplate to the wrapped cunumeric.ndarray or module-level + function. + + Every time the wrapped function is called, this wrapper will: + * Convert all specified array-like parameters, plus the special "out" + parameter (if present), to cuNumeric ndarrays. + * Convert the special "where" parameter (if present) to a valid predicate. + """ + keys = OrderedSet(array_params) + assert len(keys) == len(array_params) + + def decorator(func: Callable[P, R]) -> Callable[P, R]: + assert not hasattr( + func, "__wrapped__" + ), "this decorator must be the innermost" + + # For each parameter specified by name, also consider the case where + # it's passed as a positional parameter. + indices: OrderedSet[int] = OrderedSet() + where_idx: Optional[int] = None + out_idx: Optional[int] = None + params = signature(func).parameters + extra = keys - OrderedSet(params) + assert len(extra) == 0, f"unknown parameter(s): {extra}" + for idx, param in enumerate(params): + if param == "where": + where_idx = idx + elif param == "out": + out_idx = idx + elif param in keys: + indices.add(idx) + + @wraps(func) + def wrapper(*args: Any, **kwargs: Any) -> R: + assert (where_idx is None or len(args) <= where_idx) and ( + out_idx is None or len(args) <= out_idx + ), "'where' and 'out' should be passed as keyword arguments" + + # Convert relevant arguments to cuNumeric ndarrays + args = tuple( + convert_to_cunumeric_ndarray(arg) + if idx in indices and arg is not None + else arg + for (idx, arg) in enumerate(args) + ) + for k, v in kwargs.items(): + if v is None: + continue + elif k == "out": + kwargs[k] = convert_to_cunumeric_ndarray(v, share=True) + if not kwargs[k].flags.writeable: + raise ValueError("out is not writeable") + elif (k in keys) or (k == "where"): + kwargs[k] = convert_to_cunumeric_ndarray(v) + + return func(*args, **kwargs) + + return wrapper + + return decorator + + +def broadcast_where( + where: Union[ndarray, None], shape: NdShape +) -> Union[ndarray, None]: + if where is not None and where.shape != shape: + from .._module import broadcast_to + + where = broadcast_to(where, shape) + return where + + +def convert_to_cunumeric_ndarray(obj: Any, share: bool = False) -> ndarray: + from .array import ndarray + + # If this is an instance of one of our ndarrays then we're done + if isinstance(obj, ndarray): + return obj + # Ask the runtime to make a numpy thunk for this object + thunk = runtime.get_numpy_thunk(obj, share=share) + writeable = ( + obj.flags.writeable if isinstance(obj, np.ndarray) and share else True + ) + return ndarray(shape=None, thunk=thunk, writeable=writeable) + + +def maybe_convert_to_np_ndarray(obj: Any) -> Any: + """ + Converts cuNumeric arrays into NumPy arrays, otherwise has no effect. + """ + from ..ma import MaskedArray + from .array import ndarray + + if isinstance(obj, (ndarray, MaskedArray)): + return obj.__array__() + return obj + + +def check_writeable(arr: Union[ndarray, tuple[ndarray, ...], None]) -> None: + """ + Check if the current array is writeable + This check needs to be manually inserted + with consideration on the behavior of the corresponding method + """ + if arr is None: + return + check_list = (arr,) if not isinstance(arr, tuple) else arr + if any(not arr.flags.writeable for arr in check_list): + raise ValueError("array is not writeable") + + +def sanitize_shape( + shape: Union[NdShapeLike, Sequence[Any], npt.NDArray[Any], ndarray] +) -> NdShape: + from .array import ndarray + + seq: tuple[Any, ...] + if isinstance(shape, (ndarray, np.ndarray)): + if shape.ndim == 0: + seq = (shape.__array__().item(),) + else: + seq = tuple(shape.__array__()) + elif np.isscalar(shape): + seq = (shape,) + else: + seq = tuple(cast(NdShape, shape)) + try: + # Unfortunately, we can't do this check using + # 'isinstance(value, int)', as the values in a NumPy ndarray + # don't satisfy the predicate (they have numpy value types, + # such as numpy.int64). + result = tuple(operator.index(value) for value in seq) + except TypeError: + raise TypeError( + "expected a sequence of integers or a single integer, " + f"got {shape!r}" + ) + return result + + +def find_common_type(*args: ndarray) -> np.dtype[Any]: + """Determine common type following NumPy's coercion rules. + + Parameters + ---------- + *args : ndarray + A list of ndarrays + + Returns + ------- + datatype : data-type + The type that results from applying the NumPy type promotion rules + to the arguments. + """ + array_types = list() + scalars = list() + for array in args: + if array.ndim == 0: + scalars.append(array.dtype.type(0)) + else: + array_types.append(array.dtype) + return np.result_type(*array_types, *scalars) diff --git a/cunumeric/_module/array_basic.py b/cunumeric/_module/array_basic.py index a1fed84405..5fed9e38bd 100644 --- a/cunumeric/_module/array_basic.py +++ b/cunumeric/_module/array_basic.py @@ -16,10 +16,10 @@ from typing import TYPE_CHECKING -from ..array import add_boilerplate +from .._array.util import add_boilerplate if TYPE_CHECKING: - from ..array import ndarray + from .._array.array import ndarray from ..types import NdShape diff --git a/cunumeric/_module/array_dimension.py b/cunumeric/_module/array_dimension.py index 28447e6aaf..901a19a808 100644 --- a/cunumeric/_module/array_dimension.py +++ b/cunumeric/_module/array_dimension.py @@ -26,7 +26,8 @@ import numpy as np -from ..array import add_boilerplate, convert_to_cunumeric_ndarray, ndarray +from .._array.array import ndarray +from .._array.util import add_boilerplate, convert_to_cunumeric_ndarray from .creation_data import array if TYPE_CHECKING: diff --git a/cunumeric/_module/array_joining.py b/cunumeric/_module/array_joining.py index 3d15c9b8c2..6613024068 100644 --- a/cunumeric/_module/array_joining.py +++ b/cunumeric/_module/array_joining.py @@ -22,7 +22,8 @@ normalize_axis_index, ) -from ..array import convert_to_cunumeric_ndarray, ndarray +from .._array.array import ndarray +from .._array.util import convert_to_cunumeric_ndarray from .array_dimension import _atleast_nd if TYPE_CHECKING: diff --git a/cunumeric/_module/array_rearrange.py b/cunumeric/_module/array_rearrange.py index f0e43be4c1..a1989912de 100644 --- a/cunumeric/_module/array_rearrange.py +++ b/cunumeric/_module/array_rearrange.py @@ -16,10 +16,10 @@ from typing import TYPE_CHECKING, Optional -from ..array import add_boilerplate +from .._array.util import add_boilerplate if TYPE_CHECKING: - from ..array import ndarray + from .._array.array import ndarray from ..types import NdShapeLike diff --git a/cunumeric/_module/array_shape.py b/cunumeric/_module/array_shape.py index 105bc9f619..078dc78a99 100644 --- a/cunumeric/_module/array_shape.py +++ b/cunumeric/_module/array_shape.py @@ -16,10 +16,10 @@ from typing import TYPE_CHECKING -from ..array import add_boilerplate +from .._array.util import add_boilerplate if TYPE_CHECKING: - from ..array import ndarray + from .._array.array import ndarray from ..types import NdShapeLike, OrderType diff --git a/cunumeric/_module/array_splitting.py b/cunumeric/_module/array_splitting.py index 0fb7f34dc6..2ccf30cf09 100644 --- a/cunumeric/_module/array_splitting.py +++ b/cunumeric/_module/array_splitting.py @@ -18,7 +18,8 @@ import numpy as np -from ..array import convert_to_cunumeric_ndarray, ndarray +from .._array.array import ndarray +from .._array.util import convert_to_cunumeric_ndarray if TYPE_CHECKING: import numpy.typing as npt diff --git a/cunumeric/_module/array_tiling.py b/cunumeric/_module/array_tiling.py index 7a32d7edab..34ce2fd667 100644 --- a/cunumeric/_module/array_tiling.py +++ b/cunumeric/_module/array_tiling.py @@ -21,7 +21,8 @@ normalize_axis_index, ) -from ..array import add_boilerplate, convert_to_cunumeric_ndarray, ndarray +from .._array.array import ndarray +from .._array.util import add_boilerplate, convert_to_cunumeric_ndarray from ..runtime import runtime from .creation_shape import full diff --git a/cunumeric/_module/array_transpose.py b/cunumeric/_module/array_transpose.py index 0d386a8886..c31ddf409a 100644 --- a/cunumeric/_module/array_transpose.py +++ b/cunumeric/_module/array_transpose.py @@ -20,10 +20,10 @@ normalize_axis_tuple, ) -from ..array import add_boilerplate +from .._array.util import add_boilerplate if TYPE_CHECKING: - from ..array import ndarray + from .._array.array import ndarray @add_boilerplate("a") diff --git a/cunumeric/_module/creation_data.py b/cunumeric/_module/creation_data.py index a8e8248d3d..9e295f075c 100644 --- a/cunumeric/_module/creation_data.py +++ b/cunumeric/_module/creation_data.py @@ -18,7 +18,8 @@ import numpy as np -from ..array import add_boilerplate, ndarray +from .._array.array import ndarray +from .._array.util import add_boilerplate from ..runtime import runtime from .creation_shape import empty_like diff --git a/cunumeric/_module/creation_matrices.py b/cunumeric/_module/creation_matrices.py index 8d1f69f99e..e77aa23bfe 100644 --- a/cunumeric/_module/creation_matrices.py +++ b/cunumeric/_module/creation_matrices.py @@ -16,7 +16,8 @@ from typing import TYPE_CHECKING, Optional -from ..array import add_boilerplate, ndarray +from .._array.array import ndarray +from .._array.util import add_boilerplate from .creation_shape import ones if TYPE_CHECKING: diff --git a/cunumeric/_module/creation_ranges.py b/cunumeric/_module/creation_ranges.py index dd67ce2d74..71297b1ee1 100644 --- a/cunumeric/_module/creation_ranges.py +++ b/cunumeric/_module/creation_ranges.py @@ -19,8 +19,9 @@ import numpy as np +from .._array.array import ndarray +from .._array.util import add_boilerplate from .._ufunc.floating import floor -from ..array import add_boilerplate, ndarray if TYPE_CHECKING: import numpy.typing as npt diff --git a/cunumeric/_module/creation_shape.py b/cunumeric/_module/creation_shape.py index a8fa9c9e70..2337a11cc1 100644 --- a/cunumeric/_module/creation_shape.py +++ b/cunumeric/_module/creation_shape.py @@ -19,7 +19,8 @@ import numpy as np -from ..array import add_boilerplate, ndarray +from .._array.array import ndarray +from .._array.util import add_boilerplate from ..types import NdShapeLike if TYPE_CHECKING: diff --git a/cunumeric/_module/indexing.py b/cunumeric/_module/indexing.py index 9f97875169..5e96f58190 100644 --- a/cunumeric/_module/indexing.py +++ b/cunumeric/_module/indexing.py @@ -21,11 +21,11 @@ normalize_axis_index, ) -from ..array import ( +from .._array.array import ndarray +from .._array.util import ( add_boilerplate, check_writeable, convert_to_cunumeric_ndarray, - ndarray, ) from ..coverage import is_implemented from ..runtime import runtime diff --git a/cunumeric/_module/linalg_mvp.py b/cunumeric/_module/linalg_mvp.py index bd9cf6a0fc..1209da504a 100644 --- a/cunumeric/_module/linalg_mvp.py +++ b/cunumeric/_module/linalg_mvp.py @@ -22,8 +22,13 @@ import numpy as np import opt_einsum as oe # type: ignore [import] +from .._array.array import ndarray +from .._array.util import ( + add_boilerplate, + convert_to_cunumeric_ndarray, + find_common_type, +) from .._ufunc.math import multiply -from ..array import add_boilerplate, convert_to_cunumeric_ndarray, ndarray from ..types import NdShape from ..utils import AxesPairLike, inner_modes, matmul_modes, tensordot_modes from .creation_data import copy @@ -475,7 +480,7 @@ def _contract( elif b is None: c_dtype = a.dtype else: - c_dtype = ndarray.find_common_type(a, b) + c_dtype = find_common_type(a, b) a = _maybe_cast_input(a, c_dtype, casting) diff --git a/cunumeric/_module/logic_comparison.py b/cunumeric/_module/logic_comparison.py index 5243a99a15..996f4fd47e 100644 --- a/cunumeric/_module/logic_comparison.py +++ b/cunumeric/_module/logic_comparison.py @@ -14,15 +14,19 @@ # from __future__ import annotations -from typing import Union +from typing import TYPE_CHECKING, Union import numpy as np from legate.core import Scalar, types as ty -from ..array import add_boilerplate, ndarray +from .._array.thunk import perform_binary_reduction +from .._array.util import add_boilerplate, find_common_type from ..config import BinaryOpCode from .creation_shape import empty +if TYPE_CHECKING: + from .._array.array import ndarray + @add_boilerplate("a", "b") def allclose( @@ -83,7 +87,7 @@ def allclose( "cuNumeric does not support `equal_nan` yet for allclose" ) args = (Scalar(rtol, ty.float64), Scalar(atol, ty.float64)) - return ndarray._perform_binary_reduction( + return perform_binary_reduction( BinaryOpCode.ISCLOSE, a, b, @@ -147,7 +151,7 @@ def isclose( out_shape = np.broadcast_shapes(a.shape, b.shape) out = empty(out_shape, dtype=bool) - common_type = ndarray.find_common_type(a, b) + common_type = find_common_type(a, b) a = a.astype(common_type) b = b.astype(common_type) @@ -192,6 +196,6 @@ def array_equal( if a1.shape != a2.shape: return False - return ndarray._perform_binary_reduction( + return perform_binary_reduction( BinaryOpCode.EQUAL, a1, a2, dtype=np.dtype(np.bool_) ) diff --git a/cunumeric/_module/logic_truth.py b/cunumeric/_module/logic_truth.py index 1f4f891af7..ce31794eb3 100644 --- a/cunumeric/_module/logic_truth.py +++ b/cunumeric/_module/logic_truth.py @@ -16,10 +16,10 @@ from typing import TYPE_CHECKING, Optional, Union -from ..array import add_boilerplate +from .._array.util import add_boilerplate if TYPE_CHECKING: - from ..array import ndarray + from .._array.array import ndarray @add_boilerplate("a") diff --git a/cunumeric/_module/math_complex.py b/cunumeric/_module/math_complex.py index e7f71156a3..44c4fa5781 100644 --- a/cunumeric/_module/math_complex.py +++ b/cunumeric/_module/math_complex.py @@ -16,10 +16,10 @@ from typing import TYPE_CHECKING -from ..array import add_boilerplate +from .._array.util import add_boilerplate if TYPE_CHECKING: - from ..array import ndarray + from .._array.array import ndarray @add_boilerplate("val") diff --git a/cunumeric/_module/math_extrema.py b/cunumeric/_module/math_extrema.py index e2cf7d05c0..1b52c174d8 100644 --- a/cunumeric/_module/math_extrema.py +++ b/cunumeric/_module/math_extrema.py @@ -18,11 +18,11 @@ import numpy as np +from .._array.util import add_boilerplate from .._ufunc.comparison import maximum, minimum -from ..array import add_boilerplate if TYPE_CHECKING: - from ..array import ndarray + from .._array.array import ndarray @add_boilerplate("a") diff --git a/cunumeric/_module/math_misc.py b/cunumeric/_module/math_misc.py index 47a72bf5d9..e49f551dd9 100644 --- a/cunumeric/_module/math_misc.py +++ b/cunumeric/_module/math_misc.py @@ -16,7 +16,8 @@ from typing import TYPE_CHECKING, Any, Union -from ..array import add_boilerplate, ndarray +from .._array.array import ndarray +from .._array.util import add_boilerplate if TYPE_CHECKING: import numpy.typing as npt diff --git a/cunumeric/_module/math_sum_prod_diff.py b/cunumeric/_module/math_sum_prod_diff.py index bd75c73631..8d0d8abe9c 100644 --- a/cunumeric/_module/math_sum_prod_diff.py +++ b/cunumeric/_module/math_sum_prod_diff.py @@ -14,19 +14,23 @@ # from __future__ import annotations -from typing import Any, Optional, Union +from typing import TYPE_CHECKING, Any, Optional, Union import numpy as np +from .._array.thunk import perform_scan, perform_unary_reduction +from .._array.util import add_boilerplate from .._ufunc.floating import isnan from .._ufunc.math import add, multiply from .._unary_red_utils import get_non_nan_unary_red_code -from ..array import add_boilerplate, ndarray from ..config import ScanCode, UnaryRedCode from ..settings import settings as cunumeric_settings from .indexing import putmask from .logic_truth import all, any +if TYPE_CHECKING: + from .._array.array import ndarray + @add_boilerplate("a") def prod( @@ -245,7 +249,7 @@ def cumprod( -------- Multiple GPUs, Multiple CPUs """ - return ndarray._perform_scan( + return perform_scan( ScanCode.PROD, a, axis=axis, @@ -307,7 +311,7 @@ def cumsum( -------- Multiple GPUs, Multiple CPUs """ - return ndarray._perform_scan( + return perform_scan( ScanCode.SUM, a, axis=axis, dtype=dtype, out=out, nan_to_identity=False ) @@ -368,7 +372,7 @@ def nancumprod( -------- Multiple GPUs, Multiple CPUs """ - return ndarray._perform_scan( + return perform_scan( ScanCode.PROD, a, axis=axis, dtype=dtype, out=out, nan_to_identity=True ) @@ -429,7 +433,7 @@ def nancumsum( -------- Multiple GPUs, Multiple CPUs """ - return ndarray._perform_scan( + return perform_scan( ScanCode.SUM, a, axis=axis, dtype=dtype, out=out, nan_to_identity=True ) @@ -492,7 +496,7 @@ def nanargmax( a.dtype.kind, UnaryRedCode.NANARGMAX ) - return a._perform_unary_reduction( + return perform_unary_reduction( unary_red_code, a, axis=axis, @@ -560,7 +564,7 @@ def nanargmin( a.dtype.kind, UnaryRedCode.NANARGMIN ) - return a._perform_unary_reduction( + return perform_unary_reduction( unary_red_code, a, axis=axis, @@ -646,7 +650,7 @@ def nanmin( a.dtype.kind, UnaryRedCode.NANMIN ) - out_array = a._perform_unary_reduction( + out_array = perform_unary_reduction( unary_red_code, a, axis=axis, @@ -742,7 +746,7 @@ def nanmax( a.dtype.kind, UnaryRedCode.NANMAX ) - out_array = a._perform_unary_reduction( + out_array = perform_unary_reduction( unary_red_code, a, axis=axis, @@ -843,7 +847,7 @@ def nanprod( else: unary_red_code = UnaryRedCode.PROD - return a._perform_unary_reduction( + return perform_unary_reduction( unary_red_code, a, axis=axis, diff --git a/cunumeric/_module/sets_making.py b/cunumeric/_module/sets_making.py index 72e77af710..a15aafc06d 100644 --- a/cunumeric/_module/sets_making.py +++ b/cunumeric/_module/sets_making.py @@ -16,10 +16,10 @@ from typing import TYPE_CHECKING, Optional -from ..array import add_boilerplate +from .._array.util import add_boilerplate if TYPE_CHECKING: - from ..array import ndarray + from .._array.array import ndarray _builtin_any = any diff --git a/cunumeric/_module/ssc_counting.py b/cunumeric/_module/ssc_counting.py index e46f61d012..361166c0a1 100644 --- a/cunumeric/_module/ssc_counting.py +++ b/cunumeric/_module/ssc_counting.py @@ -16,10 +16,10 @@ from typing import TYPE_CHECKING, Optional, Union -from ..array import add_boilerplate +from .._array.util import add_boilerplate if TYPE_CHECKING: - from ..array import ndarray + from .._array.array import ndarray @add_boilerplate("a") diff --git a/cunumeric/_module/ssc_searching.py b/cunumeric/_module/ssc_searching.py index 040db11b9b..dec8907a84 100644 --- a/cunumeric/_module/ssc_searching.py +++ b/cunumeric/_module/ssc_searching.py @@ -16,7 +16,9 @@ from typing import TYPE_CHECKING, Optional, Union -from ..array import add_boilerplate, ndarray +from .._array.array import ndarray +from .._array.thunk import perform_where +from .._array.util import add_boilerplate from .array_shape import ravel, reshape if TYPE_CHECKING: @@ -230,7 +232,7 @@ def where( " 'where'" ) return nonzero(a) - return ndarray._perform_where(a, x, y) + return perform_where(a, x, y) @add_boilerplate("a") diff --git a/cunumeric/_module/ssc_sorting.py b/cunumeric/_module/ssc_sorting.py index ed456d4950..84a6d7b172 100644 --- a/cunumeric/_module/ssc_sorting.py +++ b/cunumeric/_module/ssc_sorting.py @@ -18,7 +18,8 @@ import numpy as np -from ..array import add_boilerplate, ndarray +from .._array.array import ndarray +from .._array.util import add_boilerplate if TYPE_CHECKING: from ..types import SelectKind, SortType diff --git a/cunumeric/_module/stats_avgs_vars.py b/cunumeric/_module/stats_avgs_vars.py index 212c358a39..a0064f96ea 100644 --- a/cunumeric/_module/stats_avgs_vars.py +++ b/cunumeric/_module/stats_avgs_vars.py @@ -18,10 +18,10 @@ import numpy as np -from ..array import add_boilerplate +from .._array.util import add_boilerplate if TYPE_CHECKING: - from ..array import ndarray + from .._array.util import ndarray @add_boilerplate("a") diff --git a/cunumeric/_module/stats_histograms.py b/cunumeric/_module/stats_histograms.py index 8318951fbf..c4f426e079 100644 --- a/cunumeric/_module/stats_histograms.py +++ b/cunumeric/_module/stats_histograms.py @@ -18,7 +18,8 @@ import numpy as np -from ..array import add_boilerplate, ndarray +from .._array.array import ndarray +from .._array.util import add_boilerplate from .creation_data import asarray from .creation_shape import ones, zeros from .math_extrema import amax, amin diff --git a/cunumeric/_module/stats_order.py b/cunumeric/_module/stats_order.py index e3dd6b9880..1989786cd7 100644 --- a/cunumeric/_module/stats_order.py +++ b/cunumeric/_module/stats_order.py @@ -19,7 +19,7 @@ import numpy as np -from ..array import add_boilerplate +from .._array.util import add_boilerplate from .array_transpose import moveaxis from .creation_shape import zeros from .ssc_sorting import sort @@ -29,7 +29,7 @@ import numpy.typing as npt - from ..array import ndarray + from .._array.array import ndarray # for the case when axis = tuple (non-singleton) diff --git a/cunumeric/_ufunc/ufunc.py b/cunumeric/_ufunc/ufunc.py index 4c32ace8d5..f4e0d2f81a 100644 --- a/cunumeric/_ufunc/ufunc.py +++ b/cunumeric/_ufunc/ufunc.py @@ -19,11 +19,11 @@ import numpy as np from legate.core.utils import OrderedSet -from ..array import ( +from .._array.thunk import perform_unary_reduction +from .._array.util import ( add_boilerplate, check_writeable, convert_to_cunumeric_ndarray, - ndarray, ) from ..config import BinaryOpCode, UnaryOpCode, UnaryRedCode from ..types import NdShape @@ -31,6 +31,7 @@ if TYPE_CHECKING: import numpy.typing as npt + from .._array.array import ndarray from ..types import CastingKind @@ -237,6 +238,8 @@ def _maybe_create_result( casting: CastingKind, inputs: tuple[ndarray, ...], ) -> ndarray: + from .._array.array import ndarray + if out is None: return ndarray(shape=out_shape, dtype=res_dtype, inputs=inputs) elif out.dtype != res_dtype: @@ -263,6 +266,8 @@ def _maybe_cast_output( def _maybe_convert_output_to_cunumeric_ndarray( out: Union[ndarray, npt.NDArray[Any], None] ) -> Union[ndarray, None]: + from .._array.array import ndarray + if out is None: return None if isinstance(out, ndarray): @@ -557,6 +562,8 @@ def __init__( def _find_common_type( arrs: Sequence[ndarray], orig_args: Sequence[Any] ) -> np.dtype[Any]: + from .._array.array import ndarray + all_ndarray = all(isinstance(arg, ndarray) for arg in orig_args) unique_dtypes = OrderedSet(arr.dtype for arr in arrs) # If all operands are ndarrays and they all have the same dtype, @@ -758,7 +765,7 @@ def reduce( axis = None # TODO: Unary reductions still need to be refactored - return array._perform_unary_reduction( + return perform_unary_reduction( self._red_code, array, axis=axis, diff --git a/cunumeric/bits.py b/cunumeric/bits.py index 9adb4dc102..f8846498e0 100644 --- a/cunumeric/bits.py +++ b/cunumeric/bits.py @@ -16,11 +16,11 @@ from typing import TYPE_CHECKING, Optional, Tuple +from ._array.util import add_boilerplate from ._module import empty -from .array import add_boilerplate if TYPE_CHECKING: - from .array import ndarray + from ._array.array import ndarray from .types import BitOrder diff --git a/cunumeric/coverage.py b/cunumeric/coverage.py index a8e57285f5..916fba7b7e 100644 --- a/cunumeric/coverage.py +++ b/cunumeric/coverage.py @@ -31,13 +31,13 @@ Iterable, Mapping, Optional, + Protocol, Union, cast, ) from legate.core import track_provenance from legate.core.utils import OrderedSet -from typing_extensions import Protocol from .runtime import runtime from .settings import settings diff --git a/cunumeric/deferred.py b/cunumeric/deferred.py index cd22fae8f9..b9bb519b82 100644 --- a/cunumeric/deferred.py +++ b/cunumeric/deferred.py @@ -27,6 +27,7 @@ Callable, Dict, Optional, + ParamSpec, Sequence, TypeVar, Union, @@ -50,7 +51,6 @@ from numpy.core.numeric import ( # type: ignore [attr-defined] normalize_axis_tuple, ) -from typing_extensions import ParamSpec from ._sort import sort_deferred from .config import ( diff --git a/cunumeric/fft/__init__.py b/cunumeric/fft/__init__.py index e541941cf1..2419dbbd79 100644 --- a/cunumeric/fft/__init__.py +++ b/cunumeric/fft/__init__.py @@ -16,7 +16,7 @@ import numpy.fft as _npfft -from ..array import maybe_convert_to_np_ndarray +from .._array.util import maybe_convert_to_np_ndarray from ..coverage import clone_module from .fft import * diff --git a/cunumeric/fft/fft.py b/cunumeric/fft/fft.py index dd58934b23..591a6cdc35 100644 --- a/cunumeric/fft/fft.py +++ b/cunumeric/fft/fft.py @@ -18,11 +18,11 @@ import numpy as np -from .._module import add_boilerplate +from .._array.util import add_boilerplate from ..config import FFT_C2C, FFT_Z2Z, FFTCode, FFTDirection, FFTNormalization if TYPE_CHECKING: - from ..array import ndarray + from .._array.array import ndarray def _sanitize_user_axes( diff --git a/cunumeric/linalg/__init__.py b/cunumeric/linalg/__init__.py index 18542b95da..d39cb94dd5 100644 --- a/cunumeric/linalg/__init__.py +++ b/cunumeric/linalg/__init__.py @@ -16,7 +16,7 @@ import numpy.linalg as _nplinalg -from ..array import maybe_convert_to_np_ndarray +from .._array.util import maybe_convert_to_np_ndarray from ..coverage import clone_module from .linalg import * diff --git a/cunumeric/linalg/linalg.py b/cunumeric/linalg/linalg.py index fd6c85795b..c50f322cf0 100644 --- a/cunumeric/linalg/linalg.py +++ b/cunumeric/linalg/linalg.py @@ -24,9 +24,9 @@ normalize_axis_tuple, ) +from .._array.util import add_boilerplate, convert_to_cunumeric_ndarray from .._module import dot, empty_like, eye, matmul, ndarray from .._ufunc.math import add, sqrt as _sqrt -from ..array import add_boilerplate, convert_to_cunumeric_ndarray from ._exception import LinAlgError if TYPE_CHECKING: diff --git a/cunumeric/logic.py b/cunumeric/logic.py index 25f3090282..9f32df949d 100644 --- a/cunumeric/logic.py +++ b/cunumeric/logic.py @@ -18,10 +18,11 @@ import numpy as np +from ._array.array import ndarray +from ._array.util import convert_to_cunumeric_ndarray from ._module import full from ._ufunc.comparison import logical_and from ._ufunc.floating import isinf, signbit -from .array import convert_to_cunumeric_ndarray, ndarray if TYPE_CHECKING: import numpy.typing as npt diff --git a/cunumeric/ma/__init__.py b/cunumeric/ma/__init__.py index 6e8f14bf4d..ecda9aa586 100644 --- a/cunumeric/ma/__init__.py +++ b/cunumeric/ma/__init__.py @@ -16,7 +16,7 @@ import numpy.ma as _ma -from ..array import maybe_convert_to_np_ndarray +from .._array.util import maybe_convert_to_np_ndarray from ..coverage import clone_module from ._masked_array import MaskedArray diff --git a/cunumeric/ma/_masked_array.py b/cunumeric/ma/_masked_array.py index 4420bdf1c3..ba3c5da385 100644 --- a/cunumeric/ma/_masked_array.py +++ b/cunumeric/ma/_masked_array.py @@ -23,7 +23,7 @@ import numpy as _np -from ..array import maybe_convert_to_np_ndarray +from .._array.util import maybe_convert_to_np_ndarray from ..coverage import clone_class NDARRAY_INTERNAL = { diff --git a/cunumeric/random/__init__.py b/cunumeric/random/__init__.py index 2214b99ab1..50829b055f 100644 --- a/cunumeric/random/__init__.py +++ b/cunumeric/random/__init__.py @@ -16,7 +16,7 @@ import numpy.random as _nprandom -from ..array import maybe_convert_to_np_ndarray +from .._array.util import maybe_convert_to_np_ndarray from ..coverage import clone_module from ..runtime import runtime diff --git a/cunumeric/random/_bitgenerator.py b/cunumeric/random/_bitgenerator.py index 8d99e61aae..7c1e80de43 100644 --- a/cunumeric/random/_bitgenerator.py +++ b/cunumeric/random/_bitgenerator.py @@ -20,7 +20,7 @@ import numpy as np -from ..array import ndarray +from .._array.array import ndarray from ..config import BitGeneratorType from ..runtime import runtime diff --git a/cunumeric/random/_generator.py b/cunumeric/random/_generator.py index ed8b9a6675..9e352d3766 100644 --- a/cunumeric/random/_generator.py +++ b/cunumeric/random/_generator.py @@ -23,7 +23,7 @@ if TYPE_CHECKING: import numpy.typing as npt - from ..array import ndarray + from .._array.array import ndarray from ..types import NdShapeLike diff --git a/cunumeric/random/_legacy.py b/cunumeric/random/_legacy.py index cab8939237..f4591feb58 100644 --- a/cunumeric/random/_legacy.py +++ b/cunumeric/random/_legacy.py @@ -19,7 +19,7 @@ import numpy as np import numpy.random as nprandom -from ..array import ndarray +from .._array.array import ndarray from ..runtime import runtime if TYPE_CHECKING: diff --git a/cunumeric/random/_random.py b/cunumeric/random/_random.py index d3dc144b5a..9b2c4c722b 100644 --- a/cunumeric/random/_random.py +++ b/cunumeric/random/_random.py @@ -18,7 +18,7 @@ import numpy as np -from ..array import ndarray +from .._array.array import ndarray from ..coverage import clone_class from ..runtime import runtime from ._generator import default_rng, get_static_generator # NOQA diff --git a/cunumeric/runtime.py b/cunumeric/runtime.py index 7366828dc5..0880718829 100644 --- a/cunumeric/runtime.py +++ b/cunumeric/runtime.py @@ -16,13 +16,12 @@ import warnings from functools import reduce -from typing import TYPE_CHECKING, Any, Optional, Sequence, Union +from typing import TYPE_CHECKING, Any, Optional, Sequence, TypeGuard, Union import legate.core.types as ty import numpy as np from legate.core import LEGATE_MAX_DIM, Scalar, TaskTarget, get_legate_runtime from legate.settings import settings as legate_settings -from typing_extensions import TypeGuard from .config import ( BitGeneratorOperation, @@ -42,7 +41,7 @@ import numpy.typing as npt from legate.core import AutoTask, ManualTask - from .array import ndarray + from ._array.array import ndarray from .deferred import DeferredArray from .eager import EagerArray from .thunk import NumPyThunk diff --git a/cunumeric/types.py b/cunumeric/types.py index 1e3d032b08..1a2cfd1cc3 100644 --- a/cunumeric/types.py +++ b/cunumeric/types.py @@ -14,9 +14,7 @@ # from __future__ import annotations -from typing import Literal, Tuple, Union - -from typing_extensions import TypeAlias +from typing import Literal, Tuple, TypeAlias, Union BoundsMode: TypeAlias = Literal["raise", "wrap", "clip"] diff --git a/cunumeric/window.py b/cunumeric/window.py index 9cee6022c7..99d0a3b801 100644 --- a/cunumeric/window.py +++ b/cunumeric/window.py @@ -18,8 +18,8 @@ import numpy as np +from ._array.array import ndarray from ._module import empty, ones -from .array import ndarray from .config import WindowOpCode diff --git a/docs/cunumeric/source/api/_ndarray.rst b/docs/cunumeric/source/api/_ndarray.rst index 3320f0857a..8e3f03de7d 100644 --- a/docs/cunumeric/source/api/_ndarray.rst +++ b/docs/cunumeric/source/api/_ndarray.rst @@ -31,7 +31,6 @@ cunumeric.ndarray ~ndarray.dump ~ndarray.dumps ~ndarray.fill - ~ndarray.find_common_type ~ndarray.flatten ~ndarray.flip ~ndarray.getfield diff --git a/docs/cunumeric/source/api/datatype.rst b/docs/cunumeric/source/api/datatype.rst new file mode 100644 index 0000000000..1e4d521e95 --- /dev/null +++ b/docs/cunumeric/source/api/datatype.rst @@ -0,0 +1,12 @@ +Data type routines +================== + +.. currentmodule:: cunumeric + +Data type testing +----------------- + +.. autosummary:: + :toctree: generated/ + + find_common_type \ No newline at end of file diff --git a/docs/cunumeric/source/api/ndarray.rst b/docs/cunumeric/source/api/ndarray.rst index aca3b9ce0e..40a2a6bca8 100644 --- a/docs/cunumeric/source/api/ndarray.rst +++ b/docs/cunumeric/source/api/ndarray.rst @@ -59,7 +59,6 @@ Data Type :toctree: generated/ ndarray.dtype - ndarray.find_common_type Other Attributes ~~~~~~~~~~~~~~~~ diff --git a/docs/cunumeric/source/api/routines.rst b/docs/cunumeric/source/api/routines.rst index e85a5c65b0..e3fc080a07 100644 --- a/docs/cunumeric/source/api/routines.rst +++ b/docs/cunumeric/source/api/routines.rst @@ -8,6 +8,7 @@ Routines creation manipulation binary + datatype indexing linalg logic diff --git a/examples/benchmark.py b/examples/benchmark.py index 434c76a9e5..dcb97dd148 100644 --- a/examples/benchmark.py +++ b/examples/benchmark.py @@ -17,8 +17,7 @@ import math from functools import reduce - -from typing_extensions import Protocol +from typing import Protocol class Timer(Protocol): From e0ef2dfa44aaeca088c7756782a53fba308f4913 Mon Sep 17 00:00:00 2001 From: Bryan Van de Ven Date: Thu, 29 Feb 2024 17:30:20 -0800 Subject: [PATCH 161/462] move errant modules (#130) --- cunumeric/__init__.py | 3 - cunumeric/_module/__init__.py | 17 ++- .../binary_bit_packing.py} | 8 +- cunumeric/_module/logic_array_contents.py | 114 ++++++++++++++++++ .../{logic.py => _module/logic_array_type.py} | 100 +-------------- cunumeric/{ => _module}/window.py | 6 +- 6 files changed, 140 insertions(+), 108 deletions(-) rename cunumeric/{bits.py => _module/binary_bit_packing.py} (97%) create mode 100644 cunumeric/_module/logic_array_contents.py rename cunumeric/{logic.py => _module/logic_array_type.py} (57%) rename cunumeric/{ => _module}/window.py (97%) diff --git a/cunumeric/__init__.py b/cunumeric/__init__.py index 4301307681..434a6caefd 100644 --- a/cunumeric/__init__.py +++ b/cunumeric/__init__.py @@ -31,10 +31,7 @@ from ._array.util import maybe_convert_to_np_ndarray from ._module import * from ._ufunc import * -from .bits import packbits, unpackbits from .coverage import clone_module -from .logic import * -from .window import bartlett, blackman, hamming, hanning, kaiser clone_module(_np, globals(), maybe_convert_to_np_ndarray) diff --git a/cunumeric/_module/__init__.py b/cunumeric/_module/__init__.py index 4909b058b3..f20777b236 100644 --- a/cunumeric/_module/__init__.py +++ b/cunumeric/_module/__init__.py @@ -44,6 +44,14 @@ from .array_tiling import * # Tiling arrays from .array_rearrange import * # Rearranging elements +# --- Binary operations +# https://numpy.org/doc/stable/reference/routines.bitwise.html +# +# from .binary_elementwise_bit_ops import * # Elementwise bit operations +# from .binary_output import * # Output formatting + +from .binary_bit_packing import * # Bit packing + # --- Indexing routines # # These routines in the numpy module are a bit odd, they are documented under @@ -59,11 +67,11 @@ # --- Logic functions # https://numpy.org/doc/stable/reference/routines.logic.html # -# from .logic_contents import * # Array contents -# from .logic_type import * # Array type testing # from .logic_ops import * # Logical operations from .logic_truth import * # Truth value testing +from .logic_array_contents import * # Array contents +from .logic_array_type import * # Array type testing from .logic_comparison import * # Comparison # --- Mathematical functions @@ -104,3 +112,8 @@ from .stats_order import * # Order statistics from .stats_avgs_vars import * # Averages and variances from .stats_histograms import * # Histograms + +# --- Window functions +# https://numpy.org/doc/stable/reference/routines.window.html + +from .window import * # Various windows diff --git a/cunumeric/bits.py b/cunumeric/_module/binary_bit_packing.py similarity index 97% rename from cunumeric/bits.py rename to cunumeric/_module/binary_bit_packing.py index f8846498e0..325ac2fc2d 100644 --- a/cunumeric/bits.py +++ b/cunumeric/_module/binary_bit_packing.py @@ -16,12 +16,12 @@ from typing import TYPE_CHECKING, Optional, Tuple -from ._array.util import add_boilerplate -from ._module import empty +from .._array.util import add_boilerplate +from .creation_shape import empty if TYPE_CHECKING: - from ._array.array import ndarray - from .types import BitOrder + from .._array.array import ndarray + from ..types import BitOrder def _sanitize_arguments( diff --git a/cunumeric/_module/logic_array_contents.py b/cunumeric/_module/logic_array_contents.py new file mode 100644 index 0000000000..a1fe574b98 --- /dev/null +++ b/cunumeric/_module/logic_array_contents.py @@ -0,0 +1,114 @@ +# Copyright 2024 NVIDIA Corporation +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +from __future__ import annotations + +from typing import TYPE_CHECKING + +from .._array.util import convert_to_cunumeric_ndarray +from .._ufunc.comparison import logical_and +from .._ufunc.floating import isinf, signbit + +if TYPE_CHECKING: + from .._array.array import ndarray + + +def isneginf(x: ndarray, out: ndarray | None = None) -> ndarray: + """ + + Test element-wise for negative infinity, return result as bool array. + + Parameters + ---------- + x : array_like + The input array. + out : array_like, optional + A location into which the result is stored. If provided, it must have a + shape that the input broadcasts to. If not provided or None, a + freshly-allocated boolean array is returned. + + Returns + ------- + out : ndarray + A boolean array with the same dimensions as the input. + If second argument is not supplied then a numpy boolean array is + returned with values True where the corresponding element of the + input is negative infinity and values False where the element of + the input is not negative infinity. + + If a second argument is supplied the result is stored there. If the + type of that array is a numeric type the result is represented as + zeros and ones, if the type is boolean then as False and True. The + return value `out` is then a reference to that array. + + See Also + -------- + numpy.isneginf + + Availability + -------- + Multiple GPUs, Multiple CPUs + + """ + x = convert_to_cunumeric_ndarray(x) + if out is not None: + out = convert_to_cunumeric_ndarray(out, share=True) + rhs1 = isinf(x) + rhs2 = signbit(x) + return logical_and(rhs1, rhs2, out=out) + + +def isposinf(x: ndarray, out: ndarray | None = None) -> ndarray: + """ + + Test element-wise for positive infinity, return result as bool array. + + Parameters + ---------- + x : array_like + The input array. + out : array_like, optional + A location into which the result is stored. If provided, it must have a + shape that the input broadcasts to. If not provided or None, a + freshly-allocated boolean array is returned. + + Returns + ------- + out : ndarray + A boolean array with the same dimensions as the input. + If second argument is not supplied then a boolean array is returned + with values True where the corresponding element of the input is + positive infinity and values False where the element of the input is + not positive infinity. + + If a second argument is supplied the result is stored there. If the + type of that array is a numeric type the result is represented as zeros + and ones, if the type is boolean then as False and True. + The return value `out` is then a reference to that array. + + See Also + -------- + numpy.isposinf + + Availability + -------- + Multiple GPUs, Multiple CPUs + + """ + x = convert_to_cunumeric_ndarray(x) + if out is not None: + out = convert_to_cunumeric_ndarray(out, share=True) + rhs1 = isinf(x) + rhs2 = ~signbit(x) + return logical_and(rhs1, rhs2, out=out) diff --git a/cunumeric/logic.py b/cunumeric/_module/logic_array_type.py similarity index 57% rename from cunumeric/logic.py rename to cunumeric/_module/logic_array_type.py index 9f32df949d..cde69de782 100644 --- a/cunumeric/logic.py +++ b/cunumeric/_module/logic_array_type.py @@ -1,4 +1,4 @@ -# Copyright 2022 NVIDIA Corporation +# Copyright 2024 NVIDIA Corporation # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -18,106 +18,14 @@ import numpy as np -from ._array.array import ndarray -from ._array.util import convert_to_cunumeric_ndarray -from ._module import full -from ._ufunc.comparison import logical_and -from ._ufunc.floating import isinf, signbit +from .._array.array import ndarray +from .._array.util import convert_to_cunumeric_ndarray +from .creation_shape import full if TYPE_CHECKING: import numpy.typing as npt -def isneginf(x: ndarray, out: Union[ndarray, None] = None) -> ndarray: - """ - - Test element-wise for negative infinity, return result as bool array. - - Parameters - ---------- - x : array_like - The input array. - out : array_like, optional - A location into which the result is stored. If provided, it must have a - shape that the input broadcasts to. If not provided or None, a - freshly-allocated boolean array is returned. - - Returns - ------- - out : ndarray - A boolean array with the same dimensions as the input. - If second argument is not supplied then a numpy boolean array is - returned with values True where the corresponding element of the - input is negative infinity and values False where the element of - the input is not negative infinity. - - If a second argument is supplied the result is stored there. If the - type of that array is a numeric type the result is represented as - zeros and ones, if the type is boolean then as False and True. The - return value `out` is then a reference to that array. - - See Also - -------- - numpy.isneginf - - Availability - -------- - Multiple GPUs, Multiple CPUs - - """ - x = convert_to_cunumeric_ndarray(x) - if out is not None: - out = convert_to_cunumeric_ndarray(out, share=True) - rhs1 = isinf(x) - rhs2 = signbit(x) - return logical_and(rhs1, rhs2, out=out) - - -def isposinf(x: ndarray, out: Union[ndarray, None] = None) -> ndarray: - """ - - Test element-wise for positive infinity, return result as bool array. - - Parameters - ---------- - x : array_like - The input array. - out : array_like, optional - A location into which the result is stored. If provided, it must have a - shape that the input broadcasts to. If not provided or None, a - freshly-allocated boolean array is returned. - - Returns - ------- - out : ndarray - A boolean array with the same dimensions as the input. - If second argument is not supplied then a boolean array is returned - with values True where the corresponding element of the input is - positive infinity and values False where the element of the input is - not positive infinity. - - If a second argument is supplied the result is stored there. If the - type of that array is a numeric type the result is represented as zeros - and ones, if the type is boolean then as False and True. - The return value `out` is then a reference to that array. - - See Also - -------- - numpy.isposinf - - Availability - -------- - Multiple GPUs, Multiple CPUs - - """ - x = convert_to_cunumeric_ndarray(x) - if out is not None: - out = convert_to_cunumeric_ndarray(out, share=True) - rhs1 = isinf(x) - rhs2 = ~signbit(x) - return logical_and(rhs1, rhs2, out=out) - - def iscomplex(x: Union[ndarray, npt.NDArray[Any]]) -> ndarray: """ diff --git a/cunumeric/window.py b/cunumeric/_module/window.py similarity index 97% rename from cunumeric/window.py rename to cunumeric/_module/window.py index 99d0a3b801..5a95ccb6ba 100644 --- a/cunumeric/window.py +++ b/cunumeric/_module/window.py @@ -18,9 +18,9 @@ import numpy as np -from ._array.array import ndarray -from ._module import empty, ones -from .config import WindowOpCode +from .._array.array import ndarray +from ..config import WindowOpCode +from .creation_shape import empty, ones def _create_window(M: int, op_code: WindowOpCode, *args: Any) -> ndarray: From ecf034f707008be836807de2856d6fdd0d22ddc0 Mon Sep 17 00:00:00 2001 From: Shijie Chen Date: Fri, 1 Mar 2024 10:08:50 +0800 Subject: [PATCH 162/462] Added tests for cunumeric.convolve (#98) --- tests/cpp/integration/test_convolve.cc | 135 +++++++++++++++++++++++++ 1 file changed, 135 insertions(+) create mode 100644 tests/cpp/integration/test_convolve.cc diff --git a/tests/cpp/integration/test_convolve.cc b/tests/cpp/integration/test_convolve.cc new file mode 100644 index 0000000000..8aa3744804 --- /dev/null +++ b/tests/cpp/integration/test_convolve.cc @@ -0,0 +1,135 @@ +/* Copyright 2024 NVIDIA Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +#include "common_utils.h" +#include + +using namespace cunumeric; + +namespace { + +TEST(Convolve, test_dtype) +{ + auto x = mk_array({1, 2, 3}); + auto y = mk_array({0, 1, 0.5}); + auto out1 = convolve(x, y); + auto out2 = convolve(y, x); + debug_array(out1); + debug_array(out2); + // out1 = [1, 2, 3], out2 = [1, 2.5, 4] + // It is a bug. + // It violates the "NumPy type promotion rules". +} + +TEST(Convolve, test_empty) +{ + auto a = mk_array({}, {0}); + auto v = mk_array({}, {0}); + debug_array(a); + debug_array(v); + // An exception should be thrown, but it doesn't. + auto out = convolve(a, v); + debug_array(out); +} + +TEST(Convolve, test_diff_dims) +{ + auto a = zeros({5, 5, 5}); + auto v = zeros({5, 5}); + EXPECT_ANY_THROW(convolve(a, v)); +} + +std::vector, + std::vector, + std::vector, + std::vector, + std::vector>> + test_data{ + {{0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 1, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0, 0}, + {1, 1, 0, 1, 0, 0, 1}, + {0, 0, 1, 2, 1, 1, 1, 0, 2, 3, 2, 2, 1, 2, 2, 1, 2, 0, 0, 2, 1, 0, 2, 1, 0, 2, 0, 0, 1, 0}, + {30}, + {7}}, + {{0, 1, 0, 0, 1, 1, 1, 1, 0, 1, 1, 0, 1, 0, 1, 0, 1, 0, 1, 1, 1, 1, 0, 1, 1, + 1, 1, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 1, 0, 1, 1, 0, 1, 0, 1, 1, 1, 1, 0, 1, + 0, 0, 1, 0, 0, 1, 0, 1, 1, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 1, 1, 1, 1, 0, + 1, 0, 1, 0, 1, 1, 1, 0, 1, 1, 0, 0, 0, 1, 1, 0, 1, 0, 0, 1, 1, 1, 0, 0, 0}, + {1, 1, 1, 1, 0, 1, 0, 1, 0, 0, 1, 0, 0, 0, 1}, + {2, 3, 3, 3, 4, 4, 3, 5, 2, 3, 4, 3, 6, 5, 7, 4, 5, 4, 2, 2, 4, 4, 6, 5, 6, + 4, 5, 3, 3, 1, 3, 4, 6, 6, 5, 7, 4, 6, 3, 2, 4, 1, 4, 5, 4, 5, 5, 6, 2, 3, + 3, 1, 4, 3, 3, 4, 3, 3, 2, 1, 4, 5, 3, 5, 3, 4, 3, 3, 2, 2, 4, 5, 4, 6, 2, + 3, 1, 5, 2, 3, 3, 4, 4, 5, 5, 5, 3, 4, 1, 2, 0, 2, 3, 2, 2, 2, 3, 1, 0, 0}, + {10, 10}, + {3, 5}}, + {{1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 1, 0, 0, 1, 1, 1, 0, 1, 0, 0, 0, 0, 1, + 0, 1, 0, 1, 1, 1, 0, 1, 1, 1, 0, 0, 1, 0, 0, 1, 1, 1, 0, 0, 0, 1, 0, 0, 0, + 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 1, 1, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 1, + 0, 1, 0, 1, 1, 1, 0, 1, 0, 1, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 1, 0, 1, + 0, 0, 1, 0, 1, 0, 1, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0, 1, 0, 1, 1, 1, 1, 1, 0}, + {1, 1, 0, 0, 0, 1, 0, 1, 1, 1, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 1, 0}, + {2, 1, 3, 3, 2, 1, 4, 2, 3, 4, 5, 6, 3, 4, 2, 3, 4, 3, 4, 1, 1, 2, 3, 2, 1, + 1, 3, 3, 5, 2, 5, 7, 6, 5, 2, 5, 7, 4, 4, 4, 4, 6, 6, 5, 3, 2, 3, 2, 2, 1, + 4, 3, 6, 5, 4, 1, 7, 7, 7, 3, 6, 8, 8, 6, 3, 3, 5, 4, 5, 3, 2, 2, 3, 4, 2, + 2, 3, 2, 5, 2, 4, 5, 9, 4, 3, 2, 7, 7, 4, 1, 4, 7, 7, 8, 1, 0, 4, 3, 4, 3, + 3, 3, 3, 4, 2, 1, 5, 2, 4, 2, 2, 4, 5, 3, 2, 1, 4, 5, 4, 2, 2, 3, 3, 3, 0}, + {5, 5, 5}, + {3, 3, 3}}}; + +TEST(Convolve, test_int) +{ + for (auto [a_in, v_in, out_gt, shape_a, shape_v] : test_data) { + auto a = mk_array(a_in, shape_a); + auto v = mk_array(v_in, shape_v); + auto out = convolve(a, v); + check_array(out, out_gt, shape_a); + debug_array(out, false); + } +} + +TEST(Convolve, test_double) +{ + for (auto [a_in, v_in, out_gt, shape_a, shape_v] : test_data) { + auto a = mk_array(as_type_vector(a_in), shape_a); + auto v = mk_array(as_type_vector(v_in), shape_v); + auto out = convolve(a, v); + check_array(out, as_type_vector(out_gt), shape_a); + debug_array(out, false); + } +} + +TEST(Convolve, test_ndim) +{ + std::vector shape; + std::vector filter_shape; + for (int32_t ndim = 1; ndim <= LEGATE_MAX_DIM; ++ndim) { + shape.emplace_back(5); + filter_shape.emplace_back(3); + auto a_in = mk_seq_vector(shape); + auto v_in = mk_seq_vector(filter_shape, 0, 0); + v_in[v_in.size() / 2] = 1; + auto a = mk_array(a_in, shape); + auto v = mk_array(v_in, filter_shape); + if (ndim <= 3) { + auto out = convolve(a, v); + check_array(out, a_in, shape); + debug_array(out, false); + } else { + EXPECT_ANY_THROW(convolve(a, v)); + } + } +} + +} // namespace From 13b1b7fbb5478d249dbb6eca3a318ed35ec6bf01 Mon Sep 17 00:00:00 2001 From: Manolis Papadakis Date: Fri, 1 Mar 2024 09:29:47 -0800 Subject: [PATCH 163/462] Cache Stores created for scalar EagerArrays (#119) * Make sure all thunk methods only modify the "self" argument * Don't create intermediate Store for scalar fills * Cache immutable scalar DeferredArrays * Better way to describe ways of attaching to NumPy arrays * Convert EagerArray non-write-through arguments in read-only mode To enable caching of arrays made for constants. * Fix tests * Merge artifact * Address review comments * Missing import --- cunumeric/_array/array.py | 7 +- cunumeric/_module/math_misc.py | 2 +- cunumeric/config.py | 7 ++ cunumeric/deferred.py | 58 +++++++------- cunumeric/eager.py | 133 ++++++++++++++++++--------------- cunumeric/runtime.py | 111 +++++++++++++++++++-------- cunumeric/thunk.py | 4 +- 7 files changed, 197 insertions(+), 125 deletions(-) diff --git a/cunumeric/_array/array.py b/cunumeric/_array/array.py index 80efa56c61..4a9ee974fa 100644 --- a/cunumeric/_array/array.py +++ b/cunumeric/_array/array.py @@ -35,6 +35,7 @@ FFTNormalization, FFTType, ScanCode, + TransferType, UnaryOpCode, UnaryRedCode, ) @@ -120,7 +121,7 @@ def __init__( order=order, ) self._thunk = runtime.find_or_create_array_thunk( - np_array, share=False + np_array, TransferType.SHARE ) else: # Filter the inputs if necessary @@ -146,7 +147,9 @@ def __legate_data_interface__(self) -> dict[str, Any]: if self._legate_data is None: # If the thunk is an eager array, we need to convert it to a # deferred array so we can extract a legate store - deferred_thunk = runtime.to_deferred_array(self._thunk) + deferred_thunk = runtime.to_deferred_array( + self._thunk, read_only=False + ) # We don't have nullable data for the moment # until we support masked arrays dtype = deferred_thunk.base.type diff --git a/cunumeric/_module/math_misc.py b/cunumeric/_module/math_misc.py index e49f551dd9..d29b1e8188 100644 --- a/cunumeric/_module/math_misc.py +++ b/cunumeric/_module/math_misc.py @@ -91,7 +91,7 @@ def convolve(a: ndarray, v: ndarray, mode: ConvolveMode = "full") -> ndarray: dtype=a.dtype, inputs=(a, v), ) - a._thunk.convolve(v._thunk, out._thunk, mode) + out._thunk.convolve(a._thunk, v._thunk, mode) return out diff --git a/cunumeric/config.py b/cunumeric/config.py index 734972f8a8..29e79cd4f6 100644 --- a/cunumeric/config.py +++ b/cunumeric/config.py @@ -647,6 +647,13 @@ class BitGeneratorDistribution(IntEnum): NEGATIVE_BINOMIAL = _cunumeric.CUNUMERIC_BITGENDIST_NEGATIVE_BINOMIAL +@unique +class TransferType(IntEnum): + DONATE = 0 + MAKE_COPY = 1 + SHARE = 2 + + # Match these to fftType in fft_util.h class FFTType: def __init__( diff --git a/cunumeric/deferred.py b/cunumeric/deferred.py index b9bb519b82..08ebc90292 100644 --- a/cunumeric/deferred.py +++ b/cunumeric/deferred.py @@ -106,6 +106,9 @@ def auto_convert( ) -> Callable[[Callable[P, R]], Callable[P, R]]: """ Converts all named parameters to DeferredArrays. + + This function makes an immutable copy of any parameter that wasn't already + a DeferredArray. """ keys = OrderedSet(thunk_params) assert len(keys) == len(thunk_params) @@ -126,14 +129,14 @@ def decorator(func: Callable[P, R]) -> Callable[P, R]: def wrapper(*args: Any, **kwargs: Any) -> R: # Convert relevant arguments to DeferredArrays args = tuple( - runtime.to_deferred_array(arg) + runtime.to_deferred_array(arg, read_only=True) if idx in indices and arg is not None else arg for (idx, arg) in enumerate(args) ) for k, v in kwargs.items(): if k in keys and v is not None: - kwargs[k] = runtime.to_deferred_array(v) + kwargs[k] = runtime.to_deferred_array(v, read_only=True) return func(*args, **kwargs) @@ -365,7 +368,7 @@ def _zip_indices( new_arrays: tuple[Any, ...] = tuple() # check array's type and convert them to deferred arrays for a in arrays: - a = runtime.to_deferred_array(a) + a = runtime.to_deferred_array(a, read_only=True) data_type = a.dtype if data_type != np.int64: raise TypeError("index arrays should be int64 type") @@ -586,7 +589,7 @@ def _advanced_indexing_with_boolean_array( ) -> tuple[bool, Any, Any, Any]: rhs = self if not isinstance(key, DeferredArray): - key = runtime.to_deferred_array(key) + key = runtime.to_deferred_array(key, read_only=True) # in case when boolean array is passed as an index, shape for all # its dimensions should be the same as the shape of @@ -783,8 +786,8 @@ def _create_indexing_array( elif isinstance(k, slice): k, store = self._slice_store(k, store, dim + shift) elif isinstance(k, NumPyThunk): - if not isinstance(computed_key, DeferredArray): - k = runtime.to_deferred_array(k) + if not isinstance(k, DeferredArray): + k = runtime.to_deferred_array(k, read_only=True) if k.dtype == bool: for i in range(k.ndim): if k.shape[i] != store.shape[dim + i + shift]: @@ -1058,7 +1061,7 @@ def reshape(self, newshape: NdShape, order: OrderType) -> NumPyThunk: result_array = numpy_array.reshape(newshape, order=order).copy() result = runtime.get_numpy_thunk(result_array) - return runtime.to_deferred_array(result) + return runtime.to_deferred_array(result, read_only=True) if self.shape == newshape: return self @@ -1299,24 +1302,20 @@ def convert( task.execute() - @auto_convert("v", "lhs") - def convolve(self, v: Any, lhs: Any, mode: ConvolveMode) -> None: - input = self.base - filter = v.base - output = lhs.base - + @auto_convert("input", "filter") + def convolve(self, input: Any, filter: Any, mode: ConvolveMode) -> None: task = legate_runtime.create_auto_task( self.library, CuNumericOpCode.CONVOLVE ) offsets = tuple((ext + 1) // 2 for ext in filter.shape) - p_out = task.add_output(output) - p_filter = task.add_input(filter) - p_in = task.add_input(input) + p_out = task.add_output(self.base) + p_filter = task.add_input(filter.base) + p_in = task.add_input(input.base) p_halo = task.declare_partition() - task.add_input(input, p_halo) - task.add_scalar_arg(self.shape, (ty.int64,)) + task.add_input(input.base, p_halo) + task.add_scalar_arg(input.shape, (ty.int64,)) task.add_constraint(align(p_out, p_in)) task.add_constraint(bloat(p_out, p_halo, offsets, offsets)) @@ -1338,7 +1337,9 @@ def fft( lhs_eager = runtime.to_eager_array(lhs) rhs_eager = runtime.to_eager_array(rhs) lhs_eager.fft(rhs_eager, axes, kind, direction) - lhs.base = runtime.to_deferred_array(lhs_eager).base + lhs.base = runtime.to_deferred_array( + lhs_eager, read_only=True + ).base else: input = rhs.base output = lhs.base @@ -1662,8 +1663,10 @@ def add_mode( # Create array from input array and indices def choose(self, rhs: Any, *args: Any) -> None: # convert all arrays to deferred - index_arr = runtime.to_deferred_array(rhs) - ch_def = tuple(runtime.to_deferred_array(c) for c in args) + index_arr = runtime.to_deferred_array(rhs, read_only=True) + ch_def = tuple( + runtime.to_deferred_array(c, read_only=True) for c in args + ) out_arr = self.base # broadcast input array and all choices arrays to the same shape @@ -1687,8 +1690,12 @@ def select( choicelist: Iterable[Any], default: npt.NDArray[Any], ) -> None: - condlist_ = tuple(runtime.to_deferred_array(c) for c in condlist) - choicelist_ = tuple(runtime.to_deferred_array(c) for c in choicelist) + condlist_ = tuple( + runtime.to_deferred_array(c, read_only=True) for c in condlist + ) + choicelist_ = tuple( + runtime.to_deferred_array(c, read_only=True) for c in choicelist + ) task = legate_runtime.create_auto_task( self.library, CuNumericOpCode.SELECT @@ -1952,7 +1959,7 @@ def repeat( task.add_scalar_arg(repeats, ty.int64) else: shape = self.shape - repeats = runtime.to_deferred_array(repeats).base + repeats = runtime.to_deferred_array(repeats, read_only=True).base for dim, extent in enumerate(shape): if dim == axis: continue @@ -3086,7 +3093,8 @@ def unary_op( if multiout is not None: for out in multiout: - p_out = task.add_output(out.base) + out_def = runtime.to_deferred_array(out, read_only=False) + p_out = task.add_output(out_def.base) task.add_constraint(align(p_out, p_rhs)) task.execute() diff --git a/cunumeric/eager.py b/cunumeric/eager.py index eb97e2a2dc..27f8e6b634 100644 --- a/cunumeric/eager.py +++ b/cunumeric/eager.py @@ -27,7 +27,7 @@ ) import numpy as np -from legate.core import Scalar, get_legate_runtime +from legate.core import Scalar from .config import ( FFT_C2R, @@ -38,6 +38,7 @@ ConvertCode, FFTDirection, ScanCode, + TransferType, UnaryOpCode, UnaryRedCode, WindowOpCode, @@ -45,7 +46,7 @@ from .deferred import DeferredArray from .runtime import runtime from .thunk import NumPyThunk -from .utils import is_advanced_indexing, is_supported_type, to_core_dtype +from .utils import is_advanced_indexing if TYPE_CHECKING: import numpy.typing as npt @@ -219,10 +220,11 @@ class EagerArray(NumPyThunk): def __init__( self, - array: npt.NDArray[Any], + val: npt.ArrayLike, parent: Optional[EagerArray] = None, key: Optional[tuple[Any, ...]] = None, ) -> None: + array = np.asarray(val) super().__init__(array.dtype) self.array: npt.NDArray[Any] = array self.parent: Optional[EagerArray] = parent @@ -230,7 +232,7 @@ def __init__( self.key: Optional[tuple[Any, ...]] = key #: if this ever becomes set (to a DeferredArray), we forward all #: operations to it - self.deferred: Optional[Union[DeferredArray, NumPyThunk]] = None + self.deferred: Optional[DeferredArray] = None self.escaped = False @property @@ -257,70 +259,77 @@ def check_eager_args(self, *args: Any) -> None: for arg in args: if runtime.is_eager_array(arg): if arg.deferred is not None: - self.to_deferred_array() + self.to_deferred_array(read_only=False) break elif runtime.is_deferred_array(arg): - self.to_deferred_array() + self.to_deferred_array(read_only=False) break elif arg is None or not isinstance(arg, NumPyThunk): pass else: raise RuntimeError("bad argument type") - def _convert_children(self) -> None: + def _convert_subtree(self) -> None: + assert self.deferred is None + if self.parent is None: + transfer = ( + TransferType.SHARE + if self.escaped + # We can donate the base array, since it hasn't escaped to the + # user, and we won't be using it anymore. + else TransferType.DONATE + ) + deferred = runtime.find_or_create_array_thunk( + self.array, transfer=transfer, defer=True + ) + else: + parent = self.parent.deferred + assert self.key is not None + func = getattr(parent, self.key[0]) + args = self.key[1:] + deferred = func(*args) + self.deferred = cast(DeferredArray, deferred) + for child in self.children: + child._convert_subtree() + + def _convert_tree(self) -> None: """ - Traverse down our children and convert them to deferred arrays. + Convert the entire array tree to deferred arrays. + + We have to convert the whole tree when we convert even one node, to + make sure any future use of any array in the tree will go through the + deferred path, rather than use the original eager NumPy array, that we + donated. """ - assert runtime.is_deferred_array(self.deferred) - for child in self.children: - if child.deferred is None: - assert child.key is not None - func = getattr(self.deferred, child.key[0]) - args = child.key[1:] - child.deferred = func(*args) - # After we've made all the deferred views for each child then - # we can traverse down. Do it this way so we can get partition - # coalescing where possible - for child in self.children: - child._convert_children() - - def to_deferred_array(self) -> DeferredArray: - """This is a really important method. It will convert a tree of - eager NumPy arrays into an equivalent tree of deferred arrays that - are mirrored by an equivalent logical region tree. To be consistent - we always do this from the root, so once any array in the tree needs - to be converted then we do it for all of them. - :meta private: + if self.parent is None: + self._convert_subtree() + else: + self.parent._convert_tree() + + def to_deferred_array(self, read_only: bool) -> DeferredArray: """ - # Check to see if we already have our deferred array - # or whether we need to go up the tree to have it made - if self.deferred is None: - if self.parent is None: - assert is_supported_type(self.array.dtype) - # We are at the root of the tree so we need to - # actually make a DeferredArray to use - if self.array.size == 1: - legate_runtime = get_legate_runtime() - store = legate_runtime.create_store_from_scalar( - Scalar( - self.array.tobytes(), - to_core_dtype(self.array.dtype), - ), - shape=self.shape, - ) - self.deferred = DeferredArray(store) - else: - self.deferred = runtime.find_or_create_array_thunk( - self.array, - share=self.escaped, - defer=True, - ) - self._convert_children() - else: - # Traverse up the tree to make the deferred array - self.parent.to_deferred_array() - assert self.deferred is not None - return cast(DeferredArray, self.deferred) + Convert this EagerArray into a DeferredArray. + + If `read_only` is `False`, the EagerArray's buffer is donated to + initialize the DeferredArray, and the returned DeferredArray is used + in place of the EagerArray going forward. + """ + if self.deferred is not None: + return self.deferred + if read_only: + deferred = cast( + DeferredArray, + runtime.find_or_create_array_thunk( + self.array, + transfer=TransferType.MAKE_COPY, + read_only=True, + defer=True, + ), + ) + else: + self._convert_tree() + deferred = cast(DeferredArray, self.deferred) + return deferred def imag(self) -> NumPyThunk: if self.deferred is not None: @@ -338,17 +347,17 @@ def conj(self) -> NumPyThunk: return EagerArray(self.array.conj()) - def convolve(self, v: Any, out: Any, mode: ConvolveMode) -> None: - self.check_eager_args(v, out) + def convolve(self, input: Any, filter: Any, mode: ConvolveMode) -> None: + self.check_eager_args(input, filter) if self.deferred is not None: - self.deferred.convolve(v, out, mode) + self.deferred.convolve(input, filter, mode) else: if self.ndim == 1: - out.array = np.convolve(self.array, v.array, mode) + self.array[:] = np.convolve(input.array, filter.array, mode) else: from scipy.signal import convolve # type: ignore [import] - out.array = convolve(self.array, v.array, mode) + self.array[...] = convolve(input.array, filter.array, mode) def fft( self, diff --git a/cunumeric/runtime.py b/cunumeric/runtime.py index 0880718829..8d15b06bee 100644 --- a/cunumeric/runtime.py +++ b/cunumeric/runtime.py @@ -15,7 +15,7 @@ from __future__ import annotations import warnings -from functools import reduce +from functools import lru_cache, reduce from typing import TYPE_CHECKING, Any, Optional, Sequence, TypeGuard, Union import legate.core.types as ty @@ -27,9 +27,15 @@ BitGeneratorOperation, CuNumericOpCode, CuNumericTunable, + TransferType, cunumeric_lib, ) -from .utils import calculate_volume, find_last_user_stacklevel, to_core_dtype +from .utils import ( + calculate_volume, + find_last_user_stacklevel, + is_supported_type, + to_core_dtype, +) # We need to be careful about importing from other cunumeric modules here. The # runtime is global and used in many places, but also depends on many of the @@ -52,6 +58,25 @@ legate_runtime = get_legate_runtime() +def thunk_from_scalar( + bytes: bytes, shape: NdShape, dtype: np.dtype[Any] +) -> DeferredArray: + from .deferred import DeferredArray + + store = legate_runtime.create_store_from_scalar( + Scalar(bytes, to_core_dtype(dtype)), + shape=shape, + ) + return DeferredArray(store) + + +@lru_cache +def cached_thunk_from_scalar( + bytes: bytes, shape: NdShape, dtype: np.dtype[Any] +) -> DeferredArray: + return thunk_from_scalar(bytes, shape, dtype) + + class Runtime(object): def __init__(self) -> None: self.library = legate_runtime.find_library(cunumeric_lib.name) @@ -272,7 +297,8 @@ def get_numpy_thunk( # We can't attach NumPy ndarrays in shared mode unless they are # writeable share = share and obj.flags["W"] - return self.find_or_create_array_thunk(obj, share=share) + transfer = TransferType.SHARE if share else TransferType.MAKE_COPY + return self.find_or_create_array_thunk(obj, transfer) @staticmethod def compute_parent_child_mapping( @@ -346,27 +372,32 @@ def compute_parent_child_mapping( return key def find_or_create_array_thunk( - self, array: npt.NDArray[Any], share: bool = False, defer: bool = False + self, + array: npt.NDArray[Any], + transfer: TransferType, + read_only: bool = False, + defer: bool = False, ) -> NumPyThunk: from .deferred import DeferredArray assert isinstance(array, np.ndarray) + if not is_supported_type(array.dtype): + raise TypeError(f"cuNumeric does not support dtype={array.dtype}") + # We have to be really careful here to handle the case of # aliased numpy arrays that are passed in from the application # In case of aliasing we need to make sure that they are # mapped to the same logical region. The way we handle this # is to always create the thunk for the root array and # then create sub-thunks that mirror the array views - if array.base is not None and isinstance(array.base, np.ndarray): + if ( + transfer == TransferType.SHARE + and array.base is not None + and isinstance(array.base, np.ndarray) + ): key = self.compute_parent_child_mapping(array) if key is None: # This base array wasn't made with a view - if not share: - return self.find_or_create_array_thunk( - array.copy(), - share=False, - defer=defer, - ) raise NotImplementedError( "cuNumeric does not currently know " + "how to attach to array views that are not affine " @@ -374,43 +405,53 @@ def find_or_create_array_thunk( ) parent_thunk = self.find_or_create_array_thunk( array.base, - share=share, - defer=defer, + transfer, + read_only, + defer, ) - # Don't store this one in the ptr_to_thunk as we only want to - # store the root ones return parent_thunk.get_item(key) # Once it's a normal numpy array we can make it into one of our arrays # Check to see if it is a type that we support for doing deferred # execution and big enough to be worth off-loading onto Legion - dtype = to_core_dtype(array.dtype) if defer or not self.is_eager_shape(array.shape): - if array.size == 1 and not share: - # This is a single value array - # We didn't attach to this so we don't need to save it - store = legate_runtime.create_store_from_scalar( - Scalar(array.tobytes(), dtype), - shape=array.shape, - ) - return DeferredArray(store) + if array.size == 1 and transfer != TransferType.SHARE: + # This is a single value array that we're not attaching to. + # We cache these, but only if the user has promised not to + # write-through them. + # TODO(mpapadakis): Also mark the Store as read-only, whenever + # the Core supports that. + if read_only: + return cached_thunk_from_scalar( + array.tobytes(), array.shape, array.dtype + ) + else: + return thunk_from_scalar( + array.tobytes(), array.shape, array.dtype + ) - # This is not a scalar so make a field + # This is not a scalar so make a field. + # We won't try to cache these bigger arrays. store = legate_runtime.create_store_from_buffer( - dtype, + to_core_dtype(array.dtype), array.shape, - array, - not share, # read_only + array.copy() if transfer == TransferType.MAKE_COPY else array, + # This argument should really be called "donate" + read_only=(transfer != TransferType.SHARE), ) return DeferredArray( store, - numpy_array=array if share else None, + numpy_array=( + array if transfer == TransferType.SHARE else None + ), ) from .eager import EagerArray - # Make this into an eager evaluated thunk - return EagerArray(array) + # Make this into an eagerly evaluated thunk + return EagerArray( + array.copy() if transfer == TransferType.MAKE_COPY else array + ) def create_empty_thunk( self, @@ -511,11 +552,15 @@ def to_eager_array(self, array: NumPyThunk) -> EagerArray: else: raise RuntimeError("invalid array type") - def to_deferred_array(self, array: NumPyThunk) -> DeferredArray: + def to_deferred_array( + self, + array: NumPyThunk, + read_only: bool, + ) -> DeferredArray: if self.is_deferred_array(array): return array elif self.is_eager_array(array): - return array.to_deferred_array() + return array.to_deferred_array(read_only) else: raise RuntimeError("invalid array type") diff --git a/cunumeric/thunk.py b/cunumeric/thunk.py index c99c7b0a4b..daf6829892 100644 --- a/cunumeric/thunk.py +++ b/cunumeric/thunk.py @@ -93,13 +93,13 @@ def conj(self) -> NumPyThunk: ... @abstractmethod - def convolve(self, v: Any, out: Any, mode: ConvolveMode) -> None: + def convolve(self, input: Any, filter: Any, mode: ConvolveMode) -> None: ... @abstractmethod def fft( self, - out: Any, + rhs: Any, axes: Sequence[int], kind: FFTType, direction: FFTDirection, From 1d57d87512c34470575bbe1af1893fac30ad0c8f Mon Sep 17 00:00:00 2001 From: Manolis Papadakis Date: Fri, 1 Mar 2024 17:06:37 -0800 Subject: [PATCH 164/462] Use new equal_storage check to avoid redundant copies (#131) * Use new equal_storage check to avoid redundant copies * Have to bump the legate.core hash to get the fix --- cmake/versions.json | 2 +- cunumeric/deferred.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/cmake/versions.json b/cmake/versions.json index c829cc4f02..123ef63d2d 100644 --- a/cmake/versions.json +++ b/cmake/versions.json @@ -7,7 +7,7 @@ "git_url" : "https://github.com/nv-legate/legate.core.internal.git", "git_shallow": false, "always_download": false, - "git_tag" : "5f3b74fbfa1f3a367badad94e59a6c8a573ba034" + "git_tag" : "2d257d20acfe034337a674f689d17488d6b2ba06" } } } diff --git a/cunumeric/deferred.py b/cunumeric/deferred.py index 08ebc90292..b8656c08f2 100644 --- a/cunumeric/deferred.py +++ b/cunumeric/deferred.py @@ -1034,7 +1034,7 @@ def set_item(self, key: Any, rhs: Any) -> None: # NOTE: Neither Store nor Storage have an __eq__, so we can # only check that the underlying RegionField/Future corresponds # to the same Legion handle. - if view.base == rhs.base: + if view.base.equal_storage(rhs.base): return view.copy(rhs, deep=False) From b8688e8052cd83989c03ddfbe5e6d4d52ea19c26 Mon Sep 17 00:00:00 2001 From: Marcin Zalewski Date: Fri, 1 Mar 2024 17:25:20 -0800 Subject: [PATCH 165/462] Update cehckout action version (#129) --- .github/workflows/merge-ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/merge-ci.yml b/.github/workflows/merge-ci.yml index b193c15074..105987af43 100644 --- a/.github/workflows/merge-ci.yml +++ b/.github/workflows/merge-ci.yml @@ -11,7 +11,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Cunumeric Internal repository - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: token: ${{ secrets.NV_LEGATE_INTER_REPOS_ACCESS }} From 2ad4602a46be8c93a50b8c8a92de9567000159f3 Mon Sep 17 00:00:00 2001 From: Bryan Van de Ven Date: Mon, 4 Mar 2024 17:40:56 -0800 Subject: [PATCH 166/462] More top-level cleanup (#133) * split off thunks to sub-package * more top-level cleanup * add missing __init__ * fix up unit tests --- cunumeric/__init__.py | 2 +- cunumeric/_array/array.py | 15 +-- cunumeric/_array/thunk.py | 2 +- cunumeric/_array/util.py | 7 + cunumeric/{ => _module}/_unary_red_utils.py | 2 +- cunumeric/_module/indexing.py | 2 +- cunumeric/_module/linalg_mvp.py | 7 +- cunumeric/_module/math_sum_prod_diff.py | 2 +- cunumeric/_sphinxext/_comparison_util.py | 2 +- cunumeric/_sphinxext/implemented_index.py | 2 +- cunumeric/_thunk/__init__.py | 15 +++ cunumeric/{ => _thunk}/_sort.py | 6 +- cunumeric/{ => _thunk}/deferred.py | 18 +-- cunumeric/{ => _thunk}/eager.py | 14 +- cunumeric/{ => _thunk}/thunk.py | 8 +- cunumeric/_utils/__init__.py | 15 +++ cunumeric/_utils/array.py | 70 ++++++++++ cunumeric/{ => _utils}/coverage.py | 9 +- cunumeric/{utils.py => _utils/linalg.py} | 120 +---------------- cunumeric/_utils/stack.py | 52 ++++++++ cunumeric/_utils/structure.py | 37 ++++++ cunumeric/fft/__init__.py | 2 +- cunumeric/linalg/__init__.py | 2 +- cunumeric/linalg/_cholesky.py | 2 +- cunumeric/linalg/_solve.py | 4 +- cunumeric/ma/__init__.py | 2 +- cunumeric/ma/_masked_array.py | 2 +- cunumeric/random/__init__.py | 2 +- cunumeric/random/_random.py | 2 +- cunumeric/runtime.py | 38 +++--- tests/integration/test_data_interface.py | 2 +- tests/integration/test_dot.py | 2 +- tests/integration/test_index_routines.py | 2 +- tests/integration/test_inner.py | 2 +- tests/integration/test_matmul.py | 2 +- tests/integration/test_tensordot.py | 2 +- tests/unit/cunumeric/test_utils_array.py | 101 ++++++++++++++ ...est_coverage.py => test_utils_coverage.py} | 2 +- .../{test_utils.py => test_utils_linalg.py} | 123 +----------------- tests/unit/cunumeric/test_utils_stack.py | 70 ++++++++++ 40 files changed, 450 insertions(+), 321 deletions(-) rename cunumeric/{ => _module}/_unary_red_utils.py (98%) create mode 100644 cunumeric/_thunk/__init__.py rename cunumeric/{ => _thunk}/_sort.py (97%) rename cunumeric/{ => _thunk}/deferred.py (99%) rename cunumeric/{ => _thunk}/eager.py (99%) rename cunumeric/{ => _thunk}/thunk.py (99%) create mode 100644 cunumeric/_utils/__init__.py create mode 100644 cunumeric/_utils/array.py rename cunumeric/{ => _utils}/coverage.py (98%) rename cunumeric/{utils.py => _utils/linalg.py} (54%) create mode 100644 cunumeric/_utils/stack.py create mode 100644 cunumeric/_utils/structure.py create mode 100644 tests/unit/cunumeric/test_utils_array.py rename tests/unit/cunumeric/{test_coverage.py => test_utils_coverage.py} (99%) rename tests/unit/cunumeric/{test_utils.py => test_utils_linalg.py} (61%) create mode 100644 tests/unit/cunumeric/test_utils_stack.py diff --git a/cunumeric/__init__.py b/cunumeric/__init__.py index 434a6caefd..3df52a64ca 100644 --- a/cunumeric/__init__.py +++ b/cunumeric/__init__.py @@ -31,7 +31,7 @@ from ._array.util import maybe_convert_to_np_ndarray from ._module import * from ._ufunc import * -from .coverage import clone_module +from ._utils.coverage import clone_module clone_module(_np, globals(), maybe_convert_to_np_ndarray) diff --git a/cunumeric/_array/array.py b/cunumeric/_array/array.py index 4a9ee974fa..a2f41e3cc3 100644 --- a/cunumeric/_array/array.py +++ b/cunumeric/_array/array.py @@ -30,6 +30,10 @@ ) from .. import _ufunc +from .._utils.array import calculate_volume, to_core_dtype +from .._utils.coverage import FALLBACK_WARNING, clone_class, is_implemented +from .._utils.linalg import dot_modes +from .._utils.structure import deep_apply from ..config import ( FFTDirection, FFTNormalization, @@ -39,16 +43,8 @@ UnaryOpCode, UnaryRedCode, ) -from ..coverage import FALLBACK_WARNING, clone_class, is_implemented from ..runtime import runtime from ..types import NdShape -from ..utils import ( - calculate_volume, - deep_apply, - dot_modes, - to_core_dtype, - tuple_pop, -) from .flags import flagsobj from .thunk import perform_scan, perform_unary_op, perform_unary_reduction from .util import ( @@ -58,6 +54,7 @@ convert_to_cunumeric_ndarray, maybe_convert_to_np_ndarray, sanitize_shape, + tuple_pop, ) if TYPE_CHECKING: @@ -65,7 +62,7 @@ import numpy.typing as npt - from ..thunk import NumPyThunk + from .._thunk.thunk import NumPyThunk from ..types import ( BoundsMode, CastingKind, diff --git a/cunumeric/_array/thunk.py b/cunumeric/_array/thunk.py index b7784b2b1c..a998609df1 100644 --- a/cunumeric/_array/thunk.py +++ b/cunumeric/_array/thunk.py @@ -38,7 +38,7 @@ if TYPE_CHECKING: import numpy.typing as npt - from ..thunk import NumPyThunk + from .._thunk.thunk import NumPyThunk from .array import ndarray diff --git a/cunumeric/_array/util.py b/cunumeric/_array/util.py index c129e3f6cf..806d4a4c7d 100644 --- a/cunumeric/_array/util.py +++ b/cunumeric/_array/util.py @@ -212,3 +212,10 @@ def find_common_type(*args: ndarray) -> np.dtype[Any]: else: array_types.append(array.dtype) return np.result_type(*array_types, *scalars) + + +T = TypeVar("T") + + +def tuple_pop(tup: tuple[T, ...], index: int) -> tuple[T, ...]: + return tup[:index] + tup[index + 1 :] diff --git a/cunumeric/_unary_red_utils.py b/cunumeric/_module/_unary_red_utils.py similarity index 98% rename from cunumeric/_unary_red_utils.py rename to cunumeric/_module/_unary_red_utils.py index c115b50d68..02f2da5fd6 100644 --- a/cunumeric/_unary_red_utils.py +++ b/cunumeric/_module/_unary_red_utils.py @@ -15,7 +15,7 @@ from __future__ import annotations -from .config import UnaryRedCode +from ..config import UnaryRedCode # corresponding non-nan unary reduction ops for nan unary reduction ops _EQUIVALENT_NON_NAN_OPS: dict[UnaryRedCode, UnaryRedCode] = { diff --git a/cunumeric/_module/indexing.py b/cunumeric/_module/indexing.py index 5e96f58190..d7ed058101 100644 --- a/cunumeric/_module/indexing.py +++ b/cunumeric/_module/indexing.py @@ -27,7 +27,7 @@ check_writeable, convert_to_cunumeric_ndarray, ) -from ..coverage import is_implemented +from .._utils.coverage import is_implemented from ..runtime import runtime from ..types import NdShape from .array_joining import hstack diff --git a/cunumeric/_module/linalg_mvp.py b/cunumeric/_module/linalg_mvp.py index 1209da504a..894dd3e150 100644 --- a/cunumeric/_module/linalg_mvp.py +++ b/cunumeric/_module/linalg_mvp.py @@ -29,8 +29,13 @@ find_common_type, ) from .._ufunc.math import multiply +from .._utils.linalg import ( + AxesPairLike, + inner_modes, + matmul_modes, + tensordot_modes, +) from ..types import NdShape -from ..utils import AxesPairLike, inner_modes, matmul_modes, tensordot_modes from .creation_data import copy if TYPE_CHECKING: diff --git a/cunumeric/_module/math_sum_prod_diff.py b/cunumeric/_module/math_sum_prod_diff.py index 8d0d8abe9c..50f32f7d3c 100644 --- a/cunumeric/_module/math_sum_prod_diff.py +++ b/cunumeric/_module/math_sum_prod_diff.py @@ -22,9 +22,9 @@ from .._array.util import add_boilerplate from .._ufunc.floating import isnan from .._ufunc.math import add, multiply -from .._unary_red_utils import get_non_nan_unary_red_code from ..config import ScanCode, UnaryRedCode from ..settings import settings as cunumeric_settings +from ._unary_red_utils import get_non_nan_unary_red_code from .indexing import putmask from .logic_truth import all, any diff --git a/cunumeric/_sphinxext/_comparison_util.py b/cunumeric/_sphinxext/_comparison_util.py index 8e2adc6a8c..2587bed7b3 100644 --- a/cunumeric/_sphinxext/_comparison_util.py +++ b/cunumeric/_sphinxext/_comparison_util.py @@ -18,7 +18,7 @@ from types import ModuleType from typing import TYPE_CHECKING, Any, Iterable, Iterator, Type, Union -from ..coverage import is_implemented, is_multi, is_single +from .._utils.coverage import is_implemented, is_multi, is_single from ._comparison_config import MISSING_NP_REFS, SKIP if TYPE_CHECKING: diff --git a/cunumeric/_sphinxext/implemented_index.py b/cunumeric/_sphinxext/implemented_index.py index 3d70f763ba..57d9397cf5 100644 --- a/cunumeric/_sphinxext/implemented_index.py +++ b/cunumeric/_sphinxext/implemented_index.py @@ -22,7 +22,7 @@ import cunumeric as cn -from ..coverage import is_implemented +from .._utils.coverage import is_implemented from . import PARALLEL_SAFE, SphinxParallelSpec from ._cunumeric_directive import CunumericDirective diff --git a/cunumeric/_thunk/__init__.py b/cunumeric/_thunk/__init__.py new file mode 100644 index 0000000000..31d8d448c4 --- /dev/null +++ b/cunumeric/_thunk/__init__.py @@ -0,0 +1,15 @@ +# Copyright 2024 NVIDIA Corporation +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +from __future__ import annotations diff --git a/cunumeric/_sort.py b/cunumeric/_thunk/_sort.py similarity index 97% rename from cunumeric/_sort.py rename to cunumeric/_thunk/_sort.py index 055ec69838..c55edb1f73 100644 --- a/cunumeric/_sort.py +++ b/cunumeric/_thunk/_sort.py @@ -21,11 +21,11 @@ normalize_axis_index, ) -from .config import CuNumericOpCode -from .runtime import runtime +from ..config import CuNumericOpCode +from ..runtime import runtime if TYPE_CHECKING: - from .deferred import DeferredArray + from .._thunk.deferred import DeferredArray def sort_flattened( diff --git a/cunumeric/deferred.py b/cunumeric/_thunk/deferred.py similarity index 99% rename from cunumeric/deferred.py rename to cunumeric/_thunk/deferred.py index b8656c08f2..649255eecf 100644 --- a/cunumeric/deferred.py +++ b/cunumeric/_thunk/deferred.py @@ -52,8 +52,8 @@ normalize_axis_tuple, ) -from ._sort import sort_deferred -from .config import ( +from .._utils.array import is_advanced_indexing, to_core_dtype +from ..config import ( BinaryOpCode, BitGeneratorDistribution, BitGeneratorOperation, @@ -64,17 +64,17 @@ UnaryOpCode, UnaryRedCode, ) -from .linalg._cholesky import cholesky_deferred -from .linalg._solve import solve_deferred -from .runtime import runtime +from ..linalg._cholesky import cholesky_deferred +from ..linalg._solve import solve_deferred +from ..runtime import runtime +from ._sort import sort_deferred from .thunk import NumPyThunk -from .utils import is_advanced_indexing, to_core_dtype if TYPE_CHECKING: import numpy.typing as npt - from .config import BitGeneratorType, FFTDirection, FFTType, WindowOpCode - from .types import ( + from ..config import BitGeneratorType, FFTDirection, FFTType, WindowOpCode + from ..types import ( BitOrder, ConvolveMode, NdShape, @@ -378,7 +378,7 @@ def _zip_indices( # find a broadcasted shape for all arrays passed as indices shapes = tuple(a.shape for a in arrays) if len(arrays) > 1: - from ._module import broadcast_shapes + from .._module import broadcast_shapes b_shape = broadcast_shapes(*shapes) else: diff --git a/cunumeric/eager.py b/cunumeric/_thunk/eager.py similarity index 99% rename from cunumeric/eager.py rename to cunumeric/_thunk/eager.py index 27f8e6b634..7de73a100b 100644 --- a/cunumeric/eager.py +++ b/cunumeric/_thunk/eager.py @@ -29,7 +29,8 @@ import numpy as np from legate.core import Scalar -from .config import ( +from .._utils.array import is_advanced_indexing +from ..config import ( FFT_C2R, FFT_D2Z, FFT_R2C, @@ -43,16 +44,15 @@ UnaryRedCode, WindowOpCode, ) +from ..runtime import runtime from .deferred import DeferredArray -from .runtime import runtime from .thunk import NumPyThunk -from .utils import is_advanced_indexing if TYPE_CHECKING: import numpy.typing as npt - from .config import BitGeneratorType, FFTType - from .types import ( + from ..config import BitGeneratorType, FFTType + from ..types import ( BitOrder, ConvolveMode, NdShape, @@ -1666,7 +1666,7 @@ def cholesky(self, src: Any, no_tril: bool) -> None: try: result = np.linalg.cholesky(src.array) except np.linalg.LinAlgError as e: - from .linalg import LinAlgError + from ..linalg import LinAlgError raise LinAlgError(e) from e if no_tril: @@ -1681,7 +1681,7 @@ def solve(self, a: Any, b: Any) -> None: try: result = np.linalg.solve(a.array, b.array) except np.linalg.LinAlgError as e: - from .linalg import LinAlgError + from ..linalg import LinAlgError raise LinAlgError(e) from e self.array[:] = result diff --git a/cunumeric/thunk.py b/cunumeric/_thunk/thunk.py similarity index 99% rename from cunumeric/thunk.py rename to cunumeric/_thunk/thunk.py index daf6829892..580e5acca4 100644 --- a/cunumeric/thunk.py +++ b/cunumeric/_thunk/thunk.py @@ -17,15 +17,15 @@ from abc import ABC, abstractmethod, abstractproperty from typing import TYPE_CHECKING, Any, Iterable, Optional, Sequence, Union -from .config import ConvertCode -from .runtime import runtime +from ..config import ConvertCode +from ..runtime import runtime if TYPE_CHECKING: import numpy as np import numpy.typing as npt from legate.core import Scalar - from .config import ( + from ..config import ( BinaryOpCode, BitGeneratorType, FFTDirection, @@ -34,7 +34,7 @@ UnaryRedCode, WindowOpCode, ) - from .types import ( + from ..types import ( BitOrder, ConvolveMode, NdShape, diff --git a/cunumeric/_utils/__init__.py b/cunumeric/_utils/__init__.py new file mode 100644 index 0000000000..31d8d448c4 --- /dev/null +++ b/cunumeric/_utils/__init__.py @@ -0,0 +1,15 @@ +# Copyright 2024 NVIDIA Corporation +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +from __future__ import annotations diff --git a/cunumeric/_utils/array.py b/cunumeric/_utils/array.py new file mode 100644 index 0000000000..1ba3c6bfee --- /dev/null +++ b/cunumeric/_utils/array.py @@ -0,0 +1,70 @@ +# Copyright 2021-2022 NVIDIA Corporation +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +from __future__ import annotations + +from functools import reduce +from typing import Any + +import legate.core.types as ty +import numpy as np + +from ..types import NdShape + +SUPPORTED_DTYPES = { + np.dtype(np.bool_): ty.bool_, + np.dtype(np.int8): ty.int8, + np.dtype(np.int16): ty.int16, + np.dtype(np.int32): ty.int32, + np.dtype(np.int64): ty.int64, + np.dtype(np.uint8): ty.uint8, + np.dtype(np.uint16): ty.uint16, + np.dtype(np.uint32): ty.uint32, + np.dtype(np.uint64): ty.uint64, + np.dtype(np.float16): ty.float16, + np.dtype(np.float32): ty.float32, + np.dtype(np.float64): ty.float64, + np.dtype(np.complex64): ty.complex64, + np.dtype(np.complex128): ty.complex128, +} + + +def is_supported_type(dtype: str | np.dtype[Any]) -> bool: + return np.dtype(dtype) in SUPPORTED_DTYPES + + +def to_core_dtype(dtype: str | np.dtype[Any]) -> ty.Type: + core_dtype = SUPPORTED_DTYPES.get(np.dtype(dtype)) + if core_dtype is None: + raise TypeError(f"cuNumeric does not support dtype={dtype}") + return core_dtype + + +def is_advanced_indexing(key: Any) -> bool: + if key is Ellipsis or key is None: # np.newdim case + return False + if np.isscalar(key): + return False + if isinstance(key, slice): + return False + if isinstance(key, tuple): + return any(is_advanced_indexing(k) for k in key) + # Any other kind of thing leads to advanced indexing + return True + + +def calculate_volume(shape: NdShape) -> int: + if len(shape) == 0: + return 0 + return reduce(lambda x, y: x * y, shape) diff --git a/cunumeric/coverage.py b/cunumeric/_utils/coverage.py similarity index 98% rename from cunumeric/coverage.py rename to cunumeric/_utils/coverage.py index 916fba7b7e..ce436b17c2 100644 --- a/cunumeric/coverage.py +++ b/cunumeric/_utils/coverage.py @@ -39,9 +39,10 @@ from legate.core import track_provenance from legate.core.utils import OrderedSet -from .runtime import runtime -from .settings import settings -from .utils import deep_apply, find_last_user_frames, find_last_user_stacklevel +from ..runtime import runtime +from ..settings import settings +from .stack import find_last_user_frames, find_last_user_stacklevel +from .structure import deep_apply __all__ = ("clone_module", "clone_class") @@ -251,7 +252,7 @@ def clone_module( reporting = settings.report_coverage() - from ._ufunc.ufunc import ufunc as lgufunc + from .._ufunc.ufunc import ufunc as lgufunc for attr, value in new_globals.items(): # Only need to wrap things that are in the origin module to begin with diff --git a/cunumeric/utils.py b/cunumeric/_utils/linalg.py similarity index 54% rename from cunumeric/utils.py rename to cunumeric/_utils/linalg.py index 9fd4c4b30d..b90ac0b473 100644 --- a/cunumeric/utils.py +++ b/cunumeric/_utils/linalg.py @@ -1,4 +1,4 @@ -# Copyright 2021-2022 NVIDIA Corporation +# Copyright 2024 NVIDIA Corporation # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -14,107 +14,11 @@ # from __future__ import annotations -import traceback -from functools import reduce from string import ascii_lowercase, ascii_uppercase -from types import FrameType -from typing import Any, Callable, List, Sequence, Tuple, TypeVar, Union +from typing import List, Sequence, Tuple, Union -import legate.core.types as ty -import numpy as np from legate.core.utils import OrderedSet -from .types import NdShape - -SUPPORTED_DTYPES = { - np.dtype(np.bool_): ty.bool_, - np.dtype(np.int8): ty.int8, - np.dtype(np.int16): ty.int16, - np.dtype(np.int32): ty.int32, - np.dtype(np.int64): ty.int64, - np.dtype(np.uint8): ty.uint8, - np.dtype(np.uint16): ty.uint16, - np.dtype(np.uint32): ty.uint32, - np.dtype(np.uint64): ty.uint64, - np.dtype(np.float16): ty.float16, - np.dtype(np.float32): ty.float32, - np.dtype(np.float64): ty.float64, - np.dtype(np.complex64): ty.complex64, - np.dtype(np.complex128): ty.complex128, -} - - -def is_supported_type(dtype: Union[str, np.dtype[Any]]) -> bool: - return np.dtype(dtype) in SUPPORTED_DTYPES - - -def to_core_dtype(dtype: Union[str, np.dtype[Any]]) -> ty.Type: - core_dtype = SUPPORTED_DTYPES.get(np.dtype(dtype)) - if core_dtype is None: - raise TypeError(f"cuNumeric does not support dtype={dtype}") - return core_dtype - - -def is_advanced_indexing(key: Any) -> bool: - if key is Ellipsis or key is None: # np.newdim case - return False - if np.isscalar(key): - return False - if isinstance(key, slice): - return False - if isinstance(key, tuple): - return any(is_advanced_indexing(k) for k in key) - # Any other kind of thing leads to advanced indexing - return True - - -def find_last_user_stacklevel() -> int: - stacklevel = 1 - for frame, _ in traceback.walk_stack(None): - if not frame.f_globals["__name__"].startswith("cunumeric"): - break - stacklevel += 1 - return stacklevel - - -def get_line_number_from_frame(frame: FrameType) -> str: - return f"{frame.f_code.co_filename}:{frame.f_lineno}" - - -def find_last_user_frames(top_only: bool = True) -> str: - for last, _ in traceback.walk_stack(None): - if "__name__" not in last.f_globals: - continue - name = last.f_globals["__name__"] - if not any(name.startswith(pkg) for pkg in ("cunumeric", "legate")): - break - - if top_only: - return get_line_number_from_frame(last) - - frames: list[FrameType] = [] - curr: Union[FrameType, None] = last - while curr is not None: - if "legion_top.py" in curr.f_code.co_filename: - break - frames.append(curr) - curr = curr.f_back - return "|".join(get_line_number_from_frame(f) for f in frames) - - -def calculate_volume(shape: NdShape) -> int: - if len(shape) == 0: - return 0 - return reduce(lambda x, y: x * y, shape) - - -T = TypeVar("T") - - -def tuple_pop(tup: Tuple[T, ...], index: int) -> Tuple[T, ...]: - return tup[:index] + tup[index + 1 :] - - Modes = Tuple[List[str], List[str], List[str]] @@ -229,23 +133,3 @@ def check_axes(a_axes: Axes, b_axes: Axes) -> None: ] return (a_modes, b_modes, a_out + b_out) - - -def deep_apply(obj: Any, func: Callable[[Any], Any]) -> Any: - """ - Apply the provided function to objects contained at any depth within a data - structure. - - This function will recurse over arbitrary nestings of lists, tuples and - dicts. This recursion logic is rather limited, but this function is - primarily meant to be used for arguments of NumPy API calls, which - shouldn't nest their arrays very deep. - """ - if isinstance(obj, list): - return [deep_apply(x, func) for x in obj] - elif isinstance(obj, tuple): - return tuple(deep_apply(x, func) for x in obj) - elif isinstance(obj, dict): - return {k: deep_apply(v, func) for k, v in obj.items()} - else: - return func(obj) diff --git a/cunumeric/_utils/stack.py b/cunumeric/_utils/stack.py new file mode 100644 index 0000000000..470cf77750 --- /dev/null +++ b/cunumeric/_utils/stack.py @@ -0,0 +1,52 @@ +# Copyright 2024 NVIDIA Corporation +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +from __future__ import annotations + +import traceback +from types import FrameType + + +def find_last_user_stacklevel() -> int: + stacklevel = 1 + for frame, _ in traceback.walk_stack(None): + if not frame.f_globals["__name__"].startswith("cunumeric"): + break + stacklevel += 1 + return stacklevel + + +def get_line_number_from_frame(frame: FrameType) -> str: + return f"{frame.f_code.co_filename}:{frame.f_lineno}" + + +def find_last_user_frames(top_only: bool = True) -> str: + for last, _ in traceback.walk_stack(None): + if "__name__" not in last.f_globals: + continue + name = last.f_globals["__name__"] + if not any(name.startswith(pkg) for pkg in ("cunumeric", "legate")): + break + + if top_only: + return get_line_number_from_frame(last) + + frames: list[FrameType] = [] + curr: FrameType | None = last + while curr is not None: + if "legion_top.py" in curr.f_code.co_filename: + break + frames.append(curr) + curr = curr.f_back + return "|".join(get_line_number_from_frame(f) for f in frames) diff --git a/cunumeric/_utils/structure.py b/cunumeric/_utils/structure.py new file mode 100644 index 0000000000..4a251ae6c8 --- /dev/null +++ b/cunumeric/_utils/structure.py @@ -0,0 +1,37 @@ +# Copyright 2021-2022 NVIDIA Corporation +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +from __future__ import annotations + +from typing import Any, Callable + + +def deep_apply(obj: Any, func: Callable[[Any], Any]) -> Any: + """ + Apply the provided function to objects contained at any depth within a data + structure. + + This function will recurse over arbitrary nestings of lists, tuples and + dicts. This recursion logic is rather limited, but this function is + primarily meant to be used for arguments of NumPy API calls, which + shouldn't nest their arrays very deep. + """ + if isinstance(obj, list): + return [deep_apply(x, func) for x in obj] + elif isinstance(obj, tuple): + return tuple(deep_apply(x, func) for x in obj) + elif isinstance(obj, dict): + return {k: deep_apply(v, func) for k, v in obj.items()} + else: + return func(obj) diff --git a/cunumeric/fft/__init__.py b/cunumeric/fft/__init__.py index 2419dbbd79..9f0317bea3 100644 --- a/cunumeric/fft/__init__.py +++ b/cunumeric/fft/__init__.py @@ -17,7 +17,7 @@ import numpy.fft as _npfft from .._array.util import maybe_convert_to_np_ndarray -from ..coverage import clone_module +from .._utils.coverage import clone_module from .fft import * clone_module(_npfft, globals(), maybe_convert_to_np_ndarray) diff --git a/cunumeric/linalg/__init__.py b/cunumeric/linalg/__init__.py index d39cb94dd5..84ead03516 100644 --- a/cunumeric/linalg/__init__.py +++ b/cunumeric/linalg/__init__.py @@ -17,7 +17,7 @@ import numpy.linalg as _nplinalg from .._array.util import maybe_convert_to_np_ndarray -from ..coverage import clone_module +from .._utils.coverage import clone_module from .linalg import * clone_module(_nplinalg, globals(), maybe_convert_to_np_ndarray) diff --git a/cunumeric/linalg/_cholesky.py b/cunumeric/linalg/_cholesky.py index d0b29f1b61..8a613f2037 100644 --- a/cunumeric/linalg/_cholesky.py +++ b/cunumeric/linalg/_cholesky.py @@ -34,7 +34,7 @@ if TYPE_CHECKING: from legate.core import Library, LogicalStore, LogicalStorePartition - from ..deferred import DeferredArray + from .._thunk.deferred import DeferredArray from ..runtime import Runtime diff --git a/cunumeric/linalg/_solve.py b/cunumeric/linalg/_solve.py index b543eb28ab..da238cbefc 100644 --- a/cunumeric/linalg/_solve.py +++ b/cunumeric/linalg/_solve.py @@ -27,7 +27,7 @@ if TYPE_CHECKING: from legate.core import Library, LogicalStore - from ..deferred import DeferredArray + from .._thunk.deferred import DeferredArray def solve_single(library: Library, a: LogicalStore, b: LogicalStore) -> None: @@ -78,7 +78,7 @@ def mp_solve( def solve_deferred( output: DeferredArray, a: DeferredArray, b: DeferredArray ) -> None: - from ..deferred import DeferredArray + from .._thunk.deferred import DeferredArray library = output.library diff --git a/cunumeric/ma/__init__.py b/cunumeric/ma/__init__.py index ecda9aa586..6239cbfce0 100644 --- a/cunumeric/ma/__init__.py +++ b/cunumeric/ma/__init__.py @@ -17,7 +17,7 @@ import numpy.ma as _ma from .._array.util import maybe_convert_to_np_ndarray -from ..coverage import clone_module +from .._utils.coverage import clone_module from ._masked_array import MaskedArray masked_array = MaskedArray diff --git a/cunumeric/ma/_masked_array.py b/cunumeric/ma/_masked_array.py index ba3c5da385..0809f73532 100644 --- a/cunumeric/ma/_masked_array.py +++ b/cunumeric/ma/_masked_array.py @@ -24,7 +24,7 @@ import numpy as _np from .._array.util import maybe_convert_to_np_ndarray -from ..coverage import clone_class +from .._utils.coverage import clone_class NDARRAY_INTERNAL = { "__array_finalize__", diff --git a/cunumeric/random/__init__.py b/cunumeric/random/__init__.py index 50829b055f..d753ff9f06 100644 --- a/cunumeric/random/__init__.py +++ b/cunumeric/random/__init__.py @@ -17,7 +17,7 @@ import numpy.random as _nprandom from .._array.util import maybe_convert_to_np_ndarray -from ..coverage import clone_module +from .._utils.coverage import clone_module from ..runtime import runtime if runtime.has_curand: diff --git a/cunumeric/random/_random.py b/cunumeric/random/_random.py index 9b2c4c722b..f6c93f361a 100644 --- a/cunumeric/random/_random.py +++ b/cunumeric/random/_random.py @@ -19,7 +19,7 @@ import numpy as np from .._array.array import ndarray -from ..coverage import clone_class +from .._utils.coverage import clone_class from ..runtime import runtime from ._generator import default_rng, get_static_generator # NOQA diff --git a/cunumeric/runtime.py b/cunumeric/runtime.py index 8d15b06bee..c989ac3a09 100644 --- a/cunumeric/runtime.py +++ b/cunumeric/runtime.py @@ -23,6 +23,8 @@ from legate.core import LEGATE_MAX_DIM, Scalar, TaskTarget, get_legate_runtime from legate.settings import settings as legate_settings +from ._utils.array import calculate_volume, is_supported_type, to_core_dtype +from ._utils.stack import find_last_user_stacklevel from .config import ( BitGeneratorOperation, CuNumericOpCode, @@ -30,12 +32,6 @@ TransferType, cunumeric_lib, ) -from .utils import ( - calculate_volume, - find_last_user_stacklevel, - is_supported_type, - to_core_dtype, -) # We need to be careful about importing from other cunumeric modules here. The # runtime is global and used in many places, but also depends on many of the @@ -48,9 +44,9 @@ from legate.core import AutoTask, ManualTask from ._array.array import ndarray - from .deferred import DeferredArray - from .eager import EagerArray - from .thunk import NumPyThunk + from ._thunk.deferred import DeferredArray + from ._thunk.eager import EagerArray + from ._thunk.thunk import NumPyThunk from .types import NdShape DIMENSION = int @@ -61,7 +57,7 @@ def thunk_from_scalar( bytes: bytes, shape: NdShape, dtype: np.dtype[Any] ) -> DeferredArray: - from .deferred import DeferredArray + from ._thunk.deferred import DeferredArray store = legate_runtime.create_store_from_scalar( Scalar(bytes, to_core_dtype(dtype)), @@ -278,7 +274,7 @@ def get_numpy_thunk( "Array must be non-nullable and not nested" ) - from .deferred import DeferredArray + from ._thunk.deferred import DeferredArray return DeferredArray(array.data) # See if this is a normal numpy array @@ -378,7 +374,7 @@ def find_or_create_array_thunk( read_only: bool = False, defer: bool = False, ) -> NumPyThunk: - from .deferred import DeferredArray + from ._thunk.deferred import DeferredArray assert isinstance(array, np.ndarray) if not is_supported_type(array.dtype): @@ -446,7 +442,7 @@ def find_or_create_array_thunk( ), ) - from .eager import EagerArray + from ._thunk.eager import EagerArray # Make this into an eagerly evaluated thunk return EagerArray( @@ -459,7 +455,7 @@ def create_empty_thunk( dtype: ty.Type, inputs: Optional[Sequence[NumPyThunk]] = None, ) -> NumPyThunk: - from .deferred import DeferredArray + from ._thunk.deferred import DeferredArray if self.is_eager_shape(shape) and self.are_all_eager_inputs(inputs): return self.create_eager_thunk(shape, dtype.to_numpy_dtype()) @@ -474,14 +470,14 @@ def create_eager_thunk( shape: NdShape, dtype: np.dtype[Any], ) -> NumPyThunk: - from .eager import EagerArray + from ._thunk.eager import EagerArray return EagerArray(np.empty(shape, dtype=dtype)) def create_unbound_thunk( self, dtype: ty.Type, ndim: int = 1 ) -> DeferredArray: - from .deferred import DeferredArray + from ._thunk.deferred import DeferredArray store = legate_runtime.create_store(dtype, ndim=ndim) return DeferredArray(store) @@ -517,8 +513,8 @@ def is_eager_shape(self, shape: NdShape) -> bool: @staticmethod def are_all_eager_inputs(inputs: Optional[Sequence[NumPyThunk]]) -> bool: - from .eager import EagerArray - from .thunk import NumPyThunk + from ._thunk.eager import EagerArray + from ._thunk.thunk import NumPyThunk if inputs is None: return True @@ -530,7 +526,7 @@ def are_all_eager_inputs(inputs: Optional[Sequence[NumPyThunk]]) -> bool: @staticmethod def is_eager_array(array: NumPyThunk) -> TypeGuard[EagerArray]: - from .eager import EagerArray + from ._thunk.eager import EagerArray return isinstance(array, EagerArray) @@ -538,12 +534,12 @@ def is_eager_array(array: NumPyThunk) -> TypeGuard[EagerArray]: def is_deferred_array( array: Optional[NumPyThunk], ) -> TypeGuard[DeferredArray]: - from .deferred import DeferredArray + from ._thunk.deferred import DeferredArray return isinstance(array, DeferredArray) def to_eager_array(self, array: NumPyThunk) -> EagerArray: - from .eager import EagerArray + from ._thunk.eager import EagerArray if self.is_eager_array(array): return array diff --git a/tests/integration/test_data_interface.py b/tests/integration/test_data_interface.py index a3329a1b6e..4529513c51 100644 --- a/tests/integration/test_data_interface.py +++ b/tests/integration/test_data_interface.py @@ -16,7 +16,7 @@ import pytest import cunumeric as num -from cunumeric.utils import SUPPORTED_DTYPES +from cunumeric._utils.array import SUPPORTED_DTYPES DTYPES = SUPPORTED_DTYPES.keys() diff --git a/tests/integration/test_dot.py b/tests/integration/test_dot.py index 40769c3545..61e513bb39 100644 --- a/tests/integration/test_dot.py +++ b/tests/integration/test_dot.py @@ -19,7 +19,7 @@ from utils.generators import mk_0to1_array import cunumeric as num -from cunumeric.utils import dot_modes +from cunumeric._utils.linalg import dot_modes @pytest.mark.parametrize("b_ndim", range(LEGATE_MAX_DIM + 1)) diff --git a/tests/integration/test_index_routines.py b/tests/integration/test_index_routines.py index d1c42805f2..0f37409c63 100644 --- a/tests/integration/test_index_routines.py +++ b/tests/integration/test_index_routines.py @@ -21,7 +21,7 @@ from utils.generators import mk_seq_array import cunumeric as num -from cunumeric.eager import diagonal_reference +from cunumeric._thunk.eager import diagonal_reference class TestChoose1d: diff --git a/tests/integration/test_inner.py b/tests/integration/test_inner.py index 24904a07cd..74f46b8fb8 100644 --- a/tests/integration/test_inner.py +++ b/tests/integration/test_inner.py @@ -19,7 +19,7 @@ from utils.generators import mk_0to1_array import cunumeric as num -from cunumeric.utils import inner_modes +from cunumeric._utils.linalg import inner_modes @pytest.mark.parametrize("b_ndim", range(LEGATE_MAX_DIM + 1)) diff --git a/tests/integration/test_matmul.py b/tests/integration/test_matmul.py index 66f6ad89a4..b8167bea4a 100644 --- a/tests/integration/test_matmul.py +++ b/tests/integration/test_matmul.py @@ -24,7 +24,7 @@ ) import cunumeric as num -from cunumeric.utils import matmul_modes +from cunumeric._utils.linalg import matmul_modes @pytest.mark.parametrize("a_ndim", range(1, LEGATE_MAX_DIM + 1)) diff --git a/tests/integration/test_tensordot.py b/tests/integration/test_tensordot.py index dd2873be31..5cdce9fad4 100644 --- a/tests/integration/test_tensordot.py +++ b/tests/integration/test_tensordot.py @@ -19,7 +19,7 @@ from utils.generators import mk_0to1_array import cunumeric as num -from cunumeric.utils import tensordot_modes +from cunumeric._utils.linalg import tensordot_modes def gen_axes(a_ndim, b_ndim): diff --git a/tests/unit/cunumeric/test_utils_array.py b/tests/unit/cunumeric/test_utils_array.py new file mode 100644 index 0000000000..27aa59a129 --- /dev/null +++ b/tests/unit/cunumeric/test_utils_array.py @@ -0,0 +1,101 @@ +# Copyright 2024 NVIDIA Corporation +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +import numpy as np +import pytest + +import cunumeric._utils.array as m # module under test + +EXPECTED_SUPPORTED_DTYPES = set( + [ + np.bool_, + np.int8, + np.int16, + np.int32, + np.int64, + np.uint8, + np.uint16, + np.uint32, + np.uint64, + np.float16, + np.float32, + np.float64, + np.complex64, + np.complex128, + ] +) + + +class Test_is_advanced_indexing: + def test_Ellipsis(self): + assert not m.is_advanced_indexing(...) + + def test_None(self): + assert not m.is_advanced_indexing(None) + + @pytest.mark.parametrize("typ", EXPECTED_SUPPORTED_DTYPES) + def test_np_scalar(self, typ): + assert not m.is_advanced_indexing(typ(10)) + + def test_slice(self): + assert not m.is_advanced_indexing(slice(None, 10)) + assert not m.is_advanced_indexing(slice(1, 10)) + assert not m.is_advanced_indexing(slice(None, 10, 2)) + + def test_tuple_False(self): + assert not m.is_advanced_indexing((..., None, np.int32())) + + def test_tuple_True(self): + assert m.is_advanced_indexing(([1, 2, 3], np.array([1, 2]))) + + def test_advanced(self): + assert m.is_advanced_indexing([1, 2, 3]) + assert m.is_advanced_indexing(np.array([1, 2, 3])) + + +def test__SUPPORTED_DTYPES(): + assert set(m.SUPPORTED_DTYPES.keys()) == set( + np.dtype(ty) for ty in EXPECTED_SUPPORTED_DTYPES + ) + + +class Test_is_supported_dtype: + @pytest.mark.parametrize("value", ["foo", 10, 10.2, (), set()]) + def test_type_bad(self, value) -> None: + with pytest.raises(TypeError): + m.to_core_dtype(value) + + @pytest.mark.parametrize("value", EXPECTED_SUPPORTED_DTYPES) + def test_supported(self, value) -> None: + m.to_core_dtype(value) + + # This is just a representative sample, not exhasutive + @pytest.mark.parametrize("value", [np.float128, np.datetime64, [], {}]) + def test_unsupported(self, value) -> None: + with pytest.raises(TypeError): + m.to_core_dtype(value) + + +@pytest.mark.parametrize( + "shape, volume", [[(), 0], [(10,), 10], [(1, 2, 3), 6]] +) +def test_calculate_volume(shape, volume) -> None: + assert m.calculate_volume(shape) == volume + + +if __name__ == "__main__": + import sys + + sys.exit(pytest.main(sys.argv)) diff --git a/tests/unit/cunumeric/test_coverage.py b/tests/unit/cunumeric/test_utils_coverage.py similarity index 99% rename from tests/unit/cunumeric/test_coverage.py rename to tests/unit/cunumeric/test_utils_coverage.py index ca683b51c5..23c7ff6f9f 100644 --- a/tests/unit/cunumeric/test_coverage.py +++ b/tests/unit/cunumeric/test_utils_coverage.py @@ -20,7 +20,7 @@ from mock import MagicMock, patch import cunumeric -import cunumeric.coverage as m # module under test +import cunumeric._utils.coverage as m # module under test from cunumeric.settings import settings diff --git a/tests/unit/cunumeric/test_utils.py b/tests/unit/cunumeric/test_utils_linalg.py similarity index 61% rename from tests/unit/cunumeric/test_utils.py rename to tests/unit/cunumeric/test_utils_linalg.py index 3b3da8bc58..bf51d19f67 100644 --- a/tests/unit/cunumeric/test_utils.py +++ b/tests/unit/cunumeric/test_utils_linalg.py @@ -13,133 +13,12 @@ # limitations under the License. # -import inspect from typing import List, Tuple, Union import numpy as np import pytest -import cunumeric.utils as m # module under test - -EXPECTED_SUPPORTED_DTYPES = set( - [ - np.bool_, - np.int8, - np.int16, - np.int32, - np.int64, - np.uint8, - np.uint16, - np.uint32, - np.uint64, - np.float16, - np.float32, - np.float64, - np.complex64, - np.complex128, - ] -) - - -class Test_is_advanced_indexing: - def test_Ellipsis(self): - assert not m.is_advanced_indexing(...) - - def test_None(self): - assert not m.is_advanced_indexing(None) - - @pytest.mark.parametrize("typ", EXPECTED_SUPPORTED_DTYPES) - def test_np_scalar(self, typ): - assert not m.is_advanced_indexing(typ(10)) - - def test_slice(self): - assert not m.is_advanced_indexing(slice(None, 10)) - assert not m.is_advanced_indexing(slice(1, 10)) - assert not m.is_advanced_indexing(slice(None, 10, 2)) - - def test_tuple_False(self): - assert not m.is_advanced_indexing((..., None, np.int32())) - - def test_tuple_True(self): - assert m.is_advanced_indexing(([1, 2, 3], np.array([1, 2]))) - - def test_advanced(self): - assert m.is_advanced_indexing([1, 2, 3]) - assert m.is_advanced_indexing(np.array([1, 2, 3])) - - -def test_find_last_user_stacklevel() -> None: - n = m.find_last_user_stacklevel() - assert isinstance(n, int) - assert n == 1 - - -def test_get_line_number_from_frame() -> None: - frame = inspect.currentframe() - result = m.get_line_number_from_frame(frame) - assert isinstance(result, str) - filename, lineno = result.split(":") - - # NOTE: this will break if this test filename is changed - assert filename.endswith("test_utils.py") - - # it would be too fragile to compare more specific than this - assert int(lineno) > 0 - - -class Test_find_last_user_frames: - def check_default_top_only(self) -> None: - result = m.find_last_user_frames(top_only=True) - assert isinstance(result, str) - assert "|" not in result - assert "\n" not in result - assert len(result.split(":")) == 2 - - def test_top_only_True(self) -> None: - result = m.find_last_user_frames(top_only=True) - assert isinstance(result, str) - assert "|" not in result - assert "\n" not in result - assert len(result.split(":")) == 2 - - def test_top_only_False(self) -> None: - result = m.find_last_user_frames(top_only=False) - assert isinstance(result, str) - assert "|" in result - - # it would be too fragile to compare more specific than this - assert len(result.split("|")) > 1 - assert all(len(x.split(":")) == 2 for x in result.split("|")) - - -def test__SUPPORTED_DTYPES(): - assert set(m.SUPPORTED_DTYPES.keys()) == set( - np.dtype(ty) for ty in EXPECTED_SUPPORTED_DTYPES - ) - - -class Test_is_supported_dtype: - @pytest.mark.parametrize("value", ["foo", 10, 10.2, (), set()]) - def test_type_bad(self, value) -> None: - with pytest.raises(TypeError): - m.to_core_dtype(value) - - @pytest.mark.parametrize("value", EXPECTED_SUPPORTED_DTYPES) - def test_supported(self, value) -> None: - m.to_core_dtype(value) - - # This is just a representative sample, not exhasutive - @pytest.mark.parametrize("value", [np.float128, np.datetime64, [], {}]) - def test_unsupported(self, value) -> None: - with pytest.raises(TypeError): - m.to_core_dtype(value) - - -@pytest.mark.parametrize( - "shape, volume", [[(), 0], [(10,), 10], [(1, 2, 3), 6]] -) -def test_calculate_volume(shape, volume) -> None: - assert m.calculate_volume(shape) == volume +import cunumeric._utils.linalg as m # module under test def _dot_modes_oracle(a_ndim: int, b_ndim: int) -> bool: diff --git a/tests/unit/cunumeric/test_utils_stack.py b/tests/unit/cunumeric/test_utils_stack.py new file mode 100644 index 0000000000..8157373880 --- /dev/null +++ b/tests/unit/cunumeric/test_utils_stack.py @@ -0,0 +1,70 @@ +# Copyright 2024 NVIDIA Corporation +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +import inspect + +import pytest + +import cunumeric._utils.stack as m # module under test + + +def test_find_last_user_stacklevel() -> None: + n = m.find_last_user_stacklevel() + assert isinstance(n, int) + assert n == 1 + + +def test_get_line_number_from_frame() -> None: + frame = inspect.currentframe() + result = m.get_line_number_from_frame(frame) + assert isinstance(result, str) + filename, lineno = result.split(":") + + # NOTE: this will break if this test filename is changed + assert filename.endswith("test_utils_stack.py") + + # it would be too fragile to compare more specific than this + assert int(lineno) > 0 + + +class Test_find_last_user_frames: + def check_default_top_only(self) -> None: + result = m.find_last_user_frames(top_only=True) + assert isinstance(result, str) + assert "|" not in result + assert "\n" not in result + assert len(result.split(":")) == 2 + + def test_top_only_True(self) -> None: + result = m.find_last_user_frames(top_only=True) + assert isinstance(result, str) + assert "|" not in result + assert "\n" not in result + assert len(result.split(":")) == 2 + + def test_top_only_False(self) -> None: + result = m.find_last_user_frames(top_only=False) + assert isinstance(result, str) + assert "|" in result + + # it would be too fragile to compare more specific than this + assert len(result.split("|")) > 1 + assert all(len(x.split(":")) == 2 for x in result.split("|")) + + +if __name__ == "__main__": + import sys + + sys.exit(pytest.main(sys.argv)) From 2e929ad3be0bc842a2e5690ede95e45dd3655386 Mon Sep 17 00:00:00 2001 From: Marcin Zalewski Date: Tue, 5 Mar 2024 11:13:29 -0800 Subject: [PATCH 167/462] Update label checker version (#132) --- .github/workflows/require-labels.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/require-labels.yml b/.github/workflows/require-labels.yml index 9b2704f703..b6680ed069 100644 --- a/.github/workflows/require-labels.yml +++ b/.github/workflows/require-labels.yml @@ -7,7 +7,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Check Labels - uses: mheap/github-action-required-labels@v3 + uses: mheap/github-action-required-labels@v5 with: mode: exactly count: 1 From e8d34a86fb018897ab257c2e77a7ff86cf55cf2d Mon Sep 17 00:00:00 2001 From: Marcin Zalewski Date: Tue, 5 Mar 2024 11:13:59 -0800 Subject: [PATCH 168/462] Change runners to self-hosted (#128) * Change runners to self-hosted * fix a typo --- .github/workflows/gh-build-and-test.yml | 4 ++-- .github/workflows/merge-ci.yml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/gh-build-and-test.yml b/.github/workflows/gh-build-and-test.yml index 2c4ba2c6cc..cebaf0bc01 100644 --- a/.github/workflows/gh-build-and-test.yml +++ b/.github/workflows/gh-build-and-test.yml @@ -11,7 +11,7 @@ on: jobs: setup-build: name: Setup build - runs-on: ubuntu-latest + runs-on: linux-amd64-cpu4 outputs: runner_type: ${{ steps.set_runner.outputs.runner_type }} steps: @@ -49,7 +49,7 @@ jobs: name: Setup test needs: - build - runs-on: ubuntu-latest + runs-on: linux-amd64-cpu4 outputs: matrix: ${{ steps.set-matrix.outputs.matrix }} steps: diff --git a/.github/workflows/merge-ci.yml b/.github/workflows/merge-ci.yml index 105987af43..2d0fbf4cb3 100644 --- a/.github/workflows/merge-ci.yml +++ b/.github/workflows/merge-ci.yml @@ -8,7 +8,7 @@ on: jobs: merge: if: ${{ github.repository_owner == 'nv-legate' }} - runs-on: ubuntu-latest + runs-on: linux-amd64-cpu4 steps: - name: Cunumeric Internal repository uses: actions/checkout@v4 From e69db438be47696cefd6f45a889bd76108067ff5 Mon Sep 17 00:00:00 2001 From: Bryan Van de Ven Date: Thu, 7 Mar 2024 12:39:51 -0800 Subject: [PATCH 169/462] Typing updates for minpy 3.10 (#134) * checkpoint * Apply suggestions from code review Co-authored-by: Manolis Papadakis --------- Co-authored-by: Manolis Papadakis --- cunumeric/_array/array.py | 178 +++++++++++----------- cunumeric/_array/thunk.py | 26 ++-- cunumeric/_array/util.py | 14 +- cunumeric/_module/array_dimension.py | 28 +--- cunumeric/_module/array_joining.py | 22 ++- cunumeric/_module/array_rearrange.py | 4 +- cunumeric/_module/array_splitting.py | 16 +- cunumeric/_module/array_tiling.py | 10 +- cunumeric/_module/array_transpose.py | 4 +- cunumeric/_module/binary_bit_packing.py | 12 +- cunumeric/_module/creation_data.py | 8 +- cunumeric/_module/creation_matrices.py | 6 +- cunumeric/_module/creation_ranges.py | 16 +- cunumeric/_module/creation_shape.py | 26 ++-- cunumeric/_module/indexing.py | 26 ++-- cunumeric/_module/linalg_mvp.py | 44 +++--- cunumeric/_module/logic_array_type.py | 10 +- cunumeric/_module/logic_comparison.py | 4 +- cunumeric/_module/logic_truth.py | 14 +- cunumeric/_module/math_extrema.py | 22 +-- cunumeric/_module/math_misc.py | 8 +- cunumeric/_module/math_sum_prod_diff.py | 74 ++++----- cunumeric/_module/sets_making.py | 4 +- cunumeric/_module/ssc_counting.py | 6 +- cunumeric/_module/ssc_searching.py | 20 +-- cunumeric/_module/ssc_sorting.py | 22 +-- cunumeric/_module/stats_avgs_vars.py | 26 ++-- cunumeric/_module/stats_histograms.py | 10 +- cunumeric/_module/stats_order.py | 20 +-- cunumeric/_sphinxext/_comparison_util.py | 6 +- cunumeric/_thunk/_sort.py | 4 +- cunumeric/_thunk/deferred.py | 123 +++++++-------- cunumeric/_thunk/eager.py | 138 ++++++++--------- cunumeric/_thunk/thunk.py | 102 ++++++------- cunumeric/_ufunc/ufunc.py | 44 +++--- cunumeric/_utils/coverage.py | 24 +-- cunumeric/_utils/linalg.py | 15 +- cunumeric/config.py | 8 +- cunumeric/fft/fft.py | 70 ++++----- cunumeric/linalg/linalg.py | 24 ++- cunumeric/ma/_masked_array.py | 6 +- cunumeric/random/_bitgenerator.py | 76 +++++---- cunumeric/random/_generator.py | 78 +++++----- cunumeric/random/_legacy.py | 18 +-- cunumeric/random/_random.py | 98 ++++++------ cunumeric/runtime.py | 22 +-- cunumeric/types.py | 6 +- tests/integration/test_einsum.py | 7 +- tests/integration/test_random_creation.py | 6 +- tests/integration/utils/comparisons.py | 4 +- tests/unit/cunumeric/test_utils_linalg.py | 4 +- 51 files changed, 744 insertions(+), 819 deletions(-) diff --git a/cunumeric/_array/array.py b/cunumeric/_array/array.py index a2f41e3cc3..729717fe2f 100644 --- a/cunumeric/_array/array.py +++ b/cunumeric/_array/array.py @@ -17,7 +17,7 @@ import operator import warnings from functools import reduce -from typing import TYPE_CHECKING, Any, Optional, Sequence, Union, cast +from typing import TYPE_CHECKING, Any, Sequence, cast import numpy as np from legate.core import Field, LogicalArray, Scalar @@ -92,12 +92,12 @@ def __init__( self, shape: Any, dtype: npt.DTypeLike = np.float64, - buffer: Union[Any, None] = None, + buffer: Any | None = None, offset: int = 0, - strides: Union[tuple[int], None] = None, - order: Union[OrderType, None] = None, - thunk: Union[NumPyThunk, None] = None, - inputs: Union[Any, None] = None, + strides: tuple[int] | None = None, + order: OrderType | None = None, + thunk: NumPyThunk | None = None, + inputs: Any | None = None, writeable: bool = True, ) -> None: # `inputs` being a cuNumeric ndarray is definitely a bug @@ -134,7 +134,7 @@ def __init__( ) else: self._thunk = thunk - self._legate_data: Union[dict[str, Any], None] = None + self._legate_data: dict[str, Any] | None = None self._writeable = writeable @@ -284,7 +284,7 @@ def T(self) -> ndarray: return self.transpose() @property - def base(self) -> Union[npt.NDArray[Any], None]: + def base(self) -> npt.NDArray[Any] | None: """ Returns dtype for the base element of the subarrays, regardless of their dimension or shape. @@ -610,7 +610,7 @@ def __and__(self, rhs: Any) -> ndarray: return _ufunc.bitwise_and(self, rhs) def __array__( - self, dtype: Union[np.dtype[Any], None] = None + self, dtype: np.dtype[Any] | None = None ) -> npt.NDArray[Any]: """a.__array__([dtype], /) @@ -686,7 +686,7 @@ def __copy__(self) -> ndarray: result._thunk.copy(self._thunk, deep=False) return result - def __deepcopy__(self, memo: Union[Any, None] = None) -> ndarray: + def __deepcopy__(self, memo: Any | None = None) -> ndarray: """a.__deepcopy__(memo, /) Deep copy of array. @@ -1232,9 +1232,7 @@ def __rdivmod__(self, lhs: Any) -> ndarray: "cunumeric.ndarray doesn't support __rdivmod__ yet" ) - def __reduce__( - self, *args: Any, **kwargs: Any - ) -> Union[str, tuple[str, ...]]: + def __reduce__(self, *args: Any, **kwargs: Any) -> str | tuple[str, ...]: """a.__reduce__(/) For pickling. @@ -1244,7 +1242,7 @@ def __reduce__( def __reduce_ex__( self, *args: Any, **kwargs: Any - ) -> Union[str, tuple[str, ...]]: + ) -> str | tuple[str, ...]: return self.__array__().__reduce_ex__(*args, **kwargs) def __repr__(self) -> str: @@ -1459,10 +1457,10 @@ def __xor__(self, rhs: Any) -> ndarray: def all( self, axis: Any = None, - out: Union[ndarray, None] = None, + out: ndarray | None = None, keepdims: bool = False, - initial: Union[int, float, None] = None, - where: Union[ndarray, None] = None, + initial: int | float | None = None, + where: ndarray | None = None, ) -> ndarray: """a.all(axis=None, out=None, keepdims=False, initial=None, where=True) @@ -1494,10 +1492,10 @@ def all( def any( self, axis: Any = None, - out: Union[ndarray, None] = None, + out: ndarray | None = None, keepdims: bool = False, - initial: Union[int, float, None] = None, - where: Union[ndarray, None] = None, + initial: int | float | None = None, + where: ndarray | None = None, ) -> ndarray: """a.any(axis=None, out=None, keepdims=False, initial=None, where=True) @@ -1529,7 +1527,7 @@ def any( def argmax( self, axis: Any = None, - out: Union[ndarray, None] = None, + out: ndarray | None = None, keepdims: bool = False, ) -> ndarray: """a.argmax(axis=None, out=None) @@ -1564,7 +1562,7 @@ def argmax( def argmin( self, axis: Any = None, - out: Union[ndarray, None] = None, + out: ndarray | None = None, keepdims: bool = False, ) -> ndarray: """a.argmin(axis=None, out=None) @@ -1693,7 +1691,7 @@ def take( self, indices: Any, axis: Any = None, - out: Union[ndarray, None] = None, + out: ndarray | None = None, mode: BoundsMode = "raise", ) -> ndarray: """a.take(indices, axis=None, out=None, mode="raise") @@ -1773,7 +1771,7 @@ def take( def choose( self, choices: Any, - out: Union[ndarray, None] = None, + out: ndarray | None = None, mode: BoundsMode = "raise", ) -> ndarray: """a.choose(choices, out=None, mode='raise') @@ -1875,7 +1873,7 @@ def compress( self, condition: ndarray, axis: Any = None, - out: Union[ndarray, None] = None, + out: ndarray | None = None, ) -> ndarray: """a.compress(self, condition, axis=None, out=None) @@ -1934,9 +1932,9 @@ def compress( @add_boilerplate() def clip( self, - min: Union[int, float, npt.ArrayLike, None] = None, - max: Union[int, float, npt.ArrayLike, None] = None, - out: Union[npt.NDArray[Any], ndarray, None] = None, + min: int | float | npt.ArrayLike | None = None, + max: int | float | npt.ArrayLike | None = None, + out: npt.NDArray[Any] | ndarray | None = None, ) -> ndarray: """a.clip(min=None, max=None, out=None) @@ -2040,8 +2038,8 @@ def copy(self, order: OrderType = "C") -> ndarray: def cumsum( self, axis: Any = None, - dtype: Union[np.dtype[Any], None] = None, - out: Union[ndarray, None] = None, + dtype: np.dtype[Any] | None = None, + out: ndarray | None = None, ) -> ndarray: return perform_scan( ScanCode.SUM, @@ -2056,8 +2054,8 @@ def cumsum( def cumprod( self, axis: Any = None, - dtype: Union[np.dtype[Any], None] = None, - out: Union[ndarray, None] = None, + dtype: np.dtype[Any] | None = None, + out: ndarray | None = None, ) -> ndarray: return perform_scan( ScanCode.PROD, @@ -2072,8 +2070,8 @@ def cumprod( def nancumsum( self, axis: Any = None, - dtype: Union[np.dtype[Any], None] = None, - out: Union[ndarray, None] = None, + dtype: np.dtype[Any] | None = None, + out: ndarray | None = None, ) -> ndarray: return perform_scan( ScanCode.SUM, @@ -2088,8 +2086,8 @@ def nancumsum( def nancumprod( self, axis: Any = None, - dtype: Union[np.dtype[Any], None] = None, - out: Union[ndarray, None] = None, + dtype: np.dtype[Any] | None = None, + out: ndarray | None = None, ) -> ndarray: return perform_scan( ScanCode.PROD, @@ -2107,11 +2105,11 @@ def nancumprod( def _diag_helper( self, offset: int = 0, - axes: Union[Any, None] = None, + axes: Any | None = None, extract: bool = True, trace: bool = False, - out: Union[ndarray, None] = None, - dtype: Union[np.dtype[Any], None] = None, + out: ndarray | None = None, + dtype: np.dtype[Any] | None = None, ) -> ndarray: # _diag_helper can be used only for arrays with dim>=1 if self.ndim < 1: @@ -2322,8 +2320,8 @@ def trace( offset: int = 0, axis1: Any = None, axis2: Any = None, - dtype: Union[np.dtype[Any], None] = None, - out: Union[ndarray, None] = None, + dtype: np.dtype[Any] | None = None, + out: ndarray | None = None, ) -> ndarray: """a.trace(offset=0, axis1=None, axis2=None, dtype = None, out = None) @@ -2365,7 +2363,7 @@ def trace( return res @add_boilerplate("rhs") - def dot(self, rhs: ndarray, out: Union[ndarray, None] = None) -> ndarray: + def dot(self, rhs: ndarray, out: ndarray | None = None) -> ndarray: """a.dot(rhs, out=None) Return the dot product of this array with ``rhs``. @@ -2398,7 +2396,7 @@ def dot(self, rhs: ndarray, out: Union[ndarray, None] = None) -> ndarray: casting="unsafe", ) - def dump(self, file: Union[str, Path]) -> None: + def dump(self, file: str | Path) -> None: """a.dump(file) Dump a pickle of the array to the specified file. @@ -2462,7 +2460,7 @@ def _normalize_axes_shape( def fft( self, s: Any, - axes: Union[Sequence[int], None], + axes: Sequence[int] | None, kind: FFTType, direction: FFTDirection, norm: Any, @@ -2737,10 +2735,10 @@ def itemset(self, *args: Any) -> None: def max( self, axis: Any = None, - out: Union[ndarray, None] = None, + out: ndarray | None = None, keepdims: bool = False, - initial: Union[int, float, None] = None, - where: Union[ndarray, None] = None, + initial: int | float | None = None, + where: ndarray | None = None, ) -> ndarray: """a.max(axis=None, out=None, keepdims=False, initial=, where=True) @@ -2768,7 +2766,7 @@ def max( where=where, ) - def _count_nonzero(self, axis: Any = None) -> Union[int, ndarray]: + def _count_nonzero(self, axis: Any = None) -> int | ndarray: if self.size == 0: return 0 return perform_unary_reduction( @@ -2778,9 +2776,7 @@ def _count_nonzero(self, axis: Any = None) -> Union[int, ndarray]: axis=axis, ) - def _summation_dtype( - self, dtype: Optional[np.dtype[Any]] - ) -> np.dtype[Any]: + def _summation_dtype(self, dtype: np.dtype[Any] | None) -> np.dtype[Any]: # Pick our dtype if it wasn't picked yet if dtype is None: if self.dtype.kind != "f" and self.dtype.kind != "c": @@ -2795,7 +2791,7 @@ def _normalize_summation( axis: Any, ddof: int = 0, keepdims: bool = False, - where: Union[ndarray, None] = None, + where: ndarray | None = None, ) -> None: dtype = sum_array.dtype if axis is None: @@ -2831,10 +2827,10 @@ def _normalize_summation( def mean( self, axis: Any = None, - dtype: Optional[np.dtype[Any]] = None, - out: Optional[ndarray] = None, + dtype: np.dtype[Any] | None = None, + out: ndarray | None = None, keepdims: bool = False, - where: Union[ndarray, None] = None, + where: ndarray | None = None, ) -> ndarray: """a.mean(axis=None, dtype=None, out=None, keepdims=False) @@ -2889,11 +2885,11 @@ def mean( def _nanmean( self, - axis: Optional[Union[int, tuple[int, ...]]] = None, - dtype: Union[np.dtype[Any], None] = None, - out: Union[ndarray, None] = None, + axis: int | tuple[int, ...] | None = None, + dtype: np.dtype[Any] | None = None, + out: ndarray | None = None, keepdims: bool = False, - where: Union[ndarray, None] = None, + where: ndarray | None = None, ) -> ndarray: if np.issubdtype(dtype, np.integer) or np.issubdtype(dtype, np.bool_): return self.mean( @@ -2916,13 +2912,13 @@ def _nanmean( @add_boilerplate() def var( self, - axis: Optional[Union[int, tuple[int, ...]]] = None, - dtype: Optional[np.dtype[Any]] = None, - out: Optional[ndarray] = None, + axis: int | tuple[int, ...] | None = None, + dtype: np.dtype[Any] | None = None, + out: ndarray | None = None, ddof: int = 0, keepdims: bool = False, *, - where: Union[ndarray, None] = None, + where: ndarray | None = None, ) -> ndarray: """a.var(a, axis=None, dtype=None, out=None, ddof=0, keepdims=False) @@ -3012,10 +3008,10 @@ def var( def min( self, axis: Any = None, - out: Union[ndarray, None] = None, + out: ndarray | None = None, keepdims: bool = False, - initial: Union[int, float, None] = None, - where: Union[ndarray, None] = None, + initial: int | float | None = None, + where: ndarray | None = None, ) -> ndarray: """a.min(axis=None, out=None, keepdims=False, initial=, where=True) @@ -3046,10 +3042,10 @@ def min( @add_boilerplate() def partition( self, - kth: Union[int, Sequence[int]], + kth: int | Sequence[int], axis: Any = -1, kind: SelectKind = "introselect", - order: Union[OrderType, None] = None, + order: OrderType | None = None, ) -> None: """a.partition(kth, axis=-1, kind='introselect', order=None) @@ -3074,10 +3070,10 @@ def partition( @add_boilerplate() def argpartition( self, - kth: Union[int, Sequence[int]], + kth: int | Sequence[int], axis: Any = -1, kind: SelectKind = "introselect", - order: Union[OrderType, None] = None, + order: OrderType | None = None, ) -> ndarray: """a.argpartition(kth, axis=-1, kind='introselect', order=None) @@ -3109,11 +3105,11 @@ def argpartition( def prod( self, axis: Any = None, - dtype: Union[np.dtype[Any], None] = None, - out: Union[ndarray, None] = None, + dtype: np.dtype[Any] | None = None, + out: ndarray | None = None, keepdims: bool = False, - initial: Union[int, float, None] = None, - where: Union[ndarray, None] = None, + initial: int | float | None = None, + where: ndarray | None = None, ) -> ndarray: """a.prod(axis=None, dtype=None, out=None, keepdims=False, initial=1, where=True) @@ -3257,9 +3253,9 @@ def setfield( def setflags( self, - write: Union[bool, None] = None, - align: Union[bool, None] = None, - uic: Union[bool, None] = None, + write: bool | None = None, + align: bool | None = None, + uic: bool | None = None, ) -> None: """a.setflags(write=None, align=None, uic=None) @@ -3322,10 +3318,10 @@ def setflags( @add_boilerplate() def searchsorted( self: ndarray, - v: Union[int, float, ndarray], + v: int | float | ndarray, side: SortSide = "left", - sorter: Optional[ndarray] = None, - ) -> Union[int, ndarray]: + sorter: ndarray | None = None, + ) -> int | ndarray: """a.searchsorted(v, side='left', sorter=None) Find the indices into a sorted array a such that, if the corresponding @@ -3397,7 +3393,7 @@ def sort( self, axis: Any = -1, kind: SortType = "quicksort", - order: Union[OrderType, None] = None, + order: OrderType | None = None, ) -> None: """a.sort(axis=-1, kind=None, order=None) @@ -3421,7 +3417,7 @@ def argsort( self, axis: Any = -1, kind: SortType = "quicksort", - order: Union[OrderType, None] = None, + order: OrderType | None = None, ) -> ndarray: """a.argsort(axis=-1, kind=None, order=None) @@ -3479,11 +3475,11 @@ def squeeze(self, axis: Any = None) -> ndarray: def sum( self, axis: Any = None, - dtype: Union[np.dtype[Any], None] = None, - out: Union[ndarray, None] = None, + dtype: np.dtype[Any] | None = None, + out: ndarray | None = None, keepdims: bool = False, - initial: Union[int, float, None] = None, - where: Union[ndarray, None] = None, + initial: int | float | None = None, + where: ndarray | None = None, ) -> ndarray: """a.sum(axis=None, dtype=None, out=None, keepdims=False, initial=0, where=None) @@ -3526,10 +3522,10 @@ def _nansum( self, axis: Any = None, dtype: Any = None, - out: Union[ndarray, None] = None, + out: ndarray | None = None, keepdims: bool = False, - initial: Optional[Union[int, float]] = None, - where: Optional[ndarray] = None, + initial: int | float | None = None, + where: ndarray | None = None, ) -> ndarray: # Note that np.nansum and np.sum allow complex datatypes # so there are no "disallowed types" for this API @@ -3790,8 +3786,8 @@ def flip(self, axis: Any = None) -> ndarray: def view( self, - dtype: Union[npt.DTypeLike, None] = None, - type: Union[type, None] = None, + dtype: npt.DTypeLike | None = None, + type: type | None = None, ) -> ndarray: """ New view of array with the same data. diff --git a/cunumeric/_array/thunk.py b/cunumeric/_array/thunk.py index a998609df1..535c4a6169 100644 --- a/cunumeric/_array/thunk.py +++ b/cunumeric/_array/thunk.py @@ -14,7 +14,7 @@ # from __future__ import annotations -from typing import TYPE_CHECKING, Any, Union +from typing import TYPE_CHECKING, Any import numpy as np from legate.core import Scalar @@ -43,8 +43,8 @@ def get_where_thunk( - where: Union[None, ndarray], out_shape: NdShape -) -> Union[None, NumPyThunk]: + where: ndarray | None, out_shape: NdShape +) -> NumPyThunk | None: from .array import ndarray if where is None: @@ -61,10 +61,10 @@ def get_where_thunk( def perform_unary_op( op: UnaryOpCode, src: ndarray, - out: Union[Any, None] = None, + out: Any | None = None, extra_args: Any = None, - dtype: Union[np.dtype[Any], None] = None, - out_dtype: Union[np.dtype[Any], None] = None, + dtype: np.dtype[Any] | None = None, + out_dtype: np.dtype[Any] | None = None, ) -> ndarray: from .array import ndarray @@ -154,13 +154,13 @@ def perform_unary_reduction( op: UnaryRedCode, src: ndarray, axis: Any = None, - dtype: Union[np.dtype[Any], None] = None, - res_dtype: Union[npt.DTypeLike, None] = None, - out: Union[ndarray, None] = None, + dtype: np.dtype[Any] | None = None, + res_dtype: npt.DTypeLike | None = None, + out: ndarray | None = None, keepdims: bool = False, args: tuple[Scalar, ...] = (), - initial: Union[int, float, None] = None, - where: Union[ndarray, None] = None, + initial: int | float | None = None, + where: ndarray | None = None, ) -> ndarray: from .array import ndarray @@ -303,8 +303,8 @@ def perform_scan( op: ScanCode, src: ndarray, axis: Any = None, - dtype: Union[npt.DTypeLike, None] = None, - out: Union[ndarray, None] = None, + dtype: npt.DTypeLike | None = None, + out: ndarray | None = None, nan_to_identity: bool = False, ) -> ndarray: from .array import ndarray diff --git a/cunumeric/_array/util.py b/cunumeric/_array/util.py index 806d4a4c7d..727a77e8bb 100644 --- a/cunumeric/_array/util.py +++ b/cunumeric/_array/util.py @@ -21,11 +21,9 @@ TYPE_CHECKING, Any, Callable, - Optional, ParamSpec, Sequence, TypeVar, - Union, cast, ) @@ -69,8 +67,8 @@ def decorator(func: Callable[P, R]) -> Callable[P, R]: # For each parameter specified by name, also consider the case where # it's passed as a positional parameter. indices: OrderedSet[int] = OrderedSet() - where_idx: Optional[int] = None - out_idx: Optional[int] = None + where_idx: int | None = None + out_idx: int | None = None params = signature(func).parameters extra = keys - OrderedSet(params) assert len(extra) == 0, f"unknown parameter(s): {extra}" @@ -112,9 +110,7 @@ def wrapper(*args: Any, **kwargs: Any) -> R: return decorator -def broadcast_where( - where: Union[ndarray, None], shape: NdShape -) -> Union[ndarray, None]: +def broadcast_where(where: ndarray | None, shape: NdShape) -> ndarray | None: if where is not None and where.shape != shape: from .._module import broadcast_to @@ -148,7 +144,7 @@ def maybe_convert_to_np_ndarray(obj: Any) -> Any: return obj -def check_writeable(arr: Union[ndarray, tuple[ndarray, ...], None]) -> None: +def check_writeable(arr: ndarray | tuple[ndarray, ...] | None) -> None: """ Check if the current array is writeable This check needs to be manually inserted @@ -162,7 +158,7 @@ def check_writeable(arr: Union[ndarray, tuple[ndarray, ...], None]) -> None: def sanitize_shape( - shape: Union[NdShapeLike, Sequence[Any], npt.NDArray[Any], ndarray] + shape: NdShapeLike | Sequence[Any] | npt.NDArray[Any] | ndarray, ) -> NdShape: from .array import ndarray diff --git a/cunumeric/_module/array_dimension.py b/cunumeric/_module/array_dimension.py index 901a19a808..b2c7c0cee1 100644 --- a/cunumeric/_module/array_dimension.py +++ b/cunumeric/_module/array_dimension.py @@ -14,15 +14,7 @@ # from __future__ import annotations -from typing import ( - TYPE_CHECKING, - Any, - Iterable, - Optional, - Sequence, - Tuple, - Union, -) +from typing import TYPE_CHECKING, Any, Iterable, Sequence import numpy as np @@ -46,9 +38,7 @@ def _reshape_recur(ndim: int, arr: ndarray) -> tuple[int, ...]: return cur_shape -def _atleast_nd( - ndim: int, arys: Sequence[ndarray] -) -> Union[list[ndarray], ndarray]: +def _atleast_nd(ndim: int, arys: Sequence[ndarray]) -> list[ndarray] | ndarray: inputs = list(convert_to_cunumeric_ndarray(arr) for arr in arys) # 'reshape' change the shape of arrays # only when arr.shape != _reshape_recur(ndim,arr) @@ -60,7 +50,7 @@ def _atleast_nd( return result -def atleast_1d(*arys: ndarray) -> Union[list[ndarray], ndarray]: +def atleast_1d(*arys: ndarray) -> list[ndarray] | ndarray: """ Convert inputs to arrays with at least one dimension. @@ -89,7 +79,7 @@ def atleast_1d(*arys: ndarray) -> Union[list[ndarray], ndarray]: return _atleast_nd(1, arys) -def atleast_2d(*arys: ndarray) -> Union[list[ndarray], ndarray]: +def atleast_2d(*arys: ndarray) -> list[ndarray] | ndarray: """ View inputs as arrays with at least two dimensions. @@ -119,7 +109,7 @@ def atleast_2d(*arys: ndarray) -> Union[list[ndarray], ndarray]: return _atleast_nd(2, arys) -def atleast_3d(*arys: ndarray) -> Union[list[ndarray], ndarray]: +def atleast_3d(*arys: ndarray) -> list[ndarray] | ndarray: """ View inputs as arrays with at least three dimensions. @@ -153,7 +143,7 @@ def atleast_3d(*arys: ndarray) -> Union[list[ndarray], ndarray]: @add_boilerplate("a") -def squeeze(a: ndarray, axis: Optional[NdShapeLike] = None) -> ndarray: +def squeeze(a: ndarray, axis: NdShapeLike | None = None) -> ndarray: """ Remove single-dimensional entries from the shape of an array. @@ -190,9 +180,7 @@ def squeeze(a: ndarray, axis: Optional[NdShapeLike] = None) -> ndarray: return a.squeeze(axis=axis) -def broadcast_shapes( - *args: Union[NdShapeLike, Sequence[NdShapeLike]] -) -> NdShape: +def broadcast_shapes(*args: NdShapeLike | Sequence[NdShapeLike]) -> NdShape: """ Broadcast the input shapes into a single shape. @@ -372,7 +360,7 @@ def index(self) -> int: return self._index @property - def iters(self) -> Tuple[Iterable[Any], ...]: + def iters(self) -> tuple[Iterable[Any], ...]: """tuple of iterators along self’s "components." """ return self._iters diff --git a/cunumeric/_module/array_joining.py b/cunumeric/_module/array_joining.py index 6613024068..ed5c57e894 100644 --- a/cunumeric/_module/array_joining.py +++ b/cunumeric/_module/array_joining.py @@ -15,7 +15,7 @@ from __future__ import annotations from itertools import chain -from typing import TYPE_CHECKING, Any, Optional, Sequence, Union +from typing import TYPE_CHECKING, Any, Sequence import numpy as np from numpy.core.multiarray import ( # type: ignore [attr-defined] @@ -112,7 +112,7 @@ def check_shape_with_axis( def check_shape_dtype_without_axis( inputs: Sequence[ndarray], func_name: str, - dtype: Optional[npt.DTypeLike] = None, + dtype: npt.DTypeLike | None = None, casting: CastingKind = "same_kind", ) -> tuple[list[ndarray], ArrayInfo]: if len(inputs) == 0: @@ -139,7 +139,7 @@ def check_shape_dtype_without_axis( def _block_collect_slices( - arr: Union[ndarray, Sequence[ndarray]], cur_depth: int, depth: int + arr: ndarray | Sequence[ndarray], cur_depth: int, depth: int ) -> tuple[list[Any], list[tuple[slice, ...]], Sequence[ndarray]]: # collects slices for each array in `arr` # the outcome will be slices on every dimension of the output array @@ -230,8 +230,8 @@ def _concatenate( inputs: Sequence[ndarray], common_info: ArrayInfo, axis: int = 0, - out: Optional[ndarray] = None, - dtype: Optional[npt.DTypeLike] = None, + out: ndarray | None = None, + dtype: npt.DTypeLike | None = None, casting: CastingKind = "same_kind", ) -> ndarray: if axis < 0: @@ -261,9 +261,7 @@ def _concatenate( return out_array -def append( - arr: ndarray, values: ndarray, axis: Optional[int] = None -) -> ndarray: +def append(arr: ndarray, values: ndarray, axis: int | None = None) -> ndarray: """ Append values to the end of an array. @@ -362,9 +360,9 @@ def block(arrays: Sequence[Any]) -> ndarray: def concatenate( inputs: Sequence[ndarray], - axis: Union[int, None] = 0, - out: Optional[ndarray] = None, - dtype: Optional[npt.DTypeLike] = None, + axis: int | None = 0, + out: ndarray | None = None, + dtype: npt.DTypeLike | None = None, casting: CastingKind = "same_kind", ) -> ndarray: """ @@ -443,7 +441,7 @@ def concatenate( def stack( - arrays: Sequence[ndarray], axis: int = 0, out: Optional[ndarray] = None + arrays: Sequence[ndarray], axis: int = 0, out: ndarray | None = None ) -> ndarray: """ diff --git a/cunumeric/_module/array_rearrange.py b/cunumeric/_module/array_rearrange.py index a1989912de..45dfafe5c4 100644 --- a/cunumeric/_module/array_rearrange.py +++ b/cunumeric/_module/array_rearrange.py @@ -14,7 +14,7 @@ # from __future__ import annotations -from typing import TYPE_CHECKING, Optional +from typing import TYPE_CHECKING from .._array.util import add_boilerplate @@ -24,7 +24,7 @@ @add_boilerplate("m") -def flip(m: ndarray, axis: Optional[NdShapeLike] = None) -> ndarray: +def flip(m: ndarray, axis: NdShapeLike | None = None) -> ndarray: """ Reverse the order of elements in an array along the given axis. diff --git a/cunumeric/_module/array_splitting.py b/cunumeric/_module/array_splitting.py index 2ccf30cf09..dd4a9e2b1d 100644 --- a/cunumeric/_module/array_splitting.py +++ b/cunumeric/_module/array_splitting.py @@ -14,7 +14,7 @@ # from __future__ import annotations -from typing import TYPE_CHECKING, Any, Union +from typing import TYPE_CHECKING, Any import numpy as np @@ -25,9 +25,7 @@ import numpy.typing as npt -def split( - a: ndarray, indices: Union[int, ndarray], axis: int = 0 -) -> list[ndarray]: +def split(a: ndarray, indices: int | ndarray, axis: int = 0) -> list[ndarray]: """ Split an array into multiple sub-arrays as views into `ary`. @@ -78,7 +76,7 @@ def split( def array_split( a: ndarray, - indices: Union[int, tuple[int], ndarray, npt.NDArray[Any]], + indices: int | tuple[int] | ndarray | npt.NDArray[Any], axis: int = 0, equal: bool = False, ) -> list[ndarray]: @@ -151,7 +149,7 @@ def array_split( start_idx = 0 end_idx = 0 out_shape = [] - in_shape: list[Union[int, slice]] = [] + in_shape: list[int | slice] = [] for i in range(array.ndim): if i != axis: @@ -188,7 +186,7 @@ def array_split( return result -def dsplit(a: ndarray, indices: Union[int, ndarray]) -> list[ndarray]: +def dsplit(a: ndarray, indices: int | ndarray) -> list[ndarray]: """ Split array into multiple sub-arrays along the 3rd axis (depth). @@ -208,7 +206,7 @@ def dsplit(a: ndarray, indices: Union[int, ndarray]) -> list[ndarray]: return split(a, indices, axis=2) -def hsplit(a: ndarray, indices: Union[int, ndarray]) -> list[ndarray]: +def hsplit(a: ndarray, indices: int | ndarray) -> list[ndarray]: """ Split an array into multiple sub-arrays horizontally (column-wise). @@ -228,7 +226,7 @@ def hsplit(a: ndarray, indices: Union[int, ndarray]) -> list[ndarray]: return split(a, indices, axis=1) -def vsplit(a: ndarray, indices: Union[int, ndarray]) -> list[ndarray]: +def vsplit(a: ndarray, indices: int | ndarray) -> list[ndarray]: """ Split an array into multiple sub-arrays vertically (row-wise). diff --git a/cunumeric/_module/array_tiling.py b/cunumeric/_module/array_tiling.py index 34ce2fd667..d118681f92 100644 --- a/cunumeric/_module/array_tiling.py +++ b/cunumeric/_module/array_tiling.py @@ -14,7 +14,7 @@ # from __future__ import annotations -from typing import TYPE_CHECKING, Any, Optional, Sequence, Union, cast +from typing import TYPE_CHECKING, Any, Sequence, cast import numpy as np from numpy.core.multiarray import ( # type: ignore [attr-defined] @@ -37,7 +37,7 @@ @add_boilerplate("A") def tile( - A: ndarray, reps: Union[int, Sequence[int], npt.NDArray[np.int_]] + A: ndarray, reps: int | Sequence[int] | npt.NDArray[np.int_] ) -> ndarray: """ Construct an array by repeating A the number of times given by reps. @@ -100,7 +100,7 @@ def tile( return result -def repeat(a: ndarray, repeats: Any, axis: Optional[int] = None) -> ndarray: +def repeat(a: ndarray, repeats: Any, axis: int | None = None) -> ndarray: """ Repeat elements of an array. @@ -160,7 +160,7 @@ def repeat(a: ndarray, repeats: Any, axis: Optional[int] = None) -> ndarray: category=UserWarning, ) repeats = np.int64(repeats) - return full((repeats,), cast(Union[int, float], a)) + return full((repeats,), cast(int | float, a)) elif np.ndim(repeats) == 1 and len(repeats) == 1: if not isinstance(repeats, int): runtime.warn( @@ -168,7 +168,7 @@ def repeat(a: ndarray, repeats: Any, axis: Optional[int] = None) -> ndarray: category=UserWarning, ) repeats = np.int64(repeats) - return full((repeats[0],), cast(Union[int, float], a)) + return full((repeats[0],), cast(int | float, a)) else: raise ValueError( "`repeat` with a scalar parameter `a` is only " diff --git a/cunumeric/_module/array_transpose.py b/cunumeric/_module/array_transpose.py index c31ddf409a..27337b33d4 100644 --- a/cunumeric/_module/array_transpose.py +++ b/cunumeric/_module/array_transpose.py @@ -14,7 +14,7 @@ # from __future__ import annotations -from typing import TYPE_CHECKING, Optional, Sequence +from typing import TYPE_CHECKING, Sequence from numpy.core.numeric import ( # type: ignore [attr-defined] normalize_axis_tuple, @@ -59,7 +59,7 @@ def swapaxes(a: ndarray, axis1: int, axis2: int) -> ndarray: @add_boilerplate("a") -def transpose(a: ndarray, axes: Optional[list[int]] = None) -> ndarray: +def transpose(a: ndarray, axes: list[int] | None = None) -> ndarray: """ Permute the dimensions of an array. diff --git a/cunumeric/_module/binary_bit_packing.py b/cunumeric/_module/binary_bit_packing.py index 325ac2fc2d..447a94644b 100644 --- a/cunumeric/_module/binary_bit_packing.py +++ b/cunumeric/_module/binary_bit_packing.py @@ -14,7 +14,7 @@ # from __future__ import annotations -from typing import TYPE_CHECKING, Optional, Tuple +from typing import TYPE_CHECKING from .._array.util import add_boilerplate from .creation_shape import empty @@ -25,8 +25,8 @@ def _sanitize_arguments( - a: ndarray, axis: Optional[int], bitorder: BitOrder -) -> Tuple[ndarray, int]: + a: ndarray, axis: int | None, bitorder: BitOrder +) -> tuple[ndarray, int]: if axis is None: if a.ndim > 1: a = a.ravel() @@ -49,7 +49,7 @@ def _sanitize_arguments( @add_boilerplate("a") def packbits( - a: ndarray, axis: Optional[int] = None, bitorder: BitOrder = "big" + a: ndarray, axis: int | None = None, bitorder: BitOrder = "big" ) -> ndarray: """ @@ -108,8 +108,8 @@ def packbits( @add_boilerplate("a") def unpackbits( a: ndarray, - axis: Optional[int] = None, - count: Optional[int] = None, + axis: int | None = None, + count: int | None = None, bitorder: BitOrder = "big", ) -> ndarray: """ diff --git a/cunumeric/_module/creation_data.py b/cunumeric/_module/creation_data.py index 9e295f075c..03869905c2 100644 --- a/cunumeric/_module/creation_data.py +++ b/cunumeric/_module/creation_data.py @@ -14,7 +14,7 @@ # from __future__ import annotations -from typing import TYPE_CHECKING, Any, Literal, Optional, Union +from typing import TYPE_CHECKING, Any, Literal import numpy as np @@ -29,9 +29,9 @@ def array( obj: Any, - dtype: Optional[np.dtype[Any]] = None, + dtype: np.dtype[Any] | None = None, copy: bool = True, - order: Union[OrderType, Literal["K"]] = "K", + order: OrderType | Literal["K"] = "K", subok: bool = False, ndmin: int = 0, ) -> ndarray: @@ -109,7 +109,7 @@ def array( return result -def asarray(a: Any, dtype: Optional[np.dtype[Any]] = None) -> ndarray: +def asarray(a: Any, dtype: np.dtype[Any] | None = None) -> ndarray: """ Convert the input to an array. diff --git a/cunumeric/_module/creation_matrices.py b/cunumeric/_module/creation_matrices.py index e77aa23bfe..7b97ef488f 100644 --- a/cunumeric/_module/creation_matrices.py +++ b/cunumeric/_module/creation_matrices.py @@ -14,7 +14,7 @@ # from __future__ import annotations -from typing import TYPE_CHECKING, Optional +from typing import TYPE_CHECKING from .._array.array import ndarray from .._array.util import add_boilerplate @@ -71,11 +71,11 @@ def diag(v: ndarray, k: int = 0) -> ndarray: def tri( N: int, - M: Optional[int] = None, + M: int | None = None, k: int = 0, dtype: npt.DTypeLike = float, *, - like: Optional[ndarray] = None, + like: ndarray | None = None, ) -> ndarray: """ An array with ones at and below the given diagonal and zeros elsewhere. diff --git a/cunumeric/_module/creation_ranges.py b/cunumeric/_module/creation_ranges.py index 71297b1ee1..49be45aa44 100644 --- a/cunumeric/_module/creation_ranges.py +++ b/cunumeric/_module/creation_ranges.py @@ -15,7 +15,7 @@ from __future__ import annotations import math -from typing import TYPE_CHECKING, Any, Optional, Union +from typing import TYPE_CHECKING, Any import numpy as np @@ -31,10 +31,10 @@ def arange( - start: Union[int, float] = 0, - stop: Optional[Union[int, float]] = None, - step: Optional[Union[int, float]] = 1, - dtype: Optional[npt.DTypeLike] = None, + start: int | float = 0, + stop: int | float | None = None, + step: int | float | None = 1, + dtype: npt.DTypeLike | None = None, ) -> ndarray: """ arange([start,] stop[, step,], dtype=None) @@ -110,9 +110,9 @@ def linspace( num: int = 50, endpoint: bool = True, retstep: bool = False, - dtype: Optional[npt.DTypeLike] = None, + dtype: npt.DTypeLike | None = None, axis: int = 0, -) -> Union[ndarray, tuple[ndarray, Union[float, ndarray]]]: +) -> ndarray | tuple[ndarray, float | ndarray]: """ Return evenly spaced numbers over a specified interval. @@ -225,7 +225,7 @@ def linspace( # else delta is a scalar so start must be also # therefore it will trivially broadcast correctly - step: Union[float, ndarray] + step: float | ndarray if div > 0: step = delta / div if delta.ndim == 0: diff --git a/cunumeric/_module/creation_shape.py b/cunumeric/_module/creation_shape.py index 2337a11cc1..fecb4786c1 100644 --- a/cunumeric/_module/creation_shape.py +++ b/cunumeric/_module/creation_shape.py @@ -15,7 +15,7 @@ from __future__ import annotations import operator -from typing import TYPE_CHECKING, Any, Optional, Union +from typing import TYPE_CHECKING, Any import numpy as np @@ -59,8 +59,8 @@ def empty(shape: NdShapeLike, dtype: npt.DTypeLike = np.float64) -> ndarray: @add_boilerplate("a") def empty_like( a: ndarray, - dtype: Optional[npt.DTypeLike] = None, - shape: Optional[NdShapeLike] = None, + dtype: npt.DTypeLike | None = None, + shape: NdShapeLike | None = None, ) -> ndarray: """ @@ -102,9 +102,9 @@ def empty_like( def eye( N: int, - M: Optional[int] = None, + M: int | None = None, k: int = 0, - dtype: Optional[npt.DTypeLike] = np.float64, + dtype: npt.DTypeLike | None = np.float64, ) -> ndarray: """ @@ -209,8 +209,8 @@ def ones(shape: NdShapeLike, dtype: npt.DTypeLike = np.float64) -> ndarray: def ones_like( a: ndarray, - dtype: Optional[npt.DTypeLike] = None, - shape: Optional[NdShapeLike] = None, + dtype: npt.DTypeLike | None = None, + shape: NdShapeLike | None = None, ) -> ndarray: """ @@ -278,8 +278,8 @@ def zeros(shape: NdShapeLike, dtype: npt.DTypeLike = np.float64) -> ndarray: def zeros_like( a: ndarray, - dtype: Optional[npt.DTypeLike] = None, - shape: Optional[NdShapeLike] = None, + dtype: npt.DTypeLike | None = None, + shape: NdShapeLike | None = None, ) -> ndarray: """ @@ -317,7 +317,7 @@ def zeros_like( def full( shape: NdShapeLike, value: Any, - dtype: Optional[npt.DTypeLike] = None, + dtype: npt.DTypeLike | None = None, ) -> ndarray: """ @@ -358,9 +358,9 @@ def full( def full_like( a: ndarray, - value: Union[int, float], - dtype: Optional[npt.DTypeLike] = None, - shape: Optional[NdShapeLike] = None, + value: int | float, + dtype: npt.DTypeLike | None = None, + shape: NdShapeLike | None = None, ) -> ndarray: """ diff --git a/cunumeric/_module/indexing.py b/cunumeric/_module/indexing.py index d7ed058101..96521fa72b 100644 --- a/cunumeric/_module/indexing.py +++ b/cunumeric/_module/indexing.py @@ -14,7 +14,7 @@ # from __future__ import annotations -from typing import TYPE_CHECKING, Any, Optional, Sequence, Union +from typing import TYPE_CHECKING, Any, Sequence import numpy as np from numpy.core.multiarray import ( # type: ignore [attr-defined] @@ -114,7 +114,7 @@ def place(arr: ndarray, mask: ndarray, vals: ndarray) -> None: # Indexing-like operations def indices( dimensions: Sequence[int], dtype: npt.DTypeLike = int, sparse: bool = False -) -> Union[ndarray, tuple[ndarray, ...]]: +) -> ndarray | tuple[ndarray, ...]: """ Return an array representing the indices of a grid. Compute an array where the subarrays contain index values 0, 1, ... @@ -132,7 +132,7 @@ def indices( Returns ------- - grid : ndarray or Tuple[ndarray, ...] + grid : ndarray or tuple[ndarray, ...] If sparse is False returns one array of grid indices, ``grid.shape = (len(dimensions),) + tuple(dimensions)``. If sparse is True returns a tuple of arrays, with @@ -302,7 +302,7 @@ def diag_indices_from(arr: ndarray) -> tuple[ndarray, ...]: def tril_indices( - n: int, k: int = 0, m: Optional[int] = None + n: int, k: int = 0, m: int | None = None ) -> tuple[ndarray, ...]: """ Return the indices for the lower-triangle of an (n, m) array. @@ -381,7 +381,7 @@ def tril_indices_from(arr: ndarray, k: int = 0) -> tuple[ndarray, ...]: def triu_indices( - n: int, k: int = 0, m: Optional[int] = None + n: int, k: int = 0, m: int | None = None ) -> tuple[ndarray, ...]: """ Return the indices for the upper-triangle of an (n, m) array. @@ -462,8 +462,8 @@ def triu_indices_from(arr: ndarray, k: int = 0) -> tuple[ndarray, ...]: def take( a: ndarray, indices: ndarray, - axis: Optional[int] = None, - out: Optional[ndarray] = None, + axis: int | None = None, + out: ndarray | None = None, mode: BoundsMode = "raise", ) -> ndarray: """ @@ -531,9 +531,7 @@ def _fill_fancy_index_for_along_axis_routines( @add_boilerplate("a", "indices") -def take_along_axis( - a: ndarray, indices: ndarray, axis: Union[int, None] -) -> ndarray: +def take_along_axis(a: ndarray, indices: ndarray, axis: int | None) -> ndarray: """ Take values from the input array by matching 1d index and data slices. @@ -597,7 +595,7 @@ def take_along_axis( @add_boilerplate("a", "indices", "values") def put_along_axis( - a: ndarray, indices: ndarray, values: ndarray, axis: Union[int, None] + a: ndarray, indices: ndarray, values: ndarray, axis: int | None ) -> None: """ Put values into the destination array by matching 1d index and data slices. @@ -677,7 +675,7 @@ def put_along_axis( def choose( a: ndarray, choices: Sequence[ndarray], - out: Optional[ndarray] = None, + out: ndarray | None = None, mode: BoundsMode = "raise", ) -> ndarray: """ @@ -824,8 +822,8 @@ def select( def compress( condition: ndarray, a: ndarray, - axis: Optional[int] = None, - out: Optional[ndarray] = None, + axis: int | None = None, + out: ndarray | None = None, ) -> ndarray: """ Return selected slices of an array along given axis. diff --git a/cunumeric/_module/linalg_mvp.py b/cunumeric/_module/linalg_mvp.py index 894dd3e150..dd764c04ec 100644 --- a/cunumeric/_module/linalg_mvp.py +++ b/cunumeric/_module/linalg_mvp.py @@ -17,7 +17,7 @@ import re from collections import Counter from itertools import chain -from typing import TYPE_CHECKING, Any, Literal, Optional, Union +from typing import TYPE_CHECKING, Any, Literal import numpy as np import opt_einsum as oe # type: ignore [import] @@ -46,7 +46,7 @@ @add_boilerplate("a", "b") -def inner(a: ndarray, b: ndarray, out: Optional[ndarray] = None) -> ndarray: +def inner(a: ndarray, b: ndarray, out: ndarray | None = None) -> ndarray: """ Inner product of two arrays. @@ -98,7 +98,7 @@ def inner(a: ndarray, b: ndarray, out: Optional[ndarray] = None) -> ndarray: @add_boilerplate("a", "b") -def dot(a: ndarray, b: ndarray, out: Optional[ndarray] = None) -> ndarray: +def dot(a: ndarray, b: ndarray, out: ndarray | None = None) -> ndarray: """ Dot product of two arrays. Specifically, @@ -160,10 +160,10 @@ def matmul( a: ndarray, b: ndarray, /, - out: Optional[ndarray] = None, + out: ndarray | None = None, *, casting: CastingKind = "same_kind", - dtype: Optional[np.dtype[Any]] = None, + dtype: np.dtype[Any] | None = None, ) -> ndarray: """ Matrix product of two arrays. @@ -259,7 +259,7 @@ def matmul( @add_boilerplate("a", "b") -def vdot(a: ndarray, b: ndarray, out: Optional[ndarray] = None) -> ndarray: +def vdot(a: ndarray, b: ndarray, out: ndarray | None = None) -> ndarray: """ Return the dot product of two vectors. @@ -305,7 +305,7 @@ def vdot(a: ndarray, b: ndarray, out: Optional[ndarray] = None) -> ndarray: @add_boilerplate("a", "b") -def outer(a: ndarray, b: ndarray, out: Optional[ndarray] = None) -> ndarray: +def outer(a: ndarray, b: ndarray, out: ndarray | None = None) -> ndarray: """ Compute the outer product of two vectors. @@ -352,7 +352,7 @@ def tensordot( a: ndarray, b: ndarray, axes: AxesPairLike = 2, - out: Optional[ndarray] = None, + out: ndarray | None = None, ) -> ndarray: """ Compute tensor dot product along specified axes. @@ -423,7 +423,7 @@ def __call__( inputs: list[set[str]], outputs: set[str], size_dict: dict[str, int], - memory_limit: Union[int, None] = None, + memory_limit: int | None = None, ) -> list[tuple[int, int]]: return [(0, 1)] + [(0, -1)] * (len(inputs) - 2) @@ -447,10 +447,10 @@ def _contract( b_modes: list[str], out_modes: list[str], a: ndarray, - b: Optional[ndarray] = None, - out: Optional[ndarray] = None, + b: ndarray | None = None, + out: ndarray | None = None, casting: CastingKind = "same_kind", - dtype: Optional[np.dtype[Any]] = None, + dtype: np.dtype[Any] | None = None, ) -> ndarray: # Sanity checks if len(a_modes) != a.ndim: @@ -664,10 +664,10 @@ def _contract( def einsum( expr: str, *operands: ndarray, - out: Optional[ndarray] = None, - dtype: Optional[np.dtype[Any]] = None, + out: ndarray | None = None, + dtype: np.dtype[Any] | None = None, casting: CastingKind = "safe", - optimize: Union[bool, Literal["greedy", "optimal"]] = True, + optimize: bool | Literal["greedy", "optimal"] = True, ) -> ndarray: """ Evaluates the Einstein summation convention on the operands. @@ -785,8 +785,8 @@ def einsum( def einsum_path( expr: str, *operands: ndarray, - optimize: Union[bool, list[Any], tuple[Any, ...], str] = "greedy", -) -> tuple[list[Union[str, int]], str]: + optimize: bool | list[Any] | tuple[Any, ...] | str = "greedy", +) -> tuple[list[str | int], str]: """ Evaluates the lowest cost contraction order for an einsum expression by considering the creation of intermediate arrays. @@ -821,7 +821,7 @@ def einsum_path( Returns ------- - path : list[Tuple[int,...]] + path : list[tuple[int,...]] A list representation of the einsum path. string_repr : str A printable representation of the einsum path. @@ -873,10 +873,10 @@ def einsum_path( def trace( a: ndarray, offset: int = 0, - axis1: Optional[int] = None, - axis2: Optional[int] = None, - dtype: Optional[np.dtype[Any]] = None, - out: Optional[ndarray] = None, + axis1: int | None = None, + axis2: int | None = None, + dtype: np.dtype[Any] | None = None, + out: ndarray | None = None, ) -> ndarray: """ Return the sum along diagonals of the array. diff --git a/cunumeric/_module/logic_array_type.py b/cunumeric/_module/logic_array_type.py index cde69de782..2c8553078c 100644 --- a/cunumeric/_module/logic_array_type.py +++ b/cunumeric/_module/logic_array_type.py @@ -14,7 +14,7 @@ # from __future__ import annotations -from typing import TYPE_CHECKING, Any, Union +from typing import TYPE_CHECKING, Any import numpy as np @@ -26,7 +26,7 @@ import numpy.typing as npt -def iscomplex(x: Union[ndarray, npt.NDArray[Any]]) -> ndarray: +def iscomplex(x: ndarray | npt.NDArray[Any]) -> ndarray: """ Returns a bool array, where True if input element is complex. @@ -60,7 +60,7 @@ def iscomplex(x: Union[ndarray, npt.NDArray[Any]]) -> ndarray: return x.imag != 0 -def iscomplexobj(x: Union[ndarray, npt.NDArray[Any]]) -> bool: +def iscomplexobj(x: ndarray | npt.NDArray[Any]) -> bool: """ Check for a complex type or an array of complex numbers. @@ -93,7 +93,7 @@ def iscomplexobj(x: Union[ndarray, npt.NDArray[Any]]) -> bool: return np.iscomplexobj(x) -def isreal(x: Union[ndarray, npt.NDArray[Any]]) -> ndarray: +def isreal(x: ndarray | npt.NDArray[Any]) -> ndarray: """ Returns a bool array, where True if input element is real. @@ -158,7 +158,7 @@ def isrealobj(x: ndarray) -> bool: return not iscomplexobj(x) -def isscalar(x: Union[ndarray, npt.NDArray[Any]]) -> bool: +def isscalar(x: ndarray | npt.NDArray[Any]) -> bool: """ Returns True if the type of `element` is a scalar type. diff --git a/cunumeric/_module/logic_comparison.py b/cunumeric/_module/logic_comparison.py index 996f4fd47e..9e8b25551d 100644 --- a/cunumeric/_module/logic_comparison.py +++ b/cunumeric/_module/logic_comparison.py @@ -14,7 +14,7 @@ # from __future__ import annotations -from typing import TYPE_CHECKING, Union +from typing import TYPE_CHECKING import numpy as np from legate.core import Scalar, types as ty @@ -162,7 +162,7 @@ def isclose( @add_boilerplate("a1", "a2") def array_equal( a1: ndarray, a2: ndarray, equal_nan: bool = False -) -> Union[bool, ndarray]: +) -> bool | ndarray: """ True if two arrays have the same shape and elements, False otherwise. diff --git a/cunumeric/_module/logic_truth.py b/cunumeric/_module/logic_truth.py index ce31794eb3..0f532b4143 100644 --- a/cunumeric/_module/logic_truth.py +++ b/cunumeric/_module/logic_truth.py @@ -14,7 +14,7 @@ # from __future__ import annotations -from typing import TYPE_CHECKING, Optional, Union +from typing import TYPE_CHECKING from .._array.util import add_boilerplate @@ -25,10 +25,10 @@ @add_boilerplate("a") def all( a: ndarray, - axis: Optional[Union[int, tuple[int, ...]]] = None, - out: Optional[ndarray] = None, + axis: int | tuple[int, ...] | None = None, + out: ndarray | None = None, keepdims: bool = False, - where: Optional[ndarray] = None, + where: ndarray | None = None, ) -> ndarray: """ Test whether all array elements along a given axis evaluate to True. @@ -83,10 +83,10 @@ def all( @add_boilerplate("a") def any( a: ndarray, - axis: Optional[Union[int, tuple[int, ...]]] = None, - out: Optional[ndarray] = None, + axis: int | tuple[int, ...] | None = None, + out: ndarray | None = None, keepdims: bool = False, - where: Optional[ndarray] = None, + where: ndarray | None = None, ) -> ndarray: """ Test whether any array element along a given axis evaluates to True. diff --git a/cunumeric/_module/math_extrema.py b/cunumeric/_module/math_extrema.py index 1b52c174d8..22a6450fdb 100644 --- a/cunumeric/_module/math_extrema.py +++ b/cunumeric/_module/math_extrema.py @@ -14,7 +14,7 @@ # from __future__ import annotations -from typing import TYPE_CHECKING, Any, Optional, Union +from typing import TYPE_CHECKING, Any import numpy as np @@ -28,12 +28,12 @@ @add_boilerplate("a") def amax( a: ndarray, - axis: Optional[Union[int, tuple[int, ...]]] = None, - dtype: Optional[np.dtype[Any]] = None, - out: Optional[ndarray] = None, + axis: int | tuple[int, ...] | None = None, + dtype: np.dtype[Any] | None = None, + out: ndarray | None = None, keepdims: bool = False, - initial: Optional[Union[int, float]] = None, - where: Optional[ndarray] = None, + initial: int | float | None = None, + where: ndarray | None = None, ) -> ndarray: """ @@ -105,12 +105,12 @@ def amax( @add_boilerplate("a") def amin( a: ndarray, - axis: Optional[Union[int, tuple[int, ...]]] = None, - dtype: Optional[np.dtype[Any]] = None, - out: Optional[ndarray] = None, + axis: int | tuple[int, ...] | None = None, + dtype: np.dtype[Any] | None = None, + out: ndarray | None = None, keepdims: bool = False, - initial: Optional[Union[int, float]] = None, - where: Optional[ndarray] = None, + initial: int | float | None = None, + where: ndarray | None = None, ) -> ndarray: """ diff --git a/cunumeric/_module/math_misc.py b/cunumeric/_module/math_misc.py index d29b1e8188..747a63604e 100644 --- a/cunumeric/_module/math_misc.py +++ b/cunumeric/_module/math_misc.py @@ -14,7 +14,7 @@ # from __future__ import annotations -from typing import TYPE_CHECKING, Any, Union +from typing import TYPE_CHECKING, Any from .._array.array import ndarray from .._array.util import add_boilerplate @@ -98,9 +98,9 @@ def convolve(a: ndarray, v: ndarray, mode: ConvolveMode = "full") -> ndarray: @add_boilerplate("a") def clip( a: ndarray, - a_min: Union[int, float, npt.ArrayLike, None], - a_max: Union[int, float, npt.ArrayLike, None], - out: Union[npt.NDArray[Any], ndarray, None] = None, + a_min: int | float | npt.ArrayLike | None, + a_max: int | float | npt.ArrayLike | None, + out: npt.NDArray[Any] | ndarray | None = None, ) -> ndarray: """ diff --git a/cunumeric/_module/math_sum_prod_diff.py b/cunumeric/_module/math_sum_prod_diff.py index 50f32f7d3c..8fa35961b5 100644 --- a/cunumeric/_module/math_sum_prod_diff.py +++ b/cunumeric/_module/math_sum_prod_diff.py @@ -14,7 +14,7 @@ # from __future__ import annotations -from typing import TYPE_CHECKING, Any, Optional, Union +from typing import TYPE_CHECKING, Any import numpy as np @@ -35,12 +35,12 @@ @add_boilerplate("a") def prod( a: ndarray, - axis: Optional[Union[int, tuple[int, ...]]] = None, - dtype: Optional[np.dtype[Any]] = None, - out: Optional[ndarray] = None, + axis: int | tuple[int, ...] | None = None, + dtype: np.dtype[Any] | None = None, + out: ndarray | None = None, keepdims: bool = False, - initial: Optional[Union[int, float]] = None, - where: Optional[ndarray] = None, + initial: int | float | None = None, + where: ndarray | None = None, ) -> ndarray: """ @@ -116,12 +116,12 @@ def prod( @add_boilerplate("a") def sum( a: ndarray, - axis: Optional[Union[int, tuple[int, ...]]] = None, - dtype: Optional[np.dtype[Any]] = None, - out: Optional[ndarray] = None, + axis: int | tuple[int, ...] | None = None, + dtype: np.dtype[Any] | None = None, + out: ndarray | None = None, keepdims: bool = False, - initial: Optional[Union[int, float]] = None, - where: Optional[ndarray] = None, + initial: int | float | None = None, + where: ndarray | None = None, ) -> ndarray: """ @@ -197,9 +197,9 @@ def sum( @add_boilerplate("a") def cumprod( a: ndarray, - axis: Optional[int] = None, - dtype: Optional[np.dtype[Any]] = None, - out: Optional[ndarray] = None, + axis: int | None = None, + dtype: np.dtype[Any] | None = None, + out: ndarray | None = None, ) -> ndarray: """ Return the cumulative product of the elements along a given axis. @@ -262,9 +262,9 @@ def cumprod( @add_boilerplate("a") def cumsum( a: ndarray, - axis: Optional[int] = None, - dtype: Optional[np.dtype[Any]] = None, - out: Optional[ndarray] = None, + axis: int | None = None, + dtype: np.dtype[Any] | None = None, + out: ndarray | None = None, ) -> ndarray: """ Return the cumulative sum of the elements along a given axis. @@ -319,9 +319,9 @@ def cumsum( @add_boilerplate("a") def nancumprod( a: ndarray, - axis: Optional[int] = None, - dtype: Optional[np.dtype[Any]] = None, - out: Optional[ndarray] = None, + axis: int | None = None, + dtype: np.dtype[Any] | None = None, + out: ndarray | None = None, ) -> ndarray: """ Return the cumulative product of the elements along a given axis treating @@ -380,9 +380,9 @@ def nancumprod( @add_boilerplate("a") def nancumsum( a: ndarray, - axis: Optional[int] = None, - dtype: Optional[np.dtype[Any]] = None, - out: Optional[ndarray] = None, + axis: int | None = None, + dtype: np.dtype[Any] | None = None, + out: ndarray | None = None, ) -> ndarray: """ Return the cumulative sum of the elements along a given axis treating Not a @@ -442,7 +442,7 @@ def nancumsum( def nanargmax( a: ndarray, axis: Any = None, - out: Union[ndarray, None] = None, + out: ndarray | None = None, *, keepdims: bool = False, ) -> ndarray: @@ -510,7 +510,7 @@ def nanargmax( def nanargmin( a: ndarray, axis: Any = None, - out: Union[ndarray, None] = None, + out: ndarray | None = None, *, keepdims: bool = False, ) -> ndarray: @@ -578,10 +578,10 @@ def nanargmin( def nanmin( a: ndarray, axis: Any = None, - out: Union[ndarray, None] = None, + out: ndarray | None = None, keepdims: bool = False, - initial: Optional[Union[int, float]] = None, - where: Optional[ndarray] = None, + initial: int | float | None = None, + where: ndarray | None = None, ) -> ndarray: """ Return minimum of an array or minimum along an axis, ignoring any @@ -671,10 +671,10 @@ def nanmin( def nanmax( a: ndarray, axis: Any = None, - out: Union[ndarray, None] = None, + out: ndarray | None = None, keepdims: bool = False, - initial: Optional[Union[int, float]] = None, - where: Optional[ndarray] = None, + initial: int | float | None = None, + where: ndarray | None = None, ) -> ndarray: """ Return the maximum of an array or maximum along an axis, ignoring any @@ -768,10 +768,10 @@ def nanprod( a: ndarray, axis: Any = None, dtype: Any = None, - out: Union[ndarray, None] = None, + out: ndarray | None = None, keepdims: bool = False, - initial: Optional[Union[int, float]] = None, - where: Optional[ndarray] = None, + initial: int | float | None = None, + where: ndarray | None = None, ) -> ndarray: """ Return the product of array elements over a given axis treating @@ -864,10 +864,10 @@ def nansum( a: ndarray, axis: Any = None, dtype: Any = None, - out: Union[ndarray, None] = None, + out: ndarray | None = None, keepdims: bool = False, - initial: Optional[Union[int, float]] = None, - where: Optional[ndarray] = None, + initial: int | float | None = None, + where: ndarray | None = None, ) -> ndarray: """ Return the sum of array elements over a given axis treating diff --git a/cunumeric/_module/sets_making.py b/cunumeric/_module/sets_making.py index a15aafc06d..4e1c6ef4d7 100644 --- a/cunumeric/_module/sets_making.py +++ b/cunumeric/_module/sets_making.py @@ -14,7 +14,7 @@ # from __future__ import annotations -from typing import TYPE_CHECKING, Optional +from typing import TYPE_CHECKING from .._array.util import add_boilerplate @@ -30,7 +30,7 @@ def unique( return_index: bool = False, return_inverse: bool = False, return_counts: bool = False, - axis: Optional[int] = None, + axis: int | None = None, ) -> ndarray: """ diff --git a/cunumeric/_module/ssc_counting.py b/cunumeric/_module/ssc_counting.py index 361166c0a1..879c220831 100644 --- a/cunumeric/_module/ssc_counting.py +++ b/cunumeric/_module/ssc_counting.py @@ -14,7 +14,7 @@ # from __future__ import annotations -from typing import TYPE_CHECKING, Optional, Union +from typing import TYPE_CHECKING from .._array.util import add_boilerplate @@ -24,8 +24,8 @@ @add_boilerplate("a") def count_nonzero( - a: ndarray, axis: Optional[Union[int, tuple[int, ...]]] = None -) -> Union[int, ndarray]: + a: ndarray, axis: int | tuple[int, ...] | None = None +) -> int | ndarray: """ Counts the number of non-zero values in the array ``a``. diff --git a/cunumeric/_module/ssc_searching.py b/cunumeric/_module/ssc_searching.py index dec8907a84..46b070cf0e 100644 --- a/cunumeric/_module/ssc_searching.py +++ b/cunumeric/_module/ssc_searching.py @@ -14,7 +14,7 @@ # from __future__ import annotations -from typing import TYPE_CHECKING, Optional, Union +from typing import TYPE_CHECKING from .._array.array import ndarray from .._array.thunk import perform_where @@ -28,10 +28,10 @@ @add_boilerplate("a") def searchsorted( a: ndarray, - v: Union[int, float, ndarray], + v: int | float | ndarray, side: SortSide = "left", - sorter: Optional[ndarray] = None, -) -> Union[int, ndarray]: + sorter: ndarray | None = None, +) -> int | ndarray: """ Find the indices into a sorted array a such that, if the corresponding @@ -69,8 +69,8 @@ def searchsorted( @add_boilerplate("a") def argmax( a: ndarray, - axis: Optional[int] = None, - out: Optional[ndarray] = None, + axis: int | None = None, + out: ndarray | None = None, *, keepdims: bool = False, ) -> ndarray: @@ -118,8 +118,8 @@ def argmax( @add_boilerplate("a") def argmin( a: ndarray, - axis: Optional[int] = None, - out: Optional[ndarray] = None, + axis: int | None = None, + out: ndarray | None = None, *, keepdims: bool = False, ) -> ndarray: @@ -196,8 +196,8 @@ def flatnonzero(a: ndarray) -> ndarray: @add_boilerplate("a", "x", "y") def where( - a: ndarray, x: Optional[ndarray] = None, y: Optional[ndarray] = None -) -> Union[ndarray, tuple[ndarray, ...]]: + a: ndarray, x: ndarray | None = None, y: ndarray | None = None +) -> ndarray | tuple[ndarray, ...]: """ where(condition, [x, y]) diff --git a/cunumeric/_module/ssc_sorting.py b/cunumeric/_module/ssc_sorting.py index 84a6d7b172..19add154f2 100644 --- a/cunumeric/_module/ssc_sorting.py +++ b/cunumeric/_module/ssc_sorting.py @@ -14,7 +14,7 @@ # from __future__ import annotations -from typing import TYPE_CHECKING, Optional, Sequence, Union +from typing import TYPE_CHECKING, Sequence import numpy as np @@ -28,9 +28,9 @@ @add_boilerplate("a") def argsort( a: ndarray, - axis: Union[int, None] = -1, + axis: int | None = -1, kind: SortType = "quicksort", - order: Optional[Union[str, list[str]]] = None, + order: str | list[str] | None = None, ) -> ndarray: """ @@ -100,9 +100,9 @@ def msort(a: ndarray) -> ndarray: @add_boilerplate("a") def sort( a: ndarray, - axis: Union[int, None] = -1, + axis: int | None = -1, kind: SortType = "quicksort", - order: Optional[Union[str, list[str]]] = None, + order: str | list[str] | None = None, ) -> ndarray: """ @@ -185,10 +185,10 @@ def sort_complex(a: ndarray) -> ndarray: @add_boilerplate("a") def argpartition( a: ndarray, - kth: Union[int, Sequence[int]], - axis: Union[int, None] = -1, + kth: int | Sequence[int], + axis: int | None = -1, kind: SelectKind = "introselect", - order: Optional[Union[str, list[str]]] = None, + order: str | list[str] | None = None, ) -> ndarray: """ @@ -241,10 +241,10 @@ def argpartition( @add_boilerplate("a") def partition( a: ndarray, - kth: Union[int, Sequence[int]], - axis: Union[int, None] = -1, + kth: int | Sequence[int], + axis: int | None = -1, kind: SelectKind = "introselect", - order: Optional[Union[str, list[str]]] = None, + order: str | list[str] | None = None, ) -> ndarray: """ diff --git a/cunumeric/_module/stats_avgs_vars.py b/cunumeric/_module/stats_avgs_vars.py index a0064f96ea..3c6b02e066 100644 --- a/cunumeric/_module/stats_avgs_vars.py +++ b/cunumeric/_module/stats_avgs_vars.py @@ -14,7 +14,7 @@ # from __future__ import annotations -from typing import TYPE_CHECKING, Any, Optional, Union +from typing import TYPE_CHECKING, Any import numpy as np @@ -27,11 +27,11 @@ @add_boilerplate("a") def mean( a: ndarray, - axis: Optional[Union[int, tuple[int, ...]]] = None, - dtype: Optional[np.dtype[Any]] = None, - out: Optional[ndarray] = None, + axis: int | tuple[int, ...] | None = None, + dtype: np.dtype[Any] | None = None, + out: ndarray | None = None, keepdims: bool = False, - where: Optional[ndarray] = None, + where: ndarray | None = None, ) -> ndarray: """ @@ -99,11 +99,11 @@ def mean( @add_boilerplate("a") def nanmean( a: ndarray, - axis: Optional[Union[int, tuple[int, ...]]] = None, - dtype: Optional[np.dtype[Any]] = None, - out: Optional[ndarray] = None, + axis: int | tuple[int, ...] | None = None, + dtype: np.dtype[Any] | None = None, + out: ndarray | None = None, keepdims: bool = False, - where: Optional[ndarray] = None, + where: ndarray | None = None, ) -> ndarray: """ @@ -166,13 +166,13 @@ def nanmean( @add_boilerplate("a") def var( a: ndarray, - axis: Optional[Union[int, tuple[int, ...]]] = None, - dtype: Optional[np.dtype[Any]] = None, - out: Optional[ndarray] = None, + axis: int | tuple[int, ...] | None = None, + dtype: np.dtype[Any] | None = None, + out: ndarray | None = None, ddof: int = 0, keepdims: bool = False, *, - where: Union[ndarray, None] = None, + where: ndarray | None = None, ) -> ndarray: """ Compute the variance along the specified axis. diff --git a/cunumeric/_module/stats_histograms.py b/cunumeric/_module/stats_histograms.py index c4f426e079..0415598bd3 100644 --- a/cunumeric/_module/stats_histograms.py +++ b/cunumeric/_module/stats_histograms.py @@ -14,7 +14,7 @@ # from __future__ import annotations -from typing import TYPE_CHECKING, Any, Optional, Union +from typing import TYPE_CHECKING, Any import numpy as np @@ -33,7 +33,7 @@ @add_boilerplate("x", "weights") def bincount( - x: ndarray, weights: Optional[ndarray] = None, minlength: int = 0 + x: ndarray, weights: ndarray | None = None, minlength: int = 0 ) -> ndarray: """ bincount(x, weights=None, minlength=0) @@ -132,9 +132,9 @@ def bincount( @add_boilerplate("x", "weights") def histogram( x: ndarray, - bins: Union[ndarray, npt.ArrayLike, int] = 10, - range: Optional[Union[tuple[int, int], tuple[float, float]]] = None, - weights: Optional[ndarray] = None, + bins: ndarray | npt.ArrayLike | int = 10, + range: tuple[int, int] | tuple[float, float] | None = None, + weights: ndarray | None = None, density: bool = False, ) -> tuple[ndarray, ndarray]: """ diff --git a/cunumeric/_module/stats_order.py b/cunumeric/_module/stats_order.py index 1989786cd7..d1ba5a01c8 100644 --- a/cunumeric/_module/stats_order.py +++ b/cunumeric/_module/stats_order.py @@ -15,7 +15,7 @@ from __future__ import annotations import math -from typing import TYPE_CHECKING, Any, Iterable, Optional, Union +from typing import TYPE_CHECKING, Any, Iterable import numpy as np @@ -317,13 +317,13 @@ def _nearest(q: float, n: int) -> tuple[float, int]: def _quantile_impl( arr: ndarray, q_arr: npt.NDArray[Any], - axis: Optional[int], + axis: int | None, axes_set: Iterable[int], original_shape: tuple[int, ...], method: Callable[[float, int], tuple[float, int]], keepdims: bool, to_dtype: np.dtype[Any], - qs_all: Optional[ndarray], + qs_all: ndarray | None, ) -> ndarray: ndims = len(arr.shape) @@ -407,9 +407,9 @@ def _quantile_impl( @add_boilerplate("a") def quantile( a: ndarray, - q: Union[float, Iterable[float], ndarray], - axis: Union[None, int, tuple[int, ...]] = None, - out: Optional[ndarray] = None, + q: float | Iterable[float] | ndarray, + axis: int | tuple[int, ...] | None = None, + out: ndarray | None = None, overwrite_input: bool = False, method: str = "linear", keepdims: bool = False, @@ -507,7 +507,7 @@ def quantile( "nearest": _nearest, } - real_axis: Optional[int] + real_axis: int | None axes_set: Iterable[int] = [] original_shape = a.shape @@ -602,9 +602,9 @@ def quantile( @add_boilerplate("a") def percentile( a: ndarray, - q: Union[float, Iterable[float], ndarray], - axis: Union[None, int, tuple[int, ...]] = None, - out: Optional[ndarray] = None, + q: float | Iterable[float] | ndarray, + axis: int | tuple[int, ...] | None = None, + out: ndarray | None = None, overwrite_input: bool = False, method: str = "linear", keepdims: bool = False, diff --git a/cunumeric/_sphinxext/_comparison_util.py b/cunumeric/_sphinxext/_comparison_util.py index 2587bed7b3..c5f8474a5c 100644 --- a/cunumeric/_sphinxext/_comparison_util.py +++ b/cunumeric/_sphinxext/_comparison_util.py @@ -16,7 +16,7 @@ from dataclasses import dataclass from types import ModuleType -from typing import TYPE_CHECKING, Any, Iterable, Iterator, Type, Union +from typing import TYPE_CHECKING, Any, Iterable, Iterator, Type from .._utils.coverage import is_implemented, is_multi, is_single from ._comparison_config import MISSING_NP_REFS, SKIP @@ -75,7 +75,7 @@ def _lgref(name: str, obj: Any, implemented: bool) -> str: def filter_names( obj: Any, - types: Union[tuple[Type[Any], ...], None] = None, + types: tuple[Type[Any], ...] | None = None, skip: Iterable[str] = (), ) -> Iterator[str]: names = (n for n in dir(obj)) # every name in the module or class @@ -106,7 +106,7 @@ def get_item(name: str, np_obj: Any, lg_obj: Any) -> ItemDetail: ) -def get_namespaces(attr: Union[str, None]) -> tuple[Any, Any]: +def get_namespaces(attr: str | None) -> tuple[Any, Any]: import numpy import cunumeric diff --git a/cunumeric/_thunk/_sort.py b/cunumeric/_thunk/_sort.py index c55edb1f73..e194bd3f4e 100644 --- a/cunumeric/_thunk/_sort.py +++ b/cunumeric/_thunk/_sort.py @@ -14,7 +14,7 @@ # from __future__ import annotations -from typing import TYPE_CHECKING, Union, cast +from typing import TYPE_CHECKING, cast from legate.core import get_legate_runtime, types as ty from numpy.core.multiarray import ( # type: ignore [attr-defined] @@ -121,7 +121,7 @@ def sort_deferred( output: DeferredArray, input: DeferredArray, argsort: bool, - axis: Union[int, None] = -1, + axis: int | None = -1, stable: bool = False, ) -> None: if axis is None and input.ndim > 1: diff --git a/cunumeric/_thunk/deferred.py b/cunumeric/_thunk/deferred.py index 649255eecf..fa496354d9 100644 --- a/cunumeric/_thunk/deferred.py +++ b/cunumeric/_thunk/deferred.py @@ -25,12 +25,9 @@ TYPE_CHECKING, Any, Callable, - Dict, - Optional, ParamSpec, Sequence, TypeVar, - Union, cast, ) @@ -145,7 +142,7 @@ def wrapper(*args: Any, **kwargs: Any) -> R: return decorator -_UNARY_RED_TO_REDUCTION_OPS: Dict[int, int] = { +_UNARY_RED_TO_REDUCTION_OPS: dict[int, int] = { UnaryRedCode.SUM: ReductionOp.ADD, UnaryRedCode.SUM_SQUARES: ReductionOp.ADD, UnaryRedCode.VARIANCE: ReductionOp.ADD, @@ -169,7 +166,7 @@ def wrapper(*args: Any, **kwargs: Any) -> R: def max_identity( ty: np.dtype[Any], -) -> Union[int, np.floating[Any], bool, np.complexfloating[Any, Any]]: +) -> int | np.floating[Any] | bool | np.complexfloating[Any, Any]: if ty.kind == "i" or ty.kind == "u": return np.iinfo(ty).min elif ty.kind == "f": @@ -184,7 +181,7 @@ def max_identity( def min_identity( ty: np.dtype[Any], -) -> Union[int, np.floating[Any], bool, np.complexfloating[Any, Any]]: +) -> int | np.floating[Any] | bool | np.complexfloating[Any, Any]: if ty.kind == "i" or ty.kind == "u": return np.iinfo(ty).max elif ty.kind == "f": @@ -197,7 +194,7 @@ def min_identity( raise ValueError(f"Unsupported dtype: {ty}") -_UNARY_RED_IDENTITIES: Dict[UnaryRedCode, Callable[[Any], Any]] = { +_UNARY_RED_IDENTITIES: dict[UnaryRedCode, Callable[[Any], Any]] = { UnaryRedCode.SUM: lambda _: 0, UnaryRedCode.SUM_SQUARES: lambda _: 0, UnaryRedCode.VARIANCE: lambda _: 0, @@ -243,7 +240,7 @@ class DeferredArray(NumPyThunk): def __init__( self, base: LogicalStore, - numpy_array: Optional[npt.NDArray[Any]] = None, + numpy_array: npt.NDArray[Any] | None = None, ) -> None: super().__init__(base.type.to_numpy_dtype()) assert base is not None @@ -585,7 +582,7 @@ def _advanced_indexing_with_boolean_array( self, key: Any, is_set: bool = False, - set_value: Optional[Any] = None, + set_value: Any | None = None, ) -> tuple[bool, Any, Any, Any]: rhs = self if not isinstance(key, DeferredArray): @@ -702,7 +699,7 @@ def _create_indexing_array( self, key: Any, is_set: bool = False, - set_value: Optional[Any] = None, + set_value: Any | None = None, ) -> tuple[bool, Any, Any, Any]: is_bool_array, lhs, bool_key = self._has_single_boolean_array( key, is_set @@ -1229,9 +1226,7 @@ def reshape(self, newshape: NdShape, order: OrderType) -> NumPyThunk: return result - def squeeze( - self, axis: Optional[Union[int, tuple[int, ...]]] - ) -> DeferredArray: + def squeeze(self, axis: int | tuple[int, ...] | None) -> DeferredArray: result = self.base if axis is None: shift = 0 @@ -1896,7 +1891,7 @@ def arange(self, start: float, stop: float, step: float) -> None: # Tile the src array onto the destination array @auto_convert("rhs") - def tile(self, rhs: Any, reps: Union[Any, Sequence[int]]) -> None: + def tile(self, rhs: Any, reps: Any | Sequence[int]) -> None: src_array = rhs dst_array = self assert src_array.ndim <= dst_array.ndim @@ -1918,7 +1913,7 @@ def tile(self, rhs: Any, reps: Union[Any, Sequence[int]]) -> None: # Transpose the matrix dimensions def transpose( - self, axes: Union[None, tuple[int, ...], list[int]] + self, axes: tuple[int, ...] | list[int] | None ) -> DeferredArray: computed_axes = tuple(axes) if axes is not None else () result = self.base.transpose(computed_axes) @@ -1970,7 +1965,7 @@ def repeat( return out @auto_convert("rhs") - def flip(self, rhs: Any, axes: Union[None, int, tuple[int, ...]]) -> None: + def flip(self, rhs: Any, axes: int | tuple[int, ...] | None) -> None: input = rhs.base output = self.base @@ -1993,7 +1988,7 @@ def flip(self, rhs: Any, axes: Union[None, int, tuple[int, ...]]) -> None: # Perform a bin count operation on the array @auto_convert("rhs", "weights") - def bincount(self, rhs: Any, weights: Optional[NumPyThunk] = None) -> None: + def bincount(self, rhs: Any, weights: NumPyThunk | None = None) -> None: weight_array = weights src_array = rhs dst_array = self @@ -2041,7 +2036,7 @@ def bitgenerator_random_raw( self, handle: int, generatorType: BitGeneratorType, - seed: Union[int, None], + seed: int | None, flags: int, ) -> None: task = legate_runtime.create_auto_task( @@ -2065,7 +2060,7 @@ def bitgenerator_distribution( self, handle: int, generatorType: BitGeneratorType, - seed: Union[int, None], + seed: int | None, flags: int, distribution: BitGeneratorDistribution, intparams: tuple[int, ...], @@ -2097,7 +2092,7 @@ def bitgenerator_integers( self, handle: int, generatorType: BitGeneratorType, - seed: Union[int, None], + seed: int | None, flags: int, low: int, high: int, @@ -2121,7 +2116,7 @@ def bitgenerator_uniform( self, handle: int, generatorType: BitGeneratorType, - seed: Union[int, None], + seed: int | None, flags: int, low: float, high: float, @@ -2155,7 +2150,7 @@ def bitgenerator_lognormal( self, handle: int, generatorType: BitGeneratorType, - seed: Union[int, None], + seed: int | None, flags: int, mean: float, sigma: float, @@ -2189,7 +2184,7 @@ def bitgenerator_normal( self, handle: int, generatorType: BitGeneratorType, - seed: Union[int, None], + seed: int | None, flags: int, mean: float, sigma: float, @@ -2223,7 +2218,7 @@ def bitgenerator_poisson( self, handle: int, generatorType: BitGeneratorType, - seed: Union[int, None], + seed: int | None, flags: int, lam: float, ) -> None: @@ -2249,7 +2244,7 @@ def bitgenerator_exponential( self, handle: int, generatorType: BitGeneratorType, - seed: Union[int, None], + seed: int | None, flags: int, scale: float, ) -> None: @@ -2282,7 +2277,7 @@ def bitgenerator_gumbel( self, handle: int, generatorType: BitGeneratorType, - seed: Union[int, None], + seed: int | None, flags: int, mu: float, beta: float, @@ -2316,7 +2311,7 @@ def bitgenerator_laplace( self, handle: int, generatorType: BitGeneratorType, - seed: Union[int, None], + seed: int | None, flags: int, mu: float, beta: float, @@ -2350,7 +2345,7 @@ def bitgenerator_logistic( self, handle: int, generatorType: BitGeneratorType, - seed: Union[int, None], + seed: int | None, flags: int, mu: float, beta: float, @@ -2384,7 +2379,7 @@ def bitgenerator_pareto( self, handle: int, generatorType: BitGeneratorType, - seed: Union[int, None], + seed: int | None, flags: int, alpha: float, ) -> None: @@ -2417,7 +2412,7 @@ def bitgenerator_power( self, handle: int, generatorType: BitGeneratorType, - seed: Union[int, None], + seed: int | None, flags: int, alpha: float, ) -> None: @@ -2450,7 +2445,7 @@ def bitgenerator_rayleigh( self, handle: int, generatorType: BitGeneratorType, - seed: Union[int, None], + seed: int | None, flags: int, sigma: float, ) -> None: @@ -2483,7 +2478,7 @@ def bitgenerator_cauchy( self, handle: int, generatorType: BitGeneratorType, - seed: Union[int, None], + seed: int | None, flags: int, x0: float, gamma: float, @@ -2517,7 +2512,7 @@ def bitgenerator_triangular( self, handle: int, generatorType: BitGeneratorType, - seed: Union[int, None], + seed: int | None, flags: int, a: float, b: float, @@ -2552,7 +2547,7 @@ def bitgenerator_weibull( self, handle: int, generatorType: BitGeneratorType, - seed: Union[int, None], + seed: int | None, flags: int, lam: float, k: float, @@ -2586,7 +2581,7 @@ def bitgenerator_bytes( self, handle: int, generatorType: BitGeneratorType, - seed: Union[int, None], + seed: int | None, flags: int, ) -> None: if self.dtype == np.uint8: @@ -2608,7 +2603,7 @@ def bitgenerator_beta( self, handle: int, generatorType: BitGeneratorType, - seed: Union[int, None], + seed: int | None, flags: int, a: float, b: float, @@ -2642,7 +2637,7 @@ def bitgenerator_f( self, handle: int, generatorType: BitGeneratorType, - seed: Union[int, None], + seed: int | None, flags: int, dfnum: float, dfden: float, @@ -2676,7 +2671,7 @@ def bitgenerator_logseries( self, handle: int, generatorType: BitGeneratorType, - seed: Union[int, None], + seed: int | None, flags: int, p: float, ) -> None: @@ -2699,7 +2694,7 @@ def bitgenerator_noncentral_f( self, handle: int, generatorType: BitGeneratorType, - seed: Union[int, None], + seed: int | None, flags: int, dfnum: float, dfden: float, @@ -2734,7 +2729,7 @@ def bitgenerator_chisquare( self, handle: int, generatorType: BitGeneratorType, - seed: Union[int, None], + seed: int | None, flags: int, df: float, nonc: float, @@ -2768,7 +2763,7 @@ def bitgenerator_gamma( self, handle: int, generatorType: BitGeneratorType, - seed: Union[int, None], + seed: int | None, flags: int, k: float, theta: float, @@ -2802,7 +2797,7 @@ def bitgenerator_standard_t( self, handle: int, generatorType: BitGeneratorType, - seed: Union[int, None], + seed: int | None, flags: int, df: float, ) -> None: @@ -2835,7 +2830,7 @@ def bitgenerator_hypergeometric( self, handle: int, generatorType: BitGeneratorType, - seed: Union[int, None], + seed: int | None, flags: int, ngood: int, nbad: int, @@ -2863,7 +2858,7 @@ def bitgenerator_vonmises( self, handle: int, generatorType: BitGeneratorType, - seed: Union[int, None], + seed: int | None, flags: int, mu: float, kappa: float, @@ -2897,7 +2892,7 @@ def bitgenerator_zipf( self, handle: int, generatorType: BitGeneratorType, - seed: Union[int, None], + seed: int | None, flags: int, alpha: float, ) -> None: @@ -2921,7 +2916,7 @@ def bitgenerator_geometric( self, handle: int, generatorType: BitGeneratorType, - seed: Union[int, None], + seed: int | None, flags: int, p: float, ) -> None: @@ -2947,7 +2942,7 @@ def bitgenerator_wald( self, handle: int, generatorType: BitGeneratorType, - seed: Union[int, None], + seed: int | None, flags: int, mean: float, scale: float, @@ -2981,7 +2976,7 @@ def bitgenerator_binomial( self, handle: int, generatorType: BitGeneratorType, - seed: Union[int, None], + seed: int | None, flags: int, ntrials: int, p: float, @@ -3009,7 +3004,7 @@ def bitgenerator_negative_binomial( self, handle: int, generatorType: BitGeneratorType, - seed: Union[int, None], + seed: int | None, flags: int, ntrials: int, p: float, @@ -3058,8 +3053,8 @@ def random_normal(self) -> None: def random_integer( self, - low: Union[int, npt.NDArray[Any]], - high: Union[int, npt.NDArray[Any]], + low: int | npt.NDArray[Any], + high: int | npt.NDArray[Any], ) -> None: assert self.dtype.kind == "i" args = (Scalar(low, self.base.type), Scalar(high, self.base.type)) @@ -3073,7 +3068,7 @@ def unary_op( src: Any, where: Any, args: tuple[Scalar, ...] = (), - multiout: Optional[Any] = None, + multiout: Any | None = None, ) -> None: lhs = self.base src = src._copy_if_overlapping(self) @@ -3107,13 +3102,13 @@ def unary_reduction( op: UnaryRedCode, src: Any, where: Any, - orig_axis: Union[int, None], + orig_axis: int | None, axes: tuple[int, ...], keepdims: bool, args: tuple[Scalar, ...], initial: Any, ) -> None: - lhs_array: Union[NumPyThunk, DeferredArray] = self + lhs_array: NumPyThunk | DeferredArray = self rhs_array = src assert lhs_array.ndim <= rhs_array.ndim @@ -3276,7 +3271,7 @@ def binary_reduction( op: BinaryOpCode, src1: Any, src2: Any, - broadcast: Union[NdShape, None], + broadcast: NdShape | None, args: tuple[Scalar, ...], ) -> None: lhs = self.base @@ -3370,7 +3365,7 @@ def scan( op: int, rhs: Any, axis: int, - dtype: Optional[npt.DTypeLike], + dtype: npt.DTypeLike | None, nan_to_identity: bool, ) -> None: # local sum @@ -3479,9 +3474,9 @@ def sort( self, rhs: Any, argsort: bool = False, - axis: Union[int, None] = -1, + axis: int | None = -1, kind: SortType = "quicksort", - order: Union[None, str, list[str]] = None, + order: str | list[str] | None = None, ) -> None: if kind == "stable": stable = True @@ -3502,11 +3497,11 @@ def sort( def partition( self, rhs: Any, - kth: Union[int, Sequence[int]], + kth: int | Sequence[int], argpartition: bool = False, - axis: Union[int, None] = -1, + axis: int | None = -1, kind: SelectKind = "introselect", - order: Union[None, str, list[str]] = None, + order: str | list[str] | None = None, ) -> None: if order is not None: raise NotImplementedError( @@ -3531,9 +3526,7 @@ def create_window(self, op_code: WindowOpCode, M: int, *args: Any) -> None: task.execute() @auto_convert("src") - def packbits( - self, src: Any, axis: Union[int, None], bitorder: BitOrder - ) -> None: + def packbits(self, src: Any, axis: int | None, bitorder: BitOrder) -> None: bitorder_code = getattr(Bitorder, bitorder.upper()) task = legate_runtime.create_auto_task( self.library, CuNumericOpCode.PACKBITS @@ -3550,7 +3543,7 @@ def packbits( @auto_convert("src") def unpackbits( - self, src: Any, axis: Union[int, None], bitorder: BitOrder + self, src: Any, axis: int | None, bitorder: BitOrder ) -> None: bitorder_code = getattr(Bitorder, bitorder.upper()) task = legate_runtime.create_auto_task( diff --git a/cunumeric/_thunk/eager.py b/cunumeric/_thunk/eager.py index 7de73a100b..3eab040607 100644 --- a/cunumeric/_thunk/eager.py +++ b/cunumeric/_thunk/eager.py @@ -14,17 +14,7 @@ # from __future__ import annotations -from typing import ( - TYPE_CHECKING, - Any, - Callable, - Dict, - Iterable, - Optional, - Sequence, - Union, - cast, -) +from typing import TYPE_CHECKING, Any, Callable, Iterable, Sequence, cast import numpy as np from legate.core import Scalar @@ -63,7 +53,7 @@ ) -_UNARY_OPS: Dict[UnaryOpCode, Any] = { +_UNARY_OPS: dict[UnaryOpCode, Any] = { UnaryOpCode.ABSOLUTE: np.absolute, UnaryOpCode.ARCCOS: np.arccos, UnaryOpCode.ARCCOSH: np.arccosh, @@ -110,7 +100,7 @@ # Unary reduction operations that don't return the argument of the # reduction operation -_UNARY_RED_OPS_WITHOUT_ARG: Dict[UnaryRedCode, Any] = { +_UNARY_RED_OPS_WITHOUT_ARG: dict[UnaryRedCode, Any] = { UnaryRedCode.ALL: np.all, UnaryRedCode.ANY: np.any, UnaryRedCode.MAX: np.max, @@ -125,14 +115,14 @@ # Unary reduction operations that return the argument of the # reduction operation -_UNARY_RED_OPS_WITH_ARG: Dict[UnaryRedCode, Any] = { +_UNARY_RED_OPS_WITH_ARG: dict[UnaryRedCode, Any] = { UnaryRedCode.ARGMIN: np.argmin, UnaryRedCode.ARGMAX: np.argmax, UnaryRedCode.NANARGMAX: np.nanargmax, UnaryRedCode.NANARGMIN: np.nanargmin, } -_BINARY_OPS: Dict[BinaryOpCode, Any] = { +_BINARY_OPS: dict[BinaryOpCode, Any] = { BinaryOpCode.ADD: np.add, BinaryOpCode.ARCTAN2: np.arctan2, BinaryOpCode.BITWISE_AND: np.bitwise_and, @@ -169,12 +159,10 @@ BinaryOpCode.SUBTRACT: np.subtract, } -_WINDOW_OPS: Dict[ +_WINDOW_OPS: dict[ WindowOpCode, - Union[ - Callable[[float], npt.NDArray[Any]], - Callable[[float, float], npt.NDArray[Any]], - ], + Callable[[float], npt.NDArray[Any]] + | Callable[[float, float], npt.NDArray[Any]], ] = { WindowOpCode.BARLETT: np.bartlett, WindowOpCode.BLACKMAN: np.blackman, @@ -221,18 +209,18 @@ class EagerArray(NumPyThunk): def __init__( self, val: npt.ArrayLike, - parent: Optional[EagerArray] = None, - key: Optional[tuple[Any, ...]] = None, + parent: EagerArray | None = None, + key: tuple[Any, ...] | None = None, ) -> None: array = np.asarray(val) super().__init__(array.dtype) self.array: npt.NDArray[Any] = array - self.parent: Optional[EagerArray] = parent + self.parent: EagerArray | None = parent self.children: list[EagerArray] = [] - self.key: Optional[tuple[Any, ...]] = key + self.key: tuple[Any, ...] | None = key #: if this ever becomes set (to a DeferredArray), we forward all #: operations to it - self.deferred: Optional[DeferredArray] = None + self.deferred: DeferredArray | None = None self.escaped = False @property @@ -470,7 +458,7 @@ def reshape(self, newshape: NdShape, order: OrderType) -> NumPyThunk: self.children.append(result) return result - def squeeze(self, axis: Optional[int]) -> NumPyThunk: + def squeeze(self, axis: int | None) -> NumPyThunk: if self.deferred is not None: return self.deferred.squeeze(axis) # See https://github.com/numpy/numpy/issues/22019 @@ -526,7 +514,7 @@ def fill(self, value: Any) -> None: else: self.array.fill(value) - def transpose(self, axes: Union[tuple[int, ...], list[int]]) -> NumPyThunk: + def transpose(self, axes: tuple[int, ...] | list[int]) -> NumPyThunk: if self.deferred is not None: return self.deferred.transpose(axes) # See https://github.com/numpy/numpy/issues/22019 @@ -555,7 +543,7 @@ def repeat( array = np.repeat(self.array, repeats, axis) return EagerArray(array) - def flip(self, rhs: Any, axes: Union[None, int, tuple[int, ...]]) -> None: + def flip(self, rhs: Any, axes: int | tuple[int, ...] | None) -> None: self.check_eager_args(rhs) if self.deferred is not None: self.deferred.flip(rhs, axes) @@ -690,14 +678,14 @@ def arange(self, start: float, stop: float, step: float) -> None: else: self.array = np.arange(start, stop, step, self.dtype) - def tile(self, rhs: Any, reps: Union[int, Sequence[int]]) -> None: + def tile(self, rhs: Any, reps: int | Sequence[int]) -> None: self.check_eager_args(rhs) if self.deferred is not None: self.deferred.tile(rhs, reps) else: self.array[:] = np.tile(rhs.array, reps) - def bincount(self, rhs: Any, weights: Optional[NumPyThunk] = None) -> None: + def bincount(self, rhs: Any, weights: NumPyThunk | None = None) -> None: self.check_eager_args(rhs, weights) if self.deferred is not None: self.deferred.bincount(rhs, weights=weights) @@ -729,9 +717,9 @@ def sort( self, rhs: Any, argsort: bool = False, - axis: Union[int, None] = -1, + axis: int | None = -1, kind: SortType = "quicksort", - order: Union[None, str, list[str]] = None, + order: str | list[str] | None = None, ) -> None: self.check_eager_args(rhs) if self.deferred is not None: @@ -746,7 +734,7 @@ def bitgenerator_random_raw( self, handle: int, generatorType: BitGeneratorType, - seed: Union[int, None], + seed: int | None, flags: int, ) -> None: if self.deferred is not None: @@ -769,7 +757,7 @@ def bitgenerator_integers( self, handle: int, generatorType: BitGeneratorType, - seed: Union[int, None], + seed: int | None, flags: int, low: int, high: int, @@ -789,7 +777,7 @@ def bitgenerator_lognormal( self, handle: int, generatorType: BitGeneratorType, - seed: Union[int, None], + seed: int | None, flags: int, mean: float, sigma: float, @@ -809,7 +797,7 @@ def bitgenerator_normal( self, handle: int, generatorType: BitGeneratorType, - seed: Union[int, None], + seed: int | None, flags: int, mean: float, sigma: float, @@ -829,7 +817,7 @@ def bitgenerator_uniform( self, handle: int, generatorType: BitGeneratorType, - seed: Union[int, None], + seed: int | None, flags: int, low: float, high: float, @@ -849,7 +837,7 @@ def bitgenerator_poisson( self, handle: int, generatorType: BitGeneratorType, - seed: Union[int, None], + seed: int | None, flags: int, lam: float, ) -> None: @@ -868,7 +856,7 @@ def bitgenerator_exponential( self, handle: int, generatorType: BitGeneratorType, - seed: Union[int, None], + seed: int | None, flags: int, scale: float, ) -> None: @@ -887,7 +875,7 @@ def bitgenerator_gumbel( self, handle: int, generatorType: BitGeneratorType, - seed: Union[int, None], + seed: int | None, flags: int, mu: float, beta: float, @@ -907,7 +895,7 @@ def bitgenerator_laplace( self, handle: int, generatorType: BitGeneratorType, - seed: Union[int, None], + seed: int | None, flags: int, mu: float, beta: float, @@ -927,7 +915,7 @@ def bitgenerator_logistic( self, handle: int, generatorType: BitGeneratorType, - seed: Union[int, None], + seed: int | None, flags: int, mu: float, beta: float, @@ -947,7 +935,7 @@ def bitgenerator_pareto( self, handle: int, generatorType: BitGeneratorType, - seed: Union[int, None], + seed: int | None, flags: int, alpha: float, ) -> None: @@ -966,7 +954,7 @@ def bitgenerator_power( self, handle: int, generatorType: BitGeneratorType, - seed: Union[int, None], + seed: int | None, flags: int, alpha: float, ) -> None: @@ -985,7 +973,7 @@ def bitgenerator_rayleigh( self, handle: int, generatorType: BitGeneratorType, - seed: Union[int, None], + seed: int | None, flags: int, sigma: float, ) -> None: @@ -1004,7 +992,7 @@ def bitgenerator_cauchy( self, handle: int, generatorType: BitGeneratorType, - seed: Union[int, None], + seed: int | None, flags: int, x0: float, gamma: float, @@ -1024,7 +1012,7 @@ def bitgenerator_triangular( self, handle: int, generatorType: BitGeneratorType, - seed: Union[int, None], + seed: int | None, flags: int, a: float, b: float, @@ -1045,7 +1033,7 @@ def bitgenerator_weibull( self, handle: int, generatorType: BitGeneratorType, - seed: Union[int, None], + seed: int | None, flags: int, lam: float, k: float, @@ -1065,7 +1053,7 @@ def bitgenerator_bytes( self, handle: int, generatorType: BitGeneratorType, - seed: Union[int, None], + seed: int | None, flags: int, ) -> None: if self.deferred is not None: @@ -1085,7 +1073,7 @@ def bitgenerator_beta( self, handle: int, generatorType: BitGeneratorType, - seed: Union[int, None], + seed: int | None, flags: int, a: float, b: float, @@ -1105,7 +1093,7 @@ def bitgenerator_f( self, handle: int, generatorType: BitGeneratorType, - seed: Union[int, None], + seed: int | None, flags: int, dfnum: float, dfden: float, @@ -1130,7 +1118,7 @@ def bitgenerator_logseries( self, handle: int, generatorType: BitGeneratorType, - seed: Union[int, None], + seed: int | None, flags: int, p: float, ) -> None: @@ -1149,7 +1137,7 @@ def bitgenerator_noncentral_f( self, handle: int, generatorType: BitGeneratorType, - seed: Union[int, None], + seed: int | None, flags: int, dfnum: float, dfden: float, @@ -1172,7 +1160,7 @@ def bitgenerator_chisquare( self, handle: int, generatorType: BitGeneratorType, - seed: Union[int, None], + seed: int | None, flags: int, df: float, nonc: float, @@ -1200,7 +1188,7 @@ def bitgenerator_gamma( self, handle: int, generatorType: BitGeneratorType, - seed: Union[int, None], + seed: int | None, flags: int, k: float, theta: float, @@ -1226,7 +1214,7 @@ def bitgenerator_standard_t( self, handle: int, generatorType: BitGeneratorType, - seed: Union[int, None], + seed: int | None, flags: int, df: float, ) -> None: @@ -1245,7 +1233,7 @@ def bitgenerator_hypergeometric( self, handle: int, generatorType: BitGeneratorType, - seed: Union[int, None], + seed: int | None, flags: int, ngood: int, nbad: int, @@ -1268,7 +1256,7 @@ def bitgenerator_vonmises( self, handle: int, generatorType: BitGeneratorType, - seed: Union[int, None], + seed: int | None, flags: int, mu: float, kappa: float, @@ -1288,7 +1276,7 @@ def bitgenerator_zipf( self, handle: int, generatorType: BitGeneratorType, - seed: Union[int, None], + seed: int | None, flags: int, alpha: float, ) -> None: @@ -1307,7 +1295,7 @@ def bitgenerator_geometric( self, handle: int, generatorType: BitGeneratorType, - seed: Union[int, None], + seed: int | None, flags: int, p: float, ) -> None: @@ -1326,7 +1314,7 @@ def bitgenerator_wald( self, handle: int, generatorType: BitGeneratorType, - seed: Union[int, None], + seed: int | None, flags: int, mean: float, scale: float, @@ -1346,7 +1334,7 @@ def bitgenerator_binomial( self, handle: int, generatorType: BitGeneratorType, - seed: Union[int, None], + seed: int | None, flags: int, ntrials: int, p: float, @@ -1366,7 +1354,7 @@ def bitgenerator_negative_binomial( self, handle: int, generatorType: BitGeneratorType, - seed: Union[int, None], + seed: int | None, flags: int, ntrials: int, p: float, @@ -1387,11 +1375,11 @@ def bitgenerator_negative_binomial( def partition( self, rhs: Any, - kth: Union[int, Sequence[int]], + kth: int | Sequence[int], argpartition: bool = False, - axis: Union[int, None] = -1, + axis: int | None = -1, kind: SelectKind = "introselect", - order: Union[None, str, list[str]] = None, + order: str | list[str] | None = None, ) -> None: self.check_eager_args(rhs) if self.deferred is not None: @@ -1422,8 +1410,8 @@ def random_normal(self) -> None: def random_integer( self, - low: Union[int, npt.NDArray[Any]], - high: Union[int, npt.NDArray[Any]], + low: int | npt.NDArray[Any], + high: int | npt.NDArray[Any], ) -> None: if self.deferred is not None: self.deferred.random_integer(low, high) @@ -1441,7 +1429,7 @@ def unary_op( rhs: Any, where: Any, args: tuple[Scalar, ...] = (), - multiout: Optional[Any] = None, + multiout: Any | None = None, ) -> None: if multiout is None: self.check_eager_args(rhs, where) @@ -1491,7 +1479,7 @@ def unary_reduction( op: UnaryRedCode, rhs: Any, where: Any, - orig_axis: Union[int, None], + orig_axis: int | None, axes: tuple[int, ...], keepdims: bool, args: tuple[Scalar, ...], @@ -1612,7 +1600,7 @@ def binary_reduction( op: BinaryOpCode, rhs1: Any, rhs2: Any, - broadcast: Union[NdShape, None], + broadcast: NdShape | None, args: tuple[Scalar, ...], ) -> None: self.check_eager_args(rhs1, rhs2) @@ -1691,7 +1679,7 @@ def scan( op: int, rhs: Any, axis: int, - dtype: Optional[npt.DTypeLike], + dtype: npt.DTypeLike | None, nan_to_identity: bool, ) -> None: self.check_eager_args(rhs) @@ -1724,9 +1712,7 @@ def create_window(self, op_code: WindowOpCode, M: int, *args: Any) -> None: fn = _WINDOW_OPS[op_code] self.array[:] = fn(M, *args) - def packbits( - self, src: Any, axis: Union[int, None], bitorder: BitOrder - ) -> None: + def packbits(self, src: Any, axis: int | None, bitorder: BitOrder) -> None: self.check_eager_args(src) if self.deferred is not None: self.deferred.packbits(src, axis, bitorder) @@ -1736,7 +1722,7 @@ def packbits( ) def unpackbits( - self, src: Any, axis: Union[int, None], bitorder: BitOrder + self, src: Any, axis: int | None, bitorder: BitOrder ) -> None: self.check_eager_args(src) if self.deferred is not None: diff --git a/cunumeric/_thunk/thunk.py b/cunumeric/_thunk/thunk.py index 580e5acca4..77560feb86 100644 --- a/cunumeric/_thunk/thunk.py +++ b/cunumeric/_thunk/thunk.py @@ -15,7 +15,7 @@ from __future__ import annotations from abc import ABC, abstractmethod, abstractproperty -from typing import TYPE_CHECKING, Any, Iterable, Optional, Sequence, Union +from typing import TYPE_CHECKING, Any, Iterable, Sequence from ..config import ConvertCode from ..runtime import runtime @@ -134,7 +134,7 @@ def reshape(self, newshape: NdShape, order: OrderType) -> NumPyThunk: ... @abstractmethod - def squeeze(self, axis: Optional[int]) -> NumPyThunk: + def squeeze(self, axis: int | None) -> NumPyThunk: ... @abstractmethod @@ -156,11 +156,11 @@ def fill(self, value: Any) -> None: ... @abstractmethod - def transpose(self, axes: Union[tuple[int, ...], list[int]]) -> NumPyThunk: + def transpose(self, axes: tuple[int, ...] | list[int]) -> NumPyThunk: ... @abstractmethod - def flip(self, rhs: Any, axes: Union[None, int, tuple[int, ...]]) -> None: + def flip(self, rhs: Any, axes: int | tuple[int, ...] | None) -> None: ... @abstractmethod @@ -211,7 +211,7 @@ def arange(self, start: float, stop: float, step: float) -> None: ... @abstractmethod - def tile(self, rhs: Any, reps: Union[Any, Sequence[int]]) -> None: + def tile(self, rhs: Any, reps: Any | Sequence[int]) -> None: ... @abstractmethod @@ -219,7 +219,7 @@ def trilu(self, rhs: Any, k: int, lower: bool) -> None: ... @abstractmethod - def bincount(self, rhs: Any, weights: Optional[NumPyThunk] = None) -> None: + def bincount(self, rhs: Any, weights: NumPyThunk | None = None) -> None: ... @abstractmethod @@ -231,7 +231,7 @@ def bitgenerator_random_raw( self, handle: int, generatorType: BitGeneratorType, - seed: Union[int, None], + seed: int | None, flags: int, ) -> None: ... @@ -241,7 +241,7 @@ def bitgenerator_integers( self, handle: int, generatorType: BitGeneratorType, - seed: Union[int, None], + seed: int | None, flags: int, low: int, high: int, @@ -253,7 +253,7 @@ def bitgenerator_uniform( self, handle: int, generatorType: BitGeneratorType, - seed: Union[int, None], + seed: int | None, flags: int, low: float, high: float, @@ -265,7 +265,7 @@ def bitgenerator_lognormal( self, handle: int, generatorType: BitGeneratorType, - seed: Union[int, None], + seed: int | None, flags: int, mean: float, sigma: float, @@ -277,7 +277,7 @@ def bitgenerator_normal( self, handle: int, generatorType: BitGeneratorType, - seed: Union[int, None], + seed: int | None, flags: int, mean: float, sigma: float, @@ -289,7 +289,7 @@ def bitgenerator_poisson( self, handle: int, generatorType: BitGeneratorType, - seed: Union[int, None], + seed: int | None, flags: int, lam: float, ) -> None: @@ -300,7 +300,7 @@ def bitgenerator_exponential( self, handle: int, generatorType: BitGeneratorType, - seed: Union[int, None], + seed: int | None, flags: int, scale: float, ) -> None: @@ -311,7 +311,7 @@ def bitgenerator_gumbel( self, handle: int, generatorType: BitGeneratorType, - seed: Union[int, None], + seed: int | None, flags: int, mu: float, beta: float, @@ -323,7 +323,7 @@ def bitgenerator_laplace( self, handle: int, generatorType: BitGeneratorType, - seed: Union[int, None], + seed: int | None, flags: int, mu: float, beta: float, @@ -335,7 +335,7 @@ def bitgenerator_logistic( self, handle: int, generatorType: BitGeneratorType, - seed: Union[int, None], + seed: int | None, flags: int, mu: float, beta: float, @@ -347,7 +347,7 @@ def bitgenerator_pareto( self, handle: int, generatorType: BitGeneratorType, - seed: Union[int, None], + seed: int | None, flags: int, alpha: float, ) -> None: @@ -358,7 +358,7 @@ def bitgenerator_power( self, handle: int, generatorType: BitGeneratorType, - seed: Union[int, None], + seed: int | None, flags: int, alpha: float, ) -> None: @@ -369,7 +369,7 @@ def bitgenerator_rayleigh( self, handle: int, generatorType: BitGeneratorType, - seed: Union[int, None], + seed: int | None, flags: int, sigma: float, ) -> None: @@ -380,7 +380,7 @@ def bitgenerator_cauchy( self, handle: int, generatorType: BitGeneratorType, - seed: Union[int, None], + seed: int | None, flags: int, x0: float, gamma: float, @@ -392,7 +392,7 @@ def bitgenerator_triangular( self, handle: int, generatorType: BitGeneratorType, - seed: Union[int, None], + seed: int | None, flags: int, a: float, b: float, @@ -405,7 +405,7 @@ def bitgenerator_weibull( self, handle: int, generatorType: BitGeneratorType, - seed: Union[int, None], + seed: int | None, flags: int, lam: float, k: float, @@ -417,7 +417,7 @@ def bitgenerator_bytes( self, handle: int, generatorType: BitGeneratorType, - seed: Union[int, None], + seed: int | None, flags: int, ) -> None: ... @@ -427,7 +427,7 @@ def bitgenerator_beta( self, handle: int, generatorType: BitGeneratorType, - seed: Union[int, None], + seed: int | None, flags: int, a: float, b: float, @@ -439,7 +439,7 @@ def bitgenerator_f( self, handle: int, generatorType: BitGeneratorType, - seed: Union[int, None], + seed: int | None, flags: int, dfnum: float, dfden: float, @@ -451,7 +451,7 @@ def bitgenerator_logseries( self, handle: int, generatorType: BitGeneratorType, - seed: Union[int, None], + seed: int | None, flags: int, p: float, ) -> None: @@ -462,7 +462,7 @@ def bitgenerator_noncentral_f( self, handle: int, generatorType: BitGeneratorType, - seed: Union[int, None], + seed: int | None, flags: int, dfnum: float, dfden: float, @@ -475,7 +475,7 @@ def bitgenerator_chisquare( self, handle: int, generatorType: BitGeneratorType, - seed: Union[int, None], + seed: int | None, flags: int, df: float, nonc: float, @@ -487,7 +487,7 @@ def bitgenerator_gamma( self, handle: int, generatorType: BitGeneratorType, - seed: Union[int, None], + seed: int | None, flags: int, k: float, theta: float, @@ -499,7 +499,7 @@ def bitgenerator_standard_t( self, handle: int, generatorType: BitGeneratorType, - seed: Union[int, None], + seed: int | None, flags: int, df: float, ) -> None: @@ -510,7 +510,7 @@ def bitgenerator_hypergeometric( self, handle: int, generatorType: BitGeneratorType, - seed: Union[int, None], + seed: int | None, flags: int, ngood: int, nbad: int, @@ -523,7 +523,7 @@ def bitgenerator_vonmises( self, handle: int, generatorType: BitGeneratorType, - seed: Union[int, None], + seed: int | None, flags: int, mu: float, kappa: float, @@ -535,7 +535,7 @@ def bitgenerator_zipf( self, handle: int, generatorType: BitGeneratorType, - seed: Union[int, None], + seed: int | None, flags: int, alpha: float, ) -> None: @@ -546,7 +546,7 @@ def bitgenerator_geometric( self, handle: int, generatorType: BitGeneratorType, - seed: Union[int, None], + seed: int | None, flags: int, p: float, ) -> None: @@ -557,7 +557,7 @@ def bitgenerator_wald( self, handle: int, generatorType: BitGeneratorType, - seed: Union[int, None], + seed: int | None, flags: int, mean: float, scale: float, @@ -569,7 +569,7 @@ def bitgenerator_binomial( self, handle: int, generatorType: BitGeneratorType, - seed: Union[int, None], + seed: int | None, flags: int, ntrials: int, p: float, @@ -581,7 +581,7 @@ def bitgenerator_negative_binomial( self, handle: int, generatorType: BitGeneratorType, - seed: Union[int, None], + seed: int | None, flags: int, ntrials: int, p: float, @@ -596,11 +596,11 @@ def random_uniform(self) -> None: def partition( self, rhs: Any, - kth: Union[int, Sequence[int]], + kth: int | Sequence[int], argpartition: bool = False, - axis: Union[int, None] = -1, + axis: int | None = -1, kind: SelectKind = "introselect", - order: Union[None, str, list[str]] = None, + order: str | list[str] | None = None, ) -> None: ... @@ -611,8 +611,8 @@ def random_normal(self) -> None: @abstractmethod def random_integer( self, - low: Union[int, npt.NDArray[Any]], - high: Union[int, npt.NDArray[Any]], + low: int | npt.NDArray[Any], + high: int | npt.NDArray[Any], ) -> None: ... @@ -625,9 +625,9 @@ def sort( self, rhs: Any, argsort: bool = False, - axis: Union[int, None] = -1, + axis: int | None = -1, kind: SortType = "quicksort", - order: Union[None, str, list[str]] = None, + order: str | list[str] | None = None, ) -> None: ... @@ -638,7 +638,7 @@ def unary_op( rhs: Any, where: Any, args: tuple[Scalar, ...] = (), - multiout: Optional[Any] = None, + multiout: Any | None = None, ) -> None: ... @@ -648,7 +648,7 @@ def unary_reduction( op: UnaryRedCode, rhs: Any, where: Any, - orig_axis: Union[int, None], + orig_axis: int | None, axes: tuple[int, ...], keepdims: bool, args: tuple[Scalar, ...], @@ -679,7 +679,7 @@ def binary_reduction( op: BinaryOpCode, rhs1: Any, rhs2: Any, - broadcast: Union[NdShape, None], + broadcast: NdShape | None, args: tuple[Scalar, ...], ) -> None: ... @@ -710,7 +710,7 @@ def scan( op: int, rhs: Any, axis: int, - dtype: Optional[npt.DTypeLike], + dtype: npt.DTypeLike | None, nan_to_identity: bool, ) -> None: ... @@ -724,14 +724,12 @@ def create_window(self, op_code: WindowOpCode, M: Any, *args: Any) -> None: ... @abstractmethod - def packbits( - self, src: Any, axis: Union[int, None], bitorder: BitOrder - ) -> None: + def packbits(self, src: Any, axis: int | None, bitorder: BitOrder) -> None: ... @abstractmethod def unpackbits( - self, src: Any, axis: Union[int, None], bitorder: BitOrder + self, src: Any, axis: int | None, bitorder: BitOrder ) -> None: ... diff --git a/cunumeric/_ufunc/ufunc.py b/cunumeric/_ufunc/ufunc.py index f4e0d2f81a..c2a7655cd0 100644 --- a/cunumeric/_ufunc/ufunc.py +++ b/cunumeric/_ufunc/ufunc.py @@ -14,7 +14,7 @@ # from __future__ import annotations -from typing import TYPE_CHECKING, Any, Dict, Optional, Sequence, Union +from typing import TYPE_CHECKING, Any, Sequence import numpy as np from legate.core.utils import OrderedSet @@ -185,7 +185,7 @@ def to_dtypes(chars: str) -> tuple[np.dtype[Any], ...]: class ufunc: - _types: Dict[Any, str] + _types: dict[Any, str] _nin: int _nout: int @@ -232,7 +232,7 @@ def _maybe_cast_input( def _maybe_create_result( self, - out: Union[ndarray, None], + out: ndarray | None, out_shape: NdShape, res_dtype: np.dtype[Any], casting: CastingKind, @@ -254,9 +254,7 @@ def _maybe_create_result( return out @staticmethod - def _maybe_cast_output( - out: Union[ndarray, None], result: ndarray - ) -> ndarray: + def _maybe_cast_output(out: ndarray | None, result: ndarray) -> ndarray: if out is None or out is result: return result out._thunk.convert(result._thunk, warn=False) @@ -264,8 +262,8 @@ def _maybe_cast_output( @staticmethod def _maybe_convert_output_to_cunumeric_ndarray( - out: Union[ndarray, npt.NDArray[Any], None] - ) -> Union[ndarray, None]: + out: ndarray | npt.NDArray[Any] | None, + ) -> ndarray | None: from .._array.array import ndarray if out is None: @@ -279,11 +277,11 @@ def _maybe_convert_output_to_cunumeric_ndarray( def _prepare_operands( self, *args: Any, - out: Union[ndarray, tuple[ndarray, ...], None], + out: ndarray | tuple[ndarray, ...] | None, where: bool = True, ) -> tuple[ Sequence[ndarray], - Sequence[Union[ndarray, None]], + Sequence[ndarray | None], tuple[int, ...], bool, ]: @@ -407,11 +405,11 @@ def _resolve_dtype( def __call__( self, *args: Any, - out: Union[ndarray, None] = None, + out: ndarray | None = None, where: bool = True, casting: CastingKind = "same_kind", order: str = "K", - dtype: Union[np.dtype[Any], None] = None, + dtype: np.dtype[Any] | None = None, **kwargs: Any, ) -> ndarray: (x,), (out,), out_shape, where = self._prepare_operands( @@ -492,11 +490,11 @@ def _resolve_dtype( def __call__( self, *args: Any, - out: Union[ndarray, tuple[ndarray, ...], None] = None, + out: ndarray | tuple[ndarray, ...] | None = None, where: bool = True, casting: CastingKind = "same_kind", order: str = "K", - dtype: Union[np.dtype[Any], None] = None, + dtype: np.dtype[Any] | None = None, **kwargs: Any, ) -> tuple[ndarray, ...]: (x,), outs, out_shape, where = self._prepare_operands( @@ -540,7 +538,7 @@ def __init__( doc: str, op_code: BinaryOpCode, types: dict[tuple[str, str], str], - red_code: Union[UnaryRedCode, None] = None, + red_code: UnaryRedCode | None = None, use_common_type: bool = True, ) -> None: super().__init__(name, doc) @@ -653,11 +651,11 @@ def _resolve_dtype( def __call__( self, *args: Any, - out: Union[ndarray, None] = None, + out: ndarray | None = None, where: bool = True, casting: CastingKind = "same_kind", order: str = "K", - dtype: Union[np.dtype[Any], None] = None, + dtype: np.dtype[Any] | None = None, **kwargs: Any, ) -> ndarray: arrs, (out,), out_shape, where = self._prepare_operands( @@ -696,12 +694,12 @@ def __call__( def reduce( self, array: ndarray, - axis: Union[int, tuple[int, ...], None] = 0, - dtype: Union[np.dtype[Any], None] = None, - out: Union[ndarray, None] = None, + axis: int | tuple[int, ...] | None = 0, + dtype: np.dtype[Any] | None = None, + out: ndarray | None = None, keepdims: bool = False, - initial: Union[Any, None] = None, - where: Optional[ndarray] = None, + initial: Any | None = None, + where: ndarray | None = None, ) -> ndarray: """ reduce(array, axis=0, dtype=None, out=None, keepdims=False, initial= binary_ufunc: doc = _BINARY_DOCSTRING_TEMPLATE.format(summary, name) diff --git a/cunumeric/_utils/coverage.py b/cunumeric/_utils/coverage.py index ce436b17c2..fdf92627da 100644 --- a/cunumeric/_utils/coverage.py +++ b/cunumeric/_utils/coverage.py @@ -24,17 +24,7 @@ MethodType, ModuleType, ) -from typing import ( - Any, - Callable, - Container, - Iterable, - Mapping, - Optional, - Protocol, - Union, - cast, -) +from typing import Any, Callable, Container, Iterable, Mapping, Protocol, cast from legate.core import track_provenance from legate.core.utils import OrderedSet @@ -61,7 +51,7 @@ def filter_namespace( ns: Mapping[str, Any], *, - omit_names: Optional[Container[str]] = None, + omit_names: Container[str] | None = None, omit_types: tuple[type, ...] = (), ) -> dict[str, Any]: omit_names = omit_names or OrderedSet() @@ -149,7 +139,7 @@ def unimplemented( prefix: str, name: str, reporting: bool = True, - fallback: Union[Callable[[Any], Any], None] = None, + fallback: Callable[[Any], Any] | None = None, ) -> CuWrapped: name = f"{prefix}.{name}" @@ -209,7 +199,7 @@ def wrapper(*args: Any, **kwargs: Any) -> Any: def clone_module( origin_module: ModuleType, new_globals: dict[str, Any], - fallback: Union[Callable[[Any], Any], None] = None, + fallback: Callable[[Any], Any] | None = None, include_builtin_function_type: bool = False, ) -> None: """Copy attributes from one module to another, excluding submodules @@ -225,7 +215,7 @@ def clone_module( new_globals : dict A globals() dict for the new module to clone into - fallback : Union[Callable[[Any], Any], None] + fallback :Callable[[Any], Any] | None A function that will be applied to each argument before calling into the original module, to handle unimplemented functions. The function will be called recursively on list/tuple/dict containers, and should @@ -321,8 +311,8 @@ def should_wrap(obj: object) -> bool: def clone_class( origin_class: type, - omit_names: Union[Iterable[str], None] = None, - fallback: Union[Callable[[Any], Any], None] = None, + omit_names: Iterable[str] | None = None, + fallback: Callable[[Any], Any] | None = None, ) -> Callable[[type], type]: """Copy attributes from one class to another diff --git a/cunumeric/_utils/linalg.py b/cunumeric/_utils/linalg.py index b90ac0b473..5aa0b292c4 100644 --- a/cunumeric/_utils/linalg.py +++ b/cunumeric/_utils/linalg.py @@ -15,11 +15,11 @@ from __future__ import annotations from string import ascii_lowercase, ascii_uppercase -from typing import List, Sequence, Tuple, Union +from typing import Sequence from legate.core.utils import OrderedSet -Modes = Tuple[List[str], List[str], List[str]] +Modes = tuple[list[str], list[str], list[str]] def dot_modes(a_ndim: int, b_ndim: int) -> Modes: @@ -69,14 +69,9 @@ def matmul_modes(a_ndim: int, b_ndim: int) -> Modes: Axes = Sequence[int] -AxesPair = Tuple[Axes, Axes] -AxesPairLikeTuple = Union[ - Tuple[int, int], - Tuple[int, Axes], - Tuple[Axes, int], - Tuple[Axes, Axes], -] -AxesPairLike = Union[int, AxesPairLikeTuple] +AxesPair = tuple[Axes, Axes] +AxesPairLikeTuple = tuple[int | Axes, int | Axes] +AxesPairLike = int | AxesPairLikeTuple def tensordot_modes(a_ndim: int, b_ndim: int, axes: AxesPairLike) -> Modes: diff --git a/cunumeric/config.py b/cunumeric/config.py index 29e79cd4f6..9e7ec560f3 100644 --- a/cunumeric/config.py +++ b/cunumeric/config.py @@ -19,7 +19,7 @@ from abc import abstractmethod from ctypes import CDLL, RTLD_GLOBAL from enum import IntEnum, unique -from typing import TYPE_CHECKING, Any, Union, cast +from typing import TYPE_CHECKING, Any, cast import cffi # type: ignore import numpy as np @@ -663,7 +663,7 @@ def __init__( input_dtype: npt.DTypeLike, output_dtype: npt.DTypeLike, single_precision: bool, - complex_type: Union[FFTType, None] = None, + complex_type: FFTType | None = None, ) -> None: self._name = name self._type_id = type_id @@ -802,7 +802,7 @@ class FFTNormalization(IntEnum): ORTHOGONAL = 3 @staticmethod - def from_string(in_string: str) -> Union[FFTNormalization, None]: + def from_string(in_string: str) -> FFTNormalization | None: if in_string == "forward": return FFTNormalization.FORWARD elif in_string == "ortho": @@ -813,7 +813,7 @@ def from_string(in_string: str) -> Union[FFTNormalization, None]: return None @staticmethod - def reverse(in_string: Union[str, None]) -> str: + def reverse(in_string: str | None) -> str: if in_string == "forward": return "backward" elif in_string == "backward" or in_string is None: diff --git a/cunumeric/fft/fft.py b/cunumeric/fft/fft.py index 591a6cdc35..80a941d1ac 100644 --- a/cunumeric/fft/fft.py +++ b/cunumeric/fft/fft.py @@ -14,7 +14,7 @@ # from __future__ import annotations -from typing import TYPE_CHECKING, Sequence, Union +from typing import TYPE_CHECKING, Sequence import numpy as np @@ -27,8 +27,8 @@ def _sanitize_user_axes( a: ndarray, - s: Union[Sequence[int], None], - axes: Union[Sequence[int], None], + s: Sequence[int] | None, + axes: Sequence[int] | None, is_c2r: bool = False, ) -> tuple[list[int], Sequence[int]]: if s is None: @@ -58,9 +58,9 @@ def _operate_by_axes(a: ndarray, axes: Sequence[int]) -> bool: @add_boilerplate("a") def fft( a: ndarray, - n: Union[int, None] = None, + n: int | None = None, axis: int = -1, - norm: Union[str, None] = None, + norm: str | None = None, ) -> ndarray: """ Compute the one-dimensional discrete Fourier Transform. @@ -114,9 +114,9 @@ def fft( @add_boilerplate("a") def fft2( a: ndarray, - s: Union[Sequence[int], None] = None, + s: Sequence[int] | None = None, axes: Sequence[int] = (-2, -1), - norm: Union[str, None] = None, + norm: str | None = None, ) -> ndarray: """ Compute the 2-dimensional discrete Fourier Transform. @@ -172,9 +172,9 @@ def fft2( @add_boilerplate("a") def fftn( a: ndarray, - s: Union[Sequence[int], None] = None, - axes: Union[Sequence[int], None] = None, - norm: Union[str, None] = None, + s: Sequence[int] | None = None, + axes: Sequence[int] | None = None, + norm: str | None = None, ) -> ndarray: """ Compute the N-dimensional discrete Fourier Transform. @@ -248,9 +248,9 @@ def fftn( @add_boilerplate("a") def ifft( a: ndarray, - n: Union[int, None] = None, + n: int | None = None, axis: int = -1, - norm: Union[str, None] = None, + norm: str | None = None, ) -> ndarray: """ Compute the one-dimensional inverse discrete Fourier Transform. @@ -319,9 +319,9 @@ def ifft( @add_boilerplate("a") def ifft2( a: ndarray, - s: Union[Sequence[int], None] = None, + s: Sequence[int] | None = None, axes: Sequence[int] = (-2, -1), - norm: Union[str, None] = None, + norm: str | None = None, ) -> ndarray: """ Compute the 2-dimensional inverse discrete Fourier Transform. @@ -384,9 +384,9 @@ def ifft2( @add_boilerplate("a") def ifftn( a: ndarray, - s: Union[Sequence[int], None] = None, - axes: Union[Sequence[int], None] = None, - norm: Union[str, None] = None, + s: Sequence[int] | None = None, + axes: Sequence[int] | None = None, + norm: str | None = None, ) -> ndarray: """ Compute the N-dimensional inverse discrete Fourier Transform. @@ -470,9 +470,9 @@ def ifftn( @add_boilerplate("a") def rfft( a: ndarray, - n: Union[int, None] = None, + n: int | None = None, axis: int = -1, - norm: Union[str, None] = None, + norm: str | None = None, ) -> ndarray: """ Compute the one-dimensional discrete Fourier Transform for real input. @@ -528,9 +528,9 @@ def rfft( @add_boilerplate("a") def rfft2( a: ndarray, - s: Union[Sequence[int], None] = None, + s: Sequence[int] | None = None, axes: Sequence[int] = (-2, -1), - norm: Union[str, None] = None, + norm: str | None = None, ) -> ndarray: """ Compute the 2-dimensional FFT of a real array. @@ -573,9 +573,9 @@ def rfft2( @add_boilerplate("a") def rfftn( a: ndarray, - s: Union[Sequence[int], None] = None, - axes: Union[Sequence[int], None] = None, - norm: Union[str, None] = None, + s: Sequence[int] | None = None, + axes: Sequence[int] | None = None, + norm: str | None = None, ) -> ndarray: """ Compute the N-dimensional discrete Fourier Transform for real input. @@ -670,9 +670,9 @@ def rfftn( @add_boilerplate("a") def irfft( a: ndarray, - n: Union[int, None] = None, + n: int | None = None, axis: int = -1, - norm: Union[str, None] = None, + norm: str | None = None, ) -> ndarray: """ Computes the inverse of `rfft`. @@ -738,9 +738,9 @@ def irfft( @add_boilerplate("a") def irfft2( a: ndarray, - s: Union[Sequence[int], None] = None, + s: Sequence[int] | None = None, axes: Sequence[int] = (-2, -1), - norm: Union[str, None] = None, + norm: str | None = None, ) -> ndarray: """ Computes the inverse of `rfft2`. @@ -785,9 +785,9 @@ def irfft2( @add_boilerplate("a") def irfftn( a: ndarray, - s: Union[Sequence[int], None] = None, - axes: Union[Sequence[int], None] = None, - norm: Union[str, None] = None, + s: Sequence[int] | None = None, + axes: Sequence[int] | None = None, + norm: str | None = None, ) -> ndarray: """ Computes the inverse of `rfftn`. @@ -895,9 +895,9 @@ def irfftn( @add_boilerplate("a") def hfft( a: ndarray, - n: Union[int, None] = None, + n: int | None = None, axis: int = -1, - norm: Union[str, None] = None, + norm: str | None = None, ) -> ndarray: """ Compute the FFT of a signal that has Hermitian symmetry, i.e., a real @@ -960,9 +960,9 @@ def hfft( @add_boilerplate("a") def ihfft( a: ndarray, - n: Union[int, None] = None, + n: int | None = None, axis: int = -1, - norm: Union[str, None] = None, + norm: str | None = None, ) -> ndarray: """ Compute the inverse FFT of a signal that has Hermitian symmetry. diff --git a/cunumeric/linalg/linalg.py b/cunumeric/linalg/linalg.py index c50f322cf0..e6d4de789d 100644 --- a/cunumeric/linalg/linalg.py +++ b/cunumeric/linalg/linalg.py @@ -14,7 +14,7 @@ # from __future__ import annotations -from typing import TYPE_CHECKING, Sequence, Union +from typing import TYPE_CHECKING, Sequence import numpy as np from numpy.core.multiarray import ( # type: ignore [attr-defined] @@ -30,8 +30,6 @@ from ._exception import LinAlgError if TYPE_CHECKING: - from typing import Optional - import numpy.typing as npt @@ -85,7 +83,7 @@ def cholesky(a: ndarray) -> ndarray: @add_boilerplate("a", "b") -def solve(a: ndarray, b: ndarray, out: Optional[ndarray] = None) -> ndarray: +def solve(a: ndarray, b: ndarray, out: ndarray | None = None) -> ndarray: """ Solve a linear matrix equation, or system of linear scalar equations. @@ -225,8 +223,8 @@ def matrix_power(a: ndarray, n: int) -> ndarray: # Use binary decomposition to reduce the number of matrix multiplications. # Here, we iterate over the bits of n, from LSB to MSB, raise `a` to # increasing powers of 2, and multiply into the result as needed. - z: Union[ndarray, None] = None - result: Union[ndarray, None] = None + z: ndarray | None = None + result: ndarray | None = None while n > 0: z = a if z is None else matmul(z, z) n, bit = divmod(n, 2) @@ -240,7 +238,7 @@ def matrix_power(a: ndarray, n: int) -> ndarray: # This implementation is adapted closely from NumPy def multi_dot( - arrays: Sequence[ndarray], *, out: Union[ndarray, None] = None + arrays: Sequence[ndarray], *, out: ndarray | None = None ) -> ndarray: """ Compute the dot product of two or more arrays in a single function call, @@ -317,7 +315,7 @@ def multi_dot( def _multi_dot_three( - A: ndarray, B: ndarray, C: ndarray, out: Union[ndarray, None] = None + A: ndarray, B: ndarray, C: ndarray, out: ndarray | None = None ) -> ndarray: """ Find the best order for three arrays and do the multiplication. @@ -377,7 +375,7 @@ def _multi_dot( order: npt.NDArray[np.int64], i: int, j: int, - out: Union[ndarray, None] = None, + out: ndarray | None = None, ) -> ndarray: """Actually do the multiplication with the given order.""" if i == j: @@ -397,10 +395,10 @@ def _multi_dot( @add_boilerplate("x") def norm( x: ndarray, - ord: Union[str, int, float, None] = None, - axis: Union[int, tuple[int, int], None] = None, + ord: str | int | float | None = None, + axis: int | tuple[int, int] | None = None, keepdims: bool = False, -) -> Union[float, ndarray]: +) -> float | ndarray: """ Matrix or vector norm. @@ -635,7 +633,7 @@ def _thunk_cholesky(a: ndarray, no_tril: bool = False) -> ndarray: def _thunk_solve( - a: ndarray, b: ndarray, output: Optional[ndarray] = None + a: ndarray, b: ndarray, output: ndarray | None = None ) -> ndarray: if a.dtype.kind not in ("f", "c"): a = a.astype("float64") diff --git a/cunumeric/ma/_masked_array.py b/cunumeric/ma/_masked_array.py index 0809f73532..6df3763ce7 100644 --- a/cunumeric/ma/_masked_array.py +++ b/cunumeric/ma/_masked_array.py @@ -14,7 +14,7 @@ # from __future__ import annotations -from typing import TYPE_CHECKING, Any, Type, Union +from typing import TYPE_CHECKING, Any, Type if TYPE_CHECKING: import numpy.typing as npt @@ -52,7 +52,7 @@ def __init__( self, data: Any = None, mask: _np.bool_ = nomask, - dtype: Union[npt.DTypeLike, None] = None, + dtype: npt.DTypeLike | None = None, copy: bool = False, subok: bool = True, ndmin: int = 0, @@ -60,7 +60,7 @@ def __init__( keep_mask: Any = True, hard_mask: Any = None, shrink: bool = True, - order: Union[str, None] = None, + order: str | None = None, ) -> None: self._internal_ma = _np.ma.MaskedArray( # type: ignore data=maybe_convert_to_np_ndarray(data), diff --git a/cunumeric/random/_bitgenerator.py b/cunumeric/random/_bitgenerator.py index 7c1e80de43..9510d406d6 100644 --- a/cunumeric/random/_bitgenerator.py +++ b/cunumeric/random/_bitgenerator.py @@ -16,7 +16,7 @@ import time from abc import abstractproperty -from typing import TYPE_CHECKING, Union +from typing import TYPE_CHECKING import numpy as np @@ -33,7 +33,7 @@ class BitGenerator: def __init__( self, - seed: Union[int, None] = None, + seed: int | None = None, forceBuild: bool = False, ) -> None: """ @@ -44,7 +44,7 @@ def __init__( Parameters ---------- - seed : {None, int}, optional + seed : {int, None}, optional A seed to initialize the `BitGenerator`. If None, then fresh, unpredictable entropy will be pulled from the OS. @@ -76,7 +76,7 @@ def __del__(self) -> None: runtime.bitgenerator_destroy(self.handle, disposing=True) # when output is false => skip ahead - def random_raw(self, shape: Union[NdShapeLike, None] = None) -> ndarray: + def random_raw(self, shape: NdShapeLike | None = None) -> ndarray: if shape is None: shape = (1,) if not isinstance(shape, tuple): @@ -90,8 +90,8 @@ def random_raw(self, shape: Union[NdShapeLike, None] = None) -> ndarray: def integers( self, low: int, - high: Union[int, None] = None, - shape: Union[NdShapeLike, None] = None, + high: int | None = None, + shape: NdShapeLike | None = None, type: npt.DTypeLike = np.int64, endpoint: bool = False, ) -> ndarray: @@ -112,9 +112,9 @@ def integers( def random( self, - shape: Union[NdShapeLike, None] = None, + shape: NdShapeLike | None = None, dtype: npt.DTypeLike = np.float64, - res: Union[ndarray, None] = None, + res: ndarray | None = None, ) -> ndarray: if shape is None: shape = (1,) @@ -131,7 +131,7 @@ def lognormal( self, mean: float = 0.0, sigma: float = 1.0, - shape: Union[NdShapeLike, None] = None, + shape: NdShapeLike | None = None, dtype: npt.DTypeLike = np.float64, ) -> ndarray: if shape is None: @@ -148,7 +148,7 @@ def normal( self, mean: float = 0.0, sigma: float = 1.0, - shape: Union[NdShapeLike, None] = None, + shape: NdShapeLike | None = None, dtype: npt.DTypeLike = np.float64, ) -> ndarray: if shape is None: @@ -165,7 +165,7 @@ def uniform( self, low: float = 0.0, high: float = 1.0, - shape: Union[NdShapeLike, None] = None, + shape: NdShapeLike | None = None, dtype: npt.DTypeLike = np.float64, ) -> ndarray: if shape is None: @@ -178,9 +178,7 @@ def uniform( ) return res - def poisson( - self, lam: float, shape: Union[NdShapeLike, None] = None - ) -> ndarray: + def poisson(self, lam: float, shape: NdShapeLike | None = None) -> ndarray: if shape is None: shape = (1,) if not isinstance(shape, tuple): @@ -194,7 +192,7 @@ def poisson( def exponential( self, scale: float = 1.0, - shape: Union[NdShapeLike, None] = None, + shape: NdShapeLike | None = None, dtype: npt.DTypeLike = np.float64, ) -> ndarray: if shape is None: @@ -211,7 +209,7 @@ def gumbel( self, mu: float = 0.0, beta: float = 1.0, - shape: Union[NdShapeLike, None] = None, + shape: NdShapeLike | None = None, dtype: npt.DTypeLike = np.float64, ) -> ndarray: if shape is None: @@ -228,7 +226,7 @@ def laplace( self, mu: float = 0.0, beta: float = 1.0, - shape: Union[NdShapeLike, None] = None, + shape: NdShapeLike | None = None, dtype: npt.DTypeLike = np.float64, ) -> ndarray: if shape is None: @@ -245,7 +243,7 @@ def logistic( self, mu: float = 0.0, beta: float = 1.0, - shape: Union[NdShapeLike, None] = None, + shape: NdShapeLike | None = None, dtype: npt.DTypeLike = np.float64, ) -> ndarray: if shape is None: @@ -261,7 +259,7 @@ def logistic( def pareto( self, alpha: float, - shape: Union[NdShapeLike, None] = None, + shape: NdShapeLike | None = None, dtype: npt.DTypeLike = np.float64, ) -> ndarray: if shape is None: @@ -277,7 +275,7 @@ def pareto( def power( self, alpha: float, - shape: Union[NdShapeLike, None] = None, + shape: NdShapeLike | None = None, dtype: npt.DTypeLike = np.float64, ) -> ndarray: if shape is None: @@ -293,7 +291,7 @@ def power( def rayleigh( self, sigma: float, - shape: Union[NdShapeLike, None] = None, + shape: NdShapeLike | None = None, dtype: npt.DTypeLike = np.float64, ) -> ndarray: if shape is None: @@ -310,7 +308,7 @@ def cauchy( self, x0: float, gamma: float, - shape: Union[NdShapeLike, None] = None, + shape: NdShapeLike | None = None, dtype: npt.DTypeLike = np.float64, ) -> ndarray: if shape is None: @@ -328,7 +326,7 @@ def triangular( a: float, b: float, c: float, - shape: Union[NdShapeLike, None] = None, + shape: NdShapeLike | None = None, dtype: npt.DTypeLike = np.float64, ) -> ndarray: if shape is None: @@ -345,7 +343,7 @@ def weibull( self, lam: float, k: float, - shape: Union[NdShapeLike, None] = None, + shape: NdShapeLike | None = None, dtype: npt.DTypeLike = np.float64, ) -> ndarray: if shape is None: @@ -358,7 +356,7 @@ def weibull( ) return res - def bytes(self, length: Union[int, tuple[int, ...]]) -> ndarray: + def bytes(self, length: int | tuple[int, ...]) -> ndarray: if not isinstance(length, tuple): length = (length,) res = ndarray(length, dtype=np.dtype(np.uint8)) @@ -374,7 +372,7 @@ def beta( self, a: float, b: float, - shape: Union[NdShapeLike, None] = None, + shape: NdShapeLike | None = None, dtype: npt.DTypeLike = np.float64, ) -> ndarray: if shape is None: @@ -391,7 +389,7 @@ def f( self, dfnum: float, dfden: float, - shape: Union[NdShapeLike, None] = None, + shape: NdShapeLike | None = None, dtype: npt.DTypeLike = np.float64, ) -> ndarray: if shape is None: @@ -412,7 +410,7 @@ def f( def logseries( self, p: float, - shape: Union[NdShapeLike, None] = None, + shape: NdShapeLike | None = None, dtype: npt.DTypeLike = np.uint32, ) -> ndarray: if shape is None: @@ -430,7 +428,7 @@ def noncentral_f( dfnum: float, dfden: float, nonc: float, - shape: Union[NdShapeLike, None] = None, + shape: NdShapeLike | None = None, dtype: npt.DTypeLike = np.float64, ) -> ndarray: if shape is None: @@ -453,7 +451,7 @@ def chisquare( self, df: float, nonc: float = 0.0, - shape: Union[NdShapeLike, None] = None, + shape: NdShapeLike | None = None, dtype: npt.DTypeLike = np.float64, ) -> ndarray: if shape is None: @@ -470,7 +468,7 @@ def gamma( self, k: float, theta: float = 1.0, - shape: Union[NdShapeLike, None] = None, + shape: NdShapeLike | None = None, dtype: npt.DTypeLike = np.float64, ) -> ndarray: if shape is None: @@ -486,7 +484,7 @@ def gamma( def standard_t( self, df: float, - shape: Union[NdShapeLike, None] = None, + shape: NdShapeLike | None = None, dtype: npt.DTypeLike = np.float64, ) -> ndarray: if shape is None: @@ -504,7 +502,7 @@ def hypergeometric( ngood: int, nbad: int, nsample: int, - shape: Union[NdShapeLike, None] = None, + shape: NdShapeLike | None = None, dtype: npt.DTypeLike = np.uint32, ) -> ndarray: if shape is None: @@ -527,7 +525,7 @@ def vonmises( self, mu: float, kappa: float, - shape: Union[NdShapeLike, None] = None, + shape: NdShapeLike | None = None, dtype: npt.DTypeLike = np.float64, ) -> ndarray: if shape is None: @@ -543,7 +541,7 @@ def vonmises( def zipf( self, alpha: float, - shape: Union[NdShapeLike, None] = None, + shape: NdShapeLike | None = None, dtype: npt.DTypeLike = np.uint32, ) -> ndarray: if shape is None: @@ -559,7 +557,7 @@ def zipf( def geometric( self, p: float, - shape: Union[NdShapeLike, None] = None, + shape: NdShapeLike | None = None, dtype: npt.DTypeLike = np.uint32, ) -> ndarray: if shape is None: @@ -576,7 +574,7 @@ def wald( self, mean: float, scale: float, - shape: Union[NdShapeLike, None] = None, + shape: NdShapeLike | None = None, dtype: npt.DTypeLike = np.float64, ) -> ndarray: if shape is None: @@ -593,7 +591,7 @@ def binomial( self, ntrials: int, p: float, - shape: Union[NdShapeLike, None] = None, + shape: NdShapeLike | None = None, dtype: npt.DTypeLike = np.uint32, ) -> ndarray: if shape is None: @@ -610,7 +608,7 @@ def negative_binomial( self, ntrials: int, p: float, - shape: Union[NdShapeLike, None] = None, + shape: NdShapeLike | None = None, dtype: npt.DTypeLike = np.uint32, ) -> ndarray: if shape is None: diff --git a/cunumeric/random/_generator.py b/cunumeric/random/_generator.py index 9e352d3766..cc0bd9fedd 100644 --- a/cunumeric/random/_generator.py +++ b/cunumeric/random/_generator.py @@ -14,7 +14,7 @@ # from __future__ import annotations -from typing import TYPE_CHECKING, Union +from typing import TYPE_CHECKING import numpy as np @@ -67,7 +67,7 @@ def beta( self, a: float, b: float, - size: Union[NdShapeLike, None] = None, + size: NdShapeLike | None = None, dtype: npt.DTypeLike = np.float64, ) -> ndarray: return self.bit_generator.beta(a=a, b=b, shape=size, dtype=dtype) @@ -76,21 +76,21 @@ def binomial( self, ntrials: int, p: float, - size: Union[NdShapeLike, None] = None, + size: NdShapeLike | None = None, dtype: npt.DTypeLike = np.uint32, ) -> ndarray: return self.bit_generator.binomial( ntrials=ntrials, p=p, shape=size, dtype=dtype ) - def bytes(self, length: Union[int, tuple[int, ...]]) -> ndarray: + def bytes(self, length: int | tuple[int, ...]) -> ndarray: return self.bit_generator.bytes(length=length) def cauchy( self, x0: float, gamma: float, - size: Union[NdShapeLike, None] = None, + size: NdShapeLike | None = None, dtype: npt.DTypeLike = np.float64, ) -> ndarray: return self.bit_generator.cauchy( @@ -100,7 +100,7 @@ def cauchy( def chisquare( self, df: float, - size: Union[NdShapeLike, None] = None, + size: NdShapeLike | None = None, dtype: npt.DTypeLike = np.float64, ) -> ndarray: return self.bit_generator.chisquare( @@ -110,7 +110,7 @@ def chisquare( def exponential( self, scale: float = 1.0, - size: Union[NdShapeLike, None] = None, + size: NdShapeLike | None = None, dtype: npt.DTypeLike = np.float64, ) -> ndarray: return self.bit_generator.exponential( @@ -121,7 +121,7 @@ def f( self, dfnum: float, dfden: float, - size: Union[NdShapeLike, None] = None, + size: NdShapeLike | None = None, dtype: npt.DTypeLike = np.float64, ) -> ndarray: return self.bit_generator.f( @@ -132,7 +132,7 @@ def gamma( self, shape: float, scale: float = 1.0, - size: Union[NdShapeLike, None] = None, + size: NdShapeLike | None = None, dtype: npt.DTypeLike = np.float64, ) -> ndarray: return self.bit_generator.gamma( @@ -142,7 +142,7 @@ def gamma( def geometric( self, p: float, - size: Union[NdShapeLike, None] = None, + size: NdShapeLike | None = None, dtype: npt.DTypeLike = np.uint32, ) -> ndarray: return self.bit_generator.geometric(p=p, shape=size, dtype=dtype) @@ -151,7 +151,7 @@ def gumbel( self, loc: float = 0.0, scale: float = 1.0, - size: Union[NdShapeLike, None] = None, + size: NdShapeLike | None = None, dtype: npt.DTypeLike = np.float64, ) -> ndarray: return self.bit_generator.gumbel( @@ -163,7 +163,7 @@ def hypergeometric( ngood: int, nbad: int, nsample: int, - size: Union[NdShapeLike, None] = None, + size: NdShapeLike | None = None, dtype: npt.DTypeLike = np.uint32, ) -> ndarray: return self.bit_generator.hypergeometric( @@ -173,8 +173,8 @@ def hypergeometric( def integers( self, low: int, - high: Union[int, None] = None, - size: Union[NdShapeLike, None] = None, + high: int | None = None, + size: NdShapeLike | None = None, dtype: npt.DTypeLike = np.int64, endpoint: bool = False, ) -> ndarray: @@ -184,7 +184,7 @@ def laplace( self, loc: float = 0.0, scale: float = 1.0, - size: Union[NdShapeLike, None] = None, + size: NdShapeLike | None = None, dtype: npt.DTypeLike = np.float64, ) -> ndarray: return self.bit_generator.laplace( @@ -195,7 +195,7 @@ def logistic( self, loc: float = 0.0, scale: float = 1.0, - size: Union[NdShapeLike, None] = None, + size: NdShapeLike | None = None, dtype: npt.DTypeLike = np.float64, ) -> ndarray: return self.bit_generator.logistic( @@ -206,7 +206,7 @@ def lognormal( self, mean: float = 0.0, sigma: float = 1.0, - size: Union[NdShapeLike, None] = None, + size: NdShapeLike | None = None, dtype: npt.DTypeLike = np.float64, ) -> ndarray: return self.bit_generator.lognormal(mean, sigma, size, dtype) @@ -214,7 +214,7 @@ def lognormal( def logseries( self, p: float, - size: Union[NdShapeLike, None] = None, + size: NdShapeLike | None = None, dtype: npt.DTypeLike = np.uint32, ) -> ndarray: return self.bit_generator.logseries(p=p, shape=size, dtype=dtype) @@ -223,7 +223,7 @@ def negative_binomial( self, ntrials: int, p: float, - size: Union[NdShapeLike, None] = None, + size: NdShapeLike | None = None, dtype: npt.DTypeLike = np.uint32, ) -> ndarray: return self.bit_generator.negative_binomial( @@ -234,7 +234,7 @@ def noncentral_chisquare( self, df: float, nonc: float, - size: Union[NdShapeLike, None] = None, + size: NdShapeLike | None = None, dtype: npt.DTypeLike = np.float64, ) -> ndarray: return self.bit_generator.chisquare( @@ -246,7 +246,7 @@ def noncentral_f( dfnum: float, dfden: float, nonc: float, - size: Union[NdShapeLike, None] = None, + size: NdShapeLike | None = None, dtype: npt.DTypeLike = np.float64, ) -> ndarray: return self.bit_generator.noncentral_f( @@ -257,7 +257,7 @@ def normal( self, loc: float = 0.0, scale: float = 1.0, - size: Union[NdShapeLike, None] = None, + size: NdShapeLike | None = None, dtype: npt.DTypeLike = np.float64, ) -> ndarray: return self.bit_generator.normal( @@ -267,29 +267,29 @@ def normal( def pareto( self, a: float, - size: Union[NdShapeLike, None] = None, + size: NdShapeLike | None = None, dtype: npt.DTypeLike = np.float64, ) -> ndarray: return self.bit_generator.pareto(alpha=a, shape=size, dtype=dtype) def poisson( - self, lam: float = 1.0, size: Union[NdShapeLike, None] = None + self, lam: float = 1.0, size: NdShapeLike | None = None ) -> ndarray: return self.bit_generator.poisson(lam, size) def power( self, a: float, - size: Union[NdShapeLike, None] = None, + size: NdShapeLike | None = None, dtype: npt.DTypeLike = np.float64, ) -> ndarray: return self.bit_generator.power(alpha=a, shape=size, dtype=dtype) def random( self, - size: Union[NdShapeLike, None] = None, + size: NdShapeLike | None = None, dtype: npt.DTypeLike = np.float64, - out: Union[ndarray, None] = None, + out: ndarray | None = None, ) -> ndarray: if out is not None: if size is not None and out.shape != size: @@ -306,7 +306,7 @@ def random( def rayleigh( self, scale: float, - size: Union[NdShapeLike, None] = None, + size: NdShapeLike | None = None, dtype: npt.DTypeLike = np.float64, ) -> ndarray: return self.bit_generator.rayleigh( @@ -315,14 +315,14 @@ def rayleigh( def standard_cauchy( self, - size: Union[NdShapeLike, None] = None, + size: NdShapeLike | None = None, dtype: npt.DTypeLike = np.float64, ) -> ndarray: return self.cauchy(0.0, 1.0, size, dtype) def standard_exponential( self, - size: Union[NdShapeLike, None] = None, + size: NdShapeLike | None = None, dtype: npt.DTypeLike = np.float64, ) -> ndarray: return self.exponential(1.0, size, dtype) @@ -330,7 +330,7 @@ def standard_exponential( def standard_gamma( self, shape: float, - size: Union[NdShapeLike, None] = None, + size: NdShapeLike | None = None, dtype: npt.DTypeLike = np.float64, ) -> ndarray: return self.gamma(shape=shape, scale=1.0, size=size, dtype=dtype) @@ -338,7 +338,7 @@ def standard_gamma( def standard_t( self, df: float, - size: Union[NdShapeLike, None] = None, + size: NdShapeLike | None = None, dtype: npt.DTypeLike = np.float64, ) -> ndarray: return self.bit_generator.standard_t(df=df, shape=size, dtype=dtype) @@ -348,7 +348,7 @@ def triangular( left: float, mode: float, right: float, - size: Union[NdShapeLike, None] = None, + size: NdShapeLike | None = None, dtype: npt.DTypeLike = np.float64, ) -> ndarray: return self.bit_generator.triangular( @@ -359,7 +359,7 @@ def uniform( self, low: float = 0.0, high: float = 1.0, - size: Union[NdShapeLike, None] = None, + size: NdShapeLike | None = None, dtype: npt.DTypeLike = np.float64, ) -> ndarray: return self.bit_generator.uniform(low, high, size, dtype) @@ -368,7 +368,7 @@ def vonmises( self, mu: float, kappa: float, - size: Union[NdShapeLike, None] = None, + size: NdShapeLike | None = None, dtype: npt.DTypeLike = np.float64, ) -> ndarray: return self.bit_generator.vonmises( @@ -379,7 +379,7 @@ def wald( self, mean: float, scale: float, - size: Union[NdShapeLike, None] = None, + size: NdShapeLike | None = None, dtype: npt.DTypeLike = np.float64, ) -> ndarray: return self.bit_generator.wald(mean, scale, shape=size, dtype=dtype) @@ -387,7 +387,7 @@ def wald( def weibull( self, a: float, - size: Union[NdShapeLike, None] = None, + size: NdShapeLike | None = None, dtype: npt.DTypeLike = np.float64, ) -> ndarray: return self.bit_generator.weibull(lam=1, k=a, shape=size, dtype=dtype) @@ -395,14 +395,14 @@ def weibull( def zipf( self, a: float, - size: Union[NdShapeLike, None] = None, + size: NdShapeLike | None = None, dtype: npt.DTypeLike = np.uint32, ) -> ndarray: return self.bit_generator.zipf(alpha=a, shape=size, dtype=dtype) def default_rng( - seed: Union[None, int, BitGenerator, Generator] = None + seed: int | BitGenerator | Generator | None = None, ) -> Generator: """ Construct a new Generator with the default BitGenerator (XORWOW). diff --git a/cunumeric/random/_legacy.py b/cunumeric/random/_legacy.py index f4591feb58..499f795932 100644 --- a/cunumeric/random/_legacy.py +++ b/cunumeric/random/_legacy.py @@ -14,7 +14,7 @@ # from __future__ import annotations -from typing import TYPE_CHECKING, Any, Union +from typing import TYPE_CHECKING, Any import numpy as np import numpy.random as nprandom @@ -28,13 +28,13 @@ from ..types import NdShapeLike -def seed(init: Union[int, None] = None) -> None: +def seed(init: int | None = None) -> None: if init is None: init = 0 runtime.set_next_random_epoch(int(init)) -def rand(*shapeargs: int) -> Union[float, ndarray]: +def rand(*shapeargs: int) -> float | ndarray: """ rand(d0, d1, ..., dn) @@ -72,10 +72,10 @@ def rand(*shapeargs: int) -> Union[float, ndarray]: def randint( low: int, - high: Union[int, None] = None, - size: Union[NdShapeLike, None] = None, - dtype: Union[np.dtype[Any], type, None] = int, -) -> Union[int, ndarray, npt.NDArray[Any]]: + high: int | None = None, + size: NdShapeLike | None = None, + dtype: np.dtype[Any] | type | None = int, +) -> int | ndarray | npt.NDArray[Any]: """ Return random integers from `low` (inclusive) to `high` (exclusive). @@ -147,7 +147,7 @@ def randint( return result -def randn(*shapeargs: int) -> Union[float, ndarray]: +def randn(*shapeargs: int) -> float | ndarray: """ randn(d0, d1, ..., dn) @@ -182,7 +182,7 @@ def randn(*shapeargs: int) -> Union[float, ndarray]: return result -def random(size: Union[NdShapeLike, None] = None) -> Union[float, ndarray]: +def random(size: NdShapeLike | None = None) -> float | ndarray: """ random(size=None) diff --git a/cunumeric/random/_random.py b/cunumeric/random/_random.py index f6c93f361a..084eee55b1 100644 --- a/cunumeric/random/_random.py +++ b/cunumeric/random/_random.py @@ -14,7 +14,7 @@ # from __future__ import annotations -from typing import TYPE_CHECKING, Any, Union +from typing import TYPE_CHECKING, Any import numpy as np @@ -29,7 +29,7 @@ from ..types import NdShapeLike -def seed(init: Union[int, None] = None) -> None: +def seed(init: int | None = None) -> None: """ Reseed the legacy random number generator. @@ -51,7 +51,7 @@ def seed(init: Union[int, None] = None) -> None: def beta( a: float, b: float, - size: Union[NdShapeLike, None] = None, + size: NdShapeLike | None = None, dtype: npt.DTypeLike = np.float64, ) -> ndarray: """ @@ -103,7 +103,7 @@ def beta( def binomial( ntrials: int, p: float, - size: Union[NdShapeLike, None] = None, + size: NdShapeLike | None = None, dtype: npt.DTypeLike = np.uint32, ) -> ndarray: """ @@ -174,7 +174,7 @@ def bytes(length: int) -> ndarray: def chisquare( df: float, - size: Union[NdShapeLike, None] = None, + size: NdShapeLike | None = None, dtype: npt.DTypeLike = np.float64, ) -> ndarray: """ @@ -220,7 +220,7 @@ def chisquare( def exponential( scale: float = 1.0, - size: Union[NdShapeLike, None] = None, + size: NdShapeLike | None = None, dtype: npt.DTypeLike = np.float64, ) -> ndarray: """ @@ -281,7 +281,7 @@ def exponential( def f( dfnum: float, dfden: float, - size: Union[NdShapeLike, None] = None, + size: NdShapeLike | None = None, dtype: npt.DTypeLike = np.float64, ) -> ndarray: """ @@ -329,7 +329,7 @@ def f( def gamma( shape: float, scale: float = 1.0, - size: Union[NdShapeLike, None] = None, + size: NdShapeLike | None = None, dtype: npt.DTypeLike = np.float64, ) -> ndarray: """ @@ -371,7 +371,7 @@ def gamma( def geometric( p: float, - size: Union[NdShapeLike, None] = None, + size: NdShapeLike | None = None, dtype: npt.DTypeLike = np.uint32, ) -> ndarray: """ @@ -419,7 +419,7 @@ def geometric( def gumbel( loc: float = 0.0, scale: float = 1.0, - size: Union[NdShapeLike, None] = None, + size: NdShapeLike | None = None, dtype: npt.DTypeLike = np.float64, ) -> ndarray: """ @@ -462,7 +462,7 @@ def hypergeometric( ngood: int, nbad: int, nsample: int, - size: Union[NdShapeLike, None] = None, + size: NdShapeLike | None = None, dtype: npt.DTypeLike = np.uint32, ) -> ndarray: """ @@ -513,7 +513,7 @@ def hypergeometric( def laplace( loc: float = 0.0, scale: float = 1.0, - size: Union[NdShapeLike, None] = None, + size: NdShapeLike | None = None, dtype: npt.DTypeLike = np.float64, ) -> ndarray: """ @@ -558,7 +558,7 @@ def laplace( def logistic( loc: float = 0.0, scale: float = 1.0, - size: Union[NdShapeLike, None] = None, + size: NdShapeLike | None = None, dtype: npt.DTypeLike = np.float64, ) -> ndarray: """ @@ -600,7 +600,7 @@ def logistic( def lognormal( mean: float = 0.0, sigma: float = 1.0, - size: Union[NdShapeLike, None] = None, + size: NdShapeLike | None = None, dtype: npt.DTypeLike = np.float64, ) -> ndarray: """ @@ -643,7 +643,7 @@ def lognormal( def logseries( p: float, - size: Union[NdShapeLike, None] = None, + size: NdShapeLike | None = None, dtype: npt.DTypeLike = np.uint32, ) -> ndarray: """ @@ -682,7 +682,7 @@ def logseries( def negative_binomial( n: int, p: float, - size: Union[NdShapeLike, None] = None, + size: NdShapeLike | None = None, dtype: npt.DTypeLike = np.uint32, ) -> ndarray: """ @@ -726,7 +726,7 @@ def negative_binomial( def noncentral_chisquare( df: float, nonc: float, - size: Union[NdShapeLike, None] = None, + size: NdShapeLike | None = None, dtype: npt.DTypeLike = np.float64, ) -> ndarray: """ @@ -769,7 +769,7 @@ def noncentral_f( dfnum: float, dfden: float, nonc: float, - size: Union[NdShapeLike, None] = None, + size: NdShapeLike | None = None, dtype: npt.DTypeLike = np.float64, ) -> ndarray: """ @@ -815,7 +815,7 @@ def noncentral_f( def normal( loc: float = 0.0, scale: float = 1.0, - size: Union[NdShapeLike, None] = None, + size: NdShapeLike | None = None, dtype: npt.DTypeLike = np.float64, ) -> ndarray: """ @@ -869,7 +869,7 @@ def normal( def pareto( a: float, - size: Union[NdShapeLike, None] = None, + size: NdShapeLike | None = None, dtype: npt.DTypeLike = np.float64, ) -> ndarray: """ @@ -920,9 +920,7 @@ def pareto( return get_static_generator().pareto(a, size, dtype) -def poisson( - lam: float = 1.0, size: Union[NdShapeLike, None] = None -) -> ndarray: +def poisson(lam: float = 1.0, size: NdShapeLike | None = None) -> ndarray: """ poisson(lam=1.0, size=None) @@ -960,7 +958,7 @@ def poisson( def power( a: float, - size: Union[NdShapeLike, None] = None, + size: NdShapeLike | None = None, dtype: npt.DTypeLike = np.float64, ) -> ndarray: """ @@ -1001,7 +999,7 @@ def power( return get_static_generator().power(a, size, dtype) -def rand(*shapeargs: int) -> Union[float, ndarray]: +def rand(*shapeargs: int) -> float | ndarray: """ rand(d0, d1, ..., dn) @@ -1035,10 +1033,10 @@ def rand(*shapeargs: int) -> Union[float, ndarray]: def randint( low: int, - high: Union[int, None] = None, - size: Union[NdShapeLike, None] = None, - dtype: Union[np.dtype[Any], type] = int, -) -> Union[int, ndarray, npt.NDArray[Any]]: + high: int | None = None, + size: NdShapeLike | None = None, + dtype: np.dtype[Any] | type = int, +) -> int | ndarray | npt.NDArray[Any]: """ randint(low, high=None, size=None, dtype=int) @@ -1094,7 +1092,7 @@ def randint( return get_static_generator().integers(low, high, size, dtype) -def randn(*shapeargs: int) -> Union[float, ndarray]: +def randn(*shapeargs: int) -> float | ndarray: """ randn(d0, d1, ..., dn) @@ -1131,8 +1129,8 @@ def randn(*shapeargs: int) -> Union[float, ndarray]: def random( - size: Union[NdShapeLike, None] = None, -) -> Union[float, ndarray]: + size: NdShapeLike | None = None, +) -> float | ndarray: """ random(size=None) @@ -1153,10 +1151,10 @@ def random( # deprecated in numpy from version 1.11.0 def random_integers( low: int, - high: Union[int, None] = None, - size: Union[NdShapeLike, None] = None, - dtype: Union[np.dtype[Any], type] = int, -) -> Union[int, ndarray, npt.NDArray[Any]]: + high: int | None = None, + size: NdShapeLike | None = None, + dtype: np.dtype[Any] | type = int, +) -> int | ndarray | npt.NDArray[Any]: """ random_integers(low, high=None, size=None) @@ -1205,8 +1203,8 @@ def random_integers( def random_sample( - size: Union[NdShapeLike, None] = None, dtype: npt.DTypeLike = np.float64 -) -> Union[float, ndarray]: + size: NdShapeLike | None = None, dtype: npt.DTypeLike = np.float64 +) -> float | ndarray: """ random_sample(size=None) @@ -1247,7 +1245,7 @@ def random_sample( def rayleigh( scale: float = 1.0, - size: Union[NdShapeLike, None] = None, + size: NdShapeLike | None = None, dtype: npt.DTypeLike = np.float64, ) -> ndarray: """ @@ -1287,7 +1285,7 @@ def rayleigh( def standard_cauchy( - size: Union[NdShapeLike, None] = None, + size: NdShapeLike | None = None, dtype: npt.DTypeLike = np.float64, ) -> ndarray: """ @@ -1321,7 +1319,7 @@ def standard_cauchy( def standard_exponential( - size: Union[NdShapeLike, None] = None, + size: NdShapeLike | None = None, dtype: npt.DTypeLike = np.float64, ) -> ndarray: """ @@ -1357,7 +1355,7 @@ def standard_exponential( def standard_gamma( shape: float, - size: Union[NdShapeLike, None] = None, + size: NdShapeLike | None = None, dtype: npt.DTypeLike = np.float64, ) -> ndarray: """ @@ -1395,7 +1393,7 @@ def standard_gamma( def standard_t( df: float, - size: Union[NdShapeLike, None] = None, + size: NdShapeLike | None = None, dtype: npt.DTypeLike = np.float64, ) -> ndarray: """ @@ -1437,7 +1435,7 @@ def triangular( left: float, mode: float, right: float, - size: Union[NdShapeLike, None] = None, + size: NdShapeLike | None = None, dtype: npt.DTypeLike = np.float64, ) -> ndarray: """ @@ -1484,7 +1482,7 @@ def triangular( def uniform( low: float = 0.0, high: float = 1.0, - size: Union[NdShapeLike, None] = None, + size: NdShapeLike | None = None, dtype: npt.DTypeLike = np.float64, ) -> ndarray: """ @@ -1532,7 +1530,7 @@ def uniform( def vonmises( mu: float, kappa: float, - size: Union[NdShapeLike, None] = None, + size: NdShapeLike | None = None, dtype: npt.DTypeLike = np.float64, ) -> ndarray: """ @@ -1578,7 +1576,7 @@ def vonmises( def wald( mean: float, scale: float, - size: Union[NdShapeLike, None] = None, + size: NdShapeLike | None = None, dtype: npt.DTypeLike = np.float64, ) -> ndarray: """ @@ -1624,7 +1622,7 @@ def wald( def weibull( a: float, - size: Union[NdShapeLike, None] = None, + size: NdShapeLike | None = None, dtype: npt.DTypeLike = np.float64, ) -> ndarray: """ @@ -1669,7 +1667,7 @@ def weibull( def zipf( a: float, - size: Union[NdShapeLike, None] = None, + size: NdShapeLike | None = None, dtype: npt.DTypeLike = np.uint32, ) -> ndarray: """ @@ -1735,5 +1733,5 @@ class RandomState: Random seed used to initialize the pseudo-random number generator. """ - def __init__(self, seed: Union[int, None] = None): + def __init__(self, seed: int | None = None): self._np_random_state = np.random.RandomState(seed or 0) diff --git a/cunumeric/runtime.py b/cunumeric/runtime.py index c989ac3a09..8d2f630e20 100644 --- a/cunumeric/runtime.py +++ b/cunumeric/runtime.py @@ -16,7 +16,7 @@ import warnings from functools import lru_cache, reduce -from typing import TYPE_CHECKING, Any, Optional, Sequence, TypeGuard, Union +from typing import TYPE_CHECKING, Any, Sequence, TypeGuard import legate.core.types as ty import numpy as np @@ -177,11 +177,11 @@ def destroy(self) -> None: def bitgenerator_populate_task( self, - task: Union[AutoTask, ManualTask], + task: AutoTask | ManualTask, taskop: int, generatorID: int, generatorType: int = 0, - seed: Union[int, None] = 0, + seed: int | None = 0, flags: int = 0, ) -> None: task.add_scalar_arg(taskop, ty.int32) @@ -193,7 +193,7 @@ def bitgenerator_populate_task( def bitgenerator_create( self, generatorType: int, - seed: Union[int, None], + seed: int | None, flags: int, forceCreate: bool = False, ) -> int: @@ -253,9 +253,9 @@ def get_next_random_epoch(self) -> int: def get_numpy_thunk( self, - obj: Union[ndarray, npt.NDArray[Any]], + obj: ndarray | npt.NDArray[Any], share: bool = False, - dtype: Optional[np.dtype[Any]] = None, + dtype: np.dtype[Any] | None = None, ) -> NumPyThunk: # Check to see if this object implements the Legate data interface if hasattr(obj, "__legate_data_interface__"): @@ -299,7 +299,7 @@ def get_numpy_thunk( @staticmethod def compute_parent_child_mapping( array: npt.NDArray[Any], - ) -> Union[tuple[Union[slice, None], ...], None]: + ) -> tuple[slice | None, ...] | None: # We need an algorithm for figuring out how to compute the # slice object that was used to generate a child array from # a parent array so we can build the same mapping from a @@ -324,7 +324,7 @@ def compute_parent_child_mapping( offsets.append((ptr_diff % mod) // div) assert div == array.dtype.itemsize # Now build the view and dimmap for the parent to create the view - key: tuple[Union[slice, None], ...] = () + key: tuple[slice | None, ...] = () child_idx = 0 child_strides = tuple(array.strides) parent_strides = tuple(array.base.strides) @@ -453,7 +453,7 @@ def create_empty_thunk( self, shape: NdShape, dtype: ty.Type, - inputs: Optional[Sequence[NumPyThunk]] = None, + inputs: Sequence[NumPyThunk] | None = None, ) -> NumPyThunk: from ._thunk.deferred import DeferredArray @@ -512,7 +512,7 @@ def is_eager_shape(self, shape: NdShape) -> bool: return volume <= self.max_eager_volume @staticmethod - def are_all_eager_inputs(inputs: Optional[Sequence[NumPyThunk]]) -> bool: + def are_all_eager_inputs(inputs: Sequence[NumPyThunk] | None) -> bool: from ._thunk.eager import EagerArray from ._thunk.thunk import NumPyThunk @@ -532,7 +532,7 @@ def is_eager_array(array: NumPyThunk) -> TypeGuard[EagerArray]: @staticmethod def is_deferred_array( - array: Optional[NumPyThunk], + array: NumPyThunk | None, ) -> TypeGuard[DeferredArray]: from ._thunk.deferred import DeferredArray diff --git a/cunumeric/types.py b/cunumeric/types.py index 1a2cfd1cc3..719a7ca529 100644 --- a/cunumeric/types.py +++ b/cunumeric/types.py @@ -14,15 +14,15 @@ # from __future__ import annotations -from typing import Literal, Tuple, TypeAlias, Union +from typing import Literal, TypeAlias BoundsMode: TypeAlias = Literal["raise", "wrap", "clip"] CastingKind: TypeAlias = Literal["no", "equiv", "safe", "same_kind", "unsafe"] -NdShape: TypeAlias = Tuple[int, ...] +NdShape: TypeAlias = tuple[int, ...] -NdShapeLike: TypeAlias = Union[int, NdShape] +NdShapeLike: TypeAlias = int | NdShape SortSide: TypeAlias = Literal["left", "right"] diff --git a/tests/integration/test_einsum.py b/tests/integration/test_einsum.py index 4fcdd2402f..cdbcb5afba 100644 --- a/tests/integration/test_einsum.py +++ b/tests/integration/test_einsum.py @@ -15,7 +15,6 @@ from functools import lru_cache from itertools import permutations, product -from typing import List, Optional, Set, Tuple import numpy as np import pytest @@ -44,7 +43,7 @@ def gen_operand( used_modes: int, dim_lim: int, mode_lim: int, - op: Optional[List[int]] = None, + op: list[int] | None = None, ): if op is None: op = [] @@ -77,8 +76,8 @@ def gen_operand( # Exhaustively generate all (normalized) expressions within some limits. These # limits are set low by default, to keep the unit test running time low. def gen_expr( - opers: Optional[List[List[int]]] = None, - cache: Optional[Set[Tuple[Tuple[int]]]] = None, + opers: list[list[int]] | None = None, + cache: set[tuple[tuple[int]]] | None = None, ): if opers is None: opers = [] diff --git a/tests/integration/test_random_creation.py b/tests/integration/test_random_creation.py index f4bdecec79..b4f8f84205 100644 --- a/tests/integration/test_random_creation.py +++ b/tests/integration/test_random_creation.py @@ -14,7 +14,7 @@ # import os -from typing import Any, Tuple +from typing import Any import numpy as np import pytest @@ -41,14 +41,14 @@ def test_randn(): def reseed_and_gen_random( func: str, seed: Any, *args: Any, **kwargs: Any -) -> Tuple[Any, Any]: +) -> tuple[Any, Any]: """Reseeed singleton rng and generate random in NumPy and cuNumeric.""" return gen_random_from_both(func, *args, **kwargs) def gen_random_from_both( func: str, *args: Any, **kwargs: Any -) -> Tuple[Any, Any]: +) -> tuple[Any, Any]: """Call the same random function from both NumPy and cuNumeric.""" return ( getattr(np.random, func)(*args, **kwargs), diff --git a/tests/integration/utils/comparisons.py b/tests/integration/utils/comparisons.py index 65571b38c1..64428e46a7 100644 --- a/tests/integration/utils/comparisons.py +++ b/tests/integration/utils/comparisons.py @@ -14,7 +14,7 @@ # from itertools import islice -from typing import Any, Union +from typing import Any import numpy as np @@ -26,7 +26,7 @@ def allclose( atol: float = 1e-8, equal_nan: bool = False, *, - diff_limit: Union[int, None] = 5, # None means no limit at all + diff_limit: int | None = 5, # None means no limit at all check_dtype: bool = True, ) -> bool: if np.shape(a) != np.shape(b): diff --git a/tests/unit/cunumeric/test_utils_linalg.py b/tests/unit/cunumeric/test_utils_linalg.py index bf51d19f67..0480f7a398 100644 --- a/tests/unit/cunumeric/test_utils_linalg.py +++ b/tests/unit/cunumeric/test_utils_linalg.py @@ -13,8 +13,6 @@ # limitations under the License. # -from typing import List, Tuple, Union - import numpy as np import pytest @@ -98,7 +96,7 @@ def test_matmul_modes(a: int, b: int) -> None: assert _matmul_modes_oracle(a, b) -AxesType = Union[int, Tuple[int, int], Tuple[List[int], List[int]]] +AxesType = int | tuple[int, int] | tuple[list[int], list[int]] def _tensordot_modes_oracle(a_ndim: int, b_ndim: int, axes: AxesType) -> bool: From 901e9337fc121cb04ade5f9d5004f69079512526 Mon Sep 17 00:00:00 2001 From: Parag Kulkarni Date: Tue, 12 Mar 2024 16:05:06 +0530 Subject: [PATCH 170/462] Add upload-enabled param to call gh-build.yml (#136) --- .github/workflows/gh-build-and-test.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/gh-build-and-test.yml b/.github/workflows/gh-build-and-test.yml index cebaf0bc01..8362d3dbb4 100644 --- a/.github/workflows/gh-build-and-test.yml +++ b/.github/workflows/gh-build-and-test.yml @@ -43,6 +43,7 @@ jobs: legate-gh-ci-tag: "v23.11.00" cmake-preset: "" ucx-enabled: false + upload_enabled: false secrets: inherit setup-test: From 1f1abc571a269db5d0eae21b9ef0ed531047628b Mon Sep 17 00:00:00 2001 From: Marcin Zalewski Date: Tue, 12 Mar 2024 15:17:18 -0700 Subject: [PATCH 171/462] Fix typo in workflow (#140) --- .github/workflows/gh-build-and-test.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/gh-build-and-test.yml b/.github/workflows/gh-build-and-test.yml index 8362d3dbb4..696efe5c96 100644 --- a/.github/workflows/gh-build-and-test.yml +++ b/.github/workflows/gh-build-and-test.yml @@ -43,7 +43,7 @@ jobs: legate-gh-ci-tag: "v23.11.00" cmake-preset: "" ucx-enabled: false - upload_enabled: false + upload-enabled: false secrets: inherit setup-test: From bb45fd8f3b51b09a49fc2726cf0502311ab5a0a4 Mon Sep 17 00:00:00 2001 From: Marcin Zalewski Date: Wed, 13 Mar 2024 00:35:14 -0700 Subject: [PATCH 172/462] Update legate core to 24.05 latest commit (#139) * Update legate core to 24.05 latest commit * Update hash --- cmake/versions.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cmake/versions.json b/cmake/versions.json index 123ef63d2d..48a5aa9507 100644 --- a/cmake/versions.json +++ b/cmake/versions.json @@ -3,11 +3,11 @@ "legate_core_internal" : { "repo": "legate.core.internal", "artifact_name": "${{ inputs.platform }}-${{ inputs.build-type }}-<>-${{ inputs.target-device }}-release-gcc-<>", - "version": "24.01.00", + "version": "24.05.00", "git_url" : "https://github.com/nv-legate/legate.core.internal.git", "git_shallow": false, "always_download": false, - "git_tag" : "2d257d20acfe034337a674f689d17488d6b2ba06" + "git_tag" : "1d29d332e1b59357e734dc1cabdbb3b0c6f2a254" } } } From 11725dd082e808497a48118927cbe77af89da11d Mon Sep 17 00:00:00 2001 From: Marcin Zalewski Date: Wed, 13 Mar 2024 00:35:35 -0700 Subject: [PATCH 173/462] Update version (#138) --- CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 18b121f505..1de914c1da 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -55,7 +55,7 @@ include(rapids-cuda) include(rapids-export) include(rapids-find) -set(cunumeric_version 24.01.00) +set(cunumeric_version 24.05.00) # For now we want the optimization flags to match on both normal make and cmake # builds so we override the cmake defaults here for release, this changes From a09f28a41184825bfb6ebf28b49364820a472310 Mon Sep 17 00:00:00 2001 From: Manolis Papadakis Date: Fri, 15 Mar 2024 15:54:29 -0700 Subject: [PATCH 174/462] Fix compilation under Thrust 2.2.0 (#142) As pulled by the latest legate.core CCCL update Also remove the thrust overrides, since we're not really using them (or maybe we are?) --- install.py | 14 -------------- src/cunumeric/sort/cub_sort.cuh | 2 +- src/cunumeric/unary/unary_op_util.h | 4 ++++ 3 files changed, 5 insertions(+), 15 deletions(-) diff --git a/install.py b/install.py index a532c988cd..7a9671f4c4 100755 --- a/install.py +++ b/install.py @@ -153,7 +153,6 @@ def install_cunumeric( spy, tblis_dir, thread_count, - thrust_dir, unknown, verbose, ): @@ -198,7 +197,6 @@ def install_cunumeric( print("spy: ", spy) print("tblis_dir: ", tblis_dir) print("thread_count: ", thread_count) - print("thrust_dir: ", thrust_dir) print("unknown: ", unknown) print("verbose: ", verbose) @@ -225,7 +223,6 @@ def validate_path(path): cuda_dir = validate_path(cuda_dir) nccl_dir = validate_path(nccl_dir) tblis_dir = validate_path(tblis_dir) - thrust_dir = validate_path(thrust_dir) curand_dir = validate_path(curand_dir) gasnet_dir = validate_path(gasnet_dir) cusolvermp_dir = validate_path(cusolvermp_dir) @@ -247,7 +244,6 @@ def validate_path(path): print("nccl_dir: ", nccl_dir) print("tblis_dir: ", tblis_dir) print("legate_dir: ", legate_dir) - print("thrust_dir: ", thrust_dir) print("curand_dir: ", curand_dir) print("gasnet_dir: ", gasnet_dir) print("cusolvermp_dir: ", cusolvermp_dir) @@ -369,8 +365,6 @@ def validate_path(path): cmake_flags += ["-DGASNet_CONDUIT=%s" % conduit] if tblis_dir: cmake_flags += ["-Dtblis_ROOT=%s" % tblis_dir] - if thrust_dir: - cmake_flags += ["-DThrust_ROOT=%s" % thrust_dir] if openblas_dir: cmake_flags += ["-DBLAS_DIR=%s" % openblas_dir] if cusolvermp_dir: @@ -507,14 +501,6 @@ def driver(): default=os.environ.get("CUTENSOR_PATH"), help="Path to cuTensor installation directory.", ) - parser.add_argument( - "--with-thrust", - dest="thrust_dir", - metavar="DIR", - required=False, - default=os.environ.get("THRUST_PATH"), - help="Path to Thrust installation directory.", - ) parser.add_argument( "--with-nccl", dest="nccl_dir", diff --git a/src/cunumeric/sort/cub_sort.cuh b/src/cunumeric/sort/cub_sort.cuh index b06f741237..dd54bedbbf 100644 --- a/src/cunumeric/sort/cub_sort.cuh +++ b/src/cunumeric/sort/cub_sort.cuh @@ -51,7 +51,7 @@ void cub_local_sort(const VAL* values_in, keys_in.ptr(0), values_out, sizeof(VAL) * volume, cudaMemcpyDeviceToDevice, stream)); } - auto multiply = [=] __device__(auto const& input) { return input * sort_dim_size; }; + auto multiply = [=] __host__ __device__(int x) { return x * sort_dim_size; }; size_t temp_storage_bytes = 0; if (indices_out == nullptr) { diff --git a/src/cunumeric/unary/unary_op_util.h b/src/cunumeric/unary/unary_op_util.h index f741ee6499..27c0bdc3a6 100644 --- a/src/cunumeric/unary/unary_op_util.h +++ b/src/cunumeric/unary/unary_op_util.h @@ -20,6 +20,10 @@ #include "cunumeric/arg.h" #include "cunumeric/arg.inl" +#ifdef __NVCC__ +#include "thrust/complex.h" +#endif + #define _USE_MATH_DEFINES #include From 636aad3d28c860ca09ad2000e1b6186695d0a6fa Mon Sep 17 00:00:00 2001 From: Sandeep Datta <128171450+sandeepd-nv@users.noreply.github.com> Date: Mon, 18 Mar 2024 23:29:32 +0530 Subject: [PATCH 175/462] Replaced references to the branch v23.11.00 with references to the tag v1. (#143) Ignoring unrelated mypy failure. --- .github/workflows/gh-build-and-test.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/gh-build-and-test.yml b/.github/workflows/gh-build-and-test.yml index 696efe5c96..a235a8ee28 100644 --- a/.github/workflows/gh-build-and-test.yml +++ b/.github/workflows/gh-build-and-test.yml @@ -31,7 +31,7 @@ jobs: needs: setup-build name: "Build (${{ inputs.platform }}, ${{ inputs.target-device }})" uses: - nv-legate/legate-gh-ci/.github/workflows/gh-build.yml@v23.11.00 + nv-legate/legate-gh-ci/.github/workflows/gh-build.yml@v1 with: client-repo: ${{ github.event.repository.name }} target-device: ${{ inputs.target-device }} @@ -40,7 +40,7 @@ jobs: use-container: ${{ inputs.platform == 'linux' }} platform: ${{ inputs.platform }} dependencies-file: "cmake/versions.json" - legate-gh-ci-tag: "v23.11.00" + legate-gh-ci-tag: "v1" cmake-preset: "" ucx-enabled: false upload-enabled: false @@ -110,7 +110,7 @@ jobs: matrix: ${{fromJson(needs.setup-test.outputs.matrix)}} uses: - nv-legate/legate-gh-ci/.github/workflows/gh-test-within-container.yml@v23.11.00 + nv-legate/legate-gh-ci/.github/workflows/gh-test-within-container.yml@v1 with: client-repo: ${{ github.event.repository.name }} build-type: ci @@ -120,7 +120,7 @@ jobs: has-gpu: ${{ matrix.runner.type == 'gpu' }} test-options: ${{ matrix.test-config.test-options }} platform: ${{ inputs.platform }} - legate-gh-ci-tag: "v23.11.00" + legate-gh-ci-tag: "v1" cmake-preset: "" ucx-enabled: false secrets: inherit From 713d3f05330451ad1c9a80657c01c337cff1989d Mon Sep 17 00:00:00 2001 From: Bryan Van de Ven Date: Mon, 18 Mar 2024 12:33:01 -0700 Subject: [PATCH 176/462] use more specific Sphinx type for extension (#145) --- cunumeric/_sphinxext/_cunumeric_directive.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cunumeric/_sphinxext/_cunumeric_directive.py b/cunumeric/_sphinxext/_cunumeric_directive.py index ef6402f6c4..03fb3be32b 100644 --- a/cunumeric/_sphinxext/_cunumeric_directive.py +++ b/cunumeric/_sphinxext/_cunumeric_directive.py @@ -15,14 +15,14 @@ from __future__ import annotations from docutils import nodes -from docutils.statemachine import ViewList +from docutils.statemachine import StringList from sphinx.util.docutils import SphinxDirective from sphinx.util.nodes import nested_parse_with_titles class CunumericDirective(SphinxDirective): def parse(self, rst_text: str, annotation: str) -> list[nodes.Node]: - result = ViewList() + result = StringList() for line in rst_text.split("\n"): result.append(line, annotation) node = nodes.paragraph() From 6dd432089e8d3b786d5d5fb161d313ea1f5c9443 Mon Sep 17 00:00:00 2001 From: Irina Demeshko Date: Tue, 19 Mar 2024 12:18:09 -0700 Subject: [PATCH 177/462] WIP: optimizing repeat for scalar value of repeats (#137) --- cunumeric/_thunk/deferred.py | 28 ++++++++++++++++++++++--- cunumeric/_thunk/thunk.py | 2 +- src/cunumeric/index/repeat.cc | 11 +++------- src/cunumeric/index/repeat.cu | 19 +++++++---------- src/cunumeric/index/repeat_omp.cc | 11 +++------- src/cunumeric/index/repeat_template.inl | 4 +++- 6 files changed, 42 insertions(+), 33 deletions(-) diff --git a/cunumeric/_thunk/deferred.py b/cunumeric/_thunk/deferred.py index fa496354d9..6fe09523a6 100644 --- a/cunumeric/_thunk/deferred.py +++ b/cunumeric/_thunk/deferred.py @@ -1941,12 +1941,34 @@ def trilu(self, rhs: Any, k: int, lower: bool) -> None: def repeat( self, repeats: Any, axis: int, scalar_repeats: bool ) -> DeferredArray: - out = runtime.create_unbound_thunk(self.base.type, ndim=self.ndim) task = legate_runtime.create_auto_task( self.library, CuNumericOpCode.REPEAT ) - p_self = task.add_input(self.base) - task.add_output(out.base) + if scalar_repeats: + out_shape = tuple( + self.shape[dim] * repeats if dim == axis else self.shape[dim] + for dim in range(self.ndim) + ) + out = cast( + DeferredArray, + runtime.create_empty_thunk( + out_shape, + dtype=self.base.type, + inputs=[self], + ), + ) + p_self = task.declare_partition() + p_out = task.declare_partition() + task.add_input(self.base, p_self) + task.add_output(out.base, p_out) + factors = tuple( + repeats if dim == axis else 1 for dim in range(self.ndim) + ) + task.add_constraint(scale(factors, p_self, p_out)) + else: + out = runtime.create_unbound_thunk(self.base.type, ndim=self.ndim) + p_self = task.add_input(self.base) + task.add_output(out.base) # We pass axis now but don't use for 1D case (will use for ND case task.add_scalar_arg(axis, ty.int32) task.add_scalar_arg(scalar_repeats, ty.bool_) diff --git a/cunumeric/_thunk/thunk.py b/cunumeric/_thunk/thunk.py index 77560feb86..56cda7cf9c 100644 --- a/cunumeric/_thunk/thunk.py +++ b/cunumeric/_thunk/thunk.py @@ -1,4 +1,4 @@ -# Copyright 2021-2022 NVIDIA Corporation +# Copyright 2021-2024 NVIDIA Corporation # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/src/cunumeric/index/repeat.cc b/src/cunumeric/index/repeat.cc index d33a49711c..9cf28e2c80 100644 --- a/src/cunumeric/index/repeat.cc +++ b/src/cunumeric/index/repeat.cc @@ -1,4 +1,4 @@ -/* Copyright 2022 NVIDIA Corporation +/* Copyright 2022-2024 NVIDIA Corporation * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -31,12 +31,8 @@ struct RepeatImplBody { const int32_t axis, const Rect& in_rect) const { - Point extents = in_rect.hi - in_rect.lo + Point::ONES(); - extents[axis] *= repeats; - - auto out = out_array.create_output_buffer(extents, true); - - Rect out_rect(Point::ZEROES(), extents - Point::ONES()); + auto out_rect = out_array.shape(); + auto out = out_array.write_accessor(out_rect); Pitches pitches; auto out_volume = pitches.flatten(out_rect); @@ -44,7 +40,6 @@ struct RepeatImplBody { auto out_p = pitches.unflatten(idx, out_rect.lo); auto in_p = out_p; in_p[axis] /= repeats; - in_p += in_rect.lo; out[out_p] = in[in_p]; } } diff --git a/src/cunumeric/index/repeat.cu b/src/cunumeric/index/repeat.cu index 40d4257301..2bfa6451cf 100644 --- a/src/cunumeric/index/repeat.cu +++ b/src/cunumeric/index/repeat.cu @@ -1,4 +1,4 @@ -/* Copyright 2022 NVIDIA Corporation +/* Copyright 2022-2024 NVIDIA Corporation * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -50,11 +50,11 @@ static __global__ void __launch_bounds__(THREADS_PER_BLOCK, MIN_CTAS_PER_SM) template __global__ static void __launch_bounds__(THREADS_PER_BLOCK, MIN_CTAS_PER_SM) - repeat_kernel(Buffer out, + repeat_kernel(AccessorWO out, const AccessorRO in, int64_t repeats, const int32_t axis, - const Point in_lo, + const Point out_lo, const Pitches pitches, const size_t volume) { @@ -62,10 +62,9 @@ __global__ static void __launch_bounds__(THREADS_PER_BLOCK, MIN_CTAS_PER_SM) if (idx >= volume) { return; } - auto out_p = pitches.unflatten(idx, Point::ZEROES()); + auto out_p = pitches.unflatten(idx, out_lo); auto in_p = out_p; in_p[axis] /= repeats; - in_p += in_lo; out[out_p] = in[in_p]; } @@ -107,12 +106,8 @@ struct RepeatImplBody { const int32_t axis, const Rect& in_rect) const { - Point extents = in_rect.hi - in_rect.lo + Point::ONES(); - extents[axis] *= repeats; - - auto out = out_array.create_output_buffer(extents, true); - - Rect out_rect(Point::ZEROES(), extents - Point::ONES()); + auto out_rect = out_array.shape(); + auto out = out_array.write_accessor(out_rect); Pitches pitches{}; auto out_volume = pitches.flatten(out_rect); @@ -120,7 +115,7 @@ struct RepeatImplBody { auto stream = get_cached_stream(); repeat_kernel<<>>( - out, in, repeats, axis, in_rect.lo, pitches, out_volume); + out, in, repeats, axis, out_rect.lo, pitches, out_volume); CHECK_CUDA_STREAM(stream); } diff --git a/src/cunumeric/index/repeat_omp.cc b/src/cunumeric/index/repeat_omp.cc index fca6da706b..4798d68587 100644 --- a/src/cunumeric/index/repeat_omp.cc +++ b/src/cunumeric/index/repeat_omp.cc @@ -1,4 +1,4 @@ -/* Copyright 2022 NVIDIA Corporation +/* Copyright 2022-2024 NVIDIA Corporation * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -36,12 +36,8 @@ struct RepeatImplBody { const int32_t axis, const Rect& in_rect) const { - Point extents = in_rect.hi - in_rect.lo + Point::ONES(); - extents[axis] *= repeats; - - auto out = out_array.create_output_buffer(extents, true); - - Rect out_rect(Point::ZEROES(), extents - Point::ONES()); + auto out_rect = out_array.shape(); + auto out = out_array.write_accessor(out_rect); Pitches pitches; auto out_volume = pitches.flatten(out_rect); @@ -50,7 +46,6 @@ struct RepeatImplBody { auto out_p = pitches.unflatten(idx, out_rect.lo); auto in_p = out_p; in_p[axis] /= repeats; - in_p += in_rect.lo; out[out_p] = in[in_p]; } } diff --git a/src/cunumeric/index/repeat_template.inl b/src/cunumeric/index/repeat_template.inl index 18d58f4c28..0be9ccb3ac 100644 --- a/src/cunumeric/index/repeat_template.inl +++ b/src/cunumeric/index/repeat_template.inl @@ -37,7 +37,9 @@ struct RepeatImpl { auto input_arr = args.input.read_accessor(input_rect); if (input_rect.empty()) { - args.output.bind_empty_data(); + if (!args.scalar_repeats) { + args.output.bind_empty_data(); + } return; } From 0624001e14951464b9215cb4ab4222fa1638f569 Mon Sep 17 00:00:00 2001 From: Manolis Papadakis Date: Thu, 21 Mar 2024 09:39:10 -0700 Subject: [PATCH 178/462] Expose is_supported_dtype to the public interface (#150) Also take this opportunity to clean up a naming inconsistency; NumPy types are "dtypes", core types are "types". --- cunumeric/__init__.py | 1 + cunumeric/_array/array.py | 10 +++++----- cunumeric/_thunk/deferred.py | 4 ++-- cunumeric/_utils/array.py | 17 +++++++++++++++-- cunumeric/runtime.py | 8 ++++---- tests/integration/test_argsort.py | 2 +- tests/integration/test_prod.py | 2 +- tests/integration/test_searchsorted.py | 2 +- tests/unit/cunumeric/test_utils_array.py | 6 +++--- 9 files changed, 33 insertions(+), 19 deletions(-) diff --git a/cunumeric/__init__.py b/cunumeric/__init__.py index 3df52a64ca..34c3ada133 100644 --- a/cunumeric/__init__.py +++ b/cunumeric/__init__.py @@ -31,6 +31,7 @@ from ._array.util import maybe_convert_to_np_ndarray from ._module import * from ._ufunc import * +from ._utils.array import is_supported_dtype from ._utils.coverage import clone_module clone_module(_np, globals(), maybe_convert_to_np_ndarray) diff --git a/cunumeric/_array/array.py b/cunumeric/_array/array.py index 729717fe2f..b138e75e97 100644 --- a/cunumeric/_array/array.py +++ b/cunumeric/_array/array.py @@ -30,7 +30,7 @@ ) from .. import _ufunc -from .._utils.array import calculate_volume, to_core_dtype +from .._utils.array import calculate_volume, to_core_type from .._utils.coverage import FALLBACK_WARNING, clone_class, is_implemented from .._utils.linalg import dot_modes from .._utils.structure import deep_apply @@ -128,7 +128,7 @@ def __init__( for inp in inputs if isinstance(inp, ndarray) ] - core_dtype = to_core_dtype(dtype) + core_dtype = to_core_type(dtype) self._thunk = runtime.create_empty_thunk( sanitized_shape, core_dtype, inputs ) @@ -660,7 +660,7 @@ def __contains__(self, item: Any) -> ndarray: args = (np.array(item, dtype=self.dtype),) if args[0].size != 1: raise ValueError("contains needs scalar item") - core_dtype = to_core_dtype(self.dtype) + core_dtype = to_core_type(self.dtype) return perform_unary_reduction( UnaryRedCode.CONTAINS, self, @@ -1975,7 +1975,7 @@ def clip( return convert_to_cunumeric_ndarray( self.__array__().clip(args[0], args[1]) ) - core_dtype = to_core_dtype(self.dtype) + core_dtype = to_core_type(self.dtype) extra_args = (Scalar(min, core_dtype), Scalar(max, core_dtype)) return perform_unary_op( UnaryOpCode.CLIP, self, out=out, extra_args=extra_args @@ -2971,7 +2971,7 @@ def var( # FIXME(wonchanl): the following code blocks on mu to convert # it to a Scalar object. We need to get rid of this blocking by # allowing the extra arguments to be Legate stores - args=(Scalar(mu.__array__(), to_core_dtype(self.dtype)),), + args=(Scalar(mu.__array__(), to_core_type(self.dtype)),), ) else: # TODO(https://github.com/nv-legate/cunumeric/issues/591) diff --git a/cunumeric/_thunk/deferred.py b/cunumeric/_thunk/deferred.py index 6fe09523a6..ddc5d9641b 100644 --- a/cunumeric/_thunk/deferred.py +++ b/cunumeric/_thunk/deferred.py @@ -49,7 +49,7 @@ normalize_axis_tuple, ) -from .._utils.array import is_advanced_indexing, to_core_dtype +from .._utils.array import is_advanced_indexing, to_core_type from ..config import ( BinaryOpCode, BitGeneratorDistribution, @@ -1701,7 +1701,7 @@ def select( c_arr = c._broadcast(self.shape) task.add_input(c_arr) task.add_alignment(c_arr, out_arr) - task.add_scalar_arg(default, to_core_dtype(default.dtype)) + task.add_scalar_arg(default, to_core_type(default.dtype)) task.execute() # Create or extract a diagonal from a matrix diff --git a/cunumeric/_utils/array.py b/cunumeric/_utils/array.py index 1ba3c6bfee..705335cd81 100644 --- a/cunumeric/_utils/array.py +++ b/cunumeric/_utils/array.py @@ -40,11 +40,24 @@ } -def is_supported_type(dtype: str | np.dtype[Any]) -> bool: +def is_supported_dtype(dtype: str | np.dtype[Any]) -> bool: + """ + Whether a NumPy dtype is supported by cuNumeric + + Parameters + ---------- + dtype : data-type + The dtype to query + + Returns + ------- + res : bool + True if `dtype` is a supported dtype + """ return np.dtype(dtype) in SUPPORTED_DTYPES -def to_core_dtype(dtype: str | np.dtype[Any]) -> ty.Type: +def to_core_type(dtype: str | np.dtype[Any]) -> ty.Type: core_dtype = SUPPORTED_DTYPES.get(np.dtype(dtype)) if core_dtype is None: raise TypeError(f"cuNumeric does not support dtype={dtype}") diff --git a/cunumeric/runtime.py b/cunumeric/runtime.py index 8d2f630e20..33db34f9ac 100644 --- a/cunumeric/runtime.py +++ b/cunumeric/runtime.py @@ -23,7 +23,7 @@ from legate.core import LEGATE_MAX_DIM, Scalar, TaskTarget, get_legate_runtime from legate.settings import settings as legate_settings -from ._utils.array import calculate_volume, is_supported_type, to_core_dtype +from ._utils.array import calculate_volume, is_supported_dtype, to_core_type from ._utils.stack import find_last_user_stacklevel from .config import ( BitGeneratorOperation, @@ -60,7 +60,7 @@ def thunk_from_scalar( from ._thunk.deferred import DeferredArray store = legate_runtime.create_store_from_scalar( - Scalar(bytes, to_core_dtype(dtype)), + Scalar(bytes, to_core_type(dtype)), shape=shape, ) return DeferredArray(store) @@ -377,7 +377,7 @@ def find_or_create_array_thunk( from ._thunk.deferred import DeferredArray assert isinstance(array, np.ndarray) - if not is_supported_type(array.dtype): + if not is_supported_dtype(array.dtype): raise TypeError(f"cuNumeric does not support dtype={array.dtype}") # We have to be really careful here to handle the case of @@ -429,7 +429,7 @@ def find_or_create_array_thunk( # This is not a scalar so make a field. # We won't try to cache these bigger arrays. store = legate_runtime.create_store_from_buffer( - to_core_dtype(array.dtype), + to_core_type(array.dtype), array.shape, array.copy() if transfer == TransferType.MAKE_COPY else array, # This argument should really be called "donate" diff --git a/tests/integration/test_argsort.py b/tests/integration/test_argsort.py index b36de0d16b..656e22b808 100644 --- a/tests/integration/test_argsort.py +++ b/tests/integration/test_argsort.py @@ -98,7 +98,7 @@ def test_structured_array_order(self): # if self.deferred is None: # if self.parent is None: # - # > assert self.runtime.is_supported_type(self.array.dtype) + # > assert self.runtime.is_supported_dtype(self.array.dtype) # E # AssertionError # diff --git a/tests/integration/test_prod.py b/tests/integration/test_prod.py index c004c95a38..4a820905ff 100644 --- a/tests/integration/test_prod.py +++ b/tests/integration/test_prod.py @@ -222,7 +222,7 @@ def test_dtype_complex(self, dtype): # allclose hits assertion error: # File "/legate/cunumeric/cunumeric/eager.py", line 293, # in to_deferred_array - # assert self.runtime.is_supported_type(self.array.dtype) + # assert self.runtime.is_supported_dtype(self.array.dtype) # AssertionError assert allclose(out_np, out_num) diff --git a/tests/integration/test_searchsorted.py b/tests/integration/test_searchsorted.py index c3d1461afe..03860ec971 100644 --- a/tests/integration/test_searchsorted.py +++ b/tests/integration/test_searchsorted.py @@ -83,7 +83,7 @@ def test_val_none(self): # cuNumeric raises AssertionError # if self.deferred is None: # if self.parent is None: - # > assert self.runtime.is_supported_type + # > assert self.runtime.is_supported_dtype # (self.array.dtype) # E AssertionError # cunumeric/cunumeric/eager.py:to_deferred_array() diff --git a/tests/unit/cunumeric/test_utils_array.py b/tests/unit/cunumeric/test_utils_array.py index 27aa59a129..34e124c479 100644 --- a/tests/unit/cunumeric/test_utils_array.py +++ b/tests/unit/cunumeric/test_utils_array.py @@ -75,17 +75,17 @@ class Test_is_supported_dtype: @pytest.mark.parametrize("value", ["foo", 10, 10.2, (), set()]) def test_type_bad(self, value) -> None: with pytest.raises(TypeError): - m.to_core_dtype(value) + m.to_core_type(value) @pytest.mark.parametrize("value", EXPECTED_SUPPORTED_DTYPES) def test_supported(self, value) -> None: - m.to_core_dtype(value) + m.to_core_type(value) # This is just a representative sample, not exhasutive @pytest.mark.parametrize("value", [np.float128, np.datetime64, [], {}]) def test_unsupported(self, value) -> None: with pytest.raises(TypeError): - m.to_core_dtype(value) + m.to_core_type(value) @pytest.mark.parametrize( From 81e958088ca62c00b601dd66b3c4527635070028 Mon Sep 17 00:00:00 2001 From: Robin Wang <104830875+robinwnv@users.noreply.github.com> Date: Wed, 27 Mar 2024 14:03:36 +0800 Subject: [PATCH 179/462] Port and test argwhere. (#75) * Port and test argwhere. * Check if dim=0 and add logic to handle it. * Add tests for array dim from 4 to 7. * Add tests for large array. --- src/cunumeric/ndarray.cc | 43 ++++ src/cunumeric/ndarray.h | 1 + src/cunumeric/operators.cc | 2 + src/cunumeric/operators.h | 2 + src/cunumeric/runtime.cc | 6 + src/cunumeric/runtime.h | 1 + tests/cpp/integration/test_argwhere.cc | 298 +++++++++++++++++++++++++ 7 files changed, 353 insertions(+) create mode 100644 tests/cpp/integration/test_argwhere.cc diff --git a/src/cunumeric/ndarray.cc b/src/cunumeric/ndarray.cc index 0f3317015b..9d2e11a37a 100644 --- a/src/cunumeric/ndarray.cc +++ b/src/cunumeric/ndarray.cc @@ -42,6 +42,21 @@ struct generate_zero_fn { } }; +struct check_nonzero_scalar_fn { + template + bool operator()(cunumeric::NDArray array) + { + assert(array.dim() == 0); + using VAL = legate::type_of; + auto acc = array.get_read_accessor(); + if (acc[0] == VAL(0)) { + return false; + } else { + return true; + } + } +}; + struct generate_identity_fn { template struct generator { @@ -719,6 +734,34 @@ NDArray NDArray::transpose(std::vector axes) return NDArray(store_.transpose(std::move(axes))); } +NDArray NDArray::argwhere() +{ + auto runtime = CuNumericRuntime::get_runtime(); + if (dim() == 0) { + auto not_zero = legate::type_dispatch(type().code(), check_nonzero_scalar_fn{}, *this); + if (not_zero) { + auto result = runtime->create_array({1, 0}, legate::int64()); + return result; + } else { + auto result = runtime->create_array({0, 0}, legate::int64()); + return result; + } + } + + auto result = runtime->create_array(legate::int64(), 2); + + auto task = runtime->create_task(CuNumericOpCode::CUNUMERIC_ARGWHERE); + auto part_out = task.declare_partition(); + auto part_in = task.declare_partition(); + task.add_output(result.store_, part_out); + task.add_input(store_, part_in); + if (dim() > 1) { + task.add_constraint(legate::broadcast(part_in, legate::from_range(1, dim()))); + } + runtime->submit(std::move(task)); + return result; +} + NDArray NDArray::flip(std::optional> axis) { auto runtime = CuNumericRuntime::get_runtime(); diff --git a/src/cunumeric/ndarray.h b/src/cunumeric/ndarray.h index 9d0d8bb14a..9b2d629bd4 100644 --- a/src/cunumeric/ndarray.h +++ b/src/cunumeric/ndarray.h @@ -89,6 +89,7 @@ class NDArray { std::string kind = "quicksort"); NDArray transpose(); NDArray transpose(std::vector axes); + NDArray argwhere(); NDArray flip(std::optional> axis = std::nullopt); NDArray all(std::optional> axis = std::nullopt, std::optional out = std::nullopt, diff --git a/src/cunumeric/operators.cc b/src/cunumeric/operators.cc index cc892592e4..1cdde53a34 100644 --- a/src/cunumeric/operators.cc +++ b/src/cunumeric/operators.cc @@ -468,6 +468,8 @@ NDArray moveaxis(NDArray a, std::vector source, std::vector de return a.transpose(order); } +NDArray argwhere(NDArray input) { return input.argwhere(); } + NDArray diag(NDArray v, int32_t k) { int32_t dim = v.dim(); diff --git a/src/cunumeric/operators.h b/src/cunumeric/operators.h index a3dbbe9fda..742e3e5d9f 100644 --- a/src/cunumeric/operators.h +++ b/src/cunumeric/operators.h @@ -111,6 +111,8 @@ NDArray transpose(NDArray a, std::vector axes); NDArray moveaxis(NDArray a, std::vector source, std::vector destination); +NDArray argwhere(NDArray input); + NDArray flip(NDArray input, std::optional> axis = std::nullopt); void put(NDArray& a, NDArray indices, NDArray values, std::string mode = "raise"); diff --git a/src/cunumeric/runtime.cc b/src/cunumeric/runtime.cc index 14a0822da8..9d23cc5fe7 100644 --- a/src/cunumeric/runtime.cc +++ b/src/cunumeric/runtime.cc @@ -53,6 +53,12 @@ NDArray CuNumericRuntime::create_array(legate::LogicalStore&& store) return NDArray(std::move(store)); } +NDArray CuNumericRuntime::create_array(const legate::Type& type, int32_t dim) +{ + auto store = legate_runtime_->create_store(type, dim); + return NDArray(std::move(store)); +} + legate::LogicalStore CuNumericRuntime::create_scalar_store(const Scalar& value) { return legate_runtime_->create_store(value); diff --git a/src/cunumeric/runtime.h b/src/cunumeric/runtime.h index 38f631eb4d..ae1803d43f 100644 --- a/src/cunumeric/runtime.h +++ b/src/cunumeric/runtime.h @@ -37,6 +37,7 @@ class CuNumericRuntime { const legate::Type& type, bool optimize_scalar = true); NDArray create_array(legate::LogicalStore&& store); + NDArray create_array(const legate::Type& type, int32_t dim); legate::LogicalStore create_scalar_store(const Scalar& value); public: diff --git a/tests/cpp/integration/test_argwhere.cc b/tests/cpp/integration/test_argwhere.cc new file mode 100644 index 0000000000..e365f5dbf3 --- /dev/null +++ b/tests/cpp/integration/test_argwhere.cc @@ -0,0 +1,298 @@ +/* Copyright 2024 NVIDIA Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +#include +#include +#include + +#include +#include "legate.h" +#include "cunumeric.h" +#include "common_utils.h" + +using namespace cunumeric; + +namespace { +std::vector> get_in_shapes_basic() +{ + std::vector> in_shapes = {{12}, + {4, 3}, + {2, 2, 3}, + {2, 1, 2, 3}, + {2, 1, 2, 1, 3}, + {2, 1, 2, 1, 3, 1}, + {1, 2, 1, 2, 1, 3, 1}}; + return in_shapes; +} + +std::vector> get_exp_shapes_basic() +{ + std::vector> exp_shapes = { + {6, 1}, {6, 2}, {6, 3}, {6, 4}, {6, 5}, {6, 6}, {6, 7}}; + return exp_shapes; +} + +std::vector> get_exp_vectors_basic() +{ + std::vector> exp_vectors = { + {0, 2, 5, 6, 9, 11}, + {0, 0, 0, 2, 1, 2, 2, 0, 3, 0, 3, 2}, + {0, 0, 0, 0, 0, 2, 0, 1, 2, 1, 0, 0, 1, 1, 0, 1, 1, 2}, + {0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 1, 2, 1, 0, 0, 0, 1, 0, 1, 0, 1, 0, 1, 2}, + {0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 1, 0, 2, 1, 0, 0, 0, 0, 1, 0, 1, 0, 0, 1, 0, 1, 0, 2}, + {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 1, 0, 2, 0, + 1, 0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 1, 0, 1, 0, 2, 0}, + {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 1, 0, 2, 0, + 0, 1, 0, 0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0, 1, 0, 1, 0, 2, 0}}; + return exp_vectors; +} + +template +void test_argwhere(std::vector& in_vec, + std::vector& exp_vec, + const std::vector& in_shape, + const std::vector& exp_shape) +{ + auto a = mk_array(in_vec, in_shape); + auto x = argwhere(a); + check_array(x, exp_vec, exp_shape); +} + +template +void test_argwhere_basic(std::vector& in_vec, uint32_t dim) +{ + auto in_shapes = get_in_shapes_basic(); + auto exp_shapes = get_exp_shapes_basic(); + auto exp_vectors = get_exp_vectors_basic(); + + test_argwhere(in_vec, exp_vectors[dim - 1], in_shapes[dim - 1], exp_shapes[dim - 1]); +} + +template +void test_argwhere_basic_for_all_dims(std::vector& in_vec) +{ + test_argwhere_basic(in_vec, 1); + test_argwhere_basic(in_vec, 2); + test_argwhere_basic(in_vec, 3); + +#if LEGATE_MAX_DIM >= 4 + test_argwhere_basic(in_vec, 4); +#endif + +#if LEGATE_MAX_DIM >= 5 + test_argwhere_basic(in_vec, 5); +#endif + +#if LEGATE_MAX_DIM >= 6 + test_argwhere_basic(in_vec, 6); +#endif + +#if LEGATE_MAX_DIM >= 7 + test_argwhere_basic(in_vec, 7); +#endif +} + +void argwhere_int() +{ + std::vector in_vec = {-1, 0, 4, +0, -0, 45, 5, 0, 0, 9, 0, 4}; + test_argwhere_basic_for_all_dims(in_vec); +} + +void argwhere_double() +{ + std::vector in_vec = {0.01, 0, 4.0, -0.00, 0.00, 0.1, -5, +0.0, 0, 9, 0.0, 4}; + test_argwhere_basic_for_all_dims(in_vec); +} + +void argwhere_complex() +{ + std::vector> in_vec = {complex(1.0, 0), + complex(0.0, 0.0), + 54, + 0, + 0.0, + complex(0, 1.0), + 45, + 0, + 0.0, + 9, + -0.00, + 4}; + test_argwhere_basic_for_all_dims>(in_vec); +} + +void argwhere_bool() +{ + std::vector in_vec = { + true, false, true, false, false, true, true, false, false, true, false, true}; + test_argwhere_basic_for_all_dims(in_vec); +} + +void test_argwhere_empty_array(legate::Type leg_type, + std::vector in_shape, + std::vector exp_shape) +{ + auto a = zeros(in_shape, leg_type); + auto x = argwhere(a); + EXPECT_EQ(x.size(), 0); + EXPECT_EQ(x.type(), legate::int64()); + EXPECT_EQ(x.shape(), exp_shape); +} + +template +std::vector init_large_vector(size_t size) +{ + std::vector vec = {}; + for (uint i = 0; i < size; i++) { + T element = (i % 2 == 0) ? 1 : 0; + vec.push_back(element); + } + return vec; +} + +template +std::vector argwhere_result(const std::vector& in_vec, + const std::vector& in_shape) +{ + std::vector a(in_shape.size(), 0); + std::vector result; + for (uint32_t i = 0; i < in_vec.size(); i++) { + if (in_vec[i] != 0) { + for (auto aa : a) { + result.push_back(aa); + } + } + int32_t j = a.size() - 1; + while (j >= 0) { + if (++a[j] >= in_shape[j]) { + a[j] = 0; + j--; + } else { + break; + } + } + } + return result; +} + +std::vector gen_shape(uint32_t dim, size_t in_size) +{ + std::vector shape(dim, 1); + size_t value = 2; + size_t prod = 1; + for (int i = 0; i < dim - 1; i++) { + shape[i] = value; + prod *= value; + value++; + } + shape[dim - 1] = in_size / prod; + return shape; +} + +void argwhere_large_array(uint32_t dim) +{ + size_t in_size = 2 * 3 * 4 * 5 * 6 * 7; + auto in_vec = init_large_vector(in_size); + // for dim = 1, in_shape is {5040} + // for dim = 2, in_shape is {2, 2520} + // for dim = 3, in_shape is {2, 3, 840} + // for dim = 7, in_shape is {2, 3, 4, 5, 6, 7} + auto in_shape = gen_shape(dim, in_size); + + auto a = mk_array(in_vec, in_shape); + auto x = argwhere(a); + auto x_comp = argwhere_result(in_vec, in_shape); + std::vector exp_shape = {x_comp.size() / in_shape.size(), dim}; + check_array(x, x_comp, exp_shape); +} + +TEST(Argwhere, Basic) +{ + argwhere_int(); + argwhere_double(); + argwhere_complex(); + argwhere_bool(); +} + +TEST(Argwhere, LargeArray) +{ + argwhere_large_array(1); + argwhere_large_array(2); + argwhere_large_array(3); + +#if LEGATE_MAX_DIM >= 4 + argwhere_large_array(4); +#endif + +#if LEGATE_MAX_DIM >= 5 + argwhere_large_array(5); +#endif + +#if LEGATE_MAX_DIM >= 6 + argwhere_large_array(6); +#endif + +#if LEGATE_MAX_DIM >= 7 + argwhere_large_array(7); +#endif +} + +TEST(Argwhere, EmptyArray) +{ + std::vector> in_shapes = {{ + 0, + }, + {0, 1}, + {1, 0}, + {1, 0, 0}, + {1, 1, 0}, + {1, 0, 1}}; + + std::vector> exp_shapes = { + // {0, 1}, {0, 2}, {0, 2}, {0, 3}, {0, 3}, {0, 3}};//This is shape of numpy output array. + {0, 0}, + {0, 0}, + {0, 0}, + {0, 0}, + {0, 0}, + {0, 0} // This is shape of cunumeric output array + }; + + assert(in_shapes.size() == exp_shapes.size()); + for (size_t i = 0; i < in_shapes.size(); i++) { + test_argwhere_empty_array(legate::int32(), in_shapes[i], exp_shapes[i]); + } +} + +TEST(Argwhere, Scalar) +{ + std::vector exp_shape1 = {0, 0}; + auto A1 = zeros({}, legate::int32()); + auto B1 = argwhere(A1); + EXPECT_EQ(B1.size(), 0); + EXPECT_EQ(B1.type(), legate::int64()); + EXPECT_EQ(B1.shape(), exp_shape1); + + std::vector exp_shape2 = {1, 0}; + auto A2 = zeros({}, legate::float64()); + A2.fill(legate::Scalar(static_cast(1))); + auto B2 = cunumeric::argwhere(A2); + EXPECT_EQ(B2.size(), 0); + EXPECT_EQ(B2.type(), legate::int64()); + EXPECT_EQ(B2.shape(), exp_shape2); +} + +} // namespace From 3beb63dc5181010a5e4daa8f8c5eea5ca9a844d2 Mon Sep 17 00:00:00 2001 From: Bryan Van de Ven Date: Wed, 27 Mar 2024 13:29:35 -0700 Subject: [PATCH 180/462] don't run legate cpp tests (#154) --- test.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/test.py b/test.py index 8dcda54be8..52bced68ce 100755 --- a/test.py +++ b/test.py @@ -18,10 +18,14 @@ import sys +from legate.tester.args import parser from legate.tester.config import Config from legate.tester.test_plan import TestPlan from legate.tester.test_system import TestSystem +# quick fix for new build changes +parser.set_defaults(gtest_file=None) + if __name__ == "__main__": config = Config(sys.argv) From 5552f835160e02791919f3b43a232c9c6b26a619 Mon Sep 17 00:00:00 2001 From: Manolis Papadakis Date: Fri, 29 Mar 2024 00:10:40 -0700 Subject: [PATCH 181/462] No need to pass THRUST_DEVICE flags, since the core propagates them (#147) --- cmake/versions.json | 2 +- cunumeric_cpp.cmake | 9 --------- 2 files changed, 1 insertion(+), 10 deletions(-) diff --git a/cmake/versions.json b/cmake/versions.json index 48a5aa9507..ae18735bf7 100644 --- a/cmake/versions.json +++ b/cmake/versions.json @@ -7,7 +7,7 @@ "git_url" : "https://github.com/nv-legate/legate.core.internal.git", "git_shallow": false, "always_download": false, - "git_tag" : "1d29d332e1b59357e734dc1cabdbb3b0c6f2a254" + "git_tag" : "0e49b3bafaf3369909779ebfbc203f6971142a9c" } } } diff --git a/cunumeric_cpp.cmake b/cunumeric_cpp.cmake index 41dd58682c..001053b335 100644 --- a/cunumeric_cpp.cmake +++ b/cunumeric_cpp.cmake @@ -425,15 +425,6 @@ if(Legion_USE_CUDA AND CUSOLVERMP_DIR) target_link_libraries(cunumeric PRIVATE ${CUSOLVERMP_DIR}/lib/libcusolverMp.so) endif() -# Change THRUST_DEVICE_SYSTEM for `.cpp` files -if(Legion_USE_OpenMP) - list(APPEND cunumeric_CXX_OPTIONS -UTHRUST_DEVICE_SYSTEM) - list(APPEND cunumeric_CXX_OPTIONS -DTHRUST_DEVICE_SYSTEM=THRUST_DEVICE_SYSTEM_OMP) -elseif(NOT Legion_USE_CUDA) - list(APPEND cunumeric_CXX_OPTIONS -UTHRUST_DEVICE_SYSTEM) - list(APPEND cunumeric_CXX_OPTIONS -DTHRUST_DEVICE_SYSTEM=THRUST_DEVICE_SYSTEM_CPP) -endif() - target_compile_options(cunumeric PRIVATE "$<$:${cunumeric_CXX_OPTIONS}>" "$<$:${cunumeric_CUDA_OPTIONS}>") From 91a8a8333bf6d1a76b1fad76e7ecb63a89a5e133 Mon Sep 17 00:00:00 2001 From: Shriram Jagannathan <83928561+shriram-jagan@users.noreply.github.com> Date: Mon, 8 Apr 2024 14:17:58 -0700 Subject: [PATCH 182/462] Compute pitch correctly (#161) Take edge cases into account when computing the volume/pitch/smem values in convolution --- src/cunumeric/convolution/convolve.cu | 2 +- .../convolution/convolve_template.inl | 18 +++++++++--------- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/src/cunumeric/convolution/convolve.cu b/src/cunumeric/convolution/convolve.cu index 7d185d6d86..34624cd340 100644 --- a/src/cunumeric/convolution/convolve.cu +++ b/src/cunumeric/convolution/convolve.cu @@ -815,7 +815,7 @@ __host__ void direct_convolution(AccessorWO out, } unsigned smem_size = sizeof(VAL); for (int d = 0; d < DIM; d++) { - smem_size *= (tile[d] + 2 * centers[d]); + smem_size *= std::max(static_cast(1), (tile[d] + 2 * centers[d])); } if (smem_size <= max_smem_size) { // Small tile case: diff --git a/src/cunumeric/convolution/convolve_template.inl b/src/cunumeric/convolution/convolve_template.inl index f2874bb97f..e2723eeeda 100644 --- a/src/cunumeric/convolution/convolve_template.inl +++ b/src/cunumeric/convolution/convolve_template.inl @@ -253,7 +253,7 @@ static unsigned roundup_tile(Point& tile, if (bounds[d] < tile[d]) { tile[d] = bounds[d]; } - result *= (tile[d] + padding[d]); + result *= std::max(static_cast(1), tile[d] + padding[d]); } // Find the two smallest dimensions and increase one of them // until we hit the second smallest one or exceed max_smem_size @@ -294,7 +294,7 @@ static unsigned roundup_tile(Point& tile, unsigned pitch = sizeof(VAL); for (int d = 0; d < DIM; d++) { if (d != d1) { - pitch *= (tile[d] + padding[d]); + pitch *= std::max(static_cast(1), tile[d] + padding[d]); } } // Make sure the last dimension is as large as it can go too @@ -307,7 +307,7 @@ static unsigned roundup_tile(Point& tile, tile[d1] = bounds[d1]; } } - return pitch * (tile[d1] + padding[d1]); + return pitch * std::max(static_cast(1), tile[d1] + padding[d1]); } // If we ever get two dimensions of the same size then see what dimension // has the next largest value. If we can't find one that is larger then @@ -335,7 +335,7 @@ static unsigned roundup_tile(Point& tile, unsigned pitch = sizeof(VAL); for (int d = 0; d < DIM; d++) { if (d != d1) { - pitch *= (tile[d] + padding[d]); + pitch *= std::max(static_cast(1), tile[d] + padding[d]); } } unsigned elements = max_size / pitch; @@ -346,15 +346,15 @@ static unsigned roundup_tile(Point& tile, int bound = elements - padding[d1]; if (bounds[d1] < bound) { tile[d1] = bounds[d1]; - result = pitch * (tile[d1] + padding[d1]); + result = pitch * std::max(static_cast(1), tile[d1] + padding[d1]); } else if (bound < t2) { tile[d1] = bound; - result = pitch * (bound + padding[d1]); + result = pitch * std::max(static_cast(1), bound + padding[d1]); all_same = false; break; } else { tile[d1] = t2; - result = pitch * (t2 + padding[d1]); + result = pitch * std::max(static_cast(1), t2 + padding[d1]); } } if (all_same) { @@ -368,9 +368,9 @@ static unsigned roundup_tile(Point& tile, unsigned next_size = sizeof(VAL); for (int d = 0; d < DIM; d++) { if (skipdims & (1 << d)) { - next_size *= (tile[d] + padding[d]); + next_size *= std::max(static_cast(1), tile[d] + padding[d]); } else if (tile[d] == bounds[d]) { - next_size *= (tile[d] + padding[d]); + next_size *= std::max(static_cast(1), tile[d] + padding[d]); skipdims |= (1 << d); } else { next_size *= (tile[d] + 1 + padding[d]); From 7ca7ca6f206bf6a41bab640b5c035fba6b09269f Mon Sep 17 00:00:00 2001 From: Marcin Zalewski Date: Mon, 8 Apr 2024 16:07:14 -0700 Subject: [PATCH 183/462] Run unit tests in a separate job (#162) Unit tests have been run using test.py --unit. This adds a separate job to run unit tests using pytest tests/unit. --- .github/workflows/gh-build-and-test.yml | 5 +++-- continuous_integration/scripts/test | 10 ++++++++++ 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/.github/workflows/gh-build-and-test.yml b/.github/workflows/gh-build-and-test.yml index a235a8ee28..0a2aca4d9c 100644 --- a/.github/workflows/gh-build-and-test.yml +++ b/.github/workflows/gh-build-and-test.yml @@ -63,8 +63,8 @@ jobs: 'linux-amd64-cpu32:cpu:cpu:linux' 'macos-latest:cpu:cpu:mac') TEST_CONFIGS=( - '1 CPU test:test --cpus 1 --unit --debug:cpu' - '1 CPU test:test --cpus 1 --unit --debug:gpu' + '1 CPU test:test --cpus 1 --debug:cpu' + '1 CPU test:test --cpus 1 --debug:gpu' '2 CPU test:test --cpus 2 --debug:cpu' '2 CPU test:test --cpus 2 --debug:gpu' 'GPU test:test --use cuda --gpus 1 --debug:gpu' @@ -77,6 +77,7 @@ jobs: 'Eager execution test:test --use eager --debug:cpu' 'mypy:mypy:cpu' 'Documentation:docs:cpu' + 'Unit tests:unit:cpu' ) for RUNNER in "${RUNNERS[@]}"; do IFS=':' read -ra RUNNER_INFO <<< "$RUNNER" diff --git a/continuous_integration/scripts/test b/continuous_integration/scripts/test index a23b758d67..f6bb0827b5 100755 --- a/continuous_integration/scripts/test +++ b/continuous_integration/scripts/test @@ -29,6 +29,10 @@ setup_mypy_env() { mamba install -y "mypy>=0.961" jinja2 nbsphinx sphinx-copybutton "sphinx>=4.4.0" types-docutils } +setup_unit_env() { + mamba install -y pytest pytest-mock mock +} + test_cunumeric() { set -xeo pipefail @@ -62,6 +66,12 @@ test_cunumeric() { cd docs/cunumeric make clean html ;; + "unit") + echo "Running Unit tests..." + shift; + setup_unit_env; + pytest tests/unit + ;; *) echo "Invalid command: $1" return 1 From 93699ae831d6ea4ee51258b8a0b8b847ecb68ed4 Mon Sep 17 00:00:00 2001 From: Bryan Van de Ven Date: Tue, 9 Apr 2024 11:21:21 -0700 Subject: [PATCH 184/462] Allow where/out to be able to be passed positionally to add_boilerplate (#141) * proper test directory structure * add unit test utils * allow out and where args to add_boilerplate * use correct license header * Update cunumeric/_array/util.py Co-authored-by: Manolis Papadakis * add missing init --------- Co-authored-by: Manolis Papadakis --- cunumeric/_array/util.py | 74 +++--- tests/unit/__init__.py | 16 ++ tests/unit/cunumeric/__init__.py | 16 ++ tests/unit/cunumeric/_array/__init__.py | 16 ++ tests/unit/cunumeric/_array/test_util.py | 236 ++++++++++++++++++ tests/unit/cunumeric/_sphinxext/__init__.py | 17 ++ tests/unit/cunumeric/_utils/__init__.py | 16 ++ .../test_array.py} | 0 .../test_coverage.py} | 0 .../test_linalg.py} | 0 .../test_stack.py} | 2 +- tests/unit/cunumeric/random/__init__.py | 16 ++ tests/unit/util.py | 34 +++ 13 files changed, 406 insertions(+), 37 deletions(-) create mode 100644 tests/unit/__init__.py create mode 100644 tests/unit/cunumeric/__init__.py create mode 100644 tests/unit/cunumeric/_array/__init__.py create mode 100644 tests/unit/cunumeric/_array/test_util.py create mode 100644 tests/unit/cunumeric/_sphinxext/__init__.py create mode 100644 tests/unit/cunumeric/_utils/__init__.py rename tests/unit/cunumeric/{test_utils_array.py => _utils/test_array.py} (100%) rename tests/unit/cunumeric/{test_utils_coverage.py => _utils/test_coverage.py} (100%) rename tests/unit/cunumeric/{test_utils_linalg.py => _utils/test_linalg.py} (100%) rename tests/unit/cunumeric/{test_utils_stack.py => _utils/test_stack.py} (97%) create mode 100644 tests/unit/cunumeric/random/__init__.py create mode 100644 tests/unit/util.py diff --git a/cunumeric/_array/util.py b/cunumeric/_array/util.py index 727a77e8bb..eeb2c56695 100644 --- a/cunumeric/_array/util.py +++ b/cunumeric/_array/util.py @@ -28,7 +28,6 @@ ) import numpy as np -from legate.core.utils import OrderedSet from ..runtime import runtime from ..types import NdShape @@ -51,57 +50,60 @@ def add_boilerplate( Adds required boilerplate to the wrapped cunumeric.ndarray or module-level function. - Every time the wrapped function is called, this wrapper will: - * Convert all specified array-like parameters, plus the special "out" - parameter (if present), to cuNumeric ndarrays. - * Convert the special "where" parameter (if present) to a valid predicate. + Every time the wrapped function is called, this wrapper will convert all + specified array-like parameters to cuNumeric ndarrays. Additionally, any + "out" or "where" arguments will also always be automatically converted. """ - keys = OrderedSet(array_params) - assert len(keys) == len(array_params) + to_convert = set(array_params) + assert len(to_convert) == len(array_params) def decorator(func: Callable[P, R]) -> Callable[P, R]: assert not hasattr( func, "__wrapped__" ), "this decorator must be the innermost" - # For each parameter specified by name, also consider the case where - # it's passed as a positional parameter. - indices: OrderedSet[int] = OrderedSet() - where_idx: int | None = None - out_idx: int | None = None params = signature(func).parameters - extra = keys - OrderedSet(params) + extra = to_convert - set(params) assert len(extra) == 0, f"unknown parameter(s): {extra}" + + # we also always want to convert "out" and "where" + # even if they are not explicitly specified by the user + to_convert.update(("out", "where")) + + out_idx = -1 + indices = set() for idx, param in enumerate(params): - if param == "where": - where_idx = idx - elif param == "out": + if param == "out": out_idx = idx - elif param in keys: + if param in to_convert: indices.add(idx) @wraps(func) def wrapper(*args: Any, **kwargs: Any) -> R: - assert (where_idx is None or len(args) <= where_idx) and ( - out_idx is None or len(args) <= out_idx - ), "'where' and 'out' should be passed as keyword arguments" - - # Convert relevant arguments to cuNumeric ndarrays - args = tuple( - convert_to_cunumeric_ndarray(arg) - if idx in indices and arg is not None - else arg - for (idx, arg) in enumerate(args) - ) + # convert specified non-None positional arguments, making sure + # that any out-parameters are appropriately writeable + converted_args = [] + for idx, arg in enumerate(args): + if idx in indices and arg is not None: + if idx == out_idx: + arg = convert_to_cunumeric_ndarray(arg, share=True) + if not arg.flags.writeable: + raise ValueError("out is not writeable") + else: + arg = convert_to_cunumeric_ndarray(arg) + converted_args.append(arg) + args = tuple(converted_args) + + # convert specified non-None keyword arguments, making sure + # that any out-parameters are appropriately writeable for k, v in kwargs.items(): - if v is None: - continue - elif k == "out": - kwargs[k] = convert_to_cunumeric_ndarray(v, share=True) - if not kwargs[k].flags.writeable: - raise ValueError("out is not writeable") - elif (k in keys) or (k == "where"): - kwargs[k] = convert_to_cunumeric_ndarray(v) + if k in to_convert and v is not None: + if k == "out": + kwargs[k] = convert_to_cunumeric_ndarray(v, share=True) + if not kwargs[k].flags.writeable: + raise ValueError("out is not writeable") + else: + kwargs[k] = convert_to_cunumeric_ndarray(v) return func(*args, **kwargs) diff --git a/tests/unit/__init__.py b/tests/unit/__init__.py new file mode 100644 index 0000000000..08e567f988 --- /dev/null +++ b/tests/unit/__init__.py @@ -0,0 +1,16 @@ +# Copyright 2024 NVIDIA Corporation +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +from __future__ import annotations diff --git a/tests/unit/cunumeric/__init__.py b/tests/unit/cunumeric/__init__.py new file mode 100644 index 0000000000..08e567f988 --- /dev/null +++ b/tests/unit/cunumeric/__init__.py @@ -0,0 +1,16 @@ +# Copyright 2024 NVIDIA Corporation +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +from __future__ import annotations diff --git a/tests/unit/cunumeric/_array/__init__.py b/tests/unit/cunumeric/_array/__init__.py new file mode 100644 index 0000000000..08e567f988 --- /dev/null +++ b/tests/unit/cunumeric/_array/__init__.py @@ -0,0 +1,16 @@ +# Copyright 2024 NVIDIA Corporation +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +from __future__ import annotations diff --git a/tests/unit/cunumeric/_array/test_util.py b/tests/unit/cunumeric/_array/test_util.py new file mode 100644 index 0000000000..1f5c26e79d --- /dev/null +++ b/tests/unit/cunumeric/_array/test_util.py @@ -0,0 +1,236 @@ +# Copyright 2024 NVIDIA Corporation +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +import pytest +from mock import MagicMock +from pytest_mock import MockerFixture + +import cunumeric._array.util as m # module under test + +from ...util import powerset + + +@m.add_boilerplate() +def _out_implicit(a, b, out): + pass + + +@m.add_boilerplate("out") +def _out_explicit(a, b, out): + pass + + +@m.add_boilerplate() +def _where_implicit(a, b, where): + pass + + +@m.add_boilerplate("where") +def _where_explicit(a, b, where): + pass + + +@pytest.fixture(autouse=True) +def mock_convert(mocker: MockerFixture) -> MagicMock: + return mocker.patch("cunumeric._array.util.convert_to_cunumeric_ndarray") + + +class Test_add_boilerplate_bad: + def test_bad_repeat(self) -> None: + with pytest.raises(AssertionError): + + @m.add_boilerplate("a", "a") + def _bad_repeat(a, b): + pass + + def test_bad_extra(self) -> None: + with pytest.raises(AssertionError): + + @m.add_boilerplate("c") + def _bad_repeat(a, b): + pass + + +class Test_add_boilerplate_args: + @pytest.mark.parametrize("args", powerset("abc")) + def test_args_positional_None(self, args, mock_convert: MagicMock) -> None: + @m.add_boilerplate(*args) + def func(a, b, c): + pass + + func(None, None, None) + + assert not mock_convert.called + + @pytest.mark.parametrize("args", powerset("abc")) + def test_args_positional_value( + self, args, mock_convert: MagicMock + ) -> None: + @m.add_boilerplate(*args) + def func(a, b, c): + pass + + vals = (1, 2, 3) + + func(*vals) + + assert mock_convert.call_count == len(args) + expected = ( + val for (arg, val) in zip(tuple("abc"), vals) if arg in args + ) + for item in expected: + mock_convert.assert_any_call(item) + + @pytest.mark.parametrize("args", powerset("abc")) + def test_args_kwargs_None(self, args, mock_convert: MagicMock) -> None: + @m.add_boilerplate(*args) + def func(a, b, c): + pass + + func(a=None, b=None, c=None) + + assert not mock_convert.called + + @pytest.mark.parametrize("args", powerset("abc")) + def test_args_kwargs_value(self, args, mock_convert: MagicMock) -> None: + @m.add_boilerplate(*args) + def func(a, b, c): + pass + + vals = (1, 2, 3) + + func(**dict(zip(tuple("abc"), vals))) + + assert mock_convert.call_count == len(args) + expected = ( + val for (arg, val) in zip(tuple("abc"), vals) if arg in args + ) + for item in expected: + mock_convert.assert_any_call(item) + + +class Test_add_boilerplate_out: + def test_implicit_positional_None(self, mock_convert: MagicMock) -> None: + _out_implicit(None, None, None) + assert not mock_convert.called + + @pytest.mark.parametrize("args", powerset("ab")) + def test_implicit_positional_value( + self, args, mock_convert: MagicMock + ) -> None: + _out_implicit(None, None, 10) + mock_convert.assert_called_once_with(10, share=True) + + def test_implicit_kwargs_None(self, mock_convert: MagicMock) -> None: + _out_implicit(None, None, out=None) + assert not mock_convert.called + + @pytest.mark.parametrize("args", powerset("ab")) + def test_implicit_kwargs_value( + self, args, mock_convert: MagicMock + ) -> None: + _out_implicit(None, None, out=10) + mock_convert.assert_called_once_with(10, share=True) + + def test_explicit_positional_None(self, mock_convert: MagicMock) -> None: + _out_explicit(None, None, None) + assert not mock_convert.called + + @pytest.mark.parametrize("args", powerset("ab")) + def test_explicit_positional_value( + self, args, mock_convert: MagicMock + ) -> None: + _out_explicit(None, None, 10) + mock_convert.assert_called_once_with(10, share=True) + + def test_explicit_kwargs_None(self, mock_convert: MagicMock) -> None: + _out_explicit(None, None, out=None) + assert not mock_convert.called + + @pytest.mark.parametrize("args", powerset("ab")) + def test_explicit_kwargs_value( + self, args, mock_convert: MagicMock + ) -> None: + _out_explicit(None, None, out=10) + mock_convert.assert_called_once_with(10, share=True) + + +class Test_add_boilerplate_where: + def test_implicit_positional_None(self, mock_convert: MagicMock) -> None: + _where_implicit(None, None, None) + assert not mock_convert.called + + @pytest.mark.parametrize("args", powerset("ab")) + def test_implicit_positional_value( + self, args, mock_convert: MagicMock + ) -> None: + _where_implicit(None, None, 10) + mock_convert.assert_called_once_with(10) + + def test_implicit_kwargs_None(self, mock_convert: MagicMock) -> None: + _where_implicit(None, None, where=None) + assert not mock_convert.called + + @pytest.mark.parametrize("args", powerset("ab")) + def test_implicit_kwargs_value( + self, args, mock_convert: MagicMock + ) -> None: + _where_implicit(None, None, where=10) + mock_convert.assert_called_once_with(10) + + def test_explicit_positional_None(self, mock_convert: MagicMock) -> None: + _where_explicit(None, None, None) + assert not mock_convert.called + + @pytest.mark.parametrize("args", powerset("ab")) + def test_explicit_positional_value( + self, args, mock_convert: MagicMock + ) -> None: + _where_explicit(None, None, 10) + mock_convert.assert_called_once_with(10) + + def test_explicit_kwargs_None(self, mock_convert: MagicMock) -> None: + _where_explicit(None, None, where=None) + assert not mock_convert.called + + @pytest.mark.parametrize("args", powerset("ab")) + def test_explicit_kwargs_value( + self, args, mock_convert: MagicMock + ) -> None: + _where_explicit(None, None, where=10) + mock_convert.assert_called_once_with(10) + + +def test_add_boilerplate_mixed(mock_convert: MagicMock) -> None: + @m.add_boilerplate( + "a", + "b", + "c", + ) + def func(a, b=2, c=None, d=None, e=5, out=None, where=None): + pass + + func(1, c=3, out=4, where=None) + + assert mock_convert.call_count == 3 + mock_convert.assert_any_call(1) + mock_convert.assert_any_call(3) + mock_convert.assert_any_call(4, share=True) + + +if __name__ == "__main__": + import sys + + sys.exit(pytest.main(sys.argv)) diff --git a/tests/unit/cunumeric/_sphinxext/__init__.py b/tests/unit/cunumeric/_sphinxext/__init__.py new file mode 100644 index 0000000000..350b6bdfe4 --- /dev/null +++ b/tests/unit/cunumeric/_sphinxext/__init__.py @@ -0,0 +1,17 @@ +# Copyright 2024 NVIDIA Corporation +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# + +from __future__ import annotations diff --git a/tests/unit/cunumeric/_utils/__init__.py b/tests/unit/cunumeric/_utils/__init__.py new file mode 100644 index 0000000000..08e567f988 --- /dev/null +++ b/tests/unit/cunumeric/_utils/__init__.py @@ -0,0 +1,16 @@ +# Copyright 2024 NVIDIA Corporation +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +from __future__ import annotations diff --git a/tests/unit/cunumeric/test_utils_array.py b/tests/unit/cunumeric/_utils/test_array.py similarity index 100% rename from tests/unit/cunumeric/test_utils_array.py rename to tests/unit/cunumeric/_utils/test_array.py diff --git a/tests/unit/cunumeric/test_utils_coverage.py b/tests/unit/cunumeric/_utils/test_coverage.py similarity index 100% rename from tests/unit/cunumeric/test_utils_coverage.py rename to tests/unit/cunumeric/_utils/test_coverage.py diff --git a/tests/unit/cunumeric/test_utils_linalg.py b/tests/unit/cunumeric/_utils/test_linalg.py similarity index 100% rename from tests/unit/cunumeric/test_utils_linalg.py rename to tests/unit/cunumeric/_utils/test_linalg.py diff --git a/tests/unit/cunumeric/test_utils_stack.py b/tests/unit/cunumeric/_utils/test_stack.py similarity index 97% rename from tests/unit/cunumeric/test_utils_stack.py rename to tests/unit/cunumeric/_utils/test_stack.py index 8157373880..ca4a2ed8b3 100644 --- a/tests/unit/cunumeric/test_utils_stack.py +++ b/tests/unit/cunumeric/_utils/test_stack.py @@ -33,7 +33,7 @@ def test_get_line_number_from_frame() -> None: filename, lineno = result.split(":") # NOTE: this will break if this test filename is changed - assert filename.endswith("test_utils_stack.py") + assert filename.endswith("test_stack.py") # it would be too fragile to compare more specific than this assert int(lineno) > 0 diff --git a/tests/unit/cunumeric/random/__init__.py b/tests/unit/cunumeric/random/__init__.py new file mode 100644 index 0000000000..08e567f988 --- /dev/null +++ b/tests/unit/cunumeric/random/__init__.py @@ -0,0 +1,16 @@ +# Copyright 2024 NVIDIA Corporation +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +from __future__ import annotations diff --git a/tests/unit/util.py b/tests/unit/util.py new file mode 100644 index 0000000000..a6bb0a49e2 --- /dev/null +++ b/tests/unit/util.py @@ -0,0 +1,34 @@ +# Copyright 2024 NVIDIA Corporation +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +from __future__ import annotations + +from itertools import chain, combinations +from typing import Any, Iterable, Iterator + +import pytest +from typing_extensions import TypeAlias + +Capsys: TypeAlias = pytest.CaptureFixture[str] + + +# ref: https://docs.python.org/3/library/itertools.html +def powerset(iterable: Iterable[Any]) -> Iterator[Any]: + s = list(iterable) + return chain.from_iterable(combinations(s, r) for r in range(len(s) + 1)) + + +def powerset_nonempty(iterable: Iterable[Any]) -> Iterator[Any]: + return (x for x in powerset(iterable) if len(x)) From db317de12284a471ecd18694c6d0390e099e16b3 Mon Sep 17 00:00:00 2001 From: Wonchan Lee Date: Tue, 9 Apr 2024 12:49:51 -0700 Subject: [PATCH 185/462] Cache for CUDA device properties (#160) --- src/cunumeric/convolution/convolve.cu | 12 +++------ src/cunumeric/cuda_help.h | 2 ++ src/cunumeric/cudalibs.cu | 35 +++++++++++++++++++++++++++ src/cunumeric/cudalibs.h | 4 +++ 4 files changed, 45 insertions(+), 8 deletions(-) diff --git a/src/cunumeric/convolution/convolve.cu b/src/cunumeric/convolution/convolve.cu index 34624cd340..0e3d404dcc 100644 --- a/src/cunumeric/convolution/convolve.cu +++ b/src/cunumeric/convolution/convolve.cu @@ -757,10 +757,8 @@ __host__ void direct_convolution(AccessorWO out, { constexpr int THREADVALS = THREAD_OUTPUTS(VAL); // Get the maximum amount of shared memory per threadblock - int device; - CHECK_CUDA(cudaGetDevice(&device)); - cudaDeviceProp properties; - CHECK_CUDA(cudaGetDeviceProperties(&properties, device)); + int device = get_device_ordinal(); + auto& properties = get_device_properties(); size_t max_smem_size = properties.sharedMemPerBlockOptin; // Only need to do these calls the first time on each device so @@ -1303,10 +1301,8 @@ __host__ static inline void cufft_convolution(AccessorWO out, const Rect& subrect, const Rect& filter_rect) { - int device; - CHECK_CUDA(cudaGetDevice(&device)); - cudaDeviceProp properties; - CHECK_CUDA(cudaGetDeviceProperties(&properties, device)); + int device = get_device_ordinal(); + auto& properties = get_device_properties(); size_t max_smem_size = properties.sharedMemPerBlockOptin; // Only need to do these calls the first time on each device so diff --git a/src/cunumeric/cuda_help.h b/src/cunumeric/cuda_help.h index b92201c1bc..8f2d5be0fe 100644 --- a/src/cunumeric/cuda_help.h +++ b/src/cunumeric/cuda_help.h @@ -170,6 +170,8 @@ struct cufftPlanParams { // Return a cached stream for the current GPU legate::cuda::StreamView get_cached_stream(); +int get_device_ordinal(); +const cudaDeviceProp& get_device_properties(); cublasHandle_t get_cublas(); cusolverDnHandle_t get_cusolver(); #if LegateDefined(CUNUMERIC_USE_CUSOLVERMP) diff --git a/src/cunumeric/cudalibs.cu b/src/cunumeric/cudalibs.cu index 4a16ae7f22..8d1ff9a50e 100644 --- a/src/cunumeric/cudalibs.cu +++ b/src/cunumeric/cudalibs.cu @@ -332,6 +332,27 @@ void CUDALibraries::finalize_cutensor() cutensor_ = nullptr; } +int CUDALibraries::get_device_ordinal() +{ + if (ordinal_.has_value()) { + return *ordinal_; + } + int ordinal{-1}; + CHECK_CUDA(cudaGetDevice(&ordinal)); + ordinal_ = ordinal; + return ordinal; +} + +const cudaDeviceProp& CUDALibraries::get_device_properties() +{ + if (device_prop_) { + return *device_prop_; + } + device_prop_ = std::make_unique(); + CHECK_CUDA(cudaGetDeviceProperties(device_prop_.get(), get_device_ordinal())); + return *device_prop_; +} + cublasHandle_t CUDALibraries::get_cublas() { if (nullptr == cublas_) { @@ -444,6 +465,20 @@ cufftContext get_cufft_plan(cufftType type, const cufftPlanParams& params) return lib.get_cufft_plan(type, params); } +const cudaDeviceProp& get_device_properties() +{ + const auto proc = legate::Processor::get_executing_processor(); + auto& lib = get_cuda_libraries(proc); + return lib.get_device_properties(); +} + +int get_device_ordinal() +{ + const auto proc = legate::Processor::get_executing_processor(); + auto& lib = get_cuda_libraries(proc); + return lib.get_device_ordinal(); +} + class LoadCUDALibsTask : public CuNumericTask { public: static const int TASK_ID = CUNUMERIC_LOAD_CUDALIBS; diff --git a/src/cunumeric/cudalibs.h b/src/cunumeric/cudalibs.h index 269bc09f6c..c3e7b4facc 100644 --- a/src/cunumeric/cudalibs.h +++ b/src/cunumeric/cudalibs.h @@ -34,6 +34,8 @@ struct CUDALibraries { public: void finalize(); + int get_device_ordinal(); + const cudaDeviceProp& get_device_properties(); cublasHandle_t get_cublas(); cusolverDnHandle_t get_cusolver(); #if LegateDefined(CUNUMERIC_USE_CUSOLVERMP) @@ -52,6 +54,8 @@ struct CUDALibraries { private: bool finalized_; + std::optional ordinal_{}; + std::unique_ptr device_prop_{}; cublasContext* cublas_; cusolverDnContext* cusolver_; #if LegateDefined(CUNUMERIC_USE_CUSOLVERMP) From 358cfb00e7b199debe42c3da027c3d10b246af20 Mon Sep 17 00:00:00 2001 From: Mona Han <129724851+mona-nv@users.noreply.github.com> Date: Fri, 12 Apr 2024 10:08:47 +0800 Subject: [PATCH 186/462] =?UTF-8?q?Fix=20the=20build=20failure=20caused=20?= =?UTF-8?q?by=20CCCL=20import.=20The=20complex=20class=20does=20n=E2=80=A6?= =?UTF-8?q?=20(#163)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Fix the build failure caused by CCCL import. The complex class does not have stream operator functions, therefore, will not output complex data for now. * Update code based on review comments * Correct cuda version --- tests/cpp/integration/util.inl | 38 +++++++++++++++++++++++++++++++--- 1 file changed, 35 insertions(+), 3 deletions(-) diff --git a/tests/cpp/integration/util.inl b/tests/cpp/integration/util.inl index 6d08f36e8e..5088996178 100644 --- a/tests/cpp/integration/util.inl +++ b/tests/cpp/integration/util.inl @@ -15,6 +15,35 @@ */ namespace { +template +std::stringstream& print_value(std::stringstream& ss, T value) +{ + ss << value; + return ss; +} + +template <> +std::stringstream& print_value>(std::stringstream& ss, complex value) +{ + // operator<< missing for cuda::std::complex + // The issue is going to be fixed in the next cuda release. +#if CUDART_VERSION >= 12050 + ss << value; +#endif + return ss; +} + +template <> +std::stringstream& print_value>(std::stringstream& ss, complex value) +{ + // operator<< missing for cuda::std::complex + // The issue is going to be fixed in the next cuda release. +#if CUDART_VERSION >= 12050 + ss << value; +#endif + return ss; +} + template std::string to_string(legate::AccessorRO acc, const std::vector& shape, @@ -66,7 +95,8 @@ std::string to_string(legate::AccessorRO acc, ss << ","; } } - ss << std::setw(9) << std::setprecision(3) << acc[*itr]; + ss << std::setw(9) << std::setprecision(3); + print_value(ss, acc[*itr]); count += 1; } print_brackets_in_start_end(false); @@ -88,8 +118,10 @@ std::string check_array_eq(legate::AccessorRO acc, for (legate::PointInRectIterator itr(rect, false); itr.valid(); ++itr) { auto q = *itr; ss << std::left << std::setprecision(3); - ss << std::setw(13) << "Array value: " << std::setw(10) << acc[q] << ", "; - ss << std::setw(16) << "Expected value: " << std::setw(10) << values_ptr[index] << ", "; + ss << std::setw(13) << "Array value: " << std::setw(10); + print_value(ss, acc[q]) << ", "; + ss << std::setw(16) << "Expected value: " << std::setw(10); + print_value(ss, acc[q]) << ", "; if (size > 0) { ss << std::setw(8) << "index: ["; for (uint32_t i = 0; i < size - 1; ++i) { From 30fd2c0f77d1c6b2547b8e91c0bd899570abd02a Mon Sep 17 00:00:00 2001 From: Bryan Van de Ven Date: Fri, 12 Apr 2024 13:34:14 -0700 Subject: [PATCH 187/462] Recent ports from public repo (#146) * add np.digitize (ref: https://github.com/nv-legate/cunumeric/pull/1117) * add np.diff (ref: https://github.com/nv-legate/cunumeric/pull/636) * add np.load (ref: https://github.com/nv-legate/cunumeric/pull/1126) * add cov (ref: https://github.com/nv-legate/cunumeric/pull/1129) * add average (ref: https://github.com/nv-legate/cunumeric/pull/1124) * add logical reductions (ref: https://github.com/nv-legate/cunumeric/pull/1123) * fix docstring syntax * review comments * fix import * fix error from update * fix copy pasta --- cunumeric/_array/array.py | 10 +- cunumeric/_module/__init__.py | 16 +- cunumeric/_module/io_numpy.py | 75 ++++++++ cunumeric/_module/math_sum_prod_diff.py | 145 ++++++++++++++- cunumeric/_module/stats_avgs_vars.py | 138 ++++++++++++++- cunumeric/_module/stats_correlating.py | 187 ++++++++++++++++++++ cunumeric/_module/stats_histograms.py | 112 ++++++++++++ cunumeric/_thunk/deferred.py | 37 +--- cunumeric/_ufunc/comparison.py | 2 + cunumeric/_ufunc/ufunc.py | 11 ++ cunumeric/_utils/array.py | 30 ++++ docs/cunumeric/source/api/io.rst | 11 ++ docs/cunumeric/source/api/math.rst | 1 + docs/cunumeric/source/api/routines.rst | 1 + docs/cunumeric/source/api/statistics.rst | 28 +-- tests/integration/test_average.py | 103 +++++++++++ tests/integration/test_diff.py | 66 +++++++ tests/integration/test_digitize.py | 166 +++++++++++++++++ tests/integration/test_fallback.py | 11 +- tests/integration/test_logical_reduction.py | 40 +++++ tests/integration/test_stats.py | 96 ++++++++++ 21 files changed, 1239 insertions(+), 47 deletions(-) create mode 100644 cunumeric/_module/io_numpy.py create mode 100644 cunumeric/_module/stats_correlating.py create mode 100644 docs/cunumeric/source/api/io.rst create mode 100644 tests/integration/test_average.py create mode 100644 tests/integration/test_diff.py create mode 100644 tests/integration/test_digitize.py create mode 100644 tests/integration/test_logical_reduction.py diff --git a/cunumeric/_array/array.py b/cunumeric/_array/array.py index b138e75e97..986eabe410 100644 --- a/cunumeric/_array/array.py +++ b/cunumeric/_array/array.py @@ -30,7 +30,12 @@ ) from .. import _ufunc -from .._utils.array import calculate_volume, to_core_type +from .._utils.array import ( + calculate_volume, + max_identity, + min_identity, + to_core_type, +) from .._utils.coverage import FALLBACK_WARNING, clone_class, is_implemented from .._utils.linalg import dot_modes from .._utils.structure import deep_apply @@ -1953,6 +1958,9 @@ def clip( Multiple GPUs, Multiple CPUs """ + min = max_identity(self.dtype) if min is None else min + max = min_identity(self.dtype) if max is None else max + args = ( np.array(min, dtype=self.dtype), np.array(max, dtype=self.dtype), diff --git a/cunumeric/_module/__init__.py b/cunumeric/_module/__init__.py index f20777b236..4b4529e06f 100644 --- a/cunumeric/_module/__init__.py +++ b/cunumeric/_module/__init__.py @@ -59,6 +59,20 @@ from .indexing import * +# --- Input and output +# https://numpy.org/doc/stable/reference/routines.io.html +# +# from .io_text import * # Text files +# from .io_raw import * # Raw binary files +# from .io_string import * # String formatting +# from .io_memory import * # Memory mapping files +# from .io_text import * # Text formatting options +# from .io_base import * # Base-n representations +# from .io_data import * # Data sources +# from .io_binary import * # Binary format description + +from .io_numpy import * # NumPy binary files (NPY, NPZ) + # --- Linear Algebra # https://numpy.org/doc/stable/reference/routines.linalg.html @@ -107,10 +121,10 @@ # --- Statistics # https://numpy.org/doc/stable/reference/routines.statistics.html # -# from .correlating import * # Correlating from .stats_order import * # Order statistics from .stats_avgs_vars import * # Averages and variances +from .stats_correlating import * # Correlating from .stats_histograms import * # Histograms # --- Window functions diff --git a/cunumeric/_module/io_numpy.py b/cunumeric/_module/io_numpy.py new file mode 100644 index 0000000000..67ea13c051 --- /dev/null +++ b/cunumeric/_module/io_numpy.py @@ -0,0 +1,75 @@ +# Copyright 2024 NVIDIA Corporation +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +from __future__ import annotations + +from typing import TYPE_CHECKING, Any + +if TYPE_CHECKING: + from .._array.array import ndarray + from os import PathLike + from typing import BinaryIO + +import numpy as np + +from .creation_data import array + + +def load( + file: str | bytes | PathLike[Any] | BinaryIO, + *, + max_header_size: int = 10000, +) -> ndarray: + """ + Load an array from a ``.npy`` file. + + Parameters + ---------- + file : file-like object, string, or pathlib.Path + The file to read. File-like objects must support the + ``seek()`` and ``read()`` methods and must always + be opened in binary mode. + max_header_size : int, optional + Maximum allowed size of the header. Large headers may not be safe + to load securely and thus require explicitly passing a larger value. + See :py:func:`ast.literal_eval()` for details. + + Returns + ------- + result : array + Data stored in the file. + + Raises + ------ + OSError + If the input file does not exist or cannot be read. + + See Also + -------- + numpy.load + + Notes + ----- + cuNumeric does not currently support ``.npz`` and pickled files. + + Availability + -------- + Single CPU + """ + return array( + np.load( + file, + max_header_size=max_header_size, # type: ignore [call-arg] + ) + ) diff --git a/cunumeric/_module/math_sum_prod_diff.py b/cunumeric/_module/math_sum_prod_diff.py index 8fa35961b5..87558c4d05 100644 --- a/cunumeric/_module/math_sum_prod_diff.py +++ b/cunumeric/_module/math_sum_prod_diff.py @@ -17,14 +17,21 @@ from typing import TYPE_CHECKING, Any import numpy as np +from numpy.core.multiarray import ( # type: ignore [attr-defined] + normalize_axis_index, +) from .._array.thunk import perform_scan, perform_unary_reduction from .._array.util import add_boilerplate +from .._ufunc.comparison import not_equal from .._ufunc.floating import isnan -from .._ufunc.math import add, multiply +from .._ufunc.math import add, multiply, subtract from ..config import ScanCode, UnaryRedCode from ..settings import settings as cunumeric_settings from ._unary_red_utils import get_non_nan_unary_red_code +from .array_dimension import broadcast_to +from .array_joining import concatenate +from .creation_shape import empty from .indexing import putmask from .logic_truth import all, any @@ -942,3 +949,139 @@ def nansum( initial=initial, where=where, ) + + +@add_boilerplate("a", "prepend", "append") +def diff( + a: ndarray, + n: int = 1, + axis: int = -1, + prepend: ndarray | None = None, + append: ndarray | None = None, +) -> ndarray: + """ + Calculate the n-th discrete difference along the given axis. + The first difference is given by ``out[i] = a[i+1] - a[i]`` along + the given axis, higher differences are calculated by using `diff` + recursively. + + Parameters + ---------- + a : array_like + Input array + n : int, optional + The number of times values are differenced. If zero, the input + is returned as-is. + axis : int, optional + The axis along which the difference is taken, default is the + last axis. + prepend, append : array_like, optional + Values to prepend or append to `a` along axis prior to + performing the difference. Scalar values are expanded to + arrays with length 1 in the direction of axis and the shape + of the input array in along all other axes. Otherwise the + dimension and shape must match `a` except along axis. + + Returns + ------- + diff : ndarray + The n-th differences. The shape of the output is the same as `a` + except along `axis` where the dimension is smaller by `n`. The + type of the output is the same as the type of the difference + between any two elements of `a`. This is the same as the type of + `a` in most cases. + + See Also + -------- + numpy.diff + + Notes + ----- + Type is preserved for boolean arrays, so the result will contain + `False` when consecutive elements are the same and `True` when they + differ. + + For unsigned integer arrays, the results will also be unsigned. This + should not be surprising, as the result is consistent with + calculating the difference directly:: + + >>> u8_arr = np.array([1, 0], dtype=np.uint8) + >>> np.diff(u8_arr) + array([255], dtype=uint8) + >>> u8_arr[1,...] - u8_arr[0,...] + 255 + If this is not desirable, then the array should be cast to a larger + integer type first: + >>> i16_arr = u8_arr.astype(np.int16) + >>> np.diff(i16_arr) + array([-1], dtype=int16) + Examples + -------- + >>> x = np.array([1, 2, 4, 7, 0]) + >>> np.diff(x) + array([ 1, 2, 3, -7]) + >>> np.diff(x, n=2) + array([ 1, 1, -10]) + >>> x = np.array([[1, 3, 6, 10], [0, 5, 6, 8]]) + >>> np.diff(x) + array([[2, 3, 4], + [5, 1, 2]]) + >>> np.diff(x, axis=0) + array([[-1, 2, 0, -2]]) + + Availability + -------- + Multiple GPUs, Multiple CPUs + """ + if n == 0: + return a + if n < 0: + raise ValueError("order must be non-negative but got " + repr(n)) + + nd = a.ndim + if nd == 0: + raise ValueError( + "diff requires input that is at least one dimensional" + ) + axis = normalize_axis_index(axis, nd) + + combined = [] + if prepend is not None: + if prepend.ndim == 0: + shape = list(a.shape) + shape[axis] = 1 + prepend = broadcast_to(prepend, tuple(shape)) + combined.append(prepend) + + combined.append(a) + + if append is not None: + if append.ndim == 0: + shape = list(a.shape) + shape[axis] = 1 + append = broadcast_to(append, tuple(shape)) + combined.append(append) + + if len(combined) > 1: + a = concatenate(combined, axis) + + # Diffing with n > shape results in an empty array. We have + # to handle this case explicitly as our slicing routines raise + # an exception with out-of-bounds slices, while NumPy's dont. + if a.shape[axis] <= n: + shape = list(a.shape) + shape[axis] = 0 + return empty(shape=tuple(shape), dtype=a.dtype) + + slice1l = [slice(None)] * nd + slice2l = [slice(None)] * nd + slice1l[axis] = slice(1, None) + slice2l[axis] = slice(None, -1) + slice1 = tuple(slice1l) + slice2 = tuple(slice2l) + + op = not_equal if a.dtype == np.bool_ else subtract + for _ in range(n): + a = op(a[slice1], a[slice2]) + + return a diff --git a/cunumeric/_module/stats_avgs_vars.py b/cunumeric/_module/stats_avgs_vars.py index 3c6b02e066..475e99cb11 100644 --- a/cunumeric/_module/stats_avgs_vars.py +++ b/cunumeric/_module/stats_avgs_vars.py @@ -14,14 +14,150 @@ # from __future__ import annotations +import math from typing import TYPE_CHECKING, Any import numpy as np +from numpy.core.numeric import ( # type: ignore [attr-defined] + normalize_axis_tuple, +) +from .._array.array import ndarray from .._array.util import add_boilerplate +from .creation_shape import full +from .logic_truth import any if TYPE_CHECKING: - from .._array.util import ndarray + import numpy.typing as npt + + +@add_boilerplate("a", "weights") +def average( + a: ndarray, + axis: int | tuple[int, ...] | None = None, + weights: ndarray | None = None, + returned: bool = False, + *, + keepdims: bool = False, +) -> ndarray | tuple[ndarray, ndarray]: + """ + Compute the weighted average along the specified axis. + + Parameters + ---------- + a : array_like + Array containing data to be averaged. If `a` is not an array, a + conversion is attempted. + axis : None or int or tuple of ints, optional + Axis or axes along which to average `a`. The default, + axis=None, will average over all of the elements of the input array. + If axis is negative it counts from the last to the first axis. + If axis is a tuple of ints, averaging is performed on all of the axes + specified in the tuple instead of a single axis or all the axes as + before. + weights : array_like, optional + An array of weights associated with the values in `a`. Each value in + `a` contributes to the average according to its associated weight. + The weights array can either be 1-D (in which case its length must be + the size of `a` along the given axis) or of the same shape as `a`. + If `weights=None`, then all data in `a` are assumed to have a + weight equal to one. The 1-D calculation is:: + + avg = sum(a * weights) / sum(weights) + + The only constraint on `weights` is that `sum(weights)` must not be 0. + returned : bool, optional + Default is `False`. If `True`, the tuple (`average`, `sum_of_weights`) + is returned, otherwise only the average is returned. + If `weights=None`, `sum_of_weights` is equivalent to the number of + elements over which the average is taken. + keepdims : bool, optional + If this is set to True, the axes which are reduced are left + in the result as dimensions with size one. With this option, + the result will broadcast correctly against the original `a`. + + Returns + ------- + retval, [sum_of_weights] : array_type or double + Return the average along the specified axis. When `returned` is `True`, + return a tuple with the average as the first element and the sum + of the weights as the second element. `sum_of_weights` is of the + same type as `retval`. The result dtype follows a general pattern. + If `weights` is None, the result dtype will be that of `a` , or + ``float64`` if `a` is integral. Otherwise, if `weights` is not None and + `a` is non-integral, the result type will be the type of lowest + precision capable of representing values of both `a` and `weights`. If + `a` happens to be integral, the previous rules still applies but the + result dtype will at least be ``float64``. + + Raises + ------ + ZeroDivisionError + When all weights along axis are zero. + ValueError + When the length of 1D `weights` is not the same as the shape of `a` + along axis. + + See Also + -------- + numpy.average + + Availability + -------- + Multiple GPUs, Multiple CPUs + """ + clean_axis: tuple[int, ...] | None = None + if axis is not None: + clean_axis = normalize_axis_tuple(axis, a.ndim, argname="axis") + + scl: npt.ArrayLike | ndarray = 1 + if weights is None: + scl = ( + a.size + if clean_axis is None + else math.prod([a.shape[i] for i in clean_axis]) + ) + if a.dtype.kind == "i": + scl = np.float64(scl) + avg = a.sum(axis=clean_axis, keepdims=keepdims) / scl + elif weights.shape == a.shape: + scl = weights.sum( + axis=clean_axis, + keepdims=keepdims, + dtype=(np.dtype(np.float64) if a.dtype.kind == "i" else None), + ) + if any(scl == 0): + raise ZeroDivisionError("Weights along axis sum to 0") + avg = (a * weights).sum(axis=clean_axis, keepdims=keepdims) / scl + else: + if clean_axis is None: + raise ValueError( + "a and weights must share shape or axis must be specified" + ) + if weights.ndim != 1 or len(clean_axis) != 1: + raise ValueError( + "Weights must be either 1 dimension along single " + "axis or the same shape as a" + ) + if weights.size != a.shape[clean_axis[0]]: + raise ValueError("Weights length does not match axis") + + scl = weights.sum( + dtype=(np.dtype(np.float64) if a.dtype.kind == "i" else None) + ) + project_shape = [1] * a.ndim + project_shape[clean_axis[0]] = -1 + weights = weights.reshape(project_shape) + if any(scl == 0): + raise ZeroDivisionError("Weights along axis sum to 0") + avg = (a * weights).sum(axis=clean_axis[0], keepdims=keepdims) / scl + + if returned: + if not isinstance(scl, ndarray) or scl.ndim == 0: + scl = full(avg.shape, scl) + return avg, scl + else: + return avg @add_boilerplate("a") diff --git a/cunumeric/_module/stats_correlating.py b/cunumeric/_module/stats_correlating.py new file mode 100644 index 0000000000..ac01d6a2cd --- /dev/null +++ b/cunumeric/_module/stats_correlating.py @@ -0,0 +1,187 @@ +# Copyright 2024 NVIDIA Corporation +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +from __future__ import annotations + +from typing import Any + +import numpy as np + +from .._array.array import ndarray +from .._array.util import add_boilerplate +from .array_joining import concatenate +from .creation_data import array +from .creation_shape import empty +from .linalg_mvp import dot +from .math_misc import clip +from .math_sum_prod_diff import sum +from .stats_avgs_vars import average + + +@add_boilerplate("m", "y", "fweights", "aweights") +def cov( + m: ndarray, + y: ndarray | None = None, + rowvar: bool = True, + bias: bool = False, + ddof: int | None = None, + fweights: ndarray | None = None, + aweights: ndarray | None = None, + *, + dtype: np.dtype[Any] | None = None, +) -> ndarray: + """ + Estimate a covariance matrix, given data and weights. + + Covariance indicates the level to which two variables vary together. + If we examine N-dimensional samples, :math:`X = [x_1, x_2, ... x_N]^T`, + then the covariance matrix element :math:`C_{ij}` is the covariance of + :math:`x_i` and :math:`x_j`. The element :math:`C_{ii}` is the variance + of :math:`x_i`. + + Parameters + ---------- + m : array_like + A 1-D or 2-D array containing multiple variables and observations. + Each row of `m` represents a variable, and each column a single + observation of all those variables. Also see `rowvar` below. + y : array_like, optional + An additional set of variables and observations. `y` has the same form + as that of `m`. + rowvar : bool, optional + If `rowvar` is True (default), then each row represents a + variable, with observations in the columns. Otherwise, the relationship + is transposed: each column represents a variable, while the rows + contain observations. + bias : bool, optional + Default normalization (False) is by ``(N - 1)``, where ``N`` is the + number of observations given (unbiased estimate). If `bias` is True, + then normalization is by ``N``. These values can be overridden by using + the keyword ``ddof``. + ddof : int, optional + If not ``None`` the default value implied by `bias` is overridden. + Note that ``ddof=1`` will return the unbiased estimate, even if both + `fweights` and `aweights` are specified, and ``ddof=0`` will return + the simple average. The default value is ``None``. + fweights : array_like, int, optional + 1-D array of integer frequency weights; the number of times each + observation vector should be repeated. + aweights : array_like, optional + 1-D array of observation vector weights. These relative weights are + typically large for observations considered "important" and smaller for + observations considered less "important". If ``ddof=0`` the array of + weights can be used to assign probabilities to observation vectors. + dtype : data-type, optional + Data-type of the result. By default, the return data-type will have + at least `float64` precision. + + Returns + ------- + out : ndarray + The covariance matrix of the variables. + + See Also + -------- + numpy.cov + + Availability + -------- + Multiple GPUs, Multiple CPUs + """ + # Check inputs + if ddof is not None and not isinstance(ddof, int): + raise ValueError("ddof must be integer") + + # Handles complex arrays too + if m.ndim > 2: + raise ValueError("m has more than 2 dimensions") + + if y is not None and y.ndim > 2: + raise ValueError("y has more than 2 dimensions") + + if dtype is None: + if y is None: + dtype = np.result_type(m.dtype, np.float64) + else: + dtype = np.result_type(m.dtype, y.dtype, np.float64) + + X = array(m, ndmin=2, dtype=dtype) + if not rowvar and X.shape[0] != 1: + X = X.T + if X.shape[0] == 0: + return empty((0, 0)) + if y is not None: + y = array(y, copy=False, ndmin=2, dtype=dtype) + if not rowvar and y.shape[0] != 1: + y = y.T + # TODO(mpapadakis): Could have saved on an intermediate copy of X in + # this case, if it was already of the right shape. + X = concatenate((X, y), axis=0) + + if ddof is None: + if not bias: + ddof = 1 + else: + ddof = 0 + + # Get the product of frequencies and weights + w: ndarray | None = None + if fweights is not None: + if fweights.ndim > 1: + raise RuntimeError("cannot handle multidimensional fweights") + if fweights.shape[0] != X.shape[1]: + raise RuntimeError("incompatible numbers of samples and fweights") + if any(fweights < 0): + raise ValueError("fweights cannot be negative") + w = fweights + if aweights is not None: + if aweights.ndim > 1: + raise RuntimeError("cannot handle multidimensional aweights") + if aweights.shape[0] != X.shape[1]: + raise RuntimeError("incompatible numbers of samples and aweights") + if any(aweights < 0): + raise ValueError("aweights cannot be negative") + if w is None: + w = aweights + else: + # Cannot be done in-place with *= when aweights.dtype != w.dtype + w = w * aweights + + avg, w_sum = average(X, axis=1, weights=w, returned=True) + + # Determine the normalization + fact: ndarray | float = 0.0 + if w is None: + fact = X.shape[1] - ddof + elif ddof == 0: + fact = w_sum + elif aweights is None: + fact = w_sum - ddof + else: + fact = w_sum - ddof * sum(w * aweights) / w_sum + + # TODO(mpapadakis): @add_boilerplate should extend the types of array + # arguments from `ndarray` to `npt.ArrayLike | ndarray`. + fact = clip(fact, 0.0, None) # type: ignore[arg-type] + + X -= avg[:, None] + if w is None: + X_T = X.T + else: + X_T = (X * w).T + c = dot(X, X_T.conj()) + # Cannot be done in-place with /= when the dtypes differ + c = c / fact + + return c.squeeze() diff --git a/cunumeric/_module/stats_histograms.py b/cunumeric/_module/stats_histograms.py index 0415598bd3..d6397760f2 100644 --- a/cunumeric/_module/stats_histograms.py +++ b/cunumeric/_module/stats_histograms.py @@ -20,9 +20,11 @@ from .._array.array import ndarray from .._array.util import add_boilerplate +from ..types import SortSide from .creation_data import asarray from .creation_shape import ones, zeros from .math_extrema import amax, amin +from .ssc_searching import searchsorted if TYPE_CHECKING: import numpy.typing as npt @@ -274,3 +276,113 @@ def histogram( hist /= bins_array[1:] - bins_array[:-1] return hist.astype(result_type), bins_array.astype(bins_orig_type) + + +@add_boilerplate("x", "bins") +def digitize( + x: ndarray, + bins: ndarray, + right: bool = False, +) -> ndarray | int: + """ + Return the indices of the bins to which each value in input array belongs. + + ========= ============= ============================ + `right` order of bins returned index `i` satisfies + ========= ============= ============================ + ``False`` increasing ``bins[i-1] <= x < bins[i]`` + ``True`` increasing ``bins[i-1] < x <= bins[i]`` + ``False`` decreasing ``bins[i-1] > x >= bins[i]`` + ``True`` decreasing ``bins[i-1] >= x > bins[i]`` + ========= ============= ============================ + + If values in `x` are beyond the bounds of `bins`, 0 or ``len(bins)`` is + returned as appropriate. + + Parameters + ---------- + x : array_like + Input array to be binned. Doesn't need to be 1-dimensional. + bins : array_like + Array of bins. It has to be 1-dimensional and monotonic. + right : bool, optional + Indicating whether the intervals include the right or the left bin + edge. Default behavior is (right==False) indicating that the interval + does not include the right edge. The left bin end is open in this + case, i.e., bins[i-1] <= x < bins[i] is the default behavior for + monotonically increasing bins. + + Returns + ------- + indices : ndarray of ints + Output array of indices, of same shape as `x`. + + Raises + ------ + ValueError + If `bins` is not monotonic. + TypeError + If the type of the input is complex. + + See Also + -------- + numpy.digitize + + Notes + ----- + If values in `x` are such that they fall outside the bin range, + attempting to index `bins` with the indices that `digitize` returns + will result in an IndexError. + For monotonically *increasing* `bins`, the following are equivalent:: + + np.digitize(x, bins, right=True) + np.searchsorted(bins, x, side='left') + + Note that as the order of the arguments are reversed, the side must be too. + The `searchsorted` call is marginally faster, as it does not do any + monotonicity checks. Perhaps more importantly, it supports all dtypes. + + Examples + -------- + >>> x = np.array([0.2, 6.4, 3.0, 1.6]) + >>> bins = np.array([0.0, 1.0, 2.5, 4.0, 10.0]) + >>> inds = np.digitize(x, bins) + >>> inds + array([1, 4, 3, 2]) + >>> for n in range(x.size): + ... print(bins[inds[n]-1], "<=", x[n], "<", bins[inds[n]]) + ... + 0.0 <= 0.2 < 1.0 + 4.0 <= 6.4 < 10.0 + 2.5 <= 3.0 < 4.0 + 1.0 <= 1.6 < 2.5 + >>> x = np.array([1.2, 10.0, 12.4, 15.5, 20.]) + >>> bins = np.array([0, 5, 10, 15, 20]) + >>> np.digitize(x,bins,right=True) + array([1, 2, 3, 4, 4]) + >>> np.digitize(x,bins,right=False) + array([1, 3, 3, 4, 5]) + + Availability + -------- + Multiple GPUs, Multiple CPUs + """ + # here for compatibility, searchsorted below is happy to take this + if np.issubdtype(x.dtype, np.complexfloating): + raise TypeError("x may not be complex") + + if bins.ndim > 1: + raise ValueError("bins must be one-dimensional") + + increasing = (bins[1:] >= bins[:-1]).all() + decreasing = (bins[1:] <= bins[:-1]).all() + if not increasing and not decreasing: + raise ValueError("bins must be monotonically increasing or decreasing") + + # this is backwards because the arguments below are swapped + side: SortSide = "left" if right else "right" + if decreasing: + # reverse the bins, and invert the results + return len(bins) - searchsorted(bins.flip(), x, side=side) + else: + return searchsorted(bins, x, side=side) diff --git a/cunumeric/_thunk/deferred.py b/cunumeric/_thunk/deferred.py index ddc5d9641b..7824101548 100644 --- a/cunumeric/_thunk/deferred.py +++ b/cunumeric/_thunk/deferred.py @@ -49,7 +49,12 @@ normalize_axis_tuple, ) -from .._utils.array import is_advanced_indexing, to_core_type +from .._utils.array import ( + is_advanced_indexing, + max_identity, + min_identity, + to_core_type, +) from ..config import ( BinaryOpCode, BitGeneratorDistribution, @@ -164,36 +169,6 @@ def wrapper(*args: Any, **kwargs: Any) -> R: } -def max_identity( - ty: np.dtype[Any], -) -> int | np.floating[Any] | bool | np.complexfloating[Any, Any]: - if ty.kind == "i" or ty.kind == "u": - return np.iinfo(ty).min - elif ty.kind == "f": - return np.finfo(ty).min - elif ty.kind == "c": - return np.finfo(np.float64).min + np.finfo(np.float64).min * 1j - elif ty.kind == "b": - return False - else: - raise ValueError(f"Unsupported dtype: {ty}") - - -def min_identity( - ty: np.dtype[Any], -) -> int | np.floating[Any] | bool | np.complexfloating[Any, Any]: - if ty.kind == "i" or ty.kind == "u": - return np.iinfo(ty).max - elif ty.kind == "f": - return np.finfo(ty).max - elif ty.kind == "c": - return np.finfo(np.float64).max + np.finfo(np.float64).max * 1j - elif ty.kind == "b": - return True - else: - raise ValueError(f"Unsupported dtype: {ty}") - - _UNARY_RED_IDENTITIES: dict[UnaryRedCode, Callable[[Any], Any]] = { UnaryRedCode.SUM: lambda _: 0, UnaryRedCode.SUM_SQUARES: lambda _: 0, diff --git a/cunumeric/_ufunc/comparison.py b/cunumeric/_ufunc/comparison.py index ef9d9fcc26..0d610e7d3c 100644 --- a/cunumeric/_ufunc/comparison.py +++ b/cunumeric/_ufunc/comparison.py @@ -72,6 +72,7 @@ "logical_and", BinaryOpCode.LOGICAL_AND, relation_types_of(all_dtypes), + red_code=UnaryRedCode.ALL, ) logical_or = create_binary_ufunc( @@ -79,6 +80,7 @@ "logical_or", BinaryOpCode.LOGICAL_OR, relation_types_of(all_dtypes), + red_code=UnaryRedCode.ANY, ) logical_xor = create_binary_ufunc( diff --git a/cunumeric/_ufunc/ufunc.py b/cunumeric/_ufunc/ufunc.py index c2a7655cd0..d69db9c312 100644 --- a/cunumeric/_ufunc/ufunc.py +++ b/cunumeric/_ufunc/ufunc.py @@ -758,6 +758,16 @@ def reduce( f"reduction for {self} is not yet implemented" ) + if self._op_code in ( + BinaryOpCode.LOGICAL_AND, + BinaryOpCode.LOGICAL_OR, + ): + res_dtype = bool + if dtype is not None: + raise TypeError("Cannot set dtype on a logical reduction") + else: + res_dtype = None + # NumPy seems to be using None as the default axis value for scalars if array.ndim == 0 and axis == 0: axis = None @@ -772,6 +782,7 @@ def reduce( keepdims=keepdims, initial=initial, where=where, + res_dtype=res_dtype, ) diff --git a/cunumeric/_utils/array.py b/cunumeric/_utils/array.py index 705335cd81..a764367a6e 100644 --- a/cunumeric/_utils/array.py +++ b/cunumeric/_utils/array.py @@ -81,3 +81,33 @@ def calculate_volume(shape: NdShape) -> int: if len(shape) == 0: return 0 return reduce(lambda x, y: x * y, shape) + + +def max_identity( + ty: np.dtype[Any], +) -> int | np.floating[Any] | bool | np.complexfloating[Any, Any]: + if ty.kind == "i" or ty.kind == "u": + return np.iinfo(ty).min + elif ty.kind == "f": + return np.finfo(ty).min + elif ty.kind == "c": + return np.finfo(np.float64).min + np.finfo(np.float64).min * 1j + elif ty.kind == "b": + return False + else: + raise ValueError(f"Unsupported dtype: {ty}") + + +def min_identity( + ty: np.dtype[Any], +) -> int | np.floating[Any] | bool | np.complexfloating[Any, Any]: + if ty.kind == "i" or ty.kind == "u": + return np.iinfo(ty).max + elif ty.kind == "f": + return np.finfo(ty).max + elif ty.kind == "c": + return np.finfo(np.float64).max + np.finfo(np.float64).max * 1j + elif ty.kind == "b": + return True + else: + raise ValueError(f"Unsupported dtype: {ty}") diff --git a/docs/cunumeric/source/api/io.rst b/docs/cunumeric/source/api/io.rst new file mode 100644 index 0000000000..0fd4ee4b3a --- /dev/null +++ b/docs/cunumeric/source/api/io.rst @@ -0,0 +1,11 @@ +Input and output +================ + +.. currentmodule:: cunumeric + +NumPy binary files (npy, npz) +----------------------------- +.. autosummary:: + :toctree: generated/ + + load diff --git a/docs/cunumeric/source/api/math.rst b/docs/cunumeric/source/api/math.rst index a4f71d1cf8..6e16bb66ce 100644 --- a/docs/cunumeric/source/api/math.rst +++ b/docs/cunumeric/source/api/math.rst @@ -59,6 +59,7 @@ Sums, products, differences sum cumprod cumsum + diff nancumprod nancumsum nanprod diff --git a/docs/cunumeric/source/api/routines.rst b/docs/cunumeric/source/api/routines.rst index e3fc080a07..166cb44501 100644 --- a/docs/cunumeric/source/api/routines.rst +++ b/docs/cunumeric/source/api/routines.rst @@ -10,6 +10,7 @@ Routines binary datatype indexing + io linalg logic math diff --git a/docs/cunumeric/source/api/statistics.rst b/docs/cunumeric/source/api/statistics.rst index 48f10f19cf..89b87490e8 100644 --- a/docs/cunumeric/source/api/statistics.rst +++ b/docs/cunumeric/source/api/statistics.rst @@ -3,32 +3,40 @@ Statistics .. currentmodule:: cunumeric +Order statistics +---------------- + +.. autosummary:: + :toctree: generated/ + + quantile + percentile + Averages and variances ---------------------- .. autosummary:: :toctree: generated/ + average mean nanmean var - -Histograms ----------- +Correlating +----------- .. autosummary:: :toctree: generated/ - bincount - histogram - + cov -Order statistics ----------------- +Histograms +---------- .. autosummary:: :toctree: generated/ - quantile - percentile + bincount + histogram + digitize diff --git a/tests/integration/test_average.py b/tests/integration/test_average.py new file mode 100644 index 0000000000..e8ff4934da --- /dev/null +++ b/tests/integration/test_average.py @@ -0,0 +1,103 @@ +# Copyright 2024 NVIDIA Corporation +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +import numpy as np +import pytest +from utils.comparisons import allclose + +import cunumeric as num + +axes = [None, 0, 1, 2, (0, 1, 2)] + + +input = [[[12, 3, 1, 2], [9, 1, 6, 1]], [[7, 9, 11, 50], [31, 5, 3, 2]]] +in_num = num.array(input) +in_np = np.array(input) + + +@pytest.mark.parametrize("axis", axes) +def test_no_mask(axis): + out_num, scl_num = num.average(in_num, axis=axis, returned=True) + out_num_no_scl = num.average(in_num, axis=axis, returned=False) + out_np, scl_np = np.average(in_np, axis=axis, returned=True) + assert allclose(out_num, out_np) + assert allclose(scl_num, scl_np) + assert allclose(out_num, out_num_no_scl) + + +@pytest.mark.parametrize("axis", axes) +def test_full_weights(axis): + weight_input = [[[1, 2, 3, 4], [3, 3, 7, 1]], [[2, 2, 3, 3], [4, 1, 0, 1]]] + weights_np = np.array(weight_input) + weights_num = num.array(weight_input) + + out_num, scl_num = num.average( + in_num, weights=weights_num, axis=axis, returned=True + ) + out_num_no_scl = num.average( + in_num, weights=weights_num, axis=axis, returned=False + ) + out_np, scl_np = np.average( + in_np, weights=weights_np, axis=axis, returned=True + ) + assert allclose(out_num, out_np) + assert allclose(scl_num, scl_np) + assert allclose(out_num, out_num_no_scl) + + +single_dimension_weights = [ + [3, 4], + [1, 2], + [4, 1, 2, 1], +] +single_dimension_axis = [0, 1, 2] + + +@pytest.mark.parametrize( + "weights,axis", zip(single_dimension_weights, single_dimension_axis) +) +def test_single_axis_weights(weights, axis): + weights_np = np.array(weights) + weights_num = num.array(weights) + + out_num, scl_num = num.average( + in_num, weights=weights_num, axis=axis, returned=True + ) + out_num_no_scl = num.average( + in_num, weights=weights_num, axis=axis, returned=False + ) + out_np, scl_np = np.average( + in_np, weights=weights_np, axis=axis, returned=True + ) + assert allclose(out_num, out_np) + assert allclose(scl_num, scl_np) + assert allclose(out_num, out_num_no_scl) + + +def test_exception_raising(): + with pytest.raises(ValueError): + num.average(in_num, weights=[0, 2]) + with pytest.raises(ValueError): + num.average(in_num, axis=2, weights=[0, 2]) + with pytest.raises(ValueError): + num.average(in_num, axis=0, weights=[[0, 2]]) + with pytest.raises(ZeroDivisionError): + num.average(in_num, axis=0, weights=[0, 0]) + + +if __name__ == "__main__": + import sys + + sys.exit(pytest.main(sys.argv)) diff --git a/tests/integration/test_diff.py b/tests/integration/test_diff.py new file mode 100644 index 0000000000..b1e62b7cd9 --- /dev/null +++ b/tests/integration/test_diff.py @@ -0,0 +1,66 @@ +# Copyright 2022 NVIDIA Corporation +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +import numpy as np +import pytest +from utils.comparisons import allclose + +import cunumeric as num + + +@pytest.mark.parametrize( + "args", + [ + ((100,), 1, -1, None, None), + ((100,), 2, -1, None, None), + ((100,), 3, -1, None, None), + ((100,), 2, 0, None, None), + ((10, 10), 2, -1, None, None), + ((10, 10), 2, 0, None, None), + ((10, 10), 2, 1, None, None), + ((100,), 3, -1, [1.0, 2.0], None), + ((100,), 3, -1, None, [1.0, 2.0]), + ((100,), 3, -1, [1.0, 2.0], [1.0, 2.0]), + ((5,), 5, -1, None, None), + ((5,), 6, 0, None, None), + ((5, 5), 5, 1, None, None), + ((5, 5), 6, 1, None, None), + ], +) +def test_diff(args): + shape, n, axis, prepend, append = args + nparr = np.random.random(shape) + cnarr = num.array(nparr) + + # We are not adopting the np._NoValue default arguments + # for this function, as no special behavior is needed on None. + n_prepend = np._NoValue if prepend is None else prepend + n_append = np._NoValue if append is None else append + res_np = np.diff(nparr, n=n, axis=axis, prepend=n_prepend, append=n_append) + res_cn = num.diff(cnarr, n=n, axis=axis, prepend=prepend, append=append) + + assert allclose(res_np, res_cn) + + +def test_diff_nzero(): + a = num.ones(100) + ad = num.diff(a, n=0) + assert a is ad + + +if __name__ == "__main__": + import sys + + sys.exit(pytest.main(sys.argv)) diff --git a/tests/integration/test_digitize.py b/tests/integration/test_digitize.py new file mode 100644 index 0000000000..9c75c3a608 --- /dev/null +++ b/tests/integration/test_digitize.py @@ -0,0 +1,166 @@ +# Copyright 2022 NVIDIA Corporation +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +import math + +import numpy as np +import pytest + +import cunumeric as num + +DTYPES = ( + np.uint32, + np.uint64, + np.float32, + np.float64, +) + +SHAPES = ( + (10,), + (2, 5), + (3, 7, 10), +) + + +class TestDigitizeErrors(object): + def test_complex_array(self): + a = np.array([2, 3, 10, 9], dtype=np.complex64) + bins = [0, 3, 5] + expected_exc = TypeError + with pytest.raises(expected_exc): + num.digitize(a, bins) + with pytest.raises(expected_exc): + np.digitize(a, bins) + + @pytest.mark.xfail + def test_bad_array(self): + bins = [0, 5, 3] + expected_exc = ValueError + with pytest.raises(expected_exc): + # cunumeric raises TypeError + num.digitize(None, bins) + with pytest.raises(expected_exc): + np.digitize(None, bins) + + @pytest.mark.xfail + def test_bad_bins(self): + a = [2, 3, 10, 9] + expected_exc = ValueError + with pytest.raises(expected_exc): + # cunumeric raises TypeError + num.digitize(a, None) + with pytest.raises(expected_exc): + np.digitize(a, None) + + def test_bins_non_monotonic(self): + a = [2, 3, 10, 9] + bins = [0, 5, 3] + expected_exc = ValueError + with pytest.raises(expected_exc): + num.digitize(a, bins) + with pytest.raises(expected_exc): + np.digitize(a, bins) + + def test_bins_ndim(self): + a = [2, 3, 10, 9] + bins = np.array([[0], [5], [3]]) + expected_exc = ValueError + with pytest.raises(expected_exc): + num.digitize(a, bins) + with pytest.raises(expected_exc): + np.digitize(a, bins) + + +def generate_random(shape, dtype): + a_np = None + size = math.prod(shape) + if np.issubdtype(dtype, np.integer): + a_np = np.array( + np.random.randint( + np.iinfo(dtype).min, + np.iinfo(dtype).max, + size=size, + dtype=dtype, + ), + dtype=dtype, + ) + elif np.issubdtype(dtype, np.floating): + a_np = np.array(np.random.random(size=size), dtype=dtype) + elif np.issubdtype(dtype, np.complexfloating): + a_np = np.array( + np.random.random(size=size) + np.random.random(size=size) * 1j, + dtype=dtype, + ) + else: + assert False + return a_np.reshape(shape) + + +@pytest.mark.parametrize("right", (True, False)) +def test_empty(right): + bins = [0, 3, 5] + assert len(num.digitize([], bins, right=right)) == 0 + + +@pytest.mark.parametrize("shape", SHAPES, ids=str) +@pytest.mark.parametrize("dtype", DTYPES, ids=str) +@pytest.mark.parametrize("right", (True, False)) +def test_increasing_bins(shape, dtype, right): + a = generate_random(shape, dtype) + bins = [0, 3, 5] + + a_num = num.array(a) + bins_num = num.array(bins) + + res_np = np.digitize(a, bins, right=right) + res_num = num.digitize(a, bins, right=right) + assert num.array_equal(res_np, res_num) + + res_np = np.digitize(a, bins, right=right) + res_num = num.digitize(a_num, bins, right=right) + assert num.array_equal(res_np, res_num) + + res_np = np.digitize(a, bins, right=right) + res_num = num.digitize(a_num, bins_num, right=right) + assert num.array_equal(res_np, res_num) + + +@pytest.mark.parametrize("shape", SHAPES, ids=str) +@pytest.mark.parametrize("dtype", DTYPES, ids=str) +@pytest.mark.parametrize("right", (True, False)) +def test_decreasing_bins(shape, dtype, right): + a = generate_random(shape, dtype) + bins = [5, 3, 0] + + a_num = num.array(a) + bins_num = num.array(bins) + + res_np = np.digitize(a, bins, right=right) + res_num = num.digitize(a, bins, right=right) + assert num.array_equal(res_np, res_num) + + res_np = np.digitize(a, bins, right=right) + res_num = num.digitize(a_num, bins, right=right) + assert num.array_equal(res_np, res_num) + + res_np = np.digitize(a, bins, right=right) + res_num = num.digitize(a_num, bins_num, right=right) + assert num.array_equal(res_np, res_num) + + +if __name__ == "__main__": + import sys + + sys.exit(pytest.main(sys.argv)) diff --git a/tests/integration/test_fallback.py b/tests/integration/test_fallback.py index 4e312d0bb4..3d93f5e91c 100644 --- a/tests/integration/test_fallback.py +++ b/tests/integration/test_fallback.py @@ -28,8 +28,15 @@ def test_ufunc(): in_num = num.array([0, 1, 2, 3]) in_np = in_num.__array__() - out_num = np.logical_and.reduce(in_num) - out_np = np.logical_and.reduce(in_np) + # This test uses logical_and.accumulate because it is currently + # unimplemented, and we want to verify a behaviour of unimplemented ufunc + # methods. If logical_and.accumulate becomes implemented in the future, + # this assertion will start to fail, and a new (unimplemented) ufunc method + # should be found to replace it + assert not num.logical_and.accumulate._cunumeric.implemented + + out_num = num.logical_and.accumulate(in_num) + out_np = np.logical_and.accumulate(in_np) assert np.array_equal(out_num, out_np) diff --git a/tests/integration/test_logical_reduction.py b/tests/integration/test_logical_reduction.py new file mode 100644 index 0000000000..3bd1f3e997 --- /dev/null +++ b/tests/integration/test_logical_reduction.py @@ -0,0 +1,40 @@ +# Copyright 2024 NVIDIA Corporation +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +import numpy as np +import pytest + +import cunumeric as num + + +@pytest.mark.parametrize("axis", [None, 0, 1, 2, (0, 1, 2)]) +def test_logical_reductions(axis): + input = [[[12, 0, 1, 2], [9, 0, 0, 1]], [[0, 0, 0, 5], [1, 1, 1, 1]]] + in_num = num.array(input) + in_np = np.array(input) + + out_num = num.logical_and.reduce(in_num, axis=axis) + out_np = np.logical_and.reduce(in_np, axis=axis) + assert num.array_equal(out_num, out_np) + + out_num = num.logical_or.reduce(in_num, axis=axis) + out_np = np.logical_or.reduce(in_np, axis=axis) + assert num.array_equal(out_num, out_np) + + +if __name__ == "__main__": + import sys + + sys.exit(pytest.main(sys.argv)) diff --git a/tests/integration/test_stats.py b/tests/integration/test_stats.py index 256a6b77fd..c598a78997 100644 --- a/tests/integration/test_stats.py +++ b/tests/integration/test_stats.py @@ -215,6 +215,102 @@ def test_var_xfail(dtype, ddof, axis, shape): check_op(op_np, op_num, np_in, dtype, negative_test=True) +@pytest.mark.parametrize("dtype", dtypes) +@pytest.mark.parametrize("rowvar", [True, False]) +@pytest.mark.parametrize("ddof", [None, 0, 1]) +def test_cov(dtype, rowvar, ddof): + np_in = get_op_input(astype=dtype) + num_in = num.array(np_in) + + np_out = np.cov(np_in, rowvar=rowvar, ddof=ddof) + num_out = num.cov(num_in, rowvar=rowvar, ddof=ddof) + if dtype == dtypes[0]: + assert allclose(np_out, num_out, atol=1e-2) + else: + assert allclose(np_out, num_out) + + +fweights_base = [[9, 2, 1, 2, 3], [1, 1, 3, 2, 4], None] +np_aweights_base = [ + np.abs(get_op_input(astype=dtype, shape=(5,))) for dtype in dtypes +] + [[0.03, 0.04, 01.01, 0.02, 0.08], None] + + +@pytest.mark.parametrize("dtype", dtypes) +@pytest.mark.parametrize("bias", [True, False]) +@pytest.mark.parametrize("ddof", [None, 0, 1]) +@pytest.mark.parametrize("fweights", fweights_base) +@pytest.mark.parametrize("np_aweights", np_aweights_base) +def test_cov_full(dtype, bias, ddof, fweights, np_aweights): + np_in = get_op_input(astype=dtype, shape=(4, 5)) + num_in = num.array(np_in) + if fweights is not None: + np_fweights = np.array(fweights) + num_fweights = num.array(fweights) + else: + np_fweights = None + num_fweights = None + if isinstance(np_aweights, np.ndarray): + num_aweights = num.array(np_aweights) + else: + num_aweights = np_aweights + # num_aweights = None + # np_aweights = None + + np_out = np.cov( + np_in, bias=bias, ddof=ddof, fweights=np_fweights, aweights=np_aweights + ) + num_out = num.cov( + num_in, + bias=bias, + ddof=ddof, + fweights=num_fweights, + aweights=num_aweights, + ) + # if dtype == dtypes[0]: + # assert allclose(np_out, num_out, atol=1e-2) + # else: + # assert allclose(np_out, num_out) + assert allclose(np_out, num_out, atol=1e-2) + + +@pytest.mark.parametrize("ddof", [None, 0, 1]) +@pytest.mark.parametrize("fweights", fweights_base) +@pytest.mark.parametrize("np_aweights", np_aweights_base) +def test_cov_dtype_scaling(ddof, fweights, np_aweights): + np_in = np.array( + [ + [1 + 3j, 1 - 1j, 2 + 2j, 4 + 3j, -1 + 2j], + [1 + 3j, 1 - 1j, 2 + 2j, 4 + 3j, -1 + 2j], + ] + ) + num_in = num.array(np_in) + if fweights is not None: + np_fweights = np.array(fweights) + num_fweights = num.array(fweights) + else: + np_fweights = None + num_fweights = None + if isinstance(np_aweights, np.ndarray): + num_aweights = num.array(np_aweights) + else: + num_aweights = np_aweights + + np_out = np.cov( + np_in, + ddof=ddof, + fweights=np_fweights, + aweights=np_aweights, + ) + num_out = num.cov( + num_in, + ddof=ddof, + fweights=num_fweights, + aweights=num_aweights, + ) + assert allclose(np_out, num_out, atol=1e-2) + + if __name__ == "__main__": import sys From c4fb60e6b84072059f0a747ae2e7978a10cb7099 Mon Sep 17 00:00:00 2001 From: Irina Demeshko Date: Tue, 16 Apr 2024 21:27:46 -0700 Subject: [PATCH 188/462] fixing broadcasting bug in advanced indexing with bools (#153) --- cunumeric/_thunk/deferred.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/cunumeric/_thunk/deferred.py b/cunumeric/_thunk/deferred.py index 7824101548..f47ce47abb 100644 --- a/cunumeric/_thunk/deferred.py +++ b/cunumeric/_thunk/deferred.py @@ -638,9 +638,8 @@ def _advanced_indexing_with_boolean_array( task.add_scalar_arg(is_set, ty.bool_) task.add_scalar_arg(key_dims, ty.int64) task.add_constraint(align(p_rhs, p_key)) - task.add_constraint( - broadcast(p_rhs, range(1, len(rhs.base.shape))) - ) + if rhs.base.ndim > 1: + task.add_constraint(broadcast(p_rhs, range(1, rhs.base.ndim))) task.execute() # TODO : current implementation of the ND output regions From 85e09248848d6ad542dbe131d8cdab8fd35791e9 Mon Sep 17 00:00:00 2001 From: Parag Kulkarni Date: Thu, 18 Apr 2024 08:55:37 +0530 Subject: [PATCH 189/462] Add and upload conda release nightly builds. (#157) * Create ci-gh-nightly-release.yml * Init 2 workflows. * Add upload script * Update UPL_STR * Try uploading to test folder. * Check with release build * build-type to inputs * C1 * C2 * Use vtmp version * Update make_conda_env * Use make_release_env * make_release_env_ * Use build_ci_product for both workflows. * Revert artifact name * Check versions.cmake path * Check if versiions.json is modified. * s/SED/sed * list the files * list by root * TEst with id * Check with GITHUB_WORKSPACE * Check with $GITHUB_WORKSPACE * Check version before build job * Check with separate job * Checkout the repos * Cut the extra cunumeric.internal folder * Add artifact name array * Add artifact name array, revert version update. * Cleanup * REmove comma from 2nd element * Use _ * Refine versions.json * Use upload_enabled from above to build_and_test * Check with dependencies-tag * Use a different json file. * Cleanup * Clean * Cleanup 2 * Use latest legate builds * Clean3 * Change package id * Entire Workflow filename * Revert to earlier git_sha * Create yaml file * Reformat config.yaml * refine script files * Refine2 * file name change * Use cuda specific env. * Check CPU build * Check with *_cpu* * Check for both uploads. * Cleanup + Append nightly * s/upload_enabled/upload_build * Overhaul string in meta.yaml * s/upl/upload_tag * Set upload and tests in parallel. Until we find a define to eliminate test building. * Add upload_enabled matrix. * Separate make_release_env for cpu and gpu * *_cpu* in the run * Read file list of all generated files * Only core_version. Test and revert * Revert earlier change. Use a new legate build (TR) * update legate version * Update legate version 2 * Revert version to original * Check with no pinning to cpu * Revert versions * Update legate version * Update legate version (OpenMP) * Update legate version to include *_cpu* * Cat environ*.yaml * Sync version number * Update version to sync with legate.core release build * Cleanup and Prep for release builds * Add make_conda_env_local * make_conda_env_local syntax * Add python version defn * remove separate python_version * Check the jugglery * s/upload_enabled/upload_build * Copy legate core artifacts to /tmp * Check with yaml * Update legate version * Print path followed. * Add verbosity * Set Cuda version on legate.core.internal lines. * Use default_cuda_version = '12.2' and check * Cleanup + Try using make_release_env_local * Cleanup * Revert to using yaml based environment * Cleanup 2 * My Test: Start with adding runtime libs from CI path * Use cmake build before pip install * WKG: Use -DCMAKE_CUDA_ARCHITECTURES=RAPIDS * Cleanup * s/make_release_env_local/make_release_env. Run conda-utils script in the begining. * Update legate version * Create dependencies-upload.json * Add both files in .github * Check 5.1 * condition in json * typo s/-/_ * CHeck 6.0 * s/==/= * Add condition * Check 6.2 * CHeck package name * Conditional package load * Just add the condition. Checked by jinja * Check 6.4 * Check with extra_tag * Typo * gha=ce5f96c0310f168cb57601da931781b6b6a33a06 * gha=4c3e2cb55655495b9b6a4ecb0060c83549fe63db * Cleanup * Delete .github/dependencies-upload.json * gha=031e45621db0aa66493068169e9fc03ce6092869 * s/release-gcc/release * gha=cf274827a96e738b1b771e330acd24aad8e9e5c1 * Revert extra tag * s/cmake-preset/build-mode * Use ci-gh-nightly-release.yml to download for both workflows. Remove upload-string. * Hardcode build-type in versions.json * We need the extra tags. * Revert to using ci-gh.yml * Get env * #export CUDAHOSTCXX=${CXX} update depdencies.json * SHA=ce5f96c0310f168cb57601da931781b6b6a33a06 * Use cmake/versiosns.json and latest legate.core.internal build. * upload-enabled: ${{ inputs.upload-enabled }} in test witing container * SHA=40f769796e6081a163f44ffd3c46b1b704ed0fa5 - https://github.com/nv-legate/legate.core.internal/actions/runs/8646015125 - https://github.com/nv-legate/legate.core.internal/actions/runs/8646015122/ * Use Workflow=ci-gh-release.yml * Cleanup.. * Create ci-gh-release.yml * Add schedule to nightly workflow * We don't need conda channel. Remove and Verify * Remove -ccbin ${CXX} * -Remove export OPENSSL_DIR="$PREFIX" -Remove the Test dir. * Re-add export OPENSSL_DIR="$PREFIX" * - Update legate version - Use nightly release - Use v1.1.0 (legate-gh-ci) * Verify with earlier working SHA version. * Update to the submitted CL. * - Update git_tag t latest commit - Use ci-gh-nightly-release.yml * Use updated legate-gh-ci to test download * Dummy * Use update legate-gh-ci * Update to v1.1.3 * Check with vtmp * Update to latest legate version. * Add mylog * Print all args * - Use legate-gh-ci v1.1.4 * dependencies-file: "cmake/versions.json" --- .github/workflows/ci-gh-nightly-release.yml | 31 +++++++ .github/workflows/ci-gh-release.yml | 32 +++++++ .github/workflows/ci-gh.yml | 3 + .github/workflows/gh-build-and-test.yml | 89 ++++++++++++++++--- cmake/versions.json | 6 +- conda/conda-build/build.sh | 22 +++-- conda/conda-build/meta.yaml | 34 ++++--- continuous_integration/scripts/build | 51 ++++++++++- .../scripts/build-cunumeric-conda | 6 ++ continuous_integration/scripts/make-conda-env | 4 +- 10 files changed, 243 insertions(+), 35 deletions(-) create mode 100644 .github/workflows/ci-gh-nightly-release.yml create mode 100644 .github/workflows/ci-gh-release.yml diff --git a/.github/workflows/ci-gh-nightly-release.yml b/.github/workflows/ci-gh-nightly-release.yml new file mode 100644 index 0000000000..0bbe45e8e0 --- /dev/null +++ b/.github/workflows/ci-gh-nightly-release.yml @@ -0,0 +1,31 @@ +name: Build Nightly release package + +concurrency: + group: ci-nightly-release-on-${{ github.event_name }}-from-${{ github.ref_name }} + cancel-in-progress: true + +on: + workflow_dispatch: + schedule: + - cron: '0 23 * * *' # Nightly at 11:00 PM + +jobs: + build-and-test: + strategy: + fail-fast: false + matrix: + target-device: + - gpu + - cpu + upload-enabled: + - true + - false + uses: + ./.github/workflows/gh-build-and-test.yml + with: + target-device: ${{ matrix.target-device }} + platform: linux + build-type: release + upload-enabled: ${{ matrix.upload-enabled }} + dependencies-workflow: "ci-gh-nightly-release.yml" + secrets: inherit diff --git a/.github/workflows/ci-gh-release.yml b/.github/workflows/ci-gh-release.yml new file mode 100644 index 0000000000..35a2a275af --- /dev/null +++ b/.github/workflows/ci-gh-release.yml @@ -0,0 +1,32 @@ +name: Build Release package + +concurrency: + group: ci-nightly-release-on-${{ github.event_name }}-from-${{ github.ref_name }} + cancel-in-progress: true + +on: + workflow_dispatch: + push: + branches: + - "pull-request/[0-9]+" + - "cpp-branch-*" + +jobs: + build-and-test: + strategy: + fail-fast: false + matrix: + target-device: + - gpu + - cpu + upload-enabled: + - false + uses: + ./.github/workflows/gh-build-and-test.yml + with: + target-device: ${{ matrix.target-device }} + platform: linux + build-type: release + upload-enabled: ${{ matrix.upload-enabled }} + dependencies-workflow: "ci-gh-nightly-release.yml" + secrets: inherit diff --git a/.github/workflows/ci-gh.yml b/.github/workflows/ci-gh.yml index 5c2a7a2330..8d2e108a7c 100644 --- a/.github/workflows/ci-gh.yml +++ b/.github/workflows/ci-gh.yml @@ -24,4 +24,7 @@ jobs: with: target-device: ${{ matrix.target-device }} platform: linux + build-type: ci + upload-enabled: false + dependencies-workflow: "ci-gh.yml" secrets: inherit diff --git a/.github/workflows/gh-build-and-test.yml b/.github/workflows/gh-build-and-test.yml index 0a2aca4d9c..fb79f48635 100644 --- a/.github/workflows/gh-build-and-test.yml +++ b/.github/workflows/gh-build-and-test.yml @@ -7,6 +7,16 @@ on: target-device: type: string required: true + build-type: + type: string + required: true + upload-enabled: + type: boolean + required: true + dependencies-workflow: + required: true + type: string + description: The workflow file name used by the dependency jobs: setup-build: @@ -29,24 +39,82 @@ jobs: build: needs: setup-build - name: "Build (${{ inputs.platform }}, ${{ inputs.target-device }})" + name: "Build (${{ inputs.platform }}, ${{ inputs.target-device }}, ${{ inputs.build-type }})" uses: - nv-legate/legate-gh-ci/.github/workflows/gh-build.yml@v1 + nv-legate/legate-gh-ci/.github/workflows/gh-build.yml@v1.1.4 with: client-repo: ${{ github.event.repository.name }} target-device: ${{ inputs.target-device }} runs-on: ${{ needs.setup-build.outputs.runner_type }} - build-type: ci + build-type: ${{ inputs.build-type }} use-container: ${{ inputs.platform == 'linux' }} platform: ${{ inputs.platform }} dependencies-file: "cmake/versions.json" - legate-gh-ci-tag: "v1" - cmake-preset: "" + dependencies-workflow: ${{ inputs.dependencies-workflow }} + legate-gh-ci-tag: "v1.1.4" + build-mode: "" ucx-enabled: false - upload-enabled: false + upload-enabled: ${{ inputs.upload-enabled }} + secrets: inherit + + upload: + needs: build + runs-on: linux-amd64-gpu-v100-earliest-1 + container: + options: -u root + image: condaforge/miniforge3:latest + + if: ${{ github.repository_owner == 'nv-legate' && inputs.upload-enabled == true }} + steps: + - name: Set environment variables + shell: bash --noprofile --norc -xeuo pipefail {0} + run: | + UCX_STR='' + BUILD_MODE='' + BUILD_MODE_STR="" + [ -n "${BUILD_MODE}" ] && BUILD_MODE_STR="-${BUILD_MODE}" + + echo "ARTIFACT_NAME=${{ inputs.platform }}-${{ inputs.build-type }}-${{ github.event.repository.name }}-${{ inputs.target-device }}${BUILD_MODE_STR}${UCX_STR}-${{ github.sha }}" >> $GITHUB_ENV + echo "ARTIFACTS_DIR=${{ github.workspace }}/artifacts" >> $GITHUB_ENV + echo "OUTPUT_FOLDER=conda-build/cunumeric" >> $GITHUB_ENV + echo "TARGET_PLATFORM=linux-64" >> $GITHUB_ENV + + - name: Download build artifacts + uses: actions/download-artifact@v4 + with: + name: ${{ env.ARTIFACT_NAME }} + path: ${{ env.ARTIFACTS_DIR }} + + - name: Display structure of downloaded artifacts + run: | + ls -aR $ARTIFACTS_DIR + + - name: Get Build Date + run: | + echo "BUILD_DATE=$(date +%Y%m%d)" >> $GITHUB_ENV + + - name: Dump relevant environment variables + run: | + echo "ARTIFACTS_DIR=$ARTIFACTS_DIR" + echo "ARTIFACT_NAME=$ARTIFACT_NAME" + echo "TARGET_PLATFORM=$TARGET_PLATFORM" + echo "BUILD_DATE=$BUILD_DATE" + + - name: Upload to URM + run: | + apt-get update + apt-get install -y curl + for f in $(find $ARTIFACTS_DIR/$OUTPUT_FOLDER/$TARGET_PLATFORM/. -name 'cunumeric-*.tar.bz2') + do + fname=$(basename $f) + echo "File to upload: $fname" + curl -usvc-legate-github:${{ secrets.URM_ARTIFACT_TOKEN }} -T $f "https://urm.nvidia.com/artifactory/sw-legate-conda-local/${TARGET_PLATFORM}/$fname;buildType="Nightly";buildDate=$BUILD_DATE;automatedTestingPassed=0;sha=${{ github.sha }}"; + done + setup-test: + if: inputs.upload-enabled == false name: Setup test needs: - build @@ -111,17 +179,18 @@ jobs: matrix: ${{fromJson(needs.setup-test.outputs.matrix)}} uses: - nv-legate/legate-gh-ci/.github/workflows/gh-test-within-container.yml@v1 + nv-legate/legate-gh-ci/.github/workflows/gh-test-within-container.yml@v1.1.4 with: client-repo: ${{ github.event.repository.name }} - build-type: ci + build-type: ${{ inputs.build-type }} name: ${{ matrix.test-config.name }} target-device: ${{ inputs.target-device }} runs-on: ${{ matrix.runner.name }} has-gpu: ${{ matrix.runner.type == 'gpu' }} test-options: ${{ matrix.test-config.test-options }} platform: ${{ inputs.platform }} - legate-gh-ci-tag: "v1" - cmake-preset: "" + legate-gh-ci-tag: "v1.1.4" + build-mode: "" ucx-enabled: false + upload-enabled: ${{ inputs.upload-enabled }} secrets: inherit diff --git a/cmake/versions.json b/cmake/versions.json index ae18735bf7..e9793a8aed 100644 --- a/cmake/versions.json +++ b/cmake/versions.json @@ -2,12 +2,12 @@ "packages" : { "legate_core_internal" : { "repo": "legate.core.internal", - "artifact_name": "${{ inputs.platform }}-${{ inputs.build-type }}-<>-${{ inputs.target-device }}-release-gcc-<>", + "artifact_name": "${{ inputs.platform }}-${{ inputs.build-type }}-<>-${{ inputs.target-device }}-release-<>", "version": "24.05.00", "git_url" : "https://github.com/nv-legate/legate.core.internal.git", "git_shallow": false, "always_download": false, - "git_tag" : "0e49b3bafaf3369909779ebfbc203f6971142a9c" + "git_tag" : "d1d74d822d43389c6dbbb63c6237c0253d287147" } } -} +} \ No newline at end of file diff --git a/conda/conda-build/build.sh b/conda/conda-build/build.sh index b787400903..77704eb2b7 100644 --- a/conda/conda-build/build.sh +++ b/conda/conda-build/build.sh @@ -1,5 +1,7 @@ #!/bin/bash +echo -e "\n\n--------------------- CONDA/CONDA-BUILD/BUILD.SH -----------------------\n" + # Rewrite conda's -DCMAKE_FIND_ROOT_PATH_MODE_INCLUDE=ONLY to # -DCMAKE_FIND_ROOT_PATH_MODE_INCLUDE=BOTH CMAKE_ARGS="$(echo "$CMAKE_ARGS" | sed -r "s@_INCLUDE=ONLY@_INCLUDE=BOTH@g")" @@ -7,14 +9,18 @@ CMAKE_ARGS="$(echo "$CMAKE_ARGS" | sed -r "s@_INCLUDE=ONLY@_INCLUDE=BOTH@g")" # Add our options to conda's CMAKE_ARGS CMAKE_ARGS+=" --log-level=VERBOSE --DBUILD_MARCH=haswell" +-DBUILD_SHARED_LIBS=ON +-DBUILD_MARCH=${BUILD_MARCH} +-DCMAKE_BUILD_TYPE=Release +-DCMAKE_BUILD_PARALLEL_LEVEL=${JOBS:-$(nproc --ignore=1)} +" # We rely on an environment variable to determine if we need to build cpu-only bits if [ -z "$CPU_ONLY" ]; then # cutensor, relying on the conda cutensor package CMAKE_ARGS+=" -Dcutensor_DIR=$PREFIX --DCMAKE_CUDA_ARCHITECTURES:LIST=60-real;70-real;75-real;80-real;90 +-DCMAKE_CUDA_ARCHITECTURES=RAPIDS " else # When we build without cuda, we need to provide the location of curand @@ -23,15 +29,16 @@ else " fi -# Do not compile with NDEBUG until Legion handles it without warnings -export CFLAGS="-UNDEBUG" -export CXXFLAGS="-UNDEBUG" -export CPPFLAGS="-UNDEBUG" -export CUDAFLAGS="-UNDEBUG" export CMAKE_GENERATOR=Ninja export CUDAHOSTCXX=${CXX} +export OPENSSL_DIR="$PREFIX" + +echo "Environment" +env echo "Build starting on $(date)" +CUDAFLAGS="-isystem ${PREFIX}/include -L${PREFIX}/lib" +export CUDAFLAGS cmake -S . -B build ${CMAKE_ARGS} -DCMAKE_BUILD_PARALLEL_LEVEL=$CPU_COUNT cmake --build build -j$CPU_COUNT @@ -48,6 +55,7 @@ $PYTHON -m pip install \ --no-deps \ --prefix "$PREFIX" \ --no-build-isolation \ + --upgrade \ --cache-dir "$PIP_CACHE_DIR" \ --disable-pip-version-check \ . -vv diff --git a/conda/conda-build/meta.yaml b/conda/conda-build/meta.yaml index 94102b878b..c62d4c0ac1 100644 --- a/conda/conda-build/meta.yaml +++ b/conda/conda-build/meta.yaml @@ -7,10 +7,18 @@ {# We need to have a default value for the initial pass over the recipe #} {% set gpu_enabled_bool = false %} {% endif %} +{% if upload_build == "true" %} + {% set upload_build_bool = true %} +{% elif upload_build == "false" %} + {% set upload_build_bool = false %} +{% else %} + {# We need to have a default value for the initial pass over the recipe #} + {% set upload_build_bool = false %} +{% endif %} ## The placeholder version is strictly for making two-pass conda build process. ## It should not be used for any other purpose, and this is not a default version. {% set placeholder_version = '0.0.0.dev' %} -{% set default_cuda_version = '12.0' %} +{% set default_cuda_version = '12.2' %} {% set cuda_version='.'.join(environ.get('CUDA', default_cuda_version).split('.')[:2]) %} {% set cuda_major=cuda_version.split('.')[0]|int %} {% set py_version=environ.get('CONDA_PY', '') %} @@ -53,20 +61,22 @@ build: number: {{ build_number }} missing_dso_whitelist: - '*libcuda.so*' -{% if use_local_path is not defined %} -# use git hash {% if not gpu_enabled_bool %} - string: "cuda{{ cuda_major }}_py{{ py_version }}_{{ GIT_DESCRIBE_HASH }}_{{ PKG_BUILDNUM }}_cpu" +{% set cpu_tag='_cpu' %} {% else %} - string: "cuda{{ cuda_major }}_py{{ py_version }}_{{ GIT_DESCRIBE_HASH }}_{{ PKG_BUILDNUM }}" +{% set cpu_tag='' %} {% endif %} +{% if upload_build_bool %} +{% set upload_tag='_nightly' %} {% else %} -# do not use git hash -{% if not gpu_enabled_bool %} - string: "cuda{{ cuda_major }}_py{{ py_version }}_{{ PKG_BUILDNUM }}_cpu" -{% else %} - string: "cuda{{ cuda_major }}_py{{ py_version }}_{{ PKG_BUILDNUM }}" +{% set upload_tag='' %} {% endif %} +{% if use_local_path is not defined %} +# use git hash + string: "cuda{{ cuda_major }}_py{{ py_version }}_{{ GIT_DESCRIBE_HASH }}_{{ PKG_BUILDNUM }}{{ cpu_tag }}{{ upload_tag }}" +{% else %} +# do not use git hash + string: "cuda{{ cuda_major }}_py{{ py_version }}_{{ PKG_BUILDNUM }}{{ cpu_tag }}{{ upload_tag }}" {% endif %} script_env: - SCCACHE_BUCKET @@ -110,7 +120,7 @@ requirements: - scikit-build - openblas =* =*openmp* {% if not gpu_enabled_bool %} - - legate-core ={{ core_version }} =*_cpu + - legate-core ={{ core_version }} =*_cpu* {% else %} - legate-core ={{ core_version }} - cuda-nvcc ={{ cuda_version }} @@ -132,7 +142,7 @@ requirements: - numpy {{ numpy_version }} - libopenblas =* =*openmp* {% if not gpu_enabled_bool %} - - legate-core ={{ core_version }} =*_cpu + - legate-core ={{ core_version }} =*_cpu* {% else %} - legate-core ={{ core_version }} - cuda-cudart >={{ cuda_version }},<{{ cuda_major+1 }} diff --git a/continuous_integration/scripts/build b/continuous_integration/scripts/build index 9321f04e32..f21068c9f1 100755 --- a/continuous_integration/scripts/build +++ b/continuous_integration/scripts/build @@ -1,7 +1,54 @@ #!/usr/bin/env bash set -x -# +build_release_product() { + set -xeo pipefail; + + echo "RUNNING build_release_product" + + mkdir -p /tmp/env_yaml /tmp/conda-build /tmp/out + + cp -r "${ARTIFACTS_DIR}/conda-build/legate_core" /tmp/conda-build/ + + local conda_build_args=(); + conda_build_args+=(--override-channels); + + # The channel sequence below needs to be preserved + conda_build_args+=(-c conda-forge); + conda_build_args+=(--override-channels); + conda_build_args+=(-c file:///tmp/conda-build/legate_core); + conda_build_args+=(--croot /tmp/conda-build/cunumeric); + conda_build_args+=(--numpy 1.22); + conda_build_args+=(--no-test); + conda_build_args+=(--no-verify); + conda_build_args+=(--no-build-id); + conda_build_args+=("--build-id-pat=''"); + conda_build_args+=(--no-include-recipe); + conda_build_args+=(--no-anaconda-upload); + + GPU_ENABLED=true + [ "${USE_CUDA:-}" = "OFF" ] && GPU_ENABLED=false + + UPLOAD_BUILD=true + [ "${UPLOAD_ENABLED:-}" = "OFF" ] && UPLOAD_BUILD=false + + variantOpts=$(printf "{\"gpu_enabled\": [$GPU_ENABLED], \"upload_build\": [$UPLOAD_BUILD], \"python\": [$PYTHON_VERSION]}") + + conda_build_args+=(--variants "$variantOpts") + + conda mambabuild "${conda_build_args[@]}" "${REPO_DIR}/conda/conda-build"; + + copy_release_artifacts +} + +copy_release_artifacts() { + set -xeuo pipefail; + echo Copying release artifacts + + cp -r /tmp/out "$ARTIFACTS_DIR" + cp -r /tmp/conda-build "$ARTIFACTS_DIR" + ls -lahR $ARTIFACTS_DIR +} copy_ci_artifacts() { set -xeuo pipefail; @@ -46,7 +93,7 @@ build_project() { case "$BUILD_TYPE" in ci) build_ci_product;; - release) build_release;; + release) build_release_product;; *) return 1;; esac } diff --git a/continuous_integration/scripts/build-cunumeric-conda b/continuous_integration/scripts/build-cunumeric-conda index 5544dbf15b..1a4f91c381 100755 --- a/continuous_integration/scripts/build-cunumeric-conda +++ b/continuous_integration/scripts/build-cunumeric-conda @@ -30,6 +30,9 @@ build_cunumeric_conda_package() { GPU_ENABLED=true [ "${USE_CUDA}" = "OFF" ] && GPU_ENABLED=false + UPLOAD_BUILD=true + [ "${UPLOAD_ENABLED:-}" = "OFF" ] && UPLOAD_BUILD=false + conda_build_args+=(--variants "{gpu_enabled:${GPU_ENABLED},python:${python_version}}"); rm -rf /tmp/conda-build/cunumeric; @@ -41,6 +44,9 @@ build_cunumeric_conda_package() { gpu_enabled: - "${GPU_ENABLED}" +upload_build: + - "${UPLOAD_BUILD}" + python: - "${python_version}" diff --git a/continuous_integration/scripts/make-conda-env b/continuous_integration/scripts/make-conda-env index 17ca35e418..e82838f85e 100755 --- a/continuous_integration/scripts/make-conda-env +++ b/continuous_integration/scripts/make-conda-env @@ -2,6 +2,8 @@ set -x +. conda-utils + make_ci_env() { set -xeuo pipefail yaml_file=$(find "${ARTIFACTS_DIR}" -name "environment*.yaml" | head -n 1) @@ -31,7 +33,7 @@ make_conda_env() { case "$1" in ci) make_ci_env;; - # release) make_release_env;; + release) make_release_env;; *) return 1;; esac From 5626c1fc333b91d7f7f6523caa08b46b0dc351f6 Mon Sep 17 00:00:00 2001 From: Bryan Van de Ven Date: Thu, 18 Apr 2024 10:48:04 -0700 Subject: [PATCH 190/462] remove parser set_default fixup (#166) --- test.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/test.py b/test.py index 52bced68ce..8dcda54be8 100755 --- a/test.py +++ b/test.py @@ -18,14 +18,10 @@ import sys -from legate.tester.args import parser from legate.tester.config import Config from legate.tester.test_plan import TestPlan from legate.tester.test_system import TestSystem -# quick fix for new build changes -parser.set_defaults(gtest_file=None) - if __name__ == "__main__": config = Config(sys.argv) From 43cb24ed7b0cdaee6264b82795674395a3930758 Mon Sep 17 00:00:00 2001 From: Bryan Van de Ven Date: Tue, 23 Apr 2024 09:28:13 -0700 Subject: [PATCH 191/462] specify min conda version (#171) * specify min conda version * Apply suggestions from code review Co-authored-by: Manolis Papadakis --------- Co-authored-by: Manolis Papadakis --- README.md | 21 ++++++++++++----- docs/cunumeric/source/user/installation.rst | 25 +++++++++++++++++---- 2 files changed, 37 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index cec00b0522..12c0337893 100644 --- a/README.md +++ b/README.md @@ -37,17 +37,28 @@ If you have questions, please contact us at legate(at)nvidia.com. ## Installation -cuNumeric is available [on conda](https://anaconda.org/legate/cunumeric). -Create a new environment containing cuNumeric: +cuNumeric is available from [conda](https://docs.conda.io/projects/conda/en/latest/index.html) +on the [legate channel](https://anaconda.org/legate/cunumeric). +Please make sure you have at least conda version 24.1 installed, then create +a new environment containing cuNumeric: ``` -mamba create -n myenv -c nvidia -c conda-forge -c legate cunumeric +conda create -n myenv -c nvidia -c conda-forge -c legate cunumeric ``` or install it into an existing environment: ``` -mamba install -c nvidia -c conda-forge -c legate cunumeric +conda install -c nvidia -c conda-forge -c legate cunumeric +``` + +Once installed, you can verify the installation by running one of the examples +from the cuNumeric repository, for instance: + +``` +$ legate examples/black_scholes.py +Running black scholes on 10K options... +Elapsed Time: 129.017 ms ``` Only linux-64 packages are available at the moment. @@ -59,7 +70,7 @@ installing on a machine without GPUs. You can force installation of a CPU-only package by requesting it as follows: ``` -mamba ... cunumeric=*=*_cpu +conda ... cunumeric=*=*_cpu ``` See the build instructions at https://nv-legate.github.io/cunumeric for details diff --git a/docs/cunumeric/source/user/installation.rst b/docs/cunumeric/source/user/installation.rst index 6f22dcd4c3..b06baa771d 100644 --- a/docs/cunumeric/source/user/installation.rst +++ b/docs/cunumeric/source/user/installation.rst @@ -1,17 +1,34 @@ Installation ============ -Linux-64 packages for cuNumeric are available `via conda`_: +Linux-64 packages for cuNumeric are available from +`conda `_ +on the `legate channel `_. +Please make sure you have at least conda version 24.1 installed, then create +a new environment containing cuNumeric: .. code-block:: sh - conda install -c nvidia -c conda-forge -c legate cunumeric + conda install -c nvidia -c conda-forge -c legate cunumeric + +Once installed, you can verify the installation by running one of the examples +from the cuNumeric repository, for instance: + +.. code-block:: sh + + $ legate examples/black_scholes.py + Running black scholes on 10K options... + Elapsed Time: 129.017 ms The default package contains GPU support, and is compatible with CUDA >= 11.8 (CUDA driver version >= r520), and Volta or later GPU architectures. There are also CPU-only packages available, and will be automatically selected by conda when installing on a machine without GPUs. -See :ref:`building cunumeric from source` for instructions on building cuNumeric manually. +You can force installation of a CPU-only package by requesting it as follows: -.. _via conda: https://anaconda.org/legate/cunumeric \ No newline at end of file +.. code-block:: sh + + conda ... cunumeric=*=*_cpu + +See :ref:`building cunumeric from source` for instructions on building cuNumeric manually. From 9667a2efb9e34e93b03e96817c7b2214749fde2d Mon Sep 17 00:00:00 2001 From: Manolis Papadakis Date: Wed, 24 Apr 2024 13:40:10 -0700 Subject: [PATCH 192/462] Use tblis fork that includes fixes for ARM builds (#144) --- cmake/thirdparty/get_tblis.cmake | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cmake/thirdparty/get_tblis.cmake b/cmake/thirdparty/get_tblis.cmake index b02afbd7d7..37433594c2 100644 --- a/cmake/thirdparty/get_tblis.cmake +++ b/cmake/thirdparty/get_tblis.cmake @@ -171,11 +171,11 @@ function(find_or_configure_tblis) endfunction() if(NOT DEFINED cunumeric_TBLIS_BRANCH) - set(cunumeric_TBLIS_BRANCH master) + set(cunumeric_TBLIS_BRANCH arm-build) endif() if(NOT DEFINED cunumeric_TBLIS_REPOSITORY) - set(cunumeric_TBLIS_REPOSITORY https://github.com/devinamatthews/tblis.git) + set(cunumeric_TBLIS_REPOSITORY https://github.com/nv-legate/tblis.git) endif() find_or_configure_tblis(VERSION 1.2.0 From df2e5b0255a3268d159249ca8cc9bb206bd69314 Mon Sep 17 00:00:00 2001 From: Bryan Van de Ven Date: Thu, 25 Apr 2024 15:36:41 -0700 Subject: [PATCH 193/462] Update README.md (#174) * Update README.md Omit extraneous info about forcing CPU-only installs from the README * Update README.md * Update README.md --- README.md | 16 +++++----------- 1 file changed, 5 insertions(+), 11 deletions(-) diff --git a/README.md b/README.md index 12c0337893..0f2571fe4a 100644 --- a/README.md +++ b/README.md @@ -64,17 +64,11 @@ Elapsed Time: 129.017 ms Only linux-64 packages are available at the moment. The default package contains GPU support, and is compatible with CUDA >= 11.8 -(CUDA driver version >= r520), and Volta or later GPU architectures. There are -also CPU-only packages available, and will be automatically selected when -installing on a machine without GPUs. You can force installation of a CPU-only -package by requesting it as follows: - -``` -conda ... cunumeric=*=*_cpu -``` - -See the build instructions at https://nv-legate.github.io/cunumeric for details -about building cuNumeric from source. +(driver >= 520), and Volta or later GPU architectures. There are also CPU-only +packages available, which will be automatically selected when installing on a +machine without GPUs available. See https://nv-legate.github.io/cunumeric for +details about manually forcing different install configurations, or building +cuNumeric from source. ## Usage and Execution From 012351fbff4851d4967c4aa3ef039019cf4eef82 Mon Sep 17 00:00:00 2001 From: Manolis Papadakis Date: Mon, 6 May 2024 14:31:26 -0700 Subject: [PATCH 194/462] Port Ada fix from legate.core#944 (#178) --- cmake/Modules/cuda_arch_helpers.cmake | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/cmake/Modules/cuda_arch_helpers.cmake b/cmake/Modules/cuda_arch_helpers.cmake index 9a2206f69d..2763627722 100644 --- a/cmake/Modules/cuda_arch_helpers.cmake +++ b/cmake/Modules/cuda_arch_helpers.cmake @@ -44,6 +44,9 @@ function(set_cuda_arch_from_names) if(CMAKE_CUDA_ARCHITECTURES MATCHES "ampere") list(APPEND cuda_archs 80) endif() + if(CMAKE_CUDA_ARCHITECTURES MATCHES "ada") + list(APPEND cuda_archs 89) + endif() if(CMAKE_CUDA_ARCHITECTURES MATCHES "hopper") list(APPEND cuda_archs 90) endif() @@ -86,6 +89,7 @@ function(add_cuda_architecture_defines defs) add_def_if_arch_enabled("70" "VOLTA_ARCH") add_def_if_arch_enabled("75" "TURING_ARCH") add_def_if_arch_enabled("80" "AMPERE_ARCH") + add_def_if_arch_enabled("89" "ADA_ARCH") add_def_if_arch_enabled("90" "HOPPER_ARCH") set(${defs} ${_defs} PARENT_SCOPE) From 5ca004559e1170d830f1d544bbd3b79336e93672 Mon Sep 17 00:00:00 2001 From: Bryan Van de Ven Date: Wed, 8 May 2024 14:30:48 -0700 Subject: [PATCH 195/462] Updates for Numpy 2.0 (Part 1) (#177) * add np2 flag * resolve AxisError move * np.bool_ -> bool * np.NaN -> np.nan * np.Inf -> np.inf * np.NINF -> -np.inf * don't use private np._core APIs * switch to public APIs * msort was removed entirely * don't rely on OOB integer casting * uint0 -> uintp * itemset was removed entirely * use proper parametrization for ufunc tests * give up on test_initial_list exception matching for prod * remove msort from comparison * remove extraneous singleton cases * mypy * make eager FFT error display errant dtype values * docs updates for removed np2 things * match prod exception more closely * fix fft complex inputs * don't rely on implicit OOB casting --- cunumeric/_array/array.py | 104 ++--- cunumeric/_array/flags.py | 5 - cunumeric/_array/thunk.py | 20 +- cunumeric/_module/array_joining.py | 9 +- cunumeric/_module/array_tiling.py | 15 +- cunumeric/_module/array_transpose.py | 11 +- cunumeric/_module/creation_ranges.py | 2 +- cunumeric/_module/creation_shape.py | 2 +- cunumeric/_module/indexing.py | 9 +- cunumeric/_module/logic_comparison.py | 2 +- cunumeric/_module/math_sum_prod_diff.py | 15 +- cunumeric/_module/ssc_sorting.py | 41 +- cunumeric/_module/stats_avgs_vars.py | 9 +- cunumeric/_sphinxext/_comparison_config.py | 5 - cunumeric/_thunk/_sort.py | 9 +- cunumeric/_thunk/deferred.py | 9 +- cunumeric/_thunk/eager.py | 11 +- cunumeric/_thunk/thunk.py | 2 +- cunumeric/_utils/__init__.py | 4 + cunumeric/_utils/array.py | 2 +- cunumeric/linalg/linalg.py | 21 +- cunumeric/ma/_masked_array.py | 4 +- docs/cunumeric/Makefile | 2 +- docs/cunumeric/source/api/sorting.rst | 1 - tests/integration/test_arg_reduce.py | 5 +- tests/integration/test_argsort.py | 2 +- tests/integration/test_binary_op_typing.py | 20 +- tests/integration/test_binary_ufunc.py | 418 ++++++++++++--------- tests/integration/test_extract.py | 2 +- tests/integration/test_fft_c2c.py | 31 +- tests/integration/test_fft_c2r.py | 32 +- tests/integration/test_fill.py | 2 +- tests/integration/test_flip.py | 7 +- tests/integration/test_index_routines.py | 5 +- tests/integration/test_itemset.py | 5 + tests/integration/test_moveaxis.py | 9 +- tests/integration/test_msort.py | 4 +- tests/integration/test_nonzero.py | 3 +- tests/integration/test_prod.py | 17 +- tests/integration/test_random_creation.py | 2 +- tests/integration/test_reduction.py | 5 +- tests/integration/test_repeat.py | 3 +- tests/integration/test_singleton_access.py | 22 +- tests/integration/test_sort.py | 2 +- tests/integration/test_sort_complex.py | 2 +- tests/integration/test_squeeze.py | 5 +- tests/integration/test_transpose.py | 4 +- tests/integration/utils/utils.py | 6 + 48 files changed, 515 insertions(+), 412 deletions(-) diff --git a/cunumeric/_array/array.py b/cunumeric/_array/array.py index 986eabe410..8af5889372 100644 --- a/cunumeric/_array/array.py +++ b/cunumeric/_array/array.py @@ -22,14 +22,9 @@ import numpy as np from legate.core import Field, LogicalArray, Scalar from legate.core.utils import OrderedSet -from numpy.core.multiarray import ( # type: ignore [attr-defined] - normalize_axis_index, -) -from numpy.core.numeric import ( # type: ignore [attr-defined] - normalize_axis_tuple, -) from .. import _ufunc +from .._utils import is_np2 from .._utils.array import ( calculate_volume, max_identity, @@ -62,6 +57,13 @@ tuple_pop, ) +if is_np2: + from numpy.lib.array_utils import normalize_axis_index # type: ignore + from numpy.lib.array_utils import normalize_axis_tuple # type: ignore +else: + from numpy.core.multiarray import normalize_axis_index # type: ignore + from numpy.core.numeric import normalize_axis_tuple # type: ignore + if TYPE_CHECKING: from pathlib import Path @@ -940,7 +942,7 @@ def __invert__(self) -> ndarray: Multiple GPUs, Multiple CPUs """ - if self.dtype == np.bool_: + if self.dtype == bool: # Boolean values are special, just do logical NOT return _ufunc.logical_not(self) else: @@ -2698,46 +2700,48 @@ def item(self, *args: Any) -> Any: assert result.shape == () return result._thunk.__numpy_array__() - def itemset(self, *args: Any) -> None: - """a.itemset(*args) - - Insert scalar into an array (scalar is cast to array's dtype, - if possible) - - There must be at least 1 argument, and define the last argument - as *item*. Then, ``a.itemset(*args)`` is equivalent to but faster - than ``a[args] = item``. The item should be a scalar value and `args` - must select a single item in the array `a`. - - Parameters - ---------- - \\*args : - If one argument: a scalar, only used in case `a` is of size 1. - If two arguments: the last argument is the value to be set - and must be a scalar, the first argument specifies a single array - element location. It is either an int or a tuple. - - Notes - ----- - Compared to indexing syntax, `itemset` provides some speed increase - for placing a scalar into a particular location in an `ndarray`, - if you must do this. However, generally this is discouraged: - among other problems, it complicates the appearance of the code. - Also, when using `itemset` (and `item`) inside a loop, be sure - to assign the methods to a local variable to avoid the attribute - look-up at each loop iteration. - - Availability - -------- - Multiple GPUs, Multiple CPUs - - """ - if len(args) == 0: - raise KeyError("itemset() requires at least one argument") - value = args[-1] - args = args[:-1] - key = self._convert_singleton_key(args) - self[key] = value + if not is_np2: + + def itemset(self, *args: Any) -> None: + """a.itemset(*args) + + Insert scalar into an array (scalar is cast to array's dtype, + if possible) + + There must be at least 1 argument, and define the last argument + as *item*. Then, ``a.itemset(*args)`` is equivalent to but faster + than ``a[args] = item``. The item should be a scalar value and + `args` must select a single item in the array `a`. + + Parameters + ---------- + \\*args : + If one argument: a scalar, only used in case `a` is of size 1. + If two arguments: the last argument is the value to be set + and must be a scalar, the first argument specifies a single + array element location. It is either an int or a tuple. + + Notes + ----- + Compared to indexing syntax, `itemset` provides some speed increase + for placing a scalar into a particular location in an `ndarray`, + if you must do this. However, generally this is discouraged: + among other problems, it complicates the appearance of the code. + Also, when using `itemset` (and `item`) inside a loop, be sure + to assign the methods to a local variable to avoid the attribute + look-up at each loop iteration. + + Availability + -------- + Multiple GPUs, Multiple CPUs + + """ + if len(args) == 0: + raise KeyError("itemset() requires at least one argument") + value = args[-1] + args = args[:-1] + key = self._convert_singleton_key(args) + self[key] = value @add_boilerplate() def max( @@ -2899,7 +2903,7 @@ def _nanmean( keepdims: bool = False, where: ndarray | None = None, ) -> ndarray: - if np.issubdtype(dtype, np.integer) or np.issubdtype(dtype, np.bool_): + if np.issubdtype(dtype, np.integer) or np.issubdtype(dtype, bool): return self.mean( axis=axis, dtype=dtype, out=out, keepdims=keepdims, where=where ) @@ -3135,7 +3139,7 @@ def prod( Multiple GPUs, Multiple CPUs """ - if self.dtype.type == np.bool_: + if self.dtype.type == bool: temp = ndarray( shape=self.shape, dtype=np.dtype(np.int32), @@ -3505,7 +3509,7 @@ def sum( Multiple GPUs, Multiple CPUs """ - if self.dtype.type == np.bool_: + if self.dtype.type == bool: temp = ndarray( shape=self.shape, dtype=np.dtype(np.int32), diff --git a/cunumeric/_array/flags.py b/cunumeric/_array/flags.py index 3dd6be047f..d0a2034e9d 100644 --- a/cunumeric/_array/flags.py +++ b/cunumeric/_array/flags.py @@ -16,8 +16,6 @@ from typing import TYPE_CHECKING, Any -import numpy as np - if TYPE_CHECKING: from .array import ndarray @@ -47,9 +45,6 @@ def __repr__(self) -> str: def __eq__(self, other: Any) -> bool: flags = ("C", "F", "O", "W", "A", "X") - if not isinstance(other, (flagsobj, np.core.multiarray.flagsobj)): - return False - return all(self[f] == other[f] for f in flags) # type: ignore [index] def __getattr__(self, name: str) -> Any: diff --git a/cunumeric/_array/thunk.py b/cunumeric/_array/thunk.py index 535c4a6169..965cccffed 100644 --- a/cunumeric/_array/thunk.py +++ b/cunumeric/_array/thunk.py @@ -18,13 +18,8 @@ import numpy as np from legate.core import Scalar -from numpy.core.multiarray import ( # type: ignore [attr-defined] - normalize_axis_index, -) -from numpy.core.numeric import ( # type: ignore [attr-defined] - normalize_axis_tuple, -) +from .._utils import is_np2 from ..config import ( BinaryOpCode, ConvertCode, @@ -35,6 +30,13 @@ from ..types import NdShape from .util import broadcast_where, find_common_type +if is_np2: + from numpy.lib.array_utils import normalize_axis_index # type: ignore + from numpy.lib.array_utils import normalize_axis_tuple # type: ignore +else: + from numpy.core.multiarray import normalize_axis_index # type: ignore + from numpy.core.numeric import normalize_axis_tuple # type: ignore + if TYPE_CHECKING: import numpy.typing as npt @@ -51,7 +53,7 @@ def get_where_thunk( return where if ( not isinstance(where, ndarray) - or where.dtype != np.bool_ + or where.dtype != bool or where.shape != out_shape ): raise RuntimeError("should have converted this earlier") @@ -257,7 +259,7 @@ def perform_binary_reduction( args = (one, two) # We only handle bool types here for now - assert dtype is not None and dtype == np.dtype(np.bool_) + assert dtype is not None and dtype == np.dtype(bool) # Collapsing down to a single value in this case # Check to see if we need to broadcast between inputs @@ -286,7 +288,7 @@ def perform_where(mask: ndarray, one: ndarray, two: ndarray) -> ndarray: args = (mask, one, two) - mask = mask._maybe_convert(np.dtype(np.bool_), args) + mask = mask._maybe_convert(np.dtype(bool), args) common_type = find_common_type(one, two) one = one._maybe_convert(common_type, args) diff --git a/cunumeric/_module/array_joining.py b/cunumeric/_module/array_joining.py index ed5c57e894..13956a7aad 100644 --- a/cunumeric/_module/array_joining.py +++ b/cunumeric/_module/array_joining.py @@ -18,14 +18,17 @@ from typing import TYPE_CHECKING, Any, Sequence import numpy as np -from numpy.core.multiarray import ( # type: ignore [attr-defined] - normalize_axis_index, -) from .._array.array import ndarray from .._array.util import convert_to_cunumeric_ndarray +from .._utils import is_np2 from .array_dimension import _atleast_nd +if is_np2: + from numpy.lib.array_utils import normalize_axis_index # type: ignore +else: + from numpy.core.multiarray import normalize_axis_index # type: ignore + if TYPE_CHECKING: import numpy.typing as npt diff --git a/cunumeric/_module/array_tiling.py b/cunumeric/_module/array_tiling.py index d118681f92..72e5287bc2 100644 --- a/cunumeric/_module/array_tiling.py +++ b/cunumeric/_module/array_tiling.py @@ -17,20 +17,27 @@ from typing import TYPE_CHECKING, Any, Sequence, cast import numpy as np -from numpy.core.multiarray import ( # type: ignore [attr-defined] - normalize_axis_index, -) from .._array.array import ndarray from .._array.util import add_boilerplate, convert_to_cunumeric_ndarray +from .._utils import is_np2 from ..runtime import runtime from .creation_shape import full +if is_np2: + from numpy.lib.array_utils import normalize_axis_index # type: ignore +else: + from numpy.core.multiarray import normalize_axis_index # type: ignore + if TYPE_CHECKING: import numpy.typing as npt from ..types import NdShape +if is_np2: + from numpy.exceptions import AxisError # type: ignore +else: + from numpy import AxisError # type: ignore _builtin_max = max @@ -150,7 +157,7 @@ def repeat(a: ndarray, repeats: Any, axis: int | None = None) -> ndarray: # when array is a scalar if np.ndim(a) == 0: if axis is not None and axis != 0 and axis != -1: - raise np.AxisError( + raise AxisError( f"axis {axis} is out of bounds for array of dimension 0" ) if np.ndim(repeats) == 0: diff --git a/cunumeric/_module/array_transpose.py b/cunumeric/_module/array_transpose.py index 27337b33d4..f16f1963f8 100644 --- a/cunumeric/_module/array_transpose.py +++ b/cunumeric/_module/array_transpose.py @@ -16,11 +16,14 @@ from typing import TYPE_CHECKING, Sequence -from numpy.core.numeric import ( # type: ignore [attr-defined] - normalize_axis_tuple, -) - from .._array.util import add_boilerplate +from .._utils import is_np2 + +if is_np2: + from numpy.lib.array_utils import normalize_axis_tuple # type: ignore +else: + from numpy.core.numeric import normalize_axis_tuple # type: ignore + if TYPE_CHECKING: from .._array.array import ndarray diff --git a/cunumeric/_module/creation_ranges.py b/cunumeric/_module/creation_ranges.py index 49be45aa44..2c1b849bb7 100644 --- a/cunumeric/_module/creation_ranges.py +++ b/cunumeric/_module/creation_ranges.py @@ -235,7 +235,7 @@ def linspace( else: # sequences with 0 items or 1 item with endpoint=True (i.e. div <= 0) # have an undefined step - step = np.NaN + step = np.nan if delta.ndim == 0: y *= delta else: diff --git a/cunumeric/_module/creation_shape.py b/cunumeric/_module/creation_shape.py index fecb4786c1..b208bc57bd 100644 --- a/cunumeric/_module/creation_shape.py +++ b/cunumeric/_module/creation_shape.py @@ -396,6 +396,6 @@ def full_like( else: dtype = a.dtype result = empty_like(a, dtype=dtype, shape=shape) - val = np.array(value, dtype=result.dtype) + val = np.array(value).astype(dtype) result._thunk.fill(val) return result diff --git a/cunumeric/_module/indexing.py b/cunumeric/_module/indexing.py index 96521fa72b..20d4c64c35 100644 --- a/cunumeric/_module/indexing.py +++ b/cunumeric/_module/indexing.py @@ -17,9 +17,6 @@ from typing import TYPE_CHECKING, Any, Sequence import numpy as np -from numpy.core.multiarray import ( # type: ignore [attr-defined] - normalize_axis_index, -) from .._array.array import ndarray from .._array.util import ( @@ -27,6 +24,7 @@ check_writeable, convert_to_cunumeric_ndarray, ) +from .._utils import is_np2 from .._utils.coverage import is_implemented from ..runtime import runtime from ..types import NdShape @@ -39,6 +37,11 @@ from .ssc_counting import count_nonzero from .ssc_searching import nonzero +if is_np2: + from numpy.lib.array_utils import normalize_axis_index # type: ignore +else: + from numpy.core.multiarray import normalize_axis_index # type: ignore + if TYPE_CHECKING: from typing import Callable diff --git a/cunumeric/_module/logic_comparison.py b/cunumeric/_module/logic_comparison.py index 9e8b25551d..dad4782027 100644 --- a/cunumeric/_module/logic_comparison.py +++ b/cunumeric/_module/logic_comparison.py @@ -197,5 +197,5 @@ def array_equal( if a1.shape != a2.shape: return False return perform_binary_reduction( - BinaryOpCode.EQUAL, a1, a2, dtype=np.dtype(np.bool_) + BinaryOpCode.EQUAL, a1, a2, dtype=np.dtype(bool) ) diff --git a/cunumeric/_module/math_sum_prod_diff.py b/cunumeric/_module/math_sum_prod_diff.py index 87558c4d05..8d0688b23d 100644 --- a/cunumeric/_module/math_sum_prod_diff.py +++ b/cunumeric/_module/math_sum_prod_diff.py @@ -17,15 +17,13 @@ from typing import TYPE_CHECKING, Any import numpy as np -from numpy.core.multiarray import ( # type: ignore [attr-defined] - normalize_axis_index, -) from .._array.thunk import perform_scan, perform_unary_reduction from .._array.util import add_boilerplate from .._ufunc.comparison import not_equal from .._ufunc.floating import isnan from .._ufunc.math import add, multiply, subtract +from .._utils import is_np2 from ..config import ScanCode, UnaryRedCode from ..settings import settings as cunumeric_settings from ._unary_red_utils import get_non_nan_unary_red_code @@ -35,6 +33,11 @@ from .indexing import putmask from .logic_truth import all, any +if is_np2: + from numpy.lib.array_utils import normalize_axis_index # type: ignore +else: + from numpy.core.multiarray import normalize_axis_index # type: ignore + if TYPE_CHECKING: from .._array.array import ndarray @@ -109,6 +112,10 @@ def prod( -------- Multiple GPUs, Multiple CPUs """ + if isinstance(initial, list): + exc = TypeError if is_np2 else ValueError # type: ignore [unreachable] + raise exc("initial should not be a list") + return multiply.reduce( a, axis=axis, @@ -1080,7 +1087,7 @@ def diff( slice1 = tuple(slice1l) slice2 = tuple(slice2l) - op = not_equal if a.dtype == np.bool_ else subtract + op = not_equal if a.dtype == bool else subtract for _ in range(n): a = op(a[slice1], a[slice2]) diff --git a/cunumeric/_module/ssc_sorting.py b/cunumeric/_module/ssc_sorting.py index 19add154f2..1ee86e0d02 100644 --- a/cunumeric/_module/ssc_sorting.py +++ b/cunumeric/_module/ssc_sorting.py @@ -20,6 +20,7 @@ from .._array.array import ndarray from .._array.util import add_boilerplate +from .._utils import is_np2 if TYPE_CHECKING: from ..types import SelectKind, SortType @@ -71,30 +72,32 @@ def argsort( return result -def msort(a: ndarray) -> ndarray: - """ +if not is_np2: - Returns a sorted copy of an array sorted along the first axis. + def msort(a: ndarray) -> ndarray: + """ - Parameters - ---------- - a : array_like - Input array. + Returns a sorted copy of an array sorted along the first axis. - Returns - ------- - out : ndarray - Sorted array with same dtype and shape as `a`. + Parameters + ---------- + a : array_like + Input array. - See Also - -------- - numpy.msort + Returns + ------- + out : ndarray + Sorted array with same dtype and shape as `a`. - Availability - -------- - Multiple GPUs, Multiple CPUs - """ - return sort(a, axis=0) + See Also + -------- + numpy.msort + + Availability + -------- + Multiple GPUs, Multiple CPUs + """ + return sort(a, axis=0) @add_boilerplate("a") diff --git a/cunumeric/_module/stats_avgs_vars.py b/cunumeric/_module/stats_avgs_vars.py index 475e99cb11..662683d2a1 100644 --- a/cunumeric/_module/stats_avgs_vars.py +++ b/cunumeric/_module/stats_avgs_vars.py @@ -18,15 +18,18 @@ from typing import TYPE_CHECKING, Any import numpy as np -from numpy.core.numeric import ( # type: ignore [attr-defined] - normalize_axis_tuple, -) from .._array.array import ndarray from .._array.util import add_boilerplate +from .._utils import is_np2 from .creation_shape import full from .logic_truth import any +if is_np2: + from numpy.lib.array_utils import normalize_axis_tuple # type: ignore +else: + from numpy.core.numeric import normalize_axis_tuple # type: ignore + if TYPE_CHECKING: import numpy.typing as npt diff --git a/cunumeric/_sphinxext/_comparison_config.py b/cunumeric/_sphinxext/_comparison_config.py index dc2f71d6b9..59667f1c86 100644 --- a/cunumeric/_sphinxext/_comparison_config.py +++ b/cunumeric/_sphinxext/_comparison_config.py @@ -136,7 +136,6 @@ class SectionConfig: "asarray_chkfinite", "asarray", "ascontiguousarray", - "asfarray", "asfortranarray", "asmatrix", "atleast_1d", @@ -218,7 +217,6 @@ class SectionConfig: "identity", "linspace", "logspace", - "mat", "meshgrid", "ones_like", "ones", @@ -252,7 +250,6 @@ class SectionConfig: "savez_compressed", "savez", "set_printoptions", - "set_string_function", ) IO_ND = ("tofile", "tolist") @@ -283,7 +280,6 @@ class SectionConfig: "prod", "real_if_close", "real", - "round_", "sinc", "sum", "trapz", @@ -300,7 +296,6 @@ class SectionConfig: "extract", "flatnonzero", "lexsort", - "msort", "nanargmax", "nanargmin", "nonzero", diff --git a/cunumeric/_thunk/_sort.py b/cunumeric/_thunk/_sort.py index e194bd3f4e..048c847c96 100644 --- a/cunumeric/_thunk/_sort.py +++ b/cunumeric/_thunk/_sort.py @@ -17,13 +17,16 @@ from typing import TYPE_CHECKING, cast from legate.core import get_legate_runtime, types as ty -from numpy.core.multiarray import ( # type: ignore [attr-defined] - normalize_axis_index, -) +from .._utils import is_np2 from ..config import CuNumericOpCode from ..runtime import runtime +if is_np2: + from numpy.lib.array_utils import normalize_axis_index # type: ignore +else: + from numpy.core.multiarray import normalize_axis_index # type: ignore + if TYPE_CHECKING: from .._thunk.deferred import DeferredArray diff --git a/cunumeric/_thunk/deferred.py b/cunumeric/_thunk/deferred.py index f47ce47abb..007d90be4c 100644 --- a/cunumeric/_thunk/deferred.py +++ b/cunumeric/_thunk/deferred.py @@ -45,10 +45,8 @@ scale, ) from legate.core.utils import OrderedSet -from numpy.core.numeric import ( # type: ignore [attr-defined] - normalize_axis_tuple, -) +from .._utils import is_np2 from .._utils.array import ( is_advanced_indexing, max_identity, @@ -72,6 +70,11 @@ from ._sort import sort_deferred from .thunk import NumPyThunk +if is_np2: + from numpy.lib.array_utils import normalize_axis_tuple # type: ignore +else: + from numpy.core.numeric import normalize_axis_tuple # type: ignore + if TYPE_CHECKING: import numpy.typing as npt diff --git a/cunumeric/_thunk/eager.py b/cunumeric/_thunk/eager.py index 3eab040607..ea89440f71 100644 --- a/cunumeric/_thunk/eager.py +++ b/cunumeric/_thunk/eager.py @@ -19,6 +19,7 @@ import numpy as np from legate.core import Scalar +from .._utils import is_np2 from .._utils.array import is_advanced_indexing from ..config import ( FFT_C2R, @@ -375,7 +376,13 @@ def fft( elif res.dtype == np.float64: self.array[:] = res.astype(np.float32) else: - raise RuntimeError("Unsupported data type in eager FFT") + if not is_np2: + raise RuntimeError( + f"Unsupported data type {res.dtype!r} in eager FFT" + ) + else: + self.array[:] = res + else: self.array[:] = res @@ -458,7 +465,7 @@ def reshape(self, newshape: NdShape, order: OrderType) -> NumPyThunk: self.children.append(result) return result - def squeeze(self, axis: int | None) -> NumPyThunk: + def squeeze(self, axis: int | tuple[int, ...] | None) -> NumPyThunk: if self.deferred is not None: return self.deferred.squeeze(axis) # See https://github.com/numpy/numpy/issues/22019 diff --git a/cunumeric/_thunk/thunk.py b/cunumeric/_thunk/thunk.py index 56cda7cf9c..97c382780b 100644 --- a/cunumeric/_thunk/thunk.py +++ b/cunumeric/_thunk/thunk.py @@ -134,7 +134,7 @@ def reshape(self, newshape: NdShape, order: OrderType) -> NumPyThunk: ... @abstractmethod - def squeeze(self, axis: int | None) -> NumPyThunk: + def squeeze(self, axis: int | tuple[int, ...] | None) -> NumPyThunk: ... @abstractmethod diff --git a/cunumeric/_utils/__init__.py b/cunumeric/_utils/__init__.py index 31d8d448c4..626ef7aae5 100644 --- a/cunumeric/_utils/__init__.py +++ b/cunumeric/_utils/__init__.py @@ -13,3 +13,7 @@ # limitations under the License. # from __future__ import annotations + +import numpy as np + +is_np2 = np.lib.NumpyVersion(np.__version__) >= "2.0.0b1" diff --git a/cunumeric/_utils/array.py b/cunumeric/_utils/array.py index a764367a6e..a706bee0fe 100644 --- a/cunumeric/_utils/array.py +++ b/cunumeric/_utils/array.py @@ -23,7 +23,7 @@ from ..types import NdShape SUPPORTED_DTYPES = { - np.dtype(np.bool_): ty.bool_, + np.dtype(bool): ty.bool_, np.dtype(np.int8): ty.int8, np.dtype(np.int16): ty.int16, np.dtype(np.int32): ty.int32, diff --git a/cunumeric/linalg/linalg.py b/cunumeric/linalg/linalg.py index e6d4de789d..06e0042c82 100644 --- a/cunumeric/linalg/linalg.py +++ b/cunumeric/linalg/linalg.py @@ -17,12 +17,19 @@ from typing import TYPE_CHECKING, Sequence import numpy as np -from numpy.core.multiarray import ( # type: ignore [attr-defined] - normalize_axis_index, -) -from numpy.core.numeric import ( # type: ignore [attr-defined] - normalize_axis_tuple, -) + +from .._utils import is_np2 + +if is_np2: + from numpy.lib.array_utils import normalize_axis_index # type: ignore + from numpy.lib.array_utils import normalize_axis_tuple # type: ignore +else: + from numpy.core.multiarray import ( # type: ignore + normalize_axis_index, + ) + from numpy.core.numeric import ( # type: ignore + normalize_axis_tuple, + ) from .._array.util import add_boilerplate, convert_to_cunumeric_ndarray from .._module import dot, empty_like, eye, matmul, ndarray @@ -360,7 +367,7 @@ def _multi_dot_matrix_chain_order( for l_ in range(1, n): for i in range(n - l_): j = i + l_ - m[i, j] = np.Inf + m[i, j] = np.inf for k in range(i, j): q = m[i, k] + m[k + 1, j] + p[i] * p[k + 1] * p[j + 1] if q < m[i, j]: diff --git a/cunumeric/ma/_masked_array.py b/cunumeric/ma/_masked_array.py index 6df3763ce7..7e1e1086ec 100644 --- a/cunumeric/ma/_masked_array.py +++ b/cunumeric/ma/_masked_array.py @@ -37,7 +37,7 @@ "__array_wrap__", } -MaskType = _np.bool_ +MaskType = bool nomask = MaskType(0) @@ -51,7 +51,7 @@ def __new__(cls: Type[Any], *args: Any, **kw: Any) -> MaskedArray: def __init__( self, data: Any = None, - mask: _np.bool_ = nomask, + mask: bool = nomask, dtype: npt.DTypeLike | None = None, copy: bool = False, subok: bool = True, diff --git a/docs/cunumeric/Makefile b/docs/cunumeric/Makefile index 2ba1263b84..e961077a97 100644 --- a/docs/cunumeric/Makefile +++ b/docs/cunumeric/Makefile @@ -18,7 +18,7 @@ # You can set these variables from the command line, and also # from the environment for the first two. -SPHINXOPTS ?= -v -W +SPHINXOPTS ?= -v PARALLEL_BUILD ?= 1 SPHINXBUILD ?= $(shell which sphinx-build) SOURCEDIR = source diff --git a/docs/cunumeric/source/api/sorting.rst b/docs/cunumeric/source/api/sorting.rst index 86d8e65dc0..7cbdcb9485 100644 --- a/docs/cunumeric/source/api/sorting.rst +++ b/docs/cunumeric/source/api/sorting.rst @@ -11,7 +11,6 @@ Sorting argpartition argsort - msort partition sort sort_complex diff --git a/tests/integration/test_arg_reduce.py b/tests/integration/test_arg_reduce.py index 4fa422726a..9d30e3d3f0 100644 --- a/tests/integration/test_arg_reduce.py +++ b/tests/integration/test_arg_reduce.py @@ -16,6 +16,7 @@ import numpy as np import pytest from legate.core import LEGATE_MAX_DIM +from utils.utils import AxisError import cunumeric as num @@ -57,7 +58,7 @@ def test_axis_outofbound(self, func_name): func = getattr(num, func_name) msg = r"out of bounds" - with pytest.raises(np.AxisError, match=msg): + with pytest.raises(AxisError, match=msg): func(in_num, axis=ndim + 1) @pytest.mark.parametrize("func_name", ARG_FUNCS) @@ -68,7 +69,7 @@ def test_axis_negative(self, func_name): func = getattr(num, func_name) msg = r"out of bounds" - with pytest.raises(np.AxisError, match=msg): + with pytest.raises(AxisError, match=msg): func(in_num, axis=-(ndim + 1)) @pytest.mark.parametrize("func_name", ARG_FUNCS) diff --git a/tests/integration/test_argsort.py b/tests/integration/test_argsort.py index 656e22b808..e9b858dde1 100644 --- a/tests/integration/test_argsort.py +++ b/tests/integration/test_argsort.py @@ -68,7 +68,7 @@ class TestArgSort(object): def test_arr_none(self): res_np = np.argsort( None - ) # numpy.AxisError: axis -1 is out of bounds for array of dimension 0 + ) # AxisError: axis -1 is out of bounds for array of dimension 0 res_num = num.argsort( None ) # AttributeError: 'NoneType' object has no attribute 'shape' diff --git a/tests/integration/test_binary_op_typing.py b/tests/integration/test_binary_op_typing.py index 5cb7ee8f56..3d7eae4a3b 100644 --- a/tests/integration/test_binary_op_typing.py +++ b/tests/integration/test_binary_op_typing.py @@ -65,8 +65,8 @@ def generate_array_array_cases(): for lhs_value, rhs_value in product(SCALAR_VALUES, SCALAR_VALUES): try: - lhs_np = np.array(lhs_value, dtype=lhs_type) - rhs_np = np.array(rhs_value, dtype=rhs_type) + lhs_np = np.array(lhs_value).astype(lhs_type) + rhs_np = np.array(rhs_value).astype(rhs_type) lhs_num = num.array(lhs_np) rhs_num = num.array(rhs_np) yield (lhs_np, rhs_np, lhs_num, rhs_num) @@ -94,8 +94,8 @@ def generate_array_scalar_cases(): for rhs_type in TYPES[idx:]: for array, scalar in product(ARRAY_VALUES, SCALAR_VALUES): try: - lhs_np = np.array(array, dtype=lhs_type) - rhs_np = np.array(scalar, dtype=rhs_type) + lhs_np = np.array(array).astype(lhs_type) + rhs_np = np.array(scalar).astype(rhs_type) lhs_num = num.array(lhs_np) rhs_num = num.array(rhs_np) yield (lhs_np, rhs_np, lhs_num, rhs_num) @@ -103,8 +103,8 @@ def generate_array_scalar_cases(): pass try: - lhs_np = np.array(scalar, dtype=lhs_type) - rhs_np = np.array(array, dtype=rhs_type) + lhs_np = np.array(scalar).astype(lhs_type) + rhs_np = np.array(array).astype(rhs_type) lhs_num = num.array(lhs_np) rhs_num = num.array(rhs_np) yield (lhs_np, rhs_np, lhs_num, rhs_num) @@ -121,12 +121,12 @@ def test_array_array(lhs_np, rhs_np, lhs_num, rhs_num): out_np = np.add(lhs_np, rhs_np) out_num = num.add(lhs_num, rhs_num) - assert out_np.dtype == out_num.dtype - print(f"LHS {lhs_np}") print(f"RHS {rhs_np}") print(f"NumPy type: {out_np.dtype}, cuNumeric type: {out_num.dtype}") + assert out_np.dtype == out_num.dtype + @pytest.mark.parametrize( "lhs_np, rhs_np, lhs_num, rhs_num", generate_array_scalar_cases(), ids=str @@ -137,12 +137,12 @@ def test_array_scalar(lhs_np, rhs_np, lhs_num, rhs_num): out_np = np.add(lhs_np, rhs_np) out_num = num.add(lhs_num, rhs_num) - assert out_np.dtype == out_num.dtype - print(f"LHS {lhs_np}") print(f"RHS {rhs_np}") print(f"NumPy type: {out_np.dtype}, cuNumeric type: {out_num.dtype}") + assert out_np.dtype == out_num.dtype + if __name__ == "__main__": import sys diff --git a/tests/integration/test_binary_ufunc.py b/tests/integration/test_binary_ufunc.py index cf30a1f4d4..5d7d8fa457 100644 --- a/tests/integration/test_binary_ufunc.py +++ b/tests/integration/test_binary_ufunc.py @@ -14,7 +14,6 @@ # import argparse -from itertools import product import numpy as np import pytest @@ -43,196 +42,253 @@ def check_result(op, in_np, out_np, out_num): assert False -def check_ops(ops, in_np, out_dtype="D"): +def check_op(op, in_np, out_dtype="D"): in_num = tuple(num.array(arr) for arr in in_np) - for op in ops: - if op.isidentifier(): - op_np = getattr(np, op) - op_num = getattr(num, op) - assert op_np.nout == 1 + if op.isidentifier(): + op_np = getattr(np, op) + op_num = getattr(num, op) + assert op_np.nout == 1 - out_np = op_np(*in_np) - out_num = op_num(*in_num) + out_np = op_np(*in_np) + out_num = op_num(*in_num) - check_result(op, in_np, out_np, out_num) + check_result(op, in_np, out_np, out_num) - out_np = np.empty(out_np.shape, dtype=out_dtype) - out_num = num.empty(out_num.shape, dtype=out_dtype) - op_np(*in_np, out=out_np) - op_num(*in_num, out=out_num) + out_np = np.empty(out_np.shape, dtype=out_dtype) + out_num = num.empty(out_num.shape, dtype=out_dtype) + op_np(*in_np, out=out_np) + op_num(*in_num, out=out_num) - check_result(op, in_np, out_np, out_num) + check_result(op, in_np, out_np, out_num) - # Ask cuNumeric to produce outputs to NumPy ndarrays - out_num = np.empty(out_np.shape, dtype=out_dtype) - op_num(*in_num, out=out_num) + # Ask cuNumeric to produce outputs to NumPy ndarrays + out_num = np.empty(out_np.shape, dtype=out_dtype) + op_num(*in_num, out=out_num) - check_result(op, in_np, out_np, out_num) + check_result(op, in_np, out_np, out_num) - else: - # Doing it this way instead of invoking the dunders directly, to - # avoid having to select the right version, __add__ vs __radd__, - # when one isn't supported, e.g. for scalar.__add__(array) - - out_np = eval(f"in_np[0] {op} in_np[1]") - out_num = eval(f"in_num[0] {op} in_num[1]") - - check_result(op, in_np, out_np, out_num) - - out_np = np.ones_like(out_np) - out_num = num.ones_like(out_num) - exec(f"out_np {op}= in_np[0]") - exec(f"out_num {op}= in_num[0]") - - check_result(op, in_np, out_np, out_num) - - out_num = np.ones_like(out_np) - exec(f"out_num {op}= in_num[0]") - - check_result(op, in_np, out_np, out_num) - - -def test_all(): - # TODO: right now we will simply check if the operations work - # for some boring inputs. For some of these, we will want to - # test corner cases in the future. - - # TODO: matmul, @ - - # Math operations - ops = [ - "*", - "+", - "-", - "/", - "add", - # "divmod", - "equal", - "fmax", - "fmin", - "greater", - "greater_equal", - # "heaviside", - # "ldexp", - "less", - "less_equal", - "logical_and", - "logical_or", - "logical_xor", - "maximum", - "minimum", - "multiply", - "not_equal", - "subtract", - "true_divide", - ] - - # We want to test array-array, array-scalar, and scalar-array cases - arrs = ( - np.random.randint(3, 10, size=(4, 5)).astype("I"), - np.random.uniform(size=(4, 5)).astype("e"), - np.random.uniform(size=(4, 5)).astype("f"), - np.random.uniform(size=(4, 5)).astype("d"), - np.random.uniform(size=(4, 5)).astype("F"), - ) + else: + # Doing it this way instead of invoking the dunders directly, to + # avoid having to select the right version, __add__ vs __radd__, + # when one isn't supported, e.g. for scalar.__add__(array) + + out_np = eval(f"in_np[0] {op} in_np[1]") + out_num = eval(f"in_num[0] {op} in_num[1]") + + check_result(op, in_np, out_np, out_num) + + out_np = np.ones_like(out_np) + out_num = num.ones_like(out_num) + exec(f"out_np {op}= in_np[0]") + exec(f"out_num {op}= in_num[0]") + + check_result(op, in_np, out_np, out_num) + + out_num = np.ones_like(out_np) + exec(f"out_num {op}= in_num[0]") + + check_result(op, in_np, out_np, out_num) + + +# TODO: right now we will simply check if the operations work +# for some boring inputs. For some of these, we will want to +# test corner cases in the future. + +# TODO: matmul, @ + +# Math operations +math_ops = [ + "*", + "+", + "-", + "/", + "add", + # "divmod", + "equal", + "fmax", + "fmin", + "greater", + "greater_equal", + # "heaviside", + # "ldexp", + "less", + "less_equal", + "logical_and", + "logical_or", + "logical_xor", + "maximum", + "minimum", + "multiply", + "not_equal", + "subtract", + "true_divide", +] + +# We want to test array-array, array-scalar, and scalar-array cases +arrs = ( + np.random.randint(3, 10, size=(4, 5)).astype("I"), + np.random.uniform(size=(4, 5)).astype("e"), + np.random.uniform(size=(4, 5)).astype("f"), + np.random.uniform(size=(4, 5)).astype("d"), + np.random.uniform(size=(4, 5)).astype("F"), +) + +scalars = ( + np.uint64(2), + np.int64(-3), + np.random.randn(1)[0], + np.complex64(1 + 1j), +) + + +@pytest.mark.parametrize("op", math_ops) +@pytest.mark.parametrize("arr1", arrs) +@pytest.mark.parametrize("arr2", arrs) +def test_math_ops_arr_arr(op, arr1, arr2) -> None: + check_op(op, (arr1, arr2)) + + +@pytest.mark.parametrize("op", math_ops) +@pytest.mark.parametrize("arr", arrs) +@pytest.mark.parametrize("scalar", scalars) +def test_math_ops_arr_scalar(op, arr, scalar) -> None: + check_op(op, (arr, scalar)) + check_op(op, (scalar, arr)) + + +@pytest.mark.parametrize("op", math_ops) +@pytest.mark.parametrize("scalar1", scalars) +@pytest.mark.parametrize("scalar2", scalars) +def test_math_ops_scalar_scalar(op, scalar1, scalar2) -> None: + check_op(op, (scalar1, scalar2)) + + +trig_ops = [ + "//", + "arctan2", + "copysign", + "floor_divide", + "mod", + "fmod", + "hypot", + "logaddexp", + "logaddexp2", + "nextafter", +] + + +@pytest.mark.parametrize("op", trig_ops) +@pytest.mark.parametrize("arr1", arrs[:-1]) +@pytest.mark.parametrize("arr2", arrs[:-1]) +def test_trig_ops_arr_arr(op, arr1, arr2) -> None: + check_op(op, (arr1, arr2)) + + +@pytest.mark.parametrize("op", trig_ops) +@pytest.mark.parametrize("arr", arrs[:-1]) +@pytest.mark.parametrize("scalar", scalars[:-1]) +def test_trig_ops_arr_scalar(op, arr, scalar) -> None: + check_op(op, (arr, scalar)) + check_op(op, (scalar, arr)) + + +@pytest.mark.parametrize("op", trig_ops) +@pytest.mark.parametrize("scalar1", scalars[:-1]) +@pytest.mark.parametrize("scalar2", scalars[:-1]) +def test_trig_ops_scalar_scalar(op, scalar1, scalar2) -> None: + check_op(op, (scalar1, scalar2)) + + +power_ops = [ + "**", + "power", + "float_power", +] + + +@pytest.mark.parametrize("op", power_ops) +@pytest.mark.parametrize("arr1", arrs[:-1]) +@pytest.mark.parametrize("arr2", arrs[:-1]) +def test_power_ops_arr_arr(op, arr1, arr2) -> None: + check_op(op, (arr1, arr2)) + + +@pytest.mark.parametrize("op", power_ops) +@pytest.mark.parametrize("arr", arrs[:-1]) +def test_power_ops_arr_scalar(op, arr) -> None: + check_op(op, (arr, scalars[0])) + check_op(op, (scalars[0], arr)) + check_op(op, (arr, scalars[3])) + check_op(op, (scalars[3], scalars[3])) + + +@pytest.mark.parametrize("op", power_ops) +def test_power_ops_scalar_scalar(op) -> None: + check_op(op, (scalars[0], scalars[3])) + check_op(op, (scalars[3], scalars[0])) + + +div_ops = [ + "%", + "remainder", +] + + +@pytest.mark.parametrize("op", div_ops) +@pytest.mark.parametrize("arr1", arrs[:-1]) +@pytest.mark.parametrize("arr2", arrs[:-1]) +def test_div_ops_arr_arr(op, arr1, arr2) -> None: + check_op(op, (arr1, arr2)) + + +@pytest.mark.parametrize("op", div_ops) +@pytest.mark.parametrize("arr", arrs[:-1]) +@pytest.mark.parametrize("scalar", scalars[:-2]) +def test_div_ops_arr_scalar(op, arr, scalar) -> None: + check_op(op, (arr, scalar)) + check_op(op, (scalar, arr)) + + +@pytest.mark.parametrize("op", div_ops) +@pytest.mark.parametrize("scalar1", scalars[:-2]) +@pytest.mark.parametrize("scalar2", scalars[:-2]) +def test_div_ops_scalar_scalar(op, scalar1, scalar2) -> None: + check_op(op, (scalar1, scalar2)) + + +bit_ops = [ + "&", + "<<", + ">>", + "^", + "|", + "bitwise_and", + "bitwise_or", + "bitwise_xor", + "gcd", + "lcm", + "left_shift", + "right_shift", +] + + +@pytest.mark.parametrize("op", math_ops) +def test_bit_ops_arr_arr(op) -> None: + check_op(op, (arrs[0], arrs[0])) + + +@pytest.mark.parametrize("op", math_ops) +def test_bit_ops_arr_scalar(op) -> None: + check_op(op, (arrs[0], scalars[0])) + check_op(op, (arrs[0], scalars[1])) + check_op(op, (scalars[0], arrs[0])) + check_op(op, (scalars[1], arrs[0])) - scalars = ( - np.uint64(2), - np.int64(-3), - np.random.randn(1)[0], - np.complex64(1 + 1j), - ) - for arr1, arr2 in product(arrs, arrs): - check_ops(ops, (arr1, arr2)) - - for arr, scalar in product(arrs, scalars): - check_ops(ops, (arr, scalar)) - check_ops(ops, (scalar, arr)) - - for scalar1, scalar2 in product(scalars, scalars): - check_ops(ops, (scalar1, scalar2)) - - ops = [ - "//", - "arctan2", - "copysign", - "floor_divide", - "mod", - "fmod", - "hypot", - "logaddexp", - "logaddexp2", - "nextafter", - ] - - for arr1, arr2 in product(arrs[:-1], arrs[:-1]): - check_ops(ops, (arr1, arr2)) - - for arr, scalar in product(arrs[:-1], scalars[:-1]): - check_ops(ops, (arr, scalar)) - check_ops(ops, (scalar, arr)) - - for scalar1, scalar2 in product(scalars[:-1], scalars[:-1]): - check_ops(ops, (scalar1, scalar2)) - - ops = [ - "**", - "power", - "float_power", - ] - - for arr1, arr2 in product(arrs, arrs): - check_ops(ops, (arr1, arr2)) - - for arr in arrs: - check_ops(ops, (arr, scalars[0])) - check_ops(ops, (scalars[0], arr)) - check_ops(ops, (arr, scalars[3])) - check_ops(ops, (scalars[3], scalars[3])) - - check_ops(ops, (scalars[0], scalars[3])) - check_ops(ops, (scalars[3], scalars[0])) - - ops = [ - "%", - "remainder", - ] - - for arr1, arr2 in product(arrs[:1], arrs[:1]): - check_ops(ops, (arr1, arr2)) - - for arr, scalar in product(arrs[:1], scalars[:-2]): - check_ops(ops, (arr, scalar)) - check_ops(ops, (scalar, arr)) - - for scalar1, scalar2 in product(scalars[:-2], scalars[:-2]): - check_ops(ops, (scalar1, scalar2)) - - ops = [ - "&", - "<<", - ">>", - "^", - "|", - "bitwise_and", - "bitwise_or", - "bitwise_xor", - "gcd", - "lcm", - "left_shift", - "right_shift", - ] - - check_ops(ops, (arr1[0], arr2[0])) - - check_ops(ops, (arrs[0], scalars[0])) - check_ops(ops, (arrs[0], scalars[1])) - check_ops(ops, (scalars[0], arrs[0])) - check_ops(ops, (scalars[1], arrs[0])) - - check_ops(ops, (scalars[0], scalars[0])) +@pytest.mark.parametrize("op", math_ops) +def test_bit_ops_scalar_scalar(op) -> None: + check_op(op, (scalars[0], scalars[0])) def parse_inputs(in_str, dtype_str): @@ -276,6 +332,6 @@ def parse_inputs(in_str, dtype_str): if args.op is not None: in_np = parse_inputs(args.inputs, args.dtypes) - check_ops([args.op], in_np) + check_op(args.op, in_np) else: sys.exit(pytest.main(sys.argv)) diff --git a/tests/integration/test_extract.py b/tests/integration/test_extract.py index 007d93fe04..a6407e6789 100644 --- a/tests/integration/test_extract.py +++ b/tests/integration/test_extract.py @@ -47,7 +47,7 @@ [11, 12, 13], [True, False, False, True], [42.3, 42.3, 42.3, 42.3, 42.3], - [np.inf, np.Inf], + [np.inf], ] diff --git a/tests/integration/test_fft_c2c.py b/tests/integration/test_fft_c2c.py index 35d1921040..303a47c193 100644 --- a/tests/integration/test_fft_c2c.py +++ b/tests/integration/test_fft_c2c.py @@ -13,8 +13,6 @@ # limitations under the License. # -import warnings - import numpy as np import pytest from utils.comparisons import allclose as _allclose @@ -55,12 +53,11 @@ def check_1d_c2c(N, dtype=np.float64): assert allclose(out, out_num) # Odd types - warnings.filterwarnings(action="ignore", category=np.ComplexWarning) - out = np.fft.rfft(Z) - out_num = num.fft.rfft(Z_num) + out = np.fft.rfft(Z.real) + out_num = num.fft.rfft(Z_num.real) assert allclose(out, out_num) - out = np.fft.ihfft(Z) - out_num = num.fft.ihfft(Z_num) + out = np.fft.ihfft(Z.real) + out_num = num.fft.ihfft(Z_num.real) assert allclose(out, out_num) assert allclose(Z, Z_num) @@ -106,11 +103,11 @@ def check_2d_c2c(N, dtype=np.float64): assert allclose(out, out_num) # Odd types - out = np.fft.rfft2(Z) - out_num = num.fft.rfft2(Z_num) + out = np.fft.rfft2(Z.real) + out_num = num.fft.rfft2(Z_num.real) assert allclose(out, out_num) - out = np.fft.ihfft(Z) - out_num = num.fft.ihfft(Z_num) + out = np.fft.ihfft(Z.real) + out_num = num.fft.ihfft(Z_num.real) assert allclose(out, out_num) assert allclose(Z, Z_num) @@ -155,11 +152,11 @@ def check_3d_c2c(N, dtype=np.float64): assert allclose(out, out_num) # Odd types - out = np.fft.rfftn(Z) - out_num = num.fft.rfftn(Z_num) + out = np.fft.rfftn(Z.real) + out_num = num.fft.rfftn(Z_num.real) assert allclose(out, out_num) - out = np.fft.ihfft(Z) - out_num = num.fft.ihfft(Z_num) + out = np.fft.ihfft(Z.real) + out_num = num.fft.ihfft(Z_num.real) assert allclose(out, out_num) assert allclose(Z, Z_num) @@ -214,8 +211,8 @@ def check_4d_c2c(N, dtype=np.float64): # Odd types assert allclose(out, out_num) - out = np.fft.ihfft(Z) - out_num = num.fft.ihfft(Z_num) + out = np.fft.ihfft(Z.real) + out_num = num.fft.ihfft(Z_num.real) assert allclose(out, out_num) assert allclose(Z, Z_num) diff --git a/tests/integration/test_fft_c2r.py b/tests/integration/test_fft_c2r.py index 30e5c9b34e..41958bc259 100644 --- a/tests/integration/test_fft_c2r.py +++ b/tests/integration/test_fft_c2r.py @@ -49,11 +49,11 @@ def check_1d_c2r(N, dtype=np.float64): assert allclose(out, out_num) # Odd types - out = np.fft.rfft(Z) - out_num = num.fft.rfft(Z_num) + out = np.fft.rfft(Z.real) + out_num = num.fft.rfft(Z_num.real) assert allclose(out, out_num) - out = np.fft.ihfft(Z) - out_num = num.fft.ihfft(Z_num) + out = np.fft.ihfft(Z.real) + out_num = num.fft.ihfft(Z_num.real) assert allclose(out, out_num) assert allclose(Z, Z_num) @@ -90,11 +90,11 @@ def check_2d_c2r(N, dtype=np.float64): assert allclose(out, out_num) # Odd types - out = np.fft.rfft2(Z) - out_num = num.fft.rfft2(Z_num) + out = np.fft.rfft2(Z.real) + out_num = num.fft.rfft2(Z_num.real) assert allclose(out, out_num) - out = np.fft.ihfft(Z) - out_num = num.fft.ihfft(Z_num) + out = np.fft.ihfft(Z.real) + out_num = num.fft.ihfft(Z_num.real) assert allclose(out, out_num) assert allclose(Z, Z_num) @@ -134,11 +134,11 @@ def check_3d_c2r(N, dtype=np.float64): assert allclose(out, out_num) # Odd types - out = np.fft.rfftn(Z) - out_num = num.fft.rfftn(Z_num) + out = np.fft.rfftn(Z.real) + out_num = num.fft.rfftn(Z_num.real) assert allclose(out, out_num) - out = np.fft.ihfft(Z) - out_num = num.fft.ihfft(Z_num) + out = np.fft.ihfft(Z.real) + out_num = num.fft.ihfft(Z_num.real) assert allclose(out, out_num) assert allclose(Z, Z_num) @@ -182,11 +182,11 @@ def check_4d_c2r(N, dtype=np.float64): assert allclose(out, out_num) # Odd types - out = np.fft.rfftn(Z) - out_num = num.fft.rfftn(Z_num) + out = np.fft.rfftn(Z.real) + out_num = num.fft.rfftn(Z_num.real) assert allclose(out, out_num) - out = np.fft.ihfft(Z) - out_num = num.fft.ihfft(Z_num) + out = np.fft.ihfft(Z.real) + out_num = num.fft.ihfft(Z_num.real) assert allclose(out, out_num) assert allclose(Z, Z_num) diff --git a/tests/integration/test_fill.py b/tests/integration/test_fill.py index 134e209f24..f26e9f8ff1 100644 --- a/tests/integration/test_fill.py +++ b/tests/integration/test_fill.py @@ -20,7 +20,7 @@ import cunumeric as num -INF_VALUES = [np.NINF, np.inf] +INF_VALUES = [-np.inf, np.inf] FLOAT_FILL_VALUES = (-2.4e120, -1.3, 8.9e-130, 0.0, 5.7e-150, 0.6, 3.7e160) FLOAT_BIG_VALUES = (-2.4e120, 3.7e160) diff --git a/tests/integration/test_flip.py b/tests/integration/test_flip.py index e4032174a6..b00c2b5560 100644 --- a/tests/integration/test_flip.py +++ b/tests/integration/test_flip.py @@ -17,6 +17,7 @@ import numpy as np import pytest from legate.core import LEGATE_MAX_DIM +from utils.utils import AxisError import cunumeric as num @@ -48,13 +49,13 @@ def test_axis_float(self): def test_axis_outofbound(self): axis = 12 msg = r"out of bounds" - with pytest.raises(np.AxisError, match=msg): + with pytest.raises(AxisError, match=msg): num.flip(a, axis=axis) def test_axis_outofbound_negative(self): axis = -12 msg = r"out of bounds" - with pytest.raises(np.AxisError, match=msg): + with pytest.raises(AxisError, match=msg): num.flip(a, axis=axis) def test_repeated_axis(self): @@ -66,7 +67,7 @@ def test_repeated_axis(self): def test_axis_outofbound_tuple(self): axis = (1, 5) msg = r"out of bounds" - with pytest.raises(np.AxisError, match=msg): + with pytest.raises(AxisError, match=msg): num.flip(a, axis=axis) diff --git a/tests/integration/test_index_routines.py b/tests/integration/test_index_routines.py index 0f37409c63..aac8c7914a 100644 --- a/tests/integration/test_index_routines.py +++ b/tests/integration/test_index_routines.py @@ -19,6 +19,7 @@ import pytest from legate.core import LEGATE_MAX_DIM from utils.generators import mk_seq_array +from utils.utils import AxisError import cunumeric as num from cunumeric._thunk.eager import diagonal_reference @@ -530,11 +531,11 @@ def test_axes_same(self, axes): "axes", ((0, -4), (3, 0)), ids=lambda axes: f"(axes={axes})" ) def test_axes_out_of_bound(self, axes): - # In Numpy, it raises numpy.AxisError: is out of bounds + # In Numpy, it raises AxisError: is out of bounds # In cuNumeric, it raises ValueError: # axes must be the same size as ndim for transpose axis1, axis2 = axes - with pytest.raises(np.AxisError): + with pytest.raises(AxisError): num.diagonal(self.a, 0, axis1, axis2) @pytest.mark.xfail diff --git a/tests/integration/test_itemset.py b/tests/integration/test_itemset.py index 0329be4d3b..4edd110f34 100644 --- a/tests/integration/test_itemset.py +++ b/tests/integration/test_itemset.py @@ -18,6 +18,11 @@ from utils.generators import generate_item import cunumeric as num +from cunumeric._utils import is_np2 + +# itemset was removed in numpy 2.0, skip the entire module +if is_np2: + pytestmark = pytest.mark.skip @pytest.mark.xfail diff --git a/tests/integration/test_moveaxis.py b/tests/integration/test_moveaxis.py index d52a98f3d5..74c591c82f 100644 --- a/tests/integration/test_moveaxis.py +++ b/tests/integration/test_moveaxis.py @@ -17,6 +17,7 @@ import pytest from legate.core import LEGATE_MAX_DIM from utils.generators import mk_0to1_array +from utils.utils import AxisError import cunumeric as num @@ -87,16 +88,16 @@ def test_repeated_axis(self): def test_axis_out_of_bound(self): msg = "out of bound" - with pytest.raises(np.AxisError, match=msg): + with pytest.raises(AxisError, match=msg): num.moveaxis(self.x, [0, 3], [0, 1]) - with pytest.raises(np.AxisError, match=msg): + with pytest.raises(AxisError, match=msg): num.moveaxis(self.x, [0, 1], [0, -4]) - with pytest.raises(np.AxisError, match=msg): + with pytest.raises(AxisError, match=msg): num.moveaxis(self.x, 4, 0) - with pytest.raises(np.AxisError, match=msg): + with pytest.raises(AxisError, match=msg): num.moveaxis(self.x, 0, -4) def test_axis_with_different_length(self): diff --git a/tests/integration/test_msort.py b/tests/integration/test_msort.py index fbb1cdac0f..2566dc101c 100644 --- a/tests/integration/test_msort.py +++ b/tests/integration/test_msort.py @@ -17,6 +17,7 @@ import pytest import cunumeric as num +from cunumeric._utils import is_np2 # cunumeric.msort(a: ndarray) → ndarray @@ -42,12 +43,13 @@ ] +@pytest.mark.skipif(is_np2, reason="numpy 2.0") class TestmSort(object): @pytest.mark.xfail def test_arr_none(self): res_np = np.msort( None - ) # numpy.AxisError: axis 0 is out of bounds for array of dimension 0 + ) # AxisError: axis 0 is out of bounds for array of dimension 0 res_num = num.msort( None ) # AttributeError: 'NoneType' object has no attribute 'shape' diff --git a/tests/integration/test_nonzero.py b/tests/integration/test_nonzero.py index 8d525446cf..c5467bd440 100644 --- a/tests/integration/test_nonzero.py +++ b/tests/integration/test_nonzero.py @@ -15,6 +15,7 @@ import numpy as np import pytest +from utils.utils import AxisError import cunumeric as num @@ -82,7 +83,7 @@ def test_basic(size): def test_axis_out_bound(): arr = [-1, 0, 1, 2, 10] - with pytest.raises(np.AxisError): + with pytest.raises(AxisError): num.count_nonzero(arr, axis=2) diff --git a/tests/integration/test_prod.py b/tests/integration/test_prod.py index 4a820905ff..eb4ed8e047 100644 --- a/tests/integration/test_prod.py +++ b/tests/integration/test_prod.py @@ -15,8 +15,10 @@ import numpy as np import pytest from utils.comparisons import allclose +from utils.utils import AxisError import cunumeric as num +from cunumeric._utils import is_np2 # numpy.prod(a, axis=None, dtype=None, out=None, keepdims=, # initial=, where=) @@ -74,7 +76,7 @@ (DIM, DIM, DIM), ] -ARR = ([], [[]], [[], []], np.inf, np.Inf, -10.3, 0, 200, 5 + 8j) +ARR = ([], [[]], [[], []], np.inf, -10.3, 0, 200, 5 + 8j) DTYPE = ("l", "L", "f", "e", "d") INTEGER_DTYPE = ("h", "i", "H", "I", "?", "b", "B") @@ -94,7 +96,7 @@ def test_array(self, arr): assert allclose(np.prod(arr), num.prod(arr)) def test_axis_out_bound(self): - expected_exc = np.AxisError + expected_exc = AxisError arr = [-1, 0, 1, 2, 10] with pytest.raises(expected_exc): np.prod(arr, axis=2) @@ -122,19 +124,12 @@ def test_keepdims(self): out_num = num.prod(arr_num, axis=2, keepdims=True) assert allclose(out_np, out_num) - @pytest.mark.parametrize( - "initial", - ([2, 3], pytest.param([3], marks=pytest.mark.xfail)), - ids=str, - ) + @pytest.mark.parametrize("initial", ([2, 3], [3]), ids=str) def test_initial_list(self, initial): - expected_exc = ValueError + expected_exc = TypeError if is_np2 else ValueError arr = [[1, 2], [3, 4]] - # Numpy raises ValueError: - # Input object to FillWithScalar is not a scalar with pytest.raises(expected_exc): np.prod(arr, initial=initial) - # when LEGATE_TEST=1, cuNumeric casts list to scalar and proceeds with pytest.raises(expected_exc): num.prod(arr, initial=initial) diff --git a/tests/integration/test_random_creation.py b/tests/integration/test_random_creation.py index b4f8f84205..8e149c0962 100644 --- a/tests/integration/test_random_creation.py +++ b/tests/integration/test_random_creation.py @@ -183,7 +183,7 @@ def test_rand(shape): LARGE_RNG_SIZES = [10000, (20, 50, 4)] ALL_RNG_SIZES = SMALL_RNG_SIZES + LARGE_RNG_SIZES + [None] INT_DTYPES = [np.int64, np.int32, np.int16] -UINT_DTYPES = [np.uint64, np.uint16, np.uint0] +UINT_DTYPES = [np.uint64, np.uint16, np.uintp] FLOAT_DTYPES = [np.float16, np.float128, np.float64] diff --git a/tests/integration/test_reduction.py b/tests/integration/test_reduction.py index f3379265b7..8c599e3597 100644 --- a/tests/integration/test_reduction.py +++ b/tests/integration/test_reduction.py @@ -15,6 +15,7 @@ import numpy as np import pytest from utils.comparisons import allclose +from utils.utils import AxisError import cunumeric as num @@ -56,7 +57,7 @@ (DIM, DIM, DIM), ] -ARR = ([], [[]], [[], []], np.inf, np.Inf, -10.3, 0, 200, 5 + 8j) +ARR = ([], [[]], [[], []], np.inf, -10.3, 0, 200, 5 + 8j) DTYPE = ["l", "L", "f", "d"] COMPLEX_TYPE = ["F", "D"] @@ -92,7 +93,7 @@ def test_dtype_negative(self, dtype): def test_axis_out_bound(self): arr = [-1, 0, 1, 2, 10] msg = r"bounds" - with pytest.raises(np.AxisError, match=msg): + with pytest.raises(AxisError, match=msg): num.sum(arr, axis=2) @pytest.mark.xfail diff --git a/tests/integration/test_repeat.py b/tests/integration/test_repeat.py index 3023f97c87..6dfcbc644e 100644 --- a/tests/integration/test_repeat.py +++ b/tests/integration/test_repeat.py @@ -16,6 +16,7 @@ import pytest from legate.core import LEGATE_MAX_DIM from utils.generators import mk_seq_array +from utils.utils import AxisError import cunumeric as num @@ -186,7 +187,7 @@ def test_axis_string(arr, repeats, axis): def test_array_axis_out_bound(): anp = np.array([1, 2, 3, 4, 5]) - expected_exc = np.AxisError + expected_exc = AxisError with pytest.raises(expected_exc): np.repeat(anp, 4, 2) with pytest.raises(expected_exc): diff --git a/tests/integration/test_singleton_access.py b/tests/integration/test_singleton_access.py index 118d2828f0..c5280dd5b0 100644 --- a/tests/integration/test_singleton_access.py +++ b/tests/integration/test_singleton_access.py @@ -64,38 +64,24 @@ def array_gen(lib): yield arr for arr in nonscalar_gen(lib): idx_tuple = arr.ndim * (2,) - flat_idx = 0 - for i, x in enumerate(idx_tuple): - flat_idx *= arr.shape[i] - flat_idx += x - arr.itemset(flat_idx, -1) - yield arr - for arr in nonscalar_gen(lib): - idx_tuple = arr.ndim * (2,) - arr.itemset(idx_tuple, -1) + arr[idx_tuple] = -1 yield arr for arr in nonscalar_gen(lib): idx_tuple = arr.ndim * (2,) - arr.itemset(*idx_tuple, -1) + arr[idx_tuple] = -1 yield arr # set single item on scalar array for arr in scalar_gen(lib, 42): idx_tuple = arr.ndim * (0,) arr[idx_tuple] = -1 yield arr - for arr in scalar_gen(lib, 42): - arr.itemset(-1) - yield arr - for arr in scalar_gen(lib, 42): - arr.itemset(0, -1) - yield arr for arr in scalar_gen(lib, 42): idx_tuple = arr.ndim * (0,) - arr.itemset(idx_tuple, -1) + arr[idx_tuple] = -1 yield arr for arr in scalar_gen(lib, 42): idx_tuple = arr.ndim * (0,) - arr.itemset(*idx_tuple, -1) + arr[idx_tuple] = -1 yield arr # set "multiple" items on scalar array for arr in scalar_gen(lib, 42): diff --git a/tests/integration/test_sort.py b/tests/integration/test_sort.py index 1fdfc2f13e..a4c971b214 100644 --- a/tests/integration/test_sort.py +++ b/tests/integration/test_sort.py @@ -52,7 +52,7 @@ class TestSort(object): def test_arr_none(self): res_np = np.sort( None - ) # numpy.AxisError: axis -1 is out of bounds for array of dimension 0 + ) # AxisError: axis -1 is out of bounds for array of dimension 0 res_num = num.sort( None ) # AttributeError: 'NoneType' object has no attribute 'shape' diff --git a/tests/integration/test_sort_complex.py b/tests/integration/test_sort_complex.py index d2ff93c77c..a89960c933 100644 --- a/tests/integration/test_sort_complex.py +++ b/tests/integration/test_sort_complex.py @@ -48,7 +48,7 @@ class TestSortComplex(object): def test_arr_none(self): res_np = np.sort_complex( None - ) # numpy.AxisError: axis 0 is out of bounds for array of dimension 0 + ) # AxisError: axis 0 is out of bounds for array of dimension 0 res_num = num.sort_complex( None ) # AttributeError: 'NoneType' object has no attribute 'shape' diff --git a/tests/integration/test_squeeze.py b/tests/integration/test_squeeze.py index 14c2fda0d1..f657a4554d 100644 --- a/tests/integration/test_squeeze.py +++ b/tests/integration/test_squeeze.py @@ -15,6 +15,7 @@ import numpy as np import pytest +from utils.utils import AxisError import cunumeric as num @@ -74,7 +75,7 @@ def test_num_axis_out_bound(): size = (1, 2, 1) a = num.random.randint(low=-10, high=10, size=size) msg = r"bounds" - with pytest.raises(np.AxisError, match=msg): + with pytest.raises(AxisError, match=msg): num.squeeze(a, axis=3) @@ -82,7 +83,7 @@ def test_array_axis_out_bound(): size = (1, 2, 1) a = num.random.randint(-10, 10, size=size) msg = r"bounds" - with pytest.raises(np.AxisError, match=msg): + with pytest.raises(AxisError, match=msg): a.squeeze(axis=3) diff --git a/tests/integration/test_transpose.py b/tests/integration/test_transpose.py index 4162df7139..0689e7ea27 100644 --- a/tests/integration/test_transpose.py +++ b/tests/integration/test_transpose.py @@ -104,7 +104,7 @@ def test_axes_1d_int(self, size, axes): # For cunumeric, if array.dim==1, it returns the array itself directly, # no matter what the axes value is. # For numpy, it raises - # "numpy.AxisError: axis * is out of bounds for array of dimension 1". + # "AxisError: axis * is out of bounds for array of dimension 1". a = np.random.randint(low=-10, high=10, size=size) b = num.array(a) res_np = np.transpose(a, axes=axes) @@ -215,7 +215,7 @@ def test_axes_1d_int(self, size, axes): # For cunumeric, if array.dim==1, it returns the array itself directly, # no matter what the axes value is. # For Numpy, it raises - # "numpy.AxisError: axis * is out of bounds for array of dimension 1". + # "AxisError: axis * is out of bounds for array of dimension 1". a = np.random.randint(low=-10, high=10, size=size) b = num.array(a) res_np = a.transpose(axes) diff --git a/tests/integration/utils/utils.py b/tests/integration/utils/utils.py index 892154d45c..1a8b0f87cf 100644 --- a/tests/integration/utils/utils.py +++ b/tests/integration/utils/utils.py @@ -16,6 +16,12 @@ import numpy as np import cunumeric as num +from cunumeric._utils import is_np2 + +if is_np2: + from numpy.exceptions import AxisError # noqa: F401 +else: + from numpy import AxisError # noqa: F401 def compare_array(a, b, check_type=True): From 929c3c0757f726e55d30486498738e2ea48fbf78 Mon Sep 17 00:00:00 2001 From: Jacob Faibussowitsch Date: Wed, 8 May 2024 23:25:08 -0400 Subject: [PATCH 196/462] Use `core/cuda/cuda.h` instead of `cuda_help.h` (#187) * Use core/cuda/cuda.h instead of cuda_help.h * Bump to latest legate.core --------- Co-authored-by: Manolis Papadakis --- cmake/versions.json | 4 ++-- src/cunumeric/cuda_help.h | 2 +- src/cunumeric/device_scalar_reduction_buffer.h | 2 +- src/cunumeric/random/randutil/generator.cuh | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/cmake/versions.json b/cmake/versions.json index e9793a8aed..f6c26e6d1f 100644 --- a/cmake/versions.json +++ b/cmake/versions.json @@ -7,7 +7,7 @@ "git_url" : "https://github.com/nv-legate/legate.core.internal.git", "git_shallow": false, "always_download": false, - "git_tag" : "d1d74d822d43389c6dbbb63c6237c0253d287147" + "git_tag" : "e97f7fc6364642e9ae9492bf795d54e9906f2520" } } -} \ No newline at end of file +} diff --git a/src/cunumeric/cuda_help.h b/src/cunumeric/cuda_help.h index 8f2d5be0fe..177f3da669 100644 --- a/src/cunumeric/cuda_help.h +++ b/src/cunumeric/cuda_help.h @@ -17,7 +17,7 @@ #pragma once #include "legate.h" -#include "core/cuda/cuda_help.h" +#include "core/cuda/cuda.h" #include "core/cuda/stream_pool.h" #include "cunumeric/arg.h" #include "cunumeric/device_scalar_reduction_buffer.h" diff --git a/src/cunumeric/device_scalar_reduction_buffer.h b/src/cunumeric/device_scalar_reduction_buffer.h index 4491ef8a84..c3baf4e34e 100644 --- a/src/cunumeric/device_scalar_reduction_buffer.h +++ b/src/cunumeric/device_scalar_reduction_buffer.h @@ -16,7 +16,7 @@ #pragma once -#include "core/cuda/cuda_help.h" +#include "core/cuda/cuda.h" #include "core/data/buffer.h" namespace cunumeric { diff --git a/src/cunumeric/random/randutil/generator.cuh b/src/cunumeric/random/randutil/generator.cuh index efe593bde7..f2ee620e17 100644 --- a/src/cunumeric/random/randutil/generator.cuh +++ b/src/cunumeric/random/randutil/generator.cuh @@ -18,7 +18,7 @@ #include "generator.h" -#include +#include namespace randutilimpl { static constexpr int blocksPerMultiProcessor = 2; // TODO: refine => number of blocks per mp From 81ae112e48b9f29776693c23daeda47c6bd38a51 Mon Sep 17 00:00:00 2001 From: Jacob Faibussowitsch Date: Fri, 10 May 2024 14:20:43 -0400 Subject: [PATCH 197/462] Do the rest of the Legate CUDA change fixes (#190) * Do the rest of the Legate CUDA change fixes * Bump Legate hash to get library.inl fixes * clang-format fixes --- cmake/versions.json | 2 +- src/cunumeric/binary/binary_op.cu | 2 +- src/cunumeric/binary/binary_red.cu | 2 +- src/cunumeric/bits/packbits.cu | 2 +- src/cunumeric/bits/unpackbits.cu | 2 +- src/cunumeric/convolution/convolve.cu | 70 +++++----- src/cunumeric/cudalibs.cu | 6 +- .../device_scalar_reduction_buffer.h | 6 +- .../indexing/parallel_loop.cuh | 2 +- .../reduction/scalar_reduction.cuh | 2 +- src/cunumeric/fft/fft.cu | 12 +- src/cunumeric/index/advanced_indexing.cu | 4 +- src/cunumeric/index/choose.cu | 2 +- src/cunumeric/index/repeat.cu | 6 +- src/cunumeric/index/select.cu | 2 +- src/cunumeric/index/wrap.cu | 4 +- src/cunumeric/index/zip.cu | 4 +- src/cunumeric/item/read.cu | 2 +- src/cunumeric/item/write.cu | 2 +- src/cunumeric/matrix/batched_cholesky.cu | 2 +- src/cunumeric/matrix/contract.cu | 2 +- src/cunumeric/matrix/diag.cu | 4 +- src/cunumeric/matrix/dot.cu | 2 +- src/cunumeric/matrix/gemm.cu | 4 +- src/cunumeric/matrix/matmul.cu | 10 +- src/cunumeric/matrix/matvecmul.cu | 10 +- src/cunumeric/matrix/mp_potrf.cu | 2 +- src/cunumeric/matrix/mp_solve.cu | 2 +- src/cunumeric/matrix/potrf.cu | 4 +- src/cunumeric/matrix/solve.cu | 4 +- src/cunumeric/matrix/syrk.cu | 2 +- src/cunumeric/matrix/tile.cu | 2 +- src/cunumeric/matrix/transpose.cu | 2 +- src/cunumeric/matrix/trilu.cu | 2 +- src/cunumeric/matrix/trsm.cu | 2 +- src/cunumeric/nullary/arange.cu | 2 +- src/cunumeric/nullary/eye.cu | 2 +- src/cunumeric/nullary/fill.cu | 2 +- src/cunumeric/nullary/window.cu | 2 +- src/cunumeric/random/bitgenerator.cu | 4 +- src/cunumeric/random/rand.cu | 2 +- src/cunumeric/random/randutil/generator.cuh | 24 ++-- src/cunumeric/scan/scan_global.cu | 2 +- src/cunumeric/scan/scan_local.cu | 4 +- src/cunumeric/search/argwhere.cu | 4 +- src/cunumeric/search/nonzero.cu | 2 +- src/cunumeric/search/nonzero.cuh | 2 +- src/cunumeric/set/unique.cu | 20 +-- src/cunumeric/sort/cub_sort.cuh | 4 +- src/cunumeric/sort/searchsorted.cu | 2 +- src/cunumeric/sort/sort.cu | 126 +++++++++--------- src/cunumeric/sort/thrust_sort.cuh | 4 +- src/cunumeric/stat/bincount.cu | 4 +- src/cunumeric/stat/histogram.cuh | 2 +- src/cunumeric/ternary/where.cu | 2 +- src/cunumeric/transform/flip.cu | 2 +- src/cunumeric/unary/convert.cu | 2 +- src/cunumeric/unary/unary_op.cu | 6 +- src/cunumeric/unary/unary_red.cu | 2 +- src/cunumeric/utilities/repartition.cu | 18 +-- 60 files changed, 218 insertions(+), 218 deletions(-) diff --git a/cmake/versions.json b/cmake/versions.json index f6c26e6d1f..a8081dbcd0 100644 --- a/cmake/versions.json +++ b/cmake/versions.json @@ -7,7 +7,7 @@ "git_url" : "https://github.com/nv-legate/legate.core.internal.git", "git_shallow": false, "always_download": false, - "git_tag" : "e97f7fc6364642e9ae9492bf795d54e9906f2520" + "git_tag" : "94dfb658c5f7da396dc81ae9b9401f76c58d6a08" } } } diff --git a/src/cunumeric/binary/binary_op.cu b/src/cunumeric/binary/binary_op.cu index 2334b10adb..3f14a2b1ff 100644 --- a/src/cunumeric/binary/binary_op.cu +++ b/src/cunumeric/binary/binary_op.cu @@ -82,7 +82,7 @@ struct BinaryOpImplBody { generic_kernel<<>>( volume, func, out, in1, in2, pitches, rect); } - CHECK_CUDA_STREAM(stream); + LegateCheckCUDAStream(stream); } }; diff --git a/src/cunumeric/binary/binary_red.cu b/src/cunumeric/binary/binary_red.cu index 624977c281..823ed3c238 100644 --- a/src/cunumeric/binary/binary_red.cu +++ b/src/cunumeric/binary/binary_red.cu @@ -82,7 +82,7 @@ struct BinaryRedImplBody { } copy_kernel<<<1, 1, 0, stream>>>(result, out); - CHECK_CUDA_STREAM(stream); + LegateCheckCUDAStream(stream); } }; diff --git a/src/cunumeric/bits/packbits.cu b/src/cunumeric/bits/packbits.cu index 1fb30c5787..7903bbded1 100644 --- a/src/cunumeric/bits/packbits.cu +++ b/src/cunumeric/bits/packbits.cu @@ -76,7 +76,7 @@ struct PackbitsImplBody { in_hi_axis, axis); } - CHECK_CUDA_STREAM(stream); + LegateCheckCUDAStream(stream); } }; diff --git a/src/cunumeric/bits/unpackbits.cu b/src/cunumeric/bits/unpackbits.cu index 19a166e7d2..acc7212706 100644 --- a/src/cunumeric/bits/unpackbits.cu +++ b/src/cunumeric/bits/unpackbits.cu @@ -55,7 +55,7 @@ struct UnpackbitsImplBody { const size_t blocks = (in_volume + THREADS_PER_BLOCK - 1) / THREADS_PER_BLOCK; generic_kernel<<>>( in_volume, unpack, out, in, in_pitches, in_rect.lo, axis); - CHECK_CUDA_STREAM(stream); + LegateCheckCUDAStream(stream); } }; diff --git a/src/cunumeric/convolution/convolve.cu b/src/cunumeric/convolution/convolve.cu index 0e3d404dcc..8d63086e84 100644 --- a/src/cunumeric/convolution/convolve.cu +++ b/src/cunumeric/convolution/convolve.cu @@ -744,7 +744,7 @@ __host__ static inline void launch_small_tile_kernel(AccessorWO out, out, filter, in, root_rect, subrect, filter_rect, args); } } - CHECK_CUDA_STREAM(stream); + LegateCheckCUDAStream(stream); } template @@ -766,24 +766,24 @@ __host__ void direct_convolution(AccessorWO out, static unsigned long long mask = 0; if (!(mask & (1 << device))) { if (properties.sharedMemPerBlock < max_smem_size) { - CHECK_CUDA(cudaFuncSetAttribute(convolution_small_tile1, - cudaFuncAttributeMaxDynamicSharedMemorySize, - max_smem_size)); - CHECK_CUDA(cudaFuncSetAttribute(convolution_small_tile2, - cudaFuncAttributeMaxDynamicSharedMemorySize, - max_smem_size)); - CHECK_CUDA(cudaFuncSetAttribute(convolution_large_tile, - cudaFuncAttributeMaxDynamicSharedMemorySize, - max_smem_size)); + LegateCheckCUDA(cudaFuncSetAttribute(convolution_small_tile1, + cudaFuncAttributeMaxDynamicSharedMemorySize, + max_smem_size)); + LegateCheckCUDA(cudaFuncSetAttribute(convolution_small_tile2, + cudaFuncAttributeMaxDynamicSharedMemorySize, + max_smem_size)); + LegateCheckCUDA(cudaFuncSetAttribute(convolution_large_tile, + cudaFuncAttributeMaxDynamicSharedMemorySize, + max_smem_size)); } if (sizeof(VAL) >= 8) { // Only need to set this on the first invocation - CHECK_CUDA(cudaFuncSetSharedMemConfig(convolution_small_tile1, - cudaSharedMemBankSizeEightByte)); - CHECK_CUDA(cudaFuncSetSharedMemConfig(convolution_small_tile2, - cudaSharedMemBankSizeEightByte)); - CHECK_CUDA(cudaFuncSetSharedMemConfig(convolution_large_tile, - cudaSharedMemBankSizeEightByte)); + LegateCheckCUDA(cudaFuncSetSharedMemConfig(convolution_small_tile1, + cudaSharedMemBankSizeEightByte)); + LegateCheckCUDA(cudaFuncSetSharedMemConfig(convolution_small_tile2, + cudaSharedMemBankSizeEightByte)); + LegateCheckCUDA(cudaFuncSetSharedMemConfig(convolution_large_tile, + cudaSharedMemBankSizeEightByte)); } // Make sure we have enough bits for every device assert(device < (8 * sizeof(mask))); @@ -848,7 +848,7 @@ __host__ void direct_convolution(AccessorWO out, } if (out_dense) { size_t bytes = sizeof(VAL) * out_pitch; - CHECK_CUDA(cudaMemsetAsync(out_ptr, 0, bytes)); + LegateCheckCUDA(cudaMemsetAsync(out_ptr, 0, bytes)); } else { out_pitch = 1; ConvolutionInitArgs args; @@ -1168,7 +1168,7 @@ __host__ void direct_convolution(AccessorWO out, one, args); } - CHECK_CUDA_STREAM(stream); + LegateCheckCUDAStream(stream); } } @@ -1310,19 +1310,19 @@ __host__ static inline void cufft_convolution(AccessorWO out, static unsigned long long mask = 0; if (!(mask & (1 << device))) { if (properties.sharedMemPerBlock < max_smem_size) { - CHECK_CUDA(cudaFuncSetAttribute(convolution_small_tile1, - cudaFuncAttributeMaxDynamicSharedMemorySize, - max_smem_size)); - CHECK_CUDA(cudaFuncSetAttribute(convolution_small_tile2, - cudaFuncAttributeMaxDynamicSharedMemorySize, - max_smem_size)); + LegateCheckCUDA(cudaFuncSetAttribute(convolution_small_tile1, + cudaFuncAttributeMaxDynamicSharedMemorySize, + max_smem_size)); + LegateCheckCUDA(cudaFuncSetAttribute(convolution_small_tile2, + cudaFuncAttributeMaxDynamicSharedMemorySize, + max_smem_size)); } if (sizeof(VAL) >= 8) { // Only need to set this on the first invocation - CHECK_CUDA(cudaFuncSetSharedMemConfig(convolution_small_tile1, - cudaSharedMemBankSizeEightByte)); - CHECK_CUDA(cudaFuncSetSharedMemConfig(convolution_small_tile2, - cudaSharedMemBankSizeEightByte)); + LegateCheckCUDA(cudaFuncSetSharedMemConfig(convolution_small_tile1, + cudaSharedMemBankSizeEightByte)); + LegateCheckCUDA(cudaFuncSetSharedMemConfig(convolution_small_tile2, + cudaSharedMemBankSizeEightByte)); } // Make sure we have enough bits for every device assert(device < (8 * sizeof(mask))); @@ -1405,7 +1405,7 @@ __host__ static inline void cufft_convolution(AccessorWO out, // Zero pad and copy in the input data auto signal_buffer = create_buffer(buffersize, Memory::GPU_FB_MEM, 128 /*alignment*/); VAL* signal_ptr = signal_buffer.ptr(zero); - CHECK_CUDA(cudaMemsetAsync(signal_ptr, 0, buffervolume * sizeof(VAL), stream)); + LegateCheckCUDA(cudaMemsetAsync(signal_ptr, 0, buffervolume * sizeof(VAL), stream)); // Check to see if the input pointer is dense and we can do this with a CUDA memcpy size_t strides[DIM]; const VAL* input_ptr = in.ptr(input_bounds, strides); @@ -1421,7 +1421,7 @@ __host__ static inline void cufft_convolution(AccessorWO out, // Zero pad and copy in the filter data auto filter_buffer = create_buffer(buffersize, Memory::GPU_FB_MEM, 128 /*alignment*/); VAL* filter_ptr = filter_buffer.ptr(zero); - CHECK_CUDA(cudaMemsetAsync(filter_ptr, 0, buffervolume * sizeof(VAL), stream)); + LegateCheckCUDA(cudaMemsetAsync(filter_ptr, 0, buffervolume * sizeof(VAL), stream)); const VAL* filt_ptr = filter.ptr(filter_rect, strides); pitch = 1; for (int d = DIM - 1; d >= 0; d--) { @@ -1432,7 +1432,7 @@ __host__ static inline void cufft_convolution(AccessorWO out, copy_into_buffer<<>>( filter, filter_buffer, filter_rect.lo, copy_pitches, pitch); - CHECK_CUDA_STREAM(stream); + LegateCheckCUDAStream(stream); auto forward_plan = get_cufft_plan(ForwardPlanType::value, cufftPlanParams(fftsize)); auto backward_plan = get_cufft_plan(BackwardPlanType::value, cufftPlanParams(fftsize)); @@ -1455,7 +1455,7 @@ __host__ static inline void cufft_convolution(AccessorWO out, // FFT the filter data cufft_execute_forward(forward_plan.handle(), filter_ptr, filter_ptr); - CHECK_CUDA_STREAM(stream); + LegateCheckCUDAStream(stream); // Perform the pointwise multiplcation { @@ -1492,13 +1492,13 @@ __host__ static inline void cufft_convolution(AccessorWO out, copy_from_buffer<<>>( filter_ptr, out, buffer_offset, subrect.lo, copy_pitches, fft_pitches, pitch, scaling_factor); - CHECK_CUDA_STREAM(stream); + LegateCheckCUDAStream(stream); #if 0 // This is useful debugging code for finding the output VAL *buffer = (VAL*)malloc(buffervolume*sizeof(VAL)); - CHECK_CUDA( cudaMemcpyAsync(buffer, filter_ptr, buffervolume*sizeof(VAL), cudaMemcpyDeviceToHost, stream) ); - CHECK_CUDA( cudaStreamSynchronize(stream) ); + LegateCheckCUDA( cudaMemcpyAsync(buffer, filter_ptr, buffervolume*sizeof(VAL), cudaMemcpyDeviceToHost, stream) ); + LegateCheckCUDA( cudaStreamSynchronize(stream) ); for (unsigned idx = 0; idx < buffervolume; idx++) { if ((idx % fftsize[DIM-1]) == 0) printf("\n"); diff --git a/src/cunumeric/cudalibs.cu b/src/cunumeric/cudalibs.cu index 8d1ff9a50e..2d437aafc8 100644 --- a/src/cunumeric/cudalibs.cu +++ b/src/cunumeric/cudalibs.cu @@ -338,7 +338,7 @@ int CUDALibraries::get_device_ordinal() return *ordinal_; } int ordinal{-1}; - CHECK_CUDA(cudaGetDevice(&ordinal)); + LegateCheckCUDA(cudaGetDevice(&ordinal)); ordinal_ = ordinal; return ordinal; } @@ -349,7 +349,7 @@ const cudaDeviceProp& CUDALibraries::get_device_properties() return *device_prop_; } device_prop_ = std::make_unique(); - CHECK_CUDA(cudaGetDeviceProperties(device_prop_.get(), get_device_ordinal())); + LegateCheckCUDA(cudaGetDeviceProperties(device_prop_.get(), get_device_ordinal())); return *device_prop_; } @@ -382,7 +382,7 @@ cusolverMpHandle_t CUDALibraries::get_cusolvermp() { if (nullptr == cusolvermp_) { int device = -1; - CHECK_CUDA(cudaGetDevice(&device)); + LegateCheckCUDA(cudaGetDevice(&device)); CHECK_CUSOLVER(cusolverMpCreate(&cusolvermp_, device, get_cached_stream())); } return cusolvermp_; diff --git a/src/cunumeric/device_scalar_reduction_buffer.h b/src/cunumeric/device_scalar_reduction_buffer.h index c3baf4e34e..99f6ec49b4 100644 --- a/src/cunumeric/device_scalar_reduction_buffer.h +++ b/src/cunumeric/device_scalar_reduction_buffer.h @@ -32,7 +32,7 @@ class DeviceScalarReductionBuffer { { VAL identity{REDOP::identity}; ptr_ = buffer_.ptr(0); - CHECK_CUDA(cudaMemcpyAsync(ptr_, &identity, sizeof(VAL), cudaMemcpyHostToDevice, stream)); + LegateCheckCUDA(cudaMemcpyAsync(ptr_, &identity, sizeof(VAL), cudaMemcpyHostToDevice, stream)); } template @@ -44,8 +44,8 @@ class DeviceScalarReductionBuffer { __host__ VAL read(cudaStream_t stream) const { VAL result{REDOP::identity}; - CHECK_CUDA(cudaMemcpyAsync(&result, ptr_, sizeof(VAL), cudaMemcpyDeviceToHost, stream)); - CHECK_CUDA(cudaStreamSynchronize(stream)); + LegateCheckCUDA(cudaMemcpyAsync(&result, ptr_, sizeof(VAL), cudaMemcpyDeviceToHost, stream)); + LegateCheckCUDA(cudaStreamSynchronize(stream)); return result; } diff --git a/src/cunumeric/execution_policy/indexing/parallel_loop.cuh b/src/cunumeric/execution_policy/indexing/parallel_loop.cuh index 63952ebb8b..a67b5e1212 100644 --- a/src/cunumeric/execution_policy/indexing/parallel_loop.cuh +++ b/src/cunumeric/execution_policy/indexing/parallel_loop.cuh @@ -48,7 +48,7 @@ struct ParallelLoopPolicy { parallel_loop_kernel<<>>( volume, std::forward(kernel), Tag{}); - CHECK_CUDA_STREAM(stream); + LegateCheckCUDAStream(stream); } }; diff --git a/src/cunumeric/execution_policy/reduction/scalar_reduction.cuh b/src/cunumeric/execution_policy/reduction/scalar_reduction.cuh index 3d86b4fc78..9357942c2b 100644 --- a/src/cunumeric/execution_policy/reduction/scalar_reduction.cuh +++ b/src/cunumeric/execution_policy/reduction/scalar_reduction.cuh @@ -75,7 +75,7 @@ struct ScalarReductionPolicy { volume, 1, result, std::forward(kernel), identity, Tag{}); } scalar_reduction_impl::copy_kernel<<<1, 1, 0, stream>>>(result, out); - CHECK_CUDA_STREAM(stream); + LegateCheckCUDAStream(stream); } }; diff --git a/src/cunumeric/fft/fft.cu b/src/cunumeric/fft/fft.cu index ba4ec53231..8486c403ff 100644 --- a/src/cunumeric/fft/fft.cu +++ b/src/cunumeric/fft/fft.cu @@ -46,7 +46,7 @@ __host__ static inline void copy_into_buffer(TYPE* target, cudaStream_t stream) { if (acc.accessor.is_dense_row_major(rect)) { - CHECK_CUDA(cudaMemcpyAsync( + LegateCheckCUDA(cudaMemcpyAsync( target, acc.ptr(rect.lo), volume * sizeof(TYPE), cudaMemcpyDeviceToDevice, stream)); } else { Pitches pitches{}; @@ -56,7 +56,7 @@ __host__ static inline void copy_into_buffer(TYPE* target, copy_kernel<<>>( volume, target, acc, pitches, rect.lo); - CHECK_CUDA_STREAM(stream); + LegateCheckCUDAStream(stream); } } @@ -115,7 +115,7 @@ __host__ static inline void cufft_operation(AccessorWO out, static_cast(out.ptr(out_rect.lo)), static_cast(direction))); // synchronize before cufft_context runs out of scope - CHECK_CUDA(cudaStreamSynchronize(stream)); + LegateCheckCUDA(cudaStreamSynchronize(stream)); } // Perform the FFT operation as multiple 1D FFTs along the specified axes (Complex-to-complex case). @@ -144,7 +144,7 @@ __host__ static inline void cufft_over_axes_c2c(INOUT_TYPE* out, // Copy input to output buffer (if needed) // the computation will be done inplace of the target if (in != out) { - CHECK_CUDA(cudaMemcpyAsync( + LegateCheckCUDA(cudaMemcpyAsync( out, in, num_elements * sizeof(INOUT_TYPE), cudaMemcpyDeviceToDevice, stream)); } @@ -193,7 +193,7 @@ __host__ static inline void cufft_over_axes_c2c(INOUT_TYPE* out, static_cast(direction))); } // synchronize before cufft_context runs out of scope - CHECK_CUDA(cudaStreamSynchronize(stream)); + LegateCheckCUDA(cudaStreamSynchronize(stream)); } } @@ -269,7 +269,7 @@ __host__ static inline void cufft_r2c_c2r(OUTPUT_TYPE* out, static_cast(direction))); } // synchronize before cufft_context runs out of scope - CHECK_CUDA(cudaStreamSynchronize(stream)); + LegateCheckCUDA(cudaStreamSynchronize(stream)); } // Perform the FFT operation as multiple 1D FFTs along the specified axes. diff --git a/src/cunumeric/index/advanced_indexing.cu b/src/cunumeric/index/advanced_indexing.cu index b379458e72..56cf004d9b 100644 --- a/src/cunumeric/index/advanced_indexing.cu +++ b/src/cunumeric/index/advanced_indexing.cu @@ -109,7 +109,7 @@ struct AdvancedIndexingImplBody { volume, size, offsets, in, pitches, rect.lo, 1, skip_size, key_dim); } - CHECK_CUDA_STREAM(stream); + LegateCheckCUDAStream(stream); auto off_ptr = offsets.ptr(0); thrust::exclusive_scan(DEFAULT_POLICY.on(stream), off_ptr, off_ptr + volume, off_ptr); @@ -158,7 +158,7 @@ struct AdvancedIndexingImplBody { advanced_indexing_kernel<<>>( volume, input, index, out, pitches, rect.lo, offsets, skip_size, key_dim); } - CHECK_CUDA_STREAM(stream); + LegateCheckCUDAStream(stream); } }; diff --git a/src/cunumeric/index/choose.cu b/src/cunumeric/index/choose.cu index 9bd573825e..acfb24322d 100644 --- a/src/cunumeric/index/choose.cu +++ b/src/cunumeric/index/choose.cu @@ -82,7 +82,7 @@ struct ChooseImplBody { choose_kernel <<>>(out, index_arr, ch_arr, rect, pitches, volume); } - CHECK_CUDA_STREAM(stream); + LegateCheckCUDAStream(stream); } }; diff --git a/src/cunumeric/index/repeat.cu b/src/cunumeric/index/repeat.cu index 2bfa6451cf..400cfcfb42 100644 --- a/src/cunumeric/index/repeat.cu +++ b/src/cunumeric/index/repeat.cu @@ -116,7 +116,7 @@ struct RepeatImplBody { auto stream = get_cached_stream(); repeat_kernel<<>>( out, in, repeats, axis, out_rect.lo, pitches, out_volume); - CHECK_CUDA_STREAM(stream); + LegateCheckCUDAStream(stream); } void operator()(legate::PhysicalStore& out_array, @@ -146,7 +146,7 @@ struct RepeatImplBody { count_repeat_kernel<<>>( extent, sum, repeats, in_rect.lo, axis, 1, offsets); } - CHECK_CUDA_STREAM(stream); + LegateCheckCUDAStream(stream); Point out_extents = in_rect.hi - in_rect.lo + Point::ONES(); out_extents[axis] = static_cast(sum.read(stream)); @@ -159,7 +159,7 @@ struct RepeatImplBody { const size_t blocks = (volume + THREADS_PER_BLOCK - 1) / THREADS_PER_BLOCK; repeat_kernel<<>>( out, in, repeats, offsets, axis, in_rect.lo, pitches, volume); - CHECK_CUDA_STREAM(stream); + LegateCheckCUDAStream(stream); } }; diff --git a/src/cunumeric/index/select.cu b/src/cunumeric/index/select.cu index 5396702225..171a7f05ae 100644 --- a/src/cunumeric/index/select.cu +++ b/src/cunumeric/index/select.cu @@ -118,7 +118,7 @@ struct SelectImplBody { out, narrays, cond_arr, choice_arr, default_val, rect, pitches, rect.volume()); } - CHECK_CUDA_STREAM(stream); + LegateCheckCUDAStream(stream); } }; diff --git a/src/cunumeric/index/wrap.cu b/src/cunumeric/index/wrap.cu index 13d6e45aa0..df65ad7b10 100644 --- a/src/cunumeric/index/wrap.cu +++ b/src/cunumeric/index/wrap.cu @@ -113,7 +113,7 @@ void check_out_of_bounds(const AccessorRO& indices, check_kernel<<>>( out_of_bounds, indices, start, volume, volume_base, 1); } - CHECK_CUDA_STREAM(stream); + LegateCheckCUDAStream(stream); bool res = out_of_bounds.read(stream); if (res) { @@ -158,7 +158,7 @@ struct WrapImplBody { volume_base, indices); } - CHECK_CUDA_STREAM(stream); + LegateCheckCUDAStream(stream); } }; diff --git a/src/cunumeric/index/zip.cu b/src/cunumeric/index/zip.cu index f35586f006..ab4a6bccc6 100644 --- a/src/cunumeric/index/zip.cu +++ b/src/cunumeric/index/zip.cu @@ -146,7 +146,7 @@ struct ZipImplBody { check_kernel<<>>( out_of_bounds, index_arrays, volume, 1, rect, pitches, narrays, start_index, shape); } - CHECK_CUDA_STREAM(stream); + LegateCheckCUDAStream(stream); bool res = out_of_bounds.read(stream); if (res) { @@ -198,7 +198,7 @@ struct ZipImplBody { zip_kernel<<>>( out, index_buf, rect, pitches, num_arrays, volume, key_dim, start_index, shape); } - CHECK_CUDA_STREAM(stream); + LegateCheckCUDAStream(stream); } }; diff --git a/src/cunumeric/item/read.cu b/src/cunumeric/item/read.cu index f4d677d674..3c04fd8ac8 100644 --- a/src/cunumeric/item/read.cu +++ b/src/cunumeric/item/read.cu @@ -33,7 +33,7 @@ struct ReadImplBody { { auto stream = get_cached_stream(); read_value<<<1, 1, 0, stream>>>(out, in); - CHECK_CUDA_STREAM(stream); + LegateCheckCUDAStream(stream); } }; diff --git a/src/cunumeric/item/write.cu b/src/cunumeric/item/write.cu index 1a9e8e9981..e10b07f5ca 100644 --- a/src/cunumeric/item/write.cu +++ b/src/cunumeric/item/write.cu @@ -33,7 +33,7 @@ struct WriteImplBody { { auto stream = get_cached_stream(); write_value<<<1, 1, 0, stream>>>(out, value); - CHECK_CUDA_STREAM(stream); + LegateCheckCUDAStream(stream); } }; diff --git a/src/cunumeric/matrix/batched_cholesky.cu b/src/cunumeric/matrix/batched_cholesky.cu index 7b6e82eae0..3a04ee1829 100644 --- a/src/cunumeric/matrix/batched_cholesky.cu +++ b/src/cunumeric/matrix/batched_cholesky.cu @@ -101,7 +101,7 @@ struct BatchedTransposeImplBody { // the lower diagonal transpose_2d_lower<<>>(out, n); - CHECK_CUDA_STREAM(stream); + LegateCheckCUDAStream(stream); } }; diff --git a/src/cunumeric/matrix/contract.cu b/src/cunumeric/matrix/contract.cu index 8118ab9361..a9758f0913 100644 --- a/src/cunumeric/matrix/contract.cu +++ b/src/cunumeric/matrix/contract.cu @@ -148,7 +148,7 @@ __host__ void contract(T* lhs_data, work_size, task_stream)); - CHECK_CUDA_STREAM(task_stream); + LegateCheckCUDAStream(task_stream); } template <> diff --git a/src/cunumeric/matrix/diag.cu b/src/cunumeric/matrix/diag.cu index ff64f4ebb8..174b208082 100644 --- a/src/cunumeric/matrix/diag.cu +++ b/src/cunumeric/matrix/diag.cu @@ -93,7 +93,7 @@ struct DiagImplBody { auto stream = get_cached_stream(); diag_extract<<>>( out, in, distance, volume, skip_size, start, naxes, m_pitches, m_shape); - CHECK_CUDA_STREAM(stream); + LegateCheckCUDAStream(stream); } }; @@ -110,7 +110,7 @@ struct DiagImplBody { const size_t blocks = (distance + THREADS_PER_BLOCK - 1) / THREADS_PER_BLOCK; auto stream = get_cached_stream(); diag_populate<<>>(out, in, distance, start); - CHECK_CUDA_STREAM(stream); + LegateCheckCUDAStream(stream); } }; diff --git a/src/cunumeric/matrix/dot.cu b/src/cunumeric/matrix/dot.cu index b5a88f4b56..a8b76ed90b 100644 --- a/src/cunumeric/matrix/dot.cu +++ b/src/cunumeric/matrix/dot.cu @@ -72,7 +72,7 @@ struct DotImplBody { } copy_kernel<<<1, 1, 0, stream>>>(result, out); - CHECK_CUDA_STREAM(stream); + LegateCheckCUDAStream(stream); } }; diff --git a/src/cunumeric/matrix/gemm.cu b/src/cunumeric/matrix/gemm.cu index b2fe26783a..34bb815468 100644 --- a/src/cunumeric/matrix/gemm.cu +++ b/src/cunumeric/matrix/gemm.cu @@ -39,7 +39,7 @@ static inline void gemm_template( CHECK_CUBLAS(gemm(context, transa, transb, m, n, k, &alpha, rhs1, m, rhs2, n, &beta, lhs, m)); - CHECK_CUDA_STREAM(stream); + LegateCheckCUDAStream(stream); } template @@ -58,7 +58,7 @@ static inline void complex_gemm_template( CHECK_CUBLAS(gemm(context, transa, transb, m, n, k, &alpha, rhs1, m, rhs2, n, &beta, lhs, m)); - CHECK_CUDA_STREAM(stream); + LegateCheckCUDAStream(stream); } template <> diff --git a/src/cunumeric/matrix/matmul.cu b/src/cunumeric/matrix/matmul.cu index 8433ae1b87..a52d115ea1 100644 --- a/src/cunumeric/matrix/matmul.cu +++ b/src/cunumeric/matrix/matmul.cu @@ -68,7 +68,7 @@ struct MatMulImplBody { CUDA_R_32F, lhs_stride)); - CHECK_CUDA_STREAM(task_stream); + LegateCheckCUDAStream(task_stream); } }; @@ -109,7 +109,7 @@ struct MatMulImplBody { lhs, lhs_stride)); - CHECK_CUDA_STREAM(task_stream); + LegateCheckCUDAStream(task_stream); } }; @@ -153,7 +153,7 @@ struct MatMulImplBody { CUDA_R_32F, lhs_stride)); - CHECK_CUDA_STREAM(task_stream); + LegateCheckCUDAStream(task_stream); } }; @@ -201,7 +201,7 @@ struct MatMulImplBody { CUDA_C_32F, lhs_stride)); - CHECK_CUDA_STREAM(task_stream); + LegateCheckCUDAStream(task_stream); } }; @@ -246,7 +246,7 @@ struct MatMulImplBody { lhs, lhs_stride)); - CHECK_CUDA_STREAM(task_stream); + LegateCheckCUDAStream(task_stream); } }; diff --git a/src/cunumeric/matrix/matvecmul.cu b/src/cunumeric/matrix/matvecmul.cu index e6845f57be..0945b3b0fa 100644 --- a/src/cunumeric/matrix/matvecmul.cu +++ b/src/cunumeric/matrix/matvecmul.cu @@ -71,7 +71,7 @@ struct MatVecMulImplBody { transpose_mat ? n : m)); } - CHECK_CUDA_STREAM(task_stream); + LegateCheckCUDAStream(task_stream); } }; @@ -119,7 +119,7 @@ struct MatVecMulImplBody { transpose_mat ? n : m)); } - CHECK_CUDA_STREAM(task_stream); + LegateCheckCUDAStream(task_stream); } }; @@ -161,7 +161,7 @@ struct MatVecMulImplBody { CUDA_R_32F, transpose_mat ? n : m)); - CHECK_CUDA_STREAM(task_stream); + LegateCheckCUDAStream(task_stream); } }; @@ -216,7 +216,7 @@ struct MatVecMulImplBody { transpose_mat ? n : m)); } - CHECK_CUDA_STREAM(task_stream); + LegateCheckCUDAStream(task_stream); } }; @@ -268,7 +268,7 @@ struct MatVecMulImplBody { transpose_mat ? n : m)); } - CHECK_CUDA_STREAM(task_stream); + LegateCheckCUDAStream(task_stream); } }; diff --git a/src/cunumeric/matrix/mp_potrf.cu b/src/cunumeric/matrix/mp_potrf.cu index 84a66a62b9..a69ceb6c77 100644 --- a/src/cunumeric/matrix/mp_potrf.cu +++ b/src/cunumeric/matrix/mp_potrf.cu @@ -77,7 +77,7 @@ static inline void mp_potrf_template( // TODO: We need a deferred exception to avoid this synchronization CHECK_CAL(cal_stream_sync(comm, stream)); - CHECK_CUDA_STREAM(stream); + LegateCheckCUDAStream(stream); CHECK_CUSOLVER(cusolverMpDestroyMatrixDesc(desc)); CHECK_CUSOLVER(cusolverMpDestroyGrid(grid)); diff --git a/src/cunumeric/matrix/mp_solve.cu b/src/cunumeric/matrix/mp_solve.cu index a8b0b37af3..17b77f986f 100644 --- a/src/cunumeric/matrix/mp_solve.cu +++ b/src/cunumeric/matrix/mp_solve.cu @@ -136,7 +136,7 @@ static inline void mp_solve_template(cal_comm_t comm, // TODO: We need a deferred exception to avoid this synchronization CHECK_CAL(cal_stream_sync(comm, stream)); - CHECK_CUDA_STREAM(stream); + LegateCheckCUDAStream(stream); CHECK_CUSOLVER(cusolverMpDestroyMatrixDesc(a_desc)); CHECK_CUSOLVER(cusolverMpDestroyMatrixDesc(b_desc)); diff --git a/src/cunumeric/matrix/potrf.cu b/src/cunumeric/matrix/potrf.cu index d902531570..375a8f7b9a 100644 --- a/src/cunumeric/matrix/potrf.cu +++ b/src/cunumeric/matrix/potrf.cu @@ -42,8 +42,8 @@ static inline void potrf_template( CHECK_CUSOLVER(potrf(context, uplo, n, array, m, buffer.ptr(0), bufferSize, info.ptr(0))); // TODO: We need a deferred exception to avoid this synchronization - CHECK_CUDA(cudaStreamSynchronize(stream)); - CHECK_CUDA_STREAM(stream); + LegateCheckCUDA(cudaStreamSynchronize(stream)); + LegateCheckCUDAStream(stream); if (info[0] != 0) { throw legate::TaskException("Matrix is not positive definite"); diff --git a/src/cunumeric/matrix/solve.cu b/src/cunumeric/matrix/solve.cu index 6c5492fa64..dd415e19d8 100644 --- a/src/cunumeric/matrix/solve.cu +++ b/src/cunumeric/matrix/solve.cu @@ -47,7 +47,7 @@ static inline void solve_template(GetrfBufferSize getrf_buffer_size, auto info = create_buffer(1, Memory::Kind::Z_COPY_MEM); CHECK_CUSOLVER(getrf(handle, m, n, a, m, buffer.ptr(0), ipiv.ptr(0), info.ptr(0))); - CHECK_CUDA(cudaStreamSynchronize(stream)); + LegateCheckCUDA(cudaStreamSynchronize(stream)); if (info[0] != 0) { throw legate::TaskException(SolveTask::ERROR_MESSAGE); @@ -55,7 +55,7 @@ static inline void solve_template(GetrfBufferSize getrf_buffer_size, CHECK_CUSOLVER(getrs(handle, trans, n, nrhs, a, m, ipiv.ptr(0), b, n, info.ptr(0))); - CHECK_CUDA_STREAM(stream); + LegateCheckCUDAStream(stream); #ifdef DEBUG_CUNUMERIC assert(info[0] == 0); diff --git a/src/cunumeric/matrix/syrk.cu b/src/cunumeric/matrix/syrk.cu index 08c3916f51..547e2e3fce 100644 --- a/src/cunumeric/matrix/syrk.cu +++ b/src/cunumeric/matrix/syrk.cu @@ -38,7 +38,7 @@ static inline void syrk_template( CHECK_CUBLAS(syrk(context, uplo, trans, m, n, &alpha, rhs, m, &beta, lhs, m)); - CHECK_CUDA_STREAM(stream); + LegateCheckCUDAStream(stream); } template <> diff --git a/src/cunumeric/matrix/tile.cu b/src/cunumeric/matrix/tile.cu index cc2fd4ded0..54b69c1c66 100644 --- a/src/cunumeric/matrix/tile.cu +++ b/src/cunumeric/matrix/tile.cu @@ -53,7 +53,7 @@ struct TileImplBody { auto stream = get_cached_stream(); tile_kernel<<>>( out_rect, out_pitches, out_volume, in_strides, out, in); - CHECK_CUDA_STREAM(stream); + LegateCheckCUDAStream(stream); } }; diff --git a/src/cunumeric/matrix/transpose.cu b/src/cunumeric/matrix/transpose.cu index 877a66d275..39fd497e54 100644 --- a/src/cunumeric/matrix/transpose.cu +++ b/src/cunumeric/matrix/transpose.cu @@ -99,7 +99,7 @@ struct TransposeImplBody { auto stream = get_cached_stream(); transpose_2d_physical<<>>(out, in, rect.lo, rect.hi); - CHECK_CUDA_STREAM(stream); + LegateCheckCUDAStream(stream); } }; diff --git a/src/cunumeric/matrix/trilu.cu b/src/cunumeric/matrix/trilu.cu index 9c2e9c5180..2d50d931da 100644 --- a/src/cunumeric/matrix/trilu.cu +++ b/src/cunumeric/matrix/trilu.cu @@ -70,7 +70,7 @@ struct TriluImplBody { auto stream = get_cached_stream(); trilu_kernel <<>>(out, in, pitches, lo, volume, k); - CHECK_CUDA_STREAM(stream); + LegateCheckCUDAStream(stream); } }; diff --git a/src/cunumeric/matrix/trsm.cu b/src/cunumeric/matrix/trsm.cu index 30dbeba439..a1481e78da 100644 --- a/src/cunumeric/matrix/trsm.cu +++ b/src/cunumeric/matrix/trsm.cu @@ -39,7 +39,7 @@ static inline void trsm_template( CHECK_CUBLAS(trsm(context, side, uplo, transa, diag, m, n, &alpha, rhs, n, lhs, m)); - CHECK_CUDA_STREAM(stream); + LegateCheckCUDAStream(stream); } template <> diff --git a/src/cunumeric/nullary/arange.cu b/src/cunumeric/nullary/arange.cu index 7f6596d464..eaeee3d5fb 100644 --- a/src/cunumeric/nullary/arange.cu +++ b/src/cunumeric/nullary/arange.cu @@ -45,7 +45,7 @@ struct ArangeImplBody { auto stream = get_cached_stream(); arange_kernel <<>>(out, rect.lo[0], start, step, distance); - CHECK_CUDA_STREAM(stream); + LegateCheckCUDAStream(stream); } }; diff --git a/src/cunumeric/nullary/eye.cu b/src/cunumeric/nullary/eye.cu index 033bb856cc..90a78db22f 100644 --- a/src/cunumeric/nullary/eye.cu +++ b/src/cunumeric/nullary/eye.cu @@ -40,7 +40,7 @@ struct EyeImplBody { const size_t blocks = (distance + THREADS_PER_BLOCK - 1) / THREADS_PER_BLOCK; auto stream = get_cached_stream(); eye_kernel<<>>(out, start, distance); - CHECK_CUDA_STREAM(stream); + LegateCheckCUDAStream(stream); } }; diff --git a/src/cunumeric/nullary/fill.cu b/src/cunumeric/nullary/fill.cu index 0fc58da856..c4a8769bdd 100644 --- a/src/cunumeric/nullary/fill.cu +++ b/src/cunumeric/nullary/fill.cu @@ -61,7 +61,7 @@ struct FillImplBody { } else { generic_kernel<<>>(volume, out, in, pitches, rect); } - CHECK_CUDA_STREAM(stream); + LegateCheckCUDAStream(stream); } }; diff --git a/src/cunumeric/nullary/window.cu b/src/cunumeric/nullary/window.cu index fd919b0813..de92e2b68d 100644 --- a/src/cunumeric/nullary/window.cu +++ b/src/cunumeric/nullary/window.cu @@ -64,7 +64,7 @@ struct WindowImplBody { <<>>(gen, volume, out, rect.lo[0]); } - CHECK_CUDA_STREAM(stream); + LegateCheckCUDAStream(stream); } }; diff --git a/src/cunumeric/random/bitgenerator.cu b/src/cunumeric/random/bitgenerator.cu index a98153e20f..8c04e7d1be 100644 --- a/src/cunumeric/random/bitgenerator.cu +++ b/src/cunumeric/random/bitgenerator.cu @@ -32,13 +32,13 @@ struct GPUGenerator : public CURANDGenerator { GPUGenerator(BitGeneratorType gentype, uint64_t seed, uint64_t generatorId, uint32_t flags) : CURANDGenerator(gentype, seed, generatorId) { - CHECK_CUDA(::cudaStreamCreate(&stream_)); + LegateCheckCUDA(::cudaStreamCreate(&stream_)); CHECK_CURAND(::randutilCreateGenerator(&gen_, type_, seed, generatorId, flags, stream_)); } virtual ~GPUGenerator() { - CHECK_CUDA(::cudaStreamSynchronize(stream_)); + LegateCheckCUDA(::cudaStreamSynchronize(stream_)); CHECK_CURAND(::randutilDestroyGenerator(gen_)); } }; diff --git a/src/cunumeric/random/rand.cu b/src/cunumeric/random/rand.cu index c41be1469e..13ef4ca8bc 100644 --- a/src/cunumeric/random/rand.cu +++ b/src/cunumeric/random/rand.cu @@ -50,7 +50,7 @@ struct RandImplBody { auto stream = get_cached_stream(); rand_kernel<<>>( volume, out, rng, strides, pitches, rect.lo); - CHECK_CUDA_STREAM(stream); + LegateCheckCUDAStream(stream); } }; diff --git a/src/cunumeric/random/randutil/generator.cuh b/src/cunumeric/random/randutil/generator.cuh index f2ee620e17..0e5b188161 100644 --- a/src/cunumeric/random/randutil/generator.cuh +++ b/src/cunumeric/random/randutil/generator.cuh @@ -73,8 +73,8 @@ struct inner_generator : basegenerato : seed(seed), generatorID(generatorID), stream(stream) { int deviceId; - CHECK_CUDA(::cudaGetDevice(&deviceId)); - CHECK_CUDA( + LegateCheckCUDA(::cudaGetDevice(&deviceId)); + LegateCheckCUDA( ::cudaDeviceGetAttribute(&multiProcessorCount, cudaDevAttrMultiProcessorCount, deviceId)); // get number of generators ngenerators = blockDimX * multiProcessorCount * blocksPerMultiProcessor; @@ -84,36 +84,36 @@ struct inner_generator : basegenerato // allocate buffer for generators state int driverVersion, runtimeVersion; - CHECK_CUDA(::cudaDriverGetVersion(&driverVersion)); - CHECK_CUDA(::cudaRuntimeGetVersion(&runtimeVersion)); + LegateCheckCUDA(::cudaDriverGetVersion(&driverVersion)); + LegateCheckCUDA(::cudaRuntimeGetVersion(&runtimeVersion)); asyncsupported = ((driverVersion >= 10020) && (runtimeVersion >= 10020)); if (asyncsupported) { #if (__CUDACC_VER_MAJOR__ > 11 || ((__CUDACC_VER_MAJOR__ >= 11) && (__CUDACC_VER_MINOR__ >= 2))) - CHECK_CUDA(::cudaMallocAsync(&generators, ngenerators * sizeof(gen_t), stream)); + LegateCheckCUDA(::cudaMallocAsync(&generators, ngenerators * sizeof(gen_t), stream)); #else - CHECK_CUDA(::cudaMalloc(&generators, ngenerators * sizeof(gen_t))); + LegateCheckCUDA(::cudaMalloc(&generators, ngenerators * sizeof(gen_t))); #endif } else { - CHECK_CUDA(::cudaMalloc(&generators, ngenerators * sizeof(gen_t))); + LegateCheckCUDA(::cudaMalloc(&generators, ngenerators * sizeof(gen_t))); } // initialize generators initgenerators<<>>( generators, seed, generatorID); - CHECK_CUDA(::cudaPeekAtLastError()); + LegateCheckCUDA(::cudaPeekAtLastError()); } virtual void destroy() override { - CHECK_CUDA(::cudaStreamSynchronize(stream)); + LegateCheckCUDA(::cudaStreamSynchronize(stream)); if (asyncsupported) { #if (__CUDACC_VER_MAJOR__ > 11 || ((__CUDACC_VER_MAJOR__ >= 11) && (__CUDACC_VER_MINOR__ >= 2))) - CHECK_CUDA(::cudaFreeAsync(generators, stream)); + LegateCheckCUDA(::cudaFreeAsync(generators, stream)); #else - CHECK_CUDA(::cudaFree(generators)); + LegateCheckCUDA(::cudaFree(generators)); #endif } else { - CHECK_CUDA(::cudaFree(generators)); + LegateCheckCUDA(::cudaFree(generators)); } generators = nullptr; diff --git a/src/cunumeric/scan/scan_global.cu b/src/cunumeric/scan/scan_global.cu index 7266d9e1a4..027b58e88b 100644 --- a/src/cunumeric/scan/scan_global.cu +++ b/src/cunumeric/scan/scan_global.cu @@ -79,7 +79,7 @@ struct ScanGlobalImplBody { scalar_kernel<<>>( stride, func, &outptr[index], global_prefix); } - CHECK_CUDA_STREAM(stream); + LegateCheckCUDAStream(stream); } }; diff --git a/src/cunumeric/scan/scan_local.cu b/src/cunumeric/scan/scan_local.cu index e7ef12c9a2..7519c380b7 100644 --- a/src/cunumeric/scan/scan_local.cu +++ b/src/cunumeric/scan/scan_local.cu @@ -75,7 +75,7 @@ struct ScanLocalImplBody { lazy_kernel<<<1, THREADS_PER_BLOCK, 0, stream>>>(&outptr[index + stride - 1], &sum_valsptr[sum_valp]); } - CHECK_CUDA_STREAM(stream); + LegateCheckCUDAStream(stream); } }; @@ -126,7 +126,7 @@ struct ScanLocalNanImplBody { lazy_kernel<<<1, THREADS_PER_BLOCK, 0, stream>>>(&outptr[index + stride - 1], &sum_valsptr[sum_valp]); } - CHECK_CUDA_STREAM(stream); + LegateCheckCUDAStream(stream); } }; diff --git a/src/cunumeric/search/argwhere.cu b/src/cunumeric/search/argwhere.cu index 131bb582f4..41e85a56e3 100644 --- a/src/cunumeric/search/argwhere.cu +++ b/src/cunumeric/search/argwhere.cu @@ -59,7 +59,7 @@ struct ArgWhereImplBody { auto offsets = create_buffer(volume, legate::Memory::Kind::GPU_FB_MEM); auto size = compute_offsets(input, pitches, rect, volume, offsets, stream); - CHECK_CUDA_STREAM(stream); + LegateCheckCUDAStream(stream); auto out = out_array.create_output_buffer(Point<2>(size, DIM), true); @@ -67,7 +67,7 @@ struct ArgWhereImplBody { const size_t blocks = (volume + THREADS_PER_BLOCK - 1) / THREADS_PER_BLOCK; argwhere_kernel<<>>( volume, input, pitches, rect.lo, offsets, out); - CHECK_CUDA_STREAM(stream); + LegateCheckCUDAStream(stream); } } }; diff --git a/src/cunumeric/search/nonzero.cu b/src/cunumeric/search/nonzero.cu index d834d62947..29e1a07e92 100644 --- a/src/cunumeric/search/nonzero.cu +++ b/src/cunumeric/search/nonzero.cu @@ -85,7 +85,7 @@ struct NonzeroImplBody { if (size > 0) { populate_nonzeros(in, pitches, rect, volume, results, offsets, stream); } - CHECK_CUDA_STREAM(stream); + LegateCheckCUDAStream(stream); } }; diff --git a/src/cunumeric/search/nonzero.cuh b/src/cunumeric/search/nonzero.cuh index 486fdff0e9..2ef4db7e7e 100644 --- a/src/cunumeric/search/nonzero.cuh +++ b/src/cunumeric/search/nonzero.cuh @@ -78,7 +78,7 @@ int64_t compute_offsets(const AccessorRO& in, exclusive_sum(p_offsets, volume, stream); - CHECK_CUDA_STREAM(stream); + LegateCheckCUDAStream(stream); return size.read(stream); } diff --git a/src/cunumeric/set/unique.cu b/src/cunumeric/set/unique.cu index 567a804ad2..fec687a8ae 100644 --- a/src/cunumeric/set/unique.cu +++ b/src/cunumeric/set/unique.cu @@ -66,7 +66,7 @@ static Piece tree_reduce(legate::PhysicalStore& output, // but I suspect point-to-point can be slower... all_sizes[my_id] = my_piece.second; CHECK_NCCL(ncclAllGather(all_sizes.ptr(my_id), all_sizes.ptr(0), 1, ncclUint64, *comm, stream)); - CHECK_CUDA(cudaStreamSynchronize(stream)); + LegateCheckCUDA(cudaStreamSynchronize(stream)); Piece other_piece; size_t offset = radix / 2; @@ -121,11 +121,11 @@ static Piece tree_reduce(legate::PhysicalStore& output, assert(my_piece.second <= buf_size); my_piece.first = output.create_output_buffer(buf_size); - CHECK_CUDA(cudaMemcpyAsync(my_piece.first.ptr(0), - p_merged, - sizeof(VAL) * my_piece.second, - cudaMemcpyDeviceToDevice, - stream)); + LegateCheckCUDA(cudaMemcpyAsync(my_piece.first.ptr(0), + p_merged, + sizeof(VAL) * my_piece.second, + cudaMemcpyDeviceToDevice, + stream)); merged.destroy(); } @@ -163,14 +163,14 @@ struct UniqueImplBody { if (volume > 0) { if (in.accessor.is_dense_arbitrary(rect)) { auto* src = in.ptr(rect.lo); - CHECK_CUDA( + LegateCheckCUDA( cudaMemcpyAsync(ptr, src, sizeof(VAL) * volume, cudaMemcpyDeviceToDevice, stream)); } else { const size_t num_blocks = (volume + THREADS_PER_BLOCK - 1) / THREADS_PER_BLOCK; copy_into_buffer<<>>( ptr, in, rect.lo, pitches, volume); } - CHECK_CUDA_STREAM(stream); + LegateCheckCUDAStream(stream); // Find unique values thrust::sort(DEFAULT_POLICY.on(stream), ptr, ptr + volume); @@ -183,7 +183,7 @@ struct UniqueImplBody { assert(end - ptr <= buf_size); result.first = output.create_output_buffer(buf_size); if (result.second > 0) { - CHECK_CUDA(cudaMemcpyAsync( + LegateCheckCUDA(cudaMemcpyAsync( result.first.ptr(0), ptr, sizeof(VAL) * result.second, cudaMemcpyDeviceToDevice, stream)); } @@ -193,7 +193,7 @@ struct UniqueImplBody { auto comm = comms[0].get(); result = tree_reduce(output, result, point[0], launch_domain.get_volume(), stream, comm); } - CHECK_CUDA_STREAM(stream); + LegateCheckCUDAStream(stream); // Finally we pack the result output.bind_data(result.first, Point<1>(result.second)); diff --git a/src/cunumeric/sort/cub_sort.cuh b/src/cunumeric/sort/cub_sort.cuh index dd54bedbbf..ced45d9a7a 100644 --- a/src/cunumeric/sort/cub_sort.cuh +++ b/src/cunumeric/sort/cub_sort.cuh @@ -47,7 +47,7 @@ void cub_local_sort(const VAL* values_in, if (values_in == values_out) { keys_in = create_buffer(volume, Memory::Kind::GPU_FB_MEM); values_in_cub = keys_in.ptr(0); - CHECK_CUDA(cudaMemcpyAsync( + LegateCheckCUDA(cudaMemcpyAsync( keys_in.ptr(0), values_out, sizeof(VAL) * volume, cudaMemcpyDeviceToDevice, stream)); } @@ -111,7 +111,7 @@ void cub_local_sort(const VAL* values_in, if (indices_in == indices_out) { idx_in = create_buffer(volume, Memory::Kind::GPU_FB_MEM); indices_in_cub = idx_in.ptr(0); - CHECK_CUDA(cudaMemcpyAsync( + LegateCheckCUDA(cudaMemcpyAsync( idx_in.ptr(0), indices_out, sizeof(int64_t) * volume, cudaMemcpyDeviceToDevice, stream)); } diff --git a/src/cunumeric/sort/searchsorted.cu b/src/cunumeric/sort/searchsorted.cu index 9cc8e85639..25489203eb 100644 --- a/src/cunumeric/sort/searchsorted.cu +++ b/src/cunumeric/sort/searchsorted.cu @@ -106,7 +106,7 @@ struct SearchSortedImplBody { searchsorted_kernel_max<<>>( output_reduction, input, input_v, rect_values.lo, pitches, volume, num_values, offset); } - CHECK_CUDA_STREAM(stream); + LegateCheckCUDAStream(stream); } }; diff --git a/src/cunumeric/sort/sort.cu b/src/cunumeric/sort/sort.cu index 7193e11984..b45a278f3d 100644 --- a/src/cunumeric/sort/sort.cu +++ b/src/cunumeric/sort/sort.cu @@ -647,10 +647,10 @@ SegmentMergePiece> merge_all_buffers( combine_buffers_no_sort<<>>( idc_buffers_ptr, target_offsets, result.indices, merged_size, num_sort_ranks); - CHECK_CUDA(cudaStreamSynchronize(stream)); // needed before Z-copy destroy() + LegateCheckCUDA(cudaStreamSynchronize(stream)); // needed before Z-copy destroy() idc_buffers_ptr.destroy(); } else { - CHECK_CUDA(cudaStreamSynchronize(stream)); // needed before Z-copy destroy() + LegateCheckCUDA(cudaStreamSynchronize(stream)); // needed before Z-copy destroy() } val_buffers_ptr.destroy(); target_offsets.destroy(); @@ -672,7 +672,7 @@ SegmentMergePiece> merge_all_buffers( local_sort( p_values, p_values, p_indices, p_indices, merged_size, merged_size, true, stream); - CHECK_CUDA_STREAM(stream); + LegateCheckCUDAStream(stream); return result; } else { // maybe k-way merge is more efficient here... @@ -773,7 +773,7 @@ SegmentMergePiece> merge_all_buffers( } SegmentMergePiece result = merge_buffers[0]; merge_buffers.clear(); - CHECK_CUDA_STREAM(stream); + LegateCheckCUDAStream(stream); return result; } } @@ -913,28 +913,28 @@ void rebalance_data(SegmentMergePiece& merge_buffer, segment_diff_2d_ptr, segment_diff_2d_ptr + num_segments_l * num_sort_ranks, segment_diff_2d_scan_ptr); - CHECK_CUDA(cudaMemcpy2DAsync(send_right.ptr(0), - sizeof(int64_t), - segment_diff_2d_scan.ptr(0) + my_sort_rank, - num_sort_ranks * sizeof(int64_t), - sizeof(int64_t), - num_segments_l, - cudaMemcpyDeviceToDevice, - stream)); + LegateCheckCUDA(cudaMemcpy2DAsync(send_right.ptr(0), + sizeof(int64_t), + segment_diff_2d_scan.ptr(0) + my_sort_rank, + num_sort_ranks * sizeof(int64_t), + sizeof(int64_t), + num_segments_l, + cudaMemcpyDeviceToDevice, + stream)); thrust::reverse_iterator::iterator> iter_in( segment_diff_2d_ptr + num_segments_l * num_sort_ranks); thrust::reverse_iterator::iterator> iter_out( segment_diff_2d_scan_ptr + num_segments_l * num_sort_ranks); thrust::inclusive_scan( exec_policy, iter_in, iter_in + num_segments_l * num_sort_ranks, iter_out); - CHECK_CUDA(cudaMemcpy2DAsync(send_left.ptr(0), - sizeof(int64_t), - segment_diff_2d_scan.ptr(0) + my_sort_rank, - num_sort_ranks * sizeof(int64_t), - sizeof(int64_t), - num_segments_l, - cudaMemcpyDeviceToDevice, - stream)); + LegateCheckCUDA(cudaMemcpy2DAsync(send_left.ptr(0), + sizeof(int64_t), + segment_diff_2d_scan.ptr(0) + my_sort_rank, + num_sort_ranks * sizeof(int64_t), + sizeof(int64_t), + num_segments_l, + cudaMemcpyDeviceToDevice, + stream)); segment_diff_2d.destroy(); segment_diff_2d_scan.destroy(); @@ -1212,7 +1212,7 @@ void rebalance_data(SegmentMergePiece& merge_buffer, recv_left_data.values.destroy(); recv_right_data.values.destroy(); } - CHECK_CUDA_STREAM(stream); + LegateCheckCUDAStream(stream); } } @@ -1259,13 +1259,13 @@ void sample_sort_nccl_nd( { auto worker_count_d = create_buffer(1, legate::Memory::GPU_FB_MEM); size_t worker_count = (segment_size_l > 0 ? 1 : 0); - CHECK_CUDA(cudaMemcpyAsync( + LegateCheckCUDA(cudaMemcpyAsync( worker_count_d.ptr(0), &worker_count, sizeof(int32_t), cudaMemcpyHostToDevice, stream)); CHECK_NCCL(ncclAllReduce( worker_count_d.ptr(0), worker_count_d.ptr(0), 1, ncclInt32, ncclSum, *comm, stream)); - CHECK_CUDA(cudaMemcpyAsync( + LegateCheckCUDA(cudaMemcpyAsync( &worker_count, worker_count_d.ptr(0), sizeof(int32_t), cudaMemcpyDeviceToHost, stream)); - CHECK_CUDA(cudaStreamSynchronize(stream)); + LegateCheckCUDA(cudaStreamSynchronize(stream)); if (worker_count < num_ranks) { const size_t number_sort_groups = num_ranks / num_sort_ranks; num_sort_ranks = worker_count / number_sort_groups; @@ -1313,7 +1313,7 @@ void sample_sort_nccl_nd( offset, num_sort_ranks, my_sort_rank); - CHECK_CUDA_STREAM(stream); + LegateCheckCUDAStream(stream); } // AllGather does not work here as not all have the same amount! @@ -1322,11 +1322,11 @@ void sample_sort_nccl_nd( // allocate receive buffer const size_t aligned_count = get_16b_aligned_count(num_samples_l, sizeof(SegmentSample)); auto send_buffer = create_buffer>(aligned_count, legate::Memory::GPU_FB_MEM); - CHECK_CUDA(cudaMemcpyAsync(send_buffer.ptr(0), - samples.ptr(offset), - sizeof(SegmentSample) * num_samples_l, - cudaMemcpyDeviceToDevice, - stream)); + LegateCheckCUDA(cudaMemcpyAsync(send_buffer.ptr(0), + samples.ptr(offset), + sizeof(SegmentSample) * num_samples_l, + cudaMemcpyDeviceToDevice, + stream)); auto recv_buffer = create_buffer>(aligned_count * num_sort_ranks, legate::Memory::GPU_FB_MEM); @@ -1353,11 +1353,11 @@ void sample_sort_nccl_nd( // copy back for (size_t r = 0; r < num_sort_ranks; r++) { if (r != my_sort_rank) { - CHECK_CUDA(cudaMemcpyAsync(samples.ptr(num_samples_l * r), - recv_buffer.ptr(aligned_count * r), - sizeof(SegmentSample) * num_samples_l, - cudaMemcpyDeviceToDevice, - stream)); + LegateCheckCUDA(cudaMemcpyAsync(samples.ptr(num_samples_l * r), + recv_buffer.ptr(aligned_count * r), + sizeof(SegmentSample) * num_samples_l, + cudaMemcpyDeviceToDevice, + stream)); } } @@ -1365,7 +1365,7 @@ void sample_sort_nccl_nd( send_buffer.destroy(); recv_buffer.destroy(); - CHECK_CUDA_STREAM(stream); + LegateCheckCUDAStream(stream); } ///////////////////////////////////////////////////////////////////////////////////////////////// @@ -1431,7 +1431,7 @@ void sample_sort_nccl_nd( compute_scan_per_rank<<>>( segment_blocks.ptr(0), size_send.ptr(0), num_segments_l, num_segments_l_aligned); - CHECK_CUDA_STREAM(stream); + LegateCheckCUDAStream(stream); } // cleanup intermediate data structures @@ -1468,25 +1468,25 @@ void sample_sort_nccl_nd( Buffer size_recv_total = create_buffer(num_sort_ranks, legate::Memory::Z_COPY_MEM); { - CHECK_CUDA(cudaMemcpy2DAsync(size_send_total.ptr(0), - 1 * sizeof(size_t), - size_send.ptr(num_segments_l), - num_segments_l_aligned * sizeof(size_t), - sizeof(int64_t), - num_sort_ranks, - cudaMemcpyDeviceToHost, - stream)); - CHECK_CUDA(cudaMemcpy2DAsync(size_recv_total.ptr(0), - 1 * sizeof(size_t), - size_recv.ptr(num_segments_l), - num_segments_l_aligned * sizeof(size_t), - sizeof(int64_t), - num_sort_ranks, - cudaMemcpyDeviceToHost, - stream)); + LegateCheckCUDA(cudaMemcpy2DAsync(size_send_total.ptr(0), + 1 * sizeof(size_t), + size_send.ptr(num_segments_l), + num_segments_l_aligned * sizeof(size_t), + sizeof(int64_t), + num_sort_ranks, + cudaMemcpyDeviceToHost, + stream)); + LegateCheckCUDA(cudaMemcpy2DAsync(size_recv_total.ptr(0), + 1 * sizeof(size_t), + size_recv.ptr(num_segments_l), + num_segments_l_aligned * sizeof(size_t), + sizeof(int64_t), + num_sort_ranks, + cudaMemcpyDeviceToHost, + stream)); // need to sync as we share values in between host/device - CHECK_CUDA(cudaStreamSynchronize(stream)); + LegateCheckCUDA(cudaStreamSynchronize(stream)); } // copy values into aligned send buffer @@ -1537,13 +1537,13 @@ void sample_sort_nccl_nd( segment_size_l, my_rank, num_sort_ranks); - CHECK_CUDA(cudaStreamSynchronize(stream)); // needed before Z-copy destroy() + LegateCheckCUDA(cudaStreamSynchronize(stream)); // needed before Z-copy destroy() idc_send_buffers_ptr.destroy(); } else { - CHECK_CUDA(cudaStreamSynchronize(stream)); // needed before Z-copy destroy() + LegateCheckCUDA(cudaStreamSynchronize(stream)); // needed before Z-copy destroy() } val_send_buffers_ptr.destroy(); - CHECK_CUDA_STREAM(stream); + LegateCheckCUDAStream(stream); } local_sorted.values.destroy(); @@ -1573,7 +1573,7 @@ void sample_sort_nccl_nd( size_recv.ptr(r * num_segments_l_aligned), size_recv.ptr(r * num_segments_l_aligned) + num_segments_l + 1, size_recv.ptr(r * num_segments_l_aligned)); - CHECK_CUDA( + LegateCheckCUDA( cudaMemsetAsync(merge_buffers[r].segments.ptr(0), 0, size * sizeof(size_t), stream)); const size_t num_blocks = (num_segments_l + THREADS_PER_BLOCK - 1) / THREADS_PER_BLOCK; assert(sizeof(unsigned long long int) == @@ -1597,7 +1597,7 @@ void sample_sort_nccl_nd( } } - CHECK_CUDA_STREAM(stream); + LegateCheckCUDAStream(stream); } // communicate all2all (in sort dimension) @@ -1652,7 +1652,7 @@ void sample_sort_nccl_nd( idc_send_buffers[r].destroy(); } } - CHECK_CUDA_STREAM(stream); + LegateCheckCUDAStream(stream); ///////////////////////////////////////////////////////////////////////////////////////////////// /////////////// Part 4: merge data @@ -1782,7 +1782,7 @@ struct SortImplBody { values_ptr = output.ptr(rect.lo); } } - CHECK_CUDA_STREAM(stream); + LegateCheckCUDAStream(stream); if (volume > 0) { // sort data (locally) @@ -1795,7 +1795,7 @@ struct SortImplBody { stable, stream); } - CHECK_CUDA_STREAM(stream); + LegateCheckCUDAStream(stream); if (need_distributed_sort) { if (is_index_space) { @@ -1849,7 +1849,7 @@ struct SortImplBody { local_sorted.values.destroy(); } - CHECK_CUDA_STREAM(stream); + LegateCheckCUDAStream(stream); } }; diff --git a/src/cunumeric/sort/thrust_sort.cuh b/src/cunumeric/sort/thrust_sort.cuh index 66c922d1e6..fb9bfcd5c9 100644 --- a/src/cunumeric/sort/thrust_sort.cuh +++ b/src/cunumeric/sort/thrust_sort.cuh @@ -48,12 +48,12 @@ void thrust_local_sort(const VAL* values_in, if (values_in != values_out) { // not in-place --> need a copy - CHECK_CUDA(cudaMemcpyAsync( + LegateCheckCUDA(cudaMemcpyAsync( values_out, values_in, sizeof(VAL) * volume, cudaMemcpyDeviceToDevice, stream)); } if (indices_in != indices_out) { // not in-place --> need a copy - CHECK_CUDA(cudaMemcpyAsync( + LegateCheckCUDA(cudaMemcpyAsync( indices_out, values_in, sizeof(int64_t) * volume, cudaMemcpyDeviceToDevice, stream)); } diff --git a/src/cunumeric/stat/bincount.cu b/src/cunumeric/stat/bincount.cu index 81d0e91b36..1a7d15fb48 100644 --- a/src/cunumeric/stat/bincount.cu +++ b/src/cunumeric/stat/bincount.cu @@ -183,7 +183,7 @@ struct BincountImplBody { bincount_kernel_rd_global <<>>(lhs, rhs, volume, rect.lo); } - CHECK_CUDA_STREAM(stream); + LegateCheckCUDAStream(stream); } void operator()(AccessorRD, false, 1> lhs, @@ -212,7 +212,7 @@ struct BincountImplBody { weighted_bincount_kernel_rd_global <<>>(lhs, rhs, weights, volume, rect.lo); } - CHECK_CUDA_STREAM(stream); + LegateCheckCUDAStream(stream); } }; diff --git a/src/cunumeric/stat/histogram.cuh b/src/cunumeric/stat/histogram.cuh index 9c7bd74c44..1a7acf7258 100644 --- a/src/cunumeric/stat/histogram.cuh +++ b/src/cunumeric/stat/histogram.cuh @@ -108,7 +108,7 @@ template struct sync_policy_t>> { sync_policy_t() {} - void operator()(cudaStream_t stream) { CHECK_CUDA_STREAM(stream); } + void operator()(cudaStream_t stream) { LegateCheckCUDAStream(stream); } }; } // namespace detail diff --git a/src/cunumeric/ternary/where.cu b/src/cunumeric/ternary/where.cu index 8833636411..b23509b82e 100644 --- a/src/cunumeric/ternary/where.cu +++ b/src/cunumeric/ternary/where.cu @@ -71,7 +71,7 @@ struct WhereImplBody { generic_kernel<<>>( volume, out, mask, in1, in2, pitches, rect); } - CHECK_CUDA_STREAM(stream); + LegateCheckCUDAStream(stream); } }; diff --git a/src/cunumeric/transform/flip.cu b/src/cunumeric/transform/flip.cu index 796c9aaa73..6dd5b912b4 100644 --- a/src/cunumeric/transform/flip.cu +++ b/src/cunumeric/transform/flip.cu @@ -66,7 +66,7 @@ struct FlipImplBody { auto stream = get_cached_stream(); flip_kernel<<>>( volume, out, in, pitches, rect, gpu_axes, num_axes); - CHECK_CUDA_STREAM(stream); + LegateCheckCUDAStream(stream); } }; diff --git a/src/cunumeric/unary/convert.cu b/src/cunumeric/unary/convert.cu index 4091a994d4..78e6799d3d 100644 --- a/src/cunumeric/unary/convert.cu +++ b/src/cunumeric/unary/convert.cu @@ -68,7 +68,7 @@ struct ConvertImplBody { generic_kernel<<>>( volume, func, out, in, pitches, rect); } - CHECK_CUDA_STREAM(stream); + LegateCheckCUDAStream(stream); } }; diff --git a/src/cunumeric/unary/unary_op.cu b/src/cunumeric/unary/unary_op.cu index cca4b71fab..4808f96914 100644 --- a/src/cunumeric/unary/unary_op.cu +++ b/src/cunumeric/unary/unary_op.cu @@ -95,7 +95,7 @@ struct UnaryOpImplBody { generic_kernel<<>>( volume, func, out, in, pitches, rect); } - CHECK_CUDA_STREAM(stream); + LegateCheckCUDAStream(stream); } }; @@ -117,7 +117,7 @@ struct PointCopyImplBody { } else { generic_copy_kernel<<>>(volume, out, in, pitches, rect); } - CHECK_CUDA_STREAM(stream); + LegateCheckCUDAStream(stream); } }; @@ -183,7 +183,7 @@ struct MultiOutUnaryOpImplBody { generic_kernel_multiout<<>>( volume, func, lhs, rhs1, rhs2, pitches, rect); } - CHECK_CUDA_STREAM(stream); + LegateCheckCUDAStream(stream); } }; diff --git a/src/cunumeric/unary/unary_red.cu b/src/cunumeric/unary/unary_red.cu index 1336973fe0..2e660a9d14 100644 --- a/src/cunumeric/unary/unary_red.cu +++ b/src/cunumeric/unary/unary_red.cu @@ -353,7 +353,7 @@ struct UnaryRedImplBody { blocks.compute_maximum_concurrency(reinterpret_cast(Kernel)); Kernel<<>>( lhs, rhs, where, LG_OP::identity, blocks, rect, collapsed_dim); - CHECK_CUDA_STREAM(stream); + LegateCheckCUDAStream(stream); } }; diff --git a/src/cunumeric/utilities/repartition.cu b/src/cunumeric/utilities/repartition.cu index 4355d7a4df..be31cbdcfc 100644 --- a/src/cunumeric/utilities/repartition.cu +++ b/src/cunumeric/utilities/repartition.cu @@ -439,7 +439,7 @@ std::tuple, size_t, size_t> repartition_matrix_2dbc(const VAL* input recv_info.ptr(r * stored_size_per_rank), stored_size_per_rank, ncclUint64, r, *comm, stream)); } CHECK_NCCL(ncclGroupEnd()); - CHECK_CUDA(cudaStreamSynchronize(stream)); // need Z-copy synchronized to Host + LegateCheckCUDA(cudaStreamSynchronize(stream)); // need Z-copy synchronized to Host // allocate send/recv buffer std::vector> send_buffers; @@ -499,11 +499,11 @@ std::tuple, size_t, size_t> repartition_matrix_2dbc(const VAL* input p_c, tile_r, tile_c); - CHECK_CUDA(cudaStreamSynchronize(stream)); + LegateCheckCUDA(cudaStreamSynchronize(stream)); send_buffers_ptr.destroy(); } - CHECK_CUDA_STREAM(stream); + LegateCheckCUDAStream(stream); // all2all data CHECK_NCCL(ncclGroupStart()); @@ -550,11 +550,11 @@ std::tuple, size_t, size_t> repartition_matrix_2dbc(const VAL* input tile_c, (size_t)nccl_rank, num_ranks); - CHECK_CUDA(cudaStreamSynchronize(stream)); + LegateCheckCUDA(cudaStreamSynchronize(stream)); recv_buffers_ptr.destroy(); } - CHECK_CUDA_STREAM(stream); + LegateCheckCUDAStream(stream); recv_info.destroy(); for (auto&& buf : recv_buffers) { @@ -610,7 +610,7 @@ void repartition_matrix_block( offsets[2 * local_rank + 1] = num_target_cols > 0 ? target_offset_c + num_target_cols : 0; CHECK_NCCL( ncclAllGather(offsets.ptr(2 * local_rank), offsets.ptr(0), 2, ncclUint64, *comm, stream)); - CHECK_CUDA(cudaStreamSynchronize(stream)); + LegateCheckCUDA(cudaStreamSynchronize(stream)); // re-arrange so that all row offsets come first for (size_t i = 1; i < num_ranks; i += 2) { @@ -805,7 +805,7 @@ void repartition_matrix_block( tile_r, tile_c); - CHECK_CUDA(cudaStreamSynchronize(stream)); + LegateCheckCUDA(cudaStreamSynchronize(stream)); send_buffers_ptr.destroy(); } // we can destroy the input once we distributed data into the buffers @@ -904,11 +904,11 @@ void repartition_matrix_block( p_c, tile_r, tile_c); - CHECK_CUDA(cudaStreamSynchronize(stream)); + LegateCheckCUDA(cudaStreamSynchronize(stream)); recv_buffers_ptr.destroy(); } - CHECK_CUDA_STREAM(stream); + LegateCheckCUDAStream(stream); // cleanup offsets_r.destroy(); From 02c5e366be2ba1a7bb77248243681183aade1149 Mon Sep 17 00:00:00 2001 From: Jacob Faibussowitsch Date: Tue, 14 May 2024 17:59:52 -0400 Subject: [PATCH 198/462] Fix default ctors for Legate objects (#193) * Fix default ctors for Legate objects --- src/cunumeric/index/repeat_template.inl | 8 ++++++-- src/cunumeric/index/wrap.h | 10 +++++----- src/cunumeric/index/wrap_template.inl | 2 +- src/cunumeric/unary/scalar_unary_red_template.inl | 2 +- src/cunumeric/unary/unary_red_template.inl | 2 +- 5 files changed, 14 insertions(+), 10 deletions(-) diff --git a/src/cunumeric/index/repeat_template.inl b/src/cunumeric/index/repeat_template.inl index 0be9ccb3ac..cdf5db20a1 100644 --- a/src/cunumeric/index/repeat_template.inl +++ b/src/cunumeric/index/repeat_template.inl @@ -60,8 +60,12 @@ static void repeat_template(TaskContext& context) auto axis = context.scalar(0).value(); if (scalar_repeats) { auto repeats = context.scalar(2).value(); - RepeatArgs args{ - context.output(0), context.input(0), legate::PhysicalStore(), repeats, axis, scalar_repeats}; + RepeatArgs args{context.output(0), + context.input(0), + legate::PhysicalStore{nullptr}, + repeats, + axis, + scalar_repeats}; double_dispatch(args.input.dim(), args.input.code(), RepeatImpl{}, args); } else { auto repeats = context.input(1); diff --git a/src/cunumeric/index/wrap.h b/src/cunumeric/index/wrap.h index b1ea6b1e11..cc4034d57a 100644 --- a/src/cunumeric/index/wrap.h +++ b/src/cunumeric/index/wrap.h @@ -21,13 +21,13 @@ namespace cunumeric { struct WrapArgs { - legate::PhysicalStore out; // Array with Point type that is used to - // copy information from original array to the - // `wrapped` one - const legate::DomainPoint shape; // shape of the original array + legate::PhysicalStore out{nullptr}; // Array with Point type that is used to + // copy information from original array to the + // `wrapped` one + const legate::DomainPoint shape; // shape of the original array const bool has_input; const bool check_bounds; - legate::PhysicalStore in = legate::PhysicalStore(); + legate::PhysicalStore in{nullptr}; }; class WrapTask : public CuNumericTask { diff --git a/src/cunumeric/index/wrap_template.inl b/src/cunumeric/index/wrap_template.inl index a480ebb4d7..075ba1ed53 100644 --- a/src/cunumeric/index/wrap_template.inl +++ b/src/cunumeric/index/wrap_template.inl @@ -86,7 +86,7 @@ static void wrap_template(TaskContext& context) int dim = shape.dim; bool has_input = context.scalar(1).value(); bool check_bounds = context.scalar(2).value(); - legate::PhysicalStore tmp_array{}; + legate::PhysicalStore tmp_array{nullptr}; WrapArgs args{ context.output(0), shape, has_input, check_bounds, has_input ? context.input(0) : tmp_array}; dim_dispatch(dim, WrapImpl{}, args); diff --git a/src/cunumeric/unary/scalar_unary_red_template.inl b/src/cunumeric/unary/scalar_unary_red_template.inl index 62d70af24f..ff7f39ae1a 100644 --- a/src/cunumeric/unary/scalar_unary_red_template.inl +++ b/src/cunumeric/unary/scalar_unary_red_template.inl @@ -212,7 +212,7 @@ static void scalar_unary_red_template(TaskContext& context) ScalarUnaryRedArgs args{context.reduction(0), context.input(0), - has_where ? context.input(1) : PhysicalStore{}, + has_where ? context.input(1) : PhysicalStore{nullptr}, op_code, shape, std::move(extra_args)}; diff --git a/src/cunumeric/unary/unary_red_template.inl b/src/cunumeric/unary/unary_red_template.inl index 791502c9ab..774bf212f7 100644 --- a/src/cunumeric/unary/unary_red_template.inl +++ b/src/cunumeric/unary/unary_red_template.inl @@ -88,7 +88,7 @@ static void unary_red_template(TaskContext& context) bool has_where = scalars[2].value(); UnaryRedArgs args{reductions[0], inputs[0], - has_where ? inputs[1] : legate::PhysicalStore{}, + has_where ? inputs[1] : legate::PhysicalStore{nullptr}, scalars[0].value(), scalars[1].value()}; if (has_where) { From ddfad324f54a48bef9543cb9b9b6703b5a9e95ac Mon Sep 17 00:00:00 2001 From: sgurfinkel Date: Wed, 15 May 2024 11:43:39 -0400 Subject: [PATCH 199/462] Use the definition of cudaStream_t from core/cuda/cuda.h (#194) --- src/cunumeric/stat/histogram_cpu.h | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/cunumeric/stat/histogram_cpu.h b/src/cunumeric/stat/histogram_cpu.h index f24e8514b1..2aea9e17ae 100644 --- a/src/cunumeric/stat/histogram_cpu.h +++ b/src/cunumeric/stat/histogram_cpu.h @@ -29,12 +29,9 @@ #include #include +#include #include "cunumeric/stat/histogram_gen.h" -#if !LegateDefined(LEGATE_USE_CUDA) -using cudaStream_t = void*; -#endif - namespace cunumeric { namespace detail { From 3927e394296d096cea6e661b8ce9ad1d7cfa14b8 Mon Sep 17 00:00:00 2001 From: sbahirnv <159593068+sbahirnv@users.noreply.github.com> Date: Wed, 15 May 2024 14:12:08 -0700 Subject: [PATCH 200/462] Fix Def. Const. for Legate objects (#196) * Fix Def. Const. for Legate objects --- src/cunumeric/convolution/convolve.h | 4 ++-- src/cunumeric/fft/fft.h | 4 ++-- src/cunumeric/ndarray.cc | 17 +++++++++-------- src/cunumeric/stat/bincount.h | 6 +++--- 4 files changed, 16 insertions(+), 15 deletions(-) diff --git a/src/cunumeric/convolution/convolve.h b/src/cunumeric/convolution/convolve.h index ae0c45b898..d673735ea2 100644 --- a/src/cunumeric/convolution/convolve.h +++ b/src/cunumeric/convolution/convolve.h @@ -30,8 +30,8 @@ namespace cunumeric { struct ConvolveArgs { - legate::PhysicalStore out; - legate::PhysicalStore filter; + legate::PhysicalStore out{nullptr}; + legate::PhysicalStore filter{nullptr}; std::vector inputs; legate::Domain root_domain; }; diff --git a/src/cunumeric/fft/fft.h b/src/cunumeric/fft/fft.h index 2af433a4c4..0310a48518 100644 --- a/src/cunumeric/fft/fft.h +++ b/src/cunumeric/fft/fft.h @@ -22,8 +22,8 @@ namespace cunumeric { struct FFTArgs { - legate::PhysicalStore output; - legate::PhysicalStore input; + legate::PhysicalStore output{nullptr}; + legate::PhysicalStore input{nullptr}; CuNumericFFTType type; CuNumericFFTDirection direction; bool operate_over_axes; diff --git a/src/cunumeric/ndarray.cc b/src/cunumeric/ndarray.cc index 9d2e11a37a..457156dbed 100644 --- a/src/cunumeric/ndarray.cc +++ b/src/cunumeric/ndarray.cc @@ -1135,14 +1135,15 @@ NDArray NDArray::diag_helper(int32_t offset, throw std::invalid_argument("output array has the wrong shape"); } - legate::Type res_type; - if (type) { - res_type = type.value(); - } else if (out) { - res_type = out->type(); - } else { - res_type = store_.type(); - } + auto res_type = [&] { + if (type) { + return type.value(); + } else if (out) { + return out->type(); + } else { + return store_.type(); + } + }(); if (store_.type() != res_type) { a = a.as_type(res_type); diff --git a/src/cunumeric/stat/bincount.h b/src/cunumeric/stat/bincount.h index 9ee40effb9..feead655d3 100644 --- a/src/cunumeric/stat/bincount.h +++ b/src/cunumeric/stat/bincount.h @@ -21,9 +21,9 @@ namespace cunumeric { struct BincountArgs { - legate::PhysicalStore lhs; - legate::PhysicalStore rhs; - legate::PhysicalStore weights; + legate::PhysicalStore lhs{nullptr}; + legate::PhysicalStore rhs{nullptr}; + legate::PhysicalStore weights{nullptr}; bool has_weights; }; From f24594f60ab0b3a033fde2951c555cd8132517ca Mon Sep 17 00:00:00 2001 From: Marcin Zalewski Date: Thu, 16 May 2024 20:07:54 -0700 Subject: [PATCH 201/462] Remove (#197) --- .github/workflows/merge-branches.sh | 50 ----------------------------- .github/workflows/merge-ci.yml | 33 ------------------- 2 files changed, 83 deletions(-) delete mode 100755 .github/workflows/merge-branches.sh delete mode 100644 .github/workflows/merge-ci.yml diff --git a/.github/workflows/merge-branches.sh b/.github/workflows/merge-branches.sh deleted file mode 100755 index 881358cd99..0000000000 --- a/.github/workflows/merge-branches.sh +++ /dev/null @@ -1,50 +0,0 @@ -#!/bin/bash - -main() { - REMOTES="$@"; - if [ -z "$REMOTES" ]; then - REMOTES=$(git remote); - fi - REMOTES=$(echo "$REMOTES" | xargs -n1 echo) - CLB=$(git rev-parse --abbrev-ref HEAD); - echo "$REMOTES" | while read REMOTE; do - git remote update $REMOTE - git branch -r \ - | git branch -r | awk 'BEGIN { FS = "/" };/'"$REMOTE"'/{print $2}' \ - | while read BRANCH; do - if [[ $BRANCH == !(main|branch-*|gh-pages) ]]; then - echo "Skipping branch $BRANCH because it does not match the (main|branch-*) pattern."; - continue; - fi - # first, delete the local branch if there is one - git branch -D $BRANCH 2>/dev/null || true - # checkout the branch tracking from origin or fail if there isn't one yet - git checkout --track origin/$BRANCH 2>/dev/null || true - # reset the branch, or fail if the branch is not checked out - git reset --hard origin/$BRANCH 2>/dev/null || true - ARB="refs/remotes/$REMOTE/$BRANCH"; - ALB="refs/heads/$BRANCH"; - NBEHIND=$(( $(git rev-list --count $ALB..$ARB 2>/dev/null || echo "-1") )); - NAHEAD=$(( $(git rev-list --count $ARB..$ALB 2>/dev/null || true) )); - if [ "$NBEHIND" -gt 0 ]; then - if [ "$NAHEAD" -gt 0 ]; then - echo " branch $BRANCH is $NAHEAD commit(s) ahead of $REMOTE/$BRANCH. Public branches cannot contain internal commits."; - exit 1; - else - echo " branch $BRANCH was $NBEHIND commit(s) behind of $REMOTE/$BRANCH. resetting local branch to remote"; - git reset --hard $REMOTE/$BRANCH >/dev/null; - git push origin $BRANCH - fi - elif [ "$NBEHIND" -eq -1 ]; then - echo " branch $BRANCH does not exist yet. Creating a new branch to track remote"; - git branch -f $BRANCH -t $ARB >/dev/null; - git push origin $BRANCH - else - echo "Nothing to be done for branch $BRANCH" - fi - done - done -} - -main $@ - diff --git a/.github/workflows/merge-ci.yml b/.github/workflows/merge-ci.yml deleted file mode 100644 index 2d0fbf4cb3..0000000000 --- a/.github/workflows/merge-ci.yml +++ /dev/null @@ -1,33 +0,0 @@ -name: merge-public - -on: - workflow_dispatch: - schedule: - - cron: '*/15 * * * *' # Run every 15 mins - -jobs: - merge: - if: ${{ github.repository_owner == 'nv-legate' }} - runs-on: linux-amd64-cpu4 - steps: - - name: Cunumeric Internal repository - uses: actions/checkout@v4 - with: - token: ${{ secrets.NV_LEGATE_INTER_REPOS_ACCESS }} - - - name: Merge the branches - run: | - DEFAULT_BRANCH=$(git remote show origin | awk '/HEAD branch/ {print $NF}') - git fetch origin - git checkout $DEFAULT_BRANCH - git reset --hard origin/$DEFAULT_BRANCH - git config --local user.name 'SyncAction' - git config --local user.email 'sync@nowhere' - git remote add GhCunumericPublic https://github.com/nv-legate/cunumeric.git || true - git branch -a - git remote show origin - git remote set-url --push origin https://github.com/nv-legate/cunumeric.internal.git - git remote show origin - . .github/workflows/merge-branches.sh GhCunumericPublic - git push origin --tags - From e7002a5c11fd6270e6773444a52c70c9f9d8f6b8 Mon Sep 17 00:00:00 2001 From: XiaLuNV <110973296+XiaLuNV@users.noreply.github.com> Date: Fri, 17 May 2024 12:53:38 +0800 Subject: [PATCH 202/462] add random test case (#198) * add random test case --- tests/integration/test_random.py | 99 ++++++++++++++++++++++++++++++++ 1 file changed, 99 insertions(+) create mode 100644 tests/integration/test_random.py diff --git a/tests/integration/test_random.py b/tests/integration/test_random.py new file mode 100644 index 0000000000..91de6c3147 --- /dev/null +++ b/tests/integration/test_random.py @@ -0,0 +1,99 @@ +# Copyright 2022 NVIDIA Corporation +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +import numpy as np +import pytest + +import cunumeric as num + + +def test_basic_num() -> None: + num.random.seed(10) + L1 = num.random.randn(3, 3) + num.random.seed(10) + L2 = num.random.randn(3, 3) + assert not np.array_equal(L1, L2) + + num.random.seed(10) + L1 = num.random.randn(3, 3) + L2 = num.random.randn(3, 3) + assert not np.array_equal(L1, L2) + + +def test_basic_np() -> None: + np.random.seed(10) + L1 = np.random.randn(3, 3) + np.random.seed(10) + L2 = np.random.randn(3, 3) + assert np.array_equal(L1, L2) + + np.random.seed(10) + L1 = np.random.randn(3, 3) + L2 = np.random.randn(3, 3) + assert not np.array_equal(L1, L2) + + +def test_none_num() -> None: + num.random.seed() + L1 = num.random.randn(3, 3) + num.random.seed() + L2 = num.random.randn(3, 3) + assert not np.array_equal(L1, L2) + + num.random.seed() + L1 = num.random.randn(3, 3) + L2 = num.random.randn(3, 3) + assert not np.array_equal(L1, L2) + + +def test_none_np() -> None: + np.random.seed() + L1 = np.random.randn(3, 3) + np.random.seed() + L2 = np.random.randn(3, 3) + assert not np.array_equal(L1, L2) + + np.random.seed() + L1 = np.random.randn(3, 3) + L2 = np.random.randn(3, 3) + assert not np.array_equal(L1, L2) + + +def test_basic_num_np() -> None: + np.random.seed(10) + L1 = np.random.randn(3, 3) + num.random.seed(10) + L2 = num.random.randn(3, 3) + assert not np.array_equal(L1, L2) + + +def test_RandomState() -> None: + rdm_num = num.random.RandomState(10) + L1 = rdm_num.randn(3, 3) + rdm_np = np.random.RandomState(10) + L2 = rdm_np.randn(3, 3) + assert np.array_equal(L1, L2) + + +def test_float() -> None: + with pytest.raises(TypeError): + np.random.seed(10.5) + # TypeError: 'float' object cannot be interpreted as an integer + num.random.seed(10.5) + # cuNumeric passed with float + + +if __name__ == "__main__": + import sys + + sys.exit(pytest.main(sys.argv)) From 7500d3e5d403559a5569b9a448c099cbc8b3c8fa Mon Sep 17 00:00:00 2001 From: Marcin Zalewski Date: Fri, 17 May 2024 15:25:28 -0700 Subject: [PATCH 203/462] Move legate.core commit to the latest one and fix CI (#189) --- .github/workflows/ci-gh.yml | 30 --------- .github/workflows/gh-build-and-test.yml | 16 +++-- README.md | 4 +- cmake/versions.json | 2 +- conda/conda-build/build.sh | 14 ++-- conda/conda-build/conda_build_config.yaml | 4 ++ conda/conda-build/meta.yaml | 66 +++++++------------ .../scripts/build-cunumeric-conda | 10 ++- continuous_integration/scripts/make-conda-env | 9 ++- docs/cunumeric/source/user/installation.rst | 2 +- scripts/conda-build.sh | 3 +- setup.py | 1 + versioneer.py | 2 +- 13 files changed, 65 insertions(+), 98 deletions(-) delete mode 100644 .github/workflows/ci-gh.yml diff --git a/.github/workflows/ci-gh.yml b/.github/workflows/ci-gh.yml deleted file mode 100644 index 8d2e108a7c..0000000000 --- a/.github/workflows/ci-gh.yml +++ /dev/null @@ -1,30 +0,0 @@ -name: Build and test - -concurrency: - group: ci-build-and-test-on-${{ github.event_name }}-from-${{ github.ref_name }} - cancel-in-progress: true - -on: - workflow_dispatch: - push: - branches: - - "pull-request/[0-9]+" - - "cpp-branch-*" - -jobs: - build-and-test: - strategy: - fail-fast: false - matrix: - target-device: - - gpu - - cpu - uses: - ./.github/workflows/gh-build-and-test.yml - with: - target-device: ${{ matrix.target-device }} - platform: linux - build-type: ci - upload-enabled: false - dependencies-workflow: "ci-gh.yml" - secrets: inherit diff --git a/.github/workflows/gh-build-and-test.yml b/.github/workflows/gh-build-and-test.yml index fb79f48635..c6666c47eb 100644 --- a/.github/workflows/gh-build-and-test.yml +++ b/.github/workflows/gh-build-and-test.yml @@ -29,7 +29,7 @@ jobs: run: | if [ "${{ inputs.platform }}" = "linux" ]; then if [ "${{ github.repository_owner }}" = "nv-legate" ]; then - echo "runner_type=linux-amd64-cpu32" >> $GITHUB_OUTPUT + echo "runner_type=linux-amd64-cpu16" >> $GITHUB_OUTPUT else echo "runner_type=ubuntu-latest" >> $GITHUB_OUTPUT fi @@ -41,7 +41,7 @@ jobs: needs: setup-build name: "Build (${{ inputs.platform }}, ${{ inputs.target-device }}, ${{ inputs.build-type }})" uses: - nv-legate/legate-gh-ci/.github/workflows/gh-build.yml@v1.1.4 + nv-legate/legate-gh-ci/.github/workflows/gh-build.yml@v1.4 with: client-repo: ${{ github.event.repository.name }} target-device: ${{ inputs.target-device }} @@ -51,7 +51,7 @@ jobs: platform: ${{ inputs.platform }} dependencies-file: "cmake/versions.json" dependencies-workflow: ${{ inputs.dependencies-workflow }} - legate-gh-ci-tag: "v1.1.4" + legate-gh-ci-tag: "v1.4" build-mode: "" ucx-enabled: false upload-enabled: ${{ inputs.upload-enabled }} @@ -128,14 +128,16 @@ jobs: MATRIX_JSON='{"include": [' RUNNERS=( 'linux-amd64-gpu-v100-latest-1:gpu:gpu:linux' 'linux-amd64-2gpu:gpu:2gpu:linux' - 'linux-amd64-cpu32:cpu:cpu:linux' + 'linux-amd64-cpu16:cpu:cpu:linux' 'macos-latest:cpu:cpu:mac') TEST_CONFIGS=( '1 CPU test:test --cpus 1 --debug:cpu' '1 CPU test:test --cpus 1 --debug:gpu' '2 CPU test:test --cpus 2 --debug:cpu' '2 CPU test:test --cpus 2 --debug:gpu' - 'GPU test:test --use cuda --gpus 1 --debug:gpu' + # set the number of workers manually because nvidia runners report 6 gpus when onyl one is really available + # this workaround can be removed when the number of available gpus is reported correctly (when we run on VMs) + 'GPU test:test --use cuda --gpus 1 -j 7 --debug:gpu' '2 GPU test:test --use cuda --gpus 2 --debug:2gpu' 'OpenMP test:test --use openmp --omps 1 --ompthreads 2 --debug:gpu' 'OpenMP test:test --use openmp --omps 1 --ompthreads 2 --debug:cpu' @@ -179,7 +181,7 @@ jobs: matrix: ${{fromJson(needs.setup-test.outputs.matrix)}} uses: - nv-legate/legate-gh-ci/.github/workflows/gh-test-within-container.yml@v1.1.4 + nv-legate/legate-gh-ci/.github/workflows/gh-test-within-container.yml@v1.4 with: client-repo: ${{ github.event.repository.name }} build-type: ${{ inputs.build-type }} @@ -189,7 +191,7 @@ jobs: has-gpu: ${{ matrix.runner.type == 'gpu' }} test-options: ${{ matrix.test-config.test-options }} platform: ${{ inputs.platform }} - legate-gh-ci-tag: "v1.1.4" + legate-gh-ci-tag: "v1.4" build-mode: "" ucx-enabled: false upload-enabled: ${{ inputs.upload-enabled }} diff --git a/README.md b/README.md index 0f2571fe4a..b533807e2c 100644 --- a/README.md +++ b/README.md @@ -43,13 +43,13 @@ Please make sure you have at least conda version 24.1 installed, then create a new environment containing cuNumeric: ``` -conda create -n myenv -c nvidia -c conda-forge -c legate cunumeric +conda create -n myenv -c conda-forge -c legate cunumeric ``` or install it into an existing environment: ``` -conda install -c nvidia -c conda-forge -c legate cunumeric +conda install -c conda-forge -c legate cunumeric ``` Once installed, you can verify the installation by running one of the examples diff --git a/cmake/versions.json b/cmake/versions.json index a8081dbcd0..274a12efa9 100644 --- a/cmake/versions.json +++ b/cmake/versions.json @@ -7,7 +7,7 @@ "git_url" : "https://github.com/nv-legate/legate.core.internal.git", "git_shallow": false, "always_download": false, - "git_tag" : "94dfb658c5f7da396dc81ae9b9401f76c58d6a08" + "git_tag" : "80862530505143380365b4e40aa90ca935a0f8f7" } } } diff --git a/conda/conda-build/build.sh b/conda/conda-build/build.sh index 77704eb2b7..811b5c6c39 100644 --- a/conda/conda-build/build.sh +++ b/conda/conda-build/build.sh @@ -2,6 +2,8 @@ echo -e "\n\n--------------------- CONDA/CONDA-BUILD/BUILD.SH -----------------------\n" +set -xeo pipefail; + # Rewrite conda's -DCMAKE_FIND_ROOT_PATH_MODE_INCLUDE=ONLY to # -DCMAKE_FIND_ROOT_PATH_MODE_INCLUDE=BOTH CMAKE_ARGS="$(echo "$CMAKE_ARGS" | sed -r "s@_INCLUDE=ONLY@_INCLUDE=BOTH@g")" @@ -12,21 +14,18 @@ CMAKE_ARGS+=" -DBUILD_SHARED_LIBS=ON -DBUILD_MARCH=${BUILD_MARCH} -DCMAKE_BUILD_TYPE=Release --DCMAKE_BUILD_PARALLEL_LEVEL=${JOBS:-$(nproc --ignore=1)} -" +-DCMAKE_BUILD_PARALLEL_LEVEL=${JOBS:-$(nproc --ignore=1)}" # We rely on an environment variable to determine if we need to build cpu-only bits if [ -z "$CPU_ONLY" ]; then # cutensor, relying on the conda cutensor package CMAKE_ARGS+=" -Dcutensor_DIR=$PREFIX --DCMAKE_CUDA_ARCHITECTURES=RAPIDS -" +-DCMAKE_CUDA_ARCHITECTURES=RAPIDS" else # When we build without cuda, we need to provide the location of curand CMAKE_ARGS+=" --Dcunumeric_cuRAND_INCLUDE_DIR=$PREFIX -" +-Dcunumeric_cuRAND_INCLUDE_DIR=$PREFIX/targets/x86_64-linux/include" fi export CMAKE_GENERATOR=Ninja @@ -46,8 +45,7 @@ cmake --install build CMAKE_ARGS=" -DFIND_CUNUMERIC_CPP=ON --Dcunumeric_ROOT=$PREFIX -" +-Dcunumeric_ROOT=$PREFIX" SKBUILD_BUILD_OPTIONS=-j$CPU_COUNT \ $PYTHON -m pip install \ diff --git a/conda/conda-build/conda_build_config.yaml b/conda/conda-build/conda_build_config.yaml index a72f122a58..6fbea9a911 100644 --- a/conda/conda-build/conda_build_config.yaml +++ b/conda/conda-build/conda_build_config.yaml @@ -2,6 +2,10 @@ gpu_enabled: - true - false +upload_build: + - true + - false + python: - 3.10 - 3.11 diff --git a/conda/conda-build/meta.yaml b/conda/conda-build/meta.yaml index c62d4c0ac1..28c68688b2 100644 --- a/conda/conda-build/meta.yaml +++ b/conda/conda-build/meta.yaml @@ -18,7 +18,7 @@ ## The placeholder version is strictly for making two-pass conda build process. ## It should not be used for any other purpose, and this is not a default version. {% set placeholder_version = '0.0.0.dev' %} -{% set default_cuda_version = '12.2' %} +{% set default_cuda_version = '12.2.2' %} {% set cuda_version='.'.join(environ.get('CUDA', default_cuda_version).split('.')[:2]) %} {% set cuda_major=cuda_version.split('.')[0]|int %} {% set py_version=environ.get('CONDA_PY', '') %} @@ -61,10 +61,10 @@ build: number: {{ build_number }} missing_dso_whitelist: - '*libcuda.so*' -{% if not gpu_enabled_bool %} -{% set cpu_tag='_cpu' %} -{% else %} +{% if gpu_enabled_bool %} {% set cpu_tag='' %} +{% else %} +{% set cpu_tag='_cpu' %} {% endif %} {% if upload_build_bool %} {% set upload_tag='_nightly' %} @@ -93,15 +93,14 @@ build: - CPU_ONLY=1 track_features: - cpu_only -{% else %} -# prevent nccl from pulling in cudatoolkit - ignore_run_exports: - - cudatoolkit - ignore_run_exports_from: - - cuda-nvcc - - legate-core {% endif %} +ignore_run_exports_from: + # scikit-build should really be a part of the build env, but then it installs its own Python. Conda build stacks + # the build environment on the host environment, and the build python takes over causing paths havoc. So, we put + # scikit-build into the host env, but we ignore any exports it may bring. + - scikit-build + requirements: build: - make @@ -109,59 +108,42 @@ requirements: - cmake {{ cmake_version }} - {{ compiler('c') }} =11.2 - {{ compiler('cxx') }} =11.2 - host: - # the nvcc requirement is necessary because it contains crt/host_config.h used by cuda runtime. This is a packaging bug that has been reported. + # the nvcc requirement is necessary because it contains crt/host_config.h used by cuda runtime. This is a packaging bug that has been reported. + - cuda-nvcc ={{ cuda_version }} - # libcurand is used both in CPU and GPU builds - - libcurand-dev # cudart needed for CPU and GPU builds because of curand + - cuda-cudart-dev ={{ cuda_version }} + + + host: - python - scikit-build + # libcurand is used both in CPU and GPU builds + - libcurand-dev - openblas =* =*openmp* -{% if not gpu_enabled_bool %} - - legate-core ={{ core_version }} =*_cpu* -{% else %} - - legate-core ={{ core_version }} - - cuda-nvcc ={{ cuda_version }} - - cuda-cccl ={{ cuda_version }} - - cuda-cudart ={{ cuda_version }} - - cuda-cudart-static ={{ cuda_version }} - - cuda-driver-dev ={{ cuda_version }} - - cuda-cudart-dev ={{ cuda_version }} - - cuda-nvtx ={{ cuda_version }} - # - libcutensor-dev >=1.3 +{% if gpu_enabled_bool %} + - legate-core >={{ core_version }} + - cuda-cccl - cutensor >=1.3,<2.0 =*_* - libcublas-dev - libcusolver-dev - libcufft-dev - - nccl +{% else %} + - legate-core >={{ core_version }} =*_cpu* {% endif %} run: - numpy {{ numpy_version }} - - libopenblas =* =*openmp* -{% if not gpu_enabled_bool %} - - legate-core ={{ core_version }} =*_cpu* -{% else %} - - legate-core ={{ core_version }} - - cuda-cudart >={{ cuda_version }},<{{ cuda_major+1 }} - - cuda-version >={{ cuda_version }},<{{ cuda_major+1 }} - - cutensor >=1.3 =*_* - - libcublas - - libcusolver >=11.4.1.48-0 - - libcufft - libnvjitlink - libcusparse -{% endif %} - opt_einsum >=3.3 - scipy - - typing_extensions run_constrained: - __glibc >=2.17 # [linux] {% if gpu_enabled_bool %} - - __cuda >={{ cuda_version }} + - __cuda {% endif %} about: diff --git a/continuous_integration/scripts/build-cunumeric-conda b/continuous_integration/scripts/build-cunumeric-conda index 1a4f91c381..09e2510a4b 100755 --- a/continuous_integration/scripts/build-cunumeric-conda +++ b/continuous_integration/scripts/build-cunumeric-conda @@ -15,7 +15,8 @@ build_cunumeric_conda_package() { local conda_build_args=(); conda_build_args+=(--override-channels); conda_build_args+=(-c conda-forge); - conda_build_args+=(-c nvidia); + # the ucx channel is only necessary as a WAR until the real ucx 1.17 package is available on conda-forge + conda_build_args+=(-c https://github.com/nv-legate/ucx-package/raw/main); conda_build_args+=(-c file:///tmp/conda-build/legate_core); conda_build_args+=(--croot /tmp/conda-build/cunumeric); conda_build_args+=(--numpy 1.22); @@ -78,6 +79,13 @@ EOF git -C "${REPO_DIR}" commit --allow-empty --allow-empty-message -n -m ""; # Build cuNumeric conda package + set +ux + eval "$(conda shell.bash hook)" + conda deactivate + conda create -n build + conda activate build + set -ux + conda install boa CUDA=${CUDA_VERSION} \ conda mambabuild ${conda_build_args[@]} "${REPO_DIR}/conda/conda-build"; diff --git a/continuous_integration/scripts/make-conda-env b/continuous_integration/scripts/make-conda-env index e82838f85e..76e07374d2 100755 --- a/continuous_integration/scripts/make-conda-env +++ b/continuous_integration/scripts/make-conda-env @@ -8,6 +8,9 @@ make_ci_env() { set -xeuo pipefail yaml_file=$(find "${ARTIFACTS_DIR}" -name "environment*.yaml" | head -n 1) + sed -i '$ d' ${yaml_file} + echo " - legate-core" >> "${yaml_file}" + sed -i "/channels:/!b;:a;n;/^- /ba;i\- ${ARTIFACTS_DIR}/conda-build/legate_core" ${yaml_file} [ "${USE_CUDA}" = "ON" ] && echo " - libcublas-dev" >> "${yaml_file}" && echo " - libcufft-dev" >> "${yaml_file}" && @@ -22,10 +25,10 @@ make_ci_env() { cp "${yaml_file}" /tmp/out mamba env create -n legate -f "$yaml_file" +} - mamba uninstall -yn legate numpy - - mamba install -yn legate -c "${ARTIFACTS_DIR}/conda-build/legate_core" -c conda-forge -c nvidia legate-core +make_release_env() { + mamba create -q -y -n "${CONDA_ENV}" -c conda-forge boa } make_conda_env() { diff --git a/docs/cunumeric/source/user/installation.rst b/docs/cunumeric/source/user/installation.rst index b06baa771d..698259b2e4 100644 --- a/docs/cunumeric/source/user/installation.rst +++ b/docs/cunumeric/source/user/installation.rst @@ -9,7 +9,7 @@ a new environment containing cuNumeric: .. code-block:: sh - conda install -c nvidia -c conda-forge -c legate cunumeric + conda install -c conda-forge -c legate cunumeric Once installed, you can verify the installation by running one of the examples from the cuNumeric repository, for instance: diff --git a/scripts/conda-build.sh b/scripts/conda-build.sh index 10607067ae..47a4528274 100755 --- a/scripts/conda-build.sh +++ b/scripts/conda-build.sh @@ -12,9 +12,8 @@ PYTHON_VERSION="${PYTHON_VERSION:-3.10}" CUDA="$(nvcc --version | head -n4 | tail -n1 | cut -d' ' -f5 | cut -d',' -f1).*" \ conda mambabuild \ --numpy 1.22 \ - --python $PYTHON_VERSION \ --override-channels \ - -c conda-forge -c nvidia \ + -c conda-forge -c https://github.com/nv-legate/ucx-package/raw/main \ -c file:///tmp/conda-build/legate_core \ --croot /tmp/conda-build/cunumeric \ --no-test \ diff --git a/setup.py b/setup.py index 16db8038db..b2ce671d8d 100644 --- a/setup.py +++ b/setup.py @@ -35,6 +35,7 @@ "Programming Language :: Python", "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", + "Programming Language :: Python :: 3.12", ], packages=find_packages( where=".", diff --git a/versioneer.py b/versioneer.py index 159ace09b2..4470e3562d 100644 --- a/versioneer.py +++ b/versioneer.py @@ -11,7 +11,7 @@ * https://github.com/python-versioneer/python-versioneer * Brian Warner * License: Public Domain -* Compatible with: Python 3.6, 3.7, 3.8, 3.9, 3.10 and pypy3 +* Compatible with: Python 3.10, 3.11, 3.12 and pypy3 * [![Latest Version][pypi-image]][pypi-url] * [![Build Status][travis-image]][travis-url] From ac86db3215a9cbeb0e134f62bcd17eff391abf3a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Malte=20F=C3=B6rster?= <97973773+mfoerste4@users.noreply.github.com> Date: Wed, 22 May 2024 18:07:08 +0200 Subject: [PATCH 204/462] Single GPU/CPU QR support (#165) * add single gpu/cpu QR * review suggestions --- cunumeric/_thunk/deferred.py | 5 + cunumeric/_thunk/eager.py | 14 ++ cunumeric/_thunk/thunk.py | 4 + cunumeric/config.py | 2 + cunumeric/linalg/_qr.py | 50 +++++++ cunumeric/linalg/linalg.py | 73 +++++++++++ cunumeric_cpp.cmake | 3 + docs/cunumeric/source/api/linalg.rst | 1 + examples/qr.py | 94 +++++++++++++ src/cunumeric/cunumeric_c.h | 1 + src/cunumeric/mapper.cc | 1 + src/cunumeric/matrix/qr.cc | 40 ++++++ src/cunumeric/matrix/qr.cu | 189 +++++++++++++++++++++++++++ src/cunumeric/matrix/qr.h | 38 ++++++ src/cunumeric/matrix/qr_cpu.inl | 131 +++++++++++++++++++ src/cunumeric/matrix/qr_omp.cc | 31 +++++ src/cunumeric/matrix/qr_template.inl | 102 +++++++++++++++ tests/integration/test_qr.py | 110 ++++++++++++++++ tests/unit/cunumeric/test_config.py | 1 + 19 files changed, 890 insertions(+) create mode 100644 cunumeric/linalg/_qr.py create mode 100644 examples/qr.py create mode 100644 src/cunumeric/matrix/qr.cc create mode 100644 src/cunumeric/matrix/qr.cu create mode 100644 src/cunumeric/matrix/qr.h create mode 100644 src/cunumeric/matrix/qr_cpu.inl create mode 100644 src/cunumeric/matrix/qr_omp.cc create mode 100644 src/cunumeric/matrix/qr_template.inl create mode 100644 tests/integration/test_qr.py diff --git a/cunumeric/_thunk/deferred.py b/cunumeric/_thunk/deferred.py index 007d90be4c..c0970476c8 100644 --- a/cunumeric/_thunk/deferred.py +++ b/cunumeric/_thunk/deferred.py @@ -65,6 +65,7 @@ UnaryRedCode, ) from ..linalg._cholesky import cholesky_deferred +from ..linalg._qr import qr_deferred from ..linalg._solve import solve_deferred from ..runtime import runtime from ._sort import sort_deferred @@ -3354,6 +3355,10 @@ def compute_strides(shape: NdShape) -> tuple[int, ...]: def cholesky(self, src: Any, no_tril: bool = False) -> None: cholesky_deferred(self, src, no_tril) + @auto_convert("q", "r") + def qr(self, q: Any, r: Any) -> None: + qr_deferred(self, q, r) + @auto_convert("a", "b") def solve(self, a: Any, b: Any) -> None: solve_deferred(self, a, b) diff --git a/cunumeric/_thunk/eager.py b/cunumeric/_thunk/eager.py index ea89440f71..a144cb4f81 100644 --- a/cunumeric/_thunk/eager.py +++ b/cunumeric/_thunk/eager.py @@ -1668,6 +1668,20 @@ def cholesky(self, src: Any, no_tril: bool) -> None: result = np.triu(result.T.conj(), k=1) + result self.array[:] = result + def qr(self, q: Any, r: Any) -> None: + self.check_eager_args(q, r) + if self.deferred is not None: + self.deferred.qr(q, r) + else: + try: + result_q, result_r = np.linalg.qr(self.array) + except np.linalg.LinAlgError as e: + from ..linalg import LinAlgError + + raise LinAlgError(e) from e + q.array[:] = result_q + r.array[:] = result_r + def solve(self, a: Any, b: Any) -> None: self.check_eager_args(a, b) if self.deferred is not None: diff --git a/cunumeric/_thunk/thunk.py b/cunumeric/_thunk/thunk.py index 97c382780b..94e1d8f7e3 100644 --- a/cunumeric/_thunk/thunk.py +++ b/cunumeric/_thunk/thunk.py @@ -700,6 +700,10 @@ def where(self, rhs1: Any, rhs2: Any, rhs3: Any) -> None: def cholesky(self, src: Any, no_tril: bool) -> None: ... + @abstractmethod + def qr(self, q: Any, r: Any) -> None: + ... + @abstractmethod def solve(self, a: Any, b: Any) -> None: ... diff --git a/cunumeric/config.py b/cunumeric/config.py index 9e7ec560f3..5920f20e64 100644 --- a/cunumeric/config.py +++ b/cunumeric/config.py @@ -177,6 +177,7 @@ class _CunumericSharedLib: CUNUMERIC_PACKBITS: int CUNUMERIC_POTRF: int CUNUMERIC_PUTMASK: int + CUNUMERIC_QR: int CUNUMERIC_RAND: int CUNUMERIC_READ: int CUNUMERIC_RED_ALL: int @@ -386,6 +387,7 @@ class CuNumericOpCode(IntEnum): PACKBITS = _cunumeric.CUNUMERIC_PACKBITS POTRF = _cunumeric.CUNUMERIC_POTRF PUTMASK = _cunumeric.CUNUMERIC_PUTMASK + QR = _cunumeric.CUNUMERIC_QR RAND = _cunumeric.CUNUMERIC_RAND READ = _cunumeric.CUNUMERIC_READ REPEAT = _cunumeric.CUNUMERIC_REPEAT diff --git a/cunumeric/linalg/_qr.py b/cunumeric/linalg/_qr.py new file mode 100644 index 0000000000..aa2c38e1cb --- /dev/null +++ b/cunumeric/linalg/_qr.py @@ -0,0 +1,50 @@ +# Copyright 2024 NVIDIA Corporation +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +from __future__ import annotations + +from typing import TYPE_CHECKING + +from legate.core import get_legate_runtime + +from cunumeric.config import CuNumericOpCode + +from ._exception import LinAlgError + +if TYPE_CHECKING: + from legate.core import Library, LogicalStore + + from .._thunk.deferred import DeferredArray + + +def qr_single( + library: Library, a: LogicalStore, q: LogicalStore, r: LogicalStore +) -> None: + task = get_legate_runtime().create_auto_task(library, CuNumericOpCode.QR) + task.throws_exception(LinAlgError) + task.add_input(a) + task.add_output(q) + task.add_output(r) + + task.add_broadcast(a) + task.add_broadcast(q) + task.add_broadcast(r) + + task.execute() + + +def qr_deferred(a: DeferredArray, q: DeferredArray, r: DeferredArray) -> None: + library = a.library + + qr_single(library, a.base, q.base, r.base) diff --git a/cunumeric/linalg/linalg.py b/cunumeric/linalg/linalg.py index 06e0042c82..0f30040d26 100644 --- a/cunumeric/linalg/linalg.py +++ b/cunumeric/linalg/linalg.py @@ -89,6 +89,58 @@ def cholesky(a: ndarray) -> ndarray: return _thunk_cholesky(a) +@add_boilerplate("a") +def qr(a: ndarray) -> tuple[ndarray, ...]: + """ + Compute the qr factorization of a matrix. + + Factor the matrix a as qr, where q is orthonormal + and r is upper-triangular. + + Parameters + ---------- + a : (M, N) array_like + Array like, at least dimension 2. + + Returns + ------- + q : (M, K) array_like + A matrix with orthonormal columns. K = min(M, N). + r : (K, N) array_like + The uppoer triangular matrix. + + Raises + ------ + LinAlgError + If factoring fails. + + Notes + ----- + Currently does not support the parameter 'mode' from numpy 1.8. + + See Also + -------- + numpy.linalg.qr + + Availability + -------- + Single GPU, Single CPU + """ + shape = a.shape + if len(shape) < 2: + raise LinAlgError( + f"{len(shape)}-dimensional array given. " + "Array must be at least two-dimensional" + ) + if len(shape) > 2: + raise NotImplementedError( + "cuNumeric does not yet support stacked 2d arrays" + ) + if np.dtype("e") == a.dtype: + raise TypeError("array type float16 is unsupported in linalg") + return _thunk_qr(a) + + @add_boilerplate("a", "b") def solve(a: ndarray, b: ndarray, out: ndarray | None = None) -> ndarray: """ @@ -639,6 +691,27 @@ def _thunk_cholesky(a: ndarray, no_tril: bool = False) -> ndarray: return output +def _thunk_qr(a: ndarray) -> tuple[ndarray, ...]: + if a.dtype.kind not in ("f", "c"): + a = a.astype("float64") + + k = min(a.shape[0], a.shape[1]) + + out_q = ndarray( + shape=(a.shape[0], k), + dtype=a.dtype, + inputs=(a,), + ) + out_r = ndarray( + shape=(k, a.shape[1]), + dtype=a.dtype, + inputs=(a,), + ) + + a._thunk.qr(out_q._thunk, out_r._thunk) + return out_q, out_r + + def _thunk_solve( a: ndarray, b: ndarray, output: ndarray | None = None ) -> ndarray: diff --git a/cunumeric_cpp.cmake b/cunumeric_cpp.cmake index 001053b335..f13993187b 100644 --- a/cunumeric_cpp.cmake +++ b/cunumeric_cpp.cmake @@ -153,6 +153,7 @@ list(APPEND cunumeric_SOURCES src/cunumeric/matrix/matvecmul.cc src/cunumeric/matrix/dot.cc src/cunumeric/matrix/potrf.cc + src/cunumeric/matrix/qr.cc src/cunumeric/matrix/solve.cc src/cunumeric/matrix/syrk.cc src/cunumeric/matrix/tile.cc @@ -211,6 +212,7 @@ if(Legion_USE_OpenMP) src/cunumeric/matrix/matvecmul_omp.cc src/cunumeric/matrix/dot_omp.cc src/cunumeric/matrix/potrf_omp.cc + src/cunumeric/matrix/qr_omp.cc src/cunumeric/matrix/solve_omp.cc src/cunumeric/matrix/syrk_omp.cc src/cunumeric/matrix/tile_omp.cc @@ -263,6 +265,7 @@ if(Legion_USE_CUDA) src/cunumeric/matrix/matvecmul.cu src/cunumeric/matrix/dot.cu src/cunumeric/matrix/potrf.cu + src/cunumeric/matrix/qr.cu src/cunumeric/matrix/solve.cu src/cunumeric/matrix/syrk.cu src/cunumeric/matrix/tile.cu diff --git a/docs/cunumeric/source/api/linalg.rst b/docs/cunumeric/source/api/linalg.rst index 78394ead04..28ce88d118 100644 --- a/docs/cunumeric/source/api/linalg.rst +++ b/docs/cunumeric/source/api/linalg.rst @@ -29,6 +29,7 @@ Decompositions :toctree: generated/ linalg.cholesky + linalg.qr Norms and other numbers ----------------------- diff --git a/examples/qr.py b/examples/qr.py new file mode 100644 index 0000000000..07bd57fdf2 --- /dev/null +++ b/examples/qr.py @@ -0,0 +1,94 @@ +#!/usr/bin/env python + +# Copyright 2024 NVIDIA Corporation +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +import argparse + +from benchmark import parse_args, run_benchmark + + +def check_result(a, q, r): + print("Checking result...") + + if num.allclose(a, num.matmul(q, r)): + print("PASS!") + else: + print("FAIL!") + + +def qr(m, n, dtype, perform_check, timing): + a = num.random.rand(m, n).astype(dtype=dtype) + + timer.start() + q, r = num.linalg.qr(a) + total = timer.stop() + + if perform_check: + check_result(a, q, r) + + if timing: + print(f"Elapsed Time: {total} ms") + + return total + + +if __name__ == "__main__": + parser = argparse.ArgumentParser() + parser.add_argument( + "-t", + "--time", + dest="timing", + action="store_true", + help="perform timing", + ) + parser.add_argument( + "-m", + "--rows", + type=int, + default=10, + dest="m", + help="number of rows in the matrix", + ) + parser.add_argument( + "-n", + "--cols", + type=int, + default=10, + dest="n", + help="number of cols in the matrix", + ) + parser.add_argument( + "-d", + "--dtype", + default="float64", + choices=["float32", "float64", "complex64", "complex128"], + dest="dtype", + help="data type", + ) + parser.add_argument( + "--check", + dest="check", + action="store_true", + help="compare result to numpy", + ) + args, num, timer = parse_args(parser) + + run_benchmark( + qr, + args.benchmark, + "QR", + (args.m, args.n, args.dtype, args.check, args.timing), + ) diff --git a/src/cunumeric/cunumeric_c.h b/src/cunumeric/cunumeric_c.h index 07e8fc6f72..a4de0478a5 100644 --- a/src/cunumeric/cunumeric_c.h +++ b/src/cunumeric/cunumeric_c.h @@ -57,6 +57,7 @@ enum CuNumericOpCode { CUNUMERIC_PACKBITS, CUNUMERIC_POTRF, CUNUMERIC_PUTMASK, + CUNUMERIC_QR, CUNUMERIC_RAND, CUNUMERIC_READ, CUNUMERIC_REPEAT, diff --git a/src/cunumeric/mapper.cc b/src/cunumeric/mapper.cc index c3ac9a99d5..8fe2990d6a 100644 --- a/src/cunumeric/mapper.cc +++ b/src/cunumeric/mapper.cc @@ -125,6 +125,7 @@ std::vector CuNumericMapper::store_mappings( return mappings; } case CUNUMERIC_POTRF: + case CUNUMERIC_QR: case CUNUMERIC_TRSM: case CUNUMERIC_SOLVE: case CUNUMERIC_SYRK: diff --git a/src/cunumeric/matrix/qr.cc b/src/cunumeric/matrix/qr.cc new file mode 100644 index 0000000000..ca75278419 --- /dev/null +++ b/src/cunumeric/matrix/qr.cc @@ -0,0 +1,40 @@ +/* Copyright 2024 NVIDIA Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +#include "cunumeric/matrix/qr.h" +#include "cunumeric/matrix/qr_template.inl" +#include "cunumeric/matrix/qr_cpu.inl" + +namespace cunumeric { + +using namespace legate; + +/*static*/ const char* QrTask::ERROR_MESSAGE = "Factorization failed"; + +/*static*/ void QrTask::cpu_variant(TaskContext context) +{ +#if LegateDefined(LEGATE_USE_OPENMP) + openblas_set_num_threads(1); // make sure this isn't overzealous +#endif + qr_template(context); +} + +namespace // unnamed +{ +static void __attribute__((constructor)) register_tasks(void) { QrTask::register_variants(); } +} // namespace + +} // namespace cunumeric diff --git a/src/cunumeric/matrix/qr.cu b/src/cunumeric/matrix/qr.cu new file mode 100644 index 0000000000..dc9b28602d --- /dev/null +++ b/src/cunumeric/matrix/qr.cu @@ -0,0 +1,189 @@ +/* Copyright 2024 NVIDIA Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +#include "cunumeric/matrix/qr.h" +#include "cunumeric/matrix/qr_template.inl" + +#include "cunumeric/cuda_help.h" +#include +namespace cunumeric { + +using namespace legate; + +template +static inline void qr_template(GeqrfBufferSize geqrf_buffer_size, + OrgqrBufferSize orgqr_buffer_size, + Geqrf geqrf, + Orgqr orgqr, + int32_t m, + int32_t n, + int32_t k, + const VAL* a, + VAL* q, + VAL* r) +{ + auto handle = get_cusolver(); + auto stream = get_cached_stream(); + + // m>=n : a[m][n], q[m][n] r[n][n] + // m make tmp buffer + if (m < n) { + auto q_copy = create_buffer(m * n, Memory::Kind::GPU_FB_MEM); + q_tmp = q_copy.ptr(0); + } + + LegateCheckCUDA(cudaMemcpyAsync(q_tmp, a, sizeof(VAL) * m * n, cudaMemcpyDeviceToDevice, stream)); + LegateCheckCUDA(cudaStreamSynchronize(stream)); + + CHECK_CUSOLVER(cusolverDnSetStream(handle, stream)); + + auto tau = create_buffer(k, Memory::Kind::GPU_FB_MEM); + auto info = create_buffer(1, Memory::Kind::Z_COPY_MEM); + + // compute and alloc buffer for geqrf + int32_t lwork_geqrf, lwork_orgqr; + CHECK_CUSOLVER(geqrf_buffer_size(handle, m, n, q_tmp, m, &lwork_geqrf)); + CHECK_CUSOLVER(orgqr_buffer_size(handle, m, n, k, q_tmp, m, tau.ptr(0), &lwork_orgqr)); + int32_t lwork_total = std::max(lwork_geqrf, lwork_orgqr); + + auto buffer = create_buffer(lwork_total, Memory::Kind::GPU_FB_MEM); + + CHECK_CUSOLVER( + geqrf(handle, m, n, q_tmp, m, tau.ptr(0), buffer.ptr(0), lwork_total, info.ptr(0))); + LegateCheckCUDA(cudaStreamSynchronize(stream)); + + if (info[0] != 0) { + throw legate::TaskException(QrTask::ERROR_MESSAGE); + } + + // extract R from upper triangular of geqrf result + LegateCheckCUDA(cudaMemsetAsync(r, 0, k * n * sizeof(VAL), stream)); + for (int i = 0; i < k; ++i) { + int elements = i + 1; + if (i == k - 1 && n > k) { + elements = k * (n - k + 1); + } + LegateCheckCUDA(cudaMemcpyAsync( + r + i * k, q_tmp + i * m, sizeof(VAL) * elements, cudaMemcpyDeviceToDevice, stream)); + } + + // assemble Q + CHECK_CUSOLVER( + orgqr(handle, m, k, k, q_tmp, m, tau.ptr(0), buffer.ptr(0), lwork_total, info.ptr(0))); + LegateCheckCUDA(cudaStreamSynchronize(stream)); + + if (info[0] != 0) { + throw legate::TaskException(QrTask::ERROR_MESSAGE); + } + + // if we used a tmp storage we still need to copy back Q + if (q_tmp != q) { + assert(n > m); + LegateCheckCUDA( + cudaMemcpyAsync(q, q_tmp, sizeof(VAL) * m * m, cudaMemcpyDeviceToDevice, stream)); + } + + LegateCheckCUDAStream(stream); + +#ifdef DEBUG_CUNUMERIC + assert(info[0] == 0); +#endif +} + +template <> +struct QrImplBody { + void operator()(int32_t m, int32_t n, int32_t k, const float* a, float* q, float* r) + { + qr_template(cusolverDnSgeqrf_bufferSize, + cusolverDnSorgqr_bufferSize, + cusolverDnSgeqrf, + cusolverDnSorgqr, + m, + n, + k, + a, + q, + r); + } +}; + +template <> +struct QrImplBody { + void operator()(int32_t m, int32_t n, int32_t k, const double* a, double* q, double* r) + { + qr_template(cusolverDnDgeqrf_bufferSize, + cusolverDnDorgqr_bufferSize, + cusolverDnDgeqrf, + cusolverDnDorgqr, + m, + n, + k, + a, + q, + r); + } +}; + +template <> +struct QrImplBody { + void operator()( + int32_t m, int32_t n, int32_t k, const complex* a, complex* q, complex* r) + { + qr_template(cusolverDnCgeqrf_bufferSize, + cusolverDnCungqr_bufferSize, + cusolverDnCgeqrf, + cusolverDnCungqr, + m, + n, + k, + reinterpret_cast(a), + reinterpret_cast(q), + reinterpret_cast(r)); + } +}; + +template <> +struct QrImplBody { + void operator()(int32_t m, + int32_t n, + int32_t k, + const complex* a, + complex* q, + complex* r) + { + qr_template(cusolverDnZgeqrf_bufferSize, + cusolverDnZungqr_bufferSize, + cusolverDnZgeqrf, + cusolverDnZungqr, + m, + n, + k, + reinterpret_cast(a), + reinterpret_cast(q), + reinterpret_cast(r)); + } +}; + +/*static*/ void QrTask::gpu_variant(TaskContext context) { qr_template(context); } + +} // namespace cunumeric diff --git a/src/cunumeric/matrix/qr.h b/src/cunumeric/matrix/qr.h new file mode 100644 index 0000000000..da97d291ee --- /dev/null +++ b/src/cunumeric/matrix/qr.h @@ -0,0 +1,38 @@ +/* Copyright 2024 NVIDIA Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +#pragma once + +#include "cunumeric/cunumeric_task.h" + +namespace cunumeric { + +class QrTask : public CuNumericTask { + public: + static const int TASK_ID = CUNUMERIC_QR; + static const char* ERROR_MESSAGE; + + public: + static void cpu_variant(legate::TaskContext context); +#if LegateDefined(LEGATE_USE_OPENMP) + static void omp_variant(legate::TaskContext context); +#endif +#if LegateDefined(LEGATE_USE_CUDA) + static void gpu_variant(legate::TaskContext context); +#endif +}; + +} // namespace cunumeric diff --git a/src/cunumeric/matrix/qr_cpu.inl b/src/cunumeric/matrix/qr_cpu.inl new file mode 100644 index 0000000000..ef143a57a0 --- /dev/null +++ b/src/cunumeric/matrix/qr_cpu.inl @@ -0,0 +1,131 @@ +/* Copyright 2024 NVIDIA Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +#pragma once + +#include +#include +#include + +namespace cunumeric { + +using namespace legate; + +template +static inline void qr_template( + Geqrf geqrf, Orgqr orgqr, int32_t m, int32_t n, int32_t k, const VAL* a, VAL* q, VAL* r) +{ + int32_t info = 0; + + // m>=n : a[m][n], q[m][n] r[n][n] + // m make tmp buffer + if (m < n) { + auto q_copy = create_buffer(m * n); + q_tmp = q_copy.ptr(0); + } + + std::memcpy(q_tmp, a, m * n * sizeof(VAL)); + + // compute and alloc buffer for geqrf + int32_t lwork = n; + auto buffer = create_buffer(lwork); + auto tau = create_buffer(k); + + geqrf(&m, &n, q_tmp, &m, tau.ptr(0), buffer.ptr(0), &lwork, &info); + + if (info != 0) { + throw legate::TaskException(QrTask::ERROR_MESSAGE); + } + + // extract R from upper triangular of getrf result + std::memset(r, 0, k * n * sizeof(VAL)); + for (int i = 0; i < k; ++i) { + int elements = i + 1; + if (i == k - 1 && n > k) { + elements = k * (n - k + 1); + } + std::memcpy(r + i * k, q_tmp + i * m, sizeof(VAL) * elements); + } + + // assemble Q + orgqr(&m, &k, &k, q_tmp, &m, tau.ptr(0), buffer.ptr(0), &lwork, &info); + if (info != 0) { + throw legate::TaskException(QrTask::ERROR_MESSAGE); + } + + // if we used a tmp storage we still need to copy back Q + if (q_tmp != q) { + assert(n > m); + std::memcpy(q, q_tmp, sizeof(VAL) * m * m); + } +} + +template +struct QrImplBody { + void operator()(int32_t m, int32_t n, int32_t k, const float* a, float* q, float* r) + { + qr_template(LAPACK_sgeqrf, LAPACK_sorgqr, m, n, k, a, q, r); + } +}; + +template +struct QrImplBody { + void operator()(int32_t m, int32_t n, int32_t k, const double* a, double* q, double* r) + { + qr_template(LAPACK_dgeqrf, LAPACK_dorgqr, m, n, k, a, q, r); + } +}; + +template +struct QrImplBody { + void operator()( + int32_t m, int32_t n, int32_t k, const complex* a, complex* q, complex* r) + { + qr_template(LAPACK_cgeqrf, + LAPACK_cungqr, + m, + n, + k, + reinterpret_cast(a), + reinterpret_cast<__complex__ float*>(q), + reinterpret_cast<__complex__ float*>(r)); + } +}; + +template +struct QrImplBody { + void operator()(int32_t m, + int32_t n, + int32_t k, + const complex* a, + complex* q, + complex* r) + { + qr_template(LAPACK_zgeqrf, + LAPACK_zungqr, + m, + n, + k, + reinterpret_cast(a), + reinterpret_cast<__complex__ double*>(q), + reinterpret_cast<__complex__ double*>(r)); + } +}; + +} // namespace cunumeric diff --git a/src/cunumeric/matrix/qr_omp.cc b/src/cunumeric/matrix/qr_omp.cc new file mode 100644 index 0000000000..c28c4c496b --- /dev/null +++ b/src/cunumeric/matrix/qr_omp.cc @@ -0,0 +1,31 @@ +/* Copyright 2024 NVIDIA Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +#include "cunumeric/matrix/qr.h" +#include "cunumeric/matrix/qr_template.inl" +#include "cunumeric/matrix/qr_cpu.inl" + +#include + +namespace cunumeric { + +/*static*/ void QrTask::omp_variant(TaskContext context) +{ + openblas_set_num_threads(omp_get_max_threads()); + qr_template(context); +} + +} // namespace cunumeric diff --git a/src/cunumeric/matrix/qr_template.inl b/src/cunumeric/matrix/qr_template.inl new file mode 100644 index 0000000000..637e325325 --- /dev/null +++ b/src/cunumeric/matrix/qr_template.inl @@ -0,0 +1,102 @@ +/* Copyright 2024 NVIDIA Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +#pragma once + +#include + +// Useful for IDEs +#include "cunumeric/matrix/qr.h" + +namespace cunumeric { + +using namespace legate; + +template +struct QrImplBody; + +template +struct support_qr : std::false_type {}; +template <> +struct support_qr : std::true_type {}; +template <> +struct support_qr : std::true_type {}; +template <> +struct support_qr : std::true_type {}; +template <> +struct support_qr : std::true_type {}; + +template +struct QrImpl { + template ::value>* = nullptr> + void operator()(legate::PhysicalStore a_array, + legate::PhysicalStore q_array, + legate::PhysicalStore r_array) const + { + using VAL = type_of; + +#ifdef DEBUG_CUNUMERIC + assert(a_array.dim() == 2); + assert(q_array.dim() == 2); + assert(r_array.dim() == 2); +#endif + const auto a_shape = a_array.shape<2>(); + const auto q_shape = q_array.shape<2>(); + const auto r_shape = r_array.shape<2>(); + + const int64_t m = a_shape.hi[0] - a_shape.lo[0] + 1; + const int64_t n = a_shape.hi[1] - a_shape.lo[1] + 1; + const int64_t k = std::min(m, n); + +#ifdef DEBUG_CUNUMERIC + assert(q_shape.hi[0] - q_shape.lo[0] + 1 == m); + assert(q_shape.hi[1] - q_shape.lo[1] + 1 == k); + assert(r_shape.hi[0] - r_shape.lo[0] + 1 == k); + assert(r_shape.hi[1] - r_shape.lo[1] + 1 == n); +#endif + + auto a_acc = a_array.read_accessor(a_shape); + auto q_acc = q_array.write_accessor(q_shape); + auto r_acc = r_array.write_accessor(r_shape); +#ifdef DEBUG_CUNUMERIC + assert(a_acc.accessor.is_dense_col_major(a_shape)); + assert(q_acc.accessor.is_dense_col_major(q_shape)); + assert(r_acc.accessor.is_dense_col_major(r_shape)); + assert(m > 0 && n > 0 && k > 0); +#endif + + QrImplBody()(m, n, k, a_acc.ptr(a_shape), q_acc.ptr(q_shape), r_acc.ptr(r_shape)); + } + + template ::value>* = nullptr> + void operator()(legate::PhysicalStore a_array, + legate::PhysicalStore q_array, + legate::PhysicalStore r_array) const + { + assert(false); + } +}; + +template +static void qr_template(TaskContext& context) +{ + auto& a_array = context.inputs()[0]; + auto& q_array = context.outputs()[0]; + auto& r_array = context.outputs()[1]; + type_dispatch(a_array.type().code(), QrImpl{}, a_array, q_array, r_array); +} + +} // namespace cunumeric diff --git a/tests/integration/test_qr.py b/tests/integration/test_qr.py new file mode 100644 index 0000000000..80bcea47e1 --- /dev/null +++ b/tests/integration/test_qr.py @@ -0,0 +1,110 @@ +# Copyright 2024 NVIDIA Corporation +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +import numpy as np +import pytest +from utils.comparisons import allclose + +import cunumeric as num + +SIZES = (8, 9, 255) + +RTOL = { + np.dtype(np.float32): 1e-1, + np.dtype(np.complex64): 1e-1, + np.dtype(np.float64): 1e-5, + np.dtype(np.complex128): 1e-5, +} + +ATOL = { + np.dtype(np.float32): 1e-3, + np.dtype(np.complex64): 1e-3, + np.dtype(np.float64): 1e-8, + np.dtype(np.complex128): 1e-8, +} + + +@pytest.mark.parametrize("m", SIZES) +@pytest.mark.parametrize("n", SIZES) +@pytest.mark.parametrize( + "a_dtype", (np.float32, np.float64, np.complex64, np.complex128) +) +def test_qr(m, n, a_dtype): + a = np.random.rand(m, n).astype(a_dtype) + + q, r = num.linalg.qr(a) + + rtol = RTOL[a.dtype] + atol = ATOL[a.dtype] + assert allclose( + a, num.matmul(q, r), rtol=rtol, atol=atol, check_dtype=False + ) + + +def test_qr_corner_cases(): + a = num.random.rand(1, 1) + + q, r = num.linalg.qr(a) + assert allclose(a, num.matmul(q, r)) + + +@pytest.mark.parametrize("dtype", (np.int32, np.int64)) +def test_qr_dtype_int(dtype): + a_array = [[1, 4, 5], [2, 3, 1], [9, 5, 2]] + a = num.array(a_array).astype(dtype) + + q, r = num.linalg.qr(a) + + rtol = RTOL[q.dtype] + atol = ATOL[q.dtype] + assert allclose( + a, num.matmul(q, r), rtol=rtol, atol=atol, check_dtype=False + ) + + +class TestQrErrors: + def setup_method(self): + self.n = 3 + self.a = num.random.rand(self.n, self.n).astype(np.float64) + self.b = num.random.rand(self.n).astype(np.float64) + + def test_a_bad_dim(self): + a = num.random.rand(self.n).astype(np.float64) + msg = "Array must be at least two-dimensional" + with pytest.raises(num.linalg.LinAlgError, match=msg): + num.linalg.qr(a) + + a = 10 + msg = "Array must be at least two-dimensional" + with pytest.raises(num.linalg.LinAlgError, match=msg): + num.linalg.qr(a) + + def test_a_dim_greater_than_two(self): + a = num.random.rand(self.n, self.n, self.n).astype(np.float64) + with pytest.raises(NotImplementedError): + num.linalg.qr(a) + + def test_a_bad_dtype_float16(self): + a = self.a.astype(np.float16) + msg = "array type float16 is unsupported in linalg" + with pytest.raises(TypeError, match=msg): + num.linalg.qr(a) + + +if __name__ == "__main__": + import sys + + np.random.seed(12345) + sys.exit(pytest.main(sys.argv)) diff --git a/tests/unit/cunumeric/test_config.py b/tests/unit/cunumeric/test_config.py index 85d04efdc1..cb69f4afb1 100644 --- a/tests/unit/cunumeric/test_config.py +++ b/tests/unit/cunumeric/test_config.py @@ -83,6 +83,7 @@ def test_CuNumericOpCode() -> None: "PACKBITS", "POTRF", "PUTMASK", + "QR", "RAND", "READ", "REPEAT", From 4a296c338d88447b2f622862236af3460507efba Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Malte=20F=C3=B6rster?= <97973773+mfoerste4@users.noreply.github.com> Date: Thu, 23 May 2024 00:24:31 +0200 Subject: [PATCH 205/462] Single GPU/CPU SVD support (#200) * support SVD sigle GPU/CPU * use actual complex values --- cunumeric/_thunk/deferred.py | 5 + cunumeric/_thunk/eager.py | 15 ++ cunumeric/_thunk/thunk.py | 4 + cunumeric/config.py | 2 + cunumeric/linalg/_svd.py | 58 ++++++++ cunumeric/linalg/linalg.py | 83 +++++++++++ cunumeric_cpp.cmake | 3 + docs/cunumeric/source/api/linalg.rst | 1 + examples/svd.py | 118 ++++++++++++++++ src/cunumeric/cunumeric_c.h | 1 + src/cunumeric/mapper.cc | 1 + src/cunumeric/matrix/svd.cc | 40 ++++++ src/cunumeric/matrix/svd.cu | 176 +++++++++++++++++++++++ src/cunumeric/matrix/svd.h | 38 +++++ src/cunumeric/matrix/svd_cpu.inl | 192 ++++++++++++++++++++++++++ src/cunumeric/matrix/svd_omp.cc | 31 +++++ src/cunumeric/matrix/svd_template.inl | 126 +++++++++++++++++ tests/integration/test_svd.py | 131 ++++++++++++++++++ tests/unit/cunumeric/test_config.py | 1 + 19 files changed, 1026 insertions(+) create mode 100644 cunumeric/linalg/_svd.py create mode 100644 examples/svd.py create mode 100644 src/cunumeric/matrix/svd.cc create mode 100644 src/cunumeric/matrix/svd.cu create mode 100644 src/cunumeric/matrix/svd.h create mode 100644 src/cunumeric/matrix/svd_cpu.inl create mode 100644 src/cunumeric/matrix/svd_omp.cc create mode 100644 src/cunumeric/matrix/svd_template.inl create mode 100644 tests/integration/test_svd.py diff --git a/cunumeric/_thunk/deferred.py b/cunumeric/_thunk/deferred.py index c0970476c8..bc01d7a7d3 100644 --- a/cunumeric/_thunk/deferred.py +++ b/cunumeric/_thunk/deferred.py @@ -67,6 +67,7 @@ from ..linalg._cholesky import cholesky_deferred from ..linalg._qr import qr_deferred from ..linalg._solve import solve_deferred +from ..linalg._svd import svd_deferred from ..runtime import runtime from ._sort import sort_deferred from .thunk import NumPyThunk @@ -3363,6 +3364,10 @@ def qr(self, q: Any, r: Any) -> None: def solve(self, a: Any, b: Any) -> None: solve_deferred(self, a, b) + @auto_convert("u", "s", "vh") + def svd(self, u: Any, s: Any, vh: Any) -> None: + svd_deferred(self, u, s, vh) + @auto_convert("rhs") def scan( self, diff --git a/cunumeric/_thunk/eager.py b/cunumeric/_thunk/eager.py index a144cb4f81..ca0aa9772a 100644 --- a/cunumeric/_thunk/eager.py +++ b/cunumeric/_thunk/eager.py @@ -1695,6 +1695,21 @@ def solve(self, a: Any, b: Any) -> None: raise LinAlgError(e) from e self.array[:] = result + def svd(self, u: Any, s: Any, vh: Any) -> None: + self.check_eager_args(u, s, vh) + if self.deferred is not None: + self.deferred.svd(u, s, vh) + else: + try: + result_u, result_s, result_vh = np.linalg.svd(self.array) + except np.linalg.LinAlgError as e: + from ..linalg import LinAlgError + + raise LinAlgError(e) from e + u.array[:] = result_u + s.array[:] = result_s + vh.array[:] = result_vh + def scan( self, op: int, diff --git a/cunumeric/_thunk/thunk.py b/cunumeric/_thunk/thunk.py index 94e1d8f7e3..2c65e390e4 100644 --- a/cunumeric/_thunk/thunk.py +++ b/cunumeric/_thunk/thunk.py @@ -708,6 +708,10 @@ def qr(self, q: Any, r: Any) -> None: def solve(self, a: Any, b: Any) -> None: ... + @abstractmethod + def svd(self, u: Any, s: Any, vh: Any) -> None: + ... + @abstractmethod def scan( self, diff --git a/cunumeric/config.py b/cunumeric/config.py index 5920f20e64..7266287303 100644 --- a/cunumeric/config.py +++ b/cunumeric/config.py @@ -208,6 +208,7 @@ class _CunumericSharedLib: CUNUMERIC_SELECT: int CUNUMERIC_SOLVE: int CUNUMERIC_SORT: int + CUNUMERIC_SVD: int CUNUMERIC_SYRK: int CUNUMERIC_TILE: int CUNUMERIC_TRANSPOSE_COPY_2D: int @@ -398,6 +399,7 @@ class CuNumericOpCode(IntEnum): SELECT = _cunumeric.CUNUMERIC_SELECT SOLVE = _cunumeric.CUNUMERIC_SOLVE SORT = _cunumeric.CUNUMERIC_SORT + SVD = _cunumeric.CUNUMERIC_SVD SYRK = _cunumeric.CUNUMERIC_SYRK TILE = _cunumeric.CUNUMERIC_TILE TRANSPOSE_COPY_2D = _cunumeric.CUNUMERIC_TRANSPOSE_COPY_2D diff --git a/cunumeric/linalg/_svd.py b/cunumeric/linalg/_svd.py new file mode 100644 index 0000000000..9579f06849 --- /dev/null +++ b/cunumeric/linalg/_svd.py @@ -0,0 +1,58 @@ +# Copyright 2024 NVIDIA Corporation +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +from __future__ import annotations + +from typing import TYPE_CHECKING + +from legate.core import get_legate_runtime + +from cunumeric.config import CuNumericOpCode + +from ._exception import LinAlgError + +if TYPE_CHECKING: + from legate.core import Library, LogicalStore + + from .._thunk.deferred import DeferredArray + + +def svd_single( + library: Library, + a: LogicalStore, + u: LogicalStore, + s: LogicalStore, + vh: LogicalStore, +) -> None: + task = get_legate_runtime().create_auto_task(library, CuNumericOpCode.SVD) + task.throws_exception(LinAlgError) + task.add_input(a) + task.add_output(u) + task.add_output(s) + task.add_output(vh) + + task.add_broadcast(a) + task.add_broadcast(u) + task.add_broadcast(s) + task.add_broadcast(vh) + + task.execute() + + +def svd_deferred( + a: DeferredArray, u: DeferredArray, s: DeferredArray, vh: DeferredArray +) -> None: + library = a.library + + svd_single(library, a.base, u.base, s.base, vh.base) diff --git a/cunumeric/linalg/linalg.py b/cunumeric/linalg/linalg.py index 0f30040d26..9c8416dc70 100644 --- a/cunumeric/linalg/linalg.py +++ b/cunumeric/linalg/linalg.py @@ -217,6 +217,60 @@ def solve(a: ndarray, b: ndarray, out: ndarray | None = None) -> ndarray: return _thunk_solve(a, b, out) +@add_boilerplate("a") +def svd(a: ndarray) -> tuple[ndarray, ...]: + """ + Singular Value Decomposition. + + Parameters + ---------- + a : (M, N) array_like + Array like, at least dimension 2. + + Returns + ------- + u : (M, M) array_like + Unitary array(s). + s : (K) array_like + The singular values, sorted in descending order + vh : (N, N) array_like + Unitary array(s). + + Raises + ------ + LinAlgError + If SVD computation does not converge. + + Notes + ----- + Currently does not support the parameters 'full_matrices', 'compute_uv', + and 'hermitian'. + + See Also + -------- + numpy.linalg.svd + + Availability + -------- + Single GPU, Single CPU + """ + shape = a.shape + if len(shape) < 2: + raise LinAlgError( + f"{len(shape)}-dimensional array given. " + "Array must be at least two-dimensional" + ) + if len(shape) > 2: + raise NotImplementedError( + "cuNumeric does not yet support stacked 2d arrays" + ) + if shape[0] < shape[1]: + raise NotImplementedError("cuNumeric only supports M >= N") + if np.dtype("e") == a.dtype: + raise TypeError("array type float16 is unsupported in linalg") + return _thunk_svd(a) + + # This implementation is adapted closely from NumPy @add_boilerplate("a") def matrix_power(a: ndarray, n: int) -> ndarray: @@ -747,3 +801,32 @@ def _thunk_solve( ) out._thunk.solve(a._thunk, b._thunk) return out + + +def _thunk_svd(a: ndarray) -> tuple[ndarray, ...]: + if a.dtype.kind not in ("f", "c"): + a = a.astype("float64") + + k = min(a.shape[0], a.shape[1]) + + out_u = ndarray( + shape=(a.shape[0], a.shape[0]), + dtype=a.dtype, + inputs=(a,), + ) + + real_dtype = a.dtype.type(0).real.dtype + + out_s = ndarray( + shape=(k,), + dtype=real_dtype, + inputs=(a,), + ) + out_vh = ndarray( + shape=(a.shape[1], a.shape[1]), + dtype=a.dtype, + inputs=(a,), + ) + + a._thunk.svd(out_u._thunk, out_s._thunk, out_vh._thunk) + return out_u, out_s, out_vh diff --git a/cunumeric_cpp.cmake b/cunumeric_cpp.cmake index f13993187b..5e056dab76 100644 --- a/cunumeric_cpp.cmake +++ b/cunumeric_cpp.cmake @@ -155,6 +155,7 @@ list(APPEND cunumeric_SOURCES src/cunumeric/matrix/potrf.cc src/cunumeric/matrix/qr.cc src/cunumeric/matrix/solve.cc + src/cunumeric/matrix/svd.cc src/cunumeric/matrix/syrk.cc src/cunumeric/matrix/tile.cc src/cunumeric/matrix/transpose.cc @@ -214,6 +215,7 @@ if(Legion_USE_OpenMP) src/cunumeric/matrix/potrf_omp.cc src/cunumeric/matrix/qr_omp.cc src/cunumeric/matrix/solve_omp.cc + src/cunumeric/matrix/svd_omp.cc src/cunumeric/matrix/syrk_omp.cc src/cunumeric/matrix/tile_omp.cc src/cunumeric/matrix/transpose_omp.cc @@ -267,6 +269,7 @@ if(Legion_USE_CUDA) src/cunumeric/matrix/potrf.cu src/cunumeric/matrix/qr.cu src/cunumeric/matrix/solve.cu + src/cunumeric/matrix/svd.cu src/cunumeric/matrix/syrk.cu src/cunumeric/matrix/tile.cu src/cunumeric/matrix/transpose.cu diff --git a/docs/cunumeric/source/api/linalg.rst b/docs/cunumeric/source/api/linalg.rst index 28ce88d118..5d94889803 100644 --- a/docs/cunumeric/source/api/linalg.rst +++ b/docs/cunumeric/source/api/linalg.rst @@ -30,6 +30,7 @@ Decompositions linalg.cholesky linalg.qr + linalg.svd Norms and other numbers ----------------------- diff --git a/examples/svd.py b/examples/svd.py new file mode 100644 index 0000000000..85887f7a0b --- /dev/null +++ b/examples/svd.py @@ -0,0 +1,118 @@ +#!/usr/bin/env python + +# Copyright 2024 NVIDIA Corporation +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +import argparse + +import numpy as np +from benchmark import parse_args, run_benchmark + + +def check_result(a, u, s, vh): + print("Checking result...") + + # (u * s) @ vh + m = a.shape[0] + n = a.shape[1] + k = min(m, n) + + u = u[:, :k] if k < m else u + vh = vh[:k, :] if k < m else vh + a2 = num.matmul(u * s, vh) + print("PASS!" if num.allclose(a, a2) else "FAIL!") + + +def svd(m, n, dtype, perform_check, timing): + if np.issubdtype(dtype, np.integer): + a = num.random.randint(0, 1000, size=m * n).astype(dtype) + a = a.reshape((m, n)) + elif np.issubdtype(dtype, np.floating): + a = num.random.random((m, n)).astype(dtype) + elif np.issubdtype(dtype, np.complexfloating): + a = num.array( + num.random.random((m, n)) + num.random.random((m, n)) * 1j + ).astype(dtype) + else: + print("unsupported type " + str(dtype)) + assert False + + timer.start() + u, s, vh = num.linalg.svd(a) + total = timer.stop() + + if perform_check: + check_result(a, u, s, vh) + + if timing: + print(f"Elapsed Time: {total} ms") + + return total + + +if __name__ == "__main__": + parser = argparse.ArgumentParser() + parser.add_argument( + "-t", + "--time", + dest="timing", + action="store_true", + help="perform timing", + ) + parser.add_argument( + "-m", + "--rows", + type=int, + default=10, + dest="m", + help="number of rows in the matrix", + ) + parser.add_argument( + "-n", + "--cols", + type=int, + default=10, + dest="n", + help="number of cols in the matrix", + ) + parser.add_argument( + "-d", + "--dtype", + default="float64", + choices=[ + "int32", + "int64", + "float32", + "float64", + "complex64", + "complex128", + ], + dest="dtype", + help="data type", + ) + parser.add_argument( + "--check", + dest="check", + action="store_true", + help="compare result to numpy", + ) + args, num, timer = parse_args(parser) + + run_benchmark( + svd, + args.benchmark, + "SVD", + (args.m, args.n, args.dtype, args.check, args.timing), + ) diff --git a/src/cunumeric/cunumeric_c.h b/src/cunumeric/cunumeric_c.h index a4de0478a5..78b989a72a 100644 --- a/src/cunumeric/cunumeric_c.h +++ b/src/cunumeric/cunumeric_c.h @@ -66,6 +66,7 @@ enum CuNumericOpCode { CUNUMERIC_SELECT, CUNUMERIC_SOLVE, CUNUMERIC_SORT, + CUNUMERIC_SVD, CUNUMERIC_SYRK, CUNUMERIC_TILE, CUNUMERIC_TRANSPOSE_COPY_2D, diff --git a/src/cunumeric/mapper.cc b/src/cunumeric/mapper.cc index 8fe2990d6a..27a25dff1e 100644 --- a/src/cunumeric/mapper.cc +++ b/src/cunumeric/mapper.cc @@ -128,6 +128,7 @@ std::vector CuNumericMapper::store_mappings( case CUNUMERIC_QR: case CUNUMERIC_TRSM: case CUNUMERIC_SOLVE: + case CUNUMERIC_SVD: case CUNUMERIC_SYRK: case CUNUMERIC_GEMM: case CUNUMERIC_MP_POTRF: diff --git a/src/cunumeric/matrix/svd.cc b/src/cunumeric/matrix/svd.cc new file mode 100644 index 0000000000..2174e1fa98 --- /dev/null +++ b/src/cunumeric/matrix/svd.cc @@ -0,0 +1,40 @@ +/* Copyright 2024 NVIDIA Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +#include "cunumeric/matrix/svd.h" +#include "cunumeric/matrix/svd_template.inl" +#include "cunumeric/matrix/svd_cpu.inl" + +namespace cunumeric { + +using namespace legate; + +/*static*/ const char* SvdTask::ERROR_MESSAGE = "Factorization failed"; + +/*static*/ void SvdTask::cpu_variant(TaskContext context) +{ +#if LegateDefined(LEGATE_USE_OPENMP) + openblas_set_num_threads(1); // make sure this isn't overzealous +#endif + svd_template(context); +} + +namespace // unnamed +{ +static void __attribute__((constructor)) register_tasks(void) { SvdTask::register_variants(); } +} // namespace + +} // namespace cunumeric \ No newline at end of file diff --git a/src/cunumeric/matrix/svd.cu b/src/cunumeric/matrix/svd.cu new file mode 100644 index 0000000000..1fbb704d81 --- /dev/null +++ b/src/cunumeric/matrix/svd.cu @@ -0,0 +1,176 @@ +/* Copyright 2024 NVIDIA Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +#include "cunumeric/matrix/svd.h" +#include "cunumeric/matrix/svd_template.inl" + +#include "cunumeric/cuda_help.h" +#include +namespace cunumeric { + +using namespace legate; + +template +static inline void svd_template(DataType valTypeC, + DataType valTypeR, + int64_t m, + int64_t n, + int64_t k, + const void* a, + void* u, + void* s, + void* vh) +{ + auto handle = get_cusolver(); + auto stream = get_cached_stream(); + + auto a_copy = create_buffer(m * n, Memory::Kind::GPU_FB_MEM); + LegateCheckCUDA( + cudaMemcpyAsync(a_copy.ptr(0), a, m * n * sizeof(VAL), cudaMemcpyDeviceToDevice, stream)); + + // a[m][n], u[m][m] s[k] vh[n][n] + CHECK_CUSOLVER(cusolverDnSetStream(handle, stream)); + + size_t lwork_device, lwork_host; + CHECK_CUSOLVER(cusolverDnXgesvd_bufferSize(handle, + nullptr, + 'A', + 'A', + m, + n, + valTypeC, + reinterpret_cast(a_copy.ptr(0)), + m, + valTypeR, + s, + valTypeC, + u, + m, + valTypeC, + vh, + n, + valTypeC, + &lwork_device, + &lwork_host)); + + auto buffer = create_buffer(lwork_device, Memory::Kind::GPU_FB_MEM); + std::vector buffer_host(std::max(1ul, lwork_host)); + auto info = create_buffer(1, Memory::Kind::Z_COPY_MEM); + + CHECK_CUSOLVER(cusolverDnXgesvd(handle, + nullptr, + 'A', + 'A', + m, + n, + valTypeC, + reinterpret_cast(a_copy.ptr(0)), + m, + valTypeR, + s, + valTypeC, + u, + m, + valTypeC, + vh, + n, + valTypeC, + buffer.ptr(0), + lwork_device, + buffer_host.data(), + lwork_host, + info.ptr(0))); + + LegateCheckCUDA(cudaStreamSynchronize(stream)); + + if (info[0] != 0) { + throw legate::TaskException(SvdTask::ERROR_MESSAGE); + } + + LegateCheckCUDAStream(stream); + +#ifdef DEBUG_CUNUMERIC + assert(info[0] == 0); +#endif +} + +template <> +struct SvdImplBody { + void operator()(int64_t m, int64_t n, int64_t k, const float* a, float* u, float* s, float* vh) + { + svd_template(CUDA_R_32F, CUDA_R_32F, m, n, k, a, u, s, vh); + } +}; + +template <> +struct SvdImplBody { + void operator()( + int64_t m, int64_t n, int64_t k, const double* a, double* u, double* s, double* vh) + { + svd_template(CUDA_R_64F, CUDA_R_64F, m, n, k, a, u, s, vh); + } +}; + +template <> +struct SvdImplBody { + void operator()(int64_t m, + int64_t n, + int64_t k, + const complex* a, + complex* u, + float* s, + complex* vh) + { + svd_template>(CUDA_C_32F, + CUDA_R_32F, + m, + n, + k, + reinterpret_cast(a), + reinterpret_cast(u), + s, + reinterpret_cast(vh)); + } +}; + +template <> +struct SvdImplBody { + void operator()(int64_t m, + int64_t n, + int64_t k, + const complex* a, + complex* u, + double* s, + complex* vh) + { + svd_template>(CUDA_C_64F, + CUDA_R_64F, + m, + n, + k, + reinterpret_cast(a), + reinterpret_cast(u), + s, + reinterpret_cast(vh)); + } +}; + +/*static*/ void SvdTask::gpu_variant(TaskContext context) +{ + svd_template(context); +} + +} // namespace cunumeric \ No newline at end of file diff --git a/src/cunumeric/matrix/svd.h b/src/cunumeric/matrix/svd.h new file mode 100644 index 0000000000..e5a538aab6 --- /dev/null +++ b/src/cunumeric/matrix/svd.h @@ -0,0 +1,38 @@ +/* Copyright 2024 NVIDIA Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +#pragma once + +#include "cunumeric/cunumeric_task.h" + +namespace cunumeric { + +class SvdTask : public CuNumericTask { + public: + static const int TASK_ID = CUNUMERIC_SVD; + static const char* ERROR_MESSAGE; + + public: + static void cpu_variant(legate::TaskContext context); +#if LegateDefined(LEGATE_USE_OPENMP) + static void omp_variant(legate::TaskContext context); +#endif +#if LegateDefined(LEGATE_USE_CUDA) + static void gpu_variant(legate::TaskContext context); +#endif +}; + +} // namespace cunumeric \ No newline at end of file diff --git a/src/cunumeric/matrix/svd_cpu.inl b/src/cunumeric/matrix/svd_cpu.inl new file mode 100644 index 0000000000..5ee079c5e2 --- /dev/null +++ b/src/cunumeric/matrix/svd_cpu.inl @@ -0,0 +1,192 @@ +/* Copyright 2024 NVIDIA Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +#pragma once + +#include +#include +#include + +namespace cunumeric { + +using namespace legate; + +template +struct SvdImplBody { + void operator()(int32_t m, int32_t n, int32_t k, const float* a, float* u, float* s, float* vh) + { + auto a_copy = create_buffer(m * n); + std::copy(a, a + (n * m), a_copy.ptr(0)); + + int32_t info = 0; + float wkopt = 0; + int32_t lwork = -1; + LAPACK_sgesvd("A", "A", &m, &n, a_copy.ptr(0), &m, s, u, &m, vh, &n, &wkopt, &lwork, &info); + lwork = (int)wkopt; + + std::vector work_tmp(lwork); + LAPACK_sgesvd( + "A", "A", &m, &n, a_copy.ptr(0), &m, s, u, &m, vh, &n, work_tmp.data(), &lwork, &info); + + if (info != 0) { + throw legate::TaskException(SvdTask::ERROR_MESSAGE); + } + } +}; + +template +struct SvdImplBody { + void operator()( + int32_t m, int32_t n, int32_t k, const double* a, double* u, double* s, double* vh) + { + auto a_copy = create_buffer(m * n); + std::copy(a, a + (n * m), a_copy.ptr(0)); + + int32_t info = 0; + double wkopt = 0; + int32_t lwork = -1; + + LAPACK_dgesvd("A", "A", &m, &n, a_copy.ptr(0), &m, s, u, &m, vh, &n, &wkopt, &lwork, &info); + lwork = (int)wkopt; + + std::vector work_tmp(lwork); + LAPACK_dgesvd( + "A", "A", &m, &n, a_copy.ptr(0), &m, s, u, &m, vh, &n, work_tmp.data(), &lwork, &info); + + if (info != 0) { + throw legate::TaskException(SvdTask::ERROR_MESSAGE); + } + } +}; + +template +struct SvdImplBody { + void operator()(int32_t m, + int32_t n, + int32_t k, + const complex* a, + complex* u, + float* s, + complex* vh) + { + auto a_copy = create_buffer>(m * n); + std::copy(a, a + (n * m), a_copy.ptr(0)); + + int32_t info = 0; + int32_t lwork = -1; + __complex__ float wkopt = 0; + std::vector rwork(5 * k); + + LAPACK_cgesvd("A", + "A", + &m, + &n, + reinterpret_cast<__complex__ float*>(a_copy.ptr(0)), + &m, + s, + reinterpret_cast<__complex__ float*>(u), + &m, + reinterpret_cast<__complex__ float*>(vh), + &n, + &wkopt, + &lwork, + rwork.data(), + &info); + + lwork = (int)(*((float*)&(wkopt))); + + std::vector<__complex__ float> work_tmp(lwork); + LAPACK_cgesvd("A", + "A", + &m, + &n, + reinterpret_cast<__complex__ float*>(a_copy.ptr(0)), + &m, + s, + reinterpret_cast<__complex__ float*>(u), + &m, + reinterpret_cast<__complex__ float*>(vh), + &n, + work_tmp.data(), + &lwork, + rwork.data(), + &info); + + if (info != 0) { + throw legate::TaskException(SvdTask::ERROR_MESSAGE); + } + } +}; + +template +struct SvdImplBody { + void operator()(int32_t m, + int32_t n, + int32_t k, + const complex* a, + complex* u, + double* s, + complex* vh) + { + auto a_copy = create_buffer>(m * n); + std::copy(a, a + (n * m), a_copy.ptr(0)); + + int32_t info = 0; + int32_t lwork = -1; + __complex__ double wkopt = 0; + std::vector rwork(5 * k); + LAPACK_zgesvd("A", + "A", + &m, + &n, + reinterpret_cast<__complex__ double*>(a_copy.ptr(0)), + &m, + s, + reinterpret_cast<__complex__ double*>(u), + &m, + reinterpret_cast<__complex__ double*>(vh), + &n, + &wkopt, + &lwork, + rwork.data(), + &info); + + lwork = (int)(*((double*)&(wkopt))); + + std::vector<__complex__ double> work_tmp(lwork); + LAPACK_zgesvd("A", + "A", + &m, + &n, + reinterpret_cast<__complex__ double*>(a_copy.ptr(0)), + &m, + s, + reinterpret_cast<__complex__ double*>(u), + &m, + reinterpret_cast<__complex__ double*>(vh), + &n, + work_tmp.data(), + &lwork, + rwork.data(), + &info); + + if (info != 0) { + throw legate::TaskException(SvdTask::ERROR_MESSAGE); + } + } +}; + +} // namespace cunumeric \ No newline at end of file diff --git a/src/cunumeric/matrix/svd_omp.cc b/src/cunumeric/matrix/svd_omp.cc new file mode 100644 index 0000000000..fac2cf1b1b --- /dev/null +++ b/src/cunumeric/matrix/svd_omp.cc @@ -0,0 +1,31 @@ +/* Copyright 2024 NVIDIA Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +#include "cunumeric/matrix/svd.h" +#include "cunumeric/matrix/svd_template.inl" +#include "cunumeric/matrix/svd_cpu.inl" + +#include + +namespace cunumeric { + +/*static*/ void SvdTask::omp_variant(TaskContext context) +{ + openblas_set_num_threads(omp_get_max_threads()); + svd_template(context); +} + +} // namespace cunumeric \ No newline at end of file diff --git a/src/cunumeric/matrix/svd_template.inl b/src/cunumeric/matrix/svd_template.inl new file mode 100644 index 0000000000..4b9393282b --- /dev/null +++ b/src/cunumeric/matrix/svd_template.inl @@ -0,0 +1,126 @@ +/* Copyright 2024 NVIDIA Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +#pragma once + +#include + +// Useful for IDEs +#include "cunumeric/matrix/svd.h" + +namespace cunumeric { + +using namespace legate; + +template +struct SvdImplBody; + +template +struct support_svd : std::false_type {}; +template <> +struct support_svd : std::true_type {}; +template <> +struct support_svd : std::true_type {}; +template <> +struct support_svd : std::true_type {}; +template <> +struct support_svd : std::true_type {}; + +template +struct real_type { + using TYPE = float; +}; +template <> +struct real_type { + using TYPE = double; +}; +template <> +struct real_type { + using TYPE = double; +}; + +template +struct SvdImpl { + template ::value>* = nullptr> + void operator()(legate::PhysicalStore a_array, + legate::PhysicalStore u_array, + legate::PhysicalStore s_array, + legate::PhysicalStore vh_array) const + { + using VAL = type_of; + using VAL_REAL = typename real_type::TYPE; + +#ifdef DEBUG_CUNUMERIC + assert(a_array.dim() == 2); + assert(u_array.dim() == 2); + assert(s_array.dim() == 1); + assert(vh_array.dim() == 2); +#endif + const auto a_shape = a_array.shape<2>(); + const auto u_shape = u_array.shape<2>(); + const auto s_shape = s_array.shape<1>(); + const auto vh_shape = vh_array.shape<2>(); + + const int64_t m = a_shape.hi[0] - a_shape.lo[0] + 1; + const int64_t n = a_shape.hi[1] - a_shape.lo[1] + 1; + const int64_t k = std::min(m, n); + + assert(m >= n); + +#ifdef DEBUG_CUNUMERIC + assert(u_shape.hi[0] - u_shape.lo[0] + 1 == m); + assert(u_shape.hi[1] - u_shape.lo[1] + 1 == m); + assert(s_shape.hi[0] - s_shape.lo[0] + 1 == k); + assert(vh_shape.hi[0] - vh_shape.lo[0] + 1 == n); + assert(vh_shape.hi[1] - vh_shape.lo[1] + 1 == n); +#endif + + auto a_acc = a_array.read_accessor(a_shape); + auto u_acc = u_array.write_accessor(u_shape); + auto s_acc = s_array.write_accessor(s_shape); + auto vh_acc = vh_array.write_accessor(vh_shape); +#ifdef DEBUG_CUNUMERIC + assert(a_acc.accessor.is_dense_col_major(a_shape)); + assert(u_acc.accessor.is_dense_col_major(u_shape)); + assert(vh_acc.accessor.is_dense_col_major(vh_shape)); + assert(m > 0 && n > 0 && k > 0); +#endif + + SvdImplBody()( + m, n, k, a_acc.ptr(a_shape), u_acc.ptr(u_shape), s_acc.ptr(s_shape), vh_acc.ptr(vh_shape)); + } + + template ::value>* = nullptr> + void operator()(legate::PhysicalStore a_array, + legate::PhysicalStore u_array, + legate::PhysicalStore s_array, + legate::PhysicalStore vh_array) const + { + assert(false); + } +}; + +template +static void svd_template(TaskContext& context) +{ + auto& a_array = context.inputs()[0]; + auto& u_array = context.outputs()[0]; + auto& s_array = context.outputs()[1]; + auto& vh_array = context.outputs()[2]; + type_dispatch(a_array.type().code(), SvdImpl{}, a_array, u_array, s_array, vh_array); +} + +} // namespace cunumeric \ No newline at end of file diff --git a/tests/integration/test_svd.py b/tests/integration/test_svd.py new file mode 100644 index 0000000000..e7394d9237 --- /dev/null +++ b/tests/integration/test_svd.py @@ -0,0 +1,131 @@ +# Copyright 2024 NVIDIA Corporation +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +import numpy as np +import pytest +from utils.comparisons import allclose + +import cunumeric as num + +SIZES = (8, 9, 255) + +RTOL = { + np.dtype(np.int32): 1e-1, + np.dtype(np.int64): 1e-1, + np.dtype(np.float32): 1e-1, + np.dtype(np.complex64): 1e-1, + np.dtype(np.float64): 1e-5, + np.dtype(np.complex128): 1e-5, +} + +ATOL = { + np.dtype(np.int32): 1e-3, + np.dtype(np.int64): 1e-3, + np.dtype(np.float32): 1e-3, + np.dtype(np.complex64): 1e-3, + np.dtype(np.float64): 1e-8, + np.dtype(np.complex128): 1e-8, +} + + +def assert_result(a, u, s, vh): + # (u * s) @ vh + m = a.shape[0] + n = a.shape[1] + k = min(m, n) + + if k < m: + u = u[:, :k] + + a2 = num.matmul(u * s, vh) + + rtol = RTOL[a.dtype] + atol = ATOL[a.dtype] + assert allclose(a, a2, rtol=rtol, atol=atol, check_dtype=False) + + +@pytest.mark.parametrize("m", SIZES) +@pytest.mark.parametrize("n", SIZES) +@pytest.mark.parametrize( + "a_dtype", (np.float32, np.float64, np.complex64, np.complex128) +) +def test_svd(m, n, a_dtype): + if m < n: + pytest.skip() + + if np.issubdtype(a_dtype, np.complexfloating): + a = np.random.rand(m, n) + np.random.rand(m, n) * 1j + else: + a = np.random.rand(m, n) + + a = a.astype(a_dtype) + + u, s, vh = num.linalg.svd(a) + + assert_result(a, u, s, vh) + + +def test_svd_corner_cases(): + a = num.random.rand(1, 1) + + u, s, vh = num.linalg.svd(a) + + assert_result(a, u, s, vh) + + +@pytest.mark.parametrize("dtype", (np.int32, np.int64)) +def test_svd_dtype_int(dtype): + a_array = [[1, 4, 5], [2, 3, 1], [9, 5, 2]] + a = num.array(a_array).astype(dtype) + + u, s, vh = num.linalg.svd(a) + + assert_result(a, u, s, vh) + + +class TestSvdErrors: + def setup_method(self): + self.n = 3 + self.a = num.random.rand(self.n, self.n).astype(np.float64) + self.b = num.random.rand(self.n).astype(np.float64) + + def test_a_bad_dim(self): + a = num.random.rand(self.n).astype(np.float64) + msg = "Array must be at least two-dimensional" + with pytest.raises(num.linalg.LinAlgError, match=msg): + num.linalg.svd(a) + + a = 10 + msg = "Array must be at least two-dimensional" + with pytest.raises(num.linalg.LinAlgError, match=msg): + num.linalg.svd(a) + + def test_a_dim_greater_than_two(self): + a = num.random.rand(self.n, self.n, self.n).astype(np.float64) + with pytest.raises(NotImplementedError): + num.linalg.svd(a) + + def test_a_bad_dtype_float16(self): + a = self.a.astype(np.float16) + msg = "array type float16 is unsupported in linalg" + with pytest.raises(TypeError, match=msg): + num.linalg.svd(a) + + +if __name__ == "__main__": + import sys + + np.random.seed(12345) + sys.exit(pytest.main(sys.argv)) diff --git a/tests/unit/cunumeric/test_config.py b/tests/unit/cunumeric/test_config.py index cb69f4afb1..890ff21712 100644 --- a/tests/unit/cunumeric/test_config.py +++ b/tests/unit/cunumeric/test_config.py @@ -94,6 +94,7 @@ def test_CuNumericOpCode() -> None: "SOLVE", "SORT", "SEARCHSORTED", + "SVD", "SYRK", "TILE", "TRANSPOSE_COPY_2D", From a94188d4c847b15755132329fe2595098d5c2ff0 Mon Sep 17 00:00:00 2001 From: Marcin Zalewski Date: Wed, 22 May 2024 22:21:47 -0700 Subject: [PATCH 206/462] Update legate.core version (#202) * update * env fix * Fix typo --------- Co-authored-by: Manolis Papadakis --- CMakeLists.txt | 2 +- cmake/thirdparty/get_legate_core.cmake | 1 + cmake/versions.json | 6 +++--- src/cunumeric/mapper.cc | 6 +++--- tests/cpp/CMakeLists.txt | 2 +- 5 files changed, 9 insertions(+), 8 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 1de914c1da..692f276a15 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -45,7 +45,7 @@ endif() # - Download and initialize RAPIDS CMake helpers ----------------------------- if(NOT EXISTS ${CMAKE_BINARY_DIR}/RAPIDS.cmake) - file(DOWNLOAD https://raw.githubusercontent.com/rapidsai/rapids-cmake/branch-23.08/RAPIDS.cmake + file(DOWNLOAD https://raw.githubusercontent.com/rapidsai/rapids-cmake/v24.04.00/RAPIDS.cmake ${CMAKE_BINARY_DIR}/RAPIDS.cmake) endif() include(${CMAKE_BINARY_DIR}/RAPIDS.cmake) diff --git a/cmake/thirdparty/get_legate_core.cmake b/cmake/thirdparty/get_legate_core.cmake index 6bd7dea042..1f231d0a73 100644 --- a/cmake/thirdparty/get_legate_core.cmake +++ b/cmake/thirdparty/get_legate_core.cmake @@ -59,6 +59,7 @@ function(find_or_configure_legate_core) message(VERBOSE "cunumeric: legate.core git_repo: ${git_repo}") message(VERBOSE "cunumeric: legate.core git_branch: ${git_branch}") message(VERBOSE "cunumeric: legate.core exclude_from_all: ${exclude_from_all}") + message(VERBOSE "cunumeric: legate.core legate_core_cpm_git_args: ${legate_core_cpm_git_args}") rapids_cpm_find(legate_core ${version} ${FIND_PKG_ARGS} CPM_ARGS diff --git a/cmake/versions.json b/cmake/versions.json index 274a12efa9..7a5817c62e 100644 --- a/cmake/versions.json +++ b/cmake/versions.json @@ -1,13 +1,13 @@ { "packages" : { - "legate_core_internal" : { + "legate_core" : { "repo": "legate.core.internal", "artifact_name": "${{ inputs.platform }}-${{ inputs.build-type }}-<>-${{ inputs.target-device }}-release-<>", "version": "24.05.00", - "git_url" : "https://github.com/nv-legate/legate.core.internal.git", + "git_url" : "git@github.com:nv-legate/legate.core.internal.git", "git_shallow": false, "always_download": false, - "git_tag" : "80862530505143380365b4e40aa90ca935a0f8f7" + "git_tag" : "70955bf4a7e7600e577ab6e1c9178d6ad77f9b9a" } } } diff --git a/src/cunumeric/mapper.cc b/src/cunumeric/mapper.cc index 27a25dff1e..ec3718e79f 100644 --- a/src/cunumeric/mapper.cc +++ b/src/cunumeric/mapper.cc @@ -24,10 +24,10 @@ namespace cunumeric { CuNumericMapper::CuNumericMapper() : min_gpu_chunk( - extract_env("CUNUMERIC_MIN_GPU_CHUNK", MIN_GPU_CHUNK_DEFAULT, MIN_GPU_CHUNK_TEST)), + legate::detail::EnvironmentVariable("CUNUMERIC_MIN_GPU_CHUNK").get(MIN_GPU_CHUNK_DEFAULT, MIN_GPU_CHUNK_TEST)), min_cpu_chunk( - extract_env("CUNUMERIC_MIN_CPU_CHUNK", MIN_CPU_CHUNK_DEFAULT, MIN_CPU_CHUNK_TEST)), - min_omp_chunk(extract_env("CUNUMERIC_MIN_OMP_CHUNK", MIN_OMP_CHUNK_DEFAULT, MIN_OMP_CHUNK_TEST)) + legate::detail::EnvironmentVariable("CUNUMERIC_MIN_CPU_CHUNK").get(MIN_CPU_CHUNK_DEFAULT, MIN_CPU_CHUNK_TEST)), + min_omp_chunk(legate::detail::EnvironmentVariable("CUNUMERIC_MIN_OMP_CHUNK").get(MIN_OMP_CHUNK_DEFAULT, MIN_OMP_CHUNK_TEST)) { } diff --git a/tests/cpp/CMakeLists.txt b/tests/cpp/CMakeLists.txt index 01a80d729e..2a20047501 100644 --- a/tests/cpp/CMakeLists.txt +++ b/tests/cpp/CMakeLists.txt @@ -48,7 +48,7 @@ file(GLOB integration_GPU_SRC ${PROJECT_SOURCE_DIR}/integration/*.cu) list(APPEND integration_SRC ${integration_GPU_SRC}) if(NOT EXISTS ${CMAKE_BINARY_DIR}/RAPIDS.cmake) - file(DOWNLOAD https://raw.githubusercontent.com/rapidsai/rapids-cmake/branch-23.04/RAPIDS.cmake + file(DOWNLOAD https://raw.githubusercontent.com/rapidsai/rapids-cmake/v24.04.00/RAPIDS.cmake ${CMAKE_BINARY_DIR}/RAPIDS.cmake) endif() include(${CMAKE_BINARY_DIR}/RAPIDS.cmake) From f407887f5d43adaeeec1211fed1e8a1b58e9d3ab Mon Sep 17 00:00:00 2001 From: Manolis Papadakis Date: Wed, 22 May 2024 22:46:45 -0700 Subject: [PATCH 207/462] Run pre-commit --- src/cunumeric/mapper.cc | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/src/cunumeric/mapper.cc b/src/cunumeric/mapper.cc index ec3718e79f..87727622f3 100644 --- a/src/cunumeric/mapper.cc +++ b/src/cunumeric/mapper.cc @@ -23,11 +23,12 @@ using namespace legate::mapping; namespace cunumeric { CuNumericMapper::CuNumericMapper() - : min_gpu_chunk( - legate::detail::EnvironmentVariable("CUNUMERIC_MIN_GPU_CHUNK").get(MIN_GPU_CHUNK_DEFAULT, MIN_GPU_CHUNK_TEST)), - min_cpu_chunk( - legate::detail::EnvironmentVariable("CUNUMERIC_MIN_CPU_CHUNK").get(MIN_CPU_CHUNK_DEFAULT, MIN_CPU_CHUNK_TEST)), - min_omp_chunk(legate::detail::EnvironmentVariable("CUNUMERIC_MIN_OMP_CHUNK").get(MIN_OMP_CHUNK_DEFAULT, MIN_OMP_CHUNK_TEST)) + : min_gpu_chunk(legate::detail::EnvironmentVariable("CUNUMERIC_MIN_GPU_CHUNK") + .get(MIN_GPU_CHUNK_DEFAULT, MIN_GPU_CHUNK_TEST)), + min_cpu_chunk(legate::detail::EnvironmentVariable("CUNUMERIC_MIN_CPU_CHUNK") + .get(MIN_CPU_CHUNK_DEFAULT, MIN_CPU_CHUNK_TEST)), + min_omp_chunk(legate::detail::EnvironmentVariable("CUNUMERIC_MIN_OMP_CHUNK") + .get(MIN_OMP_CHUNK_DEFAULT, MIN_OMP_CHUNK_TEST)) { } From b87dd7db87c126d9daebfe77e82f74d748adfc7c Mon Sep 17 00:00:00 2001 From: Marcin Zalewski Date: Sat, 25 May 2024 16:03:13 -0700 Subject: [PATCH 208/462] Update Legate and fix cmake (#207) --- CMakeLists.txt | 4 +++- cmake/versions.json | 2 +- cunumeric_cpp.cmake | 2 +- 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 692f276a15..69cb226697 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -44,8 +44,10 @@ endif() ############################################################################## # - Download and initialize RAPIDS CMake helpers ----------------------------- +set(rapids-cmake-version 24.04) +set(rapids-cmake-sha "365322aca32fd6ecd7027f5d7ec7be50b7f3cc2a") if(NOT EXISTS ${CMAKE_BINARY_DIR}/RAPIDS.cmake) - file(DOWNLOAD https://raw.githubusercontent.com/rapidsai/rapids-cmake/v24.04.00/RAPIDS.cmake + file(DOWNLOAD https://raw.githubusercontent.com/rapidsai/rapids-cmake/branch-${rapids-cmake-version}/RAPIDS.cmake ${CMAKE_BINARY_DIR}/RAPIDS.cmake) endif() include(${CMAKE_BINARY_DIR}/RAPIDS.cmake) diff --git a/cmake/versions.json b/cmake/versions.json index 7a5817c62e..fdac9f7bf6 100644 --- a/cmake/versions.json +++ b/cmake/versions.json @@ -7,7 +7,7 @@ "git_url" : "git@github.com:nv-legate/legate.core.internal.git", "git_shallow": false, "always_download": false, - "git_tag" : "70955bf4a7e7600e577ab6e1c9178d6ad77f9b9a" + "git_tag" : "1c5e17e1f2d86bd9f1f3752f1bc895735697f22f" } } } diff --git a/cunumeric_cpp.cmake b/cunumeric_cpp.cmake index 5e056dab76..3aedaa601d 100644 --- a/cunumeric_cpp.cmake +++ b/cunumeric_cpp.cmake @@ -56,7 +56,7 @@ endif() # add third party dependencies using CPM rapids_cpm_init(OVERRIDE ${CMAKE_CURRENT_SOURCE_DIR}/cmake/versions.json) -find_package(OpenMP) +rapids_find_package(OpenMP GLOBAL_TARGETS OpenMP::OpenMP_CXX) option(Legion_USE_CUDA "Use CUDA" ON) option(Legion_USE_OpenMP "Use OpenMP" ${OpenMP_FOUND}) From 7730bf195636ee9c3ff3ea7b8532cbdb840b452f Mon Sep 17 00:00:00 2001 From: XiaLuNV <110973296+XiaLuNV@users.noreply.github.com> Date: Tue, 28 May 2024 10:26:25 +0800 Subject: [PATCH 209/462] add nanargmax/nanargmin/nanprod test case (#206) --- tests/integration/test_nan_reduction.py | 7 +++++++ tests/integration/test_nanarg_reduction.py | 15 +++++++++++++++ 2 files changed, 22 insertions(+) diff --git a/tests/integration/test_nan_reduction.py b/tests/integration/test_nan_reduction.py index e57a46d0b1..866435cb8b 100644 --- a/tests/integration/test_nan_reduction.py +++ b/tests/integration/test_nan_reduction.py @@ -281,6 +281,13 @@ def test_all_nans_nanprod(self, ndim): assert out_num == 1.0 + def test_dtype_nanprod(self) -> None: + in_np = np.arange(1, 10, step=1, dtype=np.int64) + out_np = np.nanprod(in_np) + in_num = num.arange(1, 10, 1, dtype=np.int64) + out_num = num.nanprod(in_num) + assert out_np == out_num + @pytest.mark.parametrize("ndim", range(1, LEGATE_MAX_DIM + 1)) def test_all_nans_nansum(self, ndim): shape = (3,) * ndim diff --git a/tests/integration/test_nanarg_reduction.py b/tests/integration/test_nanarg_reduction.py index 9956244a20..bd153a486e 100644 --- a/tests/integration/test_nanarg_reduction.py +++ b/tests/integration/test_nanarg_reduction.py @@ -225,6 +225,21 @@ def test_slice_nan_no_numpy_compat(self, identity, func_name): settings.numpy_compat.unset_value() + @pytest.mark.parametrize("func_name", NAN_ARG_FUNCS) + def test_empty_arr(self, func_name: str) -> None: + a = [] + in_np = np.array(a) + in_num = num.array(a) + func_np = getattr(np, func_name) + func_num = getattr(num, func_name) + with pytest.raises(ValueError, match="All-NaN"): + func_np(in_np) + # ValueError: All-NaN slice encountered + with pytest.raises(ValueError, match=" empty sequence"): + func_num(in_num) + # ValueError: attempt to get nanargmax of an empty sequence + # ValueError: attempt to get nanargmin of an empty sequence + class TestXFail: """ From b684a4cafeabbf6e3b96408845594b02c548b148 Mon Sep 17 00:00:00 2001 From: Manolis Papadakis Date: Tue, 28 May 2024 17:48:16 -0700 Subject: [PATCH 210/462] Add xfail due to cunumeric.internal#135 (#210) --- tests/integration/test_random_creation.py | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/integration/test_random_creation.py b/tests/integration/test_random_creation.py index 8e149c0962..3c3466c7b3 100644 --- a/tests/integration/test_random_creation.py +++ b/tests/integration/test_random_creation.py @@ -150,6 +150,7 @@ def test_default_rng_bitgenerator(): EAGER_TEST, reason="cuNumeric does not respect seed in Eager mode", ) +@pytest.mark.xfail(reason="cunumeric.internal#135") def test_default_rng_generator(): steps = 3 seed = 12345 From c3ee06136b68070f932a841e4e73e05e85d3e4d1 Mon Sep 17 00:00:00 2001 From: Parag Kulkarni Date: Wed, 29 May 2024 14:03:12 +0530 Subject: [PATCH 211/462] Move Upload functionality to legate-gh-ci (#208) * Move Upload functionality to legate-gh-ci * Test * Test for release * enable ucx. * Use v1.4 * Restore release to upload disabled state * ucx-enabled: false --- .github/workflows/gh-build-and-test.yml | 93 +++++++++++-------------- 1 file changed, 40 insertions(+), 53 deletions(-) diff --git a/.github/workflows/gh-build-and-test.yml b/.github/workflows/gh-build-and-test.yml index c6666c47eb..60d8b36d8c 100644 --- a/.github/workflows/gh-build-and-test.yml +++ b/.github/workflows/gh-build-and-test.yml @@ -55,63 +55,29 @@ jobs: build-mode: "" ucx-enabled: false upload-enabled: ${{ inputs.upload-enabled }} - secrets: inherit - + upload: needs: build - runs-on: linux-amd64-gpu-v100-earliest-1 - container: - options: -u root - image: condaforge/miniforge3:latest - - if: ${{ github.repository_owner == 'nv-legate' && inputs.upload-enabled == true }} - steps: - - name: Set environment variables - shell: bash --noprofile --norc -xeuo pipefail {0} - run: | - UCX_STR='' - BUILD_MODE='' - BUILD_MODE_STR="" - [ -n "${BUILD_MODE}" ] && BUILD_MODE_STR="-${BUILD_MODE}" - - echo "ARTIFACT_NAME=${{ inputs.platform }}-${{ inputs.build-type }}-${{ github.event.repository.name }}-${{ inputs.target-device }}${BUILD_MODE_STR}${UCX_STR}-${{ github.sha }}" >> $GITHUB_ENV - echo "ARTIFACTS_DIR=${{ github.workspace }}/artifacts" >> $GITHUB_ENV - echo "OUTPUT_FOLDER=conda-build/cunumeric" >> $GITHUB_ENV - echo "TARGET_PLATFORM=linux-64" >> $GITHUB_ENV - - - name: Download build artifacts - uses: actions/download-artifact@v4 - with: - name: ${{ env.ARTIFACT_NAME }} - path: ${{ env.ARTIFACTS_DIR }} - - - name: Display structure of downloaded artifacts - run: | - ls -aR $ARTIFACTS_DIR - - - name: Get Build Date - run: | - echo "BUILD_DATE=$(date +%Y%m%d)" >> $GITHUB_ENV - - - name: Dump relevant environment variables - run: | - echo "ARTIFACTS_DIR=$ARTIFACTS_DIR" - echo "ARTIFACT_NAME=$ARTIFACT_NAME" - echo "TARGET_PLATFORM=$TARGET_PLATFORM" - echo "BUILD_DATE=$BUILD_DATE" - - - name: Upload to URM - run: | - apt-get update - apt-get install -y curl - for f in $(find $ARTIFACTS_DIR/$OUTPUT_FOLDER/$TARGET_PLATFORM/. -name 'cunumeric-*.tar.bz2') - do - fname=$(basename $f) - echo "File to upload: $fname" - curl -usvc-legate-github:${{ secrets.URM_ARTIFACT_TOKEN }} -T $f "https://urm.nvidia.com/artifactory/sw-legate-conda-local/${TARGET_PLATFORM}/$fname;buildType="Nightly";buildDate=$BUILD_DATE;automatedTestingPassed=0;sha=${{ github.sha }}"; - done + if: ${{ github.repository_owner == 'nv-legate' && contains(github.workflow, 'release') && inputs.upload-enabled == true }} + name: Upload package to Server + uses: + nv-legate/legate-gh-ci/.github/workflows/gh-upload.yml@v1.4 + with: + client-repo: ${{ github.event.repository.name }} + build-type: ${{ inputs.build-type }} + name: Upload package to Server + target-device: ${{ inputs.target-device }} + platform: ${{ inputs.platform }} + legate-gh-ci-tag: "v1.4" + build-mode: "" + ucx-enabled: false + upload-enabled: ${{ inputs.upload-enabled }} + upload-action: "upload-package" + pkgSubString: "cunumeric-" + repos-Root: "cunumeric" + secrets: inherit setup-test: if: inputs.upload-enabled == false @@ -196,3 +162,24 @@ jobs: ucx-enabled: false upload-enabled: ${{ inputs.upload-enabled }} secrets: inherit + + updateTestStatus: + needs: test + name: Update Test status on Server + if: ${{ (github.repository_owner == 'nv-legate') && contains(github.workflow, 'Nightly') && (inputs.upload-enabled == true) }} + uses: + nv-legate/legate-gh-ci/.github/workflows/gh-upload.yml@v1.4 + with: + client-repo: ${{ github.event.repository.name }} + build-type: ${{ inputs.build-type }} + name: UpdateTestStatus + target-device: ${{ inputs.target-device }} + platform: ${{ inputs.platform }} + legate-gh-ci-tag: "v1.4" + build-mode: "" + ucx-enabled: false + upload-enabled: true + upload-action: "update-test-status" + pkgSubString: "cunumeric-" + repos-Root: "cunumeric" + secrets: inherit From 6d78fbc717a686b5b2f08798c936eb5795d9adcd Mon Sep 17 00:00:00 2001 From: Manolis Papadakis Date: Fri, 31 May 2024 00:42:31 -0700 Subject: [PATCH 212/462] At-operator is matmul not dot (#213) * At-operator is matmul not dot * Reduce dimensionality of inputs, to work under default max-dim=4 --- cunumeric/_array/array.py | 32 ++++++++++++++++++- tests/integration/test_matmul.py | 54 +++++++++++++++++++++++++++++++- 2 files changed, 84 insertions(+), 2 deletions(-) diff --git a/cunumeric/_array/array.py b/cunumeric/_array/array.py index 8af5889372..1321387912 100644 --- a/cunumeric/_array/array.py +++ b/cunumeric/_array/array.py @@ -897,6 +897,20 @@ def __ilshift__(self, rhs: Any) -> ndarray: """ return _ufunc.left_shift(self, rhs, out=self) + def __imatmul__(self, rhs: Any) -> ndarray: + """a.__imatmul__(value, /) + + Return ``self@=value``. + + Availability + -------- + Multiple GPUs, Multiple CPUs + + """ + from .._module.linalg_mvp import matmul + + return matmul(self, rhs, out=self) + def __imod__(self, rhs: Any) -> ndarray: """a.__imod__(value, /) @@ -1078,7 +1092,9 @@ def __matmul__(self, value: Any) -> ndarray: Multiple GPUs, Multiple CPUs """ - return self.dot(value) + from .._module.linalg_mvp import matmul + + return matmul(self, value) def __mod__(self, rhs: Any) -> ndarray: """a.__mod__(value, /) @@ -1276,6 +1292,20 @@ def __rfloordiv__(self, lhs: Any) -> ndarray: """ return _ufunc.floor_divide(lhs, self) + def __rmatmul__(self, lhs: Any) -> ndarray: + """a.__rmatmul__(value, /) + + Return ``value@self``. + + Availability + -------- + Multiple GPUs, Multiple CPUs + + """ + from .._module.linalg_mvp import matmul + + return matmul(lhs, self) + def __rmod__(self, lhs: Any) -> ndarray: """a.__rmod__(value, /) diff --git a/tests/integration/test_matmul.py b/tests/integration/test_matmul.py index b8167bea4a..de1c333563 100644 --- a/tests/integration/test_matmul.py +++ b/tests/integration/test_matmul.py @@ -16,6 +16,7 @@ import numpy as np import pytest from legate.core import LEGATE_MAX_DIM +from utils.comparisons import allclose from utils.contractions import ( check_default, check_permutations, @@ -29,7 +30,7 @@ @pytest.mark.parametrize("a_ndim", range(1, LEGATE_MAX_DIM + 1)) @pytest.mark.parametrize("b_ndim", range(1, LEGATE_MAX_DIM + 1)) -def test(a_ndim, b_ndim): +def test_function(a_ndim, b_ndim): name = f"matmul({a_ndim} x {b_ndim})" modes = matmul_modes(a_ndim, b_ndim) @@ -43,6 +44,57 @@ def operation(lib, *args, **kwargs): check_types(name, modes, operation) +@pytest.mark.parametrize( + "a_shape", + ( + (3, 4, 5), + (4, 5), + (5,), + ), +) +@pytest.mark.parametrize( + "b_shape", + ( + (3, 5, 6), + (5, 6), + (5,), + ), +) +def test_operator(a_shape, b_shape): + np_a = np.random.random(a_shape) + np_b = np.random.random(b_shape) + num_a = num.array(np_a) + num_b = num.array(np_b) + assert allclose(np_a @ np_b, num_a @ num_b) + + +@pytest.mark.parametrize( + "a_shape", + ( + (3, 4, 5), + (4, 5), + (5,), + ), +) +@pytest.mark.parametrize( + "b_shape", + ( + (3, 5, 5), + (5, 5), + ), +) +def test_inplace_operator(a_shape, b_shape): + if len(a_shape) < len(b_shape): + return + np_a = np.random.random(a_shape) + np_b = np.random.random(b_shape) + num_a = num.array(np_a) + num_b = num.array(np_b) + np_a @= np_b + num_a @= num_b + assert allclose(np_a, num_a) + + class TestMatmulErrors: @pytest.mark.parametrize( "shapesAB", From 5bba16b211cf5438fb97e43c9023c0f55c28ed08 Mon Sep 17 00:00:00 2001 From: Jacob Faibussowitsch Date: Thu, 6 Jun 2024 15:36:33 -0400 Subject: [PATCH 213/462] SCREAMING MACROS (#217) * SCREAMING MACROS * bump legate version * Roll our own LEGATE_CHECK_CUDA() --- cmake/versions.json | 2 +- scripts/hooks/legate_defined.sh | 6 +- src/cunumeric/arg_redop_register.cc | 2 +- src/cunumeric/binary/binary_op.cu | 2 +- src/cunumeric/binary/binary_op.h | 4 +- src/cunumeric/binary/binary_op_template.inl | 2 +- src/cunumeric/binary/binary_red.cu | 2 +- src/cunumeric/binary/binary_red.h | 4 +- src/cunumeric/binary/binary_red_template.inl | 2 +- src/cunumeric/bits/packbits.cu | 2 +- src/cunumeric/bits/packbits.h | 4 +- src/cunumeric/bits/unpackbits.cu | 2 +- src/cunumeric/bits/unpackbits.h | 4 +- src/cunumeric/convolution/convolve.cu | 70 +++++----- src/cunumeric/convolution/convolve.h | 4 +- src/cunumeric/cuda_help.h | 40 +++++- src/cunumeric/cudalibs.cu | 18 +-- src/cunumeric/cudalibs.h | 6 +- src/cunumeric/cunumeric.cc | 4 +- .../device_scalar_reduction_buffer.h | 10 +- .../indexing/parallel_loop.cuh | 2 +- .../reduction/scalar_reduction.cuh | 2 +- src/cunumeric/fft/fft.cu | 12 +- src/cunumeric/fft/fft.h | 2 +- src/cunumeric/index/advanced_indexing.cu | 4 +- src/cunumeric/index/advanced_indexing.h | 4 +- src/cunumeric/index/choose.cu | 2 +- src/cunumeric/index/choose.h | 4 +- src/cunumeric/index/choose_template.inl | 2 +- src/cunumeric/index/putmask.h | 4 +- src/cunumeric/index/putmask_template.inl | 4 +- src/cunumeric/index/repeat.cu | 6 +- src/cunumeric/index/repeat.h | 4 +- src/cunumeric/index/select.cu | 2 +- src/cunumeric/index/select.h | 4 +- src/cunumeric/index/select_template.inl | 2 +- src/cunumeric/index/wrap.cu | 4 +- src/cunumeric/index/wrap.h | 4 +- src/cunumeric/index/wrap_template.inl | 2 +- src/cunumeric/index/zip.cu | 4 +- src/cunumeric/index/zip.h | 4 +- src/cunumeric/index/zip_template.inl | 2 +- src/cunumeric/item/read.cu | 2 +- src/cunumeric/item/read.h | 4 +- src/cunumeric/item/write.cu | 2 +- src/cunumeric/item/write.h | 4 +- src/cunumeric/matrix/batched_cholesky.cc | 2 +- src/cunumeric/matrix/batched_cholesky.cu | 2 +- src/cunumeric/matrix/batched_cholesky.h | 4 +- src/cunumeric/matrix/contract.cu | 2 +- src/cunumeric/matrix/contract.h | 4 +- src/cunumeric/matrix/diag.cu | 4 +- src/cunumeric/matrix/diag.h | 4 +- src/cunumeric/matrix/dot.cu | 2 +- src/cunumeric/matrix/dot.h | 4 +- src/cunumeric/matrix/dot_template.inl | 2 +- src/cunumeric/matrix/gemm.cc | 2 +- src/cunumeric/matrix/gemm.cu | 4 +- src/cunumeric/matrix/gemm.h | 4 +- src/cunumeric/matrix/matmul.cc | 4 +- src/cunumeric/matrix/matmul.cu | 10 +- src/cunumeric/matrix/matmul.h | 4 +- src/cunumeric/matrix/matvecmul.cc | 4 +- src/cunumeric/matrix/matvecmul.cu | 10 +- src/cunumeric/matrix/matvecmul.h | 4 +- src/cunumeric/matrix/mp_potrf.cu | 2 +- src/cunumeric/matrix/mp_potrf.h | 2 +- src/cunumeric/matrix/mp_solve.cu | 2 +- src/cunumeric/matrix/mp_solve.h | 2 +- src/cunumeric/matrix/potrf.cc | 2 +- src/cunumeric/matrix/potrf.cu | 4 +- src/cunumeric/matrix/potrf.h | 4 +- src/cunumeric/matrix/qr.cc | 2 +- src/cunumeric/matrix/qr.cu | 17 +-- src/cunumeric/matrix/qr.h | 4 +- src/cunumeric/matrix/solve.cc | 2 +- src/cunumeric/matrix/solve.cu | 4 +- src/cunumeric/matrix/solve.h | 4 +- src/cunumeric/matrix/svd.cc | 2 +- src/cunumeric/matrix/svd.cu | 6 +- src/cunumeric/matrix/svd.h | 4 +- src/cunumeric/matrix/syrk.cc | 2 +- src/cunumeric/matrix/syrk.cu | 2 +- src/cunumeric/matrix/syrk.h | 4 +- src/cunumeric/matrix/tile.cu | 2 +- src/cunumeric/matrix/tile.h | 4 +- src/cunumeric/matrix/transpose.cc | 4 +- src/cunumeric/matrix/transpose.cu | 2 +- src/cunumeric/matrix/transpose.h | 4 +- src/cunumeric/matrix/trilu.cu | 2 +- src/cunumeric/matrix/trilu.h | 4 +- src/cunumeric/matrix/trsm.cc | 2 +- src/cunumeric/matrix/trsm.cu | 2 +- src/cunumeric/matrix/trsm.h | 4 +- src/cunumeric/matrix/util.cc | 10 +- src/cunumeric/nullary/arange.cu | 2 +- src/cunumeric/nullary/arange.h | 4 +- src/cunumeric/nullary/eye.cu | 2 +- src/cunumeric/nullary/eye.h | 4 +- src/cunumeric/nullary/fill.cu | 2 +- src/cunumeric/nullary/fill.h | 4 +- src/cunumeric/nullary/fill_template.inl | 2 +- src/cunumeric/nullary/window.cu | 2 +- src/cunumeric/nullary/window.h | 4 +- src/cunumeric/nullary/window_template.inl | 2 +- src/cunumeric/random/bitgenerator.cu | 4 +- src/cunumeric/random/bitgenerator.h | 4 +- src/cunumeric/random/rand.cu | 2 +- src/cunumeric/random/rand.h | 4 +- src/cunumeric/random/randutil/generator.cuh | 26 ++-- src/cunumeric/random/randutil/generator.h | 2 +- .../random/randutil/generator_host.cc | 2 +- src/cunumeric/random/randutil/randutil.h | 2 +- .../random/randutil/randutil_curand.h | 2 +- src/cunumeric/scan/scan_global.cu | 2 +- src/cunumeric/scan/scan_global.h | 4 +- src/cunumeric/scan/scan_local.cu | 4 +- src/cunumeric/scan/scan_local.h | 4 +- src/cunumeric/search/argwhere.cu | 4 +- src/cunumeric/search/argwhere.h | 4 +- src/cunumeric/search/nonzero.cu | 2 +- src/cunumeric/search/nonzero.cuh | 2 +- src/cunumeric/search/nonzero.h | 4 +- src/cunumeric/set/unique.cu | 20 +-- src/cunumeric/set/unique.h | 4 +- src/cunumeric/set/unique_reduce.h | 2 +- src/cunumeric/sort/cub_sort.cuh | 4 +- src/cunumeric/sort/searchsorted.cu | 2 +- src/cunumeric/sort/searchsorted.h | 4 +- src/cunumeric/sort/sort.cu | 126 +++++++++--------- src/cunumeric/sort/sort.h | 4 +- src/cunumeric/sort/thrust_sort.cuh | 4 +- src/cunumeric/stat/bincount.cu | 4 +- src/cunumeric/stat/bincount.h | 4 +- src/cunumeric/stat/histogram.cuh | 2 +- src/cunumeric/stat/histogram.h | 4 +- src/cunumeric/stat/histogram_cpu.h | 2 +- src/cunumeric/ternary/where.cu | 2 +- src/cunumeric/ternary/where.h | 4 +- src/cunumeric/ternary/where_template.inl | 2 +- src/cunumeric/transform/flip.cu | 2 +- src/cunumeric/transform/flip.h | 4 +- src/cunumeric/unary/convert.cu | 2 +- src/cunumeric/unary/convert.h | 4 +- src/cunumeric/unary/convert_template.inl | 2 +- src/cunumeric/unary/scalar_unary_red.h | 4 +- .../unary/scalar_unary_red_template.inl | 4 +- src/cunumeric/unary/unary_op.cu | 6 +- src/cunumeric/unary/unary_op.h | 4 +- src/cunumeric/unary/unary_op_template.inl | 6 +- src/cunumeric/unary/unary_red.cu | 4 +- src/cunumeric/unary/unary_red.h | 4 +- src/cunumeric/utilities/repartition.cu | 18 +-- 153 files changed, 434 insertions(+), 399 deletions(-) diff --git a/cmake/versions.json b/cmake/versions.json index fdac9f7bf6..b9024a7f15 100644 --- a/cmake/versions.json +++ b/cmake/versions.json @@ -7,7 +7,7 @@ "git_url" : "git@github.com:nv-legate/legate.core.internal.git", "git_shallow": false, "always_download": false, - "git_tag" : "1c5e17e1f2d86bd9f1f3752f1bc895735697f22f" + "git_tag" : "0ab2504c03850c91c4dabc553f80016804aeaef2" } } } diff --git a/scripts/hooks/legate_defined.sh b/scripts/hooks/legate_defined.sh index e31c006250..d5d38fa253 100755 --- a/scripts/hooks/legate_defined.sh +++ b/scripts/hooks/legate_defined.sh @@ -21,19 +21,19 @@ elif [[ ${rc} -eq 0 ]]; then echo "${output}" echo "" echo "Instances of preprocessor ifdef/ifndef/if defined found, use" - echo "LegateDefined() instead:" + echo "LEGATE_DEFINED() instead:" echo "" echo "- #ifdef LEGATE_USE_FOO" echo "- #include \"foo.h\"" echo "- #endif" - echo "+ #if LegateDefined(LEGATE_USE_FOO)" + echo "+ #if LEGATE_DEFINED(LEGATE_USE_FOO)" echo "+ #include \"foo.h\"" echo "+ #endif" echo "" echo "- #ifdef LEGATE_USE_FOO" echo "- x = 2;" echo "- #endif" - echo "+ if (LegateDefined(LEGATE_USE_FOO)) {" + echo "+ if (LEGATE_DEFINED(LEGATE_USE_FOO)) {" echo "+ x = 2;" echo "+ }" echo "x ===------------------------------------------------------------------=== x" diff --git a/src/cunumeric/arg_redop_register.cc b/src/cunumeric/arg_redop_register.cc index ffb334046b..d37cd69972 100644 --- a/src/cunumeric/arg_redop_register.cc +++ b/src/cunumeric/arg_redop_register.cc @@ -57,7 +57,7 @@ DEFINE_IDENTITIES(uint64_t) } // namespace cunumeric -#if !LegateDefined(LEGATE_USE_CUDA) +#if !LEGATE_DEFINED(LEGATE_USE_CUDA) extern "C" { ReductionOpIds cunumeric_register_reduction_ops(int32_t code) diff --git a/src/cunumeric/binary/binary_op.cu b/src/cunumeric/binary/binary_op.cu index 3f14a2b1ff..15e8a5c99e 100644 --- a/src/cunumeric/binary/binary_op.cu +++ b/src/cunumeric/binary/binary_op.cu @@ -82,7 +82,7 @@ struct BinaryOpImplBody { generic_kernel<<>>( volume, func, out, in1, in2, pitches, rect); } - LegateCheckCUDAStream(stream); + CUNUMERIC_CHECK_CUDA_STREAM(stream); } }; diff --git a/src/cunumeric/binary/binary_op.h b/src/cunumeric/binary/binary_op.h index e963e1f981..bac015f5ac 100644 --- a/src/cunumeric/binary/binary_op.h +++ b/src/cunumeric/binary/binary_op.h @@ -35,10 +35,10 @@ class BinaryOpTask : public CuNumericTask { public: static void cpu_variant(legate::TaskContext context); -#if LegateDefined(LEGATE_USE_OPENMP) +#if LEGATE_DEFINED(LEGATE_USE_OPENMP) static void omp_variant(legate::TaskContext context); #endif -#if LegateDefined(LEGATE_USE_CUDA) +#if LEGATE_DEFINED(LEGATE_USE_CUDA) static void gpu_variant(legate::TaskContext context); #endif }; diff --git a/src/cunumeric/binary/binary_op_template.inl b/src/cunumeric/binary/binary_op_template.inl index aed10e995c..dd83a9bde6 100644 --- a/src/cunumeric/binary/binary_op_template.inl +++ b/src/cunumeric/binary/binary_op_template.inl @@ -51,7 +51,7 @@ struct BinaryOpImpl { auto in1 = args.in1.read_accessor(rect); auto in2 = args.in2.read_accessor(rect); -#if !LegateDefined(LEGATE_BOUNDS_CHECKS) +#if !LEGATE_DEFINED(LEGATE_BOUNDS_CHECKS) // Check to see if this is dense or not bool dense = out.accessor.is_dense_row_major(rect) && in1.accessor.is_dense_row_major(rect) && in2.accessor.is_dense_row_major(rect); diff --git a/src/cunumeric/binary/binary_red.cu b/src/cunumeric/binary/binary_red.cu index 823ed3c238..a2299acd0c 100644 --- a/src/cunumeric/binary/binary_red.cu +++ b/src/cunumeric/binary/binary_red.cu @@ -82,7 +82,7 @@ struct BinaryRedImplBody { } copy_kernel<<<1, 1, 0, stream>>>(result, out); - LegateCheckCUDAStream(stream); + CUNUMERIC_CHECK_CUDA_STREAM(stream); } }; diff --git a/src/cunumeric/binary/binary_red.h b/src/cunumeric/binary/binary_red.h index 749793839a..af9a3556ef 100644 --- a/src/cunumeric/binary/binary_red.h +++ b/src/cunumeric/binary/binary_red.h @@ -35,10 +35,10 @@ class BinaryRedTask : public CuNumericTask { public: static void cpu_variant(legate::TaskContext context); -#if LegateDefined(LEGATE_USE_OPENMP) +#if LEGATE_DEFINED(LEGATE_USE_OPENMP) static void omp_variant(legate::TaskContext context); #endif -#if LegateDefined(LEGATE_USE_CUDA) +#if LEGATE_DEFINED(LEGATE_USE_CUDA) static void gpu_variant(legate::TaskContext context); #endif }; diff --git a/src/cunumeric/binary/binary_red_template.inl b/src/cunumeric/binary/binary_red_template.inl index 65d0f2f00c..78143d1dcb 100644 --- a/src/cunumeric/binary/binary_red_template.inl +++ b/src/cunumeric/binary/binary_red_template.inl @@ -56,7 +56,7 @@ struct BinaryRedImpl { auto in1 = args.in1.read_accessor(rect); auto in2 = args.in2.read_accessor(rect); -#if !LegateDefined(LEGATE_BOUNDS_CHECKS) +#if !LEGATE_DEFINED(LEGATE_BOUNDS_CHECKS) // Check to see if this is dense or not bool dense = in1.accessor.is_dense_row_major(rect) && in2.accessor.is_dense_row_major(rect); #else diff --git a/src/cunumeric/bits/packbits.cu b/src/cunumeric/bits/packbits.cu index 7903bbded1..4f671783cd 100644 --- a/src/cunumeric/bits/packbits.cu +++ b/src/cunumeric/bits/packbits.cu @@ -76,7 +76,7 @@ struct PackbitsImplBody { in_hi_axis, axis); } - LegateCheckCUDAStream(stream); + CUNUMERIC_CHECK_CUDA_STREAM(stream); } }; diff --git a/src/cunumeric/bits/packbits.h b/src/cunumeric/bits/packbits.h index 712e24da08..c8807e8437 100644 --- a/src/cunumeric/bits/packbits.h +++ b/src/cunumeric/bits/packbits.h @@ -107,10 +107,10 @@ class PackbitsTask : public CuNumericTask { public: static void cpu_variant(legate::TaskContext context); -#if LegateDefined(LEGATE_USE_OPENMP) +#if LEGATE_DEFINED(LEGATE_USE_OPENMP) static void omp_variant(legate::TaskContext context); #endif -#if LegateDefined(LEGATE_USE_CUDA) +#if LEGATE_DEFINED(LEGATE_USE_CUDA) static void gpu_variant(legate::TaskContext context); #endif }; diff --git a/src/cunumeric/bits/unpackbits.cu b/src/cunumeric/bits/unpackbits.cu index acc7212706..60b50d1a3a 100644 --- a/src/cunumeric/bits/unpackbits.cu +++ b/src/cunumeric/bits/unpackbits.cu @@ -55,7 +55,7 @@ struct UnpackbitsImplBody { const size_t blocks = (in_volume + THREADS_PER_BLOCK - 1) / THREADS_PER_BLOCK; generic_kernel<<>>( in_volume, unpack, out, in, in_pitches, in_rect.lo, axis); - LegateCheckCUDAStream(stream); + CUNUMERIC_CHECK_CUDA_STREAM(stream); } }; diff --git a/src/cunumeric/bits/unpackbits.h b/src/cunumeric/bits/unpackbits.h index 0b5fc0b8ad..42f8ae0838 100644 --- a/src/cunumeric/bits/unpackbits.h +++ b/src/cunumeric/bits/unpackbits.h @@ -64,10 +64,10 @@ class UnpackbitsTask : public CuNumericTask { public: static void cpu_variant(legate::TaskContext context); -#if LegateDefined(LEGATE_USE_OPENMP) +#if LEGATE_DEFINED(LEGATE_USE_OPENMP) static void omp_variant(legate::TaskContext context); #endif -#if LegateDefined(LEGATE_USE_CUDA) +#if LEGATE_DEFINED(LEGATE_USE_CUDA) static void gpu_variant(legate::TaskContext context); #endif }; diff --git a/src/cunumeric/convolution/convolve.cu b/src/cunumeric/convolution/convolve.cu index 8d63086e84..ae266708da 100644 --- a/src/cunumeric/convolution/convolve.cu +++ b/src/cunumeric/convolution/convolve.cu @@ -744,7 +744,7 @@ __host__ static inline void launch_small_tile_kernel(AccessorWO out, out, filter, in, root_rect, subrect, filter_rect, args); } } - LegateCheckCUDAStream(stream); + CUNUMERIC_CHECK_CUDA_STREAM(stream); } template @@ -766,24 +766,24 @@ __host__ void direct_convolution(AccessorWO out, static unsigned long long mask = 0; if (!(mask & (1 << device))) { if (properties.sharedMemPerBlock < max_smem_size) { - LegateCheckCUDA(cudaFuncSetAttribute(convolution_small_tile1, - cudaFuncAttributeMaxDynamicSharedMemorySize, - max_smem_size)); - LegateCheckCUDA(cudaFuncSetAttribute(convolution_small_tile2, - cudaFuncAttributeMaxDynamicSharedMemorySize, - max_smem_size)); - LegateCheckCUDA(cudaFuncSetAttribute(convolution_large_tile, - cudaFuncAttributeMaxDynamicSharedMemorySize, - max_smem_size)); + CUNUMERIC_CHECK_CUDA(cudaFuncSetAttribute(convolution_small_tile1, + cudaFuncAttributeMaxDynamicSharedMemorySize, + max_smem_size)); + CUNUMERIC_CHECK_CUDA(cudaFuncSetAttribute(convolution_small_tile2, + cudaFuncAttributeMaxDynamicSharedMemorySize, + max_smem_size)); + CUNUMERIC_CHECK_CUDA(cudaFuncSetAttribute(convolution_large_tile, + cudaFuncAttributeMaxDynamicSharedMemorySize, + max_smem_size)); } if (sizeof(VAL) >= 8) { // Only need to set this on the first invocation - LegateCheckCUDA(cudaFuncSetSharedMemConfig(convolution_small_tile1, - cudaSharedMemBankSizeEightByte)); - LegateCheckCUDA(cudaFuncSetSharedMemConfig(convolution_small_tile2, - cudaSharedMemBankSizeEightByte)); - LegateCheckCUDA(cudaFuncSetSharedMemConfig(convolution_large_tile, - cudaSharedMemBankSizeEightByte)); + CUNUMERIC_CHECK_CUDA(cudaFuncSetSharedMemConfig(convolution_small_tile1, + cudaSharedMemBankSizeEightByte)); + CUNUMERIC_CHECK_CUDA(cudaFuncSetSharedMemConfig(convolution_small_tile2, + cudaSharedMemBankSizeEightByte)); + CUNUMERIC_CHECK_CUDA(cudaFuncSetSharedMemConfig(convolution_large_tile, + cudaSharedMemBankSizeEightByte)); } // Make sure we have enough bits for every device assert(device < (8 * sizeof(mask))); @@ -848,7 +848,7 @@ __host__ void direct_convolution(AccessorWO out, } if (out_dense) { size_t bytes = sizeof(VAL) * out_pitch; - LegateCheckCUDA(cudaMemsetAsync(out_ptr, 0, bytes)); + CUNUMERIC_CHECK_CUDA(cudaMemsetAsync(out_ptr, 0, bytes)); } else { out_pitch = 1; ConvolutionInitArgs args; @@ -1168,7 +1168,7 @@ __host__ void direct_convolution(AccessorWO out, one, args); } - LegateCheckCUDAStream(stream); + CUNUMERIC_CHECK_CUDA_STREAM(stream); } } @@ -1310,19 +1310,19 @@ __host__ static inline void cufft_convolution(AccessorWO out, static unsigned long long mask = 0; if (!(mask & (1 << device))) { if (properties.sharedMemPerBlock < max_smem_size) { - LegateCheckCUDA(cudaFuncSetAttribute(convolution_small_tile1, - cudaFuncAttributeMaxDynamicSharedMemorySize, - max_smem_size)); - LegateCheckCUDA(cudaFuncSetAttribute(convolution_small_tile2, - cudaFuncAttributeMaxDynamicSharedMemorySize, - max_smem_size)); + CUNUMERIC_CHECK_CUDA(cudaFuncSetAttribute(convolution_small_tile1, + cudaFuncAttributeMaxDynamicSharedMemorySize, + max_smem_size)); + CUNUMERIC_CHECK_CUDA(cudaFuncSetAttribute(convolution_small_tile2, + cudaFuncAttributeMaxDynamicSharedMemorySize, + max_smem_size)); } if (sizeof(VAL) >= 8) { // Only need to set this on the first invocation - LegateCheckCUDA(cudaFuncSetSharedMemConfig(convolution_small_tile1, - cudaSharedMemBankSizeEightByte)); - LegateCheckCUDA(cudaFuncSetSharedMemConfig(convolution_small_tile2, - cudaSharedMemBankSizeEightByte)); + CUNUMERIC_CHECK_CUDA(cudaFuncSetSharedMemConfig(convolution_small_tile1, + cudaSharedMemBankSizeEightByte)); + CUNUMERIC_CHECK_CUDA(cudaFuncSetSharedMemConfig(convolution_small_tile2, + cudaSharedMemBankSizeEightByte)); } // Make sure we have enough bits for every device assert(device < (8 * sizeof(mask))); @@ -1405,7 +1405,7 @@ __host__ static inline void cufft_convolution(AccessorWO out, // Zero pad and copy in the input data auto signal_buffer = create_buffer(buffersize, Memory::GPU_FB_MEM, 128 /*alignment*/); VAL* signal_ptr = signal_buffer.ptr(zero); - LegateCheckCUDA(cudaMemsetAsync(signal_ptr, 0, buffervolume * sizeof(VAL), stream)); + CUNUMERIC_CHECK_CUDA(cudaMemsetAsync(signal_ptr, 0, buffervolume * sizeof(VAL), stream)); // Check to see if the input pointer is dense and we can do this with a CUDA memcpy size_t strides[DIM]; const VAL* input_ptr = in.ptr(input_bounds, strides); @@ -1421,7 +1421,7 @@ __host__ static inline void cufft_convolution(AccessorWO out, // Zero pad and copy in the filter data auto filter_buffer = create_buffer(buffersize, Memory::GPU_FB_MEM, 128 /*alignment*/); VAL* filter_ptr = filter_buffer.ptr(zero); - LegateCheckCUDA(cudaMemsetAsync(filter_ptr, 0, buffervolume * sizeof(VAL), stream)); + CUNUMERIC_CHECK_CUDA(cudaMemsetAsync(filter_ptr, 0, buffervolume * sizeof(VAL), stream)); const VAL* filt_ptr = filter.ptr(filter_rect, strides); pitch = 1; for (int d = DIM - 1; d >= 0; d--) { @@ -1432,7 +1432,7 @@ __host__ static inline void cufft_convolution(AccessorWO out, copy_into_buffer<<>>( filter, filter_buffer, filter_rect.lo, copy_pitches, pitch); - LegateCheckCUDAStream(stream); + CUNUMERIC_CHECK_CUDA_STREAM(stream); auto forward_plan = get_cufft_plan(ForwardPlanType::value, cufftPlanParams(fftsize)); auto backward_plan = get_cufft_plan(BackwardPlanType::value, cufftPlanParams(fftsize)); @@ -1455,7 +1455,7 @@ __host__ static inline void cufft_convolution(AccessorWO out, // FFT the filter data cufft_execute_forward(forward_plan.handle(), filter_ptr, filter_ptr); - LegateCheckCUDAStream(stream); + CUNUMERIC_CHECK_CUDA_STREAM(stream); // Perform the pointwise multiplcation { @@ -1492,13 +1492,13 @@ __host__ static inline void cufft_convolution(AccessorWO out, copy_from_buffer<<>>( filter_ptr, out, buffer_offset, subrect.lo, copy_pitches, fft_pitches, pitch, scaling_factor); - LegateCheckCUDAStream(stream); + CUNUMERIC_CHECK_CUDA_STREAM(stream); #if 0 // This is useful debugging code for finding the output VAL *buffer = (VAL*)malloc(buffervolume*sizeof(VAL)); - LegateCheckCUDA( cudaMemcpyAsync(buffer, filter_ptr, buffervolume*sizeof(VAL), cudaMemcpyDeviceToHost, stream) ); - LegateCheckCUDA( cudaStreamSynchronize(stream) ); + CUNUMERIC_CHECK_CUDA( cudaMemcpyAsync(buffer, filter_ptr, buffervolume*sizeof(VAL), cudaMemcpyDeviceToHost, stream) ); + CUNUMERIC_CHECK_CUDA( cudaStreamSynchronize(stream) ); for (unsigned idx = 0; idx < buffervolume; idx++) { if ((idx % fftsize[DIM-1]) == 0) printf("\n"); diff --git a/src/cunumeric/convolution/convolve.h b/src/cunumeric/convolution/convolve.h index d673735ea2..f3a8e7663c 100644 --- a/src/cunumeric/convolution/convolve.h +++ b/src/cunumeric/convolution/convolve.h @@ -42,10 +42,10 @@ class ConvolveTask : public CuNumericTask { public: static void cpu_variant(legate::TaskContext context); -#if LegateDefined(LEGATE_USE_OPENMP) +#if LEGATE_DEFINED(LEGATE_USE_OPENMP) static void omp_variant(legate::TaskContext context); #endif -#if LegateDefined(LEGATE_USE_CUDA) +#if LEGATE_DEFINED(LEGATE_USE_CUDA) static void gpu_variant(legate::TaskContext context); #endif }; diff --git a/src/cunumeric/cuda_help.h b/src/cunumeric/cuda_help.h index 177f3da669..3f66652164 100644 --- a/src/cunumeric/cuda_help.h +++ b/src/cunumeric/cuda_help.h @@ -17,13 +17,12 @@ #pragma once #include "legate.h" -#include "core/cuda/cuda.h" #include "core/cuda/stream_pool.h" #include "cunumeric/arg.h" #include "cunumeric/device_scalar_reduction_buffer.h" #include #include -#if LegateDefined(CUNUMERIC_USE_CUSOLVERMP) +#if LEGATE_DEFINED(CUNUMERIC_USE_CUSOLVERMP) #include #include #endif @@ -75,6 +74,22 @@ check_nccl(result, __FILE__, __LINE__); \ } while (false) +#define CUNUMERIC_CHECK_CUDA(...) \ + do { \ + cudaError_t result = __VA_ARGS__; \ + check_cuda(result, __FILE__, __LINE__); \ + } while (false) + +#ifdef DEBUG_CUNUMERIC +#define CUNUMERIC_CHECK_CUDA_STREAM(stream) \ + do { \ + CUNUMERIC_CHECK_CUDA(cudaStreamSynchronize(stream)); \ + CUNUMERIC_CHECK_CUDA(cudaPeekAtLastError()); \ + } while (false) +#else +#define CUNUMERIC_CHECK_CUDA_STREAM(stream) static_cast(stream) +#endif + #ifndef MAX #define MAX(x, y) (((x) > (y)) ? (x) : (y)) #endif @@ -174,12 +189,29 @@ int get_device_ordinal(); const cudaDeviceProp& get_device_properties(); cublasHandle_t get_cublas(); cusolverDnHandle_t get_cusolver(); -#if LegateDefined(CUNUMERIC_USE_CUSOLVERMP) +#if LEGATE_DEFINED(CUNUMERIC_USE_CUSOLVERMP) cusolverMpHandle_t get_cusolvermp(); #endif cutensorHandle_t* get_cutensor(); cufftContext get_cufft_plan(cufftType type, const cufftPlanParams& params); +__host__ inline void check_cuda(cublasStatus_t status, const char* file, int line) +{ + if (error != cudaSuccess) { + fprintf(stderr, + "Internal CUDA failure with error %s (%s) in file %s at line %d\n", + cudaGetErrorString(error), + cudaGetErrorName(error), + file, + line); +#ifdef DEBUG_CUNUMERIC + assert(false); +#else + exit(status); +#endif + } +} + __host__ inline void check_cublas(cublasStatus_t status, const char* file, int line) { if (status != CUBLAS_STATUS_SUCCESS) { @@ -228,7 +260,7 @@ __host__ inline void check_cusolver(cusolverStatus_t status, const char* file, i } } -#if LegateDefined(CUNUMERIC_USE_CUSOLVERMP) +#if LEGATE_DEFINED(CUNUMERIC_USE_CUSOLVERMP) __host__ inline void check_cal(calError_t status, const char* file, int line) { if (status != CAL_OK) { diff --git a/src/cunumeric/cudalibs.cu b/src/cunumeric/cudalibs.cu index 2d437aafc8..7424cc5c64 100644 --- a/src/cunumeric/cudalibs.cu +++ b/src/cunumeric/cudalibs.cu @@ -271,7 +271,7 @@ CUDALibraries::CUDALibraries() : finalized_(false), cublas_(nullptr), cusolver_(nullptr), -#if LegateDefined(CUNUMERIC_USE_CUSOLVERMP) +#if LEGATE_DEFINED(CUNUMERIC_USE_CUSOLVERMP) cusolvermp_(nullptr), #endif cutensor_(nullptr), @@ -292,7 +292,7 @@ void CUDALibraries::finalize() if (cusolver_ != nullptr) { finalize_cusolver(); } -#if LegateDefined(CUNUMERIC_USE_CUSOLVERMP) +#if LEGATE_DEFINED(CUNUMERIC_USE_CUSOLVERMP) if (cusolvermp_ != nullptr) { finalize_cusolvermp(); } @@ -318,7 +318,7 @@ void CUDALibraries::finalize_cusolver() cusolver_ = nullptr; } -#if LegateDefined(CUNUMERIC_USE_CUSOLVERMP) +#if LEGATE_DEFINED(CUNUMERIC_USE_CUSOLVERMP) void CUDALibraries::finalize_cusolvermp() { CHECK_CUSOLVER(cusolverMpDestroy(cusolvermp_)); @@ -338,7 +338,7 @@ int CUDALibraries::get_device_ordinal() return *ordinal_; } int ordinal{-1}; - LegateCheckCUDA(cudaGetDevice(&ordinal)); + CUNUMERIC_CHECK_CUDA(cudaGetDevice(&ordinal)); ordinal_ = ordinal; return ordinal; } @@ -349,7 +349,7 @@ const cudaDeviceProp& CUDALibraries::get_device_properties() return *device_prop_; } device_prop_ = std::make_unique(); - LegateCheckCUDA(cudaGetDeviceProperties(device_prop_.get(), get_device_ordinal())); + CUNUMERIC_CHECK_CUDA(cudaGetDeviceProperties(device_prop_.get(), get_device_ordinal())); return *device_prop_; } @@ -377,12 +377,12 @@ cusolverDnHandle_t CUDALibraries::get_cusolver() return cusolver_; } -#if LegateDefined(CUNUMERIC_USE_CUSOLVERMP) +#if LEGATE_DEFINED(CUNUMERIC_USE_CUSOLVERMP) cusolverMpHandle_t CUDALibraries::get_cusolvermp() { if (nullptr == cusolvermp_) { int device = -1; - LegateCheckCUDA(cudaGetDevice(&device)); + CUNUMERIC_CHECK_CUDA(cudaGetDevice(&device)); CHECK_CUSOLVER(cusolverMpCreate(&cusolvermp_, device, get_cached_stream())); } return cusolvermp_; @@ -442,7 +442,7 @@ cusolverDnContext* get_cusolver() return lib.get_cusolver(); } -#if LegateDefined(CUNUMERIC_USE_CUSOLVERMP) +#if LEGATE_DEFINED(CUNUMERIC_USE_CUSOLVERMP) cusolverMpHandle* get_cusolvermp() { const auto proc = legate::Processor::get_executing_processor(); @@ -490,7 +490,7 @@ class LoadCUDALibsTask : public CuNumericTask { auto& lib = get_cuda_libraries(proc); lib.get_cublas(); lib.get_cusolver(); -#if LegateDefined(CUNUMERIC_USE_CUSOLVERMP) +#if LEGATE_DEFINED(CUNUMERIC_USE_CUSOLVERMP) lib.get_cusolvermp(); #endif lib.get_cutensor(); diff --git a/src/cunumeric/cudalibs.h b/src/cunumeric/cudalibs.h index c3e7b4facc..cb6d04f503 100644 --- a/src/cunumeric/cudalibs.h +++ b/src/cunumeric/cudalibs.h @@ -38,7 +38,7 @@ struct CUDALibraries { const cudaDeviceProp& get_device_properties(); cublasHandle_t get_cublas(); cusolverDnHandle_t get_cusolver(); -#if LegateDefined(CUNUMERIC_USE_CUSOLVERMP) +#if LEGATE_DEFINED(CUNUMERIC_USE_CUSOLVERMP) cusolverMpHandle_t get_cusolvermp(); #endif cutensorHandle_t* get_cutensor(); @@ -47,7 +47,7 @@ struct CUDALibraries { private: void finalize_cublas(); void finalize_cusolver(); -#if LegateDefined(CUNUMERIC_USE_CUSOLVERMP) +#if LEGATE_DEFINED(CUNUMERIC_USE_CUSOLVERMP) void finalize_cusolvermp(); #endif void finalize_cutensor(); @@ -58,7 +58,7 @@ struct CUDALibraries { std::unique_ptr device_prop_{}; cublasContext* cublas_; cusolverDnContext* cusolver_; -#if LegateDefined(CUNUMERIC_USE_CUSOLVERMP) +#if LEGATE_DEFINED(CUNUMERIC_USE_CUSOLVERMP) cusolverMpHandle* cusolvermp_; #endif cutensorHandle_t* cutensor_; diff --git a/src/cunumeric/cunumeric.cc b/src/cunumeric/cunumeric.cc index e9a407812f..23a3502cd4 100644 --- a/src/cunumeric/cunumeric.cc +++ b/src/cunumeric/cunumeric.cc @@ -72,7 +72,7 @@ void cunumeric_perform_registration(void) { cunumeric::registration_callback(); bool cunumeric_has_curand() { -#if LegateDefined(LEGATE_USE_CUDA) || defined(CUNUMERIC_CURAND_FOR_CPU_BUILD) +#if LEGATE_DEFINED(LEGATE_USE_CUDA) || LEGATE_DEFINED(CUNUMERIC_CURAND_FOR_CPU_BUILD) return true; #else return false; @@ -81,7 +81,7 @@ bool cunumeric_has_curand() bool cunumeric_has_cusolvermp() { -#if LegateDefined(LEGATE_USE_CUDA) && LegateDefined(CUNUMERIC_USE_CUSOLVERMP) +#if LEGATE_DEFINED(LEGATE_USE_CUDA) && LEGATE_DEFINED(CUNUMERIC_USE_CUSOLVERMP) return true; #else return false; diff --git a/src/cunumeric/device_scalar_reduction_buffer.h b/src/cunumeric/device_scalar_reduction_buffer.h index 99f6ec49b4..93336d0d9f 100644 --- a/src/cunumeric/device_scalar_reduction_buffer.h +++ b/src/cunumeric/device_scalar_reduction_buffer.h @@ -16,7 +16,7 @@ #pragma once -#include "core/cuda/cuda.h" +#include "cunumeric/cuda_help.h" #include "core/data/buffer.h" namespace cunumeric { @@ -32,7 +32,8 @@ class DeviceScalarReductionBuffer { { VAL identity{REDOP::identity}; ptr_ = buffer_.ptr(0); - LegateCheckCUDA(cudaMemcpyAsync(ptr_, &identity, sizeof(VAL), cudaMemcpyHostToDevice, stream)); + CUNUMERIC_CHECK_CUDA( + cudaMemcpyAsync(ptr_, &identity, sizeof(VAL), cudaMemcpyHostToDevice, stream)); } template @@ -44,8 +45,9 @@ class DeviceScalarReductionBuffer { __host__ VAL read(cudaStream_t stream) const { VAL result{REDOP::identity}; - LegateCheckCUDA(cudaMemcpyAsync(&result, ptr_, sizeof(VAL), cudaMemcpyDeviceToHost, stream)); - LegateCheckCUDA(cudaStreamSynchronize(stream)); + CUNUMERIC_CHECK_CUDA( + cudaMemcpyAsync(&result, ptr_, sizeof(VAL), cudaMemcpyDeviceToHost, stream)); + CUNUMERIC_CHECK_CUDA(cudaStreamSynchronize(stream)); return result; } diff --git a/src/cunumeric/execution_policy/indexing/parallel_loop.cuh b/src/cunumeric/execution_policy/indexing/parallel_loop.cuh index a67b5e1212..ac1bf95ad6 100644 --- a/src/cunumeric/execution_policy/indexing/parallel_loop.cuh +++ b/src/cunumeric/execution_policy/indexing/parallel_loop.cuh @@ -48,7 +48,7 @@ struct ParallelLoopPolicy { parallel_loop_kernel<<>>( volume, std::forward(kernel), Tag{}); - LegateCheckCUDAStream(stream); + CUNUMERIC_CHECK_CUDA_STREAM(stream); } }; diff --git a/src/cunumeric/execution_policy/reduction/scalar_reduction.cuh b/src/cunumeric/execution_policy/reduction/scalar_reduction.cuh index 9357942c2b..66e7f8e8a6 100644 --- a/src/cunumeric/execution_policy/reduction/scalar_reduction.cuh +++ b/src/cunumeric/execution_policy/reduction/scalar_reduction.cuh @@ -75,7 +75,7 @@ struct ScalarReductionPolicy { volume, 1, result, std::forward(kernel), identity, Tag{}); } scalar_reduction_impl::copy_kernel<<<1, 1, 0, stream>>>(result, out); - LegateCheckCUDAStream(stream); + CUNUMERIC_CHECK_CUDA_STREAM(stream); } }; diff --git a/src/cunumeric/fft/fft.cu b/src/cunumeric/fft/fft.cu index 8486c403ff..601832e8ca 100644 --- a/src/cunumeric/fft/fft.cu +++ b/src/cunumeric/fft/fft.cu @@ -46,7 +46,7 @@ __host__ static inline void copy_into_buffer(TYPE* target, cudaStream_t stream) { if (acc.accessor.is_dense_row_major(rect)) { - LegateCheckCUDA(cudaMemcpyAsync( + CUNUMERIC_CHECK_CUDA(cudaMemcpyAsync( target, acc.ptr(rect.lo), volume * sizeof(TYPE), cudaMemcpyDeviceToDevice, stream)); } else { Pitches pitches{}; @@ -56,7 +56,7 @@ __host__ static inline void copy_into_buffer(TYPE* target, copy_kernel<<>>( volume, target, acc, pitches, rect.lo); - LegateCheckCUDAStream(stream); + CUNUMERIC_CHECK_CUDA_STREAM(stream); } } @@ -115,7 +115,7 @@ __host__ static inline void cufft_operation(AccessorWO out, static_cast(out.ptr(out_rect.lo)), static_cast(direction))); // synchronize before cufft_context runs out of scope - LegateCheckCUDA(cudaStreamSynchronize(stream)); + CUNUMERIC_CHECK_CUDA(cudaStreamSynchronize(stream)); } // Perform the FFT operation as multiple 1D FFTs along the specified axes (Complex-to-complex case). @@ -144,7 +144,7 @@ __host__ static inline void cufft_over_axes_c2c(INOUT_TYPE* out, // Copy input to output buffer (if needed) // the computation will be done inplace of the target if (in != out) { - LegateCheckCUDA(cudaMemcpyAsync( + CUNUMERIC_CHECK_CUDA(cudaMemcpyAsync( out, in, num_elements * sizeof(INOUT_TYPE), cudaMemcpyDeviceToDevice, stream)); } @@ -193,7 +193,7 @@ __host__ static inline void cufft_over_axes_c2c(INOUT_TYPE* out, static_cast(direction))); } // synchronize before cufft_context runs out of scope - LegateCheckCUDA(cudaStreamSynchronize(stream)); + CUNUMERIC_CHECK_CUDA(cudaStreamSynchronize(stream)); } } @@ -269,7 +269,7 @@ __host__ static inline void cufft_r2c_c2r(OUTPUT_TYPE* out, static_cast(direction))); } // synchronize before cufft_context runs out of scope - LegateCheckCUDA(cudaStreamSynchronize(stream)); + CUNUMERIC_CHECK_CUDA(cudaStreamSynchronize(stream)); } // Perform the FFT operation as multiple 1D FFTs along the specified axes. diff --git a/src/cunumeric/fft/fft.h b/src/cunumeric/fft/fft.h index 0310a48518..3e3b12a7ea 100644 --- a/src/cunumeric/fft/fft.h +++ b/src/cunumeric/fft/fft.h @@ -35,7 +35,7 @@ class FFTTask : public CuNumericTask { static const int TASK_ID = CUNUMERIC_FFT; public: -#if LegateDefined(LEGATE_USE_CUDA) +#if LEGATE_DEFINED(LEGATE_USE_CUDA) static void gpu_variant(legate::TaskContext context); #endif }; diff --git a/src/cunumeric/index/advanced_indexing.cu b/src/cunumeric/index/advanced_indexing.cu index 56cf004d9b..5f26c4bbb4 100644 --- a/src/cunumeric/index/advanced_indexing.cu +++ b/src/cunumeric/index/advanced_indexing.cu @@ -109,7 +109,7 @@ struct AdvancedIndexingImplBody { volume, size, offsets, in, pitches, rect.lo, 1, skip_size, key_dim); } - LegateCheckCUDAStream(stream); + CUNUMERIC_CHECK_CUDA_STREAM(stream); auto off_ptr = offsets.ptr(0); thrust::exclusive_scan(DEFAULT_POLICY.on(stream), off_ptr, off_ptr + volume, off_ptr); @@ -158,7 +158,7 @@ struct AdvancedIndexingImplBody { advanced_indexing_kernel<<>>( volume, input, index, out, pitches, rect.lo, offsets, skip_size, key_dim); } - LegateCheckCUDAStream(stream); + CUNUMERIC_CHECK_CUDA_STREAM(stream); } }; diff --git a/src/cunumeric/index/advanced_indexing.h b/src/cunumeric/index/advanced_indexing.h index c5d7997fc2..be50c3be94 100644 --- a/src/cunumeric/index/advanced_indexing.h +++ b/src/cunumeric/index/advanced_indexing.h @@ -34,10 +34,10 @@ class AdvancedIndexingTask : public CuNumericTask { public: static void cpu_variant(legate::TaskContext context); -#if LegateDefined(LEGATE_USE_OPENMP) +#if LEGATE_DEFINED(LEGATE_USE_OPENMP) static void omp_variant(legate::TaskContext context); #endif -#if LegateDefined(LEGATE_USE_CUDA) +#if LEGATE_DEFINED(LEGATE_USE_CUDA) static void gpu_variant(legate::TaskContext context); #endif }; diff --git a/src/cunumeric/index/choose.cu b/src/cunumeric/index/choose.cu index acfb24322d..23923f86b2 100644 --- a/src/cunumeric/index/choose.cu +++ b/src/cunumeric/index/choose.cu @@ -82,7 +82,7 @@ struct ChooseImplBody { choose_kernel <<>>(out, index_arr, ch_arr, rect, pitches, volume); } - LegateCheckCUDAStream(stream); + CUNUMERIC_CHECK_CUDA_STREAM(stream); } }; diff --git a/src/cunumeric/index/choose.h b/src/cunumeric/index/choose.h index 690fe0012f..4cae5417d2 100644 --- a/src/cunumeric/index/choose.h +++ b/src/cunumeric/index/choose.h @@ -31,10 +31,10 @@ class ChooseTask : public CuNumericTask { public: static void cpu_variant(legate::TaskContext context); -#if LegateDefined(LEGATE_USE_OPENMP) +#if LEGATE_DEFINED(LEGATE_USE_OPENMP) static void omp_variant(legate::TaskContext context); #endif -#if LegateDefined(LEGATE_USE_CUDA) +#if LEGATE_DEFINED(LEGATE_USE_CUDA) static void gpu_variant(legate::TaskContext context); #endif }; diff --git a/src/cunumeric/index/choose_template.inl b/src/cunumeric/index/choose_template.inl index 22905fa28b..837f3e36e9 100644 --- a/src/cunumeric/index/choose_template.inl +++ b/src/cunumeric/index/choose_template.inl @@ -45,7 +45,7 @@ struct ChooseImpl { auto index_rect = args.inputs[0].shape(); auto index_arr = args.inputs[0].read_accessor(index_rect); -#if !LegateDefined(LEGATE_BOUNDS_CHECKS) +#if !LEGATE_DEFINED(LEGATE_BOUNDS_CHECKS) // Check to see if this is dense or not bool dense = index_arr.accessor.is_dense_row_major(out_rect) && out.accessor.is_dense_row_major(out_rect); diff --git a/src/cunumeric/index/putmask.h b/src/cunumeric/index/putmask.h index d8900c4ddf..6a75336a7f 100644 --- a/src/cunumeric/index/putmask.h +++ b/src/cunumeric/index/putmask.h @@ -32,10 +32,10 @@ class PutmaskTask : public CuNumericTask { public: static void cpu_variant(legate::TaskContext context); -#if LegateDefined(LEGATE_USE_OPENMP) +#if LEGATE_DEFINED(LEGATE_USE_OPENMP) static void omp_variant(legate::TaskContext context); #endif -#if LegateDefined(LEGATE_USE_CUDA) +#if LEGATE_DEFINED(LEGATE_USE_CUDA) static void gpu_variant(legate::TaskContext context); #endif }; diff --git a/src/cunumeric/index/putmask_template.inl b/src/cunumeric/index/putmask_template.inl index e6b4bb0fd5..a38e982988 100644 --- a/src/cunumeric/index/putmask_template.inl +++ b/src/cunumeric/index/putmask_template.inl @@ -59,7 +59,7 @@ struct Putmask { if (volume == 0) { return; } -#if !LegateDefined(LEGATE_BOUNDS_CHECKS) +#if !LEGATE_DEFINED(LEGATE_BOUNDS_CHECKS) dense = input.accessor.is_dense_row_major(rect) && mask.accessor.is_dense_row_major(rect); dense = dense && values.accessor.is_dense_row_major(rect); if (dense) { @@ -87,7 +87,7 @@ struct Putmask { void execute() const noexcept { -#if !LegateDefined(LEGATE_BOUNDS_CHECKS) +#if !LEGATE_DEFINED(LEGATE_BOUNDS_CHECKS) if (dense) { return ParallelLoopPolicy()(rect, *this); } diff --git a/src/cunumeric/index/repeat.cu b/src/cunumeric/index/repeat.cu index 400cfcfb42..4b53ed7b5b 100644 --- a/src/cunumeric/index/repeat.cu +++ b/src/cunumeric/index/repeat.cu @@ -116,7 +116,7 @@ struct RepeatImplBody { auto stream = get_cached_stream(); repeat_kernel<<>>( out, in, repeats, axis, out_rect.lo, pitches, out_volume); - LegateCheckCUDAStream(stream); + CUNUMERIC_CHECK_CUDA_STREAM(stream); } void operator()(legate::PhysicalStore& out_array, @@ -146,7 +146,7 @@ struct RepeatImplBody { count_repeat_kernel<<>>( extent, sum, repeats, in_rect.lo, axis, 1, offsets); } - LegateCheckCUDAStream(stream); + CUNUMERIC_CHECK_CUDA_STREAM(stream); Point out_extents = in_rect.hi - in_rect.lo + Point::ONES(); out_extents[axis] = static_cast(sum.read(stream)); @@ -159,7 +159,7 @@ struct RepeatImplBody { const size_t blocks = (volume + THREADS_PER_BLOCK - 1) / THREADS_PER_BLOCK; repeat_kernel<<>>( out, in, repeats, offsets, axis, in_rect.lo, pitches, volume); - LegateCheckCUDAStream(stream); + CUNUMERIC_CHECK_CUDA_STREAM(stream); } }; diff --git a/src/cunumeric/index/repeat.h b/src/cunumeric/index/repeat.h index 74ffa75046..3e58a3761b 100644 --- a/src/cunumeric/index/repeat.h +++ b/src/cunumeric/index/repeat.h @@ -35,10 +35,10 @@ class RepeatTask : public CuNumericTask { public: static void cpu_variant(legate::TaskContext context); -#if LegateDefined(LEGATE_USE_OPENMP) +#if LEGATE_DEFINED(LEGATE_USE_OPENMP) static void omp_variant(legate::TaskContext context); #endif -#if LegateDefined(LEGATE_USE_CUDA) +#if LEGATE_DEFINED(LEGATE_USE_CUDA) static void gpu_variant(legate::TaskContext context); #endif }; diff --git a/src/cunumeric/index/select.cu b/src/cunumeric/index/select.cu index 171a7f05ae..9315ad347c 100644 --- a/src/cunumeric/index/select.cu +++ b/src/cunumeric/index/select.cu @@ -118,7 +118,7 @@ struct SelectImplBody { out, narrays, cond_arr, choice_arr, default_val, rect, pitches, rect.volume()); } - LegateCheckCUDAStream(stream); + CUNUMERIC_CHECK_CUDA_STREAM(stream); } }; diff --git a/src/cunumeric/index/select.h b/src/cunumeric/index/select.h index 00f49a587e..0e7cb31007 100644 --- a/src/cunumeric/index/select.h +++ b/src/cunumeric/index/select.h @@ -32,10 +32,10 @@ class SelectTask : public CuNumericTask { public: static void cpu_variant(legate::TaskContext context); -#if LegateDefined(LEGATE_USE_OPENMP) +#if LEGATE_DEFINED(LEGATE_USE_OPENMP) static void omp_variant(legate::TaskContext context); #endif -#if LegateDefined(LEGATE_USE_CUDA) +#if LEGATE_DEFINED(LEGATE_USE_CUDA) static void gpu_variant(legate::TaskContext context); #endif }; diff --git a/src/cunumeric/index/select_template.inl b/src/cunumeric/index/select_template.inl index a5fd650840..1a0baf693f 100644 --- a/src/cunumeric/index/select_template.inl +++ b/src/cunumeric/index/select_template.inl @@ -43,7 +43,7 @@ struct SelectImpl { auto out = args.out.data().write_accessor(out_rect); -#if !LegateDefined(LEGATE_BOUNDS_CHECKS) +#if !LEGATE_DEFINED(LEGATE_BOUNDS_CHECKS) // Check to see if this is dense or not bool dense = out.accessor.is_dense_row_major(out_rect); #else diff --git a/src/cunumeric/index/wrap.cu b/src/cunumeric/index/wrap.cu index df65ad7b10..722cf5bcba 100644 --- a/src/cunumeric/index/wrap.cu +++ b/src/cunumeric/index/wrap.cu @@ -113,7 +113,7 @@ void check_out_of_bounds(const AccessorRO& indices, check_kernel<<>>( out_of_bounds, indices, start, volume, volume_base, 1); } - LegateCheckCUDAStream(stream); + CUNUMERIC_CHECK_CUDA_STREAM(stream); bool res = out_of_bounds.read(stream); if (res) { @@ -158,7 +158,7 @@ struct WrapImplBody { volume_base, indices); } - LegateCheckCUDAStream(stream); + CUNUMERIC_CHECK_CUDA_STREAM(stream); } }; diff --git a/src/cunumeric/index/wrap.h b/src/cunumeric/index/wrap.h index cc4034d57a..5fdfdf14d8 100644 --- a/src/cunumeric/index/wrap.h +++ b/src/cunumeric/index/wrap.h @@ -36,10 +36,10 @@ class WrapTask : public CuNumericTask { public: static void cpu_variant(legate::TaskContext context); -#if LegateDefined(LEGATE_USE_OPENMP) +#if LEGATE_DEFINED(LEGATE_USE_OPENMP) static void omp_variant(legate::TaskContext context); #endif -#if LegateDefined(LEGATE_USE_CUDA) +#if LEGATE_DEFINED(LEGATE_USE_CUDA) static void gpu_variant(legate::TaskContext context); #endif }; diff --git a/src/cunumeric/index/wrap_template.inl b/src/cunumeric/index/wrap_template.inl index 075ba1ed53..5e815a1eb8 100644 --- a/src/cunumeric/index/wrap_template.inl +++ b/src/cunumeric/index/wrap_template.inl @@ -41,7 +41,7 @@ struct WrapImpl { return; } -#if !LegateDefined(LEGATE_BOUNDS_CHECKS) +#if !LEGATE_DEFINED(LEGATE_BOUNDS_CHECKS) bool dense = out.accessor.is_dense_row_major(rect_out); #else bool dense = false; diff --git a/src/cunumeric/index/zip.cu b/src/cunumeric/index/zip.cu index ab4a6bccc6..eee9a89bfd 100644 --- a/src/cunumeric/index/zip.cu +++ b/src/cunumeric/index/zip.cu @@ -146,7 +146,7 @@ struct ZipImplBody { check_kernel<<>>( out_of_bounds, index_arrays, volume, 1, rect, pitches, narrays, start_index, shape); } - LegateCheckCUDAStream(stream); + CUNUMERIC_CHECK_CUDA_STREAM(stream); bool res = out_of_bounds.read(stream); if (res) { @@ -198,7 +198,7 @@ struct ZipImplBody { zip_kernel<<>>( out, index_buf, rect, pitches, num_arrays, volume, key_dim, start_index, shape); } - LegateCheckCUDAStream(stream); + CUNUMERIC_CHECK_CUDA_STREAM(stream); } }; diff --git a/src/cunumeric/index/zip.h b/src/cunumeric/index/zip.h index fdd27bd61d..2ca23b7c7f 100644 --- a/src/cunumeric/index/zip.h +++ b/src/cunumeric/index/zip.h @@ -35,10 +35,10 @@ class ZipTask : public CuNumericTask { public: static void cpu_variant(legate::TaskContext context); -#if LegateDefined(LEGATE_USE_OPENMP) +#if LEGATE_DEFINED(LEGATE_USE_OPENMP) static void omp_variant(legate::TaskContext context); #endif -#if LegateDefined(LEGATE_USE_CUDA) +#if LEGATE_DEFINED(LEGATE_USE_CUDA) static void gpu_variant(legate::TaskContext context); #endif }; diff --git a/src/cunumeric/index/zip_template.inl b/src/cunumeric/index/zip_template.inl index 449fdee0da..212b472700 100644 --- a/src/cunumeric/index/zip_template.inl +++ b/src/cunumeric/index/zip_template.inl @@ -41,7 +41,7 @@ struct ZipImpl { return; } -#if !LegateDefined(LEGATE_BOUNDS_CHECKS) +#if !LEGATE_DEFINED(LEGATE_BOUNDS_CHECKS) bool dense = out.accessor.is_dense_row_major(out_rect); #else bool dense = false; diff --git a/src/cunumeric/item/read.cu b/src/cunumeric/item/read.cu index 3c04fd8ac8..37c1086d32 100644 --- a/src/cunumeric/item/read.cu +++ b/src/cunumeric/item/read.cu @@ -33,7 +33,7 @@ struct ReadImplBody { { auto stream = get_cached_stream(); read_value<<<1, 1, 0, stream>>>(out, in); - LegateCheckCUDAStream(stream); + CUNUMERIC_CHECK_CUDA_STREAM(stream); } }; diff --git a/src/cunumeric/item/read.h b/src/cunumeric/item/read.h index d7d03958f7..2281d81886 100644 --- a/src/cunumeric/item/read.h +++ b/src/cunumeric/item/read.h @@ -26,10 +26,10 @@ class ReadTask : public CuNumericTask { public: static void cpu_variant(legate::TaskContext context); -#if LegateDefined(LEGATE_USE_OPENMP) +#if LEGATE_DEFINED(LEGATE_USE_OPENMP) static void omp_variant(legate::TaskContext context) { ReadTask::cpu_variant(context); } #endif -#if LegateDefined(LEGATE_USE_CUDA) +#if LEGATE_DEFINED(LEGATE_USE_CUDA) static void gpu_variant(legate::TaskContext context); #endif }; diff --git a/src/cunumeric/item/write.cu b/src/cunumeric/item/write.cu index e10b07f5ca..7cb1ffe9db 100644 --- a/src/cunumeric/item/write.cu +++ b/src/cunumeric/item/write.cu @@ -33,7 +33,7 @@ struct WriteImplBody { { auto stream = get_cached_stream(); write_value<<<1, 1, 0, stream>>>(out, value); - LegateCheckCUDAStream(stream); + CUNUMERIC_CHECK_CUDA_STREAM(stream); } }; diff --git a/src/cunumeric/item/write.h b/src/cunumeric/item/write.h index 25c10d9641..d78ec89d08 100644 --- a/src/cunumeric/item/write.h +++ b/src/cunumeric/item/write.h @@ -26,10 +26,10 @@ class WriteTask : public CuNumericTask { public: static void cpu_variant(legate::TaskContext context); -#if LegateDefined(LEGATE_USE_OPENMP) +#if LEGATE_DEFINED(LEGATE_USE_OPENMP) static void omp_variant(legate::TaskContext context) { WriteTask::cpu_variant(context); } #endif -#if LegateDefined(LEGATE_USE_CUDA) +#if LEGATE_DEFINED(LEGATE_USE_CUDA) static void gpu_variant(legate::TaskContext context); #endif }; diff --git a/src/cunumeric/matrix/batched_cholesky.cc b/src/cunumeric/matrix/batched_cholesky.cc index 1eaca48d44..ded805e668 100644 --- a/src/cunumeric/matrix/batched_cholesky.cc +++ b/src/cunumeric/matrix/batched_cholesky.cc @@ -70,7 +70,7 @@ struct BatchedTransposeImplBody { /*static*/ void BatchedCholeskyTask::cpu_variant(TaskContext context) { -#if LegateDefined(LEGATE_USE_OPENMP) +#if LEGATE_DEFINED(LEGATE_USE_OPENMP) openblas_set_num_threads(1); // make sure this isn't overzealous #endif batched_cholesky_task_context_dispatch(context); diff --git a/src/cunumeric/matrix/batched_cholesky.cu b/src/cunumeric/matrix/batched_cholesky.cu index 3a04ee1829..14387c419a 100644 --- a/src/cunumeric/matrix/batched_cholesky.cu +++ b/src/cunumeric/matrix/batched_cholesky.cu @@ -101,7 +101,7 @@ struct BatchedTransposeImplBody { // the lower diagonal transpose_2d_lower<<>>(out, n); - LegateCheckCUDAStream(stream); + CUNUMERIC_CHECK_CUDA_STREAM(stream); } }; diff --git a/src/cunumeric/matrix/batched_cholesky.h b/src/cunumeric/matrix/batched_cholesky.h index fb52876118..769446e5e2 100644 --- a/src/cunumeric/matrix/batched_cholesky.h +++ b/src/cunumeric/matrix/batched_cholesky.h @@ -27,10 +27,10 @@ class BatchedCholeskyTask : public CuNumericTask { public: static void cpu_variant(legate::TaskContext context); -#if LegateDefined(LEGATE_USE_OPENMP) +#if LEGATE_DEFINED(LEGATE_USE_OPENMP) static void omp_variant(legate::TaskContext context); #endif -#if LegateDefined(LEGATE_USE_CUDA) +#if LEGATE_DEFINED(LEGATE_USE_CUDA) static void gpu_variant(legate::TaskContext context); #endif }; diff --git a/src/cunumeric/matrix/contract.cu b/src/cunumeric/matrix/contract.cu index a9758f0913..26f6413f4d 100644 --- a/src/cunumeric/matrix/contract.cu +++ b/src/cunumeric/matrix/contract.cu @@ -148,7 +148,7 @@ __host__ void contract(T* lhs_data, work_size, task_stream)); - LegateCheckCUDAStream(task_stream); + CUNUMERIC_CHECK_CUDA_STREAM(task_stream); } template <> diff --git a/src/cunumeric/matrix/contract.h b/src/cunumeric/matrix/contract.h index 3b510fa92b..530175aa2b 100644 --- a/src/cunumeric/matrix/contract.h +++ b/src/cunumeric/matrix/contract.h @@ -35,10 +35,10 @@ class ContractTask : public CuNumericTask { public: static void cpu_variant(legate::TaskContext context); -#if LegateDefined(LEGATE_USE_OPENMP) +#if LEGATE_DEFINED(LEGATE_USE_OPENMP) static void omp_variant(legate::TaskContext context); #endif -#if LegateDefined(LEGATE_USE_CUDA) +#if LEGATE_DEFINED(LEGATE_USE_CUDA) static void gpu_variant(legate::TaskContext context); #endif }; diff --git a/src/cunumeric/matrix/diag.cu b/src/cunumeric/matrix/diag.cu index 174b208082..ac6a1a128d 100644 --- a/src/cunumeric/matrix/diag.cu +++ b/src/cunumeric/matrix/diag.cu @@ -93,7 +93,7 @@ struct DiagImplBody { auto stream = get_cached_stream(); diag_extract<<>>( out, in, distance, volume, skip_size, start, naxes, m_pitches, m_shape); - LegateCheckCUDAStream(stream); + CUNUMERIC_CHECK_CUDA_STREAM(stream); } }; @@ -110,7 +110,7 @@ struct DiagImplBody { const size_t blocks = (distance + THREADS_PER_BLOCK - 1) / THREADS_PER_BLOCK; auto stream = get_cached_stream(); diag_populate<<>>(out, in, distance, start); - LegateCheckCUDAStream(stream); + CUNUMERIC_CHECK_CUDA_STREAM(stream); } }; diff --git a/src/cunumeric/matrix/diag.h b/src/cunumeric/matrix/diag.h index 1f8d3a5826..553ddc8566 100644 --- a/src/cunumeric/matrix/diag.h +++ b/src/cunumeric/matrix/diag.h @@ -33,10 +33,10 @@ class DiagTask : public CuNumericTask { public: static void cpu_variant(legate::TaskContext context); -#if LegateDefined(LEGATE_USE_OPENMP) +#if LEGATE_DEFINED(LEGATE_USE_OPENMP) static void omp_variant(legate::TaskContext context); #endif -#if LegateDefined(LEGATE_USE_CUDA) +#if LEGATE_DEFINED(LEGATE_USE_CUDA) static void gpu_variant(legate::TaskContext context); #endif }; diff --git a/src/cunumeric/matrix/dot.cu b/src/cunumeric/matrix/dot.cu index a8b76ed90b..489f478a16 100644 --- a/src/cunumeric/matrix/dot.cu +++ b/src/cunumeric/matrix/dot.cu @@ -72,7 +72,7 @@ struct DotImplBody { } copy_kernel<<<1, 1, 0, stream>>>(result, out); - LegateCheckCUDAStream(stream); + CUNUMERIC_CHECK_CUDA_STREAM(stream); } }; diff --git a/src/cunumeric/matrix/dot.h b/src/cunumeric/matrix/dot.h index 7839a06e20..48a3135fc4 100644 --- a/src/cunumeric/matrix/dot.h +++ b/src/cunumeric/matrix/dot.h @@ -32,10 +32,10 @@ class DotTask : public CuNumericTask { public: static void cpu_variant(legate::TaskContext context); -#if LegateDefined(LEGATE_USE_OPENMP) +#if LEGATE_DEFINED(LEGATE_USE_OPENMP) static void omp_variant(legate::TaskContext context); #endif -#if LegateDefined(LEGATE_USE_CUDA) +#if LEGATE_DEFINED(LEGATE_USE_CUDA) static void gpu_variant(legate::TaskContext context); #endif }; diff --git a/src/cunumeric/matrix/dot_template.inl b/src/cunumeric/matrix/dot_template.inl index 5095663205..56bbed6581 100644 --- a/src/cunumeric/matrix/dot_template.inl +++ b/src/cunumeric/matrix/dot_template.inl @@ -59,7 +59,7 @@ struct DotImpl { return; } -#if !LegateDefined(LEGATE_BOUNDS_CHECKS) +#if !LEGATE_DEFINED(LEGATE_BOUNDS_CHECKS) // Check to see if this is dense or not bool dense = rhs1.accessor.is_dense_row_major(rect) && rhs2.accessor.is_dense_row_major(rect); #else diff --git a/src/cunumeric/matrix/gemm.cc b/src/cunumeric/matrix/gemm.cc index a15b323030..88e7516acb 100644 --- a/src/cunumeric/matrix/gemm.cc +++ b/src/cunumeric/matrix/gemm.cc @@ -99,7 +99,7 @@ struct GemmImplBody { /*static*/ void GemmTask::cpu_variant(TaskContext context) { -#if LegateDefined(LEGATE_USE_OPENMP) +#if LEGATE_DEFINED(LEGATE_USE_OPENMP) openblas_set_num_threads(1); // make sure this isn't overzealous #endif gemm_template(context); diff --git a/src/cunumeric/matrix/gemm.cu b/src/cunumeric/matrix/gemm.cu index 34bb815468..97290a4dc9 100644 --- a/src/cunumeric/matrix/gemm.cu +++ b/src/cunumeric/matrix/gemm.cu @@ -39,7 +39,7 @@ static inline void gemm_template( CHECK_CUBLAS(gemm(context, transa, transb, m, n, k, &alpha, rhs1, m, rhs2, n, &beta, lhs, m)); - LegateCheckCUDAStream(stream); + CUNUMERIC_CHECK_CUDA_STREAM(stream); } template @@ -58,7 +58,7 @@ static inline void complex_gemm_template( CHECK_CUBLAS(gemm(context, transa, transb, m, n, k, &alpha, rhs1, m, rhs2, n, &beta, lhs, m)); - LegateCheckCUDAStream(stream); + CUNUMERIC_CHECK_CUDA_STREAM(stream); } template <> diff --git a/src/cunumeric/matrix/gemm.h b/src/cunumeric/matrix/gemm.h index a84f3e1d5d..61ef90403a 100644 --- a/src/cunumeric/matrix/gemm.h +++ b/src/cunumeric/matrix/gemm.h @@ -26,10 +26,10 @@ class GemmTask : public CuNumericTask { public: static void cpu_variant(legate::TaskContext context); -#if LegateDefined(LEGATE_USE_OPENMP) +#if LEGATE_DEFINED(LEGATE_USE_OPENMP) static void omp_variant(legate::TaskContext context); #endif -#if LegateDefined(LEGATE_USE_CUDA) +#if LEGATE_DEFINED(LEGATE_USE_CUDA) static void gpu_variant(legate::TaskContext context); #endif }; diff --git a/src/cunumeric/matrix/matmul.cc b/src/cunumeric/matrix/matmul.cc index f3cfb1bf18..fae887033d 100644 --- a/src/cunumeric/matrix/matmul.cc +++ b/src/cunumeric/matrix/matmul.cc @@ -19,7 +19,7 @@ #include "cunumeric/matrix/matmul_cpu.inl" #include -#if LegateDefined(LEGATE_USE_OPENMP) +#if LEGATE_DEFINED(LEGATE_USE_OPENMP) #include #endif @@ -29,7 +29,7 @@ using namespace legate; /*static*/ void MatMulTask::cpu_variant(TaskContext context) { -#if LegateDefined(LEGATE_USE_OPENMP) +#if LEGATE_DEFINED(LEGATE_USE_OPENMP) openblas_set_num_threads(1); // make sure this isn't overzealous #endif matmul_template(context); diff --git a/src/cunumeric/matrix/matmul.cu b/src/cunumeric/matrix/matmul.cu index a52d115ea1..cfd8794eb7 100644 --- a/src/cunumeric/matrix/matmul.cu +++ b/src/cunumeric/matrix/matmul.cu @@ -68,7 +68,7 @@ struct MatMulImplBody { CUDA_R_32F, lhs_stride)); - LegateCheckCUDAStream(task_stream); + CUNUMERIC_CHECK_CUDA_STREAM(task_stream); } }; @@ -109,7 +109,7 @@ struct MatMulImplBody { lhs, lhs_stride)); - LegateCheckCUDAStream(task_stream); + CUNUMERIC_CHECK_CUDA_STREAM(task_stream); } }; @@ -153,7 +153,7 @@ struct MatMulImplBody { CUDA_R_32F, lhs_stride)); - LegateCheckCUDAStream(task_stream); + CUNUMERIC_CHECK_CUDA_STREAM(task_stream); } }; @@ -201,7 +201,7 @@ struct MatMulImplBody { CUDA_C_32F, lhs_stride)); - LegateCheckCUDAStream(task_stream); + CUNUMERIC_CHECK_CUDA_STREAM(task_stream); } }; @@ -246,7 +246,7 @@ struct MatMulImplBody { lhs, lhs_stride)); - LegateCheckCUDAStream(task_stream); + CUNUMERIC_CHECK_CUDA_STREAM(task_stream); } }; diff --git a/src/cunumeric/matrix/matmul.h b/src/cunumeric/matrix/matmul.h index f47cb6243d..1739766942 100644 --- a/src/cunumeric/matrix/matmul.h +++ b/src/cunumeric/matrix/matmul.h @@ -32,10 +32,10 @@ class MatMulTask : public CuNumericTask { public: static void cpu_variant(legate::TaskContext context); -#if LegateDefined(LEGATE_USE_OPENMP) +#if LEGATE_DEFINED(LEGATE_USE_OPENMP) static void omp_variant(legate::TaskContext context); #endif -#if LegateDefined(LEGATE_USE_CUDA) +#if LEGATE_DEFINED(LEGATE_USE_CUDA) static void gpu_variant(legate::TaskContext context); #endif }; diff --git a/src/cunumeric/matrix/matvecmul.cc b/src/cunumeric/matrix/matvecmul.cc index 3df1d2e398..5aba0407ac 100644 --- a/src/cunumeric/matrix/matvecmul.cc +++ b/src/cunumeric/matrix/matvecmul.cc @@ -19,7 +19,7 @@ #include "cunumeric/matrix/matvecmul_cpu.inl" #include -#if LegateDefined(LEGATE_USE_OPENMP) +#if LEGATE_DEFINED(LEGATE_USE_OPENMP) #include #endif @@ -29,7 +29,7 @@ using namespace legate; /*static*/ void MatVecMulTask::cpu_variant(TaskContext context) { -#if LegateDefined(LEGATE_USE_OPENMP) +#if LEGATE_DEFINED(LEGATE_USE_OPENMP) openblas_set_num_threads(1); // make sure this isn't overzealous #endif matvecmul_template(context); diff --git a/src/cunumeric/matrix/matvecmul.cu b/src/cunumeric/matrix/matvecmul.cu index 0945b3b0fa..3dd69047ba 100644 --- a/src/cunumeric/matrix/matvecmul.cu +++ b/src/cunumeric/matrix/matvecmul.cu @@ -71,7 +71,7 @@ struct MatVecMulImplBody { transpose_mat ? n : m)); } - LegateCheckCUDAStream(task_stream); + CUNUMERIC_CHECK_CUDA_STREAM(task_stream); } }; @@ -119,7 +119,7 @@ struct MatVecMulImplBody { transpose_mat ? n : m)); } - LegateCheckCUDAStream(task_stream); + CUNUMERIC_CHECK_CUDA_STREAM(task_stream); } }; @@ -161,7 +161,7 @@ struct MatVecMulImplBody { CUDA_R_32F, transpose_mat ? n : m)); - LegateCheckCUDAStream(task_stream); + CUNUMERIC_CHECK_CUDA_STREAM(task_stream); } }; @@ -216,7 +216,7 @@ struct MatVecMulImplBody { transpose_mat ? n : m)); } - LegateCheckCUDAStream(task_stream); + CUNUMERIC_CHECK_CUDA_STREAM(task_stream); } }; @@ -268,7 +268,7 @@ struct MatVecMulImplBody { transpose_mat ? n : m)); } - LegateCheckCUDAStream(task_stream); + CUNUMERIC_CHECK_CUDA_STREAM(task_stream); } }; diff --git a/src/cunumeric/matrix/matvecmul.h b/src/cunumeric/matrix/matvecmul.h index d816f2be3e..5485d5397b 100644 --- a/src/cunumeric/matrix/matvecmul.h +++ b/src/cunumeric/matrix/matvecmul.h @@ -32,10 +32,10 @@ class MatVecMulTask : public CuNumericTask { public: static void cpu_variant(legate::TaskContext context); -#if LegateDefined(LEGATE_USE_OPENMP) +#if LEGATE_DEFINED(LEGATE_USE_OPENMP) static void omp_variant(legate::TaskContext context); #endif -#if LegateDefined(LEGATE_USE_CUDA) +#if LEGATE_DEFINED(LEGATE_USE_CUDA) static void gpu_variant(legate::TaskContext context); #endif }; diff --git a/src/cunumeric/matrix/mp_potrf.cu b/src/cunumeric/matrix/mp_potrf.cu index a69ceb6c77..dc0fa28ab8 100644 --- a/src/cunumeric/matrix/mp_potrf.cu +++ b/src/cunumeric/matrix/mp_potrf.cu @@ -77,7 +77,7 @@ static inline void mp_potrf_template( // TODO: We need a deferred exception to avoid this synchronization CHECK_CAL(cal_stream_sync(comm, stream)); - LegateCheckCUDAStream(stream); + CUNUMERIC_CHECK_CUDA_STREAM(stream); CHECK_CUSOLVER(cusolverMpDestroyMatrixDesc(desc)); CHECK_CUSOLVER(cusolverMpDestroyGrid(grid)); diff --git a/src/cunumeric/matrix/mp_potrf.h b/src/cunumeric/matrix/mp_potrf.h index 11a625b679..1e93e1402e 100644 --- a/src/cunumeric/matrix/mp_potrf.h +++ b/src/cunumeric/matrix/mp_potrf.h @@ -25,7 +25,7 @@ class MpPotrfTask : public CuNumericTask { static const int TASK_ID = CUNUMERIC_MP_POTRF; public: -#if LegateDefined(LEGATE_USE_CUDA) +#if LEGATE_DEFINED(LEGATE_USE_CUDA) static void gpu_variant(legate::TaskContext context); #endif }; diff --git a/src/cunumeric/matrix/mp_solve.cu b/src/cunumeric/matrix/mp_solve.cu index 17b77f986f..342fb5f554 100644 --- a/src/cunumeric/matrix/mp_solve.cu +++ b/src/cunumeric/matrix/mp_solve.cu @@ -136,7 +136,7 @@ static inline void mp_solve_template(cal_comm_t comm, // TODO: We need a deferred exception to avoid this synchronization CHECK_CAL(cal_stream_sync(comm, stream)); - LegateCheckCUDAStream(stream); + CUNUMERIC_CHECK_CUDA_STREAM(stream); CHECK_CUSOLVER(cusolverMpDestroyMatrixDesc(a_desc)); CHECK_CUSOLVER(cusolverMpDestroyMatrixDesc(b_desc)); diff --git a/src/cunumeric/matrix/mp_solve.h b/src/cunumeric/matrix/mp_solve.h index 48cd9ecc6a..881a123b4c 100644 --- a/src/cunumeric/matrix/mp_solve.h +++ b/src/cunumeric/matrix/mp_solve.h @@ -25,7 +25,7 @@ class MpSolveTask : public CuNumericTask { static const int TASK_ID = CUNUMERIC_MP_SOLVE; public: -#if LegateDefined(LEGATE_USE_CUDA) +#if LEGATE_DEFINED(LEGATE_USE_CUDA) static void gpu_variant(legate::TaskContext context); #endif }; diff --git a/src/cunumeric/matrix/potrf.cc b/src/cunumeric/matrix/potrf.cc index 774d57cd37..409d32a53d 100644 --- a/src/cunumeric/matrix/potrf.cc +++ b/src/cunumeric/matrix/potrf.cc @@ -78,7 +78,7 @@ void PotrfImplBody::operator()(complex /*static*/ void PotrfTask::cpu_variant(TaskContext context) { -#if LegateDefined(LEGATE_USE_OPENMP) +#if LEGATE_DEFINED(LEGATE_USE_OPENMP) openblas_set_num_threads(1); // make sure this isn't overzealous #endif potrf_template(context); diff --git a/src/cunumeric/matrix/potrf.cu b/src/cunumeric/matrix/potrf.cu index 375a8f7b9a..6070f0ac2e 100644 --- a/src/cunumeric/matrix/potrf.cu +++ b/src/cunumeric/matrix/potrf.cu @@ -42,8 +42,8 @@ static inline void potrf_template( CHECK_CUSOLVER(potrf(context, uplo, n, array, m, buffer.ptr(0), bufferSize, info.ptr(0))); // TODO: We need a deferred exception to avoid this synchronization - LegateCheckCUDA(cudaStreamSynchronize(stream)); - LegateCheckCUDAStream(stream); + CUNUMERIC_CHECK_CUDA(cudaStreamSynchronize(stream)); + CUNUMERIC_CHECK_CUDA_STREAM(stream); if (info[0] != 0) { throw legate::TaskException("Matrix is not positive definite"); diff --git a/src/cunumeric/matrix/potrf.h b/src/cunumeric/matrix/potrf.h index 7cc81354af..cc9c50a18a 100644 --- a/src/cunumeric/matrix/potrf.h +++ b/src/cunumeric/matrix/potrf.h @@ -26,10 +26,10 @@ class PotrfTask : public CuNumericTask { public: static void cpu_variant(legate::TaskContext context); -#if LegateDefined(LEGATE_USE_OPENMP) +#if LEGATE_DEFINED(LEGATE_USE_OPENMP) static void omp_variant(legate::TaskContext context); #endif -#if LegateDefined(LEGATE_USE_CUDA) +#if LEGATE_DEFINED(LEGATE_USE_CUDA) static void gpu_variant(legate::TaskContext context); #endif }; diff --git a/src/cunumeric/matrix/qr.cc b/src/cunumeric/matrix/qr.cc index ca75278419..9d5881ab68 100644 --- a/src/cunumeric/matrix/qr.cc +++ b/src/cunumeric/matrix/qr.cc @@ -26,7 +26,7 @@ using namespace legate; /*static*/ void QrTask::cpu_variant(TaskContext context) { -#if LegateDefined(LEGATE_USE_OPENMP) +#if LEGATE_DEFINED(LEGATE_USE_OPENMP) openblas_set_num_threads(1); // make sure this isn't overzealous #endif qr_template(context); diff --git a/src/cunumeric/matrix/qr.cu b/src/cunumeric/matrix/qr.cu index dc9b28602d..7505565351 100644 --- a/src/cunumeric/matrix/qr.cu +++ b/src/cunumeric/matrix/qr.cu @@ -52,8 +52,9 @@ static inline void qr_template(GeqrfBufferSize geqrf_buffer_size, q_tmp = q_copy.ptr(0); } - LegateCheckCUDA(cudaMemcpyAsync(q_tmp, a, sizeof(VAL) * m * n, cudaMemcpyDeviceToDevice, stream)); - LegateCheckCUDA(cudaStreamSynchronize(stream)); + CUNUMERIC_CHECK_CUDA( + cudaMemcpyAsync(q_tmp, a, sizeof(VAL) * m * n, cudaMemcpyDeviceToDevice, stream)); + CUNUMERIC_CHECK_CUDA(cudaStreamSynchronize(stream)); CHECK_CUSOLVER(cusolverDnSetStream(handle, stream)); @@ -70,27 +71,27 @@ static inline void qr_template(GeqrfBufferSize geqrf_buffer_size, CHECK_CUSOLVER( geqrf(handle, m, n, q_tmp, m, tau.ptr(0), buffer.ptr(0), lwork_total, info.ptr(0))); - LegateCheckCUDA(cudaStreamSynchronize(stream)); + CUNUMERIC_CHECK_CUDA(cudaStreamSynchronize(stream)); if (info[0] != 0) { throw legate::TaskException(QrTask::ERROR_MESSAGE); } // extract R from upper triangular of geqrf result - LegateCheckCUDA(cudaMemsetAsync(r, 0, k * n * sizeof(VAL), stream)); + CUNUMERIC_CHECK_CUDA(cudaMemsetAsync(r, 0, k * n * sizeof(VAL), stream)); for (int i = 0; i < k; ++i) { int elements = i + 1; if (i == k - 1 && n > k) { elements = k * (n - k + 1); } - LegateCheckCUDA(cudaMemcpyAsync( + CUNUMERIC_CHECK_CUDA(cudaMemcpyAsync( r + i * k, q_tmp + i * m, sizeof(VAL) * elements, cudaMemcpyDeviceToDevice, stream)); } // assemble Q CHECK_CUSOLVER( orgqr(handle, m, k, k, q_tmp, m, tau.ptr(0), buffer.ptr(0), lwork_total, info.ptr(0))); - LegateCheckCUDA(cudaStreamSynchronize(stream)); + CUNUMERIC_CHECK_CUDA(cudaStreamSynchronize(stream)); if (info[0] != 0) { throw legate::TaskException(QrTask::ERROR_MESSAGE); @@ -99,11 +100,11 @@ static inline void qr_template(GeqrfBufferSize geqrf_buffer_size, // if we used a tmp storage we still need to copy back Q if (q_tmp != q) { assert(n > m); - LegateCheckCUDA( + CUNUMERIC_CHECK_CUDA( cudaMemcpyAsync(q, q_tmp, sizeof(VAL) * m * m, cudaMemcpyDeviceToDevice, stream)); } - LegateCheckCUDAStream(stream); + CUNUMERIC_CHECK_CUDA_STREAM(stream); #ifdef DEBUG_CUNUMERIC assert(info[0] == 0); diff --git a/src/cunumeric/matrix/qr.h b/src/cunumeric/matrix/qr.h index da97d291ee..7052cdb064 100644 --- a/src/cunumeric/matrix/qr.h +++ b/src/cunumeric/matrix/qr.h @@ -27,10 +27,10 @@ class QrTask : public CuNumericTask { public: static void cpu_variant(legate::TaskContext context); -#if LegateDefined(LEGATE_USE_OPENMP) +#if LEGATE_DEFINED(LEGATE_USE_OPENMP) static void omp_variant(legate::TaskContext context); #endif -#if LegateDefined(LEGATE_USE_CUDA) +#if LEGATE_DEFINED(LEGATE_USE_CUDA) static void gpu_variant(legate::TaskContext context); #endif }; diff --git a/src/cunumeric/matrix/solve.cc b/src/cunumeric/matrix/solve.cc index 4dd7b78bf6..033861311a 100644 --- a/src/cunumeric/matrix/solve.cc +++ b/src/cunumeric/matrix/solve.cc @@ -26,7 +26,7 @@ using namespace legate; /*static*/ void SolveTask::cpu_variant(TaskContext context) { -#if LegateDefined(LEGATE_USE_OPENMP) +#if LEGATE_DEFINED(LEGATE_USE_OPENMP) openblas_set_num_threads(1); // make sure this isn't overzealous #endif solve_template(context); diff --git a/src/cunumeric/matrix/solve.cu b/src/cunumeric/matrix/solve.cu index dd415e19d8..365f11f725 100644 --- a/src/cunumeric/matrix/solve.cu +++ b/src/cunumeric/matrix/solve.cu @@ -47,7 +47,7 @@ static inline void solve_template(GetrfBufferSize getrf_buffer_size, auto info = create_buffer(1, Memory::Kind::Z_COPY_MEM); CHECK_CUSOLVER(getrf(handle, m, n, a, m, buffer.ptr(0), ipiv.ptr(0), info.ptr(0))); - LegateCheckCUDA(cudaStreamSynchronize(stream)); + CUNUMERIC_CHECK_CUDA(cudaStreamSynchronize(stream)); if (info[0] != 0) { throw legate::TaskException(SolveTask::ERROR_MESSAGE); @@ -55,7 +55,7 @@ static inline void solve_template(GetrfBufferSize getrf_buffer_size, CHECK_CUSOLVER(getrs(handle, trans, n, nrhs, a, m, ipiv.ptr(0), b, n, info.ptr(0))); - LegateCheckCUDAStream(stream); + CUNUMERIC_CHECK_CUDA_STREAM(stream); #ifdef DEBUG_CUNUMERIC assert(info[0] == 0); diff --git a/src/cunumeric/matrix/solve.h b/src/cunumeric/matrix/solve.h index d5a47a5c00..83c7a45892 100644 --- a/src/cunumeric/matrix/solve.h +++ b/src/cunumeric/matrix/solve.h @@ -27,10 +27,10 @@ class SolveTask : public CuNumericTask { public: static void cpu_variant(legate::TaskContext context); -#if LegateDefined(LEGATE_USE_OPENMP) +#if LEGATE_DEFINED(LEGATE_USE_OPENMP) static void omp_variant(legate::TaskContext context); #endif -#if LegateDefined(LEGATE_USE_CUDA) +#if LEGATE_DEFINED(LEGATE_USE_CUDA) static void gpu_variant(legate::TaskContext context); #endif }; diff --git a/src/cunumeric/matrix/svd.cc b/src/cunumeric/matrix/svd.cc index 2174e1fa98..97d49d694f 100644 --- a/src/cunumeric/matrix/svd.cc +++ b/src/cunumeric/matrix/svd.cc @@ -26,7 +26,7 @@ using namespace legate; /*static*/ void SvdTask::cpu_variant(TaskContext context) { -#if LegateDefined(LEGATE_USE_OPENMP) +#if LEGATE_DEFINED(LEGATE_USE_OPENMP) openblas_set_num_threads(1); // make sure this isn't overzealous #endif svd_template(context); diff --git a/src/cunumeric/matrix/svd.cu b/src/cunumeric/matrix/svd.cu index 1fbb704d81..ba5dfdebac 100644 --- a/src/cunumeric/matrix/svd.cu +++ b/src/cunumeric/matrix/svd.cu @@ -38,7 +38,7 @@ static inline void svd_template(DataType valTypeC, auto stream = get_cached_stream(); auto a_copy = create_buffer(m * n, Memory::Kind::GPU_FB_MEM); - LegateCheckCUDA( + CUNUMERIC_CHECK_CUDA( cudaMemcpyAsync(a_copy.ptr(0), a, m * n * sizeof(VAL), cudaMemcpyDeviceToDevice, stream)); // a[m][n], u[m][m] s[k] vh[n][n] @@ -94,13 +94,13 @@ static inline void svd_template(DataType valTypeC, lwork_host, info.ptr(0))); - LegateCheckCUDA(cudaStreamSynchronize(stream)); + CUNUMERIC_CHECK_CUDA(cudaStreamSynchronize(stream)); if (info[0] != 0) { throw legate::TaskException(SvdTask::ERROR_MESSAGE); } - LegateCheckCUDAStream(stream); + CUNUMERIC_CHECK_CUDA_STREAM(stream); #ifdef DEBUG_CUNUMERIC assert(info[0] == 0); diff --git a/src/cunumeric/matrix/svd.h b/src/cunumeric/matrix/svd.h index e5a538aab6..ada3ed8e0e 100644 --- a/src/cunumeric/matrix/svd.h +++ b/src/cunumeric/matrix/svd.h @@ -27,10 +27,10 @@ class SvdTask : public CuNumericTask { public: static void cpu_variant(legate::TaskContext context); -#if LegateDefined(LEGATE_USE_OPENMP) +#if LEGATE_DEFINED(LEGATE_USE_OPENMP) static void omp_variant(legate::TaskContext context); #endif -#if LegateDefined(LEGATE_USE_CUDA) +#if LEGATE_DEFINED(LEGATE_USE_CUDA) static void gpu_variant(legate::TaskContext context); #endif }; diff --git a/src/cunumeric/matrix/syrk.cc b/src/cunumeric/matrix/syrk.cc index bcc5992931..83d89a6637 100644 --- a/src/cunumeric/matrix/syrk.cc +++ b/src/cunumeric/matrix/syrk.cc @@ -77,7 +77,7 @@ struct SyrkImplBody { /*static*/ void SyrkTask::cpu_variant(TaskContext context) { -#if LegateDefined(LEGATE_USE_OPENMP) +#if LEGATE_DEFINED(LEGATE_USE_OPENMP) openblas_set_num_threads(1); // make sure this isn't overzealous #endif syrk_template(context); diff --git a/src/cunumeric/matrix/syrk.cu b/src/cunumeric/matrix/syrk.cu index 547e2e3fce..32ff313f04 100644 --- a/src/cunumeric/matrix/syrk.cu +++ b/src/cunumeric/matrix/syrk.cu @@ -38,7 +38,7 @@ static inline void syrk_template( CHECK_CUBLAS(syrk(context, uplo, trans, m, n, &alpha, rhs, m, &beta, lhs, m)); - LegateCheckCUDAStream(stream); + CUNUMERIC_CHECK_CUDA_STREAM(stream); } template <> diff --git a/src/cunumeric/matrix/syrk.h b/src/cunumeric/matrix/syrk.h index 203c3b7326..d58a453b44 100644 --- a/src/cunumeric/matrix/syrk.h +++ b/src/cunumeric/matrix/syrk.h @@ -26,10 +26,10 @@ class SyrkTask : public CuNumericTask { public: static void cpu_variant(legate::TaskContext context); -#if LegateDefined(LEGATE_USE_OPENMP) +#if LEGATE_DEFINED(LEGATE_USE_OPENMP) static void omp_variant(legate::TaskContext context); #endif -#if LegateDefined(LEGATE_USE_CUDA) +#if LEGATE_DEFINED(LEGATE_USE_CUDA) static void gpu_variant(legate::TaskContext context); #endif }; diff --git a/src/cunumeric/matrix/tile.cu b/src/cunumeric/matrix/tile.cu index 54b69c1c66..4faa766f56 100644 --- a/src/cunumeric/matrix/tile.cu +++ b/src/cunumeric/matrix/tile.cu @@ -53,7 +53,7 @@ struct TileImplBody { auto stream = get_cached_stream(); tile_kernel<<>>( out_rect, out_pitches, out_volume, in_strides, out, in); - LegateCheckCUDAStream(stream); + CUNUMERIC_CHECK_CUDA_STREAM(stream); } }; diff --git a/src/cunumeric/matrix/tile.h b/src/cunumeric/matrix/tile.h index af853db817..3bd18e7900 100644 --- a/src/cunumeric/matrix/tile.h +++ b/src/cunumeric/matrix/tile.h @@ -31,10 +31,10 @@ class TileTask : public CuNumericTask { public: static void cpu_variant(legate::TaskContext context); -#if LegateDefined(LEGATE_USE_OPENMP) +#if LEGATE_DEFINED(LEGATE_USE_OPENMP) static void omp_variant(legate::TaskContext context); #endif -#if LegateDefined(LEGATE_USE_CUDA) +#if LEGATE_DEFINED(LEGATE_USE_CUDA) static void gpu_variant(legate::TaskContext context); #endif }; diff --git a/src/cunumeric/matrix/transpose.cc b/src/cunumeric/matrix/transpose.cc index 7644de579a..16564e03c9 100644 --- a/src/cunumeric/matrix/transpose.cc +++ b/src/cunumeric/matrix/transpose.cc @@ -17,7 +17,7 @@ #include "cunumeric/matrix/transpose.h" #include "cunumeric/matrix/transpose_template.inl" -#if LegateDefined(LEGATE_USE_OPENMP) +#if LEGATE_DEFINED(LEGATE_USE_OPENMP) #include "omp.h" #endif #include "cblas.h" @@ -51,7 +51,7 @@ struct TransposeImplBody { /*static*/ void TransposeTask::cpu_variant(TaskContext context) { -#if LegateDefined(LEGATE_USE_OPENMP) +#if LEGATE_DEFINED(LEGATE_USE_OPENMP) openblas_set_num_threads(1); // make sure this isn't overzealous #endif transpose_template(context); diff --git a/src/cunumeric/matrix/transpose.cu b/src/cunumeric/matrix/transpose.cu index 39fd497e54..9b8ce2e90b 100644 --- a/src/cunumeric/matrix/transpose.cu +++ b/src/cunumeric/matrix/transpose.cu @@ -99,7 +99,7 @@ struct TransposeImplBody { auto stream = get_cached_stream(); transpose_2d_physical<<>>(out, in, rect.lo, rect.hi); - LegateCheckCUDAStream(stream); + CUNUMERIC_CHECK_CUDA_STREAM(stream); } }; diff --git a/src/cunumeric/matrix/transpose.h b/src/cunumeric/matrix/transpose.h index c8659273c6..f9be58e6d1 100644 --- a/src/cunumeric/matrix/transpose.h +++ b/src/cunumeric/matrix/transpose.h @@ -31,10 +31,10 @@ class TransposeTask : public CuNumericTask { public: static void cpu_variant(legate::TaskContext context); -#if LegateDefined(LEGATE_USE_OPENMP) +#if LEGATE_DEFINED(LEGATE_USE_OPENMP) static void omp_variant(legate::TaskContext context); #endif -#if LegateDefined(LEGATE_USE_CUDA) +#if LEGATE_DEFINED(LEGATE_USE_CUDA) static void gpu_variant(legate::TaskContext context); #endif }; diff --git a/src/cunumeric/matrix/trilu.cu b/src/cunumeric/matrix/trilu.cu index 2d50d931da..49f426026f 100644 --- a/src/cunumeric/matrix/trilu.cu +++ b/src/cunumeric/matrix/trilu.cu @@ -70,7 +70,7 @@ struct TriluImplBody { auto stream = get_cached_stream(); trilu_kernel <<>>(out, in, pitches, lo, volume, k); - LegateCheckCUDAStream(stream); + CUNUMERIC_CHECK_CUDA_STREAM(stream); } }; diff --git a/src/cunumeric/matrix/trilu.h b/src/cunumeric/matrix/trilu.h index fd1874393d..b86b2c874a 100644 --- a/src/cunumeric/matrix/trilu.h +++ b/src/cunumeric/matrix/trilu.h @@ -33,10 +33,10 @@ class TriluTask : public CuNumericTask { public: static void cpu_variant(legate::TaskContext context); -#if LegateDefined(LEGATE_USE_OPENMP) +#if LEGATE_DEFINED(LEGATE_USE_OPENMP) static void omp_variant(legate::TaskContext context); #endif -#if LegateDefined(LEGATE_USE_CUDA) +#if LEGATE_DEFINED(LEGATE_USE_CUDA) static void gpu_variant(legate::TaskContext context); #endif }; diff --git a/src/cunumeric/matrix/trsm.cc b/src/cunumeric/matrix/trsm.cc index 880fa5ee57..bfe5d9e80c 100644 --- a/src/cunumeric/matrix/trsm.cc +++ b/src/cunumeric/matrix/trsm.cc @@ -88,7 +88,7 @@ struct TrsmImplBody { /*static*/ void TrsmTask::cpu_variant(TaskContext context) { -#if LegateDefined(LEGATE_USE_OPENMP) +#if LEGATE_DEFINED(LEGATE_USE_OPENMP) openblas_set_num_threads(1); // make sure this isn't overzealous #endif trsm_template(context); diff --git a/src/cunumeric/matrix/trsm.cu b/src/cunumeric/matrix/trsm.cu index a1481e78da..746950080f 100644 --- a/src/cunumeric/matrix/trsm.cu +++ b/src/cunumeric/matrix/trsm.cu @@ -39,7 +39,7 @@ static inline void trsm_template( CHECK_CUBLAS(trsm(context, side, uplo, transa, diag, m, n, &alpha, rhs, n, lhs, m)); - LegateCheckCUDAStream(stream); + CUNUMERIC_CHECK_CUDA_STREAM(stream); } template <> diff --git a/src/cunumeric/matrix/trsm.h b/src/cunumeric/matrix/trsm.h index b34a4c7af4..97311a3953 100644 --- a/src/cunumeric/matrix/trsm.h +++ b/src/cunumeric/matrix/trsm.h @@ -26,10 +26,10 @@ class TrsmTask : public CuNumericTask { public: static void cpu_variant(legate::TaskContext context); -#if LegateDefined(LEGATE_USE_OPENMP) +#if LEGATE_DEFINED(LEGATE_USE_OPENMP) static void omp_variant(legate::TaskContext context); #endif -#if LegateDefined(LEGATE_USE_CUDA) +#if LEGATE_DEFINED(LEGATE_USE_CUDA) static void gpu_variant(legate::TaskContext context); #endif }; diff --git a/src/cunumeric/matrix/util.cc b/src/cunumeric/matrix/util.cc index 31e4c99613..7a382f2973 100644 --- a/src/cunumeric/matrix/util.cc +++ b/src/cunumeric/matrix/util.cc @@ -16,7 +16,7 @@ #include "core/data/buffer.h" #include "cunumeric/matrix/util.h" -#if LegateDefined(LEGATE_USE_OPENMP) +#if LEGATE_DEFINED(LEGATE_USE_OPENMP) #include #endif @@ -82,7 +82,7 @@ float* allocate_buffer(size_t size) void half_vector_to_float(float* out, const __half* ptr, size_t n) { -#if LegateDefined(LEGATE_USE_OPENMP) +#if LEGATE_DEFINED(LEGATE_USE_OPENMP) if (legate::Processor::get_executing_processor().kind() == legate::Processor::OMP_PROC) { #pragma omp parallel for schedule(static) for (size_t idx = 0; idx < n; idx++) { @@ -98,7 +98,7 @@ void half_vector_to_float(float* out, const __half* ptr, size_t n) void half_matrix_to_float(float* out, const __half* ptr, size_t m, size_t n, size_t pitch) { -#if LegateDefined(LEGATE_USE_OPENMP) +#if LEGATE_DEFINED(LEGATE_USE_OPENMP) if (legate::Processor::get_executing_processor().kind() == legate::Processor::OMP_PROC) { #pragma omp parallel for schedule(static) for (size_t i = 0; i < m; i++) { @@ -120,7 +120,7 @@ void half_tensor_to_float( float* out, const __half* in, size_t ndim, const int64_t* shape, const int64_t* in_strides) { int64_t volume = calculate_volume(ndim, shape); -#if LegateDefined(LEGATE_USE_OPENMP) +#if LEGATE_DEFINED(LEGATE_USE_OPENMP) if (legate::Processor::get_executing_processor().kind() == legate::Processor::OMP_PROC) { #pragma omp parallel for schedule(static) for (int64_t out_idx = 0; out_idx < volume; ++out_idx) { @@ -140,7 +140,7 @@ void float_tensor_to_half( __half* out, const float* in, size_t ndim, const int64_t* shape, const int64_t* out_strides) { int64_t volume = calculate_volume(ndim, shape); -#if LegateDefined(LEGATE_USE_OPENMP) +#if LEGATE_DEFINED(LEGATE_USE_OPENMP) if (legate::Processor::get_executing_processor().kind() == legate::Processor::OMP_PROC) { #pragma omp parallel for schedule(static) for (int64_t in_idx = 0; in_idx < volume; ++in_idx) { diff --git a/src/cunumeric/nullary/arange.cu b/src/cunumeric/nullary/arange.cu index eaeee3d5fb..f777734012 100644 --- a/src/cunumeric/nullary/arange.cu +++ b/src/cunumeric/nullary/arange.cu @@ -45,7 +45,7 @@ struct ArangeImplBody { auto stream = get_cached_stream(); arange_kernel <<>>(out, rect.lo[0], start, step, distance); - LegateCheckCUDAStream(stream); + CUNUMERIC_CHECK_CUDA_STREAM(stream); } }; diff --git a/src/cunumeric/nullary/arange.h b/src/cunumeric/nullary/arange.h index d89cf49de0..075a7e4da6 100644 --- a/src/cunumeric/nullary/arange.h +++ b/src/cunumeric/nullary/arange.h @@ -32,10 +32,10 @@ class ArangeTask : public CuNumericTask { public: static void cpu_variant(legate::TaskContext context); -#if LegateDefined(LEGATE_USE_OPENMP) +#if LEGATE_DEFINED(LEGATE_USE_OPENMP) static void omp_variant(legate::TaskContext context); #endif -#if LegateDefined(LEGATE_USE_CUDA) +#if LEGATE_DEFINED(LEGATE_USE_CUDA) static void gpu_variant(legate::TaskContext context); #endif }; diff --git a/src/cunumeric/nullary/eye.cu b/src/cunumeric/nullary/eye.cu index 90a78db22f..6620d45f35 100644 --- a/src/cunumeric/nullary/eye.cu +++ b/src/cunumeric/nullary/eye.cu @@ -40,7 +40,7 @@ struct EyeImplBody { const size_t blocks = (distance + THREADS_PER_BLOCK - 1) / THREADS_PER_BLOCK; auto stream = get_cached_stream(); eye_kernel<<>>(out, start, distance); - LegateCheckCUDAStream(stream); + CUNUMERIC_CHECK_CUDA_STREAM(stream); } }; diff --git a/src/cunumeric/nullary/eye.h b/src/cunumeric/nullary/eye.h index 0520a89665..bfc1ea6860 100644 --- a/src/cunumeric/nullary/eye.h +++ b/src/cunumeric/nullary/eye.h @@ -31,10 +31,10 @@ class EyeTask : public CuNumericTask { public: static void cpu_variant(legate::TaskContext context); -#if LegateDefined(LEGATE_USE_OPENMP) +#if LEGATE_DEFINED(LEGATE_USE_OPENMP) static void omp_variant(legate::TaskContext context); #endif -#if LegateDefined(LEGATE_USE_CUDA) +#if LEGATE_DEFINED(LEGATE_USE_CUDA) static void gpu_variant(legate::TaskContext context); #endif }; diff --git a/src/cunumeric/nullary/fill.cu b/src/cunumeric/nullary/fill.cu index c4a8769bdd..2fedcba224 100644 --- a/src/cunumeric/nullary/fill.cu +++ b/src/cunumeric/nullary/fill.cu @@ -61,7 +61,7 @@ struct FillImplBody { } else { generic_kernel<<>>(volume, out, in, pitches, rect); } - LegateCheckCUDAStream(stream); + CUNUMERIC_CHECK_CUDA_STREAM(stream); } }; diff --git a/src/cunumeric/nullary/fill.h b/src/cunumeric/nullary/fill.h index aff7329226..2cb3afc29d 100644 --- a/src/cunumeric/nullary/fill.h +++ b/src/cunumeric/nullary/fill.h @@ -31,10 +31,10 @@ class FillTask : public CuNumericTask { public: static void cpu_variant(legate::TaskContext context); -#if LegateDefined(LEGATE_USE_OPENMP) +#if LEGATE_DEFINED(LEGATE_USE_OPENMP) static void omp_variant(legate::TaskContext context); #endif -#if LegateDefined(LEGATE_USE_CUDA) +#if LEGATE_DEFINED(LEGATE_USE_CUDA) static void gpu_variant(legate::TaskContext context); #endif }; diff --git a/src/cunumeric/nullary/fill_template.inl b/src/cunumeric/nullary/fill_template.inl index de9e234312..7465322248 100644 --- a/src/cunumeric/nullary/fill_template.inl +++ b/src/cunumeric/nullary/fill_template.inl @@ -46,7 +46,7 @@ struct FillImpl { auto out = args.out.write_accessor(rect); auto fill_value = args.fill_value.read_accessor(); -#if !LegateDefined(LEGATE_BOUNDS_CHECKS) +#if !LEGATE_DEFINED(LEGATE_BOUNDS_CHECKS) // Check to see if this is dense or not bool dense = out.accessor.is_dense_row_major(rect); #else diff --git a/src/cunumeric/nullary/window.cu b/src/cunumeric/nullary/window.cu index de92e2b68d..ceb2f7d0d2 100644 --- a/src/cunumeric/nullary/window.cu +++ b/src/cunumeric/nullary/window.cu @@ -64,7 +64,7 @@ struct WindowImplBody { <<>>(gen, volume, out, rect.lo[0]); } - LegateCheckCUDAStream(stream); + CUNUMERIC_CHECK_CUDA_STREAM(stream); } }; diff --git a/src/cunumeric/nullary/window.h b/src/cunumeric/nullary/window.h index 80dc5c68bf..3276472962 100644 --- a/src/cunumeric/nullary/window.h +++ b/src/cunumeric/nullary/window.h @@ -26,10 +26,10 @@ class WindowTask : public CuNumericTask { public: static void cpu_variant(legate::TaskContext context); -#if LegateDefined(LEGATE_USE_OPENMP) +#if LEGATE_DEFINED(LEGATE_USE_OPENMP) static void omp_variant(legate::TaskContext context); #endif -#if LegateDefined(LEGATE_USE_CUDA) +#if LEGATE_DEFINED(LEGATE_USE_CUDA) static void gpu_variant(legate::TaskContext context); #endif }; diff --git a/src/cunumeric/nullary/window_template.inl b/src/cunumeric/nullary/window_template.inl index 1d46568cc3..e7f145aab1 100644 --- a/src/cunumeric/nullary/window_template.inl +++ b/src/cunumeric/nullary/window_template.inl @@ -40,7 +40,7 @@ struct WindowImpl { auto out = output.write_accessor(rect); -#if !LegateDefined(LEGATE_BOUNDS_CHECKS) +#if !LEGATE_DEFINED(LEGATE_BOUNDS_CHECKS) // Check to see if this is dense or not bool dense = out.accessor.is_dense_row_major(rect); #else diff --git a/src/cunumeric/random/bitgenerator.cu b/src/cunumeric/random/bitgenerator.cu index 8c04e7d1be..19dba8039c 100644 --- a/src/cunumeric/random/bitgenerator.cu +++ b/src/cunumeric/random/bitgenerator.cu @@ -32,13 +32,13 @@ struct GPUGenerator : public CURANDGenerator { GPUGenerator(BitGeneratorType gentype, uint64_t seed, uint64_t generatorId, uint32_t flags) : CURANDGenerator(gentype, seed, generatorId) { - LegateCheckCUDA(::cudaStreamCreate(&stream_)); + CUNUMERIC_CHECK_CUDA(::cudaStreamCreate(&stream_)); CHECK_CURAND(::randutilCreateGenerator(&gen_, type_, seed, generatorId, flags, stream_)); } virtual ~GPUGenerator() { - LegateCheckCUDA(::cudaStreamSynchronize(stream_)); + CUNUMERIC_CHECK_CUDA(::cudaStreamSynchronize(stream_)); CHECK_CURAND(::randutilDestroyGenerator(gen_)); } }; diff --git a/src/cunumeric/random/bitgenerator.h b/src/cunumeric/random/bitgenerator.h index 87fc37a764..eb03c06f86 100644 --- a/src/cunumeric/random/bitgenerator.h +++ b/src/cunumeric/random/bitgenerator.h @@ -84,12 +84,12 @@ class BitGeneratorTask : public CuNumericTask { public: static void cpu_variant(legate::TaskContext context); -#if LegateDefined(LEGATE_USE_OPENMP) +#if LEGATE_DEFINED(LEGATE_USE_OPENMP) // TODO: Fully parallelized OpenMP implementation for BitGenerator // Doing it this way is safe, but only one thread is being used out of the OpenMP pool. static void omp_variant(legate::TaskContext context) { BitGeneratorTask::cpu_variant(context); } #endif -#if LegateDefined(LEGATE_USE_CUDA) +#if LEGATE_DEFINED(LEGATE_USE_CUDA) static void gpu_variant(legate::TaskContext context); #endif }; diff --git a/src/cunumeric/random/rand.cu b/src/cunumeric/random/rand.cu index 13ef4ca8bc..55376c7b2f 100644 --- a/src/cunumeric/random/rand.cu +++ b/src/cunumeric/random/rand.cu @@ -50,7 +50,7 @@ struct RandImplBody { auto stream = get_cached_stream(); rand_kernel<<>>( volume, out, rng, strides, pitches, rect.lo); - LegateCheckCUDAStream(stream); + CUNUMERIC_CHECK_CUDA_STREAM(stream); } }; diff --git a/src/cunumeric/random/rand.h b/src/cunumeric/random/rand.h index 9fe109e17b..6b961e7f03 100644 --- a/src/cunumeric/random/rand.h +++ b/src/cunumeric/random/rand.h @@ -35,10 +35,10 @@ class RandTask : public CuNumericTask { public: static void cpu_variant(legate::TaskContext context); -#if LegateDefined(LEGATE_USE_OPENMP) +#if LEGATE_DEFINED(LEGATE_USE_OPENMP) static void omp_variant(legate::TaskContext context); #endif -#if LegateDefined(LEGATE_USE_CUDA) +#if LEGATE_DEFINED(LEGATE_USE_CUDA) static void gpu_variant(legate::TaskContext context); #endif }; diff --git a/src/cunumeric/random/randutil/generator.cuh b/src/cunumeric/random/randutil/generator.cuh index 0e5b188161..389a7f7d63 100644 --- a/src/cunumeric/random/randutil/generator.cuh +++ b/src/cunumeric/random/randutil/generator.cuh @@ -18,7 +18,7 @@ #include "generator.h" -#include +#include "cunumeric/cuda_help.h" namespace randutilimpl { static constexpr int blocksPerMultiProcessor = 2; // TODO: refine => number of blocks per mp @@ -73,8 +73,8 @@ struct inner_generator : basegenerato : seed(seed), generatorID(generatorID), stream(stream) { int deviceId; - LegateCheckCUDA(::cudaGetDevice(&deviceId)); - LegateCheckCUDA( + CUNUMERIC_CHECK_CUDA(::cudaGetDevice(&deviceId)); + CUNUMERIC_CHECK_CUDA( ::cudaDeviceGetAttribute(&multiProcessorCount, cudaDevAttrMultiProcessorCount, deviceId)); // get number of generators ngenerators = blockDimX * multiProcessorCount * blocksPerMultiProcessor; @@ -84,36 +84,36 @@ struct inner_generator : basegenerato // allocate buffer for generators state int driverVersion, runtimeVersion; - LegateCheckCUDA(::cudaDriverGetVersion(&driverVersion)); - LegateCheckCUDA(::cudaRuntimeGetVersion(&runtimeVersion)); + CUNUMERIC_CHECK_CUDA(::cudaDriverGetVersion(&driverVersion)); + CUNUMERIC_CHECK_CUDA(::cudaRuntimeGetVersion(&runtimeVersion)); asyncsupported = ((driverVersion >= 10020) && (runtimeVersion >= 10020)); if (asyncsupported) { #if (__CUDACC_VER_MAJOR__ > 11 || ((__CUDACC_VER_MAJOR__ >= 11) && (__CUDACC_VER_MINOR__ >= 2))) - LegateCheckCUDA(::cudaMallocAsync(&generators, ngenerators * sizeof(gen_t), stream)); + CUNUMERIC_CHECK_CUDA(::cudaMallocAsync(&generators, ngenerators * sizeof(gen_t), stream)); #else - LegateCheckCUDA(::cudaMalloc(&generators, ngenerators * sizeof(gen_t))); + CUNUMERIC_CHECK_CUDA(::cudaMalloc(&generators, ngenerators * sizeof(gen_t))); #endif } else { - LegateCheckCUDA(::cudaMalloc(&generators, ngenerators * sizeof(gen_t))); + CUNUMERIC_CHECK_CUDA(::cudaMalloc(&generators, ngenerators * sizeof(gen_t))); } // initialize generators initgenerators<<>>( generators, seed, generatorID); - LegateCheckCUDA(::cudaPeekAtLastError()); + CUNUMERIC_CHECK_CUDA(::cudaPeekAtLastError()); } virtual void destroy() override { - LegateCheckCUDA(::cudaStreamSynchronize(stream)); + CUNUMERIC_CHECK_CUDA(::cudaStreamSynchronize(stream)); if (asyncsupported) { #if (__CUDACC_VER_MAJOR__ > 11 || ((__CUDACC_VER_MAJOR__ >= 11) && (__CUDACC_VER_MINOR__ >= 2))) - LegateCheckCUDA(::cudaFreeAsync(generators, stream)); + CUNUMERIC_CHECK_CUDA(::cudaFreeAsync(generators, stream)); #else - LegateCheckCUDA(::cudaFree(generators)); + CUNUMERIC_CHECK_CUDA(::cudaFree(generators)); #endif } else { - LegateCheckCUDA(::cudaFree(generators)); + CUNUMERIC_CHECK_CUDA(::cudaFree(generators)); } generators = nullptr; diff --git a/src/cunumeric/random/randutil/generator.h b/src/cunumeric/random/randutil/generator.h index e8f80d6576..0293717201 100644 --- a/src/cunumeric/random/randutil/generator.h +++ b/src/cunumeric/random/randutil/generator.h @@ -137,7 +137,7 @@ curandStatus_t dispatch(randutilimpl::basegenerator* gen, func_t func, size_t N, switch (gen->location()) { case randutilimpl::execlocation::HOST: return dispatcher::run(gen, func, N, out); -#if LegateDefined(LEGATE_USE_CUDA) +#if LEGATE_DEFINED(LEGATE_USE_CUDA) case randutilimpl::execlocation::DEVICE: return dispatcher::run(gen, func, N, out); #endif diff --git a/src/cunumeric/random/randutil/generator_host.cc b/src/cunumeric/random/randutil/generator_host.cc index c38a3f4eaa..0d5fdc9d5e 100644 --- a/src/cunumeric/random/randutil/generator_host.cc +++ b/src/cunumeric/random/randutil/generator_host.cc @@ -17,7 +17,7 @@ #include "generator.h" #include "generator_create.inl" -#if !LegateDefined(LEGATE_USE_CUDA) +#if !LEGATE_DEFINED(LEGATE_USE_CUDA) // the host code of cuRAND try to extern these variables out of nowhere, // so we need to define them somewhere. const dim3 blockDim{}; diff --git a/src/cunumeric/random/randutil/randutil.h b/src/cunumeric/random/randutil/randutil.h index b098f36ce3..42c370bb00 100644 --- a/src/cunumeric/random/randutil/randutil.h +++ b/src/cunumeric/random/randutil/randutil.h @@ -23,7 +23,7 @@ typedef void* randutilGenerator_t; /* generator */ // CUDA-ONLY API -#if LegateDefined(LEGATE_USE_CUDA) +#if LEGATE_DEFINED(LEGATE_USE_CUDA) extern "C" curandStatus_t randutilCreateGenerator(randutilGenerator_t* generator, curandRngType_t rng_type, uint64_t seed, diff --git a/src/cunumeric/random/randutil/randutil_curand.h b/src/cunumeric/random/randutil/randutil_curand.h index 87c09253e7..b172e563d4 100644 --- a/src/cunumeric/random/randutil/randutil_curand.h +++ b/src/cunumeric/random/randutil/randutil_curand.h @@ -20,7 +20,7 @@ // generators // also allow usage of generators on host -#if LegateDefined(LEGATE_USE_CUDA) +#if LEGATE_DEFINED(LEGATE_USE_CUDA) #define QUALIFIERS static __forceinline__ __device__ __host__ #define RANDUTIL_QUALIFIERS __forceinline__ __device__ __host__ diff --git a/src/cunumeric/scan/scan_global.cu b/src/cunumeric/scan/scan_global.cu index 027b58e88b..63bb08dcd0 100644 --- a/src/cunumeric/scan/scan_global.cu +++ b/src/cunumeric/scan/scan_global.cu @@ -79,7 +79,7 @@ struct ScanGlobalImplBody { scalar_kernel<<>>( stride, func, &outptr[index], global_prefix); } - LegateCheckCUDAStream(stream); + CUNUMERIC_CHECK_CUDA_STREAM(stream); } }; diff --git a/src/cunumeric/scan/scan_global.h b/src/cunumeric/scan/scan_global.h index 88258af6c2..add7b7f566 100644 --- a/src/cunumeric/scan/scan_global.h +++ b/src/cunumeric/scan/scan_global.h @@ -34,10 +34,10 @@ class ScanGlobalTask : public CuNumericTask { public: static void cpu_variant(legate::TaskContext context); -#if LegateDefined(LEGATE_USE_OPENMP) +#if LEGATE_DEFINED(LEGATE_USE_OPENMP) static void omp_variant(legate::TaskContext context); #endif -#if LegateDefined(LEGATE_USE_CUDA) +#if LEGATE_DEFINED(LEGATE_USE_CUDA) static void gpu_variant(legate::TaskContext context); #endif }; diff --git a/src/cunumeric/scan/scan_local.cu b/src/cunumeric/scan/scan_local.cu index 7519c380b7..f7f5ed0ccb 100644 --- a/src/cunumeric/scan/scan_local.cu +++ b/src/cunumeric/scan/scan_local.cu @@ -75,7 +75,7 @@ struct ScanLocalImplBody { lazy_kernel<<<1, THREADS_PER_BLOCK, 0, stream>>>(&outptr[index + stride - 1], &sum_valsptr[sum_valp]); } - LegateCheckCUDAStream(stream); + CUNUMERIC_CHECK_CUDA_STREAM(stream); } }; @@ -126,7 +126,7 @@ struct ScanLocalNanImplBody { lazy_kernel<<<1, THREADS_PER_BLOCK, 0, stream>>>(&outptr[index + stride - 1], &sum_valsptr[sum_valp]); } - LegateCheckCUDAStream(stream); + CUNUMERIC_CHECK_CUDA_STREAM(stream); } }; diff --git a/src/cunumeric/scan/scan_local.h b/src/cunumeric/scan/scan_local.h index 5f5534f5a6..54a4c338c4 100644 --- a/src/cunumeric/scan/scan_local.h +++ b/src/cunumeric/scan/scan_local.h @@ -35,10 +35,10 @@ class ScanLocalTask : public CuNumericTask { public: static void cpu_variant(legate::TaskContext context); -#if LegateDefined(LEGATE_USE_OPENMP) +#if LEGATE_DEFINED(LEGATE_USE_OPENMP) static void omp_variant(legate::TaskContext context); #endif -#if LegateDefined(LEGATE_USE_CUDA) +#if LEGATE_DEFINED(LEGATE_USE_CUDA) static void gpu_variant(legate::TaskContext context); #endif }; diff --git a/src/cunumeric/search/argwhere.cu b/src/cunumeric/search/argwhere.cu index 41e85a56e3..7f5b7cf6be 100644 --- a/src/cunumeric/search/argwhere.cu +++ b/src/cunumeric/search/argwhere.cu @@ -59,7 +59,7 @@ struct ArgWhereImplBody { auto offsets = create_buffer(volume, legate::Memory::Kind::GPU_FB_MEM); auto size = compute_offsets(input, pitches, rect, volume, offsets, stream); - LegateCheckCUDAStream(stream); + CUNUMERIC_CHECK_CUDA_STREAM(stream); auto out = out_array.create_output_buffer(Point<2>(size, DIM), true); @@ -67,7 +67,7 @@ struct ArgWhereImplBody { const size_t blocks = (volume + THREADS_PER_BLOCK - 1) / THREADS_PER_BLOCK; argwhere_kernel<<>>( volume, input, pitches, rect.lo, offsets, out); - LegateCheckCUDAStream(stream); + CUNUMERIC_CHECK_CUDA_STREAM(stream); } } }; diff --git a/src/cunumeric/search/argwhere.h b/src/cunumeric/search/argwhere.h index 738c62d281..b6e453f5c6 100644 --- a/src/cunumeric/search/argwhere.h +++ b/src/cunumeric/search/argwhere.h @@ -31,10 +31,10 @@ class ArgWhereTask : public CuNumericTask { public: static void cpu_variant(legate::TaskContext context); -#if LegateDefined(LEGATE_USE_OPENMP) +#if LEGATE_DEFINED(LEGATE_USE_OPENMP) static void omp_variant(legate::TaskContext context); #endif -#if LegateDefined(LEGATE_USE_CUDA) +#if LEGATE_DEFINED(LEGATE_USE_CUDA) static void gpu_variant(legate::TaskContext context); #endif }; diff --git a/src/cunumeric/search/nonzero.cu b/src/cunumeric/search/nonzero.cu index 29e1a07e92..5f6b42d94b 100644 --- a/src/cunumeric/search/nonzero.cu +++ b/src/cunumeric/search/nonzero.cu @@ -85,7 +85,7 @@ struct NonzeroImplBody { if (size > 0) { populate_nonzeros(in, pitches, rect, volume, results, offsets, stream); } - LegateCheckCUDAStream(stream); + CUNUMERIC_CHECK_CUDA_STREAM(stream); } }; diff --git a/src/cunumeric/search/nonzero.cuh b/src/cunumeric/search/nonzero.cuh index 2ef4db7e7e..2146f1447c 100644 --- a/src/cunumeric/search/nonzero.cuh +++ b/src/cunumeric/search/nonzero.cuh @@ -78,7 +78,7 @@ int64_t compute_offsets(const AccessorRO& in, exclusive_sum(p_offsets, volume, stream); - LegateCheckCUDAStream(stream); + CUNUMERIC_CHECK_CUDA_STREAM(stream); return size.read(stream); } diff --git a/src/cunumeric/search/nonzero.h b/src/cunumeric/search/nonzero.h index beee4e5162..9f4f09b888 100644 --- a/src/cunumeric/search/nonzero.h +++ b/src/cunumeric/search/nonzero.h @@ -31,10 +31,10 @@ class NonzeroTask : public CuNumericTask { public: static void cpu_variant(legate::TaskContext context); -#if LegateDefined(LEGATE_USE_OPENMP) +#if LEGATE_DEFINED(LEGATE_USE_OPENMP) static void omp_variant(legate::TaskContext context); #endif -#if LegateDefined(LEGATE_USE_CUDA) +#if LEGATE_DEFINED(LEGATE_USE_CUDA) static void gpu_variant(legate::TaskContext context); #endif }; diff --git a/src/cunumeric/set/unique.cu b/src/cunumeric/set/unique.cu index fec687a8ae..edd8dee378 100644 --- a/src/cunumeric/set/unique.cu +++ b/src/cunumeric/set/unique.cu @@ -66,7 +66,7 @@ static Piece tree_reduce(legate::PhysicalStore& output, // but I suspect point-to-point can be slower... all_sizes[my_id] = my_piece.second; CHECK_NCCL(ncclAllGather(all_sizes.ptr(my_id), all_sizes.ptr(0), 1, ncclUint64, *comm, stream)); - LegateCheckCUDA(cudaStreamSynchronize(stream)); + CUNUMERIC_CHECK_CUDA(cudaStreamSynchronize(stream)); Piece other_piece; size_t offset = radix / 2; @@ -121,11 +121,11 @@ static Piece tree_reduce(legate::PhysicalStore& output, assert(my_piece.second <= buf_size); my_piece.first = output.create_output_buffer(buf_size); - LegateCheckCUDA(cudaMemcpyAsync(my_piece.first.ptr(0), - p_merged, - sizeof(VAL) * my_piece.second, - cudaMemcpyDeviceToDevice, - stream)); + CUNUMERIC_CHECK_CUDA(cudaMemcpyAsync(my_piece.first.ptr(0), + p_merged, + sizeof(VAL) * my_piece.second, + cudaMemcpyDeviceToDevice, + stream)); merged.destroy(); } @@ -163,14 +163,14 @@ struct UniqueImplBody { if (volume > 0) { if (in.accessor.is_dense_arbitrary(rect)) { auto* src = in.ptr(rect.lo); - LegateCheckCUDA( + CUNUMERIC_CHECK_CUDA( cudaMemcpyAsync(ptr, src, sizeof(VAL) * volume, cudaMemcpyDeviceToDevice, stream)); } else { const size_t num_blocks = (volume + THREADS_PER_BLOCK - 1) / THREADS_PER_BLOCK; copy_into_buffer<<>>( ptr, in, rect.lo, pitches, volume); } - LegateCheckCUDAStream(stream); + CUNUMERIC_CHECK_CUDA_STREAM(stream); // Find unique values thrust::sort(DEFAULT_POLICY.on(stream), ptr, ptr + volume); @@ -183,7 +183,7 @@ struct UniqueImplBody { assert(end - ptr <= buf_size); result.first = output.create_output_buffer(buf_size); if (result.second > 0) { - LegateCheckCUDA(cudaMemcpyAsync( + CUNUMERIC_CHECK_CUDA(cudaMemcpyAsync( result.first.ptr(0), ptr, sizeof(VAL) * result.second, cudaMemcpyDeviceToDevice, stream)); } @@ -193,7 +193,7 @@ struct UniqueImplBody { auto comm = comms[0].get(); result = tree_reduce(output, result, point[0], launch_domain.get_volume(), stream, comm); } - LegateCheckCUDAStream(stream); + CUNUMERIC_CHECK_CUDA_STREAM(stream); // Finally we pack the result output.bind_data(result.first, Point<1>(result.second)); diff --git a/src/cunumeric/set/unique.h b/src/cunumeric/set/unique.h index c124fdab69..d074e929b4 100644 --- a/src/cunumeric/set/unique.h +++ b/src/cunumeric/set/unique.h @@ -26,10 +26,10 @@ class UniqueTask : public CuNumericTask { public: static void cpu_variant(legate::TaskContext context); -#if LegateDefined(LEGATE_USE_OPENMP) +#if LEGATE_DEFINED(LEGATE_USE_OPENMP) static void omp_variant(legate::TaskContext context); #endif -#if LegateDefined(LEGATE_USE_CUDA) +#if LEGATE_DEFINED(LEGATE_USE_CUDA) static void gpu_variant(legate::TaskContext context); #endif }; diff --git a/src/cunumeric/set/unique_reduce.h b/src/cunumeric/set/unique_reduce.h index 3d933d2add..5ae7830a11 100644 --- a/src/cunumeric/set/unique_reduce.h +++ b/src/cunumeric/set/unique_reduce.h @@ -26,7 +26,7 @@ class UniqueReduceTask : public CuNumericTask { public: static void cpu_variant(legate::TaskContext context); -#if LegateDefined(LEGATE_USE_OPENMP) +#if LEGATE_DEFINED(LEGATE_USE_OPENMP) static void omp_variant(legate::TaskContext context); #endif }; diff --git a/src/cunumeric/sort/cub_sort.cuh b/src/cunumeric/sort/cub_sort.cuh index ced45d9a7a..d8c40efedc 100644 --- a/src/cunumeric/sort/cub_sort.cuh +++ b/src/cunumeric/sort/cub_sort.cuh @@ -47,7 +47,7 @@ void cub_local_sort(const VAL* values_in, if (values_in == values_out) { keys_in = create_buffer(volume, Memory::Kind::GPU_FB_MEM); values_in_cub = keys_in.ptr(0); - LegateCheckCUDA(cudaMemcpyAsync( + CUNUMERIC_CHECK_CUDA(cudaMemcpyAsync( keys_in.ptr(0), values_out, sizeof(VAL) * volume, cudaMemcpyDeviceToDevice, stream)); } @@ -111,7 +111,7 @@ void cub_local_sort(const VAL* values_in, if (indices_in == indices_out) { idx_in = create_buffer(volume, Memory::Kind::GPU_FB_MEM); indices_in_cub = idx_in.ptr(0); - LegateCheckCUDA(cudaMemcpyAsync( + CUNUMERIC_CHECK_CUDA(cudaMemcpyAsync( idx_in.ptr(0), indices_out, sizeof(int64_t) * volume, cudaMemcpyDeviceToDevice, stream)); } diff --git a/src/cunumeric/sort/searchsorted.cu b/src/cunumeric/sort/searchsorted.cu index 25489203eb..3334b9a667 100644 --- a/src/cunumeric/sort/searchsorted.cu +++ b/src/cunumeric/sort/searchsorted.cu @@ -106,7 +106,7 @@ struct SearchSortedImplBody { searchsorted_kernel_max<<>>( output_reduction, input, input_v, rect_values.lo, pitches, volume, num_values, offset); } - LegateCheckCUDAStream(stream); + CUNUMERIC_CHECK_CUDA_STREAM(stream); } }; diff --git a/src/cunumeric/sort/searchsorted.h b/src/cunumeric/sort/searchsorted.h index 7b791e152e..576bfcf64e 100644 --- a/src/cunumeric/sort/searchsorted.h +++ b/src/cunumeric/sort/searchsorted.h @@ -35,10 +35,10 @@ class SearchSortedTask : public CuNumericTask { public: static void cpu_variant(legate::TaskContext context); -#if LegateDefined(LEGATE_USE_OPENMP) +#if LEGATE_DEFINED(LEGATE_USE_OPENMP) static void omp_variant(legate::TaskContext context); #endif -#if LegateDefined(LEGATE_USE_CUDA) +#if LEGATE_DEFINED(LEGATE_USE_CUDA) static void gpu_variant(legate::TaskContext context); #endif }; diff --git a/src/cunumeric/sort/sort.cu b/src/cunumeric/sort/sort.cu index b45a278f3d..f505be682d 100644 --- a/src/cunumeric/sort/sort.cu +++ b/src/cunumeric/sort/sort.cu @@ -647,10 +647,10 @@ SegmentMergePiece> merge_all_buffers( combine_buffers_no_sort<<>>( idc_buffers_ptr, target_offsets, result.indices, merged_size, num_sort_ranks); - LegateCheckCUDA(cudaStreamSynchronize(stream)); // needed before Z-copy destroy() + CUNUMERIC_CHECK_CUDA(cudaStreamSynchronize(stream)); // needed before Z-copy destroy() idc_buffers_ptr.destroy(); } else { - LegateCheckCUDA(cudaStreamSynchronize(stream)); // needed before Z-copy destroy() + CUNUMERIC_CHECK_CUDA(cudaStreamSynchronize(stream)); // needed before Z-copy destroy() } val_buffers_ptr.destroy(); target_offsets.destroy(); @@ -672,7 +672,7 @@ SegmentMergePiece> merge_all_buffers( local_sort( p_values, p_values, p_indices, p_indices, merged_size, merged_size, true, stream); - LegateCheckCUDAStream(stream); + CUNUMERIC_CHECK_CUDA_STREAM(stream); return result; } else { // maybe k-way merge is more efficient here... @@ -773,7 +773,7 @@ SegmentMergePiece> merge_all_buffers( } SegmentMergePiece result = merge_buffers[0]; merge_buffers.clear(); - LegateCheckCUDAStream(stream); + CUNUMERIC_CHECK_CUDA_STREAM(stream); return result; } } @@ -913,28 +913,28 @@ void rebalance_data(SegmentMergePiece& merge_buffer, segment_diff_2d_ptr, segment_diff_2d_ptr + num_segments_l * num_sort_ranks, segment_diff_2d_scan_ptr); - LegateCheckCUDA(cudaMemcpy2DAsync(send_right.ptr(0), - sizeof(int64_t), - segment_diff_2d_scan.ptr(0) + my_sort_rank, - num_sort_ranks * sizeof(int64_t), - sizeof(int64_t), - num_segments_l, - cudaMemcpyDeviceToDevice, - stream)); + CUNUMERIC_CHECK_CUDA(cudaMemcpy2DAsync(send_right.ptr(0), + sizeof(int64_t), + segment_diff_2d_scan.ptr(0) + my_sort_rank, + num_sort_ranks * sizeof(int64_t), + sizeof(int64_t), + num_segments_l, + cudaMemcpyDeviceToDevice, + stream)); thrust::reverse_iterator::iterator> iter_in( segment_diff_2d_ptr + num_segments_l * num_sort_ranks); thrust::reverse_iterator::iterator> iter_out( segment_diff_2d_scan_ptr + num_segments_l * num_sort_ranks); thrust::inclusive_scan( exec_policy, iter_in, iter_in + num_segments_l * num_sort_ranks, iter_out); - LegateCheckCUDA(cudaMemcpy2DAsync(send_left.ptr(0), - sizeof(int64_t), - segment_diff_2d_scan.ptr(0) + my_sort_rank, - num_sort_ranks * sizeof(int64_t), - sizeof(int64_t), - num_segments_l, - cudaMemcpyDeviceToDevice, - stream)); + CUNUMERIC_CHECK_CUDA(cudaMemcpy2DAsync(send_left.ptr(0), + sizeof(int64_t), + segment_diff_2d_scan.ptr(0) + my_sort_rank, + num_sort_ranks * sizeof(int64_t), + sizeof(int64_t), + num_segments_l, + cudaMemcpyDeviceToDevice, + stream)); segment_diff_2d.destroy(); segment_diff_2d_scan.destroy(); @@ -1212,7 +1212,7 @@ void rebalance_data(SegmentMergePiece& merge_buffer, recv_left_data.values.destroy(); recv_right_data.values.destroy(); } - LegateCheckCUDAStream(stream); + CUNUMERIC_CHECK_CUDA_STREAM(stream); } } @@ -1259,13 +1259,13 @@ void sample_sort_nccl_nd( { auto worker_count_d = create_buffer(1, legate::Memory::GPU_FB_MEM); size_t worker_count = (segment_size_l > 0 ? 1 : 0); - LegateCheckCUDA(cudaMemcpyAsync( + CUNUMERIC_CHECK_CUDA(cudaMemcpyAsync( worker_count_d.ptr(0), &worker_count, sizeof(int32_t), cudaMemcpyHostToDevice, stream)); CHECK_NCCL(ncclAllReduce( worker_count_d.ptr(0), worker_count_d.ptr(0), 1, ncclInt32, ncclSum, *comm, stream)); - LegateCheckCUDA(cudaMemcpyAsync( + CUNUMERIC_CHECK_CUDA(cudaMemcpyAsync( &worker_count, worker_count_d.ptr(0), sizeof(int32_t), cudaMemcpyDeviceToHost, stream)); - LegateCheckCUDA(cudaStreamSynchronize(stream)); + CUNUMERIC_CHECK_CUDA(cudaStreamSynchronize(stream)); if (worker_count < num_ranks) { const size_t number_sort_groups = num_ranks / num_sort_ranks; num_sort_ranks = worker_count / number_sort_groups; @@ -1313,7 +1313,7 @@ void sample_sort_nccl_nd( offset, num_sort_ranks, my_sort_rank); - LegateCheckCUDAStream(stream); + CUNUMERIC_CHECK_CUDA_STREAM(stream); } // AllGather does not work here as not all have the same amount! @@ -1322,11 +1322,11 @@ void sample_sort_nccl_nd( // allocate receive buffer const size_t aligned_count = get_16b_aligned_count(num_samples_l, sizeof(SegmentSample)); auto send_buffer = create_buffer>(aligned_count, legate::Memory::GPU_FB_MEM); - LegateCheckCUDA(cudaMemcpyAsync(send_buffer.ptr(0), - samples.ptr(offset), - sizeof(SegmentSample) * num_samples_l, - cudaMemcpyDeviceToDevice, - stream)); + CUNUMERIC_CHECK_CUDA(cudaMemcpyAsync(send_buffer.ptr(0), + samples.ptr(offset), + sizeof(SegmentSample) * num_samples_l, + cudaMemcpyDeviceToDevice, + stream)); auto recv_buffer = create_buffer>(aligned_count * num_sort_ranks, legate::Memory::GPU_FB_MEM); @@ -1353,11 +1353,11 @@ void sample_sort_nccl_nd( // copy back for (size_t r = 0; r < num_sort_ranks; r++) { if (r != my_sort_rank) { - LegateCheckCUDA(cudaMemcpyAsync(samples.ptr(num_samples_l * r), - recv_buffer.ptr(aligned_count * r), - sizeof(SegmentSample) * num_samples_l, - cudaMemcpyDeviceToDevice, - stream)); + CUNUMERIC_CHECK_CUDA(cudaMemcpyAsync(samples.ptr(num_samples_l * r), + recv_buffer.ptr(aligned_count * r), + sizeof(SegmentSample) * num_samples_l, + cudaMemcpyDeviceToDevice, + stream)); } } @@ -1365,7 +1365,7 @@ void sample_sort_nccl_nd( send_buffer.destroy(); recv_buffer.destroy(); - LegateCheckCUDAStream(stream); + CUNUMERIC_CHECK_CUDA_STREAM(stream); } ///////////////////////////////////////////////////////////////////////////////////////////////// @@ -1431,7 +1431,7 @@ void sample_sort_nccl_nd( compute_scan_per_rank<<>>( segment_blocks.ptr(0), size_send.ptr(0), num_segments_l, num_segments_l_aligned); - LegateCheckCUDAStream(stream); + CUNUMERIC_CHECK_CUDA_STREAM(stream); } // cleanup intermediate data structures @@ -1468,25 +1468,25 @@ void sample_sort_nccl_nd( Buffer size_recv_total = create_buffer(num_sort_ranks, legate::Memory::Z_COPY_MEM); { - LegateCheckCUDA(cudaMemcpy2DAsync(size_send_total.ptr(0), - 1 * sizeof(size_t), - size_send.ptr(num_segments_l), - num_segments_l_aligned * sizeof(size_t), - sizeof(int64_t), - num_sort_ranks, - cudaMemcpyDeviceToHost, - stream)); - LegateCheckCUDA(cudaMemcpy2DAsync(size_recv_total.ptr(0), - 1 * sizeof(size_t), - size_recv.ptr(num_segments_l), - num_segments_l_aligned * sizeof(size_t), - sizeof(int64_t), - num_sort_ranks, - cudaMemcpyDeviceToHost, - stream)); + CUNUMERIC_CHECK_CUDA(cudaMemcpy2DAsync(size_send_total.ptr(0), + 1 * sizeof(size_t), + size_send.ptr(num_segments_l), + num_segments_l_aligned * sizeof(size_t), + sizeof(int64_t), + num_sort_ranks, + cudaMemcpyDeviceToHost, + stream)); + CUNUMERIC_CHECK_CUDA(cudaMemcpy2DAsync(size_recv_total.ptr(0), + 1 * sizeof(size_t), + size_recv.ptr(num_segments_l), + num_segments_l_aligned * sizeof(size_t), + sizeof(int64_t), + num_sort_ranks, + cudaMemcpyDeviceToHost, + stream)); // need to sync as we share values in between host/device - LegateCheckCUDA(cudaStreamSynchronize(stream)); + CUNUMERIC_CHECK_CUDA(cudaStreamSynchronize(stream)); } // copy values into aligned send buffer @@ -1537,13 +1537,13 @@ void sample_sort_nccl_nd( segment_size_l, my_rank, num_sort_ranks); - LegateCheckCUDA(cudaStreamSynchronize(stream)); // needed before Z-copy destroy() + CUNUMERIC_CHECK_CUDA(cudaStreamSynchronize(stream)); // needed before Z-copy destroy() idc_send_buffers_ptr.destroy(); } else { - LegateCheckCUDA(cudaStreamSynchronize(stream)); // needed before Z-copy destroy() + CUNUMERIC_CHECK_CUDA(cudaStreamSynchronize(stream)); // needed before Z-copy destroy() } val_send_buffers_ptr.destroy(); - LegateCheckCUDAStream(stream); + CUNUMERIC_CHECK_CUDA_STREAM(stream); } local_sorted.values.destroy(); @@ -1573,7 +1573,7 @@ void sample_sort_nccl_nd( size_recv.ptr(r * num_segments_l_aligned), size_recv.ptr(r * num_segments_l_aligned) + num_segments_l + 1, size_recv.ptr(r * num_segments_l_aligned)); - LegateCheckCUDA( + CUNUMERIC_CHECK_CUDA( cudaMemsetAsync(merge_buffers[r].segments.ptr(0), 0, size * sizeof(size_t), stream)); const size_t num_blocks = (num_segments_l + THREADS_PER_BLOCK - 1) / THREADS_PER_BLOCK; assert(sizeof(unsigned long long int) == @@ -1597,7 +1597,7 @@ void sample_sort_nccl_nd( } } - LegateCheckCUDAStream(stream); + CUNUMERIC_CHECK_CUDA_STREAM(stream); } // communicate all2all (in sort dimension) @@ -1652,7 +1652,7 @@ void sample_sort_nccl_nd( idc_send_buffers[r].destroy(); } } - LegateCheckCUDAStream(stream); + CUNUMERIC_CHECK_CUDA_STREAM(stream); ///////////////////////////////////////////////////////////////////////////////////////////////// /////////////// Part 4: merge data @@ -1782,7 +1782,7 @@ struct SortImplBody { values_ptr = output.ptr(rect.lo); } } - LegateCheckCUDAStream(stream); + CUNUMERIC_CHECK_CUDA_STREAM(stream); if (volume > 0) { // sort data (locally) @@ -1795,7 +1795,7 @@ struct SortImplBody { stable, stream); } - LegateCheckCUDAStream(stream); + CUNUMERIC_CHECK_CUDA_STREAM(stream); if (need_distributed_sort) { if (is_index_space) { @@ -1849,7 +1849,7 @@ struct SortImplBody { local_sorted.values.destroy(); } - LegateCheckCUDAStream(stream); + CUNUMERIC_CHECK_CUDA_STREAM(stream); } }; diff --git a/src/cunumeric/sort/sort.h b/src/cunumeric/sort/sort.h index bb63ae6c12..3024a41b6e 100644 --- a/src/cunumeric/sort/sort.h +++ b/src/cunumeric/sort/sort.h @@ -98,10 +98,10 @@ class SortTask : public CuNumericTask { public: static void cpu_variant(legate::TaskContext context); -#if LegateDefined(LEGATE_USE_OPENMP) +#if LEGATE_DEFINED(LEGATE_USE_OPENMP) static void omp_variant(legate::TaskContext context); #endif -#if LegateDefined(LEGATE_USE_CUDA) +#if LEGATE_DEFINED(LEGATE_USE_CUDA) static void gpu_variant(legate::TaskContext context); #endif }; diff --git a/src/cunumeric/sort/thrust_sort.cuh b/src/cunumeric/sort/thrust_sort.cuh index fb9bfcd5c9..82bbbf0ef6 100644 --- a/src/cunumeric/sort/thrust_sort.cuh +++ b/src/cunumeric/sort/thrust_sort.cuh @@ -48,12 +48,12 @@ void thrust_local_sort(const VAL* values_in, if (values_in != values_out) { // not in-place --> need a copy - LegateCheckCUDA(cudaMemcpyAsync( + CUNUMERIC_CHECK_CUDA(cudaMemcpyAsync( values_out, values_in, sizeof(VAL) * volume, cudaMemcpyDeviceToDevice, stream)); } if (indices_in != indices_out) { // not in-place --> need a copy - LegateCheckCUDA(cudaMemcpyAsync( + CUNUMERIC_CHECK_CUDA(cudaMemcpyAsync( indices_out, values_in, sizeof(int64_t) * volume, cudaMemcpyDeviceToDevice, stream)); } diff --git a/src/cunumeric/stat/bincount.cu b/src/cunumeric/stat/bincount.cu index 1a7d15fb48..9bdf128c88 100644 --- a/src/cunumeric/stat/bincount.cu +++ b/src/cunumeric/stat/bincount.cu @@ -183,7 +183,7 @@ struct BincountImplBody { bincount_kernel_rd_global <<>>(lhs, rhs, volume, rect.lo); } - LegateCheckCUDAStream(stream); + CUNUMERIC_CHECK_CUDA_STREAM(stream); } void operator()(AccessorRD, false, 1> lhs, @@ -212,7 +212,7 @@ struct BincountImplBody { weighted_bincount_kernel_rd_global <<>>(lhs, rhs, weights, volume, rect.lo); } - LegateCheckCUDAStream(stream); + CUNUMERIC_CHECK_CUDA_STREAM(stream); } }; diff --git a/src/cunumeric/stat/bincount.h b/src/cunumeric/stat/bincount.h index feead655d3..9052563361 100644 --- a/src/cunumeric/stat/bincount.h +++ b/src/cunumeric/stat/bincount.h @@ -33,10 +33,10 @@ class BincountTask : public CuNumericTask { public: static void cpu_variant(legate::TaskContext context); -#if LegateDefined(LEGATE_USE_OPENMP) +#if LEGATE_DEFINED(LEGATE_USE_OPENMP) static void omp_variant(legate::TaskContext context); #endif -#if LegateDefined(LEGATE_USE_CUDA) +#if LEGATE_DEFINED(LEGATE_USE_CUDA) static void gpu_variant(legate::TaskContext context); #endif }; diff --git a/src/cunumeric/stat/histogram.cuh b/src/cunumeric/stat/histogram.cuh index 1a7acf7258..31f850e745 100644 --- a/src/cunumeric/stat/histogram.cuh +++ b/src/cunumeric/stat/histogram.cuh @@ -108,7 +108,7 @@ template struct sync_policy_t>> { sync_policy_t() {} - void operator()(cudaStream_t stream) { LegateCheckCUDAStream(stream); } + void operator()(cudaStream_t stream) { CUNUMERIC_CHECK_CUDA_STREAM(stream); } }; } // namespace detail diff --git a/src/cunumeric/stat/histogram.h b/src/cunumeric/stat/histogram.h index f513dc72e9..867f54ff73 100644 --- a/src/cunumeric/stat/histogram.h +++ b/src/cunumeric/stat/histogram.h @@ -33,10 +33,10 @@ class HistogramTask : public CuNumericTask { public: static void cpu_variant(legate::TaskContext context); -#if LegateDefined(LEGATE_USE_OPENMP) +#if LEGATE_DEFINED(LEGATE_USE_OPENMP) static void omp_variant(legate::TaskContext context); #endif -#if LegateDefined(LEGATE_USE_CUDA) +#if LEGATE_DEFINED(LEGATE_USE_CUDA) static void gpu_variant(legate::TaskContext context); #endif }; diff --git a/src/cunumeric/stat/histogram_cpu.h b/src/cunumeric/stat/histogram_cpu.h index 2aea9e17ae..63a13668c0 100644 --- a/src/cunumeric/stat/histogram_cpu.h +++ b/src/cunumeric/stat/histogram_cpu.h @@ -29,7 +29,7 @@ #include #include -#include +#include "cunumeric/cuda_help.h" #include "cunumeric/stat/histogram_gen.h" namespace cunumeric { diff --git a/src/cunumeric/ternary/where.cu b/src/cunumeric/ternary/where.cu index b23509b82e..4181a0bd00 100644 --- a/src/cunumeric/ternary/where.cu +++ b/src/cunumeric/ternary/where.cu @@ -71,7 +71,7 @@ struct WhereImplBody { generic_kernel<<>>( volume, out, mask, in1, in2, pitches, rect); } - LegateCheckCUDAStream(stream); + CUNUMERIC_CHECK_CUDA_STREAM(stream); } }; diff --git a/src/cunumeric/ternary/where.h b/src/cunumeric/ternary/where.h index d8bbb605ed..442bbdeecd 100644 --- a/src/cunumeric/ternary/where.h +++ b/src/cunumeric/ternary/where.h @@ -33,10 +33,10 @@ class WhereTask : public CuNumericTask { public: static void cpu_variant(legate::TaskContext context); -#if LegateDefined(LEGATE_USE_OPENMP) +#if LEGATE_DEFINED(LEGATE_USE_OPENMP) static void omp_variant(legate::TaskContext context); #endif -#if LegateDefined(LEGATE_USE_CUDA) +#if LEGATE_DEFINED(LEGATE_USE_CUDA) static void gpu_variant(legate::TaskContext context); #endif }; diff --git a/src/cunumeric/ternary/where_template.inl b/src/cunumeric/ternary/where_template.inl index 8d87158fd5..66df0a14af 100644 --- a/src/cunumeric/ternary/where_template.inl +++ b/src/cunumeric/ternary/where_template.inl @@ -48,7 +48,7 @@ struct WhereImpl { auto in1 = args.in1.read_accessor(rect); auto in2 = args.in2.read_accessor(rect); -#if !LegateDefined(LEGATE_BOUNDS_CHECKS) +#if !LEGATE_DEFINED(LEGATE_BOUNDS_CHECKS) // Check to see if this is dense or not bool dense = out.accessor.is_dense_row_major(rect) && in1.accessor.is_dense_row_major(rect) && in2.accessor.is_dense_row_major(rect) && mask.accessor.is_dense_row_major(rect); diff --git a/src/cunumeric/transform/flip.cu b/src/cunumeric/transform/flip.cu index 6dd5b912b4..c17fce9fd7 100644 --- a/src/cunumeric/transform/flip.cu +++ b/src/cunumeric/transform/flip.cu @@ -66,7 +66,7 @@ struct FlipImplBody { auto stream = get_cached_stream(); flip_kernel<<>>( volume, out, in, pitches, rect, gpu_axes, num_axes); - LegateCheckCUDAStream(stream); + CUNUMERIC_CHECK_CUDA_STREAM(stream); } }; diff --git a/src/cunumeric/transform/flip.h b/src/cunumeric/transform/flip.h index 009d131885..3833d86b5d 100644 --- a/src/cunumeric/transform/flip.h +++ b/src/cunumeric/transform/flip.h @@ -32,10 +32,10 @@ class FlipTask : public CuNumericTask { public: static void cpu_variant(legate::TaskContext context); -#if LegateDefined(LEGATE_USE_OPENMP) +#if LEGATE_DEFINED(LEGATE_USE_OPENMP) static void omp_variant(legate::TaskContext context); #endif -#if LegateDefined(LEGATE_USE_CUDA) +#if LEGATE_DEFINED(LEGATE_USE_CUDA) static void gpu_variant(legate::TaskContext context); #endif }; diff --git a/src/cunumeric/unary/convert.cu b/src/cunumeric/unary/convert.cu index 78e6799d3d..62422732ba 100644 --- a/src/cunumeric/unary/convert.cu +++ b/src/cunumeric/unary/convert.cu @@ -68,7 +68,7 @@ struct ConvertImplBody { generic_kernel<<>>( volume, func, out, in, pitches, rect); } - LegateCheckCUDAStream(stream); + CUNUMERIC_CHECK_CUDA_STREAM(stream); } }; diff --git a/src/cunumeric/unary/convert.h b/src/cunumeric/unary/convert.h index 2ef7904772..f72b05f92a 100644 --- a/src/cunumeric/unary/convert.h +++ b/src/cunumeric/unary/convert.h @@ -33,10 +33,10 @@ class ConvertTask : public CuNumericTask { public: static void cpu_variant(legate::TaskContext context); -#if LegateDefined(LEGATE_USE_OPENMP) +#if LEGATE_DEFINED(LEGATE_USE_OPENMP) static void omp_variant(legate::TaskContext context); #endif -#if LegateDefined(LEGATE_USE_CUDA) +#if LEGATE_DEFINED(LEGATE_USE_CUDA) static void gpu_variant(legate::TaskContext context); #endif }; diff --git a/src/cunumeric/unary/convert_template.inl b/src/cunumeric/unary/convert_template.inl index f69be96eac..fd6f42f723 100644 --- a/src/cunumeric/unary/convert_template.inl +++ b/src/cunumeric/unary/convert_template.inl @@ -49,7 +49,7 @@ struct ConvertImpl { auto out = args.out.write_accessor(rect); auto in = args.in.read_accessor(rect); -#if !LegateDefined(LEGATE_BOUNDS_CHECKS) +#if !LEGATE_DEFINED(LEGATE_BOUNDS_CHECKS) // Check to see if this is dense or not bool dense = out.accessor.is_dense_row_major(rect) && in.accessor.is_dense_row_major(rect); #else diff --git a/src/cunumeric/unary/scalar_unary_red.h b/src/cunumeric/unary/scalar_unary_red.h index 79c528969d..0f6d9e954b 100644 --- a/src/cunumeric/unary/scalar_unary_red.h +++ b/src/cunumeric/unary/scalar_unary_red.h @@ -37,10 +37,10 @@ class ScalarUnaryRedTask : public CuNumericTask { public: static void cpu_variant(legate::TaskContext context); -#if LegateDefined(LEGATE_USE_OPENMP) +#if LEGATE_DEFINED(LEGATE_USE_OPENMP) static void omp_variant(legate::TaskContext context); #endif -#if LegateDefined(LEGATE_USE_CUDA) +#if LEGATE_DEFINED(LEGATE_USE_CUDA) static void gpu_variant(legate::TaskContext context); #endif }; diff --git a/src/cunumeric/unary/scalar_unary_red_template.inl b/src/cunumeric/unary/scalar_unary_red_template.inl index ff7f39ae1a..20c8e1ffbf 100644 --- a/src/cunumeric/unary/scalar_unary_red_template.inl +++ b/src/cunumeric/unary/scalar_unary_red_template.inl @@ -74,7 +74,7 @@ struct ScalarUnaryRed { if constexpr (HAS_WHERE) { where = args.where.read_accessor(rect); } -#if !LegateDefined(LEGATE_BOUNDS_CHECKS) +#if !LEGATE_DEFINED(LEGATE_BOUNDS_CHECKS) // Check to see if this is dense or not if (in.accessor.is_dense_row_major(rect)) { dense = true; @@ -148,7 +148,7 @@ struct ScalarUnaryRed { void execute() const noexcept { auto identity = LG_OP::identity; -#if !LegateDefined(LEGATE_BOUNDS_CHECKS) +#if !LEGATE_DEFINED(LEGATE_BOUNDS_CHECKS) // The constexpr if here prevents the DenseReduction from being instantiated for GPU kernels // which limits compile times and binary sizes. if constexpr (KIND != VariantKind::GPU) { diff --git a/src/cunumeric/unary/unary_op.cu b/src/cunumeric/unary/unary_op.cu index 4808f96914..fb89f8299d 100644 --- a/src/cunumeric/unary/unary_op.cu +++ b/src/cunumeric/unary/unary_op.cu @@ -95,7 +95,7 @@ struct UnaryOpImplBody { generic_kernel<<>>( volume, func, out, in, pitches, rect); } - LegateCheckCUDAStream(stream); + CUNUMERIC_CHECK_CUDA_STREAM(stream); } }; @@ -117,7 +117,7 @@ struct PointCopyImplBody { } else { generic_copy_kernel<<>>(volume, out, in, pitches, rect); } - LegateCheckCUDAStream(stream); + CUNUMERIC_CHECK_CUDA_STREAM(stream); } }; @@ -183,7 +183,7 @@ struct MultiOutUnaryOpImplBody { generic_kernel_multiout<<>>( volume, func, lhs, rhs1, rhs2, pitches, rect); } - LegateCheckCUDAStream(stream); + CUNUMERIC_CHECK_CUDA_STREAM(stream); } }; diff --git a/src/cunumeric/unary/unary_op.h b/src/cunumeric/unary/unary_op.h index b6b4cafade..cf87c982a8 100644 --- a/src/cunumeric/unary/unary_op.h +++ b/src/cunumeric/unary/unary_op.h @@ -41,10 +41,10 @@ class UnaryOpTask : public CuNumericTask { public: static void cpu_variant(legate::TaskContext context); -#if LegateDefined(LEGATE_USE_OPENMP) +#if LEGATE_DEFINED(LEGATE_USE_OPENMP) static void omp_variant(legate::TaskContext context); #endif -#if LegateDefined(LEGATE_USE_CUDA) +#if LEGATE_DEFINED(LEGATE_USE_CUDA) static void gpu_variant(legate::TaskContext context); #endif }; diff --git a/src/cunumeric/unary/unary_op_template.inl b/src/cunumeric/unary/unary_op_template.inl index dcf7cec144..7c12cb9f78 100644 --- a/src/cunumeric/unary/unary_op_template.inl +++ b/src/cunumeric/unary/unary_op_template.inl @@ -54,7 +54,7 @@ struct UnaryOpImpl { auto out = args.out.write_accessor(rect); auto in = args.in.read_accessor(rect); -#if !LegateDefined(LEGATE_BOUNDS_CHECKS) +#if !LEGATE_DEFINED(LEGATE_BOUNDS_CHECKS) // Check to see if this is dense or not bool dense = out.accessor.is_dense_row_major(rect) && in.accessor.is_dense_row_major(rect); #else @@ -98,7 +98,7 @@ struct MultiOutUnaryOpImpl { auto rhs1 = args.in.read_accessor(rect); auto rhs2 = args.out2.write_accessor(rect); -#if !LegateDefined(LEGATE_BOUNDS_CHECKS) +#if !LEGATE_DEFINED(LEGATE_BOUNDS_CHECKS) // Check to see if this is dense or not bool dense = lhs.accessor.is_dense_row_major(rect) && rhs1.accessor.is_dense_row_major(rect) && rhs2.accessor.is_dense_row_major(rect); @@ -152,7 +152,7 @@ struct UnaryCopyImpl { auto out = args.out.write_accessor(rect); auto in = args.in.read_accessor(rect); -#if !LegateDefined(LEGATE_BOUNDS_CHECKS) +#if !LEGATE_DEFINED(LEGATE_BOUNDS_CHECKS) // Check to see if this is dense or not bool dense = out.accessor.is_dense_row_major(rect) && in.accessor.is_dense_row_major(rect); #else diff --git a/src/cunumeric/unary/unary_red.cu b/src/cunumeric/unary/unary_red.cu index 2e660a9d14..549d314600 100644 --- a/src/cunumeric/unary/unary_red.cu +++ b/src/cunumeric/unary/unary_red.cu @@ -269,7 +269,7 @@ static void __device__ __forceinline__ collapse_dims(LHS& result, } #endif - if (LegateDefined(LEGATE_BOUNDS_CHECKS)) { + if (LEGATE_DEFINED(LEGATE_BOUNDS_CHECKS)) { // Note: this isn't necessary because we know that the affine transformation on the output // accessor will ignore coordinates of the collapsed dimension. However, Legion's bounds checks // want the accessor to honor the sub-rectangle passed when it was created, so we need to @@ -353,7 +353,7 @@ struct UnaryRedImplBody { blocks.compute_maximum_concurrency(reinterpret_cast(Kernel)); Kernel<<>>( lhs, rhs, where, LG_OP::identity, blocks, rect, collapsed_dim); - LegateCheckCUDAStream(stream); + CUNUMERIC_CHECK_CUDA_STREAM(stream); } }; diff --git a/src/cunumeric/unary/unary_red.h b/src/cunumeric/unary/unary_red.h index c3e55f824f..09f60bc2e8 100644 --- a/src/cunumeric/unary/unary_red.h +++ b/src/cunumeric/unary/unary_red.h @@ -35,10 +35,10 @@ class UnaryRedTask : public CuNumericTask { public: static void cpu_variant(legate::TaskContext context); -#if LegateDefined(LEGATE_USE_OPENMP) +#if LEGATE_DEFINED(LEGATE_USE_OPENMP) static void omp_variant(legate::TaskContext context); #endif -#if LegateDefined(LEGATE_USE_CUDA) +#if LEGATE_DEFINED(LEGATE_USE_CUDA) static void gpu_variant(legate::TaskContext context); #endif }; diff --git a/src/cunumeric/utilities/repartition.cu b/src/cunumeric/utilities/repartition.cu index be31cbdcfc..d4bda0d6c5 100644 --- a/src/cunumeric/utilities/repartition.cu +++ b/src/cunumeric/utilities/repartition.cu @@ -439,7 +439,7 @@ std::tuple, size_t, size_t> repartition_matrix_2dbc(const VAL* input recv_info.ptr(r * stored_size_per_rank), stored_size_per_rank, ncclUint64, r, *comm, stream)); } CHECK_NCCL(ncclGroupEnd()); - LegateCheckCUDA(cudaStreamSynchronize(stream)); // need Z-copy synchronized to Host + CUNUMERIC_CHECK_CUDA(cudaStreamSynchronize(stream)); // need Z-copy synchronized to Host // allocate send/recv buffer std::vector> send_buffers; @@ -499,11 +499,11 @@ std::tuple, size_t, size_t> repartition_matrix_2dbc(const VAL* input p_c, tile_r, tile_c); - LegateCheckCUDA(cudaStreamSynchronize(stream)); + CUNUMERIC_CHECK_CUDA(cudaStreamSynchronize(stream)); send_buffers_ptr.destroy(); } - LegateCheckCUDAStream(stream); + CUNUMERIC_CHECK_CUDA_STREAM(stream); // all2all data CHECK_NCCL(ncclGroupStart()); @@ -550,11 +550,11 @@ std::tuple, size_t, size_t> repartition_matrix_2dbc(const VAL* input tile_c, (size_t)nccl_rank, num_ranks); - LegateCheckCUDA(cudaStreamSynchronize(stream)); + CUNUMERIC_CHECK_CUDA(cudaStreamSynchronize(stream)); recv_buffers_ptr.destroy(); } - LegateCheckCUDAStream(stream); + CUNUMERIC_CHECK_CUDA_STREAM(stream); recv_info.destroy(); for (auto&& buf : recv_buffers) { @@ -610,7 +610,7 @@ void repartition_matrix_block( offsets[2 * local_rank + 1] = num_target_cols > 0 ? target_offset_c + num_target_cols : 0; CHECK_NCCL( ncclAllGather(offsets.ptr(2 * local_rank), offsets.ptr(0), 2, ncclUint64, *comm, stream)); - LegateCheckCUDA(cudaStreamSynchronize(stream)); + CUNUMERIC_CHECK_CUDA(cudaStreamSynchronize(stream)); // re-arrange so that all row offsets come first for (size_t i = 1; i < num_ranks; i += 2) { @@ -805,7 +805,7 @@ void repartition_matrix_block( tile_r, tile_c); - LegateCheckCUDA(cudaStreamSynchronize(stream)); + CUNUMERIC_CHECK_CUDA(cudaStreamSynchronize(stream)); send_buffers_ptr.destroy(); } // we can destroy the input once we distributed data into the buffers @@ -904,11 +904,11 @@ void repartition_matrix_block( p_c, tile_r, tile_c); - LegateCheckCUDA(cudaStreamSynchronize(stream)); + CUNUMERIC_CHECK_CUDA(cudaStreamSynchronize(stream)); recv_buffers_ptr.destroy(); } - LegateCheckCUDAStream(stream); + CUNUMERIC_CHECK_CUDA_STREAM(stream); // cleanup offsets_r.destroy(); From 2a1a1bc725b470410b1d05575af0eb85e6a4eea8 Mon Sep 17 00:00:00 2001 From: Jacob Faibussowitsch Date: Fri, 7 Jun 2024 00:02:54 -0400 Subject: [PATCH 214/462] Fix include order fiasco (#220) --- src/cunumeric/cuda_help.h | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/cunumeric/cuda_help.h b/src/cunumeric/cuda_help.h index 3f66652164..c218eb3e3c 100644 --- a/src/cunumeric/cuda_help.h +++ b/src/cunumeric/cuda_help.h @@ -19,7 +19,6 @@ #include "legate.h" #include "core/cuda/stream_pool.h" #include "cunumeric/arg.h" -#include "cunumeric/device_scalar_reduction_buffer.h" #include #include #if LEGATE_DEFINED(CUNUMERIC_USE_CUSOLVERMP) @@ -97,6 +96,9 @@ #define MIN(x, y) (((x) < (y)) ? (x) : (y)) #endif +// Must go here since it depends on CUNUMERIC_CHECK_CUDA(), which is defined in this header... +#include "cunumeric/device_scalar_reduction_buffer.h" + namespace cunumeric { template From bb350362772523811415c691af05a600fb016567 Mon Sep 17 00:00:00 2001 From: Wonchan Lee Date: Fri, 7 Jun 2024 16:08:33 -0700 Subject: [PATCH 215/462] Fixing build issues (#223) * Fixing build issues * Bump the Legate commit hash to run CI --- cmake/versions.json | 2 +- src/cunumeric/matrix/qr_template.inl | 6 +++--- src/cunumeric/matrix/svd_template.inl | 10 +++++----- src/cunumeric/stat/histogram_cpu.h | 1 - 4 files changed, 9 insertions(+), 10 deletions(-) diff --git a/cmake/versions.json b/cmake/versions.json index b9024a7f15..d21a39d3c7 100644 --- a/cmake/versions.json +++ b/cmake/versions.json @@ -7,7 +7,7 @@ "git_url" : "git@github.com:nv-legate/legate.core.internal.git", "git_shallow": false, "always_download": false, - "git_tag" : "0ab2504c03850c91c4dabc553f80016804aeaef2" + "git_tag" : "7c12a14c0cd7ffb1972c80126836eec9e96ba77d" } } } diff --git a/src/cunumeric/matrix/qr_template.inl b/src/cunumeric/matrix/qr_template.inl index 637e325325..2040b555b6 100644 --- a/src/cunumeric/matrix/qr_template.inl +++ b/src/cunumeric/matrix/qr_template.inl @@ -93,9 +93,9 @@ struct QrImpl { template static void qr_template(TaskContext& context) { - auto& a_array = context.inputs()[0]; - auto& q_array = context.outputs()[0]; - auto& r_array = context.outputs()[1]; + auto a_array = context.input(0); + auto q_array = context.output(0); + auto r_array = context.output(1); type_dispatch(a_array.type().code(), QrImpl{}, a_array, q_array, r_array); } diff --git a/src/cunumeric/matrix/svd_template.inl b/src/cunumeric/matrix/svd_template.inl index 4b9393282b..c694535a93 100644 --- a/src/cunumeric/matrix/svd_template.inl +++ b/src/cunumeric/matrix/svd_template.inl @@ -116,11 +116,11 @@ struct SvdImpl { template static void svd_template(TaskContext& context) { - auto& a_array = context.inputs()[0]; - auto& u_array = context.outputs()[0]; - auto& s_array = context.outputs()[1]; - auto& vh_array = context.outputs()[2]; + auto a_array = context.input(0); + auto u_array = context.output(0); + auto s_array = context.output(1); + auto vh_array = context.output(2); type_dispatch(a_array.type().code(), SvdImpl{}, a_array, u_array, s_array, vh_array); } -} // namespace cunumeric \ No newline at end of file +} // namespace cunumeric diff --git a/src/cunumeric/stat/histogram_cpu.h b/src/cunumeric/stat/histogram_cpu.h index 63a13668c0..15f49622ea 100644 --- a/src/cunumeric/stat/histogram_cpu.h +++ b/src/cunumeric/stat/histogram_cpu.h @@ -29,7 +29,6 @@ #include #include -#include "cunumeric/cuda_help.h" #include "cunumeric/stat/histogram_gen.h" namespace cunumeric { From fb7b80ea5221bcb609c51064332c60b61b3506bb Mon Sep 17 00:00:00 2001 From: Wonchan Lee Date: Mon, 10 Jun 2024 12:18:55 -0700 Subject: [PATCH 216/462] Revert a recent fix, which wasn't the right fix (#224) --- src/cunumeric/stat/histogram_cpu.h | 1 + 1 file changed, 1 insertion(+) diff --git a/src/cunumeric/stat/histogram_cpu.h b/src/cunumeric/stat/histogram_cpu.h index 15f49622ea..63a13668c0 100644 --- a/src/cunumeric/stat/histogram_cpu.h +++ b/src/cunumeric/stat/histogram_cpu.h @@ -29,6 +29,7 @@ #include #include +#include "cunumeric/cuda_help.h" #include "cunumeric/stat/histogram_gen.h" namespace cunumeric { From 76060c07ce7e04b530dd3f89d7d292c866ac4b8c Mon Sep 17 00:00:00 2001 From: Marcin Zalewski Date: Mon, 10 Jun 2024 13:44:55 -0700 Subject: [PATCH 217/462] Fix CI issues (#222) --- .github/workflows/gh-build-and-test.yml | 16 ++++++++-------- conda/conda-build/meta.yaml | 3 --- 2 files changed, 8 insertions(+), 11 deletions(-) diff --git a/.github/workflows/gh-build-and-test.yml b/.github/workflows/gh-build-and-test.yml index 60d8b36d8c..f10b087bad 100644 --- a/.github/workflows/gh-build-and-test.yml +++ b/.github/workflows/gh-build-and-test.yml @@ -41,7 +41,7 @@ jobs: needs: setup-build name: "Build (${{ inputs.platform }}, ${{ inputs.target-device }}, ${{ inputs.build-type }})" uses: - nv-legate/legate-gh-ci/.github/workflows/gh-build.yml@v1.4 + nv-legate/legate-gh-ci/.github/workflows/gh-build.yml@v1.7 with: client-repo: ${{ github.event.repository.name }} target-device: ${{ inputs.target-device }} @@ -51,7 +51,7 @@ jobs: platform: ${{ inputs.platform }} dependencies-file: "cmake/versions.json" dependencies-workflow: ${{ inputs.dependencies-workflow }} - legate-gh-ci-tag: "v1.4" + legate-gh-ci-tag: "v1.7" build-mode: "" ucx-enabled: false upload-enabled: ${{ inputs.upload-enabled }} @@ -63,14 +63,14 @@ jobs: if: ${{ github.repository_owner == 'nv-legate' && contains(github.workflow, 'release') && inputs.upload-enabled == true }} name: Upload package to Server uses: - nv-legate/legate-gh-ci/.github/workflows/gh-upload.yml@v1.4 + nv-legate/legate-gh-ci/.github/workflows/gh-upload.yml@v1.7 with: client-repo: ${{ github.event.repository.name }} build-type: ${{ inputs.build-type }} name: Upload package to Server target-device: ${{ inputs.target-device }} platform: ${{ inputs.platform }} - legate-gh-ci-tag: "v1.4" + legate-gh-ci-tag: "v1.7" build-mode: "" ucx-enabled: false upload-enabled: ${{ inputs.upload-enabled }} @@ -147,7 +147,7 @@ jobs: matrix: ${{fromJson(needs.setup-test.outputs.matrix)}} uses: - nv-legate/legate-gh-ci/.github/workflows/gh-test-within-container.yml@v1.4 + nv-legate/legate-gh-ci/.github/workflows/gh-test-within-container.yml@v1.7 with: client-repo: ${{ github.event.repository.name }} build-type: ${{ inputs.build-type }} @@ -157,7 +157,7 @@ jobs: has-gpu: ${{ matrix.runner.type == 'gpu' }} test-options: ${{ matrix.test-config.test-options }} platform: ${{ inputs.platform }} - legate-gh-ci-tag: "v1.4" + legate-gh-ci-tag: "v1.7" build-mode: "" ucx-enabled: false upload-enabled: ${{ inputs.upload-enabled }} @@ -168,14 +168,14 @@ jobs: name: Update Test status on Server if: ${{ (github.repository_owner == 'nv-legate') && contains(github.workflow, 'Nightly') && (inputs.upload-enabled == true) }} uses: - nv-legate/legate-gh-ci/.github/workflows/gh-upload.yml@v1.4 + nv-legate/legate-gh-ci/.github/workflows/gh-upload.yml@v1.7 with: client-repo: ${{ github.event.repository.name }} build-type: ${{ inputs.build-type }} name: UpdateTestStatus target-device: ${{ inputs.target-device }} platform: ${{ inputs.platform }} - legate-gh-ci-tag: "v1.4" + legate-gh-ci-tag: "v1.7" build-mode: "" ucx-enabled: false upload-enabled: true diff --git a/conda/conda-build/meta.yaml b/conda/conda-build/meta.yaml index 28c68688b2..9dedb81e5b 100644 --- a/conda/conda-build/meta.yaml +++ b/conda/conda-build/meta.yaml @@ -86,9 +86,6 @@ build: - SCCACHE_S3_KEY_PREFIX - AWS_ACCESS_KEY_ID - AWS_SECRET_ACCESS_KEY - - CMAKE_C_COMPILER_LAUNCHER - - CMAKE_CXX_COMPILER_LAUNCHER - - CMAKE_CUDA_COMPILER_LAUNCHER {% if not gpu_enabled_bool %} - CPU_ONLY=1 track_features: From 1ae7bccd2767d525b9ae95f6e4c20a531082330a Mon Sep 17 00:00:00 2001 From: Jiakun Yan Date: Mon, 10 Jun 2024 16:44:57 -0700 Subject: [PATCH 218/462] Fix compilation errors with cuda_help.h (#225) * fix compilation errors with cuda_help.h * cuda_help.h: wrap check_* in namespace cunumeric * fix cuda_help: add cunumeric:: in the macro --------- Co-authored-by: Wonchan Lee --- src/cunumeric/cuda_help.h | 292 +++++++++++++++++++------------------- 1 file changed, 148 insertions(+), 144 deletions(-) diff --git a/src/cunumeric/cuda_help.h b/src/cunumeric/cuda_help.h index c218eb3e3c..502b160b5d 100644 --- a/src/cunumeric/cuda_help.h +++ b/src/cunumeric/cuda_help.h @@ -37,46 +37,166 @@ #define COOPERATIVE_THREADS 256 #define COOPERATIVE_CTAS_PER_SM 4 -#define CHECK_CUBLAS(expr) \ - do { \ - cublasStatus_t __result__ = (expr); \ - check_cublas(__result__, __FILE__, __LINE__); \ +namespace cunumeric { + +__host__ inline void check_cuda(cudaError_t error, const char* file, int line) +{ + if (error != cudaSuccess) { + fprintf(stderr, + "Internal CUDA failure with error %s (%s) in file %s at line %d\n", + cudaGetErrorString(error), + cudaGetErrorName(error), + file, + line); +#ifdef DEBUG_CUNUMERIC + assert(false); +#else + exit(error); +#endif + } +} + +__host__ inline void check_cublas(cublasStatus_t status, const char* file, int line) +{ + if (status != CUBLAS_STATUS_SUCCESS) { + fprintf(stderr, + "Internal cuBLAS failure with error code %d in file %s at line %d\n", + status, + file, + line); +#ifdef DEBUG_CUNUMERIC + assert(false); +#else + exit(status); +#endif + } +} + +__host__ inline void check_cufft(cufftResult result, const char* file, int line) +{ + if (result != CUFFT_SUCCESS) { + fprintf(stderr, + "Internal cuFFT failure with error code %d in file %s at line %d\n", + result, + file, + line); +#ifdef DEBUG_CUNUMERIC + assert(false); +#else + exit(result); +#endif + } +} + +__host__ inline void check_cusolver(cusolverStatus_t status, const char* file, int line) +{ + if (status != CUSOLVER_STATUS_SUCCESS) { + fprintf(stderr, + "Internal cuSOLVER failure with error code %d in file %s at line %d\n", + status, + file, + line); +#ifdef DEBUG_CUNUMERIC + assert(false); +#else + exit(status); +#endif + } +} + +#if LEGATE_DEFINED(CUNUMERIC_USE_CUSOLVERMP) +__host__ inline void check_cal(calError_t status, const char* file, int line) +{ + if (status != CAL_OK) { + fprintf(stderr, + "Internal libcal failure with error code %d in file %s at line %d\n", + status, + file, + line); +#ifdef DEBUG_CUNUMERIC + assert(false); +#else + exit(status); +#endif + } +} +#endif + +__host__ inline void check_cutensor(cutensorStatus_t result, const char* file, int line) +{ + if (result != CUTENSOR_STATUS_SUCCESS) { + fprintf(stderr, + "Internal Legate CUTENSOR failure with error %s (%d) in file %s at line %d\n", + cutensorGetErrorString(result), + result, + file, + line); +#ifdef DEBUG_CUNUMERIC + assert(false); +#else + exit(result); +#endif + } +} + +__host__ inline void check_nccl(ncclResult_t error, const char* file, int line) +{ + if (error != ncclSuccess) { + fprintf(stderr, + "Internal NCCL failure with error %s in file %s at line %d\n", + ncclGetErrorString(error), + file, + line); +#ifdef DEBUG_CUNUMERIC + assert(false); +#else + exit(error); +#endif + } +} + +} // namespace cunumeric + +#define CHECK_CUBLAS(expr) \ + do { \ + cublasStatus_t __result__ = (expr); \ + cunumeric::check_cublas(__result__, __FILE__, __LINE__); \ } while (false) -#define CHECK_CUFFT(expr) \ - do { \ - cufftResult __result__ = (expr); \ - check_cufft(__result__, __FILE__, __LINE__); \ +#define CHECK_CUFFT(expr) \ + do { \ + cufftResult __result__ = (expr); \ + cunumeric::check_cufft(__result__, __FILE__, __LINE__); \ } while (false) -#define CHECK_CUSOLVER(expr) \ - do { \ - cusolverStatus_t __result__ = (expr); \ - check_cusolver(__result__, __FILE__, __LINE__); \ +#define CHECK_CUSOLVER(expr) \ + do { \ + cusolverStatus_t __result__ = (expr); \ + cunumeric::check_cusolver(__result__, __FILE__, __LINE__); \ } while (false) -#define CHECK_CAL(expr) \ - do { \ - calError_t __result__ = (expr); \ - check_cal(__result__, __FILE__, __LINE__); \ +#define CHECK_CAL(expr) \ + do { \ + calError_t __result__ = (expr); \ + cunumeric::check_cal(__result__, __FILE__, __LINE__); \ } while (false) -#define CHECK_CUTENSOR(expr) \ - do { \ - cutensorStatus_t __result__ = (expr); \ - check_cutensor(__result__, __FILE__, __LINE__); \ +#define CHECK_CUTENSOR(expr) \ + do { \ + cutensorStatus_t __result__ = (expr); \ + cunumeric::check_cutensor(__result__, __FILE__, __LINE__); \ } while (false) -#define CHECK_NCCL(expr) \ - do { \ - ncclResult_t result = (expr); \ - check_nccl(result, __FILE__, __LINE__); \ +#define CHECK_NCCL(...) \ + do { \ + ncclResult_t __result__ = (__VA_ARGS__); \ + cunumeric::check_nccl(__result__, __FILE__, __LINE__); \ } while (false) -#define CUNUMERIC_CHECK_CUDA(...) \ - do { \ - cudaError_t result = __VA_ARGS__; \ - check_cuda(result, __FILE__, __LINE__); \ +#define CUNUMERIC_CHECK_CUDA(...) \ + do { \ + cudaError_t __result__ = (__VA_ARGS__); \ + cunumeric::check_cuda(__result__, __FILE__, __LINE__); \ } while (false) #ifdef DEBUG_CUNUMERIC @@ -197,122 +317,6 @@ cusolverMpHandle_t get_cusolvermp(); cutensorHandle_t* get_cutensor(); cufftContext get_cufft_plan(cufftType type, const cufftPlanParams& params); -__host__ inline void check_cuda(cublasStatus_t status, const char* file, int line) -{ - if (error != cudaSuccess) { - fprintf(stderr, - "Internal CUDA failure with error %s (%s) in file %s at line %d\n", - cudaGetErrorString(error), - cudaGetErrorName(error), - file, - line); -#ifdef DEBUG_CUNUMERIC - assert(false); -#else - exit(status); -#endif - } -} - -__host__ inline void check_cublas(cublasStatus_t status, const char* file, int line) -{ - if (status != CUBLAS_STATUS_SUCCESS) { - fprintf(stderr, - "Internal cuBLAS failure with error code %d in file %s at line %d\n", - status, - file, - line); -#ifdef DEBUG_CUNUMERIC - assert(false); -#else - exit(status); -#endif - } -} - -__host__ inline void check_cufft(cufftResult result, const char* file, int line) -{ - if (result != CUFFT_SUCCESS) { - fprintf(stderr, - "Internal cuFFT failure with error code %d in file %s at line %d\n", - result, - file, - line); -#ifdef DEBUG_CUNUMERIC - assert(false); -#else - exit(result); -#endif - } -} - -__host__ inline void check_cusolver(cusolverStatus_t status, const char* file, int line) -{ - if (status != CUSOLVER_STATUS_SUCCESS) { - fprintf(stderr, - "Internal cuSOLVER failure with error code %d in file %s at line %d\n", - status, - file, - line); -#ifdef DEBUG_CUNUMERIC - assert(false); -#else - exit(status); -#endif - } -} - -#if LEGATE_DEFINED(CUNUMERIC_USE_CUSOLVERMP) -__host__ inline void check_cal(calError_t status, const char* file, int line) -{ - if (status != CAL_OK) { - fprintf(stderr, - "Internal libcal failure with error code %d in file %s at line %d\n", - status, - file, - line); -#ifdef DEBUG_CUNUMERIC - assert(false); -#else - exit(status); -#endif - } -} -#endif - -__host__ inline void check_cutensor(cutensorStatus_t result, const char* file, int line) -{ - if (result != CUTENSOR_STATUS_SUCCESS) { - fprintf(stderr, - "Internal Legate CUTENSOR failure with error %s (%d) in file %s at line %d\n", - cutensorGetErrorString(result), - result, - file, - line); -#ifdef DEBUG_CUNUMERIC - assert(false); -#else - exit(result); -#endif - } -} - -__host__ inline void check_nccl(ncclResult_t error, const char* file, int line) -{ - if (error != ncclSuccess) { - fprintf(stderr, - "Internal NCCL failure with error %s in file %s at line %d\n", - ncclGetErrorString(error), - file, - line); -#ifdef DEBUG_CUNUMERIC - assert(false); -#else - exit(error); -#endif - } -} - template __device__ __forceinline__ T shuffle(unsigned mask, T var, int laneMask, int width) { From 0cb9f2d936ff8c0f72646b9173596acd0546409c Mon Sep 17 00:00:00 2001 From: Wonchan Lee Date: Tue, 11 Jun 2024 10:18:56 -0700 Subject: [PATCH 219/462] Fix for the CPU build (#226) --- src/cunumeric/cuda_help.h | 5 +++++ src/cunumeric/stat/histogram_cpu.h | 4 ++++ src/cunumeric/stat/histogram_impl.h | 4 ++-- 3 files changed, 11 insertions(+), 2 deletions(-) diff --git a/src/cunumeric/cuda_help.h b/src/cunumeric/cuda_help.h index 502b160b5d..612d8f4dc8 100644 --- a/src/cunumeric/cuda_help.h +++ b/src/cunumeric/cuda_help.h @@ -17,6 +17,11 @@ #pragma once #include "legate.h" + +#if !LEGATE_DEFINED(LEGATE_NVCC) +#error "This header can only be included from .cu files" +#endif + #include "core/cuda/stream_pool.h" #include "cunumeric/arg.h" #include diff --git a/src/cunumeric/stat/histogram_cpu.h b/src/cunumeric/stat/histogram_cpu.h index 63a13668c0..820946621d 100644 --- a/src/cunumeric/stat/histogram_cpu.h +++ b/src/cunumeric/stat/histogram_cpu.h @@ -29,7 +29,11 @@ #include #include +#if LEGATE_DEFINED(LEGATE_USE_CUDA) and LEGATE_DEFINED(LEGATE_NVCC) #include "cunumeric/cuda_help.h" +#else +#define cudaStream_t std::int32_t +#endif #include "cunumeric/stat/histogram_gen.h" namespace cunumeric { diff --git a/src/cunumeric/stat/histogram_impl.h b/src/cunumeric/stat/histogram_impl.h index 79d37b10a8..632d4fc8d3 100644 --- a/src/cunumeric/stat/histogram_impl.h +++ b/src/cunumeric/stat/histogram_impl.h @@ -86,7 +86,7 @@ void histogram_weights(exe_policy_t exe_pol, size_t n_intervals, // |bins| - 1 weight_t* ptr_hist, // result; pre-allocated, sz = n_intervals weight_t* ptr_w = nullptr, // weights array, w - cudaStream_t stream = nullptr) + cudaStream_t stream = {}) { alloc_t alloc_offsets; auto* ptr_offsets = alloc_offsets(exe_pol, n_intervals + 1); @@ -129,7 +129,7 @@ void histogram_wrapper(exe_policy_t exe_pol, const Rect<1>& weights_rect, const AccessorRD, true, 1>& result, const Rect<1>& result_rect, - cudaStream_t stream = nullptr) + cudaStream_t stream = {}) { auto&& [src_size, src_copy, src_ptr] = accessors::make_accessor_copy(exe_pol, src, src_rect); From c7361b55b15096457e570b0c75c659a34860fe7d Mon Sep 17 00:00:00 2001 From: Bryan Van de Ven Date: Tue, 11 Jun 2024 16:08:08 -0700 Subject: [PATCH 220/462] [WIP] Re-organize the Docs (#192) * installation chapter * examples to their own chapter * comparison to API ref * howto api measure * add shrirams new examples * faq (empty) structure * more user guide re-org * add shriram's usage changes * more of shriram's content * finish faqs * remove old configuration chapter * review comments --- .../source/{comparison => api}/_grouped.rst | 0 docs/cunumeric/source/api/comparison.rst | 12 + docs/cunumeric/source/api/index.rst | 1 + .../black_scholes.ipynb | 0 .../notebooks => examples}/cholesky.ipynb | 0 .../examples/compact_finite_difference.ipynb | 336 +++++++++++++++ .../source/examples/edge_detection.ipynb | 210 +++++++++ docs/cunumeric/source/examples/image.png | Bin 0 -> 305442 bytes docs/cunumeric/source/examples/index.rst | 13 + docs/cunumeric/source/examples/kmeans.ipynb | 402 ++++++++++++++++++ .../source/examples/newton_raphson_2d.ipynb | 257 +++++++++++ .../notebooks => examples}/stencil.ipynb | 0 docs/cunumeric/source/faqs.rst | 177 ++++++++ docs/cunumeric/source/index.rst | 6 +- .../source/{user => }/installation.rst | 29 ++ docs/cunumeric/source/user/advanced.rst | 42 ++ docs/cunumeric/source/user/configuration.rst | 108 ----- .../source/user/howtos/benchmarking.rst | 62 +++ docs/cunumeric/source/user/howtos/index.rst | 10 + docs/cunumeric/source/user/howtos/jupyter.rst | 107 +++++ .../index.rst => user/howtos/measuring.rst} | 24 +- .../cunumeric/source/user/howtos/patching.rst | 35 ++ docs/cunumeric/source/user/index.rst | 5 +- docs/cunumeric/source/user/notebooks.rst | 9 - docs/cunumeric/source/user/practices.rst | 64 +-- docs/cunumeric/source/user/usage.rst | 177 ++++---- 26 files changed, 1801 insertions(+), 285 deletions(-) rename docs/cunumeric/source/{comparison => api}/_grouped.rst (100%) create mode 100644 docs/cunumeric/source/api/comparison.rst rename docs/cunumeric/source/{user/notebooks => examples}/black_scholes.ipynb (100%) rename docs/cunumeric/source/{user/notebooks => examples}/cholesky.ipynb (100%) create mode 100644 docs/cunumeric/source/examples/compact_finite_difference.ipynb create mode 100644 docs/cunumeric/source/examples/edge_detection.ipynb create mode 100644 docs/cunumeric/source/examples/image.png create mode 100644 docs/cunumeric/source/examples/index.rst create mode 100644 docs/cunumeric/source/examples/kmeans.ipynb create mode 100644 docs/cunumeric/source/examples/newton_raphson_2d.ipynb rename docs/cunumeric/source/{user/notebooks => examples}/stencil.ipynb (100%) create mode 100644 docs/cunumeric/source/faqs.rst rename docs/cunumeric/source/{user => }/installation.rst (74%) create mode 100644 docs/cunumeric/source/user/advanced.rst delete mode 100644 docs/cunumeric/source/user/configuration.rst create mode 100644 docs/cunumeric/source/user/howtos/benchmarking.rst create mode 100644 docs/cunumeric/source/user/howtos/index.rst create mode 100644 docs/cunumeric/source/user/howtos/jupyter.rst rename docs/cunumeric/source/{comparison/index.rst => user/howtos/measuring.rst} (78%) create mode 100644 docs/cunumeric/source/user/howtos/patching.rst delete mode 100644 docs/cunumeric/source/user/notebooks.rst diff --git a/docs/cunumeric/source/comparison/_grouped.rst b/docs/cunumeric/source/api/_grouped.rst similarity index 100% rename from docs/cunumeric/source/comparison/_grouped.rst rename to docs/cunumeric/source/api/_grouped.rst diff --git a/docs/cunumeric/source/api/comparison.rst b/docs/cunumeric/source/api/comparison.rst new file mode 100644 index 0000000000..139a02d76e --- /dev/null +++ b/docs/cunumeric/source/api/comparison.rst @@ -0,0 +1,12 @@ +Project comparisons +=================== + +Here is a list of NumPy APIs and corresponding cuNumeric implementations. + +A dot in the cunumeric column denotes that cuNumeric implementation +is not provided yet. We welcome contributions for these functions. + +NumPy vs cuNumeric APIs +----------------------- + +.. comparison-table:: diff --git a/docs/cunumeric/source/api/index.rst b/docs/cunumeric/source/api/index.rst index c6a5243c4b..ea740628ec 100644 --- a/docs/cunumeric/source/api/index.rst +++ b/docs/cunumeric/source/api/index.rst @@ -9,3 +9,4 @@ API Reference classes routines settings + comparison diff --git a/docs/cunumeric/source/user/notebooks/black_scholes.ipynb b/docs/cunumeric/source/examples/black_scholes.ipynb similarity index 100% rename from docs/cunumeric/source/user/notebooks/black_scholes.ipynb rename to docs/cunumeric/source/examples/black_scholes.ipynb diff --git a/docs/cunumeric/source/user/notebooks/cholesky.ipynb b/docs/cunumeric/source/examples/cholesky.ipynb similarity index 100% rename from docs/cunumeric/source/user/notebooks/cholesky.ipynb rename to docs/cunumeric/source/examples/cholesky.ipynb diff --git a/docs/cunumeric/source/examples/compact_finite_difference.ipynb b/docs/cunumeric/source/examples/compact_finite_difference.ipynb new file mode 100644 index 0000000000..6c77763a4f --- /dev/null +++ b/docs/cunumeric/source/examples/compact_finite_difference.ipynb @@ -0,0 +1,336 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "a040718f-95ed-4e39-8e96-76529df1811a", + "metadata": {}, + "source": [ + "# Compact Finite Difference Scheme" + ] + }, + { + "cell_type": "markdown", + "id": "b8ced087-d285-4a55-97c6-fc78ad69d186", + "metadata": {}, + "source": [ + "## Learning Outcomes\n", + "\n", + "This examples teaches how to compute derivative of a function using Compact Finite Difference scheme as described in the paper by [Lele](https://www.sciencedirect.com/science/article/abs/pii/002199919290324R).\n", + "\n", + "In this example, you will learn:\n", + "* how to convert stencil expressions from discretization to NumPy slicing operations\n", + "* how to create a tridiagonal matrix (dense)\n", + "* how to solve the resulting tridiagonal matrix using `linalg.solve`\n", + "* how to compute the L2 norm error between exact solution and computed solution using `linalg.norm`\n", + "\n", + "Note that a more optimal way of solving tridiagonal matrices is by using the TDMA algorithm. Here, we show how this can be solved using NumPy's `solve` API.\n", + "\n", + "---" + ] + }, + { + "cell_type": "markdown", + "id": "adb9700f-d3d5-4003-a532-123c1c55e340", + "metadata": {}, + "source": [ + "## Background\n", + "\n", + "Compact finite difference schemes approximate the first derivative by including the information of derivative of function at neighboring points in addition to including the value of function themselves, as shown below:\n", + "\n", + "$\\alpha f^{'}_{i-1} + f^{'}_{i-1} + \\alpha f^{'}_{i+1} = a_{1} f_{j+1} + a_{2} f_{j+2} - a_{2} f_{j-2} - a_{1} f_{j-1}$\n", + "\n", + "This can be represented more compactly in the following form,\\\n", + "$\\mathbf{A} f' = \\mathbf{B} f$\n", + "\n", + "where the matrix $\\mathbf{A}$ is tridiagonal and $\\mathbf{B}$ is pentadiagonal. In this example, we store the matrix, $\\mathbf{A}$, as a dense matrix and explicitly compute $\\mathbf{B} f$ instead of storing B to save memory. To compute the derivative, $f'$, of a function, $f$, using a sixth order compact finite difference, we solve a linear system of equations\n", + "\n", + "The main and off-diagonal elements of matrix $\\mathbf{A}$ are 1.0 and 1.0/3 respectively. For this example, we consider a sine function, $f=\\sin({k x})$\n", + "\n", + "\n", + "---" + ] + }, + { + "cell_type": "markdown", + "id": "df0cdc69-4bf6-4ecd-bb46-4df1b95437c4", + "metadata": {}, + "source": [ + "The domain extends from 0 to $2 \\pi$ and is discretized using N points. Since the stecil for the RHS extends 2 points on either side, we may have to create arrays of size (N+4) to accomodate storing the values of points outside the domain." + ] + }, + { + "cell_type": "markdown", + "id": "9475f334-b2ab-4cad-8a1f-8803ecf97371", + "metadata": {}, + "source": [ + "## Implementation" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "id": "39bc4d10-25e2-4ef8-a70a-2ce270c8eb5f", + "metadata": {}, + "outputs": [], + "source": [ + "import numpy as np\n", + "from matplotlib import pyplot as plt" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "7dc1892b-842f-4200-abf6-769095caa861", + "metadata": {}, + "outputs": [], + "source": [ + "# number of points used in discretization\n", + "npoints: int = 100\n", + "\n", + "# number of stencil points to compute the right-hand side\n", + "n_stencil: int = 2\n", + "\n", + "# length of the domain\n", + "length = 2.0*np.pi\n", + "\n", + "# wavenumber of the initial profile\n", + "wavenumber = 10" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "id": "ee7c5c26-ffcb-4e6a-acb6-73540e0c735c", + "metadata": {}, + "outputs": [], + "source": [ + "# compute the spacing\n", + "h = length/npoints\n", + "\n", + "# generate the discretized points\n", + "x = np.linspace(0, length, npoints, endpoint=False)\n", + "\n", + "# compute the function and exact derivative\n", + "f_interior = np.sin(wavenumber*x)\n", + "derivative_exact = wavenumber* np.cos(wavenumber*x)" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "id": "17b3c8ce-22dd-4a26-8ec7-ceb51632e59f", + "metadata": {}, + "outputs": [], + "source": [ + "# For sixth order, the stencil should be of size 2\n", + "assert n_stencil == 2" + ] + }, + { + "cell_type": "markdown", + "id": "816d739e-c4d3-4559-a097-af896eb75228", + "metadata": {}, + "source": [ + "Compute the function values including the left and right-hand side boundaries" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "id": "59649243-9f42-4344-bbd7-17051d303d1d", + "metadata": {}, + "outputs": [], + "source": [ + "function_values = np.zeros(npoints + n_stencil*2)\n", + "function_values[n_stencil:-n_stencil] = f_interior\n", + "\n", + "# set the RHS boundary values using periodic boundary condition\n", + "function_values[npoints + n_stencil] = f_interior[0]\n", + "function_values[npoints + n_stencil + 1] = f_interior[1]\n", + "\n", + "# set the LHS boundary values using periodic boundary condition\n", + "function_values[0] = f_interior[npoints - n_stencil]\n", + "function_values[1] = f_interior[npoints - n_stencil + 1]" + ] + }, + { + "cell_type": "markdown", + "id": "3dbc59ce-7099-4f49-ba55-d2f918eb3adc", + "metadata": {}, + "source": [ + "Form the matrix $\\mathbf{A}$" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "id": "9a11d32c-1568-4a30-aeb6-52480aca34c6", + "metadata": {}, + "outputs": [], + "source": [ + "A = np.zeros((npoints, npoints))\n", + "\n", + "# Eqn (2.1.7) from Compact Finite Difference with Spectral-like Accuracy, Lele, 1992, JCP.\n", + "alpha = 1.0/3.0\n", + "\n", + "# generate the tridiagonal matrix using np.diag\n", + "main = np.ones((1, npoints))[0]\n", + "diagonal = alpha*np.ones((1, npoints - 1))[0]\n", + "A = np.diag(main, 0) + np.diag(diagonal, -1) + np.diag(diagonal, 1)\n", + "\n", + "# Apply periodic boundary condition\n", + "A[0, -1] = alpha\n", + "A[-1, 0] = alpha" + ] + }, + { + "cell_type": "markdown", + "id": "204c132c-b4b2-494e-96cc-1b7ee1c55e83", + "metadata": {}, + "source": [ + "Form the right-hand side" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "id": "d939df3a-6e6c-4e88-b671-18ab0b1e58b9", + "metadata": {}, + "outputs": [], + "source": [ + "# Generate the right-hand side\n", + "a1 = 7.0/(9.0*h)\n", + "a2 = 1.0/(36.*h)\n", + "\n", + "# note how $a_{1} f_{j+1} + a_{2} f_{j+2} - a_{2} f_{j-2} - a_{1} f_{j-1}$\n", + "# gets converted to slicing operations on the array function_values.\n", + "\n", + "# It is important to derive the right start and end indices for the slices corresponding to each term.\n", + "# Since the stencil size on the RHS is 2 (n_stencil), the index j in the equation starts from the second point (j=2)\n", + "# and ends at j=(npoints+2). This translates to the following slices;\n", + "\n", + "# f_{j+2} - f_{j-2} -> (function_values[4:npoints+4] - function_values[0:npoints])\n", + "# f_{j+1} - f_{j-1} -> (function_values[3:npoints+3] - function_values[1:npoints+1])\n", + "rhs = np.zeros(npoints)\n", + "rhs[0:npoints] = a1*(function_values[3:npoints+3] - function_values[1:npoints+1]) \\\n", + " + a2*(function_values[4:npoints+4] - function_values[0:npoints])" + ] + }, + { + "cell_type": "markdown", + "id": "77ee7783-933e-4be0-88c7-984396cdaeef", + "metadata": {}, + "source": [ + "Compute the derivative and the L2 norm error" + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "id": "87df4961-25af-4876-8e46-2762429a3418", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "L2 norm error: 0.0021705301478625403\n" + ] + } + ], + "source": [ + "derivative = np.linalg.solve(A, rhs)\n", + "error = np.linalg.norm(derivative - derivative_exact)\n", + "print(f\"L2 norm error: {error}\")" + ] + }, + { + "cell_type": "markdown", + "id": "1015951c-29a9-4be1-b65e-5f3d19efdb9c", + "metadata": {}, + "source": [ + "---\n", + "Compare the exact and computed derivative of the function" + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "id": "c1d82ac2-d584-451e-8bc7-b618e3a0f9af", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "" + ] + }, + "execution_count": 9, + "metadata": {}, + "output_type": "execute_result" + }, + { + "data": { + "image/png": "iVBORw0KGgoAAAANSUhEUgAAAzYAAAHFCAYAAAAg1mzfAAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjguMSwgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy/SrBM8AAAACXBIWXMAAA9hAAAPYQGoP6dpAAEAAElEQVR4nOydd5gT5fbHv+nZzfa+1KVJb4IgKAJ6ERHwWhArYgF7QcSCFfUq13IVC2IHuSqiP4oVEBVQL0WUoggiKJ1dFnbZXtLe3x9v3plkN2UmmczM7r6f59kHNplkTmaTyXvmnO/3GAghBBwOh8PhcDgcDofThDFqHQCHw+FwOBwOh8PhxApPbDgcDofD4XA4HE6Thyc2HA6Hw+FwOBwOp8nDExsOh8PhcDgcDofT5OGJDYfD4XA4HA6Hw2ny8MSGw+FwOBwOh8PhNHl4YsPhcDgcDofD4XCaPDyx4XA4HA6Hw+FwOE0enthwOBwOh8PhcDicJk9Uic2vv/6K6667Dh06dIDdbkdSUhJOPfVUPPvssygtLVU6Rk3YuXMnZs2ahf3796uyv7Vr18JgMGDt2rWKPu+sWbNgMBgUfc6GfPjhh5gzZ07Q+wwGA2bNmhXX/ceC0+nEzTffjPz8fJhMJvTr1y9u+9q6dSuGDx+O1NRUGAyGkMdMDY4ePYpZs2Zh27Ztje5T4z2jBtdeey0KCgq0DiMkBQUFuPbaa2U/Lty5acSIEejVq1fswcWB/fv3w2AwYMGCBVqH0uSpqanBrFmzFP++4FDWr1+PWbNmoaysTPJjXnnlFXTu3BlWqxUGgwFlZWUxnYMWLFgAg8EQ8DkP910bD4LFH+w7/dtvv8XAgQPhcDhgMBiwfPlyAMDixYvRs2dPJCQkwGAwBP2+4cjj2muvRVJSktZh6BsikzfffJOYzWbSs2dPMnfuXLJmzRry9ddfk6effpp06NCBXHjhhXKfUpd88sknBABZs2aNKvtbs2ZNXPZ36NAhsmHDBkWfsyFjx44l7du3D3rfhg0byKFDh+K6/1iYM2cOAUBeeeUVsn79evLrr7/GbV/9+vUjXbp0IV999RXZsGEDKSwsjNu+IrF582YCgMyfP7/RfWq8Z9Rg7969ZMuWLVqHEZItW7aQvXv3yn5cuHPT8OHDSc+ePRWITnn27dsX8j3Hkcfx48cJAPLYY49pHUqz5LnnniMAyL59+yRtv3XrVgKATJkyhfzwww9kw4YNxO12x3QOKi4uJhs2bCB1dXXCbeG+a+PB5MmTG+2v4Xe61+slGRkZ5PTTTyfffPMN2bBhAyktLSXFxcXEYrGQ8ePHk7Vr15INGzaQ6upq1WJvrkyePJk4HA6tw9A1ZjlJ0IYNG3DLLbdg1KhRWL58OWw2m3DfqFGjcM8992DlypVK5VycGKipqUFiYiLatGmDNm3aaBbH6aefrtm+pbBjxw4kJCTg9ttvV2VfU6dOxZgxY+K+r1jQ+j2jFJ06ddI6hLD0799f6xAUh5131KC2thZ2u71ZVBddLhcMBgPMZllfyRyd8PvvvwMApk6dikGDBgm3x3IOys7ORnZ2dsyxKU3D7/SjR4+itLQUF110Ec455xzh9v/9739wuVy4+uqrMXz4cEX23Zw+85w4IicLGjduHDGbzeTgwYOStvd4POSZZ54hXbt2JVarlWRnZ5NJkyY1uoLPrjKuX7+eDBkyhNjtdtK+fXvy7rvvEkII+eKLL0j//v1JQkIC6dWrF1mxYkXA4x977DECgGzZsoVcdNFFJDk5maSkpJCrrrqKFBcXB2yLEFe52rdvTyZPnkwIIWT+/PkEQKMf/yuNq1evJmeffTZJTk4mCQkJZOjQoeSbb76RdFx27dpFRo8eTRISEkhmZia56aabyGeffRb0KqyU/bDX/8svv5BLLrmEpKWlkby8vID7GP/85z9Ju3btiMfjaRTXoEGDSP/+/YXfX331VTJs2DCSnZ1NEhMTSa9evcgzzzxDnE6nsM3w4cODHiuG//Hetm0bAUDefvvtRvv+6quvCADy6aefCrf9+eef5IorriDZ2dnEarWSbt26kVdffVXCESaktraWPPDAA6SgoIBYLBbSqlUrcuutt5KTJ08GxBbub9yQr7/+mlxwwQWkdevWxGazkU6dOpEbb7yRHD9+PGwsod5PhDT++zR8jP8Vw/bt25OxY8eSFStWkP79+xO73U66du1K3nnnnUaPP3z4MJk6dSpp06YNsVgsJD8/n1xyySWkqKhIqA42/GF/p2Axyf0s//TTT+TMM88kCQkJpEOHDmT27NlB33P+hLui3/BzW1xcLLw+q9VKsrKyyNChQ8nq1auFbYJdbQRAbrvtNrJw4ULSrVs3kpCQQPr06UM+//zzRvtcvnw56d27N7FaraRDhw5kzpw5If9eDdmyZQsZO3as8N7Nz88n559/fsDx8j/nEELITTfdRGw2G/n555+F2zweDzn77LNJTk4OOXr0aMRzUyzH33+fcv7W69atI0OGDCEJCQnksssuI4QQcuTIEXLppZeSpKQkkpKSQiZOnEg2bNgQ9O+7efNmMn78eJKenk5sNhvp168fWbx4ccA27HWvWrWKXHfddSQrK4sAILW1tUFfQ21tLZk+fTrp27cvSUlJIenp6eT0008ny5cvD/p6X375ZdK3b19it9tJamoqGTx4cMC5iBBCPvjgA3L66acTh8NBHA4H6du3b8C5rOHf0/84DR8+XPidff4WLlxIpk+fTlq1akUMBgPZtWsXKS4uJrfccgvp3r07cTgcJDs7m4wcOZJ8//33wuPZ56Thj/++Yzl3Sjkeevxu/+ijj8ioUaNIXl4esdvtpFu3buT+++8nVVVVjV7jxo0bybhx40hGRgax2WykY8eO5K677grYZ8OfUN0Uwb4D2d8ilnNQw++ASN+19fX15MknnxT+JllZWeTaa69tdJxCMX/+fHLKKacI75f33nsvZPwNvyv8f9jnoOHt/p8BJT7zH330ETn99NNJYmIicTgc5Nxzz21UHWPVjT179pAxY8YQh8NB2rRpQ6ZPnx5QCSOEkLq6OvL444+Tbt26EZvNRjIyMsiIESPI//73P2Ebr9dL5s6dK3w20tLSyCWXXEL++uuviMdXyncWIYSsWLGCnH322SQlJYUkJCSQbt26kaeffjqq1yT1PcHWFp9//jnp16+f8Plh78n58+eTbt26kcTERHLaaaeRzZs3N3p9Uv6maiE5sXG73SQxMZEMHjxY8pPfeOONBAC5/fbbycqVK8nrr79OsrOzSdu2bQMWg8OHDyeZmZnCIm3VqlVk3LhxBAB5/PHHSe/evcmiRYvIV199RU4//XRis9nIkSNHhMezD1f79u3JvffeS1atWkVeeOEF4nA4SP/+/QMW4lISm+LiYvL0008TAGTu3Llkw4YNZMOGDcKb4b///S8xGAzkwgsvJEuXLiWff/45GTduHDGZTBGTm6KiIpKTk0Nat25N5s+fT7766ity1VVXkXbt2jU6eUrdj//rv//++8nq1auFL/CGC7FPP/2UAGj0Ydq1axcBQF5++WXhtrvvvpvMmzePrFy5knz33XfkxRdfJFlZWeS6664Ttvn999/JGWecQfLy8oTj5N/G1PB49+/fn5xxxhmNjsvEiRNJTk4OcblcwvOmpqaS3r17k4ULF5Kvv/6a3HPPPcRoNJJZs2aFPcZer5eMHj2amM1m8sgjj5Cvv/6aPP/888L7gX34N2zYQM4//3ySkJDQ6G8cjHnz5pHZs2eTzz77jKxbt4689957pG/fvqRr164B77GGsJYCAGTChAkBx0huYtOmTRvSo0cPsnDhQrJq1Spy6aWXEgBk3bp1wnaHDx8m+fn5JCsri7zwwgvkm2++IYsXLybXX3892bVrFykvLxee/+GHHxbiYYuSYDHJ/Sx36dKFvP7662T16tXk1ltvJQDIe++9F+avJi+xGT16NMnOziZvvvkmWbt2LVm+fDl59NFHyUcffSRsE+pLuaCggAwaNIh8/PHH5KuvviIjRowgZrM54MtpxYoVxGg0khEjRpBly5aRTz75hAwePJgUFBRETGyqqqpIZmYmGThwIPn444/JunXryOLFi8nNN99Mdu7cKWzXcCFcW1tL+vXrRzp27Cgk4I8++igxGo3k66+/JoREPjfFcvwZcv7WGRkZpG3btuSVV14ha9asIevWrSM1NTWke/fuJDU1lbzyyitk1apV5M477xTOcf5/3++++45YrVYybNgwsnjxYrJy5Upy7bXXNtqOvV9bt25NbrzxRrJixQryf//3f8Ttdgd9DWVlZeTaa68l//3vf8l3331HVq5cSWbMmEGMRmOj4zBp0iRiMBjIlClTyKeffkpWrFhBnnrqKfLSSy8J2zzyyCMEALn44ovJJ598Qr7++mvywgsvkEceeSTk39P/OAVLbFq3bk0mTJhAPvvsM/LFF1+QkpIS8scff5BbbrmFfPTRR2Tt2rXkiy++IDfccAMxGo3Cd0NdXR1ZuXIlAUBuuOEG4T3A2hpjOXdKPR56/G5/8sknyYsvvki+/PJLsnbtWvL666+TDh06kJEjRwa8vpUrVxKLxUL69OlDFixYQL777jvy7rvvkssvv5wQQltx77jjDgKALF26VDi+5eXlQY/X77//Th5++GHhPev/t4jlHNTwOyDcd63H4yHnnXcecTgc5PHHHyerV68mb7/9NmndujXp0aMHqampCfs3Z/v65z//ST7//HPy/vvvk86dO5O2bduGTWwOHTpEli5dSgCQO+64g2zYsEFosZ07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QZ9rx44daNOmDbKysmTHoQW8FU1hHnvsMVitVowaNQoejwejR4/G5s2bceqpp2odWosm2eYAnPGtIrAraekKtWMArD0q/omNki5MajgaKV0dA8SqVbyqCPFIbFgS7YljFSEe1TFbnKsI7PNiVLDFUqgixDGxEapjVuXiTohzFUEY/Ok10vZZBfA314iXvrDGVQ3YlE1sHFZHXPWFTpcHsNQB4ImNEmzbtg0XXHBB0PuiaUV79tlnkZubi9NOOw0mkwlvv/020tPThURi+/btMJlMQrsX+90/uTpw4ABOnjwpJDajR4/GzJkzcfLkSaSnpwvbVVVVYe/evcLv+/btw7Zt25CRkYF27doBAKZPn45JkyZh4MCBGDJkCN58800cPHgQN998M/bv34+xY8figQcewKRJk9CjRw+cdtpp+OWXXzBgwICA1/XDDz/g3HPPlXUstIRXbBSksLAQd955J5588kn06NEDFosFl19+OU9qdEBqAm3/iacWgfWZZyskoAWoyBqInxZB7DNXLuZ0BxNZx7/PXKnhi4AoIi6rjU/cTOOgZHUsMzn+Q1ydBmWHLwKA3RjfIa4llUw7plzM2cyEwFIbtypCnZfGzQZUKkGiz/QhXvpCph2DK4m2zypAWpKd6gsBFJfFJ26mHUtSaKAyACT7nite+kL/6liuQqYYLRWv14vffvstZMUmGurq6vD0009jwIABOPPMM7Fnzx589913QkKyfft2dOvWTejgYb8zDQtA29DS0tKEYZy9e/fGwIED8fHHHwfs6+eff0b//v2Fas/06dPRv39/PProo8I2l112GebMmYMnnngC/fr1w/fff4+vvvoKycnJGDNmDC644AI8+OCDAIABAwZg/PjxeOihhxq9pmXLlmHq1KmKHad4wys2CtKxY0d069YNn3zyCcaOHYtVq1Zh4sSJOHz4MO6//36tw2vRpPjs7Z2kCigsBEwmICdHseePR585amth8SaiDkD58WM07pQUWXaWkYhHn3ma1wvAV0UoLKR9z7m5svufw6F4n3l9PWxeKtCsPHmCxp2UBIRwOYwGZgGrZBKZSegCm5h9xxqgx9qo3DUrxatjbjcSCNW9VFeV0rgTEoC0NGWeH37aMQWrY9lwCf8/sWcfWqU7gOxswKzc16hT6eGLXi8cXhpfTV0ZPdY2GxCFKDoUQnVMQct4Y3UV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", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "plt.plot(x, f_interior, color='red', label=f'$f = sin({wavenumber}x)$')\n", + "plt.plot(x, derivative, color='blue', label=f'Computed $f\\'(x)$')\n", + "plt.plot(x, derivative_exact, color='green', label=f'Exact f\\'(x)')\n", + "\n", + "plt.xlabel(f'x')\n", + "plt.ylabel(f'$f(x), f\\'(x)$')\n", + "plt.title(f'Compute derivative of a function using sixth-order accurate compact finite difference scheme')\n", + "plt.legend(loc='center left', bbox_to_anchor=(1, 0.5))" + ] + }, + { + "cell_type": "markdown", + "id": "4f05eea3-5e1e-4567-9d7a-6ef8143f2723", + "metadata": {}, + "source": [ + "We see that the curves for the computed and the exact derivative overlap (green and blue) and the L2 error is of the order of 1e-3. If you are curious,\n", + "\n", + "* Try increasing the wavenumber, $k$, to see how this affects the accuracy of the solution. Plot error vs wavenumber to see how it varies\n", + "* Try reducing the resolution of the grid (n_points) and plot the error vs number of points.\n", + "* Try plotting the modified wavenumber and see if you can match the curve in the paper by [Lele](https://www.sciencedirect.com/science/article/abs/pii/002199919290324R)." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "9573de0a-be2a-4781-ae79-3ca9dcb17f05", + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "metadata": { + "kernelspec": { + "display_name": "python3 (legate) *", + "language": "python", + "name": "conda-env-legate-py" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.10.13" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/docs/cunumeric/source/examples/edge_detection.ipynb b/docs/cunumeric/source/examples/edge_detection.ipynb new file mode 100644 index 0000000000..6836020ee9 --- /dev/null +++ b/docs/cunumeric/source/examples/edge_detection.ipynb @@ -0,0 +1,210 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "0c2fc84f-fb0b-4fb2-b356-14a75a1f9dba", + "metadata": {}, + "source": [ + "# Edge Detection" + ] + }, + { + "cell_type": "markdown", + "id": "ceb11e7b-85d7-4598-826b-2a91ad751092", + "metadata": {}, + "source": [ + "## Learning Outcomes\n", + "This example identifies edges in an image using Sobol edge detection algorithm and is implemented using NumPy and SciPy. An edge is defined as an abrupt change in intensity of the image. The Sobol edge detection algorithm uses a kernel in each direction to compute derivative of intensity of the image. The gradient of the intensity will help us determine the locations where changes in intensity are abrupt, which can then be used to detect edges in an image.\n", + "\n", + "This example uses the following packages in addition to NumPy/cuNumeric: Scipy, Matplotlib, PIL" + ] + }, + { + "cell_type": "markdown", + "id": "15f468c7-5e09-4456-a770-03ee0e713546", + "metadata": {}, + "source": [ + "## Background\n", + "For more information on edge detection, check this [material](https://www.cs.auckland.ac.nz/compsci373s1c/PatricesLectures/Edge%20detection-Sobel_2up.pdf)." + ] + }, + { + "cell_type": "markdown", + "id": "ed8b8b45-a02d-42a0-8092-d232ef3da30f", + "metadata": {}, + "source": [ + "## Implementation" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "98e3e73e-e500-433c-94a7-9e0a5593e3c1", + "metadata": {}, + "outputs": [], + "source": [ + "import numpy as np\n", + "from numpy import ndarray\n", + "from scipy import ndimage\n", + "from scipy.signal import convolve\n", + "from matplotlib import pyplot as plt\n", + "from PIL import Image" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "94310f80-baf0-4cd3-b0bc-b49dd62ccfbb", + "metadata": {}, + "outputs": [], + "source": [ + "# Intensity varies between 0 and 255 in the image.\n", + "intensity_min = 0.0\n", + "intensity_max = 255.0" + ] + }, + { + "cell_type": "markdown", + "id": "78273013-cea0-4c28-a376-c3c40e681276", + "metadata": {}, + "source": [ + "Since NumPy's `convolve` API does not allow two-dimensional arrays and our image is represented in an two-dimensional array, we will use the `convolve` API from SciPy for this example. cuNumeric's implementation of `convolve` permits two-dimensional array and will be used if `cuNumeric` is imported instead of `NumPy`. Try changing the import statement from \"import numpy as np\" to \"import cunumeric as np\"!" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "id": "64c779b4-6167-4854-97af-ac74469d772c", + "metadata": {}, + "outputs": [], + "source": [ + "def convolve_nd(array: ndarray, kernel: ndarray, mode: str = \"same\"):\n", + " \"\"\"\n", + " array: ndarray\n", + " Input array corresponding to a grayscale image\n", + " kernel: ndarray\n", + " Kernel to compute the gradient in x or y as per Sobel Edge Detector\n", + " mode: str\n", + " The default convolution mode. Note that cuNumeric only\n", + " supports the convolution mode \"same\".\n", + "\n", + " Notes:\n", + " Check https://homepages.inf.ed.ac.uk/rbf/HIPR2/sobel.htm\n", + " for more information on Sobel Edge Detector\n", + "\n", + " The image was taken from:\n", + " https://docs.nvidia.com/vpi/algo_canny_edge_detector.html\n", + " \"\"\"\n", + " if np.__name__ == \"cunumeric\":\n", + " return np.convolve(array, kernel, mode)\n", + " return convolve(array, kernel, mode)" + ] + }, + { + "cell_type": "markdown", + "id": "a93b9fb7-f792-48ac-9a80-f5d33d800c9a", + "metadata": {}, + "source": [ + "Read the image and compute the gradient by performing a convolution operation" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "id": "4edeed3a-9729-4a2c-a52e-95be99e2e5a3", + "metadata": {}, + "outputs": [], + "source": [ + "# Read the image\n", + "image = np.array(Image.open(\"image.png\"))\n", + "\n", + "# Sobol kernels in x and y to compute the derivatives\n", + "kernel_x = np.array([[-1, 0, 1], [-2, 0, 2], [-1, 0, 1]])\n", + "kernel_y = np.array([[1, 2, 1], [0, 0, 0], [-1, -2, -1]])\n", + "\n", + "# Apply the Sobel kernels and compute the gradient\n", + "grad_x = convolve_nd(image, kernel_x, mode=\"same\")\n", + "grad_y = convolve_nd(image, kernel_y, mode=\"same\")\n", + "\n", + "# Normalize the gradients and scale to the max intensity, which defines the edge\n", + "edges = np.sqrt(grad_x**2 + grad_y**2)\n", + "edges *= intensity_max / np.max(edges)\n", + "edges = edges.astype(int)" + ] + }, + { + "cell_type": "markdown", + "id": "2d10238c-50f0-457a-be84-f50791d8989f", + "metadata": {}, + "source": [ + "Now that we have computed the gradient and the edges, we can plot the edges and see if they actually pick up the edges in the original image." + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "id": "ebe45839-78bf-42bb-ac1b-24eff249d39e", + "metadata": {}, + "outputs": [ + { + "data": { + "image/png": "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6lWvGtme57k8++yz5XnnnfeEz1WgwNMdhQKxQIECPxTOP/98eJ6HWq2Gl770pZiYmMBXvvIVuK4Krfrggw/iyJEjeOtb3wrO16eaarWKN7zhDbj11lsxGAwAAJdccgn27NmDvXv3IooifOc738FLX/pSXHzxxebN6Q033IAgCIz1eCM8mWv+MHj1q1898v9ZZ50FANi/fz8A4Jvf/CYAHGdNPu+883D66acfZ4N4IjjvvPPw/e9/H1deeSX++Z//GZ1OZ+TzKIrwjW98A6973etQLpdHlAMvf/nLEUURbr311id0rgIFChQoUKDAf0z82Z/9GW677baRn+9973vmc3rG+dmf/dmR49785jeP/N/v93H77bfjta99LXzfN9ur1epxaryvfe1rYIzhLW95y8jzy/z8PM4++2yTAfqkk07CxMQE3v/+9+Mzn/kM7rvvvid0T7fccguGw+Fxz2Xbtm3Di1/84uOeyxhjx5XxrLPOMs95Pwh79uzBm9/8ZszPz8NxHHieZ2Jsj9ulHwtveMMbRv5/Ms+Vl1xyiXHz3HzzzRgMBnjf+96H6enpkWfpCy64AJVK5THL8ON4liU8kfq98cYbUalU8MY3vnFkPyrPD/ss/fnPfx4f/ehHceuttyJN05HPb775ZqyuruKKK64Y6YdCCLz0pS/FbbfdZqzoj3euAgWe7igIxAIFCvxQoAfNG2+8Ee9617tw//33401vepP5nKT8mzZtOu7YzZs3QwiBtbU1ADBWihtuuAHf+c53kKYpXvziF+PSSy81Dwo33HADLrroIpRKpccs05O55g+Dqampkf8p29xwOHxC1/9h7A0f/OAH8d/+23/Drbfeipe97GWYmprCJZdcgttvv91cM8syfOpTn4LneSM/L3/5ywHA2E0e71wFChQoUKBAgf+YOP3003HuueeO/Nhxp1dWVuC67nHPQvPz8yP/r62tQUqJubm5464xvm1hYcHsO/4Mc+utt5rnl0ajgZtuugnnnHMOPvShD+GMM87A5s2bcdVVV/1AAufJPpeVy2WEYTiyLQgCRFH0mNcAgF6vh+c///n43ve+h49+9KP41re+hdtuuw1/93d/B2D9OfHxMF7OJ1P+Sy+9FAcOHMDDDz+MG264Ac961rMwOzuLF7/4xbjhhhswHA5x8803P659+cfxLEt4IvW7srKC+fn542Jl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B2VhBiD/9//+32bfVqsl3/72t8tmsynL5bK87LLL5AMPPHBc1lsppfzyl78szzzzTPO88fu///vyV37lV+TExMRxZfiTP/kT+bznPU9WKhVZKpXkiSeeKN/2trfJ22+/XUop5QMPPCDf9KY3yRNPPFGWSiXZaDTkeeedJz//+c8/oXv87Gc/K8866yzp+75sNBryNa95jbz33ntH9rniiitkpVI57ljKNPx4uPnmm+UFF1wgy+WynJmZke985zvlnXfe+ZiZgm38oGfrJ/pcKaWUX/nKVyQAedlll41s/4Vf+AUJQP6P//E/Hvc+nsw1n2wW5idavysrK/Ld73633LRpk3RdV+7YsUN+8IMflFEUjezXbrflO9/5Tjk3NycrlYp81ateJfft2zfSH6Moku9+97vlWWedJev1uiyVSvLUU0+VV111lez3+yPnu+mmm+QrXvEKOTk5KT3Pk1u2bJGveMUrzFrhyZyrQIGnK5iUls+vQIECBQoUKFCgQIECBQoU+BEiTVOcc8452LJlC77+9a8/1cUpUKBAgQI/BAoLc4ECBQoUKFCgQIECBQoU+JHhHe94By677DJs2rQJx44dw2c+8xncf//9+OQnP/lUF61AgQIFCvyQKAjEAgUKFChQoECBAgUKFCjwI0O328V/+S//BUtLS/A8D89+9rPxj//4j4+bEK9AgQIFCvy/i8LCXKBAgQIFChQoUKBAgQIFChQoUKBAgccEfyovfs011+CEE05AGIZ4znOeg29/+9tPZXEKFChQoECBAgUKFChQoECBAgUKFCgwhqeMQPziF7+I9773vfj1X/913HXXXXj+85+Pl73sZThw4MBTVaQCBQoUKFCgQIECBQoUKFCgQIECBQqM4SmzMD/vec/Ds5/9bPzRH/2R2Xb66afjta99LX7v937vqShSgQIFChQoUKBAgQIFChQoUKBAgQIFxvCUJFFJkgR33HEHPvCBD4xsv/zyy3HzzTc/7vFCCBw5cgS1Wg2MsR9XMQsUKFCgQIECBX5skFKi2+1i8+bN4PwpjSpT4IdE8UxaoECBAgUKFPhJxpN5Hn1KCMTl5WXkeY65ubmR7XNzczh27Nhx+8dxjDiOzf+HDx/GM57xjB97OQsUKFCgQIECBX7cOHjwILZu3fpUF6PAD4EjR45g27ZtT3UxChQoUKBAgQIF/l14Is+j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", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "fig, axes = plt.subplots(1,2,figsize=(16,9))\n", + "\n", + "axes[0].imshow(image)\n", + "axes[0].set_title(\"Row of houses\")\n", + "axes[0].set_xticks([]), axes[1].set_yticks([]);\n", + "\n", + "axes[1].imshow(edges, cmap=\"gray\")\n", + "axes[1].set_title(\"Edges on a row of houses\")\n", + "axes[1].set_xticks([]), axes[1].set_yticks([]);" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "2b961d99-f6d3-4aaa-a9b4-465bb2a02c1f", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "f150d75d-824e-458c-a410-63a6c66d1f27", + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "metadata": { + "kernelspec": { + "display_name": "python3 (legate) *", + "language": "python", + "name": "conda-env-legate-py" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.10.13" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/docs/cunumeric/source/examples/image.png b/docs/cunumeric/source/examples/image.png new file mode 100644 index 0000000000000000000000000000000000000000..68fab2d4d9365d778ee98f74344a0061970060cb GIT binary patch literal 305442 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 mmhlYJG*LFQ$R-n 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 fW?vT39hi;ti5FF!R{_wvR#grNng(^{n2LbJPH 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�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

nFz&eE;6?`O=fQ(X&HP;5-`noYIYy4A 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 z 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 Tuple[np.ndarray, np.ndarray]:\n", + " \"\"\"\n", + " Randomly initalize data and centroids of the clusters.\n", + " \n", + " n_elements: int\n", + " Number of elements/observations that need to be clusters\n", + " n_dims: int\n", + " Dimension of the elements/observations\n", + " n_centroids: int\n", + " Number of clusters\n", + "\n", + " Returns:\n", + " A Tuple with observations and centroids\n", + " \"\"\"\n", + " data = rng.random((n_elements, n_dims))\n", + " centroids = rng.random((n_centroids, n_dims))\n", + " return data, centroids" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "id": "58705e8e-dc70-4039-a820-b8e596b8b05b", + "metadata": {}, + "outputs": [], + "source": [ + "def calculate_distances(data: np.ndarray, centroids: np.ndarray, data_magnitude_squared: np.ndarray) -> np.ndarray:\n", + " \"\"\"\n", + " Return pairwise distance between the data and centroids.\n", + "\n", + " data: np.ndarray\n", + " Observations that need to be clustered\n", + " centroids: np.ndarray\n", + " The center of the clusters\n", + " data_magnitude_squared: np.ndarray\n", + " Square of magnitude of observations (|y|^2)\n", + "\n", + " Returns: np.ndarray\n", + " \n", + " \"\"\"\n", + " centroid_dots = np.square(np.linalg.norm(centroids, ord=2, axis=1))\n", + " pairwise_distances = ( \n", + " data_magnitude_squared[:, np.newaxis] + centroid_dots[np.newaxis, :]\n", + " )\n", + " # ||x-y||^2 = ||x||^2 + ||y||^2 - 2 x . y\n", + " # pairwise_distances has ||x||^2 + ||y||^2, so beta = 1\n", + " # The gemm calculates x.y for all x and y, so alpha = -2.0\n", + " pairwise_distances -= 2.0 * np.dot(data, centroids.T)\n", + " return pairwise_distances" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "id": "670efdea-fffa-4835-83bf-04c8dbc544ee", + "metadata": {}, + "outputs": [], + "source": [ + "def relabel(pairwise_distances: np.ndarray) -> np.ndarray:\n", + " return np.argmin(pairwise_distances, axis=1)" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "id": "cc81dad6-7661-449e-adaf-58c35698dfc3", + "metadata": {}, + "outputs": [], + "source": [ + "def find_centroids(\n", + " centroids: np.ndarray, \n", + " data: np.ndarray, \n", + " labels: np.ndarray, \n", + " pairwise_distances: np.ndarray,\n", + " zero_point: np.ndarray,\n", + " n_centroids: int\n", + ") -> np.ndarray:\n", + " \"\"\"Find centroids following the algorithm in the reference mentioned earlier\n", + " centroids: np.ndarray\n", + " The center of the clusters\n", + " data: np.ndarray\n", + " Observations that need to be clustered\n", + " labels: np.ndarray\n", + " The clusters the data belong to\n", + " pairwise_distances: np.ndarray\n", + " Pairwise distance between each data point and centroid\n", + " zero_point: np.ndarray\n", + " \n", + " n_centroids: np.ndarray\n", + " Number of clusters\n", + " \"\"\"\n", + " # Get the number of points associated with each centroid\n", + " counts = np.bincount(labels, minlength=n_centroids)\n", + " # Build label masks for each centroid and sum across all the\n", + " # points assocated with each new centroid\n", + " distance_sum = 0.0 \n", + " for idx in range(n_centroids):\n", + " # Boolean mask indicating where the points are for this center\n", + " centroid_mask = labels == idx \n", + " centroids[idx, :] = np.sum(\n", + " np.where(centroid_mask[..., np.newaxis], data, zero_point), axis=0\n", + " ) \n", + " distance_sum += np.sum(\n", + " np.where(centroid_mask, pairwise_distances[:, idx], 0.0)\n", + " ) \n", + " # To avoid introducing divide by zero errors\n", + " # If a centroid has no weight, we'll do no normalization\n", + " # This will keep its coordinates defined.\n", + " counts = np.maximum(counts, np.ones((1,), dtype=np.uint64))\n", + " centroids /= counts[:, np.newaxis]\n", + " return distance_sum" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "id": "4fde3328-4b8a-454d-8a43-82fdfb51d1e4", + "metadata": {}, + "outputs": [], + "source": [ + "def run_kmeans(\n", + " n_centroids: int,\n", + " n_dims: int, \n", + " n_iters: int, \n", + " n_elements: int, \n", + " n_iter_check: int\n", + ") -> Tuple[np.ndarray, np.ndarray, np.ndarray]:\n", + " \"\"\" \n", + " Generate observations and cluster them into requested number of clusters.\n", + " n_centroids: int\n", + " Number of clusters\n", + " n_dims: int\n", + " Dimension of the elements/observations\n", + " n_iters: int\n", + " Maximum number of iterations \n", + " n_elements: int\n", + " Number of elements/observations that need to be clusters\n", + " n_iter_check: int\n", + " Determines how often we check for convergence.\n", + " \"\"\"\n", + " print(\"Running kmeans...\")\n", + " print(\"Number of data points: \" + str(n_elements))\n", + " print(\"Number of dimensions: \" + str(n_dims))\n", + " print(\"Number of centroids: \" + str(n_centroids))\n", + " print(\"Max iterations: \" + str(n_iters))\n", + "\n", + " data, centroids = initialize(n_elements, n_dims, n_centroids)\n", + "\n", + " data_magnitude_squared = np.square(np.linalg.norm(data, ord=2, axis=1))\n", + " zero_point = np.zeros((1, data.shape[1]), dtype=data.dtype)\n", + "\n", + " labels = None\n", + " iteration = 0 \n", + " prior_distance_sum = None\n", + " # We run for max iterations or until we converge\n", + " # We only test convergence every n_iter_check iterations\n", + " while iteration < n_iters:\n", + " pairwise_distances = calculate_distances(data, centroids, data_magnitude_squared)\n", + "\n", + " new_labels = relabel(pairwise_distances)\n", + "\n", + " distance_sum = find_centroids(\n", + " centroids,\n", + " data,\n", + " new_labels,\n", + " pairwise_distances,\n", + " zero_point,\n", + " n_centroids,\n", + " ) \n", + "\n", + " if iteration > 0 and iteration % n_iter_check == 0:\n", + " changes = np.not_equal(labels, new_labels)\n", + " total_changes = np.sum(changes)\n", + " delta = distance_sum / prior_distance_sum\n", + " if delta > 1 - 0.000001:\n", + " break\n", + " \n", + " prior_distance_sum = distance_sum\n", + " labels = new_labels\n", + " iteration += 1\n", + "\n", + " return data, labels, centroids" + ] + }, + { + "cell_type": "markdown", + "id": "70927fda-821b-4858-862d-cae5e6e6eedc", + "metadata": {}, + "source": [ + "### Lets run the kmeans algorithm using a set of inputs" + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "id": "18ac6aab-48f3-4cce-8587-b0ec04600cba", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Running kmeans...\n", + "Number of data points: 256\n", + "Number of dimensions: 2\n", + "Number of centroids: 5\n", + "Max iterations: 100\n" + ] + } + ], + "source": [ + "n_centroids: int = 5\n", + "n_dims: int = 2\n", + "n_elements: int = 256\n", + "n_iter_check: int = 10\n", + "n_iters: int = 100\n", + "\n", + "data, labels, centroids = run_kmeans(n_centroids, n_dims, n_iters, n_elements, n_iter_check)" + ] + }, + { + "cell_type": "markdown", + "id": "0f57c8bf-831e-427a-8bd4-143b71838e4a", + "metadata": {}, + "source": [ + "Generate a color map to differentiate the clusters" + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "id": "01adc703-9a64-4985-9c86-3c5082f0d891", + "metadata": {}, + "outputs": [], + "source": [ + "label_color_map = {0: 'blue', 1: 'black', 2: 'red', 3: 'magenta', 4:'yellow', 5: 'green', 6:'gray'}\n", + "\n", + "# make sure we have unique color for each cluster (total number of clusters specified by n_centroids)\n", + "assert len(label_color_map.items()) >= n_centroids" + ] + }, + { + "cell_type": "markdown", + "id": "66864ad0-c462-4223-a249-f2539bbaf63c", + "metadata": {}, + "source": [ + "Plot the clusters. Each color represents a cluster" + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "id": "5be8c360-d945-486a-9b0f-d0774287f4b9", + "metadata": {}, + "outputs": [ + { + "data": { + "image/png": "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", + "text/plain": [ + "

" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "# lets plot the data colored by the cluster they belong to\n", + "for label_value, label_color in label_color_map.items():\n", + " index = (labels == label_value)\n", + " plt.plot(data[index, 0], data[index, 1], 'o', color=label_color)\n", + "\n", + "# lets plot the centroid of the clusters\n", + "plt.scatter(centroids[:, 0], centroids[:, 1], s = 320, marker='*', c=list(label_color_map.values())[0:n_centroids], edgecolors='gray');\n", + "\n", + "plt.show()" + ] + }, + { + "cell_type": "markdown", + "id": "f19e27d2-7f24-41da-962a-6a3ea61e53af", + "metadata": {}, + "source": [ + "#### Exercise: Change the number of clusters and number of observations and see how the clusters change" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "2b89556c-7e5d-4965-87ae-f3a653d9d3f7", + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "metadata": { + "kernelspec": { + "display_name": "python3 (legate) *", + "language": "python", + "name": "conda-env-legate-py" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.10.13" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/docs/cunumeric/source/examples/newton_raphson_2d.ipynb b/docs/cunumeric/source/examples/newton_raphson_2d.ipynb new file mode 100644 index 0000000000..b5ad9fa90a --- /dev/null +++ b/docs/cunumeric/source/examples/newton_raphson_2d.ipynb @@ -0,0 +1,257 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "ce118a4b-3c3f-42f6-ae1d-8b825fbccb93", + "metadata": {}, + "source": [ + "# Newton Raphson Method In Two Dimensions" + ] + }, + { + "cell_type": "markdown", + "id": "5e12d3fa-68b7-4c43-81c5-515b07a0b33d", + "metadata": {}, + "source": [ + "## Learning Outcomes\n", + "This example teaches how to compute the solution for systems of equations in two variables using NumPy. There are two equations, $f_{1}(x,y)$ and $f_{2}(x, y)$, with two variables each, $x$ and $y$. We seek to find a solution that satisfies these two equations using Newton's method. To understand Newton's method in multiple dimensions, please see [this](https://wiki.math.ntnu.no/_media/tma4125/2017v/newton.pdf) note by Markus Grasmair.\n", + "\n", + "The example also teaches how to interpret a warning from cuNumeric when the import statement is changed from importing numpy to importing cuNumeric.\n", + "\n", + "---" + ] + }, + { + "cell_type": "markdown", + "id": "a814eecb-682b-4573-a2c8-572e5f0638f7", + "metadata": {}, + "source": [ + "## Background\n", + "We consider the following functions,\n", + "\n", + "$f_{1}(x,y) = x^{2} + y^{2} - 13 = 0$\n", + "\n", + "$f_{2}(x,y) = x^{2} - 2y^{2} + 14 = 0$\n", + "\n", + "and their Jacobian, $J$, \n", + "\n", + "$J$ = \\\n", + "\\begin{bmatrix}\n", + " \\Huge{\\frac{\\partial f_{1}}{\\partial x}} & \\Huge{\\frac{\\partial f_{1}}{\\partial y}} \\\\\n", + " \\Huge{\\frac{\\partial f_{2}}{\\partial x}} & \\Huge{\\frac{\\partial f_{2}}{\\partial y}}\n", + "\\end{bmatrix}\n", + "\n", + "\n", + "Substituting the functions, $f_{1}(x, y)$ and $f_{2}(x, y)$, we get,\\\n", + "$J$ = \\\n", + "\\begin{bmatrix}\n", + " \\large{2x} & \\large{2y} \\\\\n", + " \\large{2x} & \\large{-4y}\n", + "\\end{bmatrix}\n" + ] + }, + { + "cell_type": "markdown", + "id": "795b4478-cd32-438d-b806-a1215f3a07bb", + "metadata": {}, + "source": [ + "## Implementation" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "id": "7a0284e7-fe55-4137-95a8-0fff06d535bf", + "metadata": {}, + "outputs": [], + "source": [ + "import numpy as np" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "4252e070-a4d4-4b07-87ad-c03e789f062a", + "metadata": {}, + "outputs": [], + "source": [ + "def function(x: np.ndarray) -> np.ndarray:\n", + " \"Return a numpy array that has the computed values of $f_{1}(x, y)$ and $f_{2}(x, y)$\"\n", + " return np.array([np.sum(x**2) - 13.0, x[0]**2 - 2.0*x[1]**2 + 14.0])\n", + " \n", + "def jacobian(x: np.ndarray) -> np.ndarray:\n", + " \"Return a 2x2 numpy array that has the computed values of the Jacobian, J\"\n", + " return np.array([[2*x[0], 2*x[1]], [2.0*x[0], -4.0*x[1]]])" + ] + }, + { + "cell_type": "markdown", + "id": "136e1298-9af2-4fc3-9b1a-a56afbd54d7a", + "metadata": {}, + "source": [ + "Setup an iterative loop that updates an initial guess $x_{k} = x_{k-1} - {[\\mathbf{J}(x_{k})]}^{-1} \\cdot \\mathbf{f}(x_{k})$\\\n", + "To compute the inverse of the matrix, $\\mathbf{J}$, we use the `inv` API from NumPy's `linalg` package, and to determine when to terminate the loop, \\\n", + "we compute the L2 norm of the difference in solution between two iterations and check if it is less than a specified tolerance." + ] + }, + { + "cell_type": "markdown", + "id": "a91752f1-5ca8-44dd-9a26-525cdf87ab51", + "metadata": {}, + "source": [ + "When you switch the import statement from importing to importing cunumeric, you might see a warning like this:\n", + "\n", + "---\n", + "\n", + "*RuntimeWarning: cuNumeric has not implemented inv and is falling back to canonical NumPy. You may notice significantly decreased performance for this function call.*\n", + "\n", + "---\n", + "\n", + "This means that cuNumeric has not implemented the `linalg.inv` API and is falling back to NumPy's implementation. This means that the API would be *eagerly* executed using NumPy's single-threaded implementation. If the API was intended to be invoked from a GPU, the data will get transferred from the GPU to the CPU before the API is executed. This can have performance implications, as indicated by the warning." + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "id": "c243f28e-ad5e-4c64-8340-96922785c253", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\n", + "Newton's method converged in 7 iterations to xk: [-2. 3.]\n" + ] + } + ], + "source": [ + "# number of iterations to try\n", + "niters = 20\n", + "\n", + "# tolerance that sets the accuracy of solution\n", + "tol = 1e-6\n", + "\n", + "# print additional information \n", + "verbose = False\n", + "\n", + "# initial guess\n", + "xk = np.array([-20.0, 20.0])\n", + "\n", + "# Newton's method \n", + "for iter in range(niters):\n", + " xk_old = xk\n", + "\n", + " if verbose:\n", + " print(f\"iter: {iter}, xk: {xk}\")\n", + " xk = xk - np.linalg.inv(jacobian(xk)).dot(function(xk))\n", + " \n", + " l2_norm = np.linalg.norm((xk - xk_old))\n", + " if l2_norm < tol:\n", + " break\n", + " \n", + "# let the user know if the solution converged or not\n", + "if iter == niters - 1:\n", + " print(f\"\\nNewton's method did not converge for this function, tolerance ({tol}) and number of iterations ({niters})\")\n", + "else:\n", + " print(f\"\\nNewton's method converged in {iter} iterations to xk: {xk}\")" + ] + }, + { + "cell_type": "markdown", + "id": "e5a2e401-e058-4bcc-ac0c-4caa80102079", + "metadata": {}, + "source": [ + "---\n", + "\n", + "We see that the solution has converged to $(x, y) = (-2, 3)$ which satisfies both the equation in 7 iterations\n", + "\n", + "The problem can be cast such that the computation of inverse is substituted by a linear solve, as shown below:\\\n", + "$x_{k} = x_{k-1} - x_{k}^{*}$\\\n", + "$x_{k}^{*} = {[\\mathbf{J}(x_{k})]}^{-1} \\cdot \\mathbf{f}(x_{k})$\n", + "\n", + "And $x_{k}^{*} $ is solution to the system of equation defined as ${\\mathbf{J}(x_{k})}~ x_{k}^{*} = \\mathbf{f}(x_{k})$\n", + "\n", + "---\n", + "\n", + "We can then use NumPy's `linalg.solve` API to perform the linear solve as shown below. And we can see that the algorithm converges to the same solution in exactly the same number of iteration" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "id": "11527885-0be6-4ebf-80fa-9dec85bb0c3c", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\n", + "Newton's method converged in 7 iterations to xk: [-2. 3.]\n" + ] + } + ], + "source": [ + "# number of iterations to try\n", + "niters = 20\n", + "\n", + "# tolerance that sets the accuracy of solution\n", + "tol = 1e-6\n", + "\n", + "# print additional information \n", + "verbose = False\n", + "\n", + "# initial guess\n", + "xk = np.array([-20.0, 20.0])\n", + "\n", + "# Newton's method \n", + "for iter in range(niters):\n", + " xk_old = xk\n", + "\n", + " if verbose:\n", + " print(f\"iter: {iter}, xk: {xk}\")\n", + " xk = xk - np.linalg.solve(jacobian(xk), function(xk)) ## This uses linalg.solve\n", + " \n", + " l2_norm = np.linalg.norm((xk - xk_old))\n", + " if l2_norm < tol:\n", + " break\n", + " \n", + "# let the user know if the solution converged or not\n", + "if iter == niters - 1:\n", + " print(f\"\\nNewton's method did not converge for this function, tolerance ({tol}) and number of iterations ({niters})\")\n", + "else:\n", + " print(f\"\\nNewton's method converged in {iter} iterations to xk: {xk}\")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "0c9f494c-518a-4f78-9e88-1aeb2221fa1b", + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "metadata": { + "kernelspec": { + "display_name": "python3 (legate) *", + "language": "python", + "name": "conda-env-legate-py" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.10.13" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/docs/cunumeric/source/user/notebooks/stencil.ipynb b/docs/cunumeric/source/examples/stencil.ipynb similarity index 100% rename from docs/cunumeric/source/user/notebooks/stencil.ipynb rename to docs/cunumeric/source/examples/stencil.ipynb diff --git a/docs/cunumeric/source/faqs.rst b/docs/cunumeric/source/faqs.rst new file mode 100644 index 0000000000..89caa30666 --- /dev/null +++ b/docs/cunumeric/source/faqs.rst @@ -0,0 +1,177 @@ +.. _faqs: + +Frequently Asked Questions +========================== + + +What are the different task variants available in Legate? +--------------------------------------------------------- + +Legate offers three different task variants: CPU, OMP, and GPU. A task variant +determines the type of processor Legate chooses to perform the computations. + +What is the difference between Legate and cuNumeric? +---------------------------------------------------- + +Legate is a task-based runtime software stack that enables development of +scalable and composable libraries for distributed and accelerated computing. + +cuNumeric is one of the foundational libraries built using Legate and aspires +to be a distributed and accelerated drop-in replacement library for NumPy, an +array programming library widely used in scientific computing. cuNumeric scales +idiomatic NumPy programs to multiple GPUs and CPUs and seamlessly interoperates +with other Legate libraries. + +Check out this `blog post `_ +to learn more about cuNumeric. + +When to use python vs legate? +----------------------------- + +The ``legate`` launcher affords comman line options for configurtion, while +using ``python`` requires configuring via ``LEGATE_CONFIG``. When running +local applications, it is mostly a matter of preference. When running in +multi-node situations, ``legate`` has some additional command line options +that may make usage simpler. + +What if I don’t have a GPU? +--------------------------- + +If you don’t have a GPU, you can either use the CPU or the OMP variant. See +`Resource allocation` for informations on how to use the respective variants. + +What does this warning mean? +---------------------------- + +.. code-block:: text + + RuntimeWarning: cuNumeric has not implemented and is falling back to canonical NumPy. You may notice significantly decreased performance for this function call. + +This means that the NumPy has not been implemented in cuNumeric and that +the Legate runtime is falling back to using NumPy’s implementation which will +be single-threaded execution and can lead to decreased performance for that +function call. + +.. code-block:: text + + [0 - 7f0524da9740] 0.000028 {4}{threads}: reservation ('dedicated worker (generic) #1') cannot be satisfied + +or + +.. code-block:: text + + [0 - 7fe90fa7d740] 0.000029 {4}{threads}: reservation ('utility proc 1d00000000000001') cannot be satisfied + +This indicates that the runtime was unable to pin threads onto available cores, +which usually means that the available CPU cores were oversubscribed because +the user has requested more cores than is available. + +If the user does not specify which type of processor to run on, legate will use +4 CPUs to execute the program. Legate will also need one core to perform the +dependency analysis and schedule the tasks. If there are fewer than five cores +on the machine, try reducing the number of cores (``--cpus``) passed to legate. + +This warning is currently expected on MacOS. + +How to handle Out-Of-Memory errors? +----------------------------------- + +.. code-block:: text + + [0 - 7fb9fc426000] 0.985000 {5}{cunumeric.mapper}: Mapper cunumeric on Node 0 failed to allocate 144000000 bytes on memory 1e00000000000000 (of kind SYSTEM_MEM: Visible to all processors on a node) for region requirement 1 of Task cunumeric::WhereTask[./script.py:90] (UID 39). + +The above error indicates that the application ran out of memory during +execution. More granular details on the type of memory, the task that triggered +the error are provided in the error message, but this usually indicates that +resources (add more cores/threads/ GPUs, or increase the amount of system +memory or framebuffer memory) or decrease the problem size and confirm that you +are able to run the program to completion. + +Reducing the ``--eager-alloc-percentage`` to, say, 10 or less can also help +since this reduces the amount of available memory available to the eager memory +pool and will consequently increase the memory reserved for the deferred memory +pool. + +Why are the results different from NumPy? +----------------------------------------- + +While a majority of the APIs will give the same result as NumPy, some APIs +might be implemented differently from that of NumPy which might lead to +differences in results. One such example is, :ref:`reshape`, which returns a +copy of the array in cuNumeric but returns a view in NumPy. Such differences +in implementation are noted in the documentation of the cuNumeric APIs, please +review them before opening an issue on `cuNumeric issue tracker `_. + +Why doesn’t Legate use my GPU? +------------------------------ + +If you explicitly asked legate to use the GPU but find that the GPU is not +being used, it is possible that your problem size is too small to be run on +GPU and be performant. Either increase your problem size significantly or set +the environment variable ``LEGATE_TEST`` to 1 and run. Setting this environment +variable tells Legate to always use the prescribed resources regardless of the +problem size. + +What are the anti-patterns in a NumPy code? +------------------------------------------- + +Check out our :ref:`practices` to avoid some of the anti-patterns commonly +encountered in applications. + +How do I time the execution of my application? +---------------------------------------------- + +Check out the :ref:`benchmarking` section for information on how to accurately +measure cuNumeric execution. + +Why is cuNumeric slower than NumPy on my laptop? +------------------------------------------------ + +For small problem sizes, cuNumeric might be slower than NumPy. We suggest you +increase the problem size and correspondingly increase the resources needed +for the problem size as described in the Usage section. Take a look at our +:ref:`practices` on how to do that. + +Why is cuNumeric slower than cuPy on my laptop? +----------------------------------------------- + +For small problem sizes, cuNumeric might be slower than cuPy. We suggest you +increase the problem size and correspondingly increase the resources needed for +the problem size as described in the :ref:`Usage` section. Take a look at +performance :ref:`practices`. + +How do I use Jupyter Notebooks? +------------------------------- + +Notebooks are useful for experimentation and evaluation on a single node. + +How to pass Legion and Realm arguments? +--------------------------------------- + +See :ref:`advanced`. + +What is the version of legate? +------------------------------ + +Use ``legate-issue`` to know more about the version of Legate, Legion and +several other key packages. + +You can also run ``legate –verbose ./script.py `` to get +verbose output. + +What are the defaults? +---------------------- + +The default values for several input arguments to Legate are mentioned in +Legate's documentation. + +Are there resources where I can read more about Legate? +------------------------------------------------------- + +Check out this `blog post `_ +to learn more about cuNumeric. + +Other questions? +---------------- + +Follow us on `GitHub `_ or reach out to us there. diff --git a/docs/cunumeric/source/index.rst b/docs/cunumeric/source/index.rst index ace34f0e90..dcbb005aa8 100644 --- a/docs/cunumeric/source/index.rst +++ b/docs/cunumeric/source/index.rst @@ -12,12 +12,14 @@ Using cuNumeric you do things like run the final example of the `DGX SuperPOD`_ and achieve good weak scaling. .. toctree:: - :maxdepth: 2 + :maxdepth: 1 :caption: Contents: + installation user/index - comparison/index + examples/index api/index + faqs developer/index .. toctree:: diff --git a/docs/cunumeric/source/user/installation.rst b/docs/cunumeric/source/installation.rst similarity index 74% rename from docs/cunumeric/source/user/installation.rst rename to docs/cunumeric/source/installation.rst index 698259b2e4..1562f4359e 100644 --- a/docs/cunumeric/source/user/installation.rst +++ b/docs/cunumeric/source/installation.rst @@ -1,6 +1,29 @@ Installation ============ +Requirements +------------ + +cuNumeric has the following runtime dependencies: + +- cuda-cudart +- cuda-version +- cutensor +- legate-core +- libcublas +- libcufft +- libcusolver +- libcusparse +- libnvjitlink +- libopenblas +- numpy +- opt_einsum +- scipy +- typing_extensions + +Default conda install +--------------------- + Linux-64 packages for cuNumeric are available from `conda `_ on the `legate channel `_. @@ -20,6 +43,9 @@ from the cuNumeric repository, for instance: Running black scholes on 10K options... Elapsed Time: 129.017 ms +Manual CPU-only packages +------------------------ + The default package contains GPU support, and is compatible with CUDA >= 11.8 (CUDA driver version >= r520), and Volta or later GPU architectures. There are also CPU-only packages available, and will be automatically selected by conda @@ -31,4 +57,7 @@ You can force installation of a CPU-only package by requesting it as follows: conda ... cunumeric=*=*_cpu +Building from source +--------------------- + See :ref:`building cunumeric from source` for instructions on building cuNumeric manually. diff --git a/docs/cunumeric/source/user/advanced.rst b/docs/cunumeric/source/user/advanced.rst new file mode 100644 index 0000000000..2fdd96d974 --- /dev/null +++ b/docs/cunumeric/source/user/advanced.rst @@ -0,0 +1,42 @@ +.. _advanced: + +Advanced topics +=============== + +Multi-node execution +-------------------- + +Using ``legate`` +~~~~~~~~~~~~~~~~ + +Cunumeric programs can be run in parallel by using the ``--nodes`` option to +the ``legate`` driver, followed by the number of nodes to be used. +When running on 2+ nodes, a task launcher must be specified. + +Legate currently supports using ``mpirun``, ``srun``, and ``jsrun`` as task +launchers for multi-node execution via the ``--launcher`` command like +arguments: + +.. code-block:: sh + + legate --launcher srun --nodes 2 script.py + +{% endblock %} + +{% block footer %} + + + +{% endblock %} From 29f18aa1694a4e0e5a85684c70084c1f749663ea Mon Sep 17 00:00:00 2001 From: Marcin Zalewski Date: Tue, 17 Sep 2024 18:27:44 -0700 Subject: [PATCH 310/462] Build packages for different Python versions (#283) --- .github/workflows/ci-gh-nightly-release.yml | 7 +++++- .github/workflows/ci-gh-release.yml | 4 ++- .github/workflows/gh-build-and-test.yml | 28 +++++++++++++-------- cmake/versions.json | 1 - 4 files changed, 26 insertions(+), 14 deletions(-) diff --git a/.github/workflows/ci-gh-nightly-release.yml b/.github/workflows/ci-gh-nightly-release.yml index d0597bd470..0b214d2c63 100644 --- a/.github/workflows/ci-gh-nightly-release.yml +++ b/.github/workflows/ci-gh-nightly-release.yml @@ -23,12 +23,17 @@ jobs: upload-enabled: - true - false + python-version: + - "3.10" + - "3.11" + - "3.12" uses: ./.github/workflows/gh-build-and-test.yml with: build-type: release + dependencies-workflow: ci-gh-nightly-release.yml platform: ${{ matrix.platform }} - python-version: "3.10" + python-version: ${{ matrix.python-version }} target-device: ${{ matrix.target-device }} upload-enabled: ${{ matrix.upload-enabled }} waive-gpu-tests: ${{ github.workflow == 'Build Release package' && matrix.platform == 'linux-aarch64' }} diff --git a/.github/workflows/ci-gh-release.yml b/.github/workflows/ci-gh-release.yml index a6930c4504..ef0ca408bd 100644 --- a/.github/workflows/ci-gh-release.yml +++ b/.github/workflows/ci-gh-release.yml @@ -1,7 +1,7 @@ name: Build Release package concurrency: - group: ci-nightly-release-on-${{ github.event_name }}-from-${{ github.ref_name }} + group: ci-release-on-${{ github.event_name }}-from-${{ github.ref_name }} cancel-in-progress: true on: @@ -25,6 +25,8 @@ jobs: - cpu upload-enabled: - false + python-version: + - "3.12" exclude: - platform: linux-aarch64 target-device: gpu diff --git a/.github/workflows/gh-build-and-test.yml b/.github/workflows/gh-build-and-test.yml index b05de69fc3..975eae822b 100644 --- a/.github/workflows/gh-build-and-test.yml +++ b/.github/workflows/gh-build-and-test.yml @@ -18,8 +18,13 @@ on: type: boolean description: Waive GPU tests based on specific configuration python-version: + required: false type: string + default: "3.12" + dependencies-workflow: required: false + type: string + default: ci-gh.yml jobs: setup-build: @@ -44,15 +49,16 @@ jobs: build: needs: setup-build - name: "Build (${{ inputs.platform }}, ${{ inputs.target-device }}, ${{ inputs.build-type }})" + name: "Build (${{ inputs.platform }}, ${{ inputs.target-device }}, ${{ inputs.build-type }}, Python ${{ inputs.python-version }})" uses: - nv-legate/legate-gh-ci/.github/workflows/gh-build.yml@v1.14 + nv-legate/legate-gh-ci/.github/workflows/gh-build.yml@v1.15 with: build-mode: "" build-type: ${{ inputs.build-type }} client-repo: ${{ github.event.repository.name }} dependencies-file: "cmake/versions.json" - legate-gh-ci-tag: "v1.14" + dependencies-workflow: ${{ inputs.dependencies-workflow }} + legate-gh-ci-tag: "v1.15" network: "ucx" platform: ${{ inputs.platform }} python-version: ${{ inputs.python-version }} @@ -66,13 +72,13 @@ jobs: needs: setup-build name: "Check if legate.internal nightly exists for SHA specified in versions.json (${{ inputs.platform }}, ${{ inputs.target-device }}, ${{ inputs.build-type }})" uses: - nv-legate/legate-gh-ci/.github/workflows/gh-check-if-nightly-exists-for-all-dependencies.yml@v1.14 + nv-legate/legate-gh-ci/.github/workflows/gh-check-if-nightly-exists-for-all-dependencies.yml@v1.15 with: build-mode: "" build-type: ${{ inputs.build-type }} client-repo: ${{ github.event.repository.name }} dependencies-file: "cmake/versions.json" - legate-gh-ci-tag: "v1.14" + legate-gh-ci-tag: "v1.15" network: "ucx" platform: ${{ inputs.platform }} python-version: ${{ inputs.python-version }} @@ -86,12 +92,12 @@ jobs: if: ${{ github.repository_owner == 'nv-legate' && contains(github.workflow, 'release') && inputs.upload-enabled == true }} name: Upload package to Server uses: - nv-legate/legate-gh-ci/.github/workflows/gh-upload.yml@v1.14 + nv-legate/legate-gh-ci/.github/workflows/gh-upload.yml@v1.15 with: build-mode: "" build-type: ${{ inputs.build-type }} client-repo: ${{ github.event.repository.name }} - legate-gh-ci-tag: "v1.14" + legate-gh-ci-tag: "v1.15" name: Upload package to Server network: "ucx" pkgSubString: "cunumeric-" @@ -174,13 +180,13 @@ jobs: matrix: ${{fromJson(needs.setup-test.outputs.matrix)}} uses: - nv-legate/legate-gh-ci/.github/workflows/gh-test-within-container.yml@v1.14 + nv-legate/legate-gh-ci/.github/workflows/gh-test-within-container.yml@v1.15 with: build-mode: "" build-type: ${{ inputs.build-type }} client-repo: ${{ github.event.repository.name }} has-gpu: ${{ matrix.runner.type == 'gpu' }} - legate-gh-ci-tag: "v1.14" + legate-gh-ci-tag: "v1.15" name: ${{ matrix.test-config.name }} network: "ucx" platform: ${{ inputs.platform }} @@ -196,12 +202,12 @@ jobs: name: Update Test status on Server if: ${{ (github.repository_owner == 'nv-legate') && contains(github.workflow, 'Nightly') && (inputs.upload-enabled == true) }} uses: - nv-legate/legate-gh-ci/.github/workflows/gh-upload.yml@v1.14 + nv-legate/legate-gh-ci/.github/workflows/gh-upload.yml@v1.15 with: build-mode: "" build-type: ${{ inputs.build-type }} client-repo: ${{ github.event.repository.name }} - legate-gh-ci-tag: "v1.14" + legate-gh-ci-tag: "v1.15" name: UpdateTestStatus network: "ucx" pkgSubString: "cunumeric-" diff --git a/cmake/versions.json b/cmake/versions.json index 8969120f9b..0553e780c2 100644 --- a/cmake/versions.json +++ b/cmake/versions.json @@ -4,7 +4,6 @@ "repo": "legate.core.internal", "artifact_name": "${{ inputs.platform }}-${{ inputs.build-type }}-<>-python${{ inputs.python-version }}-${{ inputs.target-device }}-release-with_tests-${{ inputs.network }}-<>", "org": "nv-legate", - "artifact_workflow": "ci-gh.yml", "nightly_workflow": "ci-gh-nightly-release.yml", "version": "24.09.00", "git_url" : "git@github.com:nv-legate/legate.core.internal.git", From 822af147c3df964694e83b8c2908b3c5aec25a1a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Malte=20F=C3=B6rster?= <97973773+mfoerste4@users.noreply.github.com> Date: Fri, 20 Sep 2024 13:07:51 +0200 Subject: [PATCH 311/462] 2-D matmul (#380) * add memory-friendly 2d matmul looping k * add batched matmul in C-NDArray::dot * bump core and add redundant flag * review suggestions * make cachesize for k-batching configurable * please mypy --- cunumeric/_thunk/deferred.py | 104 ++++++++++++++++++++--- cunumeric/settings.py | 16 ++++ src/cunumeric/cunumeric_c.h | 2 + src/cunumeric/mapper.cc | 19 ++++- src/cunumeric/matrix/matmul_template.inl | 70 +++++++-------- src/cunumeric/ndarray.cc | 61 ++++++++++--- src/cunumeric/runtime.cc | 18 ++++ src/cunumeric/runtime.h | 3 + src/env_defaults.h | 4 + tests/cpp/integration/test_dot.cc | 73 ++++++++++++++++ tests/integration/test_matmul.py | 29 +++++++ tests/unit/cunumeric/test_settings.py | 2 + 12 files changed, 341 insertions(+), 60 deletions(-) create mode 100644 tests/cpp/integration/test_dot.cc diff --git a/cunumeric/_thunk/deferred.py b/cunumeric/_thunk/deferred.py index 9452ca396e..9003fdbf33 100644 --- a/cunumeric/_thunk/deferred.py +++ b/cunumeric/_thunk/deferred.py @@ -41,9 +41,12 @@ align, bloat, broadcast, + constant, + dimension, get_legate_runtime, scale, ) +from legate.settings import settings as legate_settings from legate.utils import OrderedSet from .._utils import is_np2 @@ -79,6 +82,7 @@ if TYPE_CHECKING: import numpy.typing as npt + from legate import LogicalStorePartition from ..config import BitGeneratorType, FFTDirection, FFTType, WindowOpCode from ..types import ( @@ -1560,19 +1564,97 @@ def contract( assert m == rhs1.shape[0] assert n == rhs2.shape[1] assert k == rhs2.shape[0] - lhs = lhs.promote(1, k) - rhs1 = rhs1.promote(2, n) - rhs2 = rhs2.promote(0, m) - task = legate_runtime.create_auto_task( - self.library, CuNumericOpCode.MATMUL + def rounding_divide( + lhs: tuple[int, ...], rhs: tuple[int, ...] + ) -> tuple[int, ...]: + return tuple( + (lh + rh - 1) // rh for (lh, rh) in zip(lhs, rhs) + ) + + # TODO: better heuristics + def choose_2d_color_shape( + shape: tuple[int, int] + ) -> tuple[int, int]: + # 1M elements, we should probably even go larger + MIN_MATRIX_SIZE = 1 << 20 + # If the matrix is too small don't partition it at all + if (not legate_settings.test()) and shape[0] * shape[ + 1 + ] <= MIN_MATRIX_SIZE: + return (1, 1) + + # start with 1D and re-balance by powers of 2 + # (don't worry about other primes) + color_shape = (runtime.num_procs, 1) + while ( + shape[0] / color_shape[0] + < 2 * shape[1] / color_shape[1] + and color_shape[0] % 2 == 0 + ): + color_shape = (color_shape[0] // 2, color_shape[1] * 2) + + return color_shape + + # TODO: better heuristics? + def choose_batchsize( + tilesize: tuple[int, int], k: int, itemsize: int + ) -> int: + # default corresponds to 128MB (to store A and B tile) + from ..settings import settings + + max_elements_per_tile = ( + settings.matmul_cache_size() // itemsize + ) + total_elements_rhs = (tilesize[0] + tilesize[1]) * k + num_batches = rounding_divide( + (total_elements_rhs,), (max_elements_per_tile,) + )[0] + batch_size = rounding_divide((k,), (num_batches,))[0] + + return batch_size + + # choose color-shape/k_batch_size + initial_color_shape = choose_2d_color_shape((m, n)) + tile_shape = rounding_divide((m, n), initial_color_shape) + color_shape = rounding_divide((m, n), tile_shape) + k_batch_size = choose_batchsize( + tile_shape, k, rhs1_thunk.dtype.itemsize # type: ignore ) - p_lhs = task.add_reduction(lhs, ReductionOpKind.ADD) - p_rhs1 = task.add_input(rhs1) - p_rhs2 = task.add_input(rhs2) - task.add_constraint(align(p_lhs, p_rhs1)) - task.add_constraint(align(p_lhs, p_rhs2)) - task.execute() + k_color = rounding_divide((k,), (k_batch_size,)) + + # initial partition of lhs defined py tile-shape + tiled_lhs = lhs.partition_by_tiling(tile_shape) + tiled_rhs1 = rhs1.partition_by_tiling( + (tile_shape[0], k_batch_size) + ) + tiled_rhs2 = rhs2.partition_by_tiling( + (k_batch_size, tile_shape[1]) + ) + + def run_matmul_for_batch( + tiled_lhs: LogicalStorePartition, + tiled_rhs1: LogicalStorePartition, + tiled_rhs2: LogicalStorePartition, + i: int, + ) -> None: + manual_task = legate_runtime.create_manual_task( + self.library, CuNumericOpCode.MATMUL, color_shape + ) + + manual_task.add_output(tiled_lhs) + manual_task.add_input(tiled_lhs) + manual_task.add_input( + tiled_rhs1, (dimension(0), constant(i)) + ) + manual_task.add_input( + tiled_rhs2, (constant(i), dimension(1)) + ) + + manual_task.execute() + + for i in range(0, k_color[0]): + run_matmul_for_batch(tiled_lhs, tiled_rhs1, tiled_rhs2, i) else: assert False diff --git a/cunumeric/settings.py b/cunumeric/settings.py index 4e87c8f2ff..292699d260 100644 --- a/cunumeric/settings.py +++ b/cunumeric/settings.py @@ -169,5 +169,21 @@ class CunumericRuntimeSettings(Settings): """, ) + matmul_cache_size: EnvOnlySetting[int] = EnvOnlySetting( + "matmul_cache_size", + "CUNUMERIC_MATMUL_CACHE_SIZE", + default=134217728, # 128MB + test_default=4096, # 4KB + convert=convert_int, + help=""" + Force cuNumeric to keep temporary task slices during matmul + computations smaller than this threshold. Whenever the temporary + space needed during computation would exceed this value the task + will be batched over 'k' to fulfill the requirement. + + This is a read-only environment variable setting used by the runtime. + """, + ) + settings = CunumericRuntimeSettings() diff --git a/src/cunumeric/cunumeric_c.h b/src/cunumeric/cunumeric_c.h index 73a909b146..e7bdbca8d1 100644 --- a/src/cunumeric/cunumeric_c.h +++ b/src/cunumeric/cunumeric_c.h @@ -338,6 +338,8 @@ bool cunumeric_has_cusolvermp(); unsigned cunumeric_max_eager_volume(); +unsigned cunumeric_matmul_cache_size(); + struct ReductionOpIds cunumeric_register_reduction_ops(int code); #ifdef __cplusplus diff --git a/src/cunumeric/mapper.cc b/src/cunumeric/mapper.cc index cd8c0e9ea2..711ee0363e 100644 --- a/src/cunumeric/mapper.cc +++ b/src/cunumeric/mapper.cc @@ -64,7 +64,24 @@ std::vector CuNumericMapper::store_mappings( mappings.back().policy().exact = true; return std::move(mappings); } - case CUNUMERIC_MATMUL: + case CUNUMERIC_MATMUL: { + std::vector mappings; + auto inputA = task.input(1); + auto inputB = task.input(2); + + mappings.push_back( + StoreMapping::default_mapping(inputA.data(), options.front(), true /*exact*/)); + mappings.back().policy().redundant = true; + mappings.push_back( + StoreMapping::default_mapping(inputB.data(), options.front(), true /*exact*/)); + mappings.back().policy().redundant = true; + + auto outputC = task.output(0); + mappings.push_back( + StoreMapping::default_mapping(outputC.data(), options.front(), true /*exact*/)); + + return mappings; + } case CUNUMERIC_MATVECMUL: case CUNUMERIC_UNIQUE_REDUCE: { // TODO: Our actual requirements are a little less strict than this; we require each array or diff --git a/src/cunumeric/matrix/matmul_template.inl b/src/cunumeric/matrix/matmul_template.inl index 2b68a7a6e4..2b041f923c 100644 --- a/src/cunumeric/matrix/matmul_template.inl +++ b/src/cunumeric/matrix/matmul_template.inl @@ -58,40 +58,42 @@ struct MatMulImpl { using VAL = type_of; using ACC = typename support_matmul::ACC_TYPE; - // Note that rhs1 and rhs2 may have different shapes. Here's why: rhs1 and rhs2 are promoted - // on one of their dimensions, and in case that the promoted dimension is partitioned, - // the store cannot see that partitioning, because that dimension doesn't map to the store's - // original domain whose partitioning is only what the store can observe. Therefore, we must - // take an intersection of the rhs1's and rhs2's shapes to get a correct "active" area - // in their bloated domains. - auto shape = args.rhs1.shape<3>().intersection(args.rhs2.shape<3>()); - - if (shape.empty()) { + auto shape_lhs = args.lhs.shape<2>(); + auto shape_rhs1 = args.rhs1.shape<2>(); + auto shape_rhs2 = args.rhs2.shape<2>(); + + if (shape_lhs.empty() || shape_rhs1.empty() || shape_rhs2.empty()) { return; } - const auto m = shape.hi[0] - shape.lo[0] + 1; - const auto k = shape.hi[1] - shape.lo[1] + 1; - const auto n = shape.hi[2] - shape.lo[2] + 1; + const auto m = shape_lhs.hi[0] - shape_lhs.lo[0] + 1; + const auto n = shape_lhs.hi[1] - shape_lhs.lo[1] + 1; + const auto k = shape_rhs1.hi[1] - shape_rhs1.lo[1] + 1; + +#ifdef DEBUG_CUNUMERIC + assert(m == shape_rhs1.hi[0] - shape_rhs1.lo[0] + 1); + assert(k == shape_rhs2.hi[0] - shape_rhs2.lo[0] + 1); + assert(n == shape_rhs2.hi[1] - shape_rhs2.lo[1] + 1); +#endif - size_t lhs_strides[3]; - size_t rhs1_strides[3]; - size_t rhs2_strides[3]; + size_t strides_lhs[2]; + size_t strides_rhs1[2]; + size_t strides_rhs2[2]; - auto rhs1 = args.rhs1.read_accessor(shape).ptr(shape, rhs1_strides); - auto rhs2 = args.rhs2.read_accessor(shape).ptr(shape, rhs2_strides); - auto lhs = args.lhs.reduce_accessor, true, 3>(shape).ptr(shape, lhs_strides); + auto rhs1 = args.rhs1.read_accessor(shape_rhs1).ptr(shape_rhs1, strides_rhs1); + auto rhs2 = args.rhs2.read_accessor(shape_rhs2).ptr(shape_rhs2, strides_rhs2); + auto lhs = args.lhs.read_write_accessor(shape_lhs).ptr(shape_lhs, strides_lhs); #ifdef DEBUG_CUNUMERIC - assert(rhs1_strides[2] == 0); - assert(rhs2_strides[0] == 0); - assert(lhs_strides[2] == 1 && lhs_strides[1] == 0); + assert(strides_rhs1[0] == 1 || strides_rhs1[1] == 1); + assert(strides_rhs2[0] == 1 || strides_rhs2[1] == 1); + assert(strides_lhs[1] == 1); #endif - bool rhs1_transposed; - bool rhs2_transposed; - size_t rhs1_stride = stride_for_blas(m, k, rhs1_strides[0], rhs1_strides[1], rhs1_transposed); - size_t rhs2_stride = stride_for_blas(k, n, rhs2_strides[1], rhs2_strides[2], rhs2_transposed); + bool transposed_rhs1; + bool transposed_rhs2; + size_t stride_rhs1 = stride_for_blas(m, k, strides_rhs1[0], strides_rhs1[1], transposed_rhs1); + size_t stride_rhs2 = stride_for_blas(k, n, strides_rhs2[0], strides_rhs2[1], transposed_rhs2); MatMulImplBody()(m, n, @@ -99,12 +101,12 @@ struct MatMulImpl { lhs, rhs1, rhs2, - lhs_strides[0], - rhs1_stride, - rhs2_stride, - rhs1_transposed, - rhs2_transposed, - args.lhs.is_readable()); + strides_lhs[0], + stride_rhs1, + stride_rhs2, + transposed_rhs1, + transposed_rhs2, + /*args.lhs.is_readable()*/ false); } template ::value>* = nullptr> @@ -117,10 +119,10 @@ struct MatMulImpl { template static void matmul_template(TaskContext& context) { - auto reductions = context.reductions(); - auto inputs = context.inputs(); + auto outputs = context.outputs(); + auto inputs = context.inputs(); - MatMulArgs args{reductions[0], inputs[0], inputs[1]}; + MatMulArgs args{outputs[0], inputs[1], inputs[2]}; // Note that we can't dispatch on the lhs's type, // as the lhs can have a different type than the rhs' type_dispatch(args.rhs1.code(), MatMulImpl{}, args); diff --git a/src/cunumeric/ndarray.cc b/src/cunumeric/ndarray.cc index e14eb1400d..01a65f6b6f 100644 --- a/src/cunumeric/ndarray.cc +++ b/src/cunumeric/ndarray.cc @@ -16,6 +16,7 @@ #include "cunumeric/ndarray.h" #include +#include #include "cunumeric/binary/binary_op_util.h" #include "cunumeric/operators.h" @@ -57,6 +58,15 @@ struct check_nonzero_scalar_fn { } }; +struct get_typesize_fn { + template + uint64_t operator()() + { + using VAL = legate::type_of; + return uint64_t(sizeof(VAL)); + } +}; + struct generate_identity_fn { template struct generator { @@ -542,6 +552,8 @@ void NDArray::unary_reduction(int32_t op_code_, NDArray input) runtime->submit(std::move(task)); } +uint64_t ceildiv(uint64_t a, uint64_t b) { return (a + b - 1) / b; } + void NDArray::dot(NDArray rhs1, NDArray rhs2) { if (size() == 0) { @@ -558,20 +570,41 @@ void NDArray::dot(NDArray rhs1, NDArray rhs2) auto n = rhs2.shape()[1]; auto k = rhs1.shape()[1]; - auto lhs_s = store_.promote(1, k); - auto rhs1_s = rhs1.store_.promote(2, n); - auto rhs2_s = rhs2.store_.promote(0, m); - - auto task = runtime->create_task(CuNumericOpCode::CUNUMERIC_MATMUL); - - auto p_lhs = task.add_reduction(lhs_s, get_reduction_op(UnaryRedCode::SUM)); - auto p_rhs1 = task.add_input(rhs1_s); - auto p_rhs2 = task.add_input(rhs2_s); - - task.add_constraint(align(p_lhs, p_rhs1)); - task.add_constraint(align(p_rhs1, p_rhs2)); - - runtime->submit(std::move(task)); + // compute tilesize for lhs and batch_size for k + // TODO make generic + std::vector initial_tile_shape = {512, 512}; + + legate::tuple color_shape = {ceildiv(m, initial_tile_shape[0]), + ceildiv(n, initial_tile_shape[1])}; + std::vector tile_shape = {ceildiv(m, color_shape[0]), ceildiv(n, color_shape[1])}; + + auto get_batchsize = [&](const std::vector& tilesize, std::uint64_t k) { + uint64_t typesize = legate::type_dispatch(type().code(), get_typesize_fn{}); + // default corresponds to 128MB (to store A and B tile) + uint64_t max_elements_per_tile = cunumeric_matmul_cache_size() / typesize; + uint64_t total_elements_rhs = (tilesize[0] + tilesize[1]) * k; + uint64_t num_batches = ceildiv(total_elements_rhs, max_elements_per_tile); + uint64_t batch_size = ceildiv(k, num_batches); + return batch_size; + }; + std::uint64_t k_batch_size = get_batchsize(tile_shape, k); + + std::vector tile_shape_rhs1 = {tile_shape[0], k_batch_size}; + std::vector tile_shape_rhs2 = {k_batch_size, tile_shape[1]}; + auto color_k = ceildiv(k, k_batch_size); + + auto p_lhs = store_.partition_by_tiling(tile_shape); + auto p_rhs1 = rhs1.store_.partition_by_tiling(tile_shape_rhs1); + auto p_rhs2 = rhs2.store_.partition_by_tiling(tile_shape_rhs2); + + for (std::uint64_t i = 0; i < color_k; ++i) { + auto task = runtime->create_task(CuNumericOpCode::CUNUMERIC_MATMUL, color_shape); + task.add_output(p_lhs); + task.add_input(p_lhs); + task.add_input(p_rhs1, legate::SymbolicPoint{legate::dimension(0), legate::constant(i)}); + task.add_input(p_rhs2, legate::SymbolicPoint{legate::constant(i), legate::dimension(1)}); + runtime->submit(std::move(task)); + } } void NDArray::arange(Scalar start, Scalar stop, Scalar step) diff --git a/src/cunumeric/runtime.cc b/src/cunumeric/runtime.cc index 03bafdfeb6..ff6afc92fc 100644 --- a/src/cunumeric/runtime.cc +++ b/src/cunumeric/runtime.cc @@ -86,8 +86,19 @@ legate::AutoTask CuNumericRuntime::create_task(CuNumericOpCode op_code) return legate_runtime_->create_task(library_, legate::LocalTaskID{op_code}); } +legate::ManualTask CuNumericRuntime::create_task(CuNumericOpCode op_code, + const legate::tuple& launch_shape) +{ + return legate_runtime_->create_task(library_, legate::LocalTaskID{op_code}, launch_shape); +} + void CuNumericRuntime::submit(legate::AutoTask&& task) { legate_runtime_->submit(std::move(task)); } +void CuNumericRuntime::submit(legate::ManualTask&& task) +{ + legate_runtime_->submit(std::move(task)); +} + uint32_t CuNumericRuntime::get_next_random_epoch() { return next_epoch_++; } /*static*/ CuNumericRuntime* CuNumericRuntime::get_runtime() { return runtime_; } @@ -154,4 +165,11 @@ unsigned cunumeric_max_eager_volume() return min_cpu_chunk; } +unsigned cunumeric_matmul_cache_size() +{ + static const auto max_cache_size = cunumeric::extract_env( + "CUNUMERIC_MATMUL_CACHE_SIZE", MATMUL_CACHE_SIZE_DEFAULT, MATMUL_CACHE_SIZE_TEST); + return max_cache_size; +} + } // extern "C" diff --git a/src/cunumeric/runtime.h b/src/cunumeric/runtime.h index 31fb8d89dd..ce8fa49247 100644 --- a/src/cunumeric/runtime.h +++ b/src/cunumeric/runtime.h @@ -45,7 +45,10 @@ class CuNumericRuntime { public: legate::AutoTask create_task(CuNumericOpCode op_code); + legate::ManualTask create_task(CuNumericOpCode op_code, + const legate::tuple& launch_shape); void submit(legate::AutoTask&& task); + void submit(legate::ManualTask&& task); public: uint32_t get_next_random_epoch(); diff --git a/src/env_defaults.h b/src/env_defaults.h index c9a89963af..ec63f96027 100644 --- a/src/env_defaults.h +++ b/src/env_defaults.h @@ -30,3 +30,7 @@ // 1 << 13 (need actual number for python to parse) #define MIN_OMP_CHUNK_DEFAULT 8192 #define MIN_OMP_CHUNK_TEST 2 + +// 1 << 27 (need actual number for python to parse) +#define MATMUL_CACHE_SIZE_DEFAULT 134217728 +#define MATMUL_CACHE_SIZE_TEST 4096 diff --git a/tests/cpp/integration/test_dot.cc b/tests/cpp/integration/test_dot.cc new file mode 100644 index 0000000000..f38f646b7d --- /dev/null +++ b/tests/cpp/integration/test_dot.cc @@ -0,0 +1,73 @@ +/* Copyright 2024 NVIDIA Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +#include +#include +#include +#include + +#include +#include "legate.h" +#include "cunumeric.h" +#include "common_utils.h" + +using namespace cunumeric; + +namespace { + +template +auto test_standard(uint64_t m, uint64_t n, uint64_t k, legate::Type leg_type) +{ + std::vector data_a(m * k); + std::vector data_b(n * k); + std::iota(data_a.begin(), data_a.end(), 0); + std::iota(data_b.begin(), data_b.end(), 0.0); + + auto A = cunumeric::zeros({m, k}, leg_type); + auto B = cunumeric::zeros({k, n}, leg_type); + + assign_values_to_array(A, data_a.data(), m * k); + assign_values_to_array(B, data_b.data(), n * k); + + auto C = dot(A, B); + std::vector exp_shape = {m, n}; + EXPECT_EQ(C.type(), leg_type); + EXPECT_EQ(C.shape(), exp_shape); +} + +TEST(Dot, Standard) +{ + test_standard(124, 95, 30, legate::float32()); + test_standard(124, 95, 30, legate::float64()); +} + +TEST(Dot, Complex) +{ + test_standard>(124, 95, 30, legate::complex64()); + test_standard>(124, 95, 30, legate::complex128()); +} + +TEST(Dot, Large) +{ + // activate tiling (m,n) and/or batching (k) + test_standard(513, 12, 4, legate::float32()); + test_standard(12, 518, 30, legate::float32()); + test_standard(513, 513, 30, legate::float32()); + test_standard(512, 512, 4097, legate::float64()); + test_standard(1024, 1024, 4097, legate::float64()); +} + +} // namespace diff --git a/tests/integration/test_matmul.py b/tests/integration/test_matmul.py index bbb2aa4867..f80c50d966 100644 --- a/tests/integration/test_matmul.py +++ b/tests/integration/test_matmul.py @@ -95,6 +95,35 @@ def test_inplace_operator(a_shape, b_shape): assert allclose(np_a, num_a) +@pytest.mark.parametrize( + "mnk", + ( + (134, 5, 45), + (134, 66, 7), + (26, 154, 45), + (46, 154, 5), + (13, 5, 45), + (4, 15, 45), + (1, 5, 45), + (34, 5, 1), + (1, 5, 45), + (1, 1, 5), + (1, 5, 1), + (1, 1, 1), + (5, 1, 1), + ), +) +def test_2d_matmul(mnk): + assert len(mnk) == 3 + a_shape = (mnk[0], mnk[2]) + b_shape = (mnk[2], mnk[1]) + np_a = np.random.random(a_shape) + np_b = np.random.random(b_shape) + num_a = num.array(np_a) + num_b = num.array(np_b) + assert allclose(np_a @ np_b, num_a @ num_b) + + class TestMatmulErrors: @pytest.mark.parametrize( "shapesAB", diff --git a/tests/unit/cunumeric/test_settings.py b/tests/unit/cunumeric/test_settings.py index e830700860..66cee2eb72 100644 --- a/tests/unit/cunumeric/test_settings.py +++ b/tests/unit/cunumeric/test_settings.py @@ -34,6 +34,7 @@ "min_cpu_chunk", "min_omp_chunk", "force_thunk", + "matmul_cache_size", ) _settings_with_test_defaults = ( @@ -42,6 +43,7 @@ "min_gpu_chunk", "min_cpu_chunk", "min_omp_chunk", + "matmul_cache_size", ) ENV_HEADER = Path(__file__).parents[3] / "src" / "env_defaults.h" From cc904b1349e0a72915ef5bd7577c904e8969f632 Mon Sep 17 00:00:00 2001 From: Jacob Faibussowitsch Date: Mon, 23 Sep 2024 16:37:08 -0400 Subject: [PATCH 312/462] Fix `install.py` for legate src-layout (#404) * Fix install.py for legate src-layout --- install.py | 54 ++++++++++++++++++++++++++++++++++++++++++++---------- 1 file changed, 44 insertions(+), 10 deletions(-) diff --git a/install.py b/install.py index e6474a4533..5f52484754 100755 --- a/install.py +++ b/install.py @@ -14,6 +14,7 @@ # See the License for the specific language governing permissions and # limitations under the License. # +from __future__ import annotations import argparse import multiprocessing @@ -22,6 +23,7 @@ import shutil import subprocess import sys +from pathlib import Path # Flush output on newlines sys.stdout.reconfigure(line_buffering=True) @@ -125,6 +127,46 @@ def was_previously_built_with_different_build_isolation( return False +def find_legate_cmake_dir() -> Path: + r"""Try to determine the location of legate cmake files. + + Returns + ------- + Path + The directory containing the legate cmake files. + + Raises + ------ + RuntimeError + If legate cmake directory could not be found. + """ + try: + import legate.install_info as lg_install_info + except (ImportError, ModuleNotFoundError) as e: + raise RuntimeError( + "Cannot determine Legate install directory. Please make sure " + "Legate is installed in the current Python environment." + ) from e + + path = Path(lg_install_info.libpath).resolve() + if (path / "cmake" / "legate").exists(): + # If this exists, then we were installed normally into a python or + # conda env. + return path + + # Possibly installed in an editable installation, in which case legate-config.cmake + # and friends will live in the root binary directory. + root_path = path.root + assert isinstance(root_path, str) + while not any(p.name == "legate-config.cmake" for p in path.iterdir()): + path = path.parent + if str(path) == root_path: + raise RuntimeError( + "Could not determine directory containing legate CMake files" + ) + return path + + def install_cunumeric( arch, build_isolation, @@ -233,15 +275,7 @@ def validate_path(path): cutensor_dir = validate_path(cutensor_dir) openblas_dir = validate_path(openblas_dir) - try: - import legate.install_info as lg_install_info - except ImportError: - raise RuntimeError( - "Cannot determine Legate install directory. Please make sure " - "Legate is installed in the current Python environment." - ) - - legate_dir = dirname(lg_install_info.libpath) + legate_dir = find_legate_cmake_dir() if verbose: print("cuda_dir: ", cuda_dir) @@ -380,7 +414,7 @@ def validate_path(path): if cuda and curand_dir is not None: cmake_flags += ["-Dcunumeric_cuRAND_INCLUDE_DIR=%s" % curand_dir] - cmake_flags += ["-Dlegate_ROOT=%s" % legate_dir] + cmake_flags += ["-Dlegate_ROOT=%s" % str(legate_dir)] cmake_flags += ["-DCMAKE_BUILD_PARALLEL_LEVEL=%s" % thread_count] cmake_flags += extra_flags From 5f82aafd10a6aa7ebfad67a4af2d764e48545b77 Mon Sep 17 00:00:00 2001 From: Andrei Schaffer <37386037+aschaffer@users.noreply.github.com> Date: Tue, 24 Sep 2024 18:27:44 -0500 Subject: [PATCH 313/462] STL random engine for MacOS (#195) * Chunk of touched files to address MacOs. * Fix for new macro dependency with legate.core. * Replacing macro name by something more cunumeric specific. * Addressed review comment on using string_view. * Addressed review comment on using curand_help. * Addressed review comment on removing dead code. * Addressed review comment on ignored cnstr param. * Addressed review comment on return a. * Addressed review comment on f_(std::move(f)). * Addressed review comment on inline constexpr. * Fixed linker issues. * Fixed Cauchy tan(pi/2) case. * Better solution for Cauchy's tan(pi/2) issue. * Minor test fixes * Addressed reviews on static_assert for double generators. * Addressed reviews on _not_ using std::invoke(). * Addressed reviews on LEGATE_ABORT(). * Addressed reviews on better macro definition. * Disabling tests for which problems are elsewhere. --------- Co-authored-by: Manolis Papadakis --- src/cunumeric/random/bitgenerator.cc | 21 +- src/cunumeric/random/bitgenerator.cu | 12 + src/cunumeric/random/bitgenerator_curand.inl | 111 ++++---- src/cunumeric/random/curand_help.h | 24 +- src/cunumeric/random/randutil/generator.h | 76 ++--- .../random/randutil/generator_cauchy.inl | 16 +- .../random/randutil/generator_create.inl | 38 ++- .../random/randutil/generator_exponential.inl | 6 +- .../random/randutil/generator_gumbel.inl | 16 +- .../random/randutil/generator_host.cc | 53 ++-- .../randutil/generator_host_advanced.cc | 112 ++++---- .../generator_host_straightforward.cc | 82 +++--- .../random/randutil/generator_integers.inl | 51 ++-- .../random/randutil/generator_laplace.inl | 6 +- .../random/randutil/generator_logistic.inl | 16 +- .../random/randutil/generator_lognormal.inl | 6 +- .../randutil/generator_negative_binomial.inl | 4 +- .../random/randutil/generator_normal.inl | 6 +- .../random/randutil/generator_pareto.inl | 10 +- .../random/randutil/generator_poisson.inl | 4 +- .../random/randutil/generator_power.inl | 6 +- .../random/randutil/generator_raw.inl | 4 +- .../random/randutil/generator_rayleigh.inl | 6 +- .../random/randutil/generator_triangular.inl | 26 +- .../random/randutil/generator_uniform.inl | 8 +- .../random/randutil/generator_wald.inl | 10 +- .../random/randutil/generator_weibull.inl | 18 +- .../random/randutil/random_distributions.h | 148 +++------- src/cunumeric/random/randutil/randomizer.h | 143 ++++++++++ src/cunumeric/random/randutil/randutil.h | 264 +++++++++--------- .../random/randutil/randutil_curand.h | 6 +- src/cunumeric/random/rnd_aliases.h | 75 +++++ src/cunumeric/random/rnd_types.h | 63 +++++ tests/integration/test_random.py | 25 +- tests/integration/test_random_bitgenerator.py | 2 +- tests/integration/test_random_creation.py | 1 - 36 files changed, 907 insertions(+), 568 deletions(-) create mode 100644 src/cunumeric/random/randutil/randomizer.h create mode 100644 src/cunumeric/random/rnd_aliases.h create mode 100644 src/cunumeric/random/rnd_types.h diff --git a/src/cunumeric/random/bitgenerator.cc b/src/cunumeric/random/bitgenerator.cc index cef9c80599..eb8cffd790 100644 --- a/src/cunumeric/random/bitgenerator.cc +++ b/src/cunumeric/random/bitgenerator.cc @@ -14,10 +14,18 @@ * */ +// MacOS host variant: +// +#if defined(__APPLE__) && defined(__MACH__) +#define CUNUMERIC_USE_STL_RANDOM_ENGINE +#endif + #include "cunumeric/random/bitgenerator.h" #include "cunumeric/random/bitgenerator_template.inl" #include "cunumeric/random/bitgenerator_util.h" +#include "cunumeric/random/rnd_types.h" + #include "cunumeric/random/curand_help.h" #include "cunumeric/random/randutil/randutil.h" @@ -31,7 +39,17 @@ static Logger log_curand("cunumeric.random"); Logger& randutil_log() { return log_curand; } -void randutil_check_curand(curandStatus_t error, const char* file, int line) +#ifdef CUNUMERIC_USE_STL_RANDOM_ENGINE +void randutil_check_status(rnd_status_t error, std::string_view file, int line) +{ + if (error) { + randutil_log().fatal() << "Internal random engine failure with error " << (int)error + << " in file " << file << " at line " << line; + assert(false); + } +} +#else +void randutil_check_curand(curandStatus_t error, std::string_view file, int line) { if (error != CURAND_STATUS_SUCCESS) { randutil_log().fatal() << "Internal CURAND failure with error " << (int)error << " in file " @@ -39,6 +57,7 @@ void randutil_check_curand(curandStatus_t error, const char* file, int line) assert(false); } } +#endif struct CPUGenerator : public CURANDGenerator { CPUGenerator(BitGeneratorType gentype, uint64_t seed, uint64_t generatorId, uint32_t flags) diff --git a/src/cunumeric/random/bitgenerator.cu b/src/cunumeric/random/bitgenerator.cu index 9e16e1f780..4471964d45 100644 --- a/src/cunumeric/random/bitgenerator.cu +++ b/src/cunumeric/random/bitgenerator.cu @@ -23,12 +23,24 @@ #include "cunumeric/random/bitgenerator_curand.inl" +#include #include namespace cunumeric { using namespace legate; +// required by CHECK_CURAND_DEVICE: +// +void randutil_check_curand_device(curandStatus_t error, std::string_view file, int line) +{ + if (error != CURAND_STATUS_SUCCESS) { + randutil_log().fatal() << "Internal CURAND failure with error " << (int)error << " in file " + << file << " at line " << line; + assert(false); + } +} + struct GPUGenerator : public CURANDGenerator { cudaStream_t stream_; GPUGenerator(BitGeneratorType gentype, uint64_t seed, uint64_t generatorId, uint32_t flags) diff --git a/src/cunumeric/random/bitgenerator_curand.inl b/src/cunumeric/random/bitgenerator_curand.inl index 3f0046b944..20c8a6bfb9 100644 --- a/src/cunumeric/random/bitgenerator_curand.inl +++ b/src/cunumeric/random/bitgenerator_curand.inl @@ -25,7 +25,7 @@ #include "cunumeric/random/bitgenerator_template.inl" #include "cunumeric/random/bitgenerator_util.h" -#include "cunumeric/random/curand_help.h" +#include "cunumeric/random/rnd_types.h" #include "cunumeric/random/randutil/randutil.h" namespace cunumeric { @@ -39,7 +39,7 @@ struct CURANDGenerator { randutilGenerator_t gen_; uint64_t seed_; uint64_t generatorId_; - curandRngType type_; + randRngType type_; protected: CURANDGenerator(BitGeneratorType gentype, uint64_t seed, uint64_t generatorId) @@ -55,217 +55,218 @@ struct CURANDGenerator { void generate_raw(uint64_t count, uint32_t* out) { - CHECK_CURAND(::randutilGenerateRawUInt32(gen_, out, count)); + CHECK_RND_ENGINE(::randutilGenerateRawUInt32(gen_, out, count)); } void generate_integer_64(uint64_t count, int64_t* out, int64_t low, int64_t high) { - CHECK_CURAND(::randutilGenerateIntegers64(gen_, out, count, low, high)); + CHECK_RND_ENGINE(::randutilGenerateIntegers64(gen_, out, count, low, high)); } void generate_integer_16(uint64_t count, int16_t* out, int16_t low, int16_t high) { - CHECK_CURAND(::randutilGenerateIntegers16(gen_, out, count, low, high)); + CHECK_RND_ENGINE(::randutilGenerateIntegers16(gen_, out, count, low, high)); } void generate_integer_32(uint64_t count, int32_t* out, int32_t low, int32_t high) { - CHECK_CURAND(::randutilGenerateIntegers32(gen_, out, count, low, high)); + CHECK_RND_ENGINE(::randutilGenerateIntegers32(gen_, out, count, low, high)); } void generate_uniform_64(uint64_t count, double* out, double low, double high) { - CHECK_CURAND(::randutilGenerateUniformDoubleEx(gen_, out, count, low, high)); + CHECK_RND_ENGINE(::randutilGenerateUniformDoubleEx(gen_, out, count, low, high)); } void generate_uniform_32(uint64_t count, float* out, float low, float high) { - CHECK_CURAND(::randutilGenerateUniformEx(gen_, out, count, low, high)); + CHECK_RND_ENGINE(::randutilGenerateUniformEx(gen_, out, count, low, high)); } void generate_lognormal_64(uint64_t count, double* out, double mean, double stdev) { - CHECK_CURAND(::randutilGenerateLogNormalDoubleEx(gen_, out, count, mean, stdev)); + CHECK_RND_ENGINE(::randutilGenerateLogNormalDoubleEx(gen_, out, count, mean, stdev)); } void generate_lognormal_32(uint64_t count, float* out, float mean, float stdev) { - CHECK_CURAND(::randutilGenerateLogNormalEx(gen_, out, count, mean, stdev)); + CHECK_RND_ENGINE(::randutilGenerateLogNormalEx(gen_, out, count, mean, stdev)); } void generate_normal_64(uint64_t count, double* out, double mean, double stdev) { - CHECK_CURAND(::randutilGenerateNormalDoubleEx(gen_, out, count, mean, stdev)); + CHECK_RND_ENGINE(::randutilGenerateNormalDoubleEx(gen_, out, count, mean, stdev)); } void generate_normal_32(uint64_t count, float* out, float mean, float stdev) { - CHECK_CURAND(::randutilGenerateNormalEx(gen_, out, count, mean, stdev)); + CHECK_RND_ENGINE(::randutilGenerateNormalEx(gen_, out, count, mean, stdev)); } void generate_poisson(uint64_t count, uint32_t* out, double lam) { - CHECK_CURAND(::randutilGeneratePoissonEx(gen_, out, count, lam)); + CHECK_RND_ENGINE(::randutilGeneratePoissonEx(gen_, out, count, lam)); } void generate_exponential_64(uint64_t count, double* out, double scale) { - CHECK_CURAND(::randutilGenerateExponentialDoubleEx(gen_, out, count, scale)); + CHECK_RND_ENGINE(::randutilGenerateExponentialDoubleEx(gen_, out, count, scale)); } void generate_exponential_32(uint64_t count, float* out, float scale) { - CHECK_CURAND(::randutilGenerateExponentialEx(gen_, out, count, scale)); + CHECK_RND_ENGINE(::randutilGenerateExponentialEx(gen_, out, count, scale)); } void generate_gumbel_64(uint64_t count, double* out, double mu, double beta) { - CHECK_CURAND(::randutilGenerateGumbelDoubleEx(gen_, out, count, mu, beta)); + CHECK_RND_ENGINE(::randutilGenerateGumbelDoubleEx(gen_, out, count, mu, beta)); } void generate_gumbel_32(uint64_t count, float* out, float mu, float beta) { - CHECK_CURAND(::randutilGenerateGumbelEx(gen_, out, count, mu, beta)); + CHECK_RND_ENGINE(::randutilGenerateGumbelEx(gen_, out, count, mu, beta)); } void generate_laplace_64(uint64_t count, double* out, double mu, double beta) { - CHECK_CURAND(::randutilGenerateLaplaceDoubleEx(gen_, out, count, mu, beta)); + CHECK_RND_ENGINE(::randutilGenerateLaplaceDoubleEx(gen_, out, count, mu, beta)); } void generate_laplace_32(uint64_t count, float* out, float mu, float beta) { - CHECK_CURAND(::randutilGenerateLaplaceEx(gen_, out, count, mu, beta)); + CHECK_RND_ENGINE(::randutilGenerateLaplaceEx(gen_, out, count, mu, beta)); } void generate_logistic_64(uint64_t count, double* out, double mu, double beta) { - CHECK_CURAND(::randutilGenerateLogisticDoubleEx(gen_, out, count, mu, beta)); + CHECK_RND_ENGINE(::randutilGenerateLogisticDoubleEx(gen_, out, count, mu, beta)); } void generate_logistic_32(uint64_t count, float* out, float mu, float beta) { - CHECK_CURAND(::randutilGenerateLogisticEx(gen_, out, count, mu, beta)); + CHECK_RND_ENGINE(::randutilGenerateLogisticEx(gen_, out, count, mu, beta)); } void generate_pareto_64(uint64_t count, double* out, double alpha) { - CHECK_CURAND(::randutilGenerateParetoDoubleEx(gen_, out, count, 1.0, alpha)); + CHECK_RND_ENGINE(::randutilGenerateParetoDoubleEx(gen_, out, count, 1.0, alpha)); } void generate_pareto_32(uint64_t count, float* out, float alpha) { - CHECK_CURAND(::randutilGenerateParetoEx(gen_, out, count, 1.0f, alpha)); + CHECK_RND_ENGINE(::randutilGenerateParetoEx(gen_, out, count, 1.0f, alpha)); } void generate_power_64(uint64_t count, double* out, double alpha) { - CHECK_CURAND(::randutilGeneratePowerDoubleEx(gen_, out, count, alpha)); + CHECK_RND_ENGINE(::randutilGeneratePowerDoubleEx(gen_, out, count, alpha)); } void generate_power_32(uint64_t count, float* out, float alpha) { - CHECK_CURAND(::randutilGeneratePowerEx(gen_, out, count, alpha)); + CHECK_RND_ENGINE(::randutilGeneratePowerEx(gen_, out, count, alpha)); } void generate_rayleigh_64(uint64_t count, double* out, double sigma) { - CHECK_CURAND(::randutilGenerateRayleighDoubleEx(gen_, out, count, sigma)); + CHECK_RND_ENGINE(::randutilGenerateRayleighDoubleEx(gen_, out, count, sigma)); } void generate_rayleigh_32(uint64_t count, float* out, float sigma) { - CHECK_CURAND(::randutilGenerateRayleighEx(gen_, out, count, sigma)); + CHECK_RND_ENGINE(::randutilGenerateRayleighEx(gen_, out, count, sigma)); } void generate_cauchy_64(uint64_t count, double* out, double x0, double gamma) { - CHECK_CURAND(::randutilGenerateCauchyDoubleEx(gen_, out, count, x0, gamma)); + CHECK_RND_ENGINE(::randutilGenerateCauchyDoubleEx(gen_, out, count, x0, gamma)); } void generate_cauchy_32(uint64_t count, float* out, float x0, float gamma) { - CHECK_CURAND(::randutilGenerateCauchyEx(gen_, out, count, x0, gamma)); + CHECK_RND_ENGINE(::randutilGenerateCauchyEx(gen_, out, count, x0, gamma)); } void generate_triangular_64(uint64_t count, double* out, double a, double b, double c) { - CHECK_CURAND(::randutilGenerateTriangularDoubleEx(gen_, out, count, a, b, c)); + CHECK_RND_ENGINE(::randutilGenerateTriangularDoubleEx(gen_, out, count, a, b, c)); } void generate_triangular_32(uint64_t count, float* out, float a, float b, float c) { - CHECK_CURAND(::randutilGenerateTriangularEx(gen_, out, count, a, b, c)); + CHECK_RND_ENGINE(::randutilGenerateTriangularEx(gen_, out, count, a, b, c)); } void generate_weibull_64(uint64_t count, double* out, double lam, double k) { - CHECK_CURAND(::randutilGenerateWeibullDoubleEx(gen_, out, count, lam, k)); + CHECK_RND_ENGINE(::randutilGenerateWeibullDoubleEx(gen_, out, count, lam, k)); } void generate_weibull_32(uint64_t count, float* out, float lam, float k) { - CHECK_CURAND(::randutilGenerateWeibullEx(gen_, out, count, lam, k)); + CHECK_RND_ENGINE(::randutilGenerateWeibullEx(gen_, out, count, lam, k)); } void generate_beta_64(uint64_t count, double* out, double a, double b) { - CHECK_CURAND(::randutilGenerateBetaDoubleEx(gen_, out, count, a, b)); + CHECK_RND_ENGINE(::randutilGenerateBetaDoubleEx(gen_, out, count, a, b)); } void generate_beta_32(uint64_t count, float* out, float a, float b) { - CHECK_CURAND(::randutilGenerateBetaEx(gen_, out, count, a, b)); + CHECK_RND_ENGINE(::randutilGenerateBetaEx(gen_, out, count, a, b)); } void generate_f_64(uint64_t count, double* out, double dfnum, double dfden) { - CHECK_CURAND(::randutilGenerateFisherSnedecorDoubleEx(gen_, out, count, dfnum, dfden)); + CHECK_RND_ENGINE(::randutilGenerateFisherSnedecorDoubleEx(gen_, out, count, dfnum, dfden)); } void generate_f_32(uint64_t count, float* out, float dfnum, float dfden) { - CHECK_CURAND(::randutilGenerateFisherSnedecorEx(gen_, out, count, dfnum, dfden)); + CHECK_RND_ENGINE(::randutilGenerateFisherSnedecorEx(gen_, out, count, dfnum, dfden)); } void generate_logseries(uint64_t count, uint32_t* out, double p) { - CHECK_CURAND(::randutilGenerateLogSeriesEx(gen_, out, count, p)); + CHECK_RND_ENGINE(::randutilGenerateLogSeriesEx(gen_, out, count, p)); } void generate_noncentral_f_64( uint64_t count, double* out, double dfnum, double dfden, double nonc) { - CHECK_CURAND(::randutilGenerateFisherSnedecorDoubleEx(gen_, out, count, dfnum, dfden, nonc)); + CHECK_RND_ENGINE( + ::randutilGenerateFisherSnedecorDoubleEx(gen_, out, count, dfnum, dfden, nonc)); } void generate_noncentral_f_32(uint64_t count, float* out, float dfnum, float dfden, float nonc) { - CHECK_CURAND(::randutilGenerateFisherSnedecorEx(gen_, out, count, dfnum, dfden, nonc)); + CHECK_RND_ENGINE(::randutilGenerateFisherSnedecorEx(gen_, out, count, dfnum, dfden, nonc)); } void generate_chisquare_64(uint64_t count, double* out, double df, double nonc) { - CHECK_CURAND(::randutilGenerateChiSquareDoubleEx(gen_, out, count, df, nonc)); + CHECK_RND_ENGINE(::randutilGenerateChiSquareDoubleEx(gen_, out, count, df, nonc)); } void generate_chisquare_32(uint64_t count, float* out, float df, float nonc) { - CHECK_CURAND(::randutilGenerateChiSquareEx(gen_, out, count, df, nonc)); + CHECK_RND_ENGINE(::randutilGenerateChiSquareEx(gen_, out, count, df, nonc)); } void generate_gamma_64(uint64_t count, double* out, double k, double theta) { - CHECK_CURAND(::randutilGenerateGammaDoubleEx(gen_, out, count, k, theta)); + CHECK_RND_ENGINE(::randutilGenerateGammaDoubleEx(gen_, out, count, k, theta)); } void generate_gamma_32(uint64_t count, float* out, float k, float theta) { - CHECK_CURAND(::randutilGenerateGammaEx(gen_, out, count, k, theta)); + CHECK_RND_ENGINE(::randutilGenerateGammaEx(gen_, out, count, k, theta)); } void generate_standard_t_64(uint64_t count, double* out, double df) { - CHECK_CURAND(::randutilGenerateStandardTDoubleEx(gen_, out, count, df)); + CHECK_RND_ENGINE(::randutilGenerateStandardTDoubleEx(gen_, out, count, df)); } void generate_standard_t_32(uint64_t count, float* out, float df) { - CHECK_CURAND(::randutilGenerateStandardTEx(gen_, out, count, df)); + CHECK_RND_ENGINE(::randutilGenerateStandardTEx(gen_, out, count, df)); } void generate_hypergeometric( uint64_t count, uint32_t* out, int64_t ngood, int64_t nbad, int64_t nsample) { - CHECK_CURAND(::randutilGenerateHyperGeometricEx(gen_, out, count, ngood, nbad, nsample)); + CHECK_RND_ENGINE(::randutilGenerateHyperGeometricEx(gen_, out, count, ngood, nbad, nsample)); } void generate_vonmises_64(uint64_t count, double* out, double mu, double kappa) { - CHECK_CURAND(::randutilGenerateVonMisesDoubleEx(gen_, out, count, mu, kappa)); + CHECK_RND_ENGINE(::randutilGenerateVonMisesDoubleEx(gen_, out, count, mu, kappa)); } void generate_vonmises_32(uint64_t count, float* out, float mu, float kappa) { - CHECK_CURAND(::randutilGenerateVonMisesEx(gen_, out, count, mu, kappa)); + CHECK_RND_ENGINE(::randutilGenerateVonMisesEx(gen_, out, count, mu, kappa)); } void generate_zipf(uint64_t count, uint32_t* out, double a) { - CHECK_CURAND(::randutilGenerateZipfEx(gen_, out, count, a)); + CHECK_RND_ENGINE(::randutilGenerateZipfEx(gen_, out, count, a)); } void generate_geometric(uint64_t count, uint32_t* out, double p) { - CHECK_CURAND(::randutilGenerateGeometricEx(gen_, out, count, p)); + CHECK_RND_ENGINE(::randutilGenerateGeometricEx(gen_, out, count, p)); } void generate_wald_64(uint64_t count, double* out, double mean, double scale) { - CHECK_CURAND(::randutilGenerateWaldDoubleEx(gen_, out, count, mean, scale)); + CHECK_RND_ENGINE(::randutilGenerateWaldDoubleEx(gen_, out, count, mean, scale)); } void generate_wald_32(uint64_t count, float* out, float mean, float scale) { - CHECK_CURAND(::randutilGenerateWaldEx(gen_, out, count, mean, scale)); + CHECK_RND_ENGINE(::randutilGenerateWaldEx(gen_, out, count, mean, scale)); } void generate_binomial(uint64_t count, uint32_t* out, uint32_t ntrials, double p) { - CHECK_CURAND(::randutilGenerateBinomialEx(gen_, out, count, ntrials, p)); + CHECK_RND_ENGINE(::randutilGenerateBinomialEx(gen_, out, count, ntrials, p)); } void generate_negative_binomial(uint64_t count, uint32_t* out, uint32_t ntrials, double p) { - CHECK_CURAND(::randutilGenerateNegativeBinomialEx(gen_, out, count, ntrials, p)); + CHECK_RND_ENGINE(::randutilGenerateNegativeBinomialEx(gen_, out, count, ntrials, p)); } }; diff --git a/src/cunumeric/random/curand_help.h b/src/cunumeric/random/curand_help.h index ec62e2eda9..39847860aa 100644 --- a/src/cunumeric/random/curand_help.h +++ b/src/cunumeric/random/curand_help.h @@ -18,15 +18,33 @@ #include -#define CHECK_CURAND(expr) \ +#include + +#define CHECK_CURAND(...) \ do { \ - curandStatus_t __result__ = (expr); \ + curandStatus_t __result__ = __VA_ARGS__; \ randutil_check_curand(__result__, __FILE__, __LINE__); \ } while (false) +// necessary, b/c the STL variant (host only MacOS) uses non-curand abstractions, +// hence a different checker defined in bitgenerator.cc, while the curand checker +// gets undefined; however the device code still requires the curand checker and +// attempts to link against the definition in bitgenerator.cc, which is disabled +// in this situation; the checker below fulfills that purpose: +// +#define CHECK_CURAND_DEVICE(expr) \ + do { \ + curandStatus_t __result__ = (expr); \ + randutil_check_curand_device(__result__, __FILE__, __LINE__); \ + } while (false) + namespace cunumeric { legate::Logger& randutil_log(); -void randutil_check_curand(curandStatus_t error, const char* file, int line); +void randutil_check_curand(curandStatus_t error, std::string_view file, int line); + +// required by CHECK_CURAND_DEVICE: +// +void randutil_check_curand_device(curandStatus_t error, const char* file, int line); static inline curandRngType get_curandRngType(cunumeric::BitGeneratorType kind) { diff --git a/src/cunumeric/random/randutil/generator.h b/src/cunumeric/random/randutil/generator.h index b5b54ad538..6b07193bc2 100644 --- a/src/cunumeric/random/randutil/generator.h +++ b/src/cunumeric/random/randutil/generator.h @@ -23,6 +23,8 @@ #include "randutil_curand.h" #include "randutil_impl.h" +#include "cunumeric/random/rnd_aliases.h" + namespace randutilimpl { struct basegenerator { @@ -36,33 +38,22 @@ template struct generatorid; template <> -struct generatorid { - static constexpr int rng_type = CURAND_RNG_PSEUDO_XORWOW; -}; -template <> -struct generatorid { - static constexpr int rng_type = CURAND_RNG_PSEUDO_PHILOX4_32_10; +struct generatorid { + static constexpr int rng_type = RND_RNG_PSEUDO_XORWOW; }; -template <> -struct generatorid { - static constexpr int rng_type = CURAND_RNG_PSEUDO_MRG32K3A; -}; - -template -struct generatortype; +#ifndef CUNUMERIC_USE_STL_RANDOM_ENGINE +// Curand *different* specializations, not possible with only one generator +// template <> -struct generatortype { - using rng_t = curandStateXORWOW_t; +struct generatorid { + static constexpr int rng_type = RND_RNG_PSEUDO_PHILOX4_32_10; }; template <> -struct generatortype { - using rng_t = curandStatePhilox4_32_10_t; -}; -template <> -struct generatortype { - using rng_t = curandStateMRG32k3a_t; +struct generatorid { + static constexpr int rng_type = RND_RNG_PSEUDO_MRG32K3A; }; +#endif template struct inner_generator : basegenerator { @@ -70,10 +61,19 @@ struct inner_generator : basegenerator uint64_t generatorID; gen_t generator; - inner_generator(uint64_t seed, uint64_t generatorID, cudaStream_t ignored) - : seed(seed), generatorID(generatorID) + inner_generator(uint64_t seed, uint64_t generatorID, stream_t) + : seed(seed), + generatorID(generatorID) +#ifdef CUNUMERIC_USE_STL_RANDOM_ENGINE + , + generator(seed) +#endif { +#ifdef CUNUMERIC_USE_STL_RANDOM_ENGINE + std::srand(seed); +#else curand_init(seed, generatorID, 0, &generator); +#endif } virtual void destroy() override {} @@ -95,36 +95,36 @@ struct inner_generator : basegenerator }; template -curandStatus_t inner_dispatch_sample(basegenerator* gen, func_t func, size_t N, out_t* out) +rnd_status_t inner_dispatch_sample(basegenerator* gen, func_t func, size_t N, out_t* out) { switch (gen->generatorTypeId()) { - case CURAND_RNG_PSEUDO_XORWOW: - return static_cast*>(gen) + case RND_RNG_PSEUDO_XORWOW: + return static_cast*>(gen) ->template draw(func, N, out); - case CURAND_RNG_PSEUDO_PHILOX4_32_10: - return static_cast*>(gen) + case RND_RNG_PSEUDO_PHILOX4_32_10: + return static_cast*>(gen) ->template draw(func, N, out); - case CURAND_RNG_PSEUDO_MRG32K3A: - return static_cast*>(gen) + case RND_RNG_PSEUDO_MRG32K3A: + return static_cast*>(gen) ->template draw(func, N, out); default: LEGATE_ABORT("Unknown base generator"); } - return CURAND_STATUS_INTERNAL_ERROR; + return RND_STATUS_INTERNAL_ERROR; } // template funtion with HOST and DEVICE implementations template struct dispatcher { - static curandStatus_t run(randutilimpl::basegenerator* generator, - func_t func, - size_t N, - out_t* out); + static rnd_status_t run(randutilimpl::basegenerator* generator, + func_t func, + size_t N, + out_t* out); }; // HOST-side template instantiation of generator template struct dispatcher { - static curandStatus_t run(randutilimpl::basegenerator* gen, func_t func, size_t N, out_t* out) + static rnd_status_t run(randutilimpl::basegenerator* gen, func_t func, size_t N, out_t* out) { return inner_dispatch_sample( gen, func, N, out); @@ -132,7 +132,7 @@ struct dispatcher { }; template -curandStatus_t dispatch(randutilimpl::basegenerator* gen, func_t func, size_t N, out_t* out) +rnd_status_t dispatch(randutilimpl::basegenerator* gen, func_t func, size_t N, out_t* out) { switch (gen->location()) { case randutilimpl::execlocation::HOST: @@ -143,7 +143,7 @@ curandStatus_t dispatch(randutilimpl::basegenerator* gen, func_t func, size_t N, #endif default: LEGATE_ABORT("Unknown generator execution location"); } - return CURAND_STATUS_INTERNAL_ERROR; + return RND_STATUS_INTERNAL_ERROR; } } // namespace randutilimpl diff --git a/src/cunumeric/random/randutil/generator_cauchy.inl b/src/cunumeric/random/randutil/generator_cauchy.inl index 9ee34e6df0..1b185f649c 100644 --- a/src/cunumeric/random/randutil/generator_cauchy.inl +++ b/src/cunumeric/random/randutil/generator_cauchy.inl @@ -16,6 +16,8 @@ #include "generator.h" +#include "randomizer.h" + template struct cauchy_t; @@ -28,7 +30,12 @@ struct cauchy_t { template RANDUTIL_QUALIFIERS float operator()(gen_t& gen) { - float y = curand_uniform(&gen); // y cannot be 0 + using value_t = float; + value_t y = randutilimpl::engine_uniform(gen); // y \in (0, 1] + + // must avoid tan(pi/2), hence scale the interval by (1-eps): + // + y = y * (value_t{1} - cuda::std::numeric_limits::epsilon()); return x0 + gamma * ::tanf(pi * (y - 0.5f)); } }; @@ -42,7 +49,12 @@ struct cauchy_t { template RANDUTIL_QUALIFIERS double operator()(gen_t& gen) { - double y = curand_uniform_double(&gen); // y cannot be 0 + using value_t = double; + value_t y = randutilimpl::engine_uniform(gen); // y \in (0, 1] + + // must avoid tan(pi/2), hence scale the interval by (1-eps): + // + y = y * (value_t{1} - cuda::std::numeric_limits::epsilon()); return x0 + gamma * ::tan(pi * (y - 0.5)); } }; diff --git a/src/cunumeric/random/randutil/generator_create.inl b/src/cunumeric/random/randutil/generator_create.inl index 5668c0c0a1..c0dfdce0e2 100644 --- a/src/cunumeric/random/randutil/generator_create.inl +++ b/src/cunumeric/random/randutil/generator_create.inl @@ -17,37 +17,35 @@ #include "generator.h" template -curandStatus_t randutilGenerator(randutilGenerator_t* generator, - uint64_t seed, - uint64_t generatorID, - cudaStream_t stream = nullptr) +rnd_status_t randutilGenerator(randutilGenerator_t* generator, + uint64_t seed, + uint64_t generatorID, + stream_t stream = nullptr) { randutilimpl::inner_generator* result = new randutilimpl::inner_generator(seed, generatorID, stream); *generator = (randutilGenerator_t)result; - return CURAND_STATUS_SUCCESS; + return RND_STATUS_SUCCESS; } template -static curandStatus_t inner_randutilCreateGenerator(randutilGenerator_t* generator, - curandRngType_t rng_type, - uint64_t seed, - uint64_t generatorID, - cudaStream_t stream = nullptr) +static rnd_status_t inner_randutilCreateGenerator(randutilGenerator_t* generator, + randRngType_t rng_type, + uint64_t seed, + uint64_t generatorID, + stream_t stream = nullptr) { - switch (rng_type) { - case CURAND_RNG_PSEUDO_XORWOW: - return randutilGenerator(generator, seed, generatorID, stream); - case CURAND_RNG_PSEUDO_PHILOX4_32_10: - return randutilGenerator( - generator, seed, generatorID, stream); - case CURAND_RNG_PSEUDO_MRG32K3A: - return randutilGenerator( - generator, seed, generatorID, stream); + switch (static_cast(rng_type)) { + case RND_RNG_PSEUDO_XORWOW: + return randutilGenerator(generator, seed, generatorID, stream); + case RND_RNG_PSEUDO_PHILOX4_32_10: + return randutilGenerator(generator, seed, generatorID, stream); + case RND_RNG_PSEUDO_MRG32K3A: + return randutilGenerator(generator, seed, generatorID, stream); default: { LEGATE_ABORT("Unknown generator type"); break; } } - return CURAND_STATUS_TYPE_ERROR; + return RND_STATUS_TYPE_ERROR; } diff --git a/src/cunumeric/random/randutil/generator_exponential.inl b/src/cunumeric/random/randutil/generator_exponential.inl index bec7b96ddb..ee4eb56ee8 100644 --- a/src/cunumeric/random/randutil/generator_exponential.inl +++ b/src/cunumeric/random/randutil/generator_exponential.inl @@ -16,6 +16,8 @@ #include "generator.h" +#include "randomizer.h" + template struct exponential_t; @@ -26,7 +28,7 @@ struct exponential_t { template RANDUTIL_QUALIFIERS float operator()(gen_t& gen) { - float uni = curand_uniform(&gen); + float uni = randutilimpl::engine_uniform(gen); return -::logf(uni) * scale; } }; @@ -38,7 +40,7 @@ struct exponential_t { template RANDUTIL_QUALIFIERS double operator()(gen_t& gen) { - double uni = curand_uniform_double(&gen); + double uni = randutilimpl::engine_uniform(gen); return -::logf(uni) * scale; } }; diff --git a/src/cunumeric/random/randutil/generator_gumbel.inl b/src/cunumeric/random/randutil/generator_gumbel.inl index db2becd4db..e60b8a60bc 100644 --- a/src/cunumeric/random/randutil/generator_gumbel.inl +++ b/src/cunumeric/random/randutil/generator_gumbel.inl @@ -16,6 +16,8 @@ #include "generator.h" +#include "randomizer.h" + template struct gumbel_t; @@ -27,10 +29,9 @@ struct gumbel_t { template RANDUTIL_QUALIFIERS float operator()(gen_t& gen) { - float y = curand_uniform(&gen); // y cannot be zero - if (y == 1.0f) { - return mu; - } + auto y = randutilimpl::engine_uniform(gen); // y cannot be zero + + if (y == 1.0f) return mu; float lny = ::logf(y); return mu - beta * ::logf(-lny); } @@ -43,10 +44,9 @@ struct gumbel_t { template RANDUTIL_QUALIFIERS double operator()(gen_t& gen) { - double y = curand_uniform_double(&gen); // y cannot be zero - if (y == 1.0) { - return mu; - } + auto y = randutilimpl::engine_uniform(gen); // y cannot be zero + + if (y == 1.0) return mu; double lny = ::log(y); return mu - beta * ::log(-lny); } diff --git a/src/cunumeric/random/randutil/generator_host.cc b/src/cunumeric/random/randutil/generator_host.cc index d2343ee951..11896817c4 100644 --- a/src/cunumeric/random/randutil/generator_host.cc +++ b/src/cunumeric/random/randutil/generator_host.cc @@ -14,8 +14,15 @@ * */ +// MacOS host variant: +// +#if defined(__APPLE__) && defined(__MACH__) +#define CUNUMERIC_USE_STL_RANDOM_ENGINE +#endif + #include "generator.h" #include "generator_create.inl" +#include "cunumeric/random/rnd_aliases.h" #if !LEGATE_DEFINED(LEGATE_USE_CUDA) // the host code of cuRAND try to extern these variables out of nowhere, @@ -24,27 +31,27 @@ const dim3 blockDim{}; const uint3 threadIdx{}; #endif -extern "C" curandStatus_t randutilCreateGeneratorHost(randutilGenerator_t* generator, - curandRngType_t rng_type, - uint64_t seed, - uint64_t generatorID, - uint32_t flags) +extern "C" rnd_status_t randutilCreateGeneratorHost(randutilGenerator_t* generator, + randRngType_t rng_type, + uint64_t seed, + uint64_t generatorID, + uint32_t flags) { return inner_randutilCreateGenerator( generator, rng_type, seed, generatorID, nullptr); } -extern "C" curandStatus_t randutilDestroyGenerator(randutilGenerator_t generator) +extern "C" rnd_status_t randutilDestroyGenerator(randutilGenerator_t generator) { randutilimpl::basegenerator* gen = (randutilimpl::basegenerator*)generator; gen->destroy(); delete gen; - return CURAND_STATUS_SUCCESS; + return RND_STATUS_SUCCESS; } #include "generator_integers.inl" -extern "C" curandStatus_t randutilGenerateIntegers16( +extern "C" rnd_status_t randutilGenerateIntegers16( randutilGenerator_t generator, int16_t* outputPtr, size_t n, int16_t low, int16_t high) { randutilimpl::basegenerator* gen = (randutilimpl::basegenerator*)generator; @@ -54,7 +61,7 @@ extern "C" curandStatus_t randutilGenerateIntegers16( return randutilimpl::dispatch(gen, func, n, outputPtr); } -extern "C" curandStatus_t randutilGenerateIntegers32( +extern "C" rnd_status_t randutilGenerateIntegers32( randutilGenerator_t generator, int32_t* outputPtr, size_t n, int32_t low, int32_t high) { randutilimpl::basegenerator* gen = (randutilimpl::basegenerator*)generator; @@ -64,7 +71,7 @@ extern "C" curandStatus_t randutilGenerateIntegers32( return randutilimpl::dispatch(gen, func, n, outputPtr); } -extern "C" curandStatus_t randutilGenerateIntegers64( +extern "C" rnd_status_t randutilGenerateIntegers64( randutilGenerator_t generator, int64_t* outputPtr, size_t n, int64_t low, int64_t high) { randutilimpl::basegenerator* gen = (randutilimpl::basegenerator*)generator; @@ -76,7 +83,7 @@ extern "C" curandStatus_t randutilGenerateIntegers64( #include "generator_lognormal.inl" -extern "C" curandStatus_t randutilGenerateLogNormalEx( +extern "C" rnd_status_t randutilGenerateLogNormalEx( randutilGenerator_t generator, float* outputPtr, size_t n, float mean, float stddev) { randutilimpl::basegenerator* gen = (randutilimpl::basegenerator*)generator; @@ -86,7 +93,7 @@ extern "C" curandStatus_t randutilGenerateLogNormalEx( return randutilimpl::dispatch(gen, func, n, outputPtr); } -extern "C" curandStatus_t randutilGenerateLogNormalDoubleEx( +extern "C" rnd_status_t randutilGenerateLogNormalDoubleEx( randutilGenerator_t generator, double* outputPtr, size_t n, double mean, double stddev) { randutilimpl::basegenerator* gen = (randutilimpl::basegenerator*)generator; @@ -98,7 +105,7 @@ extern "C" curandStatus_t randutilGenerateLogNormalDoubleEx( #include "generator_normal.inl" -extern "C" curandStatus_t randutilGenerateNormalEx( +extern "C" rnd_status_t randutilGenerateNormalEx( randutilGenerator_t generator, float* outputPtr, size_t n, float mean, float stddev) { randutilimpl::basegenerator* gen = (randutilimpl::basegenerator*)generator; @@ -108,7 +115,7 @@ extern "C" curandStatus_t randutilGenerateNormalEx( return randutilimpl::dispatch(gen, func, n, outputPtr); } -extern "C" curandStatus_t randutilGenerateNormalDoubleEx( +extern "C" rnd_status_t randutilGenerateNormalDoubleEx( randutilGenerator_t generator, double* outputPtr, size_t n, double mean, double stddev) { randutilimpl::basegenerator* gen = (randutilimpl::basegenerator*)generator; @@ -120,10 +127,10 @@ extern "C" curandStatus_t randutilGenerateNormalDoubleEx( #include "generator_poisson.inl" -extern "C" curandStatus_t randutilGeneratePoissonEx(randutilGenerator_t generator, - uint32_t* outputPtr, - size_t n, - double lambda) +extern "C" rnd_status_t randutilGeneratePoissonEx(randutilGenerator_t generator, + uint32_t* outputPtr, + size_t n, + double lambda) { randutilimpl::basegenerator* gen = (randutilimpl::basegenerator*)generator; poisson func; @@ -133,9 +140,9 @@ extern "C" curandStatus_t randutilGeneratePoissonEx(randutilGenerator_t generato #include "generator_raw.inl" -extern "C" curandStatus_t randutilGenerateRawUInt32(randutilGenerator_t generator, - uint32_t* outputPtr, - size_t n) +extern "C" rnd_status_t randutilGenerateRawUInt32(randutilGenerator_t generator, + uint32_t* outputPtr, + size_t n) { randutilimpl::basegenerator* gen = (randutilimpl::basegenerator*)generator; raw func; @@ -144,7 +151,7 @@ extern "C" curandStatus_t randutilGenerateRawUInt32(randutilGenerator_t generato #include "generator_uniform.inl" -extern "C" curandStatus_t randutilGenerateUniformEx( +extern "C" rnd_status_t randutilGenerateUniformEx( randutilGenerator_t generator, float* outputPtr, size_t n, float low, float high) { randutilimpl::basegenerator* gen = (randutilimpl::basegenerator*)generator; @@ -155,7 +162,7 @@ extern "C" curandStatus_t randutilGenerateUniformEx( return randutilimpl::dispatch(gen, func, n, outputPtr); } -extern "C" curandStatus_t randutilGenerateUniformDoubleEx( +extern "C" rnd_status_t randutilGenerateUniformDoubleEx( randutilGenerator_t generator, double* outputPtr, size_t n, double low, double high) { randutilimpl::basegenerator* gen = (randutilimpl::basegenerator*)generator; diff --git a/src/cunumeric/random/randutil/generator_host_advanced.cc b/src/cunumeric/random/randutil/generator_host_advanced.cc index 8ccc1fdf19..75624f5e43 100644 --- a/src/cunumeric/random/randutil/generator_host_advanced.cc +++ b/src/cunumeric/random/randutil/generator_host_advanced.cc @@ -14,11 +14,17 @@ * */ +// MacOS host variant: +// +#if defined(__APPLE__) && defined(__MACH__) +#define CUNUMERIC_USE_STL_RANDOM_ENGINE +#endif + #include "generator.h" #include "generator_beta.inl" -extern "C" curandStatus_t randutilGenerateBetaEx( +extern "C" rnd_status_t randutilGenerateBetaEx( randutilGenerator_t generator, float* outputPtr, size_t n, float a, float b) { randutilimpl::basegenerator* gen = (randutilimpl::basegenerator*)generator; @@ -28,7 +34,7 @@ extern "C" curandStatus_t randutilGenerateBetaEx( return randutilimpl::dispatch(gen, func, n, outputPtr); } -extern "C" curandStatus_t randutilGenerateBetaDoubleEx( +extern "C" rnd_status_t randutilGenerateBetaDoubleEx( randutilGenerator_t generator, double* outputPtr, size_t n, double a, double b) { randutilimpl::basegenerator* gen = (randutilimpl::basegenerator*)generator; @@ -40,12 +46,12 @@ extern "C" curandStatus_t randutilGenerateBetaDoubleEx( #include "generator_f.inl" -extern "C" curandStatus_t randutilGenerateFisherSnedecorEx(randutilGenerator_t generator, - float* outputPtr, - size_t n, - float dfnum, - float dfden, - float nonc) // 0.0f is F distribution +extern "C" rnd_status_t randutilGenerateFisherSnedecorEx(randutilGenerator_t generator, + float* outputPtr, + size_t n, + float dfnum, + float dfden, + float nonc) // 0.0f is F distribution { randutilimpl::basegenerator* gen = (randutilimpl::basegenerator*)generator; if (nonc == 0.0f) { @@ -62,7 +68,7 @@ extern "C" curandStatus_t randutilGenerateFisherSnedecorEx(randutilGenerator_t g } } -extern "C" curandStatus_t randutilGenerateFisherSnedecorDoubleEx( +extern "C" rnd_status_t randutilGenerateFisherSnedecorDoubleEx( randutilGenerator_t generator, double* outputPtr, size_t n, @@ -87,10 +93,10 @@ extern "C" curandStatus_t randutilGenerateFisherSnedecorDoubleEx( #include "generator_logseries.inl" -extern "C" curandStatus_t randutilGenerateLogSeriesEx(randutilGenerator_t generator, - uint32_t* outputPtr, - size_t n, - double p) +extern "C" rnd_status_t randutilGenerateLogSeriesEx(randutilGenerator_t generator, + uint32_t* outputPtr, + size_t n, + double p) { randutilimpl::basegenerator* gen = (randutilimpl::basegenerator*)generator; logseries_t func; @@ -100,7 +106,7 @@ extern "C" curandStatus_t randutilGenerateLogSeriesEx(randutilGenerator_t genera #include "generator_chisquare.inl" -extern "C" curandStatus_t randutilGenerateChiSquareEx( +extern "C" rnd_status_t randutilGenerateChiSquareEx( randutilGenerator_t generator, float* outputPtr, size_t n, @@ -120,7 +126,7 @@ extern "C" curandStatus_t randutilGenerateChiSquareEx( } } -extern "C" curandStatus_t randutilGenerateChiSquareDoubleEx( +extern "C" rnd_status_t randutilGenerateChiSquareDoubleEx( randutilGenerator_t generator, double* outputPtr, size_t n, @@ -142,11 +148,11 @@ extern "C" curandStatus_t randutilGenerateChiSquareDoubleEx( #include "generator_gamma.inl" -extern "C" curandStatus_t randutilGenerateGammaEx(randutilGenerator_t generator, - float* outputPtr, - size_t n, - float shape, - float scale) // = 1.0f is standard_gamma +extern "C" rnd_status_t randutilGenerateGammaEx(randutilGenerator_t generator, + float* outputPtr, + size_t n, + float shape, + float scale) // = 1.0f is standard_gamma { randutilimpl::basegenerator* gen = (randutilimpl::basegenerator*)generator; gamma_t func; @@ -155,11 +161,11 @@ extern "C" curandStatus_t randutilGenerateGammaEx(randutilGenerator_t generator, return randutilimpl::dispatch(gen, func, n, outputPtr); } -extern "C" curandStatus_t randutilGenerateGammaDoubleEx(randutilGenerator_t generator, - double* outputPtr, - size_t n, - double shape, - double scale) // = 1.0 is standard_gamma +extern "C" rnd_status_t randutilGenerateGammaDoubleEx(randutilGenerator_t generator, + double* outputPtr, + size_t n, + double shape, + double scale) // = 1.0 is standard_gamma { randutilimpl::basegenerator* gen = (randutilimpl::basegenerator*)generator; gamma_t func; @@ -170,10 +176,10 @@ extern "C" curandStatus_t randutilGenerateGammaDoubleEx(randutilGenerator_t gene #include "generator_standard_t.inl" -extern "C" curandStatus_t randutilGenerateStandardTEx(randutilGenerator_t generator, - float* outputPtr, - size_t n, - float df) +extern "C" rnd_status_t randutilGenerateStandardTEx(randutilGenerator_t generator, + float* outputPtr, + size_t n, + float df) { randutilimpl::basegenerator* gen = (randutilimpl::basegenerator*)generator; standard_t_t func; @@ -181,10 +187,10 @@ extern "C" curandStatus_t randutilGenerateStandardTEx(randutilGenerator_t genera return randutilimpl::dispatch(gen, func, n, outputPtr); } -extern "C" curandStatus_t randutilGenerateStandardTDoubleEx(randutilGenerator_t generator, - double* outputPtr, - size_t n, - double df) +extern "C" rnd_status_t randutilGenerateStandardTDoubleEx(randutilGenerator_t generator, + double* outputPtr, + size_t n, + double df) { randutilimpl::basegenerator* gen = (randutilimpl::basegenerator*)generator; standard_t_t func; @@ -194,7 +200,7 @@ extern "C" curandStatus_t randutilGenerateStandardTDoubleEx(randutilGenerator_t #include "generator_vonmises.inl" -extern "C" curandStatus_t randutilGenerateVonMisesEx( +extern "C" rnd_status_t randutilGenerateVonMisesEx( randutilGenerator_t generator, float* outputPtr, size_t n, float mu, float kappa) { randutilimpl::basegenerator* gen = (randutilimpl::basegenerator*)generator; @@ -204,7 +210,7 @@ extern "C" curandStatus_t randutilGenerateVonMisesEx( return randutilimpl::dispatch(gen, func, n, outputPtr); } -extern "C" curandStatus_t randutilGenerateVonMisesDoubleEx( +extern "C" rnd_status_t randutilGenerateVonMisesDoubleEx( randutilGenerator_t generator, double* outputPtr, size_t n, double mu, double kappa) { randutilimpl::basegenerator* gen = (randutilimpl::basegenerator*)generator; @@ -216,12 +222,12 @@ extern "C" curandStatus_t randutilGenerateVonMisesDoubleEx( #include "generator_hypergeometric.inl" -extern "C" curandStatus_t randutilGenerateHyperGeometricEx(randutilGenerator_t generator, - uint32_t* outputPtr, - size_t n, - int64_t ngood, - int64_t nbad, - int64_t nsample) +extern "C" rnd_status_t randutilGenerateHyperGeometricEx(randutilGenerator_t generator, + uint32_t* outputPtr, + size_t n, + int64_t ngood, + int64_t nbad, + int64_t nsample) { randutilimpl::basegenerator* gen = (randutilimpl::basegenerator*)generator; hypergeometric_t func; @@ -233,10 +239,10 @@ extern "C" curandStatus_t randutilGenerateHyperGeometricEx(randutilGenerator_t g #include "generator_zipf.inl" -extern "C" curandStatus_t randutilGenerateZipfEx(randutilGenerator_t generator, - uint32_t* outputPtr, - size_t n, - double a) +extern "C" rnd_status_t randutilGenerateZipfEx(randutilGenerator_t generator, + uint32_t* outputPtr, + size_t n, + double a) { randutilimpl::basegenerator* gen = (randutilimpl::basegenerator*)generator; zipf_t func; @@ -246,10 +252,10 @@ extern "C" curandStatus_t randutilGenerateZipfEx(randutilGenerator_t generator, #include "generator_geometric.inl" -extern "C" curandStatus_t randutilGenerateGeometricEx(randutilGenerator_t generator, - uint32_t* outputPtr, - size_t n, - double p) +extern "C" rnd_status_t randutilGenerateGeometricEx(randutilGenerator_t generator, + uint32_t* outputPtr, + size_t n, + double p) { randutilimpl::basegenerator* gen = (randutilimpl::basegenerator*)generator; geometric_t func; @@ -259,7 +265,7 @@ extern "C" curandStatus_t randutilGenerateGeometricEx(randutilGenerator_t genera #include "generator_wald.inl" -extern "C" curandStatus_t randutilGenerateWaldEx( +extern "C" rnd_status_t randutilGenerateWaldEx( randutilGenerator_t generator, float* outputPtr, size_t n, float mu, float lambda) { randutilimpl::basegenerator* gen = (randutilimpl::basegenerator*)generator; @@ -269,7 +275,7 @@ extern "C" curandStatus_t randutilGenerateWaldEx( return randutilimpl::dispatch(gen, func, n, outputPtr); } -extern "C" curandStatus_t randutilGenerateWaldDoubleEx( +extern "C" rnd_status_t randutilGenerateWaldDoubleEx( randutilGenerator_t generator, double* outputPtr, size_t n, double mu, double lambda) { randutilimpl::basegenerator* gen = (randutilimpl::basegenerator*)generator; @@ -281,7 +287,7 @@ extern "C" curandStatus_t randutilGenerateWaldDoubleEx( #include "generator_binomial.inl" -extern "C" curandStatus_t randutilGenerateBinomialEx( +extern "C" rnd_status_t randutilGenerateBinomialEx( randutilGenerator_t generator, uint32_t* outputPtr, size_t n, uint32_t ntrials, double p) { randutilimpl::basegenerator* gen = (randutilimpl::basegenerator*)generator; @@ -293,7 +299,7 @@ extern "C" curandStatus_t randutilGenerateBinomialEx( #include "generator_negative_binomial.inl" -extern "C" curandStatus_t randutilGenerateNegativeBinomialEx( +extern "C" rnd_status_t randutilGenerateNegativeBinomialEx( randutilGenerator_t generator, uint32_t* outputPtr, size_t n, uint32_t ntrials, double p) { randutilimpl::basegenerator* gen = (randutilimpl::basegenerator*)generator; diff --git a/src/cunumeric/random/randutil/generator_host_straightforward.cc b/src/cunumeric/random/randutil/generator_host_straightforward.cc index 15edf26b21..732b77b134 100644 --- a/src/cunumeric/random/randutil/generator_host_straightforward.cc +++ b/src/cunumeric/random/randutil/generator_host_straightforward.cc @@ -14,14 +14,20 @@ * */ +// MacOS host variant: +// +#if defined(__APPLE__) && defined(__MACH__) +#define CUNUMERIC_USE_STL_RANDOM_ENGINE +#endif + #include "generator.h" #include "generator_exponential.inl" -extern "C" curandStatus_t randutilGenerateExponentialEx(randutilGenerator_t generator, - float* outputPtr, - size_t n, - float scale) +extern "C" rnd_status_t randutilGenerateExponentialEx(randutilGenerator_t generator, + float* outputPtr, + size_t n, + float scale) { randutilimpl::basegenerator* gen = (randutilimpl::basegenerator*)generator; exponential_t func; @@ -29,10 +35,10 @@ extern "C" curandStatus_t randutilGenerateExponentialEx(randutilGenerator_t gene return randutilimpl::dispatch(gen, func, n, outputPtr); } -extern "C" curandStatus_t randutilGenerateExponentialDoubleEx(randutilGenerator_t generator, - double* outputPtr, - size_t n, - double scale) +extern "C" rnd_status_t randutilGenerateExponentialDoubleEx(randutilGenerator_t generator, + double* outputPtr, + size_t n, + double scale) { randutilimpl::basegenerator* gen = (randutilimpl::basegenerator*)generator; exponential_t func; @@ -42,7 +48,7 @@ extern "C" curandStatus_t randutilGenerateExponentialDoubleEx(randutilGenerator_ #include "generator_gumbel.inl" -extern "C" curandStatus_t randutilGenerateGumbelEx( +extern "C" rnd_status_t randutilGenerateGumbelEx( randutilGenerator_t generator, float* outputPtr, size_t n, float mu, float beta) { randutilimpl::basegenerator* gen = (randutilimpl::basegenerator*)generator; @@ -52,7 +58,7 @@ extern "C" curandStatus_t randutilGenerateGumbelEx( return randutilimpl::dispatch(gen, func, n, outputPtr); } -extern "C" curandStatus_t randutilGenerateGumbelDoubleEx( +extern "C" rnd_status_t randutilGenerateGumbelDoubleEx( randutilGenerator_t generator, double* outputPtr, size_t n, double mu, double beta) { randutilimpl::basegenerator* gen = (randutilimpl::basegenerator*)generator; @@ -64,7 +70,7 @@ extern "C" curandStatus_t randutilGenerateGumbelDoubleEx( #include "generator_laplace.inl" -extern "C" curandStatus_t randutilGenerateLaplaceEx( +extern "C" rnd_status_t randutilGenerateLaplaceEx( randutilGenerator_t generator, float* outputPtr, size_t n, float mu, float beta) { randutilimpl::basegenerator* gen = (randutilimpl::basegenerator*)generator; @@ -74,7 +80,7 @@ extern "C" curandStatus_t randutilGenerateLaplaceEx( return randutilimpl::dispatch(gen, func, n, outputPtr); } -extern "C" curandStatus_t randutilGenerateLaplaceDoubleEx( +extern "C" rnd_status_t randutilGenerateLaplaceDoubleEx( randutilGenerator_t generator, double* outputPtr, size_t n, double mu, double beta) { randutilimpl::basegenerator* gen = (randutilimpl::basegenerator*)generator; @@ -86,7 +92,7 @@ extern "C" curandStatus_t randutilGenerateLaplaceDoubleEx( #include "generator_logistic.inl" -extern "C" curandStatus_t randutilGenerateLogisticEx( +extern "C" rnd_status_t randutilGenerateLogisticEx( randutilGenerator_t generator, float* outputPtr, size_t n, float mu, float beta) { randutilimpl::basegenerator* gen = (randutilimpl::basegenerator*)generator; @@ -96,7 +102,7 @@ extern "C" curandStatus_t randutilGenerateLogisticEx( return randutilimpl::dispatch(gen, func, n, outputPtr); } -extern "C" curandStatus_t randutilGenerateLogisticDoubleEx( +extern "C" rnd_status_t randutilGenerateLogisticDoubleEx( randutilGenerator_t generator, double* outputPtr, size_t n, double mu, double beta) { randutilimpl::basegenerator* gen = (randutilimpl::basegenerator*)generator; @@ -108,7 +114,7 @@ extern "C" curandStatus_t randutilGenerateLogisticDoubleEx( #include "generator_pareto.inl" -extern "C" curandStatus_t randutilGenerateParetoEx( +extern "C" rnd_status_t randutilGenerateParetoEx( randutilGenerator_t generator, float* outputPtr, size_t n, float xm, float alpha) { randutilimpl::basegenerator* gen = (randutilimpl::basegenerator*)generator; @@ -118,7 +124,7 @@ extern "C" curandStatus_t randutilGenerateParetoEx( return randutilimpl::dispatch(gen, func, n, outputPtr); } -extern "C" curandStatus_t randutilGenerateParetoDoubleEx( +extern "C" rnd_status_t randutilGenerateParetoDoubleEx( randutilGenerator_t generator, double* outputPtr, size_t n, double xm, double alpha) { randutilimpl::basegenerator* gen = (randutilimpl::basegenerator*)generator; @@ -130,10 +136,10 @@ extern "C" curandStatus_t randutilGenerateParetoDoubleEx( #include "generator_power.inl" -extern "C" curandStatus_t randutilGeneratePowerEx(randutilGenerator_t generator, - float* outputPtr, - size_t n, - float alpha) +extern "C" rnd_status_t randutilGeneratePowerEx(randutilGenerator_t generator, + float* outputPtr, + size_t n, + float alpha) { randutilimpl::basegenerator* gen = (randutilimpl::basegenerator*)generator; power_t func; @@ -141,10 +147,10 @@ extern "C" curandStatus_t randutilGeneratePowerEx(randutilGenerator_t generator, return randutilimpl::dispatch(gen, func, n, outputPtr); } -extern "C" curandStatus_t randutilGeneratePowerDoubleEx(randutilGenerator_t generator, - double* outputPtr, - size_t n, - double alpha) +extern "C" rnd_status_t randutilGeneratePowerDoubleEx(randutilGenerator_t generator, + double* outputPtr, + size_t n, + double alpha) { randutilimpl::basegenerator* gen = (randutilimpl::basegenerator*)generator; power_t func; @@ -154,10 +160,10 @@ extern "C" curandStatus_t randutilGeneratePowerDoubleEx(randutilGenerator_t gene #include "generator_rayleigh.inl" -extern "C" curandStatus_t randutilGenerateRayleighEx(randutilGenerator_t generator, - float* outputPtr, - size_t n, - float sigma) +extern "C" rnd_status_t randutilGenerateRayleighEx(randutilGenerator_t generator, + float* outputPtr, + size_t n, + float sigma) { randutilimpl::basegenerator* gen = (randutilimpl::basegenerator*)generator; rayleigh_t func; @@ -165,10 +171,10 @@ extern "C" curandStatus_t randutilGenerateRayleighEx(randutilGenerator_t generat return randutilimpl::dispatch(gen, func, n, outputPtr); } -extern "C" curandStatus_t randutilGenerateRayleighDoubleEx(randutilGenerator_t generator, - double* outputPtr, - size_t n, - double sigma) +extern "C" rnd_status_t randutilGenerateRayleighDoubleEx(randutilGenerator_t generator, + double* outputPtr, + size_t n, + double sigma) { randutilimpl::basegenerator* gen = (randutilimpl::basegenerator*)generator; rayleigh_t func; @@ -178,7 +184,7 @@ extern "C" curandStatus_t randutilGenerateRayleighDoubleEx(randutilGenerator_t g #include "generator_cauchy.inl" -extern "C" curandStatus_t randutilGenerateCauchyEx( +extern "C" rnd_status_t randutilGenerateCauchyEx( randutilGenerator_t generator, float* outputPtr, size_t n, float x0, float gamma) { randutilimpl::basegenerator* gen = (randutilimpl::basegenerator*)generator; @@ -188,7 +194,7 @@ extern "C" curandStatus_t randutilGenerateCauchyEx( return randutilimpl::dispatch(gen, func, n, outputPtr); } -extern "C" curandStatus_t randutilGenerateCauchyDoubleEx( +extern "C" rnd_status_t randutilGenerateCauchyDoubleEx( randutilGenerator_t generator, double* outputPtr, size_t n, double x0, double gamma) { randutilimpl::basegenerator* gen = (randutilimpl::basegenerator*)generator; @@ -200,7 +206,7 @@ extern "C" curandStatus_t randutilGenerateCauchyDoubleEx( #include "generator_triangular.inl" -extern "C" curandStatus_t randutilGenerateTriangularEx( +extern "C" rnd_status_t randutilGenerateTriangularEx( randutilGenerator_t generator, float* outputPtr, size_t n, float a, float b, float c) { randutilimpl::basegenerator* gen = (randutilimpl::basegenerator*)generator; @@ -211,7 +217,7 @@ extern "C" curandStatus_t randutilGenerateTriangularEx( return randutilimpl::dispatch(gen, func, n, outputPtr); } -extern "C" curandStatus_t randutilGenerateTriangularDoubleEx( +extern "C" rnd_status_t randutilGenerateTriangularDoubleEx( randutilGenerator_t generator, double* outputPtr, size_t n, double a, double b, double c) { randutilimpl::basegenerator* gen = (randutilimpl::basegenerator*)generator; @@ -224,7 +230,7 @@ extern "C" curandStatus_t randutilGenerateTriangularDoubleEx( #include "generator_weibull.inl" -extern "C" curandStatus_t randutilGenerateWeibullEx( +extern "C" rnd_status_t randutilGenerateWeibullEx( randutilGenerator_t generator, float* outputPtr, size_t n, float lam, float k) { randutilimpl::basegenerator* gen = (randutilimpl::basegenerator*)generator; @@ -234,7 +240,7 @@ extern "C" curandStatus_t randutilGenerateWeibullEx( return randutilimpl::dispatch(gen, func, n, outputPtr); } -extern "C" curandStatus_t randutilGenerateWeibullDoubleEx( +extern "C" rnd_status_t randutilGenerateWeibullDoubleEx( randutilGenerator_t generator, double* outputPtr, size_t n, double lam, double k) { randutilimpl::basegenerator* gen = (randutilimpl::basegenerator*)generator; diff --git a/src/cunumeric/random/randutil/generator_integers.inl b/src/cunumeric/random/randutil/generator_integers.inl index a8931fd477..a46742f905 100644 --- a/src/cunumeric/random/randutil/generator_integers.inl +++ b/src/cunumeric/random/randutil/generator_integers.inl @@ -15,45 +15,42 @@ */ #include "generator.h" +#include -template -struct integers; - -template <> -struct integers { - int16_t from; - int16_t to; +#include "randomizer.h" - template - RANDUTIL_QUALIFIERS int32_t operator()(gen_t& gen) - { - return (int16_t)(curand(&gen) % (uint16_t)(to - from)) + from; - } -}; +template +struct integers; -template <> -struct integers { - int32_t from; - int32_t to; +template +struct integers< + field_t, + std::enable_if_t || std::is_same_v>> { + using ufield_t = std::conditional_t, uint16_t, uint32_t>; + field_t from; + field_t to; template - RANDUTIL_QUALIFIERS int32_t operator()(gen_t& gen) + RANDUTIL_QUALIFIERS field_t operator()(gen_t& gen) { - return (int32_t)(curand(&gen) % (uint32_t)(to - from)) + from; + auto y = randutilimpl::engine_rand(gen); + return (field_t)(y % (ufield_t)(to - from)) + from; } }; -template <> -struct integers { - int64_t from; - int64_t to; +template +struct integers>> { + using ufield_t = uint64_t; + field_t from; + field_t to; template - RANDUTIL_QUALIFIERS int64_t operator()(gen_t& gen) + RANDUTIL_QUALIFIERS field_t operator()(gen_t& gen) { // take two draws to get a 64 bits value - unsigned low = curand(&gen); - unsigned high = curand(&gen); - return (int64_t)((((uint64_t)high << 32) | (uint64_t)low) % (to - from)) + from; + unsigned low = randutilimpl::engine_rand(gen); + unsigned high = randutilimpl::engine_rand(gen); + + return (field_t)((((ufield_t)high << 32) | (ufield_t)low) % (to - from)) + from; } }; diff --git a/src/cunumeric/random/randutil/generator_laplace.inl b/src/cunumeric/random/randutil/generator_laplace.inl index 09b0f60c7d..7c02c6d3cf 100644 --- a/src/cunumeric/random/randutil/generator_laplace.inl +++ b/src/cunumeric/random/randutil/generator_laplace.inl @@ -16,6 +16,8 @@ #include "generator.h" +#include "randomizer.h" + template struct laplace_t; @@ -26,7 +28,7 @@ struct laplace_t { template RANDUTIL_QUALIFIERS float operator()(gen_t& gen) { - float y = curand_uniform(&gen); // y cannot be zero + float y = randutilimpl::engine_uniform(gen); // y cannot be zero if (y == 0.5f) { return mu; } @@ -45,7 +47,7 @@ struct laplace_t { template RANDUTIL_QUALIFIERS double operator()(gen_t& gen) { - double y = curand_uniform_double(&gen); // y cannot be zero + double y = randutilimpl::engine_uniform(gen); // y cannot be zero if (y == 0.5) { return mu; } diff --git a/src/cunumeric/random/randutil/generator_logistic.inl b/src/cunumeric/random/randutil/generator_logistic.inl index 3b06c3a61d..e0db0e4dd4 100644 --- a/src/cunumeric/random/randutil/generator_logistic.inl +++ b/src/cunumeric/random/randutil/generator_logistic.inl @@ -16,6 +16,8 @@ #include "generator.h" +#include "randomizer.h" + template struct logistic_t; @@ -26,11 +28,9 @@ struct logistic_t { template RANDUTIL_QUALIFIERS float operator()(gen_t& gen) { - float y = curand_uniform(&gen); // y cannot be 0 + float y = randutilimpl::engine_uniform(gen); // y cannot be 0 float t = 1.0f / y - 1.0f; - if (t == 0) { - t = 1.0f; - } + if (t == 0) t = 1.0f; return mu - beta * ::logf(t); } }; @@ -42,11 +42,9 @@ struct logistic_t { template RANDUTIL_QUALIFIERS double operator()(gen_t& gen) { - float y = curand_uniform_double(&gen); // y cannot be 0 - float t = 1.0 / y - 1.0; - if (t == 0) { - t = 1.0; - } + auto y = randutilimpl::engine_uniform(gen); // y cannot be 0 + auto t = 1.0 / y - 1.0; + if (t == 0) t = 1.0; return mu - beta * ::log(t); } }; diff --git a/src/cunumeric/random/randutil/generator_lognormal.inl b/src/cunumeric/random/randutil/generator_lognormal.inl index 1127f0a079..e70d531e8d 100644 --- a/src/cunumeric/random/randutil/generator_lognormal.inl +++ b/src/cunumeric/random/randutil/generator_lognormal.inl @@ -16,6 +16,8 @@ #include "generator.h" +#include "randomizer.h" + template struct lognormal_t; @@ -27,7 +29,7 @@ struct lognormal_t { template RANDUTIL_QUALIFIERS float operator()(gen_t& gen) { - return curand_log_normal(&gen, mean, stddev); + return randutilimpl::engine_log_normal(gen, mean, stddev); } }; @@ -39,6 +41,6 @@ struct lognormal_t { template RANDUTIL_QUALIFIERS double operator()(gen_t& gen) { - return curand_log_normal_double(&gen, mean, stddev); + return randutilimpl::engine_log_normal(gen, mean, stddev); } }; diff --git a/src/cunumeric/random/randutil/generator_negative_binomial.inl b/src/cunumeric/random/randutil/generator_negative_binomial.inl index 9dd9ada1b0..0310620235 100644 --- a/src/cunumeric/random/randutil/generator_negative_binomial.inl +++ b/src/cunumeric/random/randutil/generator_negative_binomial.inl @@ -17,6 +17,8 @@ #include "generator.h" #include "random_distributions.h" +#include "randomizer.h" + template struct negative_binomial_t; @@ -29,6 +31,6 @@ struct negative_binomial_t { RANDUTIL_QUALIFIERS float operator()(gen_t& gen) { double lambda = rk_standard_gamma(&gen, (double)n) * ((1 - p) / p); - return curand_poisson(&gen, lambda); + return static_cast(randutilimpl::engine_poisson(gen, lambda)); } }; diff --git a/src/cunumeric/random/randutil/generator_normal.inl b/src/cunumeric/random/randutil/generator_normal.inl index c9daa2722c..c0939feede 100644 --- a/src/cunumeric/random/randutil/generator_normal.inl +++ b/src/cunumeric/random/randutil/generator_normal.inl @@ -16,6 +16,8 @@ #include "generator.h" +#include "randomizer.h" + template struct normal_t; @@ -27,7 +29,7 @@ struct normal_t { template RANDUTIL_QUALIFIERS float operator()(gen_t& gen) { - return stddev * curand_normal(&gen) + mean; + return stddev * randutilimpl::engine_normal(gen) + mean; } }; @@ -39,6 +41,6 @@ struct normal_t { template RANDUTIL_QUALIFIERS double operator()(gen_t& gen) { - return stddev * curand_normal_double(&gen) + mean; + return stddev * randutilimpl::engine_normal(gen) + mean; } }; diff --git a/src/cunumeric/random/randutil/generator_pareto.inl b/src/cunumeric/random/randutil/generator_pareto.inl index 1355656d8a..a5b3557509 100644 --- a/src/cunumeric/random/randutil/generator_pareto.inl +++ b/src/cunumeric/random/randutil/generator_pareto.inl @@ -16,6 +16,8 @@ #include "generator.h" +#include "randomizer.h" + template struct pareto_t; @@ -26,8 +28,8 @@ struct pareto_t { template RANDUTIL_QUALIFIERS float operator()(gen_t& gen) { - float y = curand_uniform(&gen); // y cannot be 0 - return xm * ::expf(-::logf(y) * invalpha) - 1.0f; // here, use -1.0f to align with numpy + auto y = randutilimpl::engine_uniform(gen); // y cannot be 0 + return xm * ::expf(-::logf(y) * invalpha) - 1.0f; // here, use -1.0f to align with numpy } }; @@ -38,7 +40,7 @@ struct pareto_t { template RANDUTIL_QUALIFIERS double operator()(gen_t& gen) { - double y = curand_uniform_double(&gen); // y cannot be 0 - return xm * ::exp(-::log(y) * invalpha) - 1.0; // here, use -1.0 to align with numpy + auto y = randutilimpl::engine_uniform(gen); // y cannot be 0 + return xm * ::exp(-::log(y) * invalpha) - 1.0; // here, use -1.0 to align with numpy } }; diff --git a/src/cunumeric/random/randutil/generator_poisson.inl b/src/cunumeric/random/randutil/generator_poisson.inl index 4472591b7c..f383c3f589 100644 --- a/src/cunumeric/random/randutil/generator_poisson.inl +++ b/src/cunumeric/random/randutil/generator_poisson.inl @@ -16,12 +16,14 @@ #include "generator.h" +#include "randomizer.h" + struct poisson { double lambda = 1.0; template RANDUTIL_QUALIFIERS unsigned operator()(gen_t& gen) { - return curand_poisson(&gen, lambda); + return randutilimpl::engine_poisson(gen, lambda); } }; diff --git a/src/cunumeric/random/randutil/generator_power.inl b/src/cunumeric/random/randutil/generator_power.inl index 41b7b00d4c..38292548dc 100644 --- a/src/cunumeric/random/randutil/generator_power.inl +++ b/src/cunumeric/random/randutil/generator_power.inl @@ -16,6 +16,8 @@ #include "generator.h" +#include "randomizer.h" + template struct power_t; @@ -26,7 +28,7 @@ struct power_t { template RANDUTIL_QUALIFIERS float operator()(gen_t& gen) { - float y = curand_uniform(&gen); // y cannot be 0 + float y = randutilimpl::engine_uniform(gen); // y cannot be 0 return ::expf(::logf(y) * invalpha); } }; @@ -38,7 +40,7 @@ struct power_t { template RANDUTIL_QUALIFIERS double operator()(gen_t& gen) { - double y = curand_uniform_double(&gen); // y cannot be 0 -- use y as 1-cdf(x) + double y = randutilimpl::engine_uniform(gen); // y cannot be 0 -- use y as 1-cdf(x) return ::exp(::log(y) * invalpha); } }; diff --git a/src/cunumeric/random/randutil/generator_raw.inl b/src/cunumeric/random/randutil/generator_raw.inl index fa7308d6ad..1aaa77bdca 100644 --- a/src/cunumeric/random/randutil/generator_raw.inl +++ b/src/cunumeric/random/randutil/generator_raw.inl @@ -16,6 +16,8 @@ #include "generator.h" +#include "randomizer.h" + template struct raw; @@ -24,6 +26,6 @@ struct raw { template RANDUTIL_QUALIFIERS uint32_t operator()(gen_t& gen) { - return (uint32_t)curand(&gen); + return (uint32_t)randutilimpl::engine_rand(gen); } }; diff --git a/src/cunumeric/random/randutil/generator_rayleigh.inl b/src/cunumeric/random/randutil/generator_rayleigh.inl index f32304fcfa..adb4c09d56 100644 --- a/src/cunumeric/random/randutil/generator_rayleigh.inl +++ b/src/cunumeric/random/randutil/generator_rayleigh.inl @@ -16,6 +16,8 @@ #include "generator.h" +#include "randomizer.h" + template struct rayleigh_t; @@ -26,7 +28,7 @@ struct rayleigh_t { template RANDUTIL_QUALIFIERS float operator()(gen_t& gen) { - float y = curand_uniform(&gen); // y cannot be 0 + auto y = randutilimpl::engine_uniform(gen); // returns (0, 1]; y cannot be 0 return sigma * ::sqrtf(-2.0f * ::logf(y)); } }; @@ -38,7 +40,7 @@ struct rayleigh_t { template RANDUTIL_QUALIFIERS double operator()(gen_t& gen) { - double y = curand_uniform_double(&gen); // y cannot be 0 + auto y = randutilimpl::engine_uniform(gen); // returns (0, 1]; y cannot be 0 return sigma * ::sqrt(-2.0 * ::log(y)); } }; diff --git a/src/cunumeric/random/randutil/generator_triangular.inl b/src/cunumeric/random/randutil/generator_triangular.inl index 919e6bb8e4..beadee178b 100644 --- a/src/cunumeric/random/randutil/generator_triangular.inl +++ b/src/cunumeric/random/randutil/generator_triangular.inl @@ -16,6 +16,8 @@ #include "generator.h" +#include "randomizer.h" + template struct triangular_t; @@ -26,19 +28,21 @@ struct triangular_t { template RANDUTIL_QUALIFIERS float operator()(gen_t& gen) { - float y = curand_uniform(&gen); // y cannot be 0 + float y = randutilimpl::engine_uniform(gen); // y cannot be 0 if (y <= ((c - a) / (b - a))) { float delta = (y * (b - a) * (c - a)); if (delta < 0.0f) { - delta = 0.0f; + return a; + } else { + return a + ::sqrtf(delta); } - return a + ::sqrtf(delta); } else { float delta = ((1.0f - y) * (b - a) * (b - c)); if (delta < 0.0f) { - delta = 0.0f; + return b; + } else { + return b - ::sqrtf(delta); } - return b - ::sqrtf(delta); } } }; @@ -50,19 +54,21 @@ struct triangular_t { template RANDUTIL_QUALIFIERS double operator()(gen_t& gen) { - double y = curand_uniform_double(&gen); // y cannot be 0 + double y = randutilimpl::engine_uniform(gen); // y cannot be 0 if (y <= ((c - a) / (b - a))) { double delta = (y * (b - a) * (c - a)); if (delta < 0.0) { - delta = 0.0; + return a; + } else { + return a + ::sqrt(delta); } - return a + ::sqrt(delta); } else { double delta = ((1.0 - y) * (b - a) * (b - c)); if (delta < 0.0) { - delta = 0.0; + return b; + } else { + return b - ::sqrt(delta); } - return b - ::sqrt(delta); } } }; diff --git a/src/cunumeric/random/randutil/generator_uniform.inl b/src/cunumeric/random/randutil/generator_uniform.inl index fee4f3edb4..319dadec98 100644 --- a/src/cunumeric/random/randutil/generator_uniform.inl +++ b/src/cunumeric/random/randutil/generator_uniform.inl @@ -16,6 +16,8 @@ #include "generator.h" +#include "randomizer.h" + template struct uniform_t; @@ -26,7 +28,8 @@ struct uniform_t { template RANDUTIL_QUALIFIERS float operator()(gen_t& gen) { - return offset + mult * curand_uniform(&gen); + auto y = randutilimpl::engine_uniform(gen); // returns (0, 1]; + return offset + mult * y; } }; @@ -37,6 +40,7 @@ struct uniform_t { template RANDUTIL_QUALIFIERS double operator()(gen_t& gen) { - return offset + mult * curand_uniform_double(&gen); + auto y = randutilimpl::engine_uniform(gen); // returns (0, 1]; + return offset + mult * y; } }; diff --git a/src/cunumeric/random/randutil/generator_wald.inl b/src/cunumeric/random/randutil/generator_wald.inl index 66b3191ad2..e068ea6b86 100644 --- a/src/cunumeric/random/randutil/generator_wald.inl +++ b/src/cunumeric/random/randutil/generator_wald.inl @@ -17,6 +17,8 @@ #include "generator.h" #include "random_distributions.h" +#include "randomizer.h" + template struct wald_t; @@ -27,11 +29,11 @@ struct wald_t { template RANDUTIL_QUALIFIERS float operator()(gen_t& gen) { - float v = curand_normal(&gen); + float v = randutilimpl::engine_normal(gen); float y = v * v; float x = mu + (mu * mu * y) / (2.0f * lambda) - (mu / (2.0f * lambda)) * ::sqrtf(mu * y * (4.0f * lambda + mu * y)); - float z = curand_uniform(&gen); + float z = randutilimpl::engine_uniform(gen); if (z <= (mu) / (mu + x)) { return x; } else { @@ -47,11 +49,11 @@ struct wald_t { template RANDUTIL_QUALIFIERS double operator()(gen_t& gen) { - double v = curand_normal(&gen); + double v = randutilimpl::engine_normal(gen); double y = v * v; double x = mu + (mu * mu * y) / (2.0 * lambda) - (mu / (2.0 * lambda)) * ::sqrtf(mu * y * (4.0 * lambda + mu * y)); - double z = curand_uniform(&gen); + double z = randutilimpl::engine_uniform(gen); if (z <= (mu) / (mu + x)) { return x; } else { diff --git a/src/cunumeric/random/randutil/generator_weibull.inl b/src/cunumeric/random/randutil/generator_weibull.inl index ccd533b015..4f0411e2ac 100644 --- a/src/cunumeric/random/randutil/generator_weibull.inl +++ b/src/cunumeric/random/randutil/generator_weibull.inl @@ -16,6 +16,8 @@ #include "generator.h" +#include "randomizer.h" + template struct weibull_t; @@ -26,12 +28,10 @@ struct weibull_t { template RANDUTIL_QUALIFIERS float operator()(gen_t& gen) { - float y = curand_uniform(&gen); // y cannot be 0 + float y = randutilimpl::engine_uniform(gen); // y cannot be 0 // log(y) can be zero ! - float lny = ::logf(y); - if (lny == 0.0f) { - return 0.0f; - } + auto lny = ::logf(y); + if (lny == 0.0f) return 0.0f; return lambda * ::expf(::logf(-lny) * invk); } }; @@ -43,12 +43,10 @@ struct weibull_t { template RANDUTIL_QUALIFIERS double operator()(gen_t& gen) { - double y = curand_uniform_double(&gen); // y cannot be 0 + double y = randutilimpl::engine_uniform(gen); // y cannot be 0 // log(y) can be zero ! - float lny = ::log(y); - if (lny == 0.0f) { - return 0.0f; - } + auto lny = ::log(y); + if (lny == 0.0f) return 0.0f; return lambda * ::exp(::log(-lny) * invk); } }; diff --git a/src/cunumeric/random/randutil/random_distributions.h b/src/cunumeric/random/randutil/random_distributions.h index 845350b890..244c8d83f4 100644 --- a/src/cunumeric/random/randutil/random_distributions.h +++ b/src/cunumeric/random/randutil/random_distributions.h @@ -49,10 +49,12 @@ #pragma once +#include "randomizer.h" + template RANDUTIL_QUALIFIERS double rk_double(rk_state* gen) { - return curand_uniform_double(gen); + return randutilimpl::engine_uniform(*gen); // returns (0, 1]; } RANDUTIL_QUALIFIERS double loggam(double x) @@ -120,7 +122,7 @@ RANDUTIL_QUALIFIERS double rk_gauss(rk_state* state) return f*x2; } #endif - return curand_normal(state); + return randutilimpl::engine_normal(*state); } template @@ -143,15 +145,11 @@ RANDUTIL_QUALIFIERS double rk_standard_gamma(rk_state* state, double shape) V = rk_standard_exponential(state); if (U <= 1.0 - shape) { X = pow(U, 1. / shape); - if (X <= V) { - return X; - } + if (X <= V) { return X; } } else { Y = -log((1 - U) / shape); X = pow(1.0 - shape + shape * Y, 1. / shape); - if (X <= (V + Y)) { - return X; - } + if (X <= (V + Y)) { return X; } } } } else { @@ -164,12 +162,8 @@ RANDUTIL_QUALIFIERS double rk_standard_gamma(rk_state* state, double shape) } while (V <= 0.0); V = V * V * V; U = rk_double(state); - if (U < 1.0 - 0.0331 * (X * X) * (X * X)) { - return (b * V); - } - if (log(U) < 0.5 * X * X + b * (1. - V + log(V))) { - return (b * V); - } + if (U < 1.0 - 0.0331 * (X * X) * (X * X)) return (b * V); + if (log(U) < 0.5 * X * X + b * (1. - V + log(V))) return (b * V); } } } @@ -229,12 +223,8 @@ RANDUTIL_QUALIFIERS long rk_poisson_ptrs(rk_state* state, double lam) V = rk_double(state); us = 0.5 - fabs(U); k = (long)floor((2 * a / us + b) * U + lam + 0.43); - if ((us >= 0.07) && (V <= vr)) { - return k; - } - if ((k < 0) || ((us < 0.013) && (V > us))) { - continue; - } + if ((us >= 0.07) && (V <= vr)) { return k; } + if ((k < 0) || ((us < 0.013) && (V > us))) { continue; } if ((log(V) + log(invalpha) - log(a / (us * us) + b)) <= (-lam + k * loglam - loggam(k + 1))) { return k; } @@ -272,7 +262,7 @@ RANDUTIL_QUALIFIERS long rk_poisson(rk_state* state, double lam) return rk_poisson_mult(state, lam); } #endif - return curand_poisson(state, lam); + return randutilimpl::engine_poisson(*state, lam); } template @@ -284,9 +274,7 @@ RANDUTIL_QUALIFIERS double rk_chisquare(rk_state* state, double df) template RANDUTIL_QUALIFIERS double rk_noncentral_chisquare(rk_state* state, double df, double nonc) { - if (nonc == 0) { - return rk_chisquare(state, df); - } + if (nonc == 0) { return rk_chisquare(state, df); } if (1 < df) { const double Chi2 = rk_chisquare(state, df - 1); const double N = rk_gauss(state) + sqrt(nonc); @@ -318,9 +306,7 @@ RANDUTIL_QUALIFIERS long rk_logseries(rk_state* state, double p) r = log(1.0 - p); while (1) { V = rk_double(state); - if (V >= p) { - return 1; - } + if (V >= p) { return 1; } U = rk_double(state); q = 1.0 - exp(r * U); if (V <= q * q) { @@ -331,9 +317,7 @@ RANDUTIL_QUALIFIERS long rk_logseries(rk_state* state, double p) return result; } } - if (V >= q) { - return 1; - } + if (V >= q) { return 1; } return 2; } } @@ -389,13 +373,9 @@ RANDUTIL_QUALIFIERS long rk_zipf(rk_state* state, double a) U = 1.0 - rk_double(state); V = rk_double(state); X = floor(pow(U, -1.0 / am1)); - if (X < 1.0) { - continue; - } + if (X < 1.0) { continue; } T = pow(1.0 + 1.0 / X, am1); - if (V * X * (T - 1.0) / (b - 1.0) <= T / b) { - return (long)X; - } + if (V * X * (T - 1.0) / (b - 1.0) <= T / b) { return (long)X; } } } @@ -427,22 +407,16 @@ RANDUTIL_QUALIFIERS double rk_vonmises(rk_state* state, double mu, double kappa) W = (1 + s * Z) / (s + Z); Y = kappa * (s - W); V = rk_double(state); - if ((Y * (2 - Y) - V >= 0) || (log(Y / V) + 1 - Y >= 0)) { - break; - } + if ((Y * (2 - Y) - V >= 0) || (log(Y / V) + 1 - Y >= 0)) { break; } } U = rk_double(state); result = acos(W); - if (U < 0.5) { - result = -result; - } + if (U < 0.5) { result = -result; } result += mu; neg = (result < 0); mod = fabs(result); mod = (fmod(mod + M_PI, 2 * M_PI) - M_PI); - if (neg) { - mod *= -1; - } + if (neg) { mod *= -1; } return mod; } } @@ -463,14 +437,10 @@ RANDUTIL_QUALIFIERS long rk_hypergeometric_hyp(rk_state* state, long good, long U = rk_double(state); Y -= (long)floor(U + Y / (d1 + K)); K--; - if (K == 0) { - break; - } + if (K == 0) break; } Z = (long)(d2 - Y); - if (good > bad) { - Z = sample - Z; - } + if (good > bad) Z = sample - Z; return Z; } /* D1 = 2*sqrt(2/e) */ @@ -503,32 +473,20 @@ RANDUTIL_QUALIFIERS long rk_hypergeometric_hrua(rk_state* state, long good, long Y = rk_double(state); W = d6 + d8 * (Y - 0.5) / X; /* fast rejection: */ - if ((W < 0.0) || (W >= d11)) { - continue; - } + if ((W < 0.0) || (W >= d11)) continue; Z = (long)floor(W); T = d10 - (loggam(Z + 1) + loggam(mingoodbad - Z + 1) + loggam(m - Z + 1) + loggam(maxgoodbad - m + Z + 1)); /* fast acceptance: */ - if ((X * (4.0 - X) - 3.0) <= T) { - break; - } + if ((X * (4.0 - X) - 3.0) <= T) break; /* fast rejection: */ - if (X * (X - T) >= 1) { - continue; - } - if (2.0 * log(X) <= T) { - break; /* acceptance */ - } + if (X * (X - T) >= 1) continue; + if (2.0 * log(X) <= T) break; /* acceptance */ } /* this is a correction to HRUA* by Ivan Frohne in rv.py */ - if (good > bad) { - Z = m - Z; - } + if (good > bad) Z = m - Z; /* another fix from rv.py to allow sample to exceed popsize/2 */ - if (m < sample) { - Z = good - Z; - } + if (m < sample) Z = good - Z; return Z; } #undef D1 @@ -573,69 +531,45 @@ RANDUTIL_QUALIFIERS unsigned rk_binomial_btpe(rk_state* state, unsigned n, doubl nrq = n * r * q; u = rk_double(state) * p4; v = rk_double(state); - if (u > p1) { - goto Step20; - } + if (u > p1) goto Step20; y = (long)floor(xm - p1 * v + u); goto Step60; Step20: - if (u > p2) { - goto Step30; - } + if (u > p2) goto Step30; x = xl + (u - p1) / c; v = v * c + 1.0 - fabs(m - x + 0.5) / p1; - if (v > 1.0) { - goto Step10; - } + if (v > 1.0) goto Step10; y = (long)floor(x); goto Step50; Step30: - if (u > p3) { - goto Step40; - } + if (u > p3) goto Step40; y = (long)floor(xl + log(v) / laml); - if (y < 0) { - goto Step10; - } + if (y < 0) goto Step10; v = v * (u - p2) * laml; goto Step50; Step40: y = (long)floor(xr - log(v) / lamr); - if (y > n) { - goto Step10; - } + if (y > n) goto Step10; v = v * (u - p3) * lamr; Step50: k = labs(y - m); - if ((k > 20) && (k < ((nrq) / 2.0 - 1))) { - goto Step52; - } + if ((k > 20) && (k < ((nrq) / 2.0 - 1))) goto Step52; s = r / q; a = s * (n + 1); F = 1.0; if (m < y) { - for (i = m + 1; i <= y; i++) { - F *= (a / i - s); - } + for (i = m + 1; i <= y; i++) { F *= (a / i - s); } } else if (m > y) { - for (i = y + 1; i <= m; i++) { - F /= (a / i - s); - } - } - if (v > F) { - goto Step10; + for (i = y + 1; i <= m; i++) { F /= (a / i - s); } } + if (v > F) goto Step10; goto Step60; Step52: rho = (k / (nrq)) * ((k * (k / 3.0 + 0.625) + 0.16666666666666666) / nrq + 0.5); t = -k * k / (2 * nrq); A = log(v); - if (A < (t - rho)) { - goto Step60; - } - if (A > (t + rho)) { - goto Step10; - } + if (A < (t - rho)) goto Step60; + if (A > (t + rho)) goto Step10; x1 = y + 1; f1 = m + 1; z = n + 1 - m; @@ -652,9 +586,7 @@ RANDUTIL_QUALIFIERS unsigned rk_binomial_btpe(rk_state* state, unsigned n, doubl goto Step10; } Step60: - if (p > 0.5) { - y = n - y; - } + if (p > 0.5) { y = n - y; } return (unsigned)y; } diff --git a/src/cunumeric/random/randutil/randomizer.h b/src/cunumeric/random/randutil/randomizer.h new file mode 100644 index 0000000000..4b9fc068db --- /dev/null +++ b/src/cunumeric/random/randutil/randomizer.h @@ -0,0 +1,143 @@ +/* Copyright 2023 NVIDIA Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +#pragma once + +#include +#include +#include + +#include + +#include + +namespace randutilimpl { + +// trampoline functions for branching-off CURAND vs. STL +// implementations (STL used on platforms that don't support CUDA) +// +template +RANDUTIL_QUALIFIERS decltype(auto) engine_uniform(gen_t& gen) +{ +#ifdef CUNUMERIC_USE_STL_RANDOM_ENGINE + std::uniform_real_distribution dis(0, 1); + auto y = dis(gen); // returns [0, 1); + + // bring to (0, 1]: + return 1 - y; +#else + if constexpr (std::is_same_v) { + return curand_uniform(&gen); // returns (0, 1]; + } else { + static_assert(std::is_same_v, + "Unexpected type for uniform double generator."); + return curand_uniform_double(&gen); // returns (0, 1]; + } +#endif +} + +template +RANDUTIL_QUALIFIERS decltype(auto) engine_poisson(gen_t& gen, double lambda) +{ +#ifdef CUNUMERIC_USE_STL_RANDOM_ENGINE + std::poisson_distribution dis(lambda); + return dis(gen); +#else + return curand_poisson(&gen, lambda); +#endif +} + +template +RANDUTIL_QUALIFIERS decltype(auto) engine_normal(gen_t& gen) +{ +#ifdef CUNUMERIC_USE_STL_RANDOM_ENGINE + std::normal_distribution dis(0, 1); + return dis(gen); +#else + if constexpr (std::is_same_v) { + return curand_normal(&gen); + } else { + static_assert(std::is_same_v, + "Unexpected type for normal double generator."); + return curand_normal_double(&gen); + } +#endif +} + +template +RANDUTIL_QUALIFIERS decltype(auto) engine_log_normal(gen_t& gen, element_t mean, element_t stddev) +{ +#ifdef CUNUMERIC_USE_STL_RANDOM_ENGINE + std::lognormal_distribution dis{mean, stddev}; + return dis(gen); +#else + if constexpr (std::is_same_v) { + return curand_log_normal(&gen, mean, stddev); + } else { + static_assert(std::is_same_v, + "Unexpected type for log normal double generator."); + return curand_log_normal_double(&gen, mean, stddev); + } +#endif +} + +template +RANDUTIL_QUALIFIERS decltype(auto) engine_rand(gen_t& gen) +{ +#ifdef CUNUMERIC_USE_STL_RANDOM_ENGINE + return std::rand(); +#else + return curand(&gen); +#endif +} + +#ifdef EXPERIMENTAL_STL_BRANCH_OFF_ +enum class random_client_t : int { CURAND = 0, STL }; + +template +struct randomizer_t; + +// Curand randomizer maintains a state: +// +template +struct randomizer_t { + randomizer_t(state_t state, std::function f) : state_(state), f_(std::move(f)) {} + + ret_t run(void) { return f_(state_); } + + private: + state_t state_; + std::function f_; +}; + +// STL randomizer uses Distribution-specific info (DSI); e.g., (low, high) for uniform: +// +template +struct randomizer_t { + randomizer_t(dsi_data_t const& data, std::function f) + : data_(data), f_(std::move(f)) + { + } + + ret_t run(void) const { return f_(data_); } + + private: + dsi_data_t data_; + std::function f_; +}; +#endif + +} // namespace randutilimpl diff --git a/src/cunumeric/random/randutil/randutil.h b/src/cunumeric/random/randutil/randutil.h index 302ed40e02..e52e8a77ca 100644 --- a/src/cunumeric/random/randutil/randutil.h +++ b/src/cunumeric/random/randutil/randutil.h @@ -16,7 +16,9 @@ #pragma once #include -#include +// #include + +#include "cunumeric/random/rnd_aliases.h" typedef void* randutilGenerator_t; @@ -24,149 +26,149 @@ typedef void* randutilGenerator_t; // CUDA-ONLY API #if LEGATE_DEFINED(LEGATE_USE_CUDA) -extern "C" curandStatus_t randutilCreateGenerator(randutilGenerator_t* generator, - curandRngType_t rng_type, - uint64_t seed, - uint64_t generatorID, - uint32_t flags, - cudaStream_t stream); +extern "C" rnd_status_t randutilCreateGenerator(randutilGenerator_t* generator, + randRngType_t rng_type, + uint64_t seed, + uint64_t generatorID, + uint32_t flags, + stream_t stream); #endif -extern "C" curandStatus_t randutilCreateGeneratorHost(randutilGenerator_t* generator, - curandRngType_t rng_type, - uint64_t seed, - uint64_t generatorID, - uint32_t flags); -extern "C" curandStatus_t randutilDestroyGenerator(randutilGenerator_t generator); +extern "C" rnd_status_t randutilCreateGeneratorHost(randutilGenerator_t* generator, + randRngType_t rng_type, + uint64_t seed, + uint64_t generatorID, + uint32_t flags); +extern "C" rnd_status_t randutilDestroyGenerator(randutilGenerator_t generator); /* curand distributions */ -extern "C" curandStatus_t randutilGenerateIntegers16(randutilGenerator_t generator, - int16_t* outputPtr, - size_t num, - int16_t low /* inclusive */, - int16_t high /* exclusive */); - -extern "C" curandStatus_t randutilGenerateIntegers32(randutilGenerator_t generator, - int32_t* outputPtr, - size_t num, - int32_t low /* inclusive */, - int32_t high /* exclusive */); -extern "C" curandStatus_t randutilGenerateIntegers64(randutilGenerator_t generator, - int64_t* outputPtr, - size_t num, - int64_t low /* inclusive */, - int64_t high /* exclusive */); -extern "C" curandStatus_t randutilGenerateRawUInt32(randutilGenerator_t generator, - uint32_t* outputPtr, - size_t num); - -extern "C" curandStatus_t randutilGenerateUniformEx(randutilGenerator_t generator, - float* outputPtr, - size_t num, - float low = 0.0f, /* inclusive */ - float high = 1.0f /* exclusive */); +extern "C" rnd_status_t randutilGenerateIntegers16(randutilGenerator_t generator, + int16_t* outputPtr, + size_t num, + int16_t low /* inclusive */, + int16_t high /* exclusive */); + +extern "C" rnd_status_t randutilGenerateIntegers32(randutilGenerator_t generator, + int32_t* outputPtr, + size_t num, + int32_t low /* inclusive */, + int32_t high /* exclusive */); +extern "C" rnd_status_t randutilGenerateIntegers64(randutilGenerator_t generator, + int64_t* outputPtr, + size_t num, + int64_t low /* inclusive */, + int64_t high /* exclusive */); +extern "C" rnd_status_t randutilGenerateRawUInt32(randutilGenerator_t generator, + uint32_t* outputPtr, + size_t num); + +extern "C" rnd_status_t randutilGenerateUniformEx(randutilGenerator_t generator, + float* outputPtr, + size_t num, + float low = 0.0f, /* inclusive */ + float high = 1.0f /* exclusive */); -extern "C" curandStatus_t randutilGenerateUniformDoubleEx(randutilGenerator_t generator, - double* outputPtr, - size_t num, - double low = 0.0, /* inclusive */ - double high = 1.0 /* exclusive */); +extern "C" rnd_status_t randutilGenerateUniformDoubleEx(randutilGenerator_t generator, + double* outputPtr, + size_t num, + double low = 0.0, /* inclusive */ + double high = 1.0 /* exclusive */); -extern "C" curandStatus_t randutilGenerateLogNormalEx( +extern "C" rnd_status_t randutilGenerateLogNormalEx( randutilGenerator_t generator, float* outputPtr, size_t n, float mean, float stddev); -extern "C" curandStatus_t randutilGenerateLogNormalDoubleEx( +extern "C" rnd_status_t randutilGenerateLogNormalDoubleEx( randutilGenerator_t generator, double* outputPtr, size_t n, double mean, double stddev); -extern "C" curandStatus_t randutilGenerateNormalEx( +extern "C" rnd_status_t randutilGenerateNormalEx( randutilGenerator_t generator, float* outputPtr, size_t n, float mean, float stddev); -extern "C" curandStatus_t randutilGenerateNormalDoubleEx( +extern "C" rnd_status_t randutilGenerateNormalDoubleEx( randutilGenerator_t generator, double* outputPtr, size_t n, double mean, double stddev); -extern "C" curandStatus_t randutilGeneratePoissonEx(randutilGenerator_t generator, - uint32_t* outputPtr, - size_t n, - double lambda); +extern "C" rnd_status_t randutilGeneratePoissonEx(randutilGenerator_t generator, + uint32_t* outputPtr, + size_t n, + double lambda); /* Straightforward Distributions */ -extern "C" curandStatus_t randutilGenerateExponentialEx(randutilGenerator_t generator, - float* outputPtr, - size_t n, - float scale); -extern "C" curandStatus_t randutilGenerateExponentialDoubleEx(randutilGenerator_t generator, - double* outputPtr, - size_t n, - double scale); +extern "C" rnd_status_t randutilGenerateExponentialEx(randutilGenerator_t generator, + float* outputPtr, + size_t n, + float scale); +extern "C" rnd_status_t randutilGenerateExponentialDoubleEx(randutilGenerator_t generator, + double* outputPtr, + size_t n, + double scale); -extern "C" curandStatus_t randutilGenerateGumbelEx( +extern "C" rnd_status_t randutilGenerateGumbelEx( randutilGenerator_t generator, float* outputPtr, size_t n, float mu, float beta); -extern "C" curandStatus_t randutilGenerateGumbelDoubleEx( +extern "C" rnd_status_t randutilGenerateGumbelDoubleEx( randutilGenerator_t generator, double* outputPtr, size_t n, double mu, double beta); -extern "C" curandStatus_t randutilGenerateLaplaceEx( +extern "C" rnd_status_t randutilGenerateLaplaceEx( randutilGenerator_t generator, float* outputPtr, size_t n, float mu, float beta); -extern "C" curandStatus_t randutilGenerateLaplaceDoubleEx( +extern "C" rnd_status_t randutilGenerateLaplaceDoubleEx( randutilGenerator_t generator, double* outputPtr, size_t n, double mu, double beta); -extern "C" curandStatus_t randutilGenerateLogisticEx( +extern "C" rnd_status_t randutilGenerateLogisticEx( randutilGenerator_t generator, float* outputPtr, size_t n, float mu, float beta); -extern "C" curandStatus_t randutilGenerateLogisticDoubleEx( +extern "C" rnd_status_t randutilGenerateLogisticDoubleEx( randutilGenerator_t generator, double* outputPtr, size_t n, double mu, double beta); -extern "C" curandStatus_t randutilGenerateParetoEx( +extern "C" rnd_status_t randutilGenerateParetoEx( randutilGenerator_t generator, float* outputPtr, size_t n, float xm, float alpha); -extern "C" curandStatus_t randutilGenerateParetoDoubleEx( +extern "C" rnd_status_t randutilGenerateParetoDoubleEx( randutilGenerator_t generator, double* outputPtr, size_t n, double xm, double alpha); -extern "C" curandStatus_t randutilGeneratePowerEx(randutilGenerator_t generator, - float* outputPtr, - size_t n, - float alpha); -extern "C" curandStatus_t randutilGeneratePowerDoubleEx(randutilGenerator_t generator, - double* outputPtr, - size_t n, - double alpha); - -extern "C" curandStatus_t randutilGenerateRayleighEx(randutilGenerator_t generator, - float* outputPtr, - size_t n, - float sigma); -extern "C" curandStatus_t randutilGenerateRayleighDoubleEx(randutilGenerator_t generator, - double* outputPtr, - size_t n, - double sigma); - -extern "C" curandStatus_t randutilGenerateCauchyEx( +extern "C" rnd_status_t randutilGeneratePowerEx(randutilGenerator_t generator, + float* outputPtr, + size_t n, + float alpha); +extern "C" rnd_status_t randutilGeneratePowerDoubleEx(randutilGenerator_t generator, + double* outputPtr, + size_t n, + double alpha); + +extern "C" rnd_status_t randutilGenerateRayleighEx(randutilGenerator_t generator, + float* outputPtr, + size_t n, + float sigma); +extern "C" rnd_status_t randutilGenerateRayleighDoubleEx(randutilGenerator_t generator, + double* outputPtr, + size_t n, + double sigma); + +extern "C" rnd_status_t randutilGenerateCauchyEx( randutilGenerator_t generator, float* outputPtr, size_t n, float x0, float gamma); -extern "C" curandStatus_t randutilGenerateCauchyDoubleEx( +extern "C" rnd_status_t randutilGenerateCauchyDoubleEx( randutilGenerator_t generator, double* outputPtr, size_t n, double x0, double gamma); -extern "C" curandStatus_t randutilGenerateTriangularEx( +extern "C" rnd_status_t randutilGenerateTriangularEx( randutilGenerator_t generator, float* outputPtr, size_t n, float a, float b, float c); -extern "C" curandStatus_t randutilGenerateTriangularDoubleEx( +extern "C" rnd_status_t randutilGenerateTriangularDoubleEx( randutilGenerator_t generator, double* outputPtr, size_t n, double a, double b, double c); -extern "C" curandStatus_t randutilGenerateWeibullEx( +extern "C" rnd_status_t randutilGenerateWeibullEx( randutilGenerator_t generator, float* outputPtr, size_t n, float lam, float k); -extern "C" curandStatus_t randutilGenerateWeibullDoubleEx( +extern "C" rnd_status_t randutilGenerateWeibullDoubleEx( randutilGenerator_t generator, double* outputPtr, size_t n, double lam, double k); /* more advanced distributions */ -extern "C" curandStatus_t randutilGenerateBetaEx( +extern "C" rnd_status_t randutilGenerateBetaEx( randutilGenerator_t generator, float* outputPtr, size_t n, float a, float b); -extern "C" curandStatus_t randutilGenerateBetaDoubleEx( +extern "C" rnd_status_t randutilGenerateBetaDoubleEx( randutilGenerator_t generator, double* outputPtr, size_t n, double a, double b); -extern "C" curandStatus_t randutilGenerateFisherSnedecorEx( +extern "C" rnd_status_t randutilGenerateFisherSnedecorEx( randutilGenerator_t generator, float* outputPtr, size_t n, float dfnum, float dfden, float nonc = 0.0f); // 0.0f is F distribution -extern "C" curandStatus_t randutilGenerateFisherSnedecorDoubleEx( +extern "C" rnd_status_t randutilGenerateFisherSnedecorDoubleEx( randutilGenerator_t generator, double* outputPtr, size_t n, @@ -174,57 +176,57 @@ extern "C" curandStatus_t randutilGenerateFisherSnedecorDoubleEx( double dfden, double nonc = 0.0); // 0.0 is F distribution -extern "C" curandStatus_t randutilGenerateLogSeriesEx(randutilGenerator_t generator, - uint32_t* outputPtr, - size_t n, - double p); +extern "C" rnd_status_t randutilGenerateLogSeriesEx(randutilGenerator_t generator, + uint32_t* outputPtr, + size_t n, + double p); -extern "C" curandStatus_t randutilGenerateChiSquareEx( +extern "C" rnd_status_t randutilGenerateChiSquareEx( randutilGenerator_t generator, float* outputPtr, size_t n, float df, float nonc = 0.0); -extern "C" curandStatus_t randutilGenerateChiSquareDoubleEx( +extern "C" rnd_status_t randutilGenerateChiSquareDoubleEx( randutilGenerator_t generator, double* outputPtr, size_t n, double df, double nonc = 0.0); -extern "C" curandStatus_t randutilGenerateGammaEx( +extern "C" rnd_status_t randutilGenerateGammaEx( randutilGenerator_t generator, float* outputPtr, size_t n, float shape, float scale = 1.0f); // scale = 1.0 is standard_gamma -extern "C" curandStatus_t randutilGenerateGammaDoubleEx( +extern "C" rnd_status_t randutilGenerateGammaDoubleEx( randutilGenerator_t generator, double* outputPtr, size_t n, double shape, double scale = 1.0); -extern "C" curandStatus_t randutilGenerateStandardTDoubleEx(randutilGenerator_t generator, - double* outputPtr, - size_t n, - double df); -extern "C" curandStatus_t randutilGenerateStandardTEx(randutilGenerator_t generator, - float* outputPtr, - size_t n, - float df); -extern "C" curandStatus_t randutilGenerateHyperGeometricEx(randutilGenerator_t generator, - uint32_t* outputPtr, - size_t n, - int64_t ngood, - int64_t nbad, - int64_t nsample); -extern "C" curandStatus_t randutilGenerateVonMisesDoubleEx( +extern "C" rnd_status_t randutilGenerateStandardTDoubleEx(randutilGenerator_t generator, + double* outputPtr, + size_t n, + double df); +extern "C" rnd_status_t randutilGenerateStandardTEx(randutilGenerator_t generator, + float* outputPtr, + size_t n, + float df); +extern "C" rnd_status_t randutilGenerateHyperGeometricEx(randutilGenerator_t generator, + uint32_t* outputPtr, + size_t n, + int64_t ngood, + int64_t nbad, + int64_t nsample); +extern "C" rnd_status_t randutilGenerateVonMisesDoubleEx( randutilGenerator_t generator, double* outputPtr, size_t n, double mu, double kappa); -extern "C" curandStatus_t randutilGenerateVonMisesEx( +extern "C" rnd_status_t randutilGenerateVonMisesEx( randutilGenerator_t generator, float* outputPtr, size_t n, float mu, float kappa); -extern "C" curandStatus_t randutilGenerateZipfEx(randutilGenerator_t generator, - uint32_t* outputPtr, - size_t n, - double a); -extern "C" curandStatus_t randutilGenerateGeometricEx(randutilGenerator_t generator, - uint32_t* outputPtr, - size_t n, - double p); -extern "C" curandStatus_t randutilGenerateWaldDoubleEx( +extern "C" rnd_status_t randutilGenerateZipfEx(randutilGenerator_t generator, + uint32_t* outputPtr, + size_t n, + double a); +extern "C" rnd_status_t randutilGenerateGeometricEx(randutilGenerator_t generator, + uint32_t* outputPtr, + size_t n, + double p); +extern "C" rnd_status_t randutilGenerateWaldDoubleEx( randutilGenerator_t generator, double* outputPtr, size_t n, double mu, double lambda); -extern "C" curandStatus_t randutilGenerateWaldEx( +extern "C" rnd_status_t randutilGenerateWaldEx( randutilGenerator_t generator, float* outputPtr, size_t n, float mu, float lambda); -extern "C" curandStatus_t randutilGenerateBinomialEx( +extern "C" rnd_status_t randutilGenerateBinomialEx( randutilGenerator_t generator, uint32_t* outputPtr, size_t n, uint32_t ntrials, double p); -extern "C" curandStatus_t randutilGenerateNegativeBinomialEx( +extern "C" rnd_status_t randutilGenerateNegativeBinomialEx( randutilGenerator_t generator, uint32_t* outputPtr, size_t n, uint32_t ntrials, double p); diff --git a/src/cunumeric/random/randutil/randutil_curand.h b/src/cunumeric/random/randutil/randutil_curand.h index 86c3130e3c..c8add0635e 100644 --- a/src/cunumeric/random/randutil/randutil_curand.h +++ b/src/cunumeric/random/randutil/randutil_curand.h @@ -30,6 +30,10 @@ // host generators are not compiled with nvcc #define QUALIFIERS static #define RANDUTIL_QUALIFIERS +#ifdef CUNUMERIC_USE_STL_RANDOM_ENGINE +#include +#else #include +#endif -#endif \ No newline at end of file +#endif diff --git a/src/cunumeric/random/rnd_aliases.h b/src/cunumeric/random/rnd_aliases.h new file mode 100644 index 0000000000..89d3fbebd2 --- /dev/null +++ b/src/cunumeric/random/rnd_aliases.h @@ -0,0 +1,75 @@ +/* Copyright 2023 NVIDIA Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +#pragma once + +#ifdef CUNUMERIC_USE_STL_RANDOM_ENGINE + +// #pragma message("************ STL path *************") + +#include +#include + +using rnd_status_t = int; +enum class randRngType : int { RND_RNG_TEST = 0, STL_MT_19937 = 1 }; +using randRngType_t = randRngType; +inline constexpr int RND_STATUS_SUCCESS = 0; + +// same as for curand: +// +inline constexpr rnd_status_t RND_STATUS_INTERNAL_ERROR = 999; +inline constexpr rnd_status_t RND_STATUS_TYPE_ERROR = 103; + +// namespace randutilimpl { +inline constexpr int RND_RNG_PSEUDO_XORWOW = static_cast(randRngType::STL_MT_19937); +inline constexpr int RND_RNG_PSEUDO_PHILOX4_32_10 = static_cast(randRngType::STL_MT_19937) + 1; +inline constexpr int RND_RNG_PSEUDO_MRG32K3A = static_cast(randRngType::STL_MT_19937) + 2; + +// cannot be same, b/c they are used for +// specializing class generatorid: +// +using gen_XORWOW_t = std::mt19937; +using gen_Philox4_32_10_t = std::mt19937; +using gen_MRG32k3a_t = std::mt19937; +//} // namespace randutilimpl + +using stream_t = void*; +#else +#include +#include + +// #pragma message("************ CURAND path ************") + +using rnd_status_t = curandStatus_t; +using randRngType = curandRngType; +using randRngType_t = curandRngType_t; +inline constexpr rnd_status_t RND_STATUS_SUCCESS = CURAND_STATUS_SUCCESS; +inline constexpr rnd_status_t RND_STATUS_INTERNAL_ERROR = CURAND_STATUS_INTERNAL_ERROR; +inline constexpr rnd_status_t RND_STATUS_TYPE_ERROR = CURAND_STATUS_TYPE_ERROR; + +inline constexpr randRngType_t RND_RNG_TEST = CURAND_RNG_TEST; + +inline constexpr int RND_RNG_PSEUDO_XORWOW = CURAND_RNG_PSEUDO_XORWOW; +inline constexpr int RND_RNG_PSEUDO_PHILOX4_32_10 = CURAND_RNG_PSEUDO_PHILOX4_32_10; +inline constexpr int RND_RNG_PSEUDO_MRG32K3A = CURAND_RNG_PSEUDO_MRG32K3A; + +using gen_XORWOW_t = curandStateXORWOW_t; +using gen_Philox4_32_10_t = curandStatePhilox4_32_10_t; +using gen_MRG32k3a_t = curandStateMRG32k3a_t; + +using stream_t = cudaStream_t; + +#endif diff --git a/src/cunumeric/random/rnd_types.h b/src/cunumeric/random/rnd_types.h new file mode 100644 index 0000000000..6815f12e44 --- /dev/null +++ b/src/cunumeric/random/rnd_types.h @@ -0,0 +1,63 @@ +/* Copyright 2023 NVIDIA Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +#pragma once + +#include "cunumeric/random/rnd_aliases.h" + +#ifdef CUNUMERIC_USE_STL_RANDOM_ENGINE + +#define CHECK_RND_ENGINE(expr) \ + do { \ + rnd_status_t __result__ = (expr); \ + randutil_check_status(__result__, __FILE__, __LINE__); \ + } while (false) + +// #define randutil_check_curand randutil_check_status + +namespace cunumeric { +legate::Logger& randutil_log(); + +void randutil_check_status(rnd_status_t error, const char* file, int line); + +static inline randRngType get_rndRngType(cunumeric::BitGeneratorType kind) +{ + // for now, all generator types rerouted to STL + // would use the MT19937 generator; perhaps, + // this might become more flexible in the future; + // + switch (kind) { + case cunumeric::BitGeneratorType::DEFAULT: return randRngType::STL_MT_19937; + case cunumeric::BitGeneratorType::XORWOW: return randRngType::STL_MT_19937; + case cunumeric::BitGeneratorType::MRG32K3A: return randRngType::STL_MT_19937; + case cunumeric::BitGeneratorType::MTGP32: return randRngType::STL_MT_19937; + case cunumeric::BitGeneratorType::MT19937: return randRngType::STL_MT_19937; + case cunumeric::BitGeneratorType::PHILOX4_32_10: return randRngType::STL_MT_19937; + default: LEGATE_ABORT(); + } + return randRngType::RND_RNG_TEST; +} + +} // namespace cunumeric + +#else +#include "cunumeric/random/curand_help.h" + +#define CHECK_RND_ENGINE(...) CHECK_CURAND(__VA_ARGS__) +#define randutil_check_status randutil_check_curand +#define get_rndRngType get_curandRngType + +#endif diff --git a/tests/integration/test_random.py b/tests/integration/test_random.py index 34041ff165..abbf281188 100644 --- a/tests/integration/test_random.py +++ b/tests/integration/test_random.py @@ -16,20 +16,25 @@ import cunumeric as num - +@pytest.mark.xfail( + reason = "https://github.com/nv-legate/cunumeric.internal/issues/199" +) def test_basic_num() -> None: num.random.seed(10) L1 = num.random.randn(3, 3) num.random.seed(10) L2 = num.random.randn(3, 3) - assert not np.array_equal(L1, L2) + assert np.array_equal(L1, L2) num.random.seed(10) L1 = num.random.randn(3, 3) L2 = num.random.randn(3, 3) - assert not np.array_equal(L1, L2) + assert np.array_equal(L1, L2) +@pytest.mark.xfail( + reason = "numpy failures" +) def test_basic_np() -> None: np.random.seed(10) L1 = np.random.randn(3, 3) @@ -43,12 +48,15 @@ def test_basic_np() -> None: assert not np.array_equal(L1, L2) +@pytest.mark.xfail( + reason = "https://github.com/nv-legate/cunumeric.internal/issues/199" +) def test_none_num() -> None: num.random.seed() L1 = num.random.randn(3, 3) num.random.seed() L2 = num.random.randn(3, 3) - assert not np.array_equal(L1, L2) + assert np.array_equal(L1, L2) num.random.seed() L1 = num.random.randn(3, 3) @@ -56,6 +64,9 @@ def test_none_num() -> None: assert not np.array_equal(L1, L2) +@pytest.mark.xfail( + reason = "numpy failures" +) def test_none_np() -> None: np.random.seed() L1 = np.random.randn(3, 3) @@ -69,6 +80,9 @@ def test_none_np() -> None: assert not np.array_equal(L1, L2) +@pytest.mark.xfail( + reason = "numpy failures" +) def test_basic_num_np() -> None: np.random.seed(10) L1 = np.random.randn(3, 3) @@ -77,6 +91,9 @@ def test_basic_num_np() -> None: assert not np.array_equal(L1, L2) +@pytest.mark.xfail( + reason = "https://github.com/nv-legate/cunumeric.internal/issues/199" +) def test_RandomState() -> None: rdm_num = num.random.RandomState(10) L1 = rdm_num.randn(3, 3) diff --git a/tests/integration/test_random_bitgenerator.py b/tests/integration/test_random_bitgenerator.py index 864fbb9650..0268af14a6 100644 --- a/tests/integration/test_random_bitgenerator.py +++ b/tests/integration/test_random_bitgenerator.py @@ -272,7 +272,7 @@ def test_init_bitgenerator(self): num.random.BitGenerator() @pytest.mark.xfail - @pytest.mark.parametrize("dtype", (np.int32, np.float128, str)) + @pytest.mark.parametrize("dtype", (np.int32, str)) def test_random_invalid_dtype(self, dtype): expected_exc = TypeError seed = 42 diff --git a/tests/integration/test_random_creation.py b/tests/integration/test_random_creation.py index e4391507aa..78ad827dc4 100644 --- a/tests/integration/test_random_creation.py +++ b/tests/integration/test_random_creation.py @@ -185,7 +185,6 @@ def test_rand(shape): ALL_RNG_SIZES = SMALL_RNG_SIZES + LARGE_RNG_SIZES + [None] INT_DTYPES = [np.int64, np.int32, np.int16] UINT_DTYPES = [np.uint64, np.uint16, np.uintp] -FLOAT_DTYPES = [np.float16, np.float128, np.float64] @pytest.mark.parametrize("size", ALL_RNG_SIZES, ids=str) From 5cba7f1b47ee53fab55ef1d188a1596a6b046ab6 Mon Sep 17 00:00:00 2001 From: Bryan Van de Ven Date: Tue, 24 Sep 2024 16:29:35 -0700 Subject: [PATCH 314/462] safer self-assigns for advanced indexing (#399) * safer self-assigns for advanced indexing * restrict copy check to advanced indexing case * skip on old np --- cunumeric/_thunk/deferred.py | 5 ++++- tests/integration/test_overlap.py | 12 ++++++++++++ 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/cunumeric/_thunk/deferred.py b/cunumeric/_thunk/deferred.py index 9003fdbf33..0037a25eb2 100644 --- a/cunumeric/_thunk/deferred.py +++ b/cunumeric/_thunk/deferred.py @@ -943,8 +943,12 @@ def get_item(self, key: Any) -> NumPyThunk: @auto_convert("rhs") def set_item(self, key: Any, rhs: Any) -> None: assert self.dtype == rhs.dtype + # Check to see if this is advanced indexing or not if is_advanced_indexing(key): + # copy if a self-copy might overlap + rhs = rhs._copy_if_overlapping(self) + # Create the indexing array ( copy_needed, @@ -1017,7 +1021,6 @@ def set_item(self, key: Any, rhs: Any) -> None: # done, the effect of inplace update is already reflected # to the arr. Therefore, we skip the copy to avoid redundant # copies if we know that we hit such a scenario. - # TODO: We should make this work for the advanced indexing case # NOTE: Neither Store nor Storage have an __eq__, so we can # only check that the underlying RegionField/Future corresponds # to the same Legion handle. diff --git a/tests/integration/test_overlap.py b/tests/integration/test_overlap.py index 8d24958dc8..e695297486 100644 --- a/tests/integration/test_overlap.py +++ b/tests/integration/test_overlap.py @@ -15,6 +15,7 @@ import numpy as np import pytest +from numpy.lib import NumpyVersion from utils.comparisons import allclose from utils.generators import mk_seq_array @@ -75,6 +76,17 @@ def test_partial(partial, shape, operation): assert allclose(a_np, a_num) +@pytest.mark.skipif( + NumpyVersion(np.__version__) < "2.1.0", reason="old np broken" +) +def test_advanced_indexing_setitem(): + arr = num.array([0, 1, 2]) + idx = num.array([2, 1, 0]) + arr[idx] = arr + + assert np.array_equal(arr, [2, 1, 0]) + + if __name__ == "__main__": import sys From 5070232560f8a1087ab0f626178c9d24a3b18594 Mon Sep 17 00:00:00 2001 From: Mark Vaz Date: Wed, 25 Sep 2024 16:15:54 -0700 Subject: [PATCH 315/462] Updated legate pinned hash (#402) --- cmake/versions.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmake/versions.json b/cmake/versions.json index 0553e780c2..87c4760d6d 100644 --- a/cmake/versions.json +++ b/cmake/versions.json @@ -9,7 +9,7 @@ "git_url" : "git@github.com:nv-legate/legate.core.internal.git", "git_shallow": false, "always_download": false, - "git_tag" : "8fc11d96ffa3c6dfafe7435e984a433703c8d409" + "git_tag" : "426b1b0480b020cf7e1366e27be1f0284d2b8af3" } } } From a0df1ef0bbe47bf21be07717b45834c8b5870e8c Mon Sep 17 00:00:00 2001 From: Irina Demeshko Date: Thu, 26 Sep 2024 10:39:57 -0700 Subject: [PATCH 316/462] implementing numpy.unravel_index (#398) * implementing numpy.unravel_index --- cunumeric/_module/indexing.py | 74 ++++++++++- docs/cunumeric/source/api/indexing.rst | 1 + tests/integration/test_unravel_index.py | 166 ++++++++++++++++++++++++ 3 files changed, 240 insertions(+), 1 deletion(-) create mode 100644 tests/integration/test_unravel_index.py diff --git a/cunumeric/_module/indexing.py b/cunumeric/_module/indexing.py index 8d98d4e4ed..30f4c1633b 100644 --- a/cunumeric/_module/indexing.py +++ b/cunumeric/_module/indexing.py @@ -25,6 +25,7 @@ convert_to_cunumeric_ndarray, ) from .._utils import is_np2 +from .._utils.array import calculate_volume from .._utils.coverage import is_implemented from ..runtime import runtime from ..types import NdShape @@ -48,7 +49,7 @@ import numpy.typing as npt - from ..types import BoundsMode + from ..types import BoundsMode, OrderType _builtin_min = min @@ -244,6 +245,77 @@ def mask_indices( return mask_func(a, k).nonzero() +@add_boilerplate("indices") +def unravel_index( + indices: ndarray, + shape: NdShape, + order: OrderType = "C", +) -> tuple[ndarray, ...] | ndarray: + """ + Converts a flat index or array of flat indices into a tuple + of coordinate arrays. + + Parameters + ---------- + indices : array_like + An integer array whose elements are indices into the flattened + version of an array of dimensions ``shape``. + shape : tuple of ints + The shape of the array to use for unraveling ``indices``. + + order : {'C', 'F'}, optional + Determines whether the indices should be viewed as indexing in + row-major (C-style) or column-major (Fortran-style) order. + + Returns + ------- + unraveled_coords : tuple of ndarray + Each array in the tuple has the same shape as the ``indices`` + array. + + See Also + -------- + numpy.unravel_index + + Availability + -------- + Multiple GPUs, Multiple CPUs + """ + + if order not in ( + "F", + "C", + ): + raise ValueError("order is not understood") + + if indices is None or not np.can_cast( + indices.dtype, np.int64, "same_kind" + ): + raise TypeError("only int indices permitted") + + size = calculate_volume(shape) + + if (indices < 0).any() or (indices > size).any(): + raise ValueError("indices have out-of-bounds value(s)") + + if indices.size == 0: + unraveled_coords = tuple( + empty(indices.shape, dtype=indices.dtype) + for dim in range(len(shape)) + ) + return unraveled_coords + + unraveled_coords = tuple() + for dim in shape[::-1] if order == "C" else shape: + unraveled_coords = ( + (indices % dim,) + unraveled_coords + if order == "C" + else unraveled_coords + (indices % dim,) + ) + indices = indices // dim + return unraveled_coords + + def diag_indices(n: int, ndim: int = 2) -> tuple[ndarray, ...]: """ Return the indices to access the main diagonal of an array. diff --git a/docs/cunumeric/source/api/indexing.rst b/docs/cunumeric/source/api/indexing.rst index 91f393b5e8..2723a2d317 100644 --- a/docs/cunumeric/source/api/indexing.rst +++ b/docs/cunumeric/source/api/indexing.rst @@ -9,6 +9,7 @@ Generating index arrays .. autosummary:: :toctree: generated/ + unravel_index diag_indices diag_indices_from mask_indices diff --git a/tests/integration/test_unravel_index.py b/tests/integration/test_unravel_index.py new file mode 100644 index 0000000000..a812746759 --- /dev/null +++ b/tests/integration/test_unravel_index.py @@ -0,0 +1,166 @@ +# Copyright 2024 NVIDIA Corporation +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +import numpy as np +import pytest +from legate import LEGATE_MAX_DIM +from utils.generators import mk_seq_array + +import cunumeric as num + + +class TestUnravelIndexErrors: + def test_none_array(self): + expected_exc = TypeError + with pytest.raises(expected_exc): + np.unravel_index(None, (3,)) + with pytest.raises(expected_exc): + num.unravel_index(None, (3,)) + + def test_indices_wrong_type(self): + indices = [ + 1.0, + 2.0, + ] + shape = ( + 3, + 3, + ) + expected_exc = TypeError + with pytest.raises(expected_exc): + num.unravel_index(indices, shape) + + def test_invalid_shape(self): + # Test invalid shape specification (non-integer shape) + index = 5 + shape = (3, "3") + expected_exc = TypeError + with pytest.raises(expected_exc): + num.unravel_index(index, shape) + + def test_invalid_index(self): + # Test an invalid index value (negative index) + index = -1 + shape = (3, 3) + expected_exc = ValueError + with pytest.raises(expected_exc): + num.unravel_index(index, shape) + + def test_flat_index_out_of_bounds(self): + # Test an index out of bounds + index = 10 + shape = (2, 2) + expected_exc = ValueError + with pytest.raises(expected_exc): + num.unravel_index(index, shape) + + def test_empty_shape(self): + index = 1 + shape = (0,) + expected_exc = ValueError + with pytest.raises(expected_exc): + num.unravel_index(index, shape) + + def test_empty_indices(self): + # Test an empty indices array + index = [] + shape = (3, 3) + expected_exc = TypeError + with pytest.raises(expected_exc): + num.unravel_index(index, shape) + + def test_wrong_order(self): + # Test an empty indices array + index = [1, 2] + shape = (3, 3) + expected_exc = ValueError + with pytest.raises(expected_exc): + num.unravel_index(index, shape, "K") + + +def test_empty_indices(): + # Test an empty indices array + np_arr = mk_seq_array(np, 0) + num_arr = mk_seq_array(num, 0) + shape = (3, 3) + res_num = num.unravel_index(num_arr, shape) + res_np = np.unravel_index(np_arr, shape) + assert np.array_equal(res_num, res_np) + + +def test_large_shape(): + # Test a large shape + index = 123 + shape = ( + 100, + 100, + 100, + ) + res_np = np.unravel_index(index, shape) + res_num = num.unravel_index(index, shape) + assert np.array_equal(res_num, res_np) + + +def test_large_index(): + # Test large index values + index = 1000000000 + shape = (1000000001,) + res_np = np.unravel_index(index, shape) + res_num = num.unravel_index(index, shape) + assert np.array_equal(res_num, res_np) + + +@pytest.mark.parametrize("ndim", range(1, LEGATE_MAX_DIM + 1)) +@pytest.mark.parametrize( + "order", + ( + "F", + "C", + ), +) +def test_basic(ndim, order): + shape = (6,) * ndim + size = (6**ndim) % 2 + np_arr = mk_seq_array(np, size) + num_arr = mk_seq_array(num, size) + + res_np = np.unravel_index(np_arr, shape, order) + res_num = num.unravel_index(num_arr, shape, order) + assert np.array_equal(res_num, res_np) + + +@pytest.mark.parametrize("ndim", range(1, LEGATE_MAX_DIM + 1)) +@pytest.mark.parametrize( + "order", + ( + "F", + "C", + ), +) +def test_uneven_shape(ndim, order): + shape = np.random.randint(1, 6, ndim, dtype=int) + size = ndim + np_arr = mk_seq_array(np, size) + num_arr = mk_seq_array(num, size) + + res_np = np.unravel_index(np_arr, shape, order) + res_num = num.unravel_index(num_arr, shape, order) + assert np.array_equal(res_num, res_np) + + +if __name__ == "__main__": + import sys + + sys.exit(pytest.main(sys.argv)) From 66249ed0cdbd6c1c2abc51eaf72494c4306135ad Mon Sep 17 00:00:00 2001 From: Mona Han <129724851+mona-nv@users.noreply.github.com> Date: Mon, 30 Sep 2024 10:37:14 +0800 Subject: [PATCH 317/462] Add tests for window API (#90) * Add tests for window API * Update the test code based on review comments * Update code * Refactor the test code --------- Signed-off-by: monah --- tests/cpp/integration/common_utils.h | 42 +++++- tests/cpp/integration/test_window.cc | 196 +++++++++++++++++++++++++++ 2 files changed, 233 insertions(+), 5 deletions(-) create mode 100644 tests/cpp/integration/test_window.cc diff --git a/tests/cpp/integration/common_utils.h b/tests/cpp/integration/common_utils.h index 34d8fa836a..e919c50c17 100644 --- a/tests/cpp/integration/common_utils.h +++ b/tests/cpp/integration/common_utils.h @@ -74,31 +74,63 @@ NDArray mk_array(std::vector const& values, std::vector shape = {}) } template -void check_array(NDArray a, std::vector values, std::vector shape = {}) +void check_and_wrap(NDArray& a, const std::vector& values, std::vector& shape) { if (shape.empty() && values.size() > 1) { shape.push_back(values.size()); } ASSERT_EQ(a.size(), values.size()); ASSERT_EQ(a.shape(), shape); - ASSERT_EQ(a.type().code(), legate::type_code_of_v); - if (a.size() == 0) { - return; - } + ASSERT_EQ(a.type().code(), legate::type_code_of); + if (a.dim() > 1) { a = a._wrap(a.size()); } +} + +template +void check_array(NDArray a, const std::vector& values, std::vector shape = {}) +{ + check_and_wrap(a, values, shape); + if (a.size() == 0) { + return; + } + auto err_msg = [](auto i) { std::stringstream ss; ss << "check_array failed at [i = " << i << "]"; return ss.str(); }; + auto acc = a.get_read_accessor(); for (size_t i = 0; i < values.size(); ++i) { ASSERT_EQ(acc[i], values[i]) << err_msg(i); } } +template +void check_array_near(NDArray a, + const std::vector& values, + std::vector shape = {}, + double abs_error = 1.e-8) +{ + check_and_wrap(a, values, shape); + if (a.size() == 0) { + return; + } + + auto err_msg = [](auto i) { + std::stringstream ss; + ss << "check_array_near failed at [i = " << i << "]"; + return ss.str(); + }; + + auto acc = a.get_read_accessor(); + for (size_t i = 0; i < values.size(); ++i) { + EXPECT_NEAR(acc[i], values[i], abs_error) << err_msg(i); + } +} + template struct PrintArray { template diff --git a/tests/cpp/integration/test_window.cc b/tests/cpp/integration/test_window.cc new file mode 100644 index 0000000000..f25cdf81bf --- /dev/null +++ b/tests/cpp/integration/test_window.cc @@ -0,0 +1,196 @@ +/* Copyright 2023 NVIDIA Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +#include +#include "common_utils.h" + +using namespace cunumeric; + +namespace { + +struct windows_case { + int64_t input; + std::vector expected_values; + std::vector expected_shape; +}; + +struct kaiser_case { + int64_t input; + double beta_input; + std::vector expected_values; + std::vector expected_shape; +}; + +class NormalInput : public ::testing::Test, public ::testing::WithParamInterface {}; +class BartlettTest : public NormalInput {}; +class BlackmanTest : public NormalInput {}; +class HammingTest : public NormalInput {}; +class HanningTest : public NormalInput {}; +class KaiserTest : public ::testing::Test, public ::testing::WithParamInterface {}; + +INSTANTIATE_TEST_SUITE_P(WindowsTests, + BartlettTest, + ::testing::Values(windows_case{-1, {}, {0}}, + windows_case{0, {}, {0}}, + windows_case{1, {1}, {1}}, + windows_case{10, + {0.0, + 0.22222222, + 0.44444444, + 0.66666667, + 0.88888889, + 0.88888889, + 0.66666667, + 0.44444444, + 0.22222222, + 0.0}, + {10}})); + +INSTANTIATE_TEST_SUITE_P(WindowsTests, + BlackmanTest, + ::testing::Values(windows_case{-1, {}, {0}}, + windows_case{0, {}, {0}}, + windows_case{1, {1}, {1}}, + windows_case{10, + {-1.38777878e-17, + 5.08696327e-02, + 2.58000502e-01, + 6.30000000e-01, + 9.51129866e-01, + 9.51129866e-01, + 6.30000000e-01, + 2.58000502e-01, + 5.08696327e-02, + -1.38777878e-17}, + {10}})); + +INSTANTIATE_TEST_SUITE_P(WindowsTests, + HammingTest, + ::testing::Values(windows_case{-1, {}, {0}}, + windows_case{0, {}, {0}}, + windows_case{1, {1}, {1}}, + windows_case{10, + {0.08, + 0.18761956, + 0.46012184, + 0.77, + 0.97225861, + 0.97225861, + 0.77, + 0.46012184, + 0.18761956, + 0.08}, + {10}})); + +INSTANTIATE_TEST_SUITE_P(WindowsTests, + HanningTest, + ::testing::Values(windows_case{-1, {}, {0}}, + windows_case{0, {}, {0}}, + windows_case{1, {1}, {1}}, + windows_case{10, + {0.0, + 0.11697778, + 0.41317591, + 0.75, + 0.96984631, + 0.96984631, + 0.75, + 0.41317591, + 0.11697778, + 0.0}, + {10}})); + +INSTANTIATE_TEST_SUITE_P(WindowsTests, + KaiserTest, + ::testing::Values(kaiser_case{-1, -1.0, {}, {0}}, + kaiser_case{-1, 0, {}, {0}}, + kaiser_case{-1, 5, {}, {0}}, + kaiser_case{0, -1.0, {}, {0}}, + kaiser_case{0, 0, {}, {0}}, + kaiser_case{0, 5, {}, {0}}, + kaiser_case{1, -1.0, {1}, {1}}, + kaiser_case{1, 0, {1}, {1}}, + kaiser_case{1, 5, {1}, {1}}, + kaiser_case{10, + -1.0, + {0.78984831, + 0.86980546, + 0.93237871, + 0.97536552, + 0.99724655, + 0.99724655, + 0.97536552, + 0.93237871, + 0.86980546, + 0.78984831}, + {10}}, + kaiser_case{ + 10, 0, {1., 1., 1., 1., 1., 1., 1., 1., 1., 1.}, {10}}, + kaiser_case{10, + 5, + {0.03671089, + 0.20127873, + 0.47552746, + 0.7753221, + 0.97273069, + 0.97273069, + 0.7753221, + 0.47552746, + 0.20127873, + 0.03671089}, + {10}})); + +TEST_P(BartlettTest, Basic) +{ + auto& [input, expected_values, expected_shape] = GetParam(); + + auto result = cunumeric::bartlett(input); + check_array_near(result, expected_values, expected_shape); +} + +TEST_P(BlackmanTest, Basic) +{ + auto& [input, expected_values, expected_shape] = GetParam(); + + auto result = cunumeric::blackman(input); + check_array_near(result, expected_values, expected_shape); +} + +TEST_P(HammingTest, Basic) +{ + auto& [input, expected_values, expected_shape] = GetParam(); + + auto result = cunumeric::hamming(input); + check_array_near(result, expected_values, expected_shape); +} + +TEST_P(HanningTest, Basic) +{ + auto& [input, expected_values, expected_shape] = GetParam(); + + auto result = cunumeric::hanning(input); + check_array_near(result, expected_values, expected_shape); +} + +TEST_P(KaiserTest, Basic) +{ + auto& [input, beta_input, expected_values, expected_shape] = GetParam(); + + auto result = cunumeric::kaiser(input, beta_input); + check_array_near(result, expected_values, expected_shape); +} + +} // namespace \ No newline at end of file From f826fd0ba0e65ad8a85da0470a49ca4eec5857b1 Mon Sep 17 00:00:00 2001 From: Irina Demeshko Date: Mon, 30 Sep 2024 13:15:21 -0700 Subject: [PATCH 318/462] numpy.angle (#394) implementing numpy.angle --- cunumeric/_array/thunk.py | 11 +-- cunumeric/_module/math_complex.py | 45 ++++++++++++ cunumeric/_thunk/eager.py | 2 + cunumeric/config.py | 2 + docs/cunumeric/source/api/math.rst | 1 + src/cunumeric/cunumeric_c.h | 1 + src/cunumeric/ndarray.cc | 8 ++- src/cunumeric/ndarray.h | 2 +- src/cunumeric/operators.cc | 13 +++- src/cunumeric/unary/unary_op_util.h | 30 ++++++++ tests/integration/test_angle.py | 106 ++++++++++++++++++++++++++++ tests/unit/cunumeric/test_config.py | 1 + 12 files changed, 214 insertions(+), 8 deletions(-) create mode 100644 tests/integration/test_angle.py diff --git a/cunumeric/_array/thunk.py b/cunumeric/_array/thunk.py index 0bf13e3d7f..6247042003 100644 --- a/cunumeric/_array/thunk.py +++ b/cunumeric/_array/thunk.py @@ -80,11 +80,13 @@ def perform_unary_op( # No output yet, so make one out_shape = src.shape - if op == UnaryOpCode.ROUND or src.dtype.kind != "c": + if op == UnaryOpCode.ANGLE: + dtype = np.dtype(np.float64) + elif op == UnaryOpCode.ROUND or src.dtype.kind != "c": dtype = src.dtype else: if src.dtype == np.dtype(np.complex64): - dtype = np.dtype(np.float32) + dtype = np.dtype(np.float32) # type: ignore else: dtype = np.dtype(np.float64) @@ -95,14 +97,15 @@ def perform_unary_op( ) if out.dtype != src.dtype: - if op == UnaryOpCode.ABSOLUTE and src.dtype.kind == "c": + if ( + op == UnaryOpCode.ABSOLUTE and src.dtype.kind == "c" + ) or op == UnaryOpCode.ANGLE: out._thunk.unary_op( op, src._thunk, True, extra_args, ) - else: temp = ndarray( out.shape, diff --git a/cunumeric/_module/math_complex.py b/cunumeric/_module/math_complex.py index 44c4fa5781..a1a4590b15 100644 --- a/cunumeric/_module/math_complex.py +++ b/cunumeric/_module/math_complex.py @@ -16,7 +16,13 @@ from typing import TYPE_CHECKING +from legate import Scalar + +from .._array.thunk import perform_unary_op from .._array.util import add_boilerplate +from ..config import UnaryOpCode + +from .._utils.array import to_core_type if TYPE_CHECKING: from .._array.array import ndarray @@ -77,3 +83,42 @@ def imag(val: ndarray) -> ndarray: Multiple GPUs, Multiple CPUs """ return val.imag + + +@add_boilerplate("z") +def angle(z: ndarray, deg: bool = False) -> ndarray: + """ + Return the angle of the complex argument. + + Parameters + ---------- + z : array_like + A complex number or sequence of complex numbers. + deg : bool, optional + Return angle in degrees if True, radians if False (default). + + Returns + ------- + angle : ndarray or scalar + The counterclockwise angle from the positive real axis on the complex + plane in the range ``(-pi, pi]``, with dtype as numpy.float64. + + See Also + -------- + numpy.angle + + Notes + ----- + This function passes the imaginary and real parts of the argument to + `arctan2` to compute the result; consequently, it follows the convention + of `arctan2` when the magnitude of the argument is zero. + + Availability + -------- + Multiple GPUs, Multiple CPUs + """ + + if z is None: + raise TypeError ("can't compute 'angle' for None") + extra_args = (Scalar(deg),) + return perform_unary_op(UnaryOpCode.ANGLE, z, extra_args=extra_args) diff --git a/cunumeric/_thunk/eager.py b/cunumeric/_thunk/eager.py index 7d7e3dff21..1ce2449671 100644 --- a/cunumeric/_thunk/eager.py +++ b/cunumeric/_thunk/eager.py @@ -1484,6 +1484,8 @@ def unary_op( out=self.array, decimals=args[0].value(), ) + elif op == UnaryOpCode.ANGLE: + self.array = np.angle(rhs.array, args[0].value()) else: raise RuntimeError("unsupported unary op " + str(op)) diff --git a/cunumeric/config.py b/cunumeric/config.py index c4d6b60f46..1d578b5897 100644 --- a/cunumeric/config.py +++ b/cunumeric/config.py @@ -221,6 +221,7 @@ class _CunumericSharedLib: CUNUMERIC_UNLOAD_CUDALIBS: int CUNUMERIC_UNPACKBITS: int CUNUMERIC_UOP_ABSOLUTE: int + CUNUMERIC_UOP_ANGLE: int CUNUMERIC_UOP_ARCCOS: int CUNUMERIC_UOP_ARCCOSH: int CUNUMERIC_UOP_ARCSIN: int @@ -429,6 +430,7 @@ class CuNumericOpCode(IntEnum): @unique class UnaryOpCode(IntEnum): ABSOLUTE = _cunumeric.CUNUMERIC_UOP_ABSOLUTE + ANGLE = _cunumeric.CUNUMERIC_UOP_ANGLE ARCCOS = _cunumeric.CUNUMERIC_UOP_ARCCOS ARCCOSH = _cunumeric.CUNUMERIC_UOP_ARCCOSH ARCSIN = _cunumeric.CUNUMERIC_UOP_ARCSIN diff --git a/docs/cunumeric/source/api/math.rst b/docs/cunumeric/source/api/math.rst index 142b60203b..ef40212852 100644 --- a/docs/cunumeric/source/api/math.rst +++ b/docs/cunumeric/source/api/math.rst @@ -139,6 +139,7 @@ Handling complex numbers real imag + angle conj conjugate diff --git a/src/cunumeric/cunumeric_c.h b/src/cunumeric/cunumeric_c.h index e7bdbca8d1..33d401cbfb 100644 --- a/src/cunumeric/cunumeric_c.h +++ b/src/cunumeric/cunumeric_c.h @@ -84,6 +84,7 @@ enum CuNumericOpCode { // Also, sort these alphabetically for easy lookup later enum CuNumericUnaryOpCode { CUNUMERIC_UOP_ABSOLUTE = 1, + CUNUMERIC_UOP_ANGLE, CUNUMERIC_UOP_ARCCOS, CUNUMERIC_UOP_ARCCOSH, CUNUMERIC_UOP_ARCSIN, diff --git a/src/cunumeric/ndarray.cc b/src/cunumeric/ndarray.cc index 01a65f6b6f..3aa4767b7f 100644 --- a/src/cunumeric/ndarray.cc +++ b/src/cunumeric/ndarray.cc @@ -508,7 +508,9 @@ void NDArray::binary_reduction(int32_t op_code, NDArray rhs1, NDArray rhs2) runtime->submit(std::move(task)); } -void NDArray::unary_op(int32_t op_code, NDArray input) +void NDArray::unary_op(int32_t op_code, + NDArray input, + const std::vector& extra_args /*= {}*/) { if (size() == 0) { return; @@ -524,6 +526,10 @@ void NDArray::unary_op(int32_t op_code, NDArray input) auto p_in = task.add_input(rhs); task.add_scalar_arg(legate::Scalar(op_code)); + for (auto&& arg : extra_args) { + task.add_scalar_arg(arg); + } + task.add_constraint(align(p_out, p_in)); runtime->submit(std::move(task)); diff --git a/src/cunumeric/ndarray.h b/src/cunumeric/ndarray.h index 02a423fa50..ae49189d46 100644 --- a/src/cunumeric/ndarray.h +++ b/src/cunumeric/ndarray.h @@ -71,7 +71,7 @@ class NDArray { void fill(const Scalar& value); void binary_op(int32_t op_code, NDArray rhs1, NDArray rhs2); void binary_reduction(int32_t op_code, NDArray rhs1, NDArray rhs2); - void unary_op(int32_t op_code, NDArray input); + void unary_op(int32_t op_code, NDArray input, const std::vector& extra_args = {}); void unary_reduction(int32_t op_code, NDArray input); void eye(int32_t k); void trilu(NDArray rhs, int32_t k, bool lower); diff --git a/src/cunumeric/operators.cc b/src/cunumeric/operators.cc index 6871bd8ac4..6168f71ac2 100644 --- a/src/cunumeric/operators.cc +++ b/src/cunumeric/operators.cc @@ -34,11 +34,13 @@ NDArray array(std::vector shape, const legate::Type& type) return CuNumericRuntime::get_runtime()->create_array(std::move(shape), type); } -NDArray unary_op(UnaryOpCode op_code, NDArray input) +NDArray unary_op(UnaryOpCode op_code, + NDArray input, + const std::vector& extra_args = {}) { auto runtime = CuNumericRuntime::get_runtime(); auto out = runtime->create_array(input.shape(), input.type()); - out.unary_op(static_cast(op_code), std::move(input)); + out.unary_op(static_cast(op_code), std::move(input), extra_args); return out; } @@ -68,6 +70,13 @@ NDArray add(NDArray rhs1, NDArray rhs2, std::optional out) return binary_op(BinaryOpCode::ADD, std::move(rhs1), std::move(rhs2), std::move(out)); } +NDArray angle(NDArray input, bool deg) +{ + const std::vector extra_args = {Scalar(deg)}; + + return unary_op(UnaryOpCode::ANGLE, std::move(input), extra_args); +} + NDArray multiply(NDArray rhs1, NDArray rhs2, std::optional out) { return binary_op(BinaryOpCode::MULTIPLY, std::move(rhs1), std::move(rhs2), std::move(out)); diff --git a/src/cunumeric/unary/unary_op_util.h b/src/cunumeric/unary/unary_op_util.h index f4f5dbe3c6..37c0f1419f 100644 --- a/src/cunumeric/unary/unary_op_util.h +++ b/src/cunumeric/unary/unary_op_util.h @@ -33,6 +33,7 @@ namespace cunumeric { enum class UnaryOpCode : int { ABSOLUTE = CUNUMERIC_UOP_ABSOLUTE, + ANGLE = CUNUMERIC_UOP_ANGLE, ARCCOS = CUNUMERIC_UOP_ARCCOS, ARCCOSH = CUNUMERIC_UOP_ARCCOSH, ARCSIN = CUNUMERIC_UOP_ARCSIN, @@ -88,6 +89,8 @@ constexpr decltype(auto) op_dispatch(UnaryOpCode op_code, Functor f, Fnargs&&... switch (op_code) { case UnaryOpCode::ABSOLUTE: return f.template operator()(std::forward(args)...); + case UnaryOpCode::ANGLE: + return f.template operator()(std::forward(args)...); case UnaryOpCode::ARCCOS: return f.template operator()(std::forward(args)...); case UnaryOpCode::ARCCOSH: @@ -242,6 +245,33 @@ struct UnaryOp { } }; +template +struct UnaryOp { + using T = legate::type_of_t; + static constexpr bool valid = true; + + UnaryOp(const std::vector& args) : deg{args.size() == 1 && args[0].value()} + { + assert(args.size() == 1); + } + + template ::value>* = nullptr> + constexpr decltype(auto) operator()(const T& x) const + { + double res = atan2(x.imag(), x.real()); + return deg ? res * 180.0 / M_PI : res; + } + + template ::value>* = nullptr> + constexpr decltype(auto) operator()(const T& x) const + { + double res = atan2(0.0, static_cast(x)); + return res >= 0 ? 0.0 : (deg ? 180.0 : M_PI); + } + + bool deg; +}; + template struct UnaryOp { static constexpr bool valid = is_floating_or_complex; diff --git a/tests/integration/test_angle.py b/tests/integration/test_angle.py new file mode 100644 index 0000000000..2ec56c386d --- /dev/null +++ b/tests/integration/test_angle.py @@ -0,0 +1,106 @@ +# Copyright 2024 NVIDIA Corporation +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +import numpy as np +import pytest +from legate import LEGATE_MAX_DIM +from utils.generators import mk_seq_array + +import cunumeric as num + + +class TestAngleErrors: + def test_none_array(self): + expected_exc = AttributeError + msg="'int' object has no attribute 'arctan2'" + with pytest.raises(expected_exc, match=msg): + np.angle(None) + expected_exc = TypeError + msg="can't compute 'angle' for None" + with pytest.raises(expected_exc, match=msg): + num.angle(None) + +class TestAngle: + def test_empty_array(self): + res_np = np.angle([]) + res_num = num.angle([]) + assert np.array_equal(res_np, res_num) + + + def test_zero_input(self): + res_np = np.angle(0) + res_num = num.angle(0) + assert np.array_equal(res_np, res_num) + + + def test_pure_real_and_imaginary(self): + # Testing pure real and pure imaginary numbers + assert np.array_equal(num.angle(5), np.angle(5)) + assert np.array_equal(num.angle(-5), np.angle(-5)) + assert np.array_equal(num.angle(5j), np.angle(5j)) + assert np.array_equal(num.angle(-5j), np.angle(-5j)) + + + @pytest.mark.parametrize("ndim", range(1, LEGATE_MAX_DIM + 1)) + @pytest.mark.parametrize("in_type", (int, float, complex)) + @pytest.mark.parametrize("deg", (False, True)) + def test_basic(self,ndim, in_type, deg): + shape = (5,) * ndim + np_arr = mk_seq_array(np, shape).astype(in_type) + num_arr = mk_seq_array(num, shape).astype(in_type) + + res_np = np.angle(np_arr, deg) + res_num = num.angle(num_arr, deg) + assert np.array_equal(res_num, res_np) + + + @pytest.mark.parametrize( + "array", + ( + [1 + 1j, -1 - 1j, 1 - 1j, -1 + 1j], + [[1 + 1j, -1 - 1j], [1 - 1j, -1 + 1j]], + [ + [[1 + 1j, -1 - 1j], [1 - 1j, -1 + 1j]], + [[1j, -1j], [1 + 1j, -1 - 1j]], + ], + ), + ) + @pytest.mark.parametrize("deg", (False, True)) + def test_complex_arrays(self, array, deg): + res_np = np.angle(array, deg) + res_num = num.angle(array, deg) + assert np.array_equal(res_num, res_np) + + + def test_edge_cases(self): + # Testing angles with large and small numbers + assert np.array_equal(np.angle(1e10 + 1e10j), num.angle(1e10 + 1e10j)) + assert np.array_equal(np.angle(1e-10 + 1e-10j), num.angle(1e-10 + 1e-10j)) + + def test_nan(self): + # Testing behavior with NaN and Inf values + assert np.array_equal( + np.angle(np.nan + 1j), num.angle(np.nan + 1j), equal_nan=True + ) + + def test_inf(self): + assert np.array_equal(np.angle(np.inf + 1j), num.angle(np.inf + 1j)) + assert np.array_equal(np.angle(1j + np.inf), num.angle(1j + np.inf)) + + +if __name__ == "__main__": + import sys + + sys.exit(pytest.main(sys.argv)) diff --git a/tests/unit/cunumeric/test_config.py b/tests/unit/cunumeric/test_config.py index 637768d990..e002040304 100644 --- a/tests/unit/cunumeric/test_config.py +++ b/tests/unit/cunumeric/test_config.py @@ -117,6 +117,7 @@ def test_CuNumericOpCode() -> None: def test_UnaryOpCode() -> None: assert (set(m.UnaryOpCode.__members__)) == { "ABSOLUTE", + "ANGLE", "ARCCOS", "ARCCOSH", "ARCSIN", From a11875be9a22f00f9ce8dfe0d908734398ca4618 Mon Sep 17 00:00:00 2001 From: Shijie Chen Date: Tue, 1 Oct 2024 11:46:40 +0800 Subject: [PATCH 319/462] Porting reshape function to C++ (#66) * Porting reshape function to C++ * added more tests * Refactor reshape and add comments explaining the algorithm * Refactor the implementation of reshape into a functional programming style --- src/cunumeric/ndarray.cc | 122 ++++++++++++++ src/cunumeric/ndarray.h | 3 + src/cunumeric/operators.cc | 7 + src/cunumeric/operators.h | 32 ++++ tests/cpp/integration/test_reshape.cc | 232 ++++++++++++++++++++++++++ 5 files changed, 396 insertions(+) create mode 100644 tests/cpp/integration/test_reshape.cc diff --git a/src/cunumeric/ndarray.cc b/src/cunumeric/ndarray.cc index 3aa4767b7f..751684810e 100644 --- a/src/cunumeric/ndarray.cc +++ b/src/cunumeric/ndarray.cc @@ -1643,6 +1643,128 @@ NDArray NDArray::trace(int32_t offset, return diag_helper(offset, {axis1, axis2}, true, true, type, out); } +NDArray NDArray::ravel(std::string order) { return reshape({-1}, order); } + +NDArray NDArray::reshape(std::vector newshape, std::string order) +{ + if (order == "A") { + order = "C"; + } + if (order == "F") { + throw std::invalid_argument( + "cuNumeric has not implemented reshape using Fortran-like index order."); + } + if (order != "C") { + throw std::invalid_argument("order must be one of 'C', 'F', 'A'"); + } + return reshape(newshape); +} + +NDArray NDArray::reshape(std::vector newshape) +{ + auto runtime = cunumeric::CuNumericRuntime::get_runtime(); + int num_unknowns = std::count_if(newshape.begin(), newshape.end(), [](auto x) { return x < 0; }); + if (num_unknowns > 1) { + throw std::invalid_argument("can only specify one unknown dimension"); + } + + // case 1: zero size + if (size() == 0) { + if (1 == num_unknowns) { + std::replace_if(newshape.begin(), newshape.end(), [](auto x) { return x < 0; }, 0); + } + auto out_size = vec_prod(newshape); + if (out_size != 0) { + throw std::invalid_argument("new shape is not the same size as the original"); + } + return runtime->create_array(vec_convert(newshape), type()); + } + + int64_t known_volume = 1; + for (auto x : newshape) { + if (x >= 0) { + known_volume *= x; + } + } + if (num_unknowns > 0 && 0 == known_volume) { + throw std::invalid_argument("cannot reshape, size mismatch"); + } + int64_t unknown_extent = (0 == num_unknowns) ? 1 : size() / known_volume; + if (unknown_extent * known_volume != size()) { + throw std::invalid_argument("cannot reshape, size mismatch"); + } + std::replace_if(newshape.begin(), newshape.end(), [](auto x) { return x < 0; }, unknown_extent); + + auto in_shape = shape(); + auto out_shape = vec_convert(newshape); + + // case 2: same shape + if (vec_is_equal(in_shape, out_shape)) { + return *this; + } + + bool need_copy = false; + auto out_iter = out_shape.rbegin(); + std::for_each( + in_shape.rbegin(), in_shape.rend(), [&out_shape, &out_iter, &need_copy](size_t elem_in) { + size_t prod = 1; + for (; prod < elem_in && out_iter != out_shape.rend(); ++out_iter) { + prod *= *out_iter; + } + if (prod != elem_in) { + need_copy = true; + } + }); + + // case 3: need copy + if (need_copy) { + auto flat_arr = array({size()}, type()); + auto in_shape_store = flat_arr.get_store().delinearize(0, in_shape); + NDArray in_shape_arr(std::move(in_shape_store)); + in_shape_arr.assign(*this); + auto out_shape_store = flat_arr.get_store().delinearize(0, out_shape); + NDArray out_shape_arr(std::move(out_shape_store)); + return out_shape_arr; + } + + // case 4: No need to copy, provides a view to the input store + out_iter = out_shape.rbegin(); + auto out_store = get_store(); + + std::for_each( + in_shape.rbegin(), + in_shape.rend(), + [&out_shape, &out_iter, &out_store, dim_in = int32_t(in_shape.size())](size_t elem_in) mutable { + --dim_in; + if (out_iter != out_shape.rend() && elem_in == *out_iter) { + ++out_iter; + // NOOP + return; + } + if (elem_in == 1) { + // "project" operation + out_store = out_store.project(dim_in, 0); + return; + } + // "delinearize" operation + std::vector new_sizes; + new_sizes.reserve(8); + for (size_t prod = 1; prod < elem_in && out_iter != out_shape.rend(); ++out_iter) { + prod *= *out_iter; + new_sizes.push_back(*out_iter); + } + std::reverse(new_sizes.begin(), new_sizes.end()); + out_store = out_store.delinearize(dim_in, new_sizes); + }); + + for (; out_iter != out_shape.rend(); ++out_iter) { + // "promote" operation + out_store = out_store.promote(0, 1); + } + + return NDArray(std::move(out_store)); +} + legate::LogicalStore NDArray::get_store() { return store_; } legate::LogicalStore NDArray::broadcast(const std::vector& shape, diff --git a/src/cunumeric/ndarray.h b/src/cunumeric/ndarray.h index ae49189d46..1bc32dceea 100644 --- a/src/cunumeric/ndarray.h +++ b/src/cunumeric/ndarray.h @@ -108,6 +108,9 @@ class NDArray { std::optional out = std::nullopt); NDArray repeat(NDArray repeats, std::optional axis = std::nullopt); NDArray repeat(int64_t repeats, std::optional axis = std::nullopt); + NDArray reshape(std::vector newshape, std::string order); + NDArray reshape(std::vector newshape); + NDArray ravel(std::string order = "C"); public: NDArray as_type(const legate::Type& type); diff --git a/src/cunumeric/operators.cc b/src/cunumeric/operators.cc index 6168f71ac2..997f8f7731 100644 --- a/src/cunumeric/operators.cc +++ b/src/cunumeric/operators.cc @@ -533,4 +533,11 @@ NDArray repeat(NDArray a, int64_t repeats, std::optional axis) return a.repeat(repeats, axis); } +NDArray reshape(NDArray a, std::vector newshape, std::string order) +{ + return a.reshape(newshape, order); +} + +NDArray ravel(NDArray a, std::string order) { return a.ravel(order); } + } // namespace cunumeric diff --git a/src/cunumeric/operators.h b/src/cunumeric/operators.h index 99f16935e0..4b1c96ed8b 100644 --- a/src/cunumeric/operators.h +++ b/src/cunumeric/operators.h @@ -142,5 +142,37 @@ NDArray trace(NDArray a, int32_t axis2 = 1, std::optional type = std::nullopt, std::optional out = std::nullopt); + +NDArray reshape(NDArray a, std::vector newshape, std::string order = "C"); + +NDArray ravel(NDArray a, std::string order = "C"); + +template +bool vec_is_equal(const std::vector& vec1, const std::vector& vec2) +{ + return vec1.size() == vec2.size() && std::equal(vec1.begin(), vec1.end(), vec2.begin()); +} + +template +T vec_prod(const std::vector& vec) +{ + T result = 1; + for (const auto& elem : vec) { + result *= elem; + } + return result; +} + +template +std::vector vec_convert(const std::vector& input) +{ + std::vector output; + output.reserve(input.size()); + for (const auto& elem : input) { + output.push_back(static_cast(elem)); + } + return output; +} + } // namespace cunumeric #include "cunumeric/operators.inl" diff --git a/tests/cpp/integration/test_reshape.cc b/tests/cpp/integration/test_reshape.cc new file mode 100644 index 0000000000..6a254bbe00 --- /dev/null +++ b/tests/cpp/integration/test_reshape.cc @@ -0,0 +1,232 @@ +/* Copyright 2024 NVIDIA Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +#include "common_utils.h" + +using namespace cunumeric; + +namespace { + +std::vector> SQUARE_CASES{ + {10, 5, 2}, + // (-1, 5, 2), + {5, 2, 10}, + {5, 2, 5, 2}, + {10, 10, 1}, + {10, 1, 10}, + {1, 10, 10}, +}; + +class Reshape_TestSquare : public ::testing::Test { + public: + std::vector a_gt = mk_seq_vector({100}, 1, -1); +}; + +TEST_F(Reshape_TestSquare, test_basic) +{ + auto a = arange(100).reshape({10, -1}); + check_array(a, a_gt, {10, 10}); +} + +TEST_F(Reshape_TestSquare, test_shape) +{ + for (auto shape : SQUARE_CASES) { + auto a = arange(100).reshape({10, 10}); + check_array(reshape(a, shape), a_gt, as_type_vector(shape)); + } + { + auto a = arange(100).reshape({10, 10}); + check_array(reshape(a, {-1, 5, 2}), a_gt, {10, 5, 2}); + } +} + +TEST_F(Reshape_TestSquare, test_shape_mode) +{ + for (std::string order : {"C", "F", "A"}) { + for (auto shape : SQUARE_CASES) { + auto a = arange(100).reshape({10, 10}); + if (order == "F") { + EXPECT_THROW(reshape(a, shape, order), std::invalid_argument); + } else { + check_array(reshape(a, shape, order), a_gt, as_type_vector(shape)); + } + } + { + auto a = arange(100).reshape({10, 10}); + if (order == "F") { + EXPECT_THROW(reshape(a, {-1, 5, 2}, order), std::invalid_argument); + } else { + check_array(reshape(a, {-1, 5, 2}, order), a_gt, {10, 5, 2}); + } + } + } +} + +TEST_F(Reshape_TestSquare, test_1d) +{ + auto a = arange(100).reshape({10, 10}); + check_array(reshape(a, {100}), a_gt, {100}); +} + +TEST_F(Reshape_TestSquare, test_ravel) +{ + auto a = arange(100).reshape({10, 10}); + check_array(ravel(a), a_gt, {100}); +} + +TEST_F(Reshape_TestSquare, test_ravel_empty_array) +{ + auto a = full({3, 0}, Scalar(int64_t(1))); + check_array(ravel(a), {}, {0}); + + a = full({0, 3}, Scalar(int64_t(1))); + check_array(ravel(a), {}, {0}); +} + +std::vector> RECT_CASES = { + {10, 2, 10}, + {20, 10}, + // {20, -5}, + {5, 40}, + {200, 1}, + {1, 200}, + {10, 20}, +}; + +class Reshape_TestRect : public ::testing::Test { + public: + std::vector a_gt = mk_seq_vector({200}); +}; + +TEST_F(Reshape_TestRect, test_shape) +{ + for (auto shape : RECT_CASES) { + auto a = mk_array(a_gt, {5, 4, 10}); + check_array(reshape(a, shape), a_gt, as_type_vector(shape)); + } + { + auto a = mk_array(a_gt, {5, 4, 10}); + check_array(reshape(a, {20, -5}), a_gt, {20, 10}); + } +} + +TEST_F(Reshape_TestRect, test_shape_mode) +{ + for (std::string order : {"C", "F", "A"}) { + for (auto shape : RECT_CASES) { + auto a = mk_array(a_gt, {5, 4, 10}); + if (order == "F") { + EXPECT_THROW(reshape(a, shape, order), std::invalid_argument); + } else { + check_array(reshape(a, shape, order), a_gt, as_type_vector(shape)); + } + } + { + auto a = mk_array(a_gt, {5, 4, 10}); + if (order == "F") { + EXPECT_THROW(reshape(a, {20, -5}, order), std::invalid_argument); + } else { + check_array(reshape(a, {20, -5}, order), a_gt, {20, 10}); + } + } + } +} + +TEST_F(Reshape_TestRect, test_ravel) +{ + for (std::string order : {"C", "F", "A"}) { + auto a = mk_array(a_gt, {5, 4, 10}); + if (order == "F") { + EXPECT_THROW(ravel(a, order), std::invalid_argument); + } else { + check_array(ravel(a, order), a_gt, {200}); + } + } +} + +TEST(Reshape, test_reshape_empty_array) +{ + std::vector> shape_list{ + {0}, + {1, 0}, + {0, 1, 1}, + }; + auto a = mk_array({}, {0, 1}); + for (auto shape : shape_list) { + check_array(reshape(a, shape), {}, as_type_vector(shape)); + } +} + +TEST(Reshape, test_reshape_same_shape) +{ + std::vector shape{1, 2, 3}; + auto a_gt = mk_seq_vector(shape); + auto a = mk_array(a_gt, shape); + auto a_out = reshape(a, as_type_vector(shape)); + check_array(a_out, a_gt, shape); +} + +class Reshape_Errors : public ::testing::Test { + public: + NDArray a = mk_array(mk_seq_vector({24})); +}; + +TEST_F(Reshape_Errors, test_empty_array_shape_invalid_size) +{ + auto a = mk_array({}, {0, 1, 1}); + std::vector shape{1, 1}; + EXPECT_THROW(reshape(a, shape), std::invalid_argument); +} + +TEST_F(Reshape_Errors, test_shape_invalid_size) +{ + std::vector> shape_list{ + {-1, 0, 2}, + {4, 3, 4}, + {4, 3, 0}, + {4, 3}, + {4}, + {0}, + }; + for (auto shape : shape_list) { + EXPECT_THROW(reshape(a, shape), std::invalid_argument); + } +} + +TEST_F(Reshape_Errors, test_shape_unknown_dimensions) +{ + std::vector shape{-5, -1, 2}; + EXPECT_THROW(reshape(a, shape), std::invalid_argument); +} + +TEST_F(Reshape_Errors, test_invalid_order) +{ + EXPECT_THROW(reshape(a, {4, 3, 2}, "Z"), std::invalid_argument); +} + +TEST_F(Reshape_Errors, test_reshape_no_args) +{ + { + auto a = mk_array({1}, {1, 1, 1}); + check_array(reshape(a, {}), {1}); + } + { + auto a = mk_array({}, {0}); + EXPECT_THROW(reshape(a, {}), std::invalid_argument); + } +} + +} // namespace From 4fb9db949b6a64e86e979f8415770f1803760c5c Mon Sep 17 00:00:00 2001 From: Andrei Schaffer <37386037+aschaffer@users.noreply.github.com> Date: Tue, 1 Oct 2024 13:55:15 -0500 Subject: [PATCH 320/462] Fix and test for negative axes in quantiles. (#412) * Fix and test for negative axes in quantiles. * mypy fixes. * minor fix + RNG test debt. --- cunumeric/_module/stats_order.py | 28 +++-- tests/integration/test_negaxes_quantiles.py | 113 ++++++++++++++++++++ tests/integration/test_random.py | 11 +- 3 files changed, 136 insertions(+), 16 deletions(-) create mode 100644 tests/integration/test_negaxes_quantiles.py diff --git a/cunumeric/_module/stats_order.py b/cunumeric/_module/stats_order.py index 72ccb93c7f..1867217b9b 100644 --- a/cunumeric/_module/stats_order.py +++ b/cunumeric/_module/stats_order.py @@ -15,10 +15,19 @@ from __future__ import annotations import math -from typing import TYPE_CHECKING, Any, Iterable +from typing import TYPE_CHECKING, Any, Iterable, Sequence import numpy as np +from .._utils import is_np2 + +if is_np2: + from numpy.lib.array_utils import normalize_axis_tuple # type: ignore +else: + from numpy.core.numeric import ( # type: ignore + normalize_axis_tuple, + ) + from .._array.util import add_boilerplate from .._ufunc.comparison import logical_not from .._ufunc.floating import isnan @@ -48,7 +57,7 @@ # # return: pair: (minimal_index, reshuffled_and_collapsed source array) def _reshuffle_reshape( - arr: ndarray, axes_set: Iterable[int] + arr: ndarray, axes_set: Sequence[int] ) -> tuple[int, ndarray]: ndim = len(arr.shape) @@ -249,7 +258,7 @@ def _quantile_impl( arr: ndarray, q_arr: npt.NDArray[Any], axis: int | None, - axes_set: Iterable[int], + axes_set: Sequence[int], original_shape: tuple[int, ...], method: Callable[[float, int], tuple[float | None, int]], keepdims: bool, @@ -441,24 +450,27 @@ def quantile( """ real_axis: int | None - axes_set: Iterable[int] = [] + axes_set: Sequence[int] = () original_shape = a.shape if axis is not None and isinstance(axis, Iterable): + nrm_axis = normalize_axis_tuple(axis, a.ndim) if len(axis) == 1: - real_axis = axis[0] + real_axis = nrm_axis[0] a_rr = a else: - (real_axis, a_rr) = _reshuffle_reshape(a, axis) + # reshuffling requires non-negative axes: + (real_axis, a_rr) = _reshuffle_reshape(a, nrm_axis) # What happens with multiple axes and overwrite_input = True ? # It seems overwrite_input is reset to False; overwrite_input = False - axes_set = axis + axes_set = nrm_axis else: real_axis = axis a_rr = a if real_axis is not None: - axes_set = [real_axis] + axes_set = normalize_axis_tuple(real_axis, a.ndim) + real_axis = axes_set[0] # covers both array-like and scalar cases: # diff --git a/tests/integration/test_negaxes_quantiles.py b/tests/integration/test_negaxes_quantiles.py new file mode 100644 index 0000000000..721e2f95a0 --- /dev/null +++ b/tests/integration/test_negaxes_quantiles.py @@ -0,0 +1,113 @@ +# Copyright 2024 NVIDIA Corporation +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + + +import numpy as np +import pytest +from legate import LEGATE_MAX_DIM +from utils.comparisons import allclose + +import cunumeric as num + +ALL_METHODS = ( + "inverted_cdf", + "averaged_inverted_cdf", + "closest_observation", + "interpolated_inverted_cdf", + "hazen", + "weibull", + "linear", + "median_unbiased", + "normal_unbiased", + "lower", + "higher", + "midpoint", + "nearest", +) + + +@pytest.mark.parametrize("str_method", ALL_METHODS) +@pytest.mark.parametrize("axes", (-1, -2)) +def test_quantiles_negative_axes(str_method, axes): + keepdims = False + qs_arr = 0.5 + eps = 1.0e-8 + original_shape = (2, 3, 4) + arr = np.ndarray( + shape=original_shape, + buffer=np.array( + [ + 1, + 2, + 2, + 40, + 1, + 1, + 2, + 1, + 0, + 10, + 3, + 3, + 40, + 15, + 3, + 7, + 5, + 4, + 7, + 3, + 5, + 1, + 0, + 9, + ] + ), + dtype=float, + ) + + # cunumeric: + # print("cunumeric axis = %d:"%(axis)) + num_q_out = num.quantile( + arr, qs_arr, axis=axes, method=str_method, keepdims=keepdims + ) + # print(q_out) + + # np: + # print("numpy axis = %d:"%(axis)) + # due to numpy bug https://github.com/numpy/numpy/issues/22544 + # out = fails with keepdims = True + # + np_q_out = np.quantile( + arr, + qs_arr, + axis=axes, + method=str_method, + keepdims=keepdims, + ) + # print(np_q_out) + + assert num_q_out.shape == np_q_out.shape + assert num_q_out.dtype == np_q_out.dtype + + assert allclose(np_q_out, num_q_out, atol=eps) + + +if __name__ == "__main__": + import sys + + np.random.seed(12345) + + sys.exit(pytest.main(sys.argv)) diff --git a/tests/integration/test_random.py b/tests/integration/test_random.py index abbf281188..e238fee2ca 100644 --- a/tests/integration/test_random.py +++ b/tests/integration/test_random.py @@ -26,14 +26,9 @@ def test_basic_num() -> None: L2 = num.random.randn(3, 3) assert np.array_equal(L1, L2) - num.random.seed(10) - L1 = num.random.randn(3, 3) - L2 = num.random.randn(3, 3) - assert np.array_equal(L1, L2) - @pytest.mark.xfail( - reason = "numpy failures" + reason = "numpy failures in random.mtrand.RandomState.standard_normal" ) def test_basic_np() -> None: np.random.seed(10) @@ -65,7 +60,7 @@ def test_none_num() -> None: @pytest.mark.xfail( - reason = "numpy failures" + reason = "numpy failures in random.mtrand.RandomState.standard_normal" ) def test_none_np() -> None: np.random.seed() @@ -81,7 +76,7 @@ def test_none_np() -> None: @pytest.mark.xfail( - reason = "numpy failures" + reason = "numpy failures in random.mtrand.RandomState.standard_normal" ) def test_basic_num_np() -> None: np.random.seed(10) From 395ddfb542b936a8585eb0101a4accdf6ca62430 Mon Sep 17 00:00:00 2001 From: Marcin Zalewski Date: Wed, 2 Oct 2024 20:54:15 -0700 Subject: [PATCH 321/462] Update CI and change main branch CI concurrency (#420) --- .github/workflows/ci-gh-release.yml | 2 +- .github/workflows/gh-build-and-test.yml | 20 ++++++++++---------- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/.github/workflows/ci-gh-release.yml b/.github/workflows/ci-gh-release.yml index ef0ca408bd..654fad29ef 100644 --- a/.github/workflows/ci-gh-release.yml +++ b/.github/workflows/ci-gh-release.yml @@ -1,7 +1,7 @@ name: Build Release package concurrency: - group: ci-release-on-${{ github.event_name }}-from-${{ github.ref_name }} + group: ${{ startsWith(github.ref_name, 'main') && format('unique-{0}', github.run_id) || format('ci-build-and-test-on-{0}-from-{1}', github.event_name, github.ref_name) }} cancel-in-progress: true on: diff --git a/.github/workflows/gh-build-and-test.yml b/.github/workflows/gh-build-and-test.yml index 975eae822b..b9890641a0 100644 --- a/.github/workflows/gh-build-and-test.yml +++ b/.github/workflows/gh-build-and-test.yml @@ -51,14 +51,14 @@ jobs: needs: setup-build name: "Build (${{ inputs.platform }}, ${{ inputs.target-device }}, ${{ inputs.build-type }}, Python ${{ inputs.python-version }})" uses: - nv-legate/legate-gh-ci/.github/workflows/gh-build.yml@v1.15 + nv-legate/legate-gh-ci/.github/workflows/gh-build.yml@v1.17 with: build-mode: "" build-type: ${{ inputs.build-type }} client-repo: ${{ github.event.repository.name }} dependencies-file: "cmake/versions.json" dependencies-workflow: ${{ inputs.dependencies-workflow }} - legate-gh-ci-tag: "v1.15" + legate-gh-ci-tag: "v1.17" network: "ucx" platform: ${{ inputs.platform }} python-version: ${{ inputs.python-version }} @@ -72,13 +72,13 @@ jobs: needs: setup-build name: "Check if legate.internal nightly exists for SHA specified in versions.json (${{ inputs.platform }}, ${{ inputs.target-device }}, ${{ inputs.build-type }})" uses: - nv-legate/legate-gh-ci/.github/workflows/gh-check-if-nightly-exists-for-all-dependencies.yml@v1.15 + nv-legate/legate-gh-ci/.github/workflows/gh-check-if-nightly-exists-for-all-dependencies.yml@v1.17 with: build-mode: "" build-type: ${{ inputs.build-type }} client-repo: ${{ github.event.repository.name }} dependencies-file: "cmake/versions.json" - legate-gh-ci-tag: "v1.15" + legate-gh-ci-tag: "v1.17" network: "ucx" platform: ${{ inputs.platform }} python-version: ${{ inputs.python-version }} @@ -92,12 +92,12 @@ jobs: if: ${{ github.repository_owner == 'nv-legate' && contains(github.workflow, 'release') && inputs.upload-enabled == true }} name: Upload package to Server uses: - nv-legate/legate-gh-ci/.github/workflows/gh-upload.yml@v1.15 + nv-legate/legate-gh-ci/.github/workflows/gh-upload.yml@v1.17 with: build-mode: "" build-type: ${{ inputs.build-type }} client-repo: ${{ github.event.repository.name }} - legate-gh-ci-tag: "v1.15" + legate-gh-ci-tag: "v1.17" name: Upload package to Server network: "ucx" pkgSubString: "cunumeric-" @@ -180,13 +180,13 @@ jobs: matrix: ${{fromJson(needs.setup-test.outputs.matrix)}} uses: - nv-legate/legate-gh-ci/.github/workflows/gh-test-within-container.yml@v1.15 + nv-legate/legate-gh-ci/.github/workflows/gh-test-within-container.yml@v1.17 with: build-mode: "" build-type: ${{ inputs.build-type }} client-repo: ${{ github.event.repository.name }} has-gpu: ${{ matrix.runner.type == 'gpu' }} - legate-gh-ci-tag: "v1.15" + legate-gh-ci-tag: "v1.17" name: ${{ matrix.test-config.name }} network: "ucx" platform: ${{ inputs.platform }} @@ -202,12 +202,12 @@ jobs: name: Update Test status on Server if: ${{ (github.repository_owner == 'nv-legate') && contains(github.workflow, 'Nightly') && (inputs.upload-enabled == true) }} uses: - nv-legate/legate-gh-ci/.github/workflows/gh-upload.yml@v1.15 + nv-legate/legate-gh-ci/.github/workflows/gh-upload.yml@v1.17 with: build-mode: "" build-type: ${{ inputs.build-type }} client-repo: ${{ github.event.repository.name }} - legate-gh-ci-tag: "v1.15" + legate-gh-ci-tag: "v1.17" name: UpdateTestStatus network: "ucx" pkgSubString: "cunumeric-" From f6239566482a5532e345a3cbcf0772f5fb7bf461 Mon Sep 17 00:00:00 2001 From: Marcin Zalewski Date: Thu, 3 Oct 2024 10:14:42 -0700 Subject: [PATCH 322/462] Make curand a GPU only host dependency (#416) --- conda/conda-build/meta.yaml | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/conda/conda-build/meta.yaml b/conda/conda-build/meta.yaml index 3d54200d1a..9ca9968fe7 100644 --- a/conda/conda-build/meta.yaml +++ b/conda/conda-build/meta.yaml @@ -109,13 +109,11 @@ requirements: # cudart needed for CPU and GPU builds because of curand - cuda-cudart-dev - cuda-version ={{ cuda_version }} - + - libcurand-dev host: - python - scikit-build - # libcurand is used both in CPU and GPU builds - - libcurand-dev - openblas =* =*openmp* {% if gpu_enabled_bool %} - legate >={{ legate_version }} =*_gpu* @@ -124,25 +122,24 @@ requirements: - libcublas-dev - libcusolver-dev - libcufft-dev + - libcurand-dev + - cuda-version ={{ cuda_version }} {% else %} - legate >={{ legate_version }} =*_cpu* {% endif %} - - cuda-version ={{ cuda_version }} run: - numpy {{ numpy_version }} - - libnvjitlink - - libcusparse - opt_einsum >=3.3 - scipy - openblas =* =*openmp* {% if gpu_enabled_bool %} + - libnvjitlink + - libcusparse - cutensor >=2.0 =*_* -{% endif %} # Pin to all minor versions of CUDA newer than the one built against, within the same major version. # cuda-version constrains the CUDA runtime version and ensures a compatible driver is available - {{ pin_compatible('cuda-version', min_pin='x.x', max_pin='x') }} -{% if gpu_enabled_bool %} - __cuda >={{ cuda_version }} {% endif %} From 78bfd74e0fbd2c17b5e790a0df1d5d2ea0d89194 Mon Sep 17 00:00:00 2001 From: Wonchan Lee Date: Thu, 3 Oct 2024 13:27:06 -0700 Subject: [PATCH 323/462] A bag of fixes (#418) - Stop broadcasting the whole array in nonzero when the input is 1D - Use std::uint64_t instead of std::size_t for machines where the two are different (e.g., Mac) - Stop using CUDA keywords in the header that can be used without CUDA --- cunumeric/_thunk/deferred.py | 3 +- src/cunumeric/ndarray.cc | 65 +++++++++++++++-------------- src/cunumeric/stat/histogram_impl.h | 4 +- 3 files changed, 38 insertions(+), 34 deletions(-) diff --git a/cunumeric/_thunk/deferred.py b/cunumeric/_thunk/deferred.py index 0037a25eb2..fd80d4a17e 100644 --- a/cunumeric/_thunk/deferred.py +++ b/cunumeric/_thunk/deferred.py @@ -2120,7 +2120,8 @@ def nonzero(self) -> tuple[NumPyThunk, ...]: for result in results: task.add_output(result.base) - task.add_constraint(broadcast(p_self, range(1, self.ndim))) + if self.ndim > 1: + task.add_constraint(broadcast(p_self, range(1, self.ndim))) task.execute() return results diff --git a/src/cunumeric/ndarray.cc b/src/cunumeric/ndarray.cc index 751684810e..f43a9ef7a6 100644 --- a/src/cunumeric/ndarray.cc +++ b/src/cunumeric/ndarray.cc @@ -1671,13 +1671,14 @@ NDArray NDArray::reshape(std::vector newshape) // case 1: zero size if (size() == 0) { if (1 == num_unknowns) { - std::replace_if(newshape.begin(), newshape.end(), [](auto x) { return x < 0; }, 0); + std::replace_if( + newshape.begin(), newshape.end(), [](auto x) { return x < 0; }, 0); } auto out_size = vec_prod(newshape); if (out_size != 0) { throw std::invalid_argument("new shape is not the same size as the original"); } - return runtime->create_array(vec_convert(newshape), type()); + return runtime->create_array(vec_convert(newshape), type()); } int64_t known_volume = 1; @@ -1693,10 +1694,11 @@ NDArray NDArray::reshape(std::vector newshape) if (unknown_extent * known_volume != size()) { throw std::invalid_argument("cannot reshape, size mismatch"); } - std::replace_if(newshape.begin(), newshape.end(), [](auto x) { return x < 0; }, unknown_extent); + std::replace_if( + newshape.begin(), newshape.end(), [](auto x) { return x < 0; }, unknown_extent); auto in_shape = shape(); - auto out_shape = vec_convert(newshape); + auto out_shape = vec_convert(newshape); // case 2: same shape if (vec_is_equal(in_shape, out_shape)) { @@ -1706,8 +1708,8 @@ NDArray NDArray::reshape(std::vector newshape) bool need_copy = false; auto out_iter = out_shape.rbegin(); std::for_each( - in_shape.rbegin(), in_shape.rend(), [&out_shape, &out_iter, &need_copy](size_t elem_in) { - size_t prod = 1; + in_shape.rbegin(), in_shape.rend(), [&out_shape, &out_iter, &need_copy](uint64_t elem_in) { + uint64_t prod = 1; for (; prod < elem_in && out_iter != out_shape.rend(); ++out_iter) { prod *= *out_iter; } @@ -1731,31 +1733,32 @@ NDArray NDArray::reshape(std::vector newshape) out_iter = out_shape.rbegin(); auto out_store = get_store(); - std::for_each( - in_shape.rbegin(), - in_shape.rend(), - [&out_shape, &out_iter, &out_store, dim_in = int32_t(in_shape.size())](size_t elem_in) mutable { - --dim_in; - if (out_iter != out_shape.rend() && elem_in == *out_iter) { - ++out_iter; - // NOOP - return; - } - if (elem_in == 1) { - // "project" operation - out_store = out_store.project(dim_in, 0); - return; - } - // "delinearize" operation - std::vector new_sizes; - new_sizes.reserve(8); - for (size_t prod = 1; prod < elem_in && out_iter != out_shape.rend(); ++out_iter) { - prod *= *out_iter; - new_sizes.push_back(*out_iter); - } - std::reverse(new_sizes.begin(), new_sizes.end()); - out_store = out_store.delinearize(dim_in, new_sizes); - }); + std::for_each(in_shape.rbegin(), + in_shape.rend(), + [&out_shape, &out_iter, &out_store, dim_in = int32_t(in_shape.size())]( + uint64_t elem_in) mutable { + --dim_in; + if (out_iter != out_shape.rend() && elem_in == *out_iter) { + ++out_iter; + // NOOP + return; + } + if (elem_in == 1) { + // "project" operation + out_store = out_store.project(dim_in, 0); + return; + } + // "delinearize" operation + std::vector new_sizes; + new_sizes.reserve(8); + for (uint64_t prod = 1; prod < elem_in && out_iter != out_shape.rend(); + ++out_iter) { + prod *= *out_iter; + new_sizes.push_back(*out_iter); + } + std::reverse(new_sizes.begin(), new_sizes.end()); + out_store = out_store.delinearize(dim_in, new_sizes); + }); for (; out_iter != out_shape.rend(); ++out_iter) { // "promote" operation diff --git a/src/cunumeric/stat/histogram_impl.h b/src/cunumeric/stat/histogram_impl.h index ddb63eb965..47ece8e6f3 100644 --- a/src/cunumeric/stat/histogram_impl.h +++ b/src/cunumeric/stat/histogram_impl.h @@ -27,7 +27,7 @@ struct lower_bound_op_t { : p_bins_(p_bins), n_intervs_(n_intervs) // CTAD { } - __host__ __device__ bool operator()(elem_t left, bin_t right) const + __CUDA_HD__ bool operator()(elem_t left, bin_t right) const { // sentinel logic accounts for comparison // against last bin's upper bound, when @@ -67,7 +67,7 @@ struct lower_bound_op_t { template struct reduction_op_t { - __host__ __device__ weight_t operator()(weight_t local_value, weight_t global_value) + __CUDA_HD__ weight_t operator()(weight_t local_value, weight_t global_value) { return local_value + global_value; } From 4d42186fa83cb93a920eca4bea9b331effb6e780 Mon Sep 17 00:00:00 2001 From: Irina Demeshko Date: Thu, 3 Oct 2024 16:32:33 -0700 Subject: [PATCH 324/462] implementing np.median (#415) * implementing np.median * fixing error message in test * fixing error message in test --- cunumeric/_module/stats_avgs_vars.py | 145 ++++++++++++++ docs/cunumeric/source/api/statistics.rst | 2 + tests/integration/test_median.py | 241 +++++++++++++++++++++++ 3 files changed, 388 insertions(+) create mode 100644 tests/integration/test_median.py diff --git a/cunumeric/_module/stats_avgs_vars.py b/cunumeric/_module/stats_avgs_vars.py index 56810c29b8..c463e174fc 100644 --- a/cunumeric/_module/stats_avgs_vars.py +++ b/cunumeric/_module/stats_avgs_vars.py @@ -24,6 +24,7 @@ from .._utils import is_np2 from .creation_shape import full from .logic_truth import any +from .stats_order import nanquantile, quantile if is_np2: from numpy.lib.array_utils import normalize_axis_tuple # type: ignore @@ -371,3 +372,147 @@ def var( keepdims=keepdims, where=where, ) + + +@add_boilerplate("a") +def median( + a: ndarray, + axis: int | tuple[int, ...] | None = None, + out: ndarray | None = None, + overwrite_input: bool = False, + keepdims: bool = False, +) -> ndarray: + """ + Compute the median along the specified axis. + + Returns the median of the array elements. + + Parameters + ---------- + a : array_like + Input array or object that can be converted to an array. + axis : {int, sequence of int, None}, optional + Axis or axes along which the medians are computed. The default, + axis=None, will compute the median along a flattened version of + the array. + If a sequence of axes, the array is first flattened along the + given axes, then the median is computed along the resulting + flattened axis. + out : ndarray, optional + Alternative output array in which to place the result. It must + have the same shape and buffer length as the expected output, + but the type (of the output) will be cast if necessary. + overwrite_input : bool, optional + If True, then allow use of memory of input array `a` for + calculations. The input array will be modified by the call to + `median`. This will save memory when you do not need to preserve + the contents of the input array. Treat the input as undefined, + but it will probably be fully or partially sorted. Default is + False. If `overwrite_input` is ``True`` and `a` is not already an + `ndarray`, an error will be raised. + keepdims : bool, optional + If this is set to True, the axes which are reduced are left + in the result as dimensions with size one. With this option, + the result will broadcast correctly against the original `arr`. + + Returns + ------- + median : ndarray + A new array holding the result. If the input contains integers + or floats smaller than ``float64``, then the output data-type is + ``np.float64``. Otherwise, the data-type of the output is the + same as that of the input. If `out` is specified, that array is + returned instead. + + See Also + -------- + numpy median + + Availability + -------- + Multiple GPUs, Multiple CPUs + + """ + if a is None: + raise TypeError("'None' is not suported input to 'median'") + return quantile( + a, + 0.5, + axis=axis, + out=out, + overwrite_input=overwrite_input, + keepdims=keepdims, + method="midpoint", + ) + + +@add_boilerplate("a") +def nanmedian( + a: ndarray, + axis: int | tuple[int, ...] | None = None, + out: ndarray | None = None, + overwrite_input: bool = False, + keepdims: bool = False, +) -> ndarray: + """ + Compute the median along the specified axis, while ignoring NaNs + + Returns the median of the array elements. + + Parameters + ---------- + a : array_like + Input array or object that can be converted to an array. + axis : {int, sequence of int, None}, optional + Axis or axes along which the medians are computed. The default, + axis=None, will compute the median along a flattened version of + the array. + If a sequence of axes, the array is first flattened along the + given axes, then the median is computed along the resulting + flattened axis. + out : ndarray, optional + Alternative output array in which to place the result. It must + have the same shape and buffer length as the expected output, + but the type (of the output) will be cast if necessary. + overwrite_input : bool, optional + If True, then allow use of memory of input array `a` for + calculations. The input array will be modified by the call to + `median`. This will save memory when you do not need to preserve + the contents of the input array. Treat the input as undefined, + but it will probably be fully or partially sorted. Default is + False. If `overwrite_input` is ``True`` and `a` is not already an + `ndarray`, an error will be raised. + keepdims : bool, optional + If this is set to True, the axes which are reduced are left + in the result as dimensions with size one. With this option, + the result will broadcast correctly against the original `arr`. + + Returns + ------- + median : ndarray + A new array holding the result. If the input contains integers + or floats smaller than ``float64``, then the output data-type is + ``np.float64``. Otherwise, the data-type of the output is the + same as that of the input. If `out` is specified, that array is + returned instead. + + See Also + -------- + numpy median + + Availability + -------- + Multiple GPUs, Multiple CPUs + + """ + if a is None: + raise TypeError("'None' is not suported input to 'nanmedian'") + return nanquantile( + a, + 0.5, + axis=axis, + out=out, + overwrite_input=overwrite_input, + keepdims=keepdims, + method="midpoint", + ) diff --git a/docs/cunumeric/source/api/statistics.rst b/docs/cunumeric/source/api/statistics.rst index 9f82799e3f..5fb0cdc95f 100644 --- a/docs/cunumeric/source/api/statistics.rst +++ b/docs/cunumeric/source/api/statistics.rst @@ -24,6 +24,8 @@ Averages and variances mean nanmean var + median + nanmedian Correlating ----------- diff --git a/tests/integration/test_median.py b/tests/integration/test_median.py new file mode 100644 index 0000000000..f394710af3 --- /dev/null +++ b/tests/integration/test_median.py @@ -0,0 +1,241 @@ +# Copyright 2024 NVIDIA Corporation +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +import numpy as np +import pytest +from legate import LEGATE_MAX_DIM +from utils.generators import mk_seq_array + +import cunumeric as num + + +class TestMedianErrors: + def test_none_array(self): + expected_exc = TypeError + msg = "unsupported operand type" + with pytest.raises(expected_exc, match=msg): + np.median(None) + expected_exc = TypeError + msg = "'None' is not suported input to 'median'" + with pytest.raises(expected_exc, match=msg): + num.median(None) + + def test_out(self): + array = np.arange(0, 5) + out_a = np.arange(0, 3) + expected_exc = ValueError + # shorten the error message as it vary depending on numpy version + msg = "output parameter for reduction operation add" + with pytest.raises(expected_exc, match=msg): + np.median(array, out=out_a) + msg = "wrong shape on output array" + with pytest.raises(expected_exc, match=msg): + num.median(array, out=out_a) + + def test_median_empty_array(self): + expected_exc = IndexError + # Numpy returns Warning instead of the Error + # msg="invalid value encountered in scalar divide" + # with pytest.raises(expected_exc, match=msg): + # np.median([]) + msg = "nvalid entry in indices array" + with pytest.raises(expected_exc, match=msg): + num.median([]) + + +class TestMedian: + @pytest.mark.parametrize("ndim", range(1, LEGATE_MAX_DIM)) + @pytest.mark.parametrize( + "keepdims", + ( + False, + True, + ), + ) + def test_median_basic(self, ndim, keepdims): + shape = np.random.randint(1, 6, ndim, dtype=int) + size = 1 + for dim in shape: + size *= dim + np_arr = mk_seq_array(np, shape) + num_arr = num.array(np_arr) + for axis in range(0, ndim): + np_res = np.median(np_arr, axis=axis, keepdims=keepdims) + num_res = num.median(num_arr, axis=axis, keepdims=keepdims) + assert np.array_equal(np_res, num_res) + + @pytest.mark.parametrize( + "axis", + ( + None, + -2, + [ + 1, + -1, + ], + [ + 0, + 1, + 2, + 3, + ], + [ + 0, + 3, + ], + 1, + ), + ) + def test_axis(self, axis): + shape = np.random.randint(3, 10, 4, dtype=int) + size = 1 + for dim in shape: + size *= dim + np_arr = mk_seq_array(np, size).reshape(shape) + num_arr = mk_seq_array(num, size).reshape(shape) + + np_res = np.median(np_arr, axis=axis) + num_res = num.median(num_arr, axis=axis) + assert np.array_equal(np_res, num_res) + + def test_median_identical_values(self): + assert num.median([5, 5, 5, 5]) == 5 + + def test_median_nan_behavior(self): + assert num.isnan(num.median([1, 2, np.nan])) + + def test_median_with_out_array(self): + arr = num.array([[1, 3, 5, 7], [9, 11, 13, 15]]) + out = num.zeros((4,)) + num.median(arr, axis=0, out=out) + assert np.array_equal( + out, + [ + 5.0, + 7.0, + 9.0, + 11.0, + ], + ) # Ensure result is written into `out` array + + +class TestNanMedianErrors: + def test_none_array(self): + expected_exc = TypeError + msg = "unsupported operand type" + with pytest.raises(expected_exc, match=msg): + np.nanmedian(None) + expected_exc = TypeError + msg = "'None' is not suported input to 'nanmedian'" + with pytest.raises(expected_exc, match=msg): + num.nanmedian(None) + + def test_out(self): + array = np.arange(0, 5) + out_a = np.arange(0, 3) + expected_exc = ValueError + # shorten the error message as it vary depending on numpy version + msg = "output parameter for reduction operation add" + # with pytest.raises(expected_exc, match=msg): + np.nanmedian(array, out=out_a) + print("IRINA DEBUG out_a", out_a) + msg = "data type not inexact" + with pytest.raises(expected_exc, match=msg): + num.nanmedian(array, out=out_a) + + def test_nanmedian_empty_array(self): + expected_exc = UnboundLocalError + # Numpy returns Warning instead of the Error + # msg="invalid value encountered in scalar divide" + # with pytest.raises(expected_exc, match=msg): + # np.median([]) + msg = "local variable 'gamma' referenced before assignment" + with pytest.raises(expected_exc, match=msg): + num.nanmedian([]) + + def test_nanmedian_all_nan_values(self): + # Numpy returns Warning instead of the Error and doesn't produce + # any output + expected_exc = UnboundLocalError + msg = "local variable 'gamma' referenced before assignment" + with pytest.raises(expected_exc, match=msg): + num.nanmedian([np.nan, np.nan, np.nan]) + + def test_median_overwrite_input(self): + arr = num.array([7, 1, 5, 3]) + median = num.median(arr, overwrite_input=True) + assert median == 4 # Ensure correct median + assert np.array_equal(arr, [1, 3, 5, 7]) # Input array is modified + + arr_2d = num.array([[1, 3], [5, 7]]) + median_axis0 = num.median(arr_2d, axis=0, overwrite_input=True) + assert np.array_equal(median_axis0, [3, 5]) + assert np.array_equal( + arr_2d, [[1, 3], [5, 7]] + ) # Input array is not always modified along axis + + +class TestNanmedian: + @pytest.mark.parametrize("ndim", range(1, LEGATE_MAX_DIM + 1)) + def test_nanmedian_basic(self, ndim): + shape = np.random.randint(2, 6, ndim, dtype=int) + size = 1 + for dim in shape: + size *= dim + np_arr = mk_seq_array(np, shape).astype(float) + np.putmask(np_arr, np_arr > 2, np.nan) + num_arr = num.array(np_arr) + for axis in range(0, ndim): + np_res = np.nanmedian(np_arr, axis=axis) + num_res = num.nanmedian(num_arr, axis=axis) + assert np.array_equal(np_res, num_res, equal_nan=True) + + def test_nanmedian_identical_values_with_nans(self): + assert num.nanmedian([np.nan, np.nan, 5, np.nan]) == 5 + + def test_median_with_out_array(self): + arr = num.array([[1, 3, 5, 7, np.nan], [9, np.nan, 11, 13, 15]]) + out = num.zeros((5,)) + num.nanmedian(arr, axis=0, out=out) + assert np.array_equal( + out, + [ + 5.0, + 3.0, + 8.0, + 10.0, + 15.0, + ], + ) # Ensure result is written into `out` array + + def test_nanmedian_overwrite_input(self): + arr = num.array([7, 1, np.nan, 3]) + median = num.nanmedian(arr, overwrite_input=True) + assert median == 3 # Ensure correct median + # FIXME: doesn't work + # assert np.array_equal(arr, [1, 3, 7, np.nan], equal_nan=True) + + arr_2d = num.array([[1, np.nan], [5, 7]]) + median_axis0 = num.nanmedian(arr_2d, axis=0, overwrite_input=True) + assert np.array_equal(median_axis0, [3, 7]) + assert np.array_equal( + arr_2d, [[1, np.nan], [5, 7]], equal_nan=True + ) # Check if input array is altered + + +if __name__ == "__main__": + import sys + + sys.exit(pytest.main(sys.argv)) From 82d32f52d856d9b2fbd4df222caea4d3fc6ff956 Mon Sep 17 00:00:00 2001 From: Bryan Van de Ven Date: Fri, 4 Oct 2024 10:34:11 -0700 Subject: [PATCH 325/462] [WIP] remove curand dependency in CPU build case (#417) * enable the STL-based paths for all CPU builds * remove the _legacy paths in the RNG code * remove has_curand * Fixed aliases usage. * Fixed LEGATE_ABORT(missing_string). * Fixed alias for generator getter. * Fixed missing aliases in bitgenerator. * Fix for randutil_check_status(). * Fix for random header. * Fix for string_view arg consistency in randutil_check_status(). * restore xfails for random creation tests * XFAIL test for which numpy and cunumeric cannot agree in dimensionality or value. * skip bytes tests for generators inapplicable to STL * include random source files unconditionallY * Fix future linker error for when curand will be removed. * cmake cleanup * leave cpu curand as an option * Update cunumeric_cpp.cmake Co-authored-by: Manolis Papadakis --------- Co-authored-by: Andrei Schaffer Co-authored-by: Manolis Papadakis --- cunumeric/config.py | 4 - cunumeric/random/__init__.py | 9 +- cunumeric/random/_legacy.py | 203 ------------------ cunumeric/runtime.py | 1 - cunumeric_cpp.cmake | 16 +- src/cunumeric/cunumeric.cc | 5 - src/cunumeric/cunumeric_c.h | 1 - src/cunumeric/random/bitgenerator.cc | 22 +- src/cunumeric/random/bitgenerator_curand.inl | 2 +- src/cunumeric/random/randutil/generator.h | 4 +- .../random/randutil/generator_host.cc | 6 +- .../randutil/generator_host_advanced.cc | 6 +- .../generator_host_straightforward.cc | 6 +- .../random/randutil/randutil_curand.h | 5 - src/cunumeric/random/rnd_types.h | 6 +- tests/integration/test_random_advanced.py | 17 +- tests/integration/test_random_beta.py | 16 +- tests/integration/test_random_bitgenerator.py | 16 +- tests/integration/test_random_creation.py | 26 +-- tests/integration/test_random_gamma.py | 16 +- .../test_random_straightforward.py | 27 +-- 21 files changed, 88 insertions(+), 326 deletions(-) delete mode 100644 cunumeric/random/_legacy.py diff --git a/cunumeric/config.py b/cunumeric/config.py index 1d578b5897..844ea008ba 100644 --- a/cunumeric/config.py +++ b/cunumeric/config.py @@ -280,10 +280,6 @@ class _CunumericSharedLib: CUNUMERIC_WRITE: int CUNUMERIC_ZIP: int - @abstractmethod - def cunumeric_has_curand(self) -> bool: - ... - @abstractmethod def cunumeric_has_cusolvermp(self) -> bool: ... diff --git a/cunumeric/random/__init__.py b/cunumeric/random/__init__.py index 8ba461d783..09e5054ff6 100644 --- a/cunumeric/random/__init__.py +++ b/cunumeric/random/__init__.py @@ -20,12 +20,9 @@ from .._utils.coverage import clone_module from ..runtime import runtime -if runtime.has_curand: - from ._random import * - from ._bitgenerator import * - from ._generator import * -else: - from ._legacy import * +from ._random import * +from ._bitgenerator import * +from ._generator import * clone_module( _nprandom, diff --git a/cunumeric/random/_legacy.py b/cunumeric/random/_legacy.py deleted file mode 100644 index 05514d0389..0000000000 --- a/cunumeric/random/_legacy.py +++ /dev/null @@ -1,203 +0,0 @@ -# Copyright 2024 NVIDIA Corporation -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# -from __future__ import annotations - -from typing import TYPE_CHECKING, Any - -import numpy as np -import numpy.random as nprandom - -from .._array.array import ndarray -from ..runtime import runtime - -if TYPE_CHECKING: - import numpy.typing as npt - - from ..types import NdShapeLike - - -def seed(init: int | None = None) -> None: - if init is None: - init = 0 - runtime.set_next_random_epoch(int(init)) - - -def rand(*shapeargs: int) -> float | ndarray: - """ - rand(d0, d1, ..., dn) - - Random values in a given shape. - - Create an array of the given shape and populate it with random samples from - a uniform distribution over ``[0, 1)``. - - Parameters - ---------- - d0, d1, ..., dn : int, optional - The dimensions of the returned array, must be non-negative. - If no argument is given a single Python float is returned. - - Returns - ------- - out : ndarray, shape ``(d0, d1, ..., dn)`` - Random values. - - See Also - -------- - numpy.random.rand - - Availability - -------- - Multiple GPUs, Multiple CPUs - """ - - if shapeargs == (): - return nprandom.rand() - result = ndarray(shapeargs, dtype=np.dtype(np.float64)) - result._thunk.random_uniform() - return result - - -def randint( - low: int, - high: int | None = None, - size: NdShapeLike | None = None, - dtype: np.dtype[Any] | type | None = int, -) -> int | ndarray | npt.NDArray[Any]: - """ - Return random integers from `low` (inclusive) to `high` (exclusive). - - Parameters - ---------- - low : int or array_like[int] - Lowest (signed) integers to be drawn from the distribution (unless - ``high=None``, in which case this parameter is one above the - *highest* such integer). - high : int or array_like[int], optional - If provided, one above the largest (signed) integer to be drawn - from the distribution (see above for behavior if ``high=None``). - If array-like, must contain integer values - size : int or tuple[int], optional - Output shape. If the given shape is, e.g., ``(m, n, k)``, then - ``m * n * k`` samples are drawn. Default is None, in which case a - single value is returned. - dtype : data-type, optional - Desired dtype of the result. Byteorder must be native. - The default value is int. - - Returns - ------- - out : int or ndarray[int] - `size`-shaped array of random integers from the appropriate - distribution, or a single such random int if `size` not provided. - - See Also - -------- - numpy.random.randint - - Availability - -------- - Multiple GPUs, Multiple CPUs - """ - if not isinstance(low, int): - raise NotImplementedError("'low' must be an integer") - if high is not None and not isinstance(high, int): - raise NotImplementedError("'high' must be an integer or None") - - if size is None: - return nprandom.randint(low=low, high=high, size=size, dtype=dtype) - - if dtype is not None: - dtype = np.dtype(dtype) - else: - dtype = np.dtype(np.int64) - # TODO: randint must support unsigned integer dtypes as well - if dtype.kind != "i": - raise NotImplementedError( - "cunumeric.random.randint must be given an integer dtype" - ) - if isinstance(size, int): - size = (size,) - result = ndarray(size, dtype=dtype) - if high is None: - if low <= 0: - raise ValueError( - "bound must be strictly greater than 0 for randint" - ) - result._thunk.random_integer(low=0, high=low) - else: - if low >= high: - raise ValueError( - "'high' bound must be strictly greater than 'low' " - "bound for randint" - ) - result._thunk.random_integer(low=low, high=high) - return result - - -def randn(*shapeargs: int) -> float | ndarray: - """ - randn(d0, d1, ..., dn) - - Return a sample (or samples) from the "standard normal" distribution. - - Parameters - ---------- - d0, d1, ..., dn : int, optional - The dimensions of the returned array, must be non-negative. - If no argument is given a single Python float is returned. - - Returns - ------- - Z : ndarray or float - A ``(d0, d1, ..., dn)``-shaped array of floating-point samples from - the standard normal distribution, or a single such float if - no parameters were supplied. - - See Also - -------- - numpy.random.randn - - Availability - -------- - Multiple GPUs, Multiple CPUs - """ - - if shapeargs == (): - return nprandom.randn() - result = ndarray(shapeargs, dtype=np.dtype(np.float64)) - result._thunk.random_normal() - return result - - -def random(size: NdShapeLike | None = None) -> float | ndarray: - """ - random(size=None) - - Return random floats in the half-open interval [0.0, 1.0). - - See Also - -------- - numpy.random.random - - Availability - -------- - Multiple GPUs, Multiple CPUs - """ - if size is None: - return nprandom.random() - result = ndarray(size, dtype=np.dtype(np.float64)) - result._thunk.random_uniform() - return result diff --git a/cunumeric/runtime.py b/cunumeric/runtime.py index 885ce48cf9..b5ab59faf3 100644 --- a/cunumeric/runtime.py +++ b/cunumeric/runtime.py @@ -88,7 +88,6 @@ def __init__(self) -> None: assert cunumeric_lib.shared_object is not None self.cunumeric_lib = cunumeric_lib.shared_object - self.has_curand = cunumeric_lib.shared_object.cunumeric_has_curand() self.has_cusolvermp = ( cunumeric_lib.shared_object.cunumeric_has_cusolvermp() ) diff --git a/cunumeric_cpp.cmake b/cunumeric_cpp.cmake index 65efcd3b7e..5c324f1dc3 100644 --- a/cunumeric_cpp.cmake +++ b/cunumeric_cpp.cmake @@ -189,6 +189,10 @@ list(APPEND cunumeric_SOURCES src/cunumeric/matrix/trilu.cc src/cunumeric/matrix/trsm.cc src/cunumeric/matrix/util.cc + src/cunumeric/random/bitgenerator.cc + src/cunumeric/random/randutil/generator_host.cc + src/cunumeric/random/randutil/generator_host_straightforward.cc + src/cunumeric/random/randutil/generator_host_advanced.cc src/cunumeric/random/rand.cc src/cunumeric/search/argwhere.cc src/cunumeric/search/nonzero.cc @@ -364,21 +368,13 @@ if(Legion_USE_CUDA) endif() # Add `src/cunumeric/random/random.mk` sources -if(Legion_USE_CUDA OR cunumeric_cuRAND_INCLUDE_DIR) +if(Legion_USE_CUDA) list(APPEND cunumeric_SOURCES - src/cunumeric/random/bitgenerator.cc - src/cunumeric/random/randutil/generator_host.cc - src/cunumeric/random/randutil/generator_host_straightforward.cc - src/cunumeric/random/randutil/generator_host_advanced.cc - ) - if(Legion_USE_CUDA) - list(APPEND cunumeric_SOURCES src/cunumeric/random/bitgenerator.cu src/cunumeric/random/randutil/generator_device.cu src/cunumeric/random/randutil/generator_device_straightforward.cu src/cunumeric/random/randutil/generator_device_advanced.cu - ) - endif() +) endif() # add sources for cusolverMp diff --git a/src/cunumeric/cunumeric.cc b/src/cunumeric/cunumeric.cc index 72f21bdc5b..2f62991118 100644 --- a/src/cunumeric/cunumeric.cc +++ b/src/cunumeric/cunumeric.cc @@ -75,11 +75,6 @@ extern "C" { void cunumeric_perform_registration(void) { cunumeric::registration_callback(); } -bool cunumeric_has_curand() -{ - return LEGATE_DEFINED(LEGATE_USE_CUDA) || LEGATE_DEFINED(CUNUMERIC_CURAND_FOR_CPU_BUILD); -} - bool cunumeric_has_cusolvermp() { return LEGATE_DEFINED(LEGATE_USE_CUDA) && LEGATE_DEFINED(CUNUMERIC_USE_CUSOLVERMP); diff --git a/src/cunumeric/cunumeric_c.h b/src/cunumeric/cunumeric_c.h index 33d401cbfb..c569f786e1 100644 --- a/src/cunumeric/cunumeric_c.h +++ b/src/cunumeric/cunumeric_c.h @@ -334,7 +334,6 @@ typedef struct ReductionOpIds { } ReductionOpIds; void cunumeric_perform_registration(); -bool cunumeric_has_curand(); bool cunumeric_has_cusolvermp(); unsigned cunumeric_max_eager_volume(); diff --git a/src/cunumeric/random/bitgenerator.cc b/src/cunumeric/random/bitgenerator.cc index eb8cffd790..3da591d0ee 100644 --- a/src/cunumeric/random/bitgenerator.cc +++ b/src/cunumeric/random/bitgenerator.cc @@ -14,9 +14,11 @@ * */ -// MacOS host variant: +#include "legate.h" + +// CPU Builds: // -#if defined(__APPLE__) && defined(__MACH__) +#if !LEGATE_DEFINED(LEGATE_USE_CUDA) && !LEGATE_DEFINED(CUNUMERIC_CURAND_FOR_CPU_BUILD) #define CUNUMERIC_USE_STL_RANDOM_ENGINE #endif @@ -48,6 +50,12 @@ void randutil_check_status(rnd_status_t error, std::string_view file, int line) assert(false); } } +// for the STL path: delegate to randutil_check_status(...): +// +void randutil_check_curand(curandStatus_t error, std::string_view file, int line) +{ + randutil_check_status(error, file, line); +} #else void randutil_check_curand(curandStatus_t error, std::string_view file, int line) { @@ -57,16 +65,22 @@ void randutil_check_curand(curandStatus_t error, std::string_view file, int line assert(false); } } +// for the curand path: delegate to randutil_check_curand(...): +// +void randutil_check_status(rnd_status_t error, std::string_view file, int line) +{ + randutil_check_curand(error, file, line); +} #endif struct CPUGenerator : public CURANDGenerator { CPUGenerator(BitGeneratorType gentype, uint64_t seed, uint64_t generatorId, uint32_t flags) : CURANDGenerator(gentype, seed, generatorId) { - CHECK_CURAND(::randutilCreateGeneratorHost(&gen_, type_, seed, generatorId, flags)); + CHECK_RND_ENGINE(::randutilCreateGeneratorHost(&gen_, type_, seed, generatorId, flags)); } - virtual ~CPUGenerator() { CHECK_CURAND(::randutilDestroyGenerator(gen_)); } + virtual ~CPUGenerator() { CHECK_RND_ENGINE(::randutilDestroyGenerator(gen_)); } }; template <> diff --git a/src/cunumeric/random/bitgenerator_curand.inl b/src/cunumeric/random/bitgenerator_curand.inl index 20c8a6bfb9..ddf19b0244 100644 --- a/src/cunumeric/random/bitgenerator_curand.inl +++ b/src/cunumeric/random/bitgenerator_curand.inl @@ -43,7 +43,7 @@ struct CURANDGenerator { protected: CURANDGenerator(BitGeneratorType gentype, uint64_t seed, uint64_t generatorId) - : seed_(seed), generatorId_(generatorId), type_(get_curandRngType(gentype)) + : seed_(seed), generatorId_(generatorId), type_(get_rndRngType(gentype)) { randutil_log().debug() << "CURANDGenerator::create"; } diff --git a/src/cunumeric/random/randutil/generator.h b/src/cunumeric/random/randutil/generator.h index 6b07193bc2..50d3b0489f 100644 --- a/src/cunumeric/random/randutil/generator.h +++ b/src/cunumeric/random/randutil/generator.h @@ -85,12 +85,12 @@ struct inner_generator : basegenerator virtual ~inner_generator() {} template - curandStatus_t draw(func_t func, size_t N, out_t* out) + rnd_status_t draw(func_t func, size_t N, out_t* out) { for (size_t k = 0; k < N; ++k) { out[k] = func(generator); } - return CURAND_STATUS_SUCCESS; + return RND_STATUS_SUCCESS; } }; diff --git a/src/cunumeric/random/randutil/generator_host.cc b/src/cunumeric/random/randutil/generator_host.cc index 11896817c4..25e6d65741 100644 --- a/src/cunumeric/random/randutil/generator_host.cc +++ b/src/cunumeric/random/randutil/generator_host.cc @@ -14,9 +14,11 @@ * */ -// MacOS host variant: +#include "legate.h" + +// CPU Builds: // -#if defined(__APPLE__) && defined(__MACH__) +#if !LEGATE_DEFINED(LEGATE_USE_CUDA) && !LEGATE_DEFINED(CUNUMERIC_CURAND_FOR_CPU_BUILD) #define CUNUMERIC_USE_STL_RANDOM_ENGINE #endif diff --git a/src/cunumeric/random/randutil/generator_host_advanced.cc b/src/cunumeric/random/randutil/generator_host_advanced.cc index 75624f5e43..fc3817c8b5 100644 --- a/src/cunumeric/random/randutil/generator_host_advanced.cc +++ b/src/cunumeric/random/randutil/generator_host_advanced.cc @@ -14,9 +14,11 @@ * */ -// MacOS host variant: +#include "legate.h" + +// CPU Builds: // -#if defined(__APPLE__) && defined(__MACH__) +#if !LEGATE_DEFINED(LEGATE_USE_CUDA) && !LEGATE_DEFINED(CUNUMERIC_CURAND_FOR_CPU_BUILD) #define CUNUMERIC_USE_STL_RANDOM_ENGINE #endif diff --git a/src/cunumeric/random/randutil/generator_host_straightforward.cc b/src/cunumeric/random/randutil/generator_host_straightforward.cc index 732b77b134..3deffdf216 100644 --- a/src/cunumeric/random/randutil/generator_host_straightforward.cc +++ b/src/cunumeric/random/randutil/generator_host_straightforward.cc @@ -14,9 +14,11 @@ * */ -// MacOS host variant: +#include "legate.h" + +// CPU Builds: // -#if defined(__APPLE__) && defined(__MACH__) +#if !LEGATE_DEFINED(LEGATE_USE_CUDA) && !LEGATE_DEFINED(CUNUMERIC_CURAND_FOR_CPU_BUILD) #define CUNUMERIC_USE_STL_RANDOM_ENGINE #endif diff --git a/src/cunumeric/random/randutil/randutil_curand.h b/src/cunumeric/random/randutil/randutil_curand.h index c8add0635e..fdcba71fb0 100644 --- a/src/cunumeric/random/randutil/randutil_curand.h +++ b/src/cunumeric/random/randutil/randutil_curand.h @@ -30,10 +30,5 @@ // host generators are not compiled with nvcc #define QUALIFIERS static #define RANDUTIL_QUALIFIERS -#ifdef CUNUMERIC_USE_STL_RANDOM_ENGINE #include -#else -#include -#endif - #endif diff --git a/src/cunumeric/random/rnd_types.h b/src/cunumeric/random/rnd_types.h index 6815f12e44..0fcab9f489 100644 --- a/src/cunumeric/random/rnd_types.h +++ b/src/cunumeric/random/rnd_types.h @@ -17,6 +17,7 @@ #pragma once #include "cunumeric/random/rnd_aliases.h" +#include #ifdef CUNUMERIC_USE_STL_RANDOM_ENGINE @@ -31,7 +32,7 @@ namespace cunumeric { legate::Logger& randutil_log(); -void randutil_check_status(rnd_status_t error, const char* file, int line); +void randutil_check_status(rnd_status_t error, std::string_view, int line); static inline randRngType get_rndRngType(cunumeric::BitGeneratorType kind) { @@ -46,7 +47,7 @@ static inline randRngType get_rndRngType(cunumeric::BitGeneratorType kind) case cunumeric::BitGeneratorType::MTGP32: return randRngType::STL_MT_19937; case cunumeric::BitGeneratorType::MT19937: return randRngType::STL_MT_19937; case cunumeric::BitGeneratorType::PHILOX4_32_10: return randRngType::STL_MT_19937; - default: LEGATE_ABORT(); + default: LEGATE_ABORT("Unsupported random generator."); } return randRngType::RND_RNG_TEST; } @@ -57,7 +58,6 @@ static inline randRngType get_rndRngType(cunumeric::BitGeneratorType kind) #include "cunumeric/random/curand_help.h" #define CHECK_RND_ENGINE(...) CHECK_CURAND(__VA_ARGS__) -#define randutil_check_status randutil_check_curand #define get_rndRngType get_curandRngType #endif diff --git a/tests/integration/test_random_advanced.py b/tests/integration/test_random_advanced.py index decc8d01e6..c470643231 100644 --- a/tests/integration/test_random_advanced.py +++ b/tests/integration/test_random_advanced.py @@ -21,16 +21,13 @@ import cunumeric as num LEGATE_TEST = os.environ.get("LEGATE_TEST", None) == "1" -if not num.runtime.has_curand: - pytestmark = pytest.mark.skip() - BITGENERATOR_ARGS = [] -else: - BITGENERATOR_ARGS = [ - ModuleGenerator, - num.random.XORWOW, - num.random.MRG32k3a, - num.random.PHILOX4_32_10, - ] + +BITGENERATOR_ARGS = [ + ModuleGenerator, + num.random.XORWOW, + num.random.MRG32k3a, + num.random.PHILOX4_32_10, +] @pytest.mark.parametrize("t", BITGENERATOR_ARGS, ids=str) diff --git a/tests/integration/test_random_beta.py b/tests/integration/test_random_beta.py index 6d2783b509..1670b742a1 100644 --- a/tests/integration/test_random_beta.py +++ b/tests/integration/test_random_beta.py @@ -19,16 +19,12 @@ import cunumeric as num -if not num.runtime.has_curand: - pytestmark = pytest.mark.skip() - BITGENERATOR_ARGS = [] -else: - BITGENERATOR_ARGS = [ - ModuleGenerator, - num.random.XORWOW, - num.random.MRG32k3a, - num.random.PHILOX4_32_10, - ] +BITGENERATOR_ARGS = [ + ModuleGenerator, + num.random.XORWOW, + num.random.MRG32k3a, + num.random.PHILOX4_32_10, +] @pytest.mark.parametrize("t", BITGENERATOR_ARGS, ids=str) diff --git a/tests/integration/test_random_bitgenerator.py b/tests/integration/test_random_bitgenerator.py index 0268af14a6..c0b8b70a2f 100644 --- a/tests/integration/test_random_bitgenerator.py +++ b/tests/integration/test_random_bitgenerator.py @@ -19,16 +19,12 @@ import cunumeric as num -if not num.runtime.has_curand: - pytestmark = pytest.mark.skip() - BITGENERATOR_ARGS = [] -else: - BITGENERATOR_ARGS = [ - ModuleGenerator, - num.random.XORWOW, - num.random.MRG32k3a, - num.random.PHILOX4_32_10, - ] +BITGENERATOR_ARGS = [ + ModuleGenerator, + num.random.XORWOW, + num.random.MRG32k3a, + num.random.PHILOX4_32_10, +] @pytest.mark.parametrize("t", BITGENERATOR_ARGS, ids=str) diff --git a/tests/integration/test_random_creation.py b/tests/integration/test_random_creation.py index 78ad827dc4..82fd8f5483 100644 --- a/tests/integration/test_random_creation.py +++ b/tests/integration/test_random_creation.py @@ -70,16 +70,7 @@ def gen_random_from_both( # cuNumeric: keeps generating different arrays. # seed is respected in Eager mode. ), - pytest.param( - None, - marks=pytest.mark.xfail( - not num.runtime.has_curand, - reason="legacy RNG fallback treats seed(None) as seed(0)", - ), - # https://github.com/nv-legate/cunumeric/issues/1018 - # NumPy: seed(None) is equivalent to seed(()) - # cuNumeric non-cuRAND fallback: seed(None) equivalent to seed(0) - ), + pytest.param(None), pytest.param( (4, 6, 8), marks=pytest.mark.xfail( @@ -132,10 +123,6 @@ def test_default_rng_seed(seed): EAGER_TEST, reason="cuNumeric does not respect seed in Eager mode", ) -@pytest.mark.xfail( - not num.runtime.has_curand, - reason="XORWOW not available without cuRAND", -) def test_default_rng_bitgenerator(): seed = 12345 rng_np_1 = np.random.default_rng(np.random.PCG64(seed)) @@ -246,7 +233,7 @@ def test_randint_float_range(low, high): @pytest.mark.xfail( - not num.runtime.has_curand or not EAGER_TEST, + not EAGER_TEST, reason="cuNumeric raises NotImplementedError", ) @pytest.mark.parametrize("size", ALL_RNG_SIZES, ids=str) @@ -290,7 +277,7 @@ def test_randint_distribution(low, high, size, dtype): @pytest.mark.xfail( - not num.runtime.has_curand or not EAGER_TEST, + not EAGER_TEST, reason="cuNumeric raises NotImplementedError", ) @pytest.mark.parametrize("size", (1024, 1025)) @@ -300,13 +287,6 @@ def test_randint_bool(size): assert_distribution( arr_num, np.mean(arr_np), np.std(arr_np), mean_tol=0.05 ) - # NumPy pass - # cuNumeric not num.runtime.has_curand: - # NotImplementedError: cunumeric.random.randint must be given an integer - # dtype - # cuNumeric LEGATE_TEST=1 or size > 1024: - # NotImplementedError: type for random.integers has to be int64 or int32 - # or int16 @pytest.mark.parametrize("low, high", LOW_HIGH, ids=str) diff --git a/tests/integration/test_random_gamma.py b/tests/integration/test_random_gamma.py index 6e6edd3c74..56ec28159b 100644 --- a/tests/integration/test_random_gamma.py +++ b/tests/integration/test_random_gamma.py @@ -19,16 +19,12 @@ import cunumeric as num -if not num.runtime.has_curand: - pytestmark = pytest.mark.skip() - BITGENERATOR_ARGS = [] -else: - BITGENERATOR_ARGS = [ - ModuleGenerator, - num.random.XORWOW, - num.random.MRG32k3a, - num.random.PHILOX4_32_10, - ] +BITGENERATOR_ARGS = [ + ModuleGenerator, + num.random.XORWOW, + num.random.MRG32k3a, + num.random.PHILOX4_32_10, +] @pytest.mark.parametrize("t", BITGENERATOR_ARGS, ids=str) diff --git a/tests/integration/test_random_straightforward.py b/tests/integration/test_random_straightforward.py index 8c3363e47a..f3e022c35f 100644 --- a/tests/integration/test_random_straightforward.py +++ b/tests/integration/test_random_straightforward.py @@ -14,22 +14,19 @@ # import math +import legate.install_info as info import numpy as np import pytest from utils.random import ModuleGenerator, assert_distribution import cunumeric as num -if not num.runtime.has_curand: - pytestmark = pytest.mark.skip() - BITGENERATOR_ARGS = [] -else: - BITGENERATOR_ARGS = [ - ModuleGenerator, - num.random.XORWOW, - num.random.MRG32k3a, - num.random.PHILOX4_32_10, - ] +BITGENERATOR_ARGS = [ + ModuleGenerator, + num.random.XORWOW, + num.random.MRG32k3a, + num.random.PHILOX4_32_10, +] @pytest.mark.parametrize("t", BITGENERATOR_ARGS, ids=str) @@ -328,6 +325,9 @@ def test_weibull_float64(t): assert_distribution(a, theo_mean, theo_std) +@pytest.mark.skipif( + not info.use_cuda, reason="generators do not exist in STL random" +) @pytest.mark.parametrize("t", BITGENERATOR_ARGS, ids=str) def test_bytes(t): bitgen = t(seed=42) @@ -365,7 +365,9 @@ def test_beta_sizes(t, func, args, size): assert a_np.shape == a_num.shape -@pytest.mark.xfail +@pytest.mark.xfail( + reason="cuNumeric returns singleton array; NumPy returns scalar" +) @pytest.mark.parametrize("t", BITGENERATOR_ARGS, ids=str) @pytest.mark.parametrize("func, args", FUNC_ARGS, ids=str) def test_beta_size_none(t, func, args): @@ -376,7 +378,8 @@ def test_beta_size_none(t, func, args): a_num = getattr(gen_num, func)(*args, size=None) # cuNumeric returns singleton array # NumPy returns scalar - assert np.ndim(a_np) == np.ndim(a_num) + # print("a_np: %s, a_num=%s\n"%(str(a_np), str(a_num))) + assert (1 + np.ndim(a_np)) == np.ndim(a_num) if __name__ == "__main__": From db4851a056e5d074e03c7c6653934b370cd3786f Mon Sep 17 00:00:00 2001 From: Irina Demeshko Date: Fri, 4 Oct 2024 21:53:59 -0700 Subject: [PATCH 326/462] stack arrays when needed (#422) * fixing the logic when a tuple of arrays should be converted to a single array --- cunumeric/runtime.py | 20 ++++++++++++++++++++ tests/integration/test_logical_reduction.py | 21 +++++++++++++++++++++ 2 files changed, 41 insertions(+) diff --git a/cunumeric/runtime.py b/cunumeric/runtime.py index b5ab59faf3..662a3e2ce1 100644 --- a/cunumeric/runtime.py +++ b/cunumeric/runtime.py @@ -14,6 +14,7 @@ # from __future__ import annotations +import math import warnings from functools import lru_cache, reduce from typing import TYPE_CHECKING, Any, Sequence, TypeGuard @@ -282,6 +283,25 @@ def get_numpy_thunk( if share: obj = np.asarray(obj, dtype=dtype) else: + from ._array.array import ndarray + from ._module.array_joining import stack + + if ( + any( + ( + isinstance(obj, tuple), + isinstance(obj, list), + ) + ) + and len(obj) > 1 + and all( + (isinstance(o, ndarray) or isinstance(o, np.ndarray)) + for o in obj + ) + and math.prod(obj[0].shape) != 0 + ): + obj = stack(obj) # type: ignore + return obj._thunk obj = np.array(obj, dtype=dtype) elif dtype is not None and dtype != obj.dtype: obj = obj.astype(dtype) diff --git a/tests/integration/test_logical_reduction.py b/tests/integration/test_logical_reduction.py index 3bd1f3e997..ef9c425ec9 100644 --- a/tests/integration/test_logical_reduction.py +++ b/tests/integration/test_logical_reduction.py @@ -15,6 +15,8 @@ import numpy as np import pytest +from legate import LEGATE_MAX_DIM +from utils.generators import mk_seq_array import cunumeric as num @@ -34,6 +36,25 @@ def test_logical_reductions(axis): assert num.array_equal(out_num, out_np) +@pytest.mark.parametrize("ndim", range(1, LEGATE_MAX_DIM)) +@pytest.mark.parametrize( + "axis", + [ + None, + 0, + -1, + ], +) +def test_logical_reductions_over_cunumeric_arrays(ndim, axis): + shape = (5,) * ndim + np_arr = mk_seq_array(np, shape) + in_np = tuple(np_arr % 2 for dim in range(ndim)) + in_num = tuple(num.array(np_arr % 2) for dim in range(ndim)) + out_num = num.logical_and.reduce(in_num, axis=axis) + out_np = np.logical_and.reduce(in_np, axis=axis) + assert num.array_equal(out_num, out_np) + + if __name__ == "__main__": import sys From d0ff65da22f3f367c591396dccdb60f1301ed3b4 Mon Sep 17 00:00:00 2001 From: Shijie Chen Date: Mon, 7 Oct 2024 14:05:53 +0800 Subject: [PATCH 327/462] Added tests for cunumeric.unique (#97) --- tests/cpp/integration/test_unique.cc | 96 ++++++++++++++++++++++++++++ 1 file changed, 96 insertions(+) create mode 100644 tests/cpp/integration/test_unique.cc diff --git a/tests/cpp/integration/test_unique.cc b/tests/cpp/integration/test_unique.cc new file mode 100644 index 0000000000..b7aa1bacab --- /dev/null +++ b/tests/cpp/integration/test_unique.cc @@ -0,0 +1,96 @@ +/* Copyright 2024 NVIDIA Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +#include "common_utils.h" +#include +#include + +using namespace cunumeric; + +namespace { + +template +std::vector unique_result(std::vector v) +{ + std::sort(v.begin(), v.end()); + auto last = std::unique(v.begin(), v.end()); + v.erase(last, v.end()); + return v; +} + +TEST(Unique, test_basic) +{ + { + auto x = mk_array({1, 1, 2, 2, 3, 3}); + auto x_out = x.unique(); + check_array(x_out, {1, 2, 3}); + } + { + auto x = mk_array({1, 1, 2, 3}, {2, 2}); + auto x_out = unique(x); + check_array(x_out, {1, 2, 3}); + } + { + std::vector x_in{1, 2, 1, 1, 3, 3, 3, 4, 5, 4}; + auto x_gt = unique_result(x_in); + auto x = mk_array(x_in); + auto x_out = unique(x); + check_array(x_out, x_gt); + debug_array(x); + debug_array(x_out); + } +} + +TEST(Unique, test_scalar) +{ + // If x is a 0-D scalar, "List of axes to broadcast must not be empty" exception will be thrown. + // auto x = mk_array({99}); + auto x = mk_array({99}, {1}); + auto x_out = x.unique(); + check_array(x_out, {99}, {1}); +} + +template +std::vector mk_random_vector(std::vector shape, std::function gen) +{ + size_t size = std::accumulate(shape.begin(), shape.end(), size_t(1), std::multiplies()); + std::vector v(size); + std::generate(v.begin(), v.end(), gen); + return v; +} + +static int randint(int low, int high) { return rand() % (high - low) + low; } + +TEST(Unique, test_ndim) +{ + srand(111); + std::vector shape; + size_t size = 1; + for (int32_t ndim = 1; ndim <= LEGATE_MAX_DIM; ++ndim) { + shape.emplace_back(4); + size *= 4; + auto x_in = + mk_random_vector(shape, [] { return static_cast(randint(0, 10)); }); + auto x_gt = unique_result(x_in); + auto x = mk_array(x_in, shape); + auto x_out = unique(x); + check_array(x_out, x_gt); + debug_array(x, false); + debug_array(x_out); + } +} + +} // namespace From 09bd854c9601cc409dd31fd73113dbde30403cd8 Mon Sep 17 00:00:00 2001 From: XiaLuNV <110973296+XiaLuNV@users.noreply.github.com> Date: Mon, 7 Oct 2024 15:41:03 +0800 Subject: [PATCH 328/462] mv test_stack.py to integration test (#409) --- tests/{unit/cunumeric/_utils => integration}/test_stack.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) rename tests/{unit/cunumeric/_utils => integration}/test_stack.py (97%) diff --git a/tests/unit/cunumeric/_utils/test_stack.py b/tests/integration/test_stack.py similarity index 97% rename from tests/unit/cunumeric/_utils/test_stack.py rename to tests/integration/test_stack.py index ca4a2ed8b3..d981a94f22 100644 --- a/tests/unit/cunumeric/_utils/test_stack.py +++ b/tests/integration/test_stack.py @@ -40,7 +40,7 @@ def test_get_line_number_from_frame() -> None: class Test_find_last_user_frames: - def check_default_top_only(self) -> None: + def test_default_top_only(self) -> None: result = m.find_last_user_frames(top_only=True) assert isinstance(result, str) assert "|" not in result From f04cb89dee63f3eac695252eeb3dc69f604e28bc Mon Sep 17 00:00:00 2001 From: Andrei Schaffer <37386037+aschaffer@users.noreply.github.com> Date: Wed, 9 Oct 2024 18:05:22 -0500 Subject: [PATCH 329/462] Added fix for multi-dimensional OMP convolution (#427) * Added local_l1_outputs = 1; * Added multi-dimensional convolution test. --- src/cunumeric/convolution/convolve_omp.cc | 3 +- tests/integration/test_nd_convolve.py | 73 +++++++++++++++++++++++ 2 files changed, 75 insertions(+), 1 deletion(-) create mode 100644 tests/integration/test_nd_convolve.py diff --git a/src/cunumeric/convolution/convolve_omp.cc b/src/cunumeric/convolution/convolve_omp.cc index 355fe37af1..6bb80383ba 100644 --- a/src/cunumeric/convolution/convolve_omp.cc +++ b/src/cunumeric/convolution/convolve_omp.cc @@ -141,7 +141,8 @@ struct ConvolveImplBody { Rect l2_output_rect(l2_output, l2_output + l2_output_tile - one); unsigned local_l1_outputs = total_l1_outputs; if (!subrect.contains(l2_output_rect)) { - l2_output_rect = subrect.intersection(l2_output_rect); + l2_output_rect = subrect.intersection(l2_output_rect); + local_l1_outputs = 1; for (int d = 0; d < DIM; d++) { local_l1_outputs *= ((l2_output_rect.hi[d] - l2_output_rect.lo[d] + l1_output_tile[d]) / l1_output_tile[d]); diff --git a/tests/integration/test_nd_convolve.py b/tests/integration/test_nd_convolve.py new file mode 100644 index 0000000000..4090707945 --- /dev/null +++ b/tests/integration/test_nd_convolve.py @@ -0,0 +1,73 @@ +# Copyright 2024 NVIDIA Corporation +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +import os + +import numpy as np +import pytest +import scipy.signal as sig +from utils.comparisons import allclose + +import cunumeric as num + +CUDA_TEST = os.environ.get("LEGATE_NEED_CUDA") == "1" + + +def test_interpolation_x(): + import scipy.signal as signal + + nz = 100 + nx = 200 + hs = 2 + nvariables = 4 + shape = (nvariables, nz + 2 * hs, nx + 2 * hs) + nelements = num.prod(shape) + + kernel = num.array([-1.0 / 12, 7.0 / 12, 7.0 / 12, -1.0 / 12], dtype=num.float64).reshape(1, 1, 4) + state = num.arange(nelements).astype(num.float64).reshape(shape) + out_legate = num.convolve(state[:, 2 : nz + 2, :], kernel, mode="same",) + out_scipy = signal.convolve(state[:, 2 : nz + 2, :], kernel, mode="same",) + + #print(f"min/max, legate: {out_legate.min()}, {out_legate.max()}", flush=True) + #print(f"min/max, scipy : {out_scipy.min()}, {out_scipy.max()}", flush=True) + + assert allclose(out_scipy, out_legate) + + +def test_interpolation_z(): + import scipy.signal as signal + + nz = 100 + nx = 200 + hs = 2 + nvariables = 4 + shape = (nvariables, nz + 2 * hs, nx + 2 * hs) + nelements = num.prod(shape) + + kernel = num.array([-1.0 / 12, 7.0 / 12, 7.0 / 12, -1.0 / 12], dtype=num.float64).reshape(1, 4, 1) + state = num.arange(nelements).astype(num.float64).reshape(shape) + out_legate = num.convolve(state[:, :, 2 : nx + 2], kernel, mode="same",) + out_scipy = signal.convolve(state[:, :, 2 : nx + 2], kernel, mode="same",) + + #print(f"min/max, legate: {out_legate.min()}, {out_legate.max()}", flush=True) + #print(f"min/max, scipy : {out_scipy.min()}, {out_scipy.max()}", flush=True) + + assert allclose(out_scipy, out_legate) + + +if __name__ == "__main__": + import sys + + sys.exit(pytest.main(sys.argv)) From ecd587b525be589790d9522c6601e269fb7f348f Mon Sep 17 00:00:00 2001 From: Manolis Papadakis Date: Wed, 9 Oct 2024 18:50:05 -0700 Subject: [PATCH 330/462] Bump Legate to fix #405 (#408) * Bump Legate to fix #405 Fixes #405 * Update versions.json * Bump for more fixes * Bisect to find offending Legate commit * Update versions.json * Update versions.json * Update versions.json * Pull revert of legate#1267 * Update to a nightly build package SHA --- cmake/versions.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmake/versions.json b/cmake/versions.json index 87c4760d6d..328c7abd49 100644 --- a/cmake/versions.json +++ b/cmake/versions.json @@ -9,7 +9,7 @@ "git_url" : "git@github.com:nv-legate/legate.core.internal.git", "git_shallow": false, "always_download": false, - "git_tag" : "426b1b0480b020cf7e1366e27be1f0284d2b8af3" + "git_tag" : "ab9de06afd6179fb6bef1679481a6a327b653f9e" } } } From 9d9d2df3e07af5087f433648e0bc92215638859f Mon Sep 17 00:00:00 2001 From: Andrei Schaffer <37386037+aschaffer@users.noreply.github.com> Date: Thu, 10 Oct 2024 09:31:04 -0500 Subject: [PATCH 331/462] Fix for gamma and normalized axes in nan-quantiles. (#424) * Fix for gamma and normalized axes in nan-quantiles. * Removed commented-out code. --- cunumeric/_module/stats_order.py | 15 +++++++++------ tests/integration/test_median.py | 18 ------------------ 2 files changed, 9 insertions(+), 24 deletions(-) diff --git a/cunumeric/_module/stats_order.py b/cunumeric/_module/stats_order.py index 1867217b9b..7c70424761 100644 --- a/cunumeric/_module/stats_order.py +++ b/cunumeric/_module/stats_order.py @@ -663,7 +663,7 @@ def nanquantile_impl( q_arr: npt.NDArray[Any], non_nan_counts: ndarray, axis: int | None, - axes_set: Iterable[int], + axes_set: Sequence[int], original_shape: tuple[int, ...], method: Callable[[float, int], tuple[float | None, int]], keepdims: bool, @@ -721,6 +721,7 @@ def nanquantile_impl( # TODO(aschaffer): Vectorize this operation, see # github.com/nv-legate/cunumeric/pull/1121#discussion_r1484731763 + gamma = None for aindex, n in np.ndenumerate(non_nan_counts): # TODO (2024-08): `n` should be an integral type, but wasn't: n = int(n) @@ -855,26 +856,28 @@ def nanquantile( """ real_axis: int | None - axes_set: Iterable[int] = [] + axes_set: Sequence[int] = () original_shape = a.shape if axis is not None and isinstance(axis, Iterable): + nrm_axis = normalize_axis_tuple(axis, a.ndim) if len(axis) == 1: - real_axis = axis[0] + real_axis = nrm_axis[0] a_rr = a else: - (real_axis, a_rr) = _reshuffle_reshape(a, axis) + (real_axis, a_rr) = _reshuffle_reshape(a, nrm_axis) # What happens with multiple axes and overwrite_input = True ? # It seems overwrite_input is reset to False; # But `overwrite_input` doesn't matter for the NaN version of this # function # overwrite_input = False - axes_set = axis + axes_set = nrm_axis else: real_axis = axis a_rr = a if real_axis is not None: - axes_set = [real_axis] + axes_set = normalize_axis_tuple(real_axis, a.ndim) + real_axis = axes_set[0] # ndarray of non-NaNs: # diff --git a/tests/integration/test_median.py b/tests/integration/test_median.py index f394710af3..1e81896f7a 100644 --- a/tests/integration/test_median.py +++ b/tests/integration/test_median.py @@ -155,24 +155,6 @@ def test_out(self): with pytest.raises(expected_exc, match=msg): num.nanmedian(array, out=out_a) - def test_nanmedian_empty_array(self): - expected_exc = UnboundLocalError - # Numpy returns Warning instead of the Error - # msg="invalid value encountered in scalar divide" - # with pytest.raises(expected_exc, match=msg): - # np.median([]) - msg = "local variable 'gamma' referenced before assignment" - with pytest.raises(expected_exc, match=msg): - num.nanmedian([]) - - def test_nanmedian_all_nan_values(self): - # Numpy returns Warning instead of the Error and doesn't produce - # any output - expected_exc = UnboundLocalError - msg = "local variable 'gamma' referenced before assignment" - with pytest.raises(expected_exc, match=msg): - num.nanmedian([np.nan, np.nan, np.nan]) - def test_median_overwrite_input(self): arr = num.array([7, 1, 5, 3]) median = num.median(arr, overwrite_input=True) From 9f5e7cd76398e965cd653bfb5b7d6158bad8ce49 Mon Sep 17 00:00:00 2001 From: Bryan Van de Ven Date: Thu, 10 Oct 2024 12:29:05 -0700 Subject: [PATCH 332/462] change default value for astype copy arg (#428) * change default value for astype copy arg * Update docs/cunumeric/source/faqs.rst Co-authored-by: Manolis Papadakis --------- Co-authored-by: Manolis Papadakis --- cunumeric/_array/array.py | 14 +++++++++----- docs/cunumeric/source/faqs.rst | 9 ++++++--- tests/integration/test_astype.py | 8 ++++++++ 3 files changed, 23 insertions(+), 8 deletions(-) diff --git a/cunumeric/_array/array.py b/cunumeric/_array/array.py index c79677e50e..86aa73e46d 100644 --- a/cunumeric/_array/array.py +++ b/cunumeric/_array/array.py @@ -1638,7 +1638,7 @@ def astype( order: OrderType = "C", casting: CastingKind = "unsafe", subok: bool = True, - copy: bool = True, + copy: bool = False, ) -> ndarray: """a.astype(dtype, order='C', casting='unsafe', subok=True, copy=True) @@ -1674,10 +1674,14 @@ def astype( array. copy : bool, optional - By default, astype always returns a newly allocated array. If this - is set to false, and the `dtype`, `order`, and `subok` - requirements are satisfied, the input array is returned instead - of a copy. + By default, astype does not returns a newly allocated array. If + this is set to True, a copy is made and returned, instead of the + input array. + + Notes + ----- + The default value for the ``copy`` argument is the opposite of Numpy. + Avoiding copies reduces memory pressure. Returns ------- diff --git a/docs/cunumeric/source/faqs.rst b/docs/cunumeric/source/faqs.rst index cbe91448b3..553bc16710 100644 --- a/docs/cunumeric/source/faqs.rst +++ b/docs/cunumeric/source/faqs.rst @@ -121,9 +121,12 @@ Why are the results different from NumPy? While a majority of the APIs will give the same result as NumPy, some APIs might be implemented differently from that of NumPy which might lead to differences in results. One such example is, :ref:`reshape`, which returns a -copy of the array in cuNumeric but returns a view in NumPy. Such differences -in implementation are noted in the documentation of the cuNumeric APIs, please -review them before opening an issue on `cuNumeric issue tracker `_. +copy of the array in cuNumeric but returns a view in NumPy. Another example +is :ref:`astype` which does *not* return a copy by default, where NumPy does. + +Such differences in implementation are noted in the documentation of the +cuNumeric APIs, please review them before opening an issue on the +`cuNumeric issue tracker `_. Why doesn’t Legate use my GPU? ------------------------------ diff --git a/tests/integration/test_astype.py b/tests/integration/test_astype.py index 8bc96de12e..fe5aa3068f 100644 --- a/tests/integration/test_astype.py +++ b/tests/integration/test_astype.py @@ -12,6 +12,7 @@ # See the License for the specific language governing permissions and # limitations under the License. # +import inspect import numpy as np import pytest @@ -124,6 +125,13 @@ def test_complex_negative(src_dtype): assert np.array_equal(out_num, out_np) +def test_default_copy_value(): + # it was decided to explicitly diverge from the numpy default value in + # https://github.com/nv-legate/cunumeric.internal/issues/421 + a = num.array([]) + assert inspect.signature(a.astype).parameters["copy"].default is False + + if __name__ == "__main__": import sys From f4313452f7d1fb59cab9ce10d2eb047dc3f6fde1 Mon Sep 17 00:00:00 2001 From: amberhassaan Date: Mon, 14 Oct 2024 16:05:10 -0400 Subject: [PATCH 333/462] check kernel launch failures. Fix missing include in util.inl (#434) nv-legate/legate.core#956 revealed that kernel launch failure was not being checked and silently ignored, leading to wrong output. Here we make a partial attempt to check for kernel launch failures that can be queried immediately. Also fixed a missing include. --- src/cunumeric/cuda_help.h | 5 ++++- tests/cpp/integration/util.inl | 2 ++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/src/cunumeric/cuda_help.h b/src/cunumeric/cuda_help.h index 3d42e53906..1be68ffdb4 100644 --- a/src/cunumeric/cuda_help.h +++ b/src/cunumeric/cuda_help.h @@ -211,7 +211,10 @@ __host__ inline void check_nccl(ncclResult_t error, const char* file, int line) CUNUMERIC_CHECK_CUDA(cudaPeekAtLastError()); \ } while (false) #else -#define CUNUMERIC_CHECK_CUDA_STREAM(stream) static_cast(stream) +#define CUNUMERIC_CHECK_CUDA_STREAM(stream) \ + do { \ + CUNUMERIC_CHECK_CUDA(cudaPeekAtLastError()); \ + } while (false) #endif #ifndef MAX diff --git a/tests/cpp/integration/util.inl b/tests/cpp/integration/util.inl index 9d92393e36..4a18896284 100644 --- a/tests/cpp/integration/util.inl +++ b/tests/cpp/integration/util.inl @@ -14,6 +14,8 @@ * */ +#include + namespace { template From df1344ba7751d2386550b390bd23e893145b2a95 Mon Sep 17 00:00:00 2001 From: Jacob Faibussowitsch Date: Mon, 14 Oct 2024 17:30:42 -0400 Subject: [PATCH 334/462] Revert back to 2-level legate module (#431) * Revert back to 2-level legate module * Bump legate hash --- cmake/versions.json | 2 +- cunumeric/_array/array.py | 6 ++-- cunumeric/_array/thunk.py | 2 +- cunumeric/_module/logic_comparison.py | 2 +- cunumeric/_module/math_complex.py | 7 ++--- cunumeric/_thunk/_sort.py | 2 +- cunumeric/_thunk/deferred.py | 8 +++--- cunumeric/_thunk/eager.py | 2 +- cunumeric/_thunk/thunk.py | 2 +- cunumeric/_ufunc/ufunc.py | 2 +- cunumeric/_utils/array.py | 2 +- cunumeric/_utils/coverage.py | 4 +-- cunumeric/_utils/linalg.py | 2 +- cunumeric/config.py | 2 +- cunumeric/linalg/_cholesky.py | 4 +-- cunumeric/linalg/_qr.py | 4 +-- cunumeric/linalg/_solve.py | 6 ++-- cunumeric/linalg/_svd.py | 4 +-- cunumeric/runtime.py | 6 ++-- tests/integration/test_advanced_indexing.py | 2 +- tests/integration/test_amax_amin.py | 2 +- tests/integration/test_angle.py | 32 ++++++++++----------- tests/integration/test_arg_reduce.py | 2 +- tests/integration/test_array.py | 2 +- tests/integration/test_atleast_nd.py | 2 +- tests/integration/test_bits.py | 2 +- tests/integration/test_broadcast.py | 2 +- tests/integration/test_clip.py | 2 +- tests/integration/test_compress.py | 2 +- tests/integration/test_corner_quantiles.py | 2 +- tests/integration/test_diag_indices.py | 2 +- tests/integration/test_dot.py | 2 +- tests/integration/test_einsum.py | 2 +- tests/integration/test_fill_diagonal.py | 2 +- tests/integration/test_flip.py | 2 +- tests/integration/test_gradient.py | 2 +- tests/integration/test_index_routines.py | 2 +- tests/integration/test_indices.py | 2 +- tests/integration/test_inner.py | 2 +- tests/integration/test_intra_array_copy.py | 2 +- tests/integration/test_item.py | 2 +- tests/integration/test_itemset.py | 2 +- tests/integration/test_logical.py | 2 +- tests/integration/test_logical_reduction.py | 2 +- tests/integration/test_matmul.py | 2 +- tests/integration/test_matrix_power.py | 2 +- tests/integration/test_median.py | 2 +- tests/integration/test_moveaxis.py | 2 +- tests/integration/test_nan_reduction.py | 2 +- tests/integration/test_nanarg_reduction.py | 2 +- tests/integration/test_ndim.py | 2 +- tests/integration/test_negaxes_quantiles.py | 2 +- tests/integration/test_norm.py | 2 +- tests/integration/test_put.py | 2 +- tests/integration/test_put_along_axis.py | 2 +- tests/integration/test_putmask.py | 2 +- tests/integration/test_repeat.py | 2 +- tests/integration/test_round.py | 2 +- tests/integration/test_searchsorted.py | 2 +- tests/integration/test_setflags.py | 2 +- tests/integration/test_singleton_access.py | 2 +- tests/integration/test_take.py | 2 +- tests/integration/test_take_along_axis.py | 2 +- tests/integration/test_tensordot.py | 2 +- tests/integration/test_trace.py | 2 +- tests/integration/test_unique.py | 2 +- tests/integration/test_unravel_index.py | 2 +- tests/integration/utils/contractions.py | 4 +-- tests/integration/utils/generators.py | 2 +- 69 files changed, 99 insertions(+), 102 deletions(-) diff --git a/cmake/versions.json b/cmake/versions.json index 328c7abd49..d25dcc6d1e 100644 --- a/cmake/versions.json +++ b/cmake/versions.json @@ -9,7 +9,7 @@ "git_url" : "git@github.com:nv-legate/legate.core.internal.git", "git_shallow": false, "always_download": false, - "git_tag" : "ab9de06afd6179fb6bef1679481a6a327b653f9e" + "git_tag" : "9039cf6f540416c57721cffec696cef2f965b240" } } } diff --git a/cunumeric/_array/array.py b/cunumeric/_array/array.py index 86aa73e46d..0527c3ba80 100644 --- a/cunumeric/_array/array.py +++ b/cunumeric/_array/array.py @@ -19,10 +19,10 @@ from functools import reduce from typing import TYPE_CHECKING, Any, Sequence, cast -import legate.types as ty +import legate.core.types as ty import numpy as np -from legate import Field, LogicalArray, Scalar -from legate.utils import OrderedSet +from legate.core import Field, LogicalArray, Scalar +from legate.core.utils import OrderedSet from .. import _ufunc from .._utils import is_np2 diff --git a/cunumeric/_array/thunk.py b/cunumeric/_array/thunk.py index 6247042003..391c91ba8d 100644 --- a/cunumeric/_array/thunk.py +++ b/cunumeric/_array/thunk.py @@ -17,7 +17,7 @@ from typing import TYPE_CHECKING, Any import numpy as np -from legate import Scalar +from legate.core import Scalar from .._utils import is_np2 from ..config import ( diff --git a/cunumeric/_module/logic_comparison.py b/cunumeric/_module/logic_comparison.py index d9856408f2..dad4782027 100644 --- a/cunumeric/_module/logic_comparison.py +++ b/cunumeric/_module/logic_comparison.py @@ -17,7 +17,7 @@ from typing import TYPE_CHECKING import numpy as np -from legate import Scalar, types as ty +from legate.core import Scalar, types as ty from .._array.thunk import perform_binary_reduction from .._array.util import add_boilerplate, find_common_type diff --git a/cunumeric/_module/math_complex.py b/cunumeric/_module/math_complex.py index a1a4590b15..3d05580ad2 100644 --- a/cunumeric/_module/math_complex.py +++ b/cunumeric/_module/math_complex.py @@ -16,13 +16,12 @@ from typing import TYPE_CHECKING -from legate import Scalar +from legate.core import Scalar from .._array.thunk import perform_unary_op from .._array.util import add_boilerplate -from ..config import UnaryOpCode - from .._utils.array import to_core_type +from ..config import UnaryOpCode if TYPE_CHECKING: from .._array.array import ndarray @@ -119,6 +118,6 @@ def angle(z: ndarray, deg: bool = False) -> ndarray: """ if z is None: - raise TypeError ("can't compute 'angle' for None") + raise TypeError("can't compute 'angle' for None") extra_args = (Scalar(deg),) return perform_unary_op(UnaryOpCode.ANGLE, z, extra_args=extra_args) diff --git a/cunumeric/_thunk/_sort.py b/cunumeric/_thunk/_sort.py index 8455582ff1..b97a8eba0b 100644 --- a/cunumeric/_thunk/_sort.py +++ b/cunumeric/_thunk/_sort.py @@ -16,7 +16,7 @@ from typing import TYPE_CHECKING, cast -from legate import get_legate_runtime, types as ty +from legate.core import get_legate_runtime, types as ty from .._utils import is_np2 from ..config import CuNumericOpCode diff --git a/cunumeric/_thunk/deferred.py b/cunumeric/_thunk/deferred.py index fd80d4a17e..df44f57065 100644 --- a/cunumeric/_thunk/deferred.py +++ b/cunumeric/_thunk/deferred.py @@ -31,9 +31,9 @@ cast, ) -import legate.types as ty +import legate.core.types as ty import numpy as np -from legate import ( +from legate.core import ( Annotation, LogicalStore, ReductionOpKind, @@ -47,7 +47,7 @@ scale, ) from legate.settings import settings as legate_settings -from legate.utils import OrderedSet +from legate.core.utils import OrderedSet from .._utils import is_np2 from .._utils.array import ( @@ -82,7 +82,7 @@ if TYPE_CHECKING: import numpy.typing as npt - from legate import LogicalStorePartition + from legate.core import LogicalStorePartition from ..config import BitGeneratorType, FFTDirection, FFTType, WindowOpCode from ..types import ( diff --git a/cunumeric/_thunk/eager.py b/cunumeric/_thunk/eager.py index 1ce2449671..fda0079790 100644 --- a/cunumeric/_thunk/eager.py +++ b/cunumeric/_thunk/eager.py @@ -17,7 +17,7 @@ from typing import TYPE_CHECKING, Any, Callable, Iterable, Sequence, cast import numpy as np -from legate import Scalar +from legate.core import Scalar from .._utils import is_np2 from .._utils.array import is_advanced_indexing diff --git a/cunumeric/_thunk/thunk.py b/cunumeric/_thunk/thunk.py index 7cd1ee981d..cb96e5d1dc 100644 --- a/cunumeric/_thunk/thunk.py +++ b/cunumeric/_thunk/thunk.py @@ -23,7 +23,7 @@ if TYPE_CHECKING: import numpy as np import numpy.typing as npt - from legate import Scalar + from legate.core import Scalar from ..config import ( BinaryOpCode, diff --git a/cunumeric/_ufunc/ufunc.py b/cunumeric/_ufunc/ufunc.py index 41aeb4c726..74b4f8badf 100644 --- a/cunumeric/_ufunc/ufunc.py +++ b/cunumeric/_ufunc/ufunc.py @@ -17,7 +17,7 @@ from typing import TYPE_CHECKING, Any, Callable, Sequence, TypeAlias import numpy as np -from legate.utils import OrderedSet +from legate.core.utils import OrderedSet from .._array.thunk import perform_unary_reduction from .._array.util import ( diff --git a/cunumeric/_utils/array.py b/cunumeric/_utils/array.py index 0578c0a21e..6e35735d30 100644 --- a/cunumeric/_utils/array.py +++ b/cunumeric/_utils/array.py @@ -17,7 +17,7 @@ from functools import reduce from typing import Any -import legate.types as ty +import legate.core.types as ty import numpy as np from ..types import NdShape diff --git a/cunumeric/_utils/coverage.py b/cunumeric/_utils/coverage.py index 790e1ee872..3b87bb89f6 100644 --- a/cunumeric/_utils/coverage.py +++ b/cunumeric/_utils/coverage.py @@ -26,8 +26,8 @@ ) from typing import Any, Callable, Container, Iterable, Mapping, Protocol, cast -from legate import track_provenance -from legate.utils import OrderedSet +from legate.core import track_provenance +from legate.core.utils import OrderedSet from ..runtime import runtime from ..settings import settings diff --git a/cunumeric/_utils/linalg.py b/cunumeric/_utils/linalg.py index 1f8aedbce6..5aa0b292c4 100644 --- a/cunumeric/_utils/linalg.py +++ b/cunumeric/_utils/linalg.py @@ -17,7 +17,7 @@ from string import ascii_lowercase, ascii_uppercase from typing import Sequence -from legate.utils import OrderedSet +from legate.core.utils import OrderedSet Modes = tuple[list[str], list[str], list[str]] diff --git a/cunumeric/config.py b/cunumeric/config.py index 844ea008ba..310ca9f416 100644 --- a/cunumeric/config.py +++ b/cunumeric/config.py @@ -322,7 +322,7 @@ def __init__(self, name: str) -> None: self.shared_object = cast(_CunumericSharedLib, shared_lib) def register(self) -> None: - from legate import get_legate_runtime + from legate.core import get_legate_runtime # We need to make sure that the runtime is started get_legate_runtime() diff --git a/cunumeric/linalg/_cholesky.py b/cunumeric/linalg/_cholesky.py index a55e31a0a0..3775951dcd 100644 --- a/cunumeric/linalg/_cholesky.py +++ b/cunumeric/linalg/_cholesky.py @@ -16,7 +16,7 @@ from typing import TYPE_CHECKING -from legate import ( +from legate.core import ( broadcast, constant, dimension, @@ -32,7 +32,7 @@ legate_runtime = get_legate_runtime() if TYPE_CHECKING: - from legate import Library, LogicalStore, LogicalStorePartition + from legate.core import Library, LogicalStore, LogicalStorePartition from .._thunk.deferred import DeferredArray from ..runtime import Runtime diff --git a/cunumeric/linalg/_qr.py b/cunumeric/linalg/_qr.py index 02abea2817..aa2c38e1cb 100644 --- a/cunumeric/linalg/_qr.py +++ b/cunumeric/linalg/_qr.py @@ -16,14 +16,14 @@ from typing import TYPE_CHECKING -from legate import get_legate_runtime +from legate.core import get_legate_runtime from cunumeric.config import CuNumericOpCode from ._exception import LinAlgError if TYPE_CHECKING: - from legate import Library, LogicalStore + from legate.core import Library, LogicalStore from .._thunk.deferred import DeferredArray diff --git a/cunumeric/linalg/_solve.py b/cunumeric/linalg/_solve.py index 2eefb2479e..7681444ac3 100644 --- a/cunumeric/linalg/_solve.py +++ b/cunumeric/linalg/_solve.py @@ -16,8 +16,8 @@ from typing import TYPE_CHECKING, cast -import legate.types as ty -from legate import broadcast, get_legate_runtime +import legate.core.types as ty +from legate.core import broadcast, get_legate_runtime from ..config import CuNumericOpCode from ..runtime import runtime @@ -25,7 +25,7 @@ from ._exception import LinAlgError if TYPE_CHECKING: - from legate import Library, LogicalStore + from legate.core import Library, LogicalStore from .._thunk.deferred import DeferredArray diff --git a/cunumeric/linalg/_svd.py b/cunumeric/linalg/_svd.py index cdf214eba5..9579f06849 100644 --- a/cunumeric/linalg/_svd.py +++ b/cunumeric/linalg/_svd.py @@ -16,14 +16,14 @@ from typing import TYPE_CHECKING -from legate import get_legate_runtime +from legate.core import get_legate_runtime from cunumeric.config import CuNumericOpCode from ._exception import LinAlgError if TYPE_CHECKING: - from legate import Library, LogicalStore + from legate.core import Library, LogicalStore from .._thunk.deferred import DeferredArray diff --git a/cunumeric/runtime.py b/cunumeric/runtime.py index 662a3e2ce1..85cc7d9548 100644 --- a/cunumeric/runtime.py +++ b/cunumeric/runtime.py @@ -19,9 +19,9 @@ from functools import lru_cache, reduce from typing import TYPE_CHECKING, Any, Sequence, TypeGuard -import legate.types as ty +import legate.core.types as ty import numpy as np -from legate import LEGATE_MAX_DIM, Scalar, TaskTarget, get_legate_runtime +from legate.core import LEGATE_MAX_DIM, Scalar, TaskTarget, get_legate_runtime from legate.settings import settings as legate_settings from ._utils.array import calculate_volume, is_supported_dtype, to_core_type @@ -41,7 +41,7 @@ if TYPE_CHECKING: import numpy.typing as npt - from legate import AutoTask, ManualTask + from legate.core import AutoTask, ManualTask from ._array.array import ndarray from ._thunk.deferred import DeferredArray diff --git a/tests/integration/test_advanced_indexing.py b/tests/integration/test_advanced_indexing.py index f4f47e5d1f..90751500de 100644 --- a/tests/integration/test_advanced_indexing.py +++ b/tests/integration/test_advanced_indexing.py @@ -15,7 +15,7 @@ import numpy as np import pytest -from legate import LEGATE_MAX_DIM +from legate.core import LEGATE_MAX_DIM from utils.generators import mk_seq_array import cunumeric as num diff --git a/tests/integration/test_amax_amin.py b/tests/integration/test_amax_amin.py index 9b7a3939e9..ee85b2e2b0 100755 --- a/tests/integration/test_amax_amin.py +++ b/tests/integration/test_amax_amin.py @@ -15,7 +15,7 @@ import numpy as np import pytest -from legate import LEGATE_MAX_DIM +from legate.core import LEGATE_MAX_DIM import cunumeric as num diff --git a/tests/integration/test_angle.py b/tests/integration/test_angle.py index 2ec56c386d..16a81bc5ce 100644 --- a/tests/integration/test_angle.py +++ b/tests/integration/test_angle.py @@ -15,7 +15,7 @@ import numpy as np import pytest -from legate import LEGATE_MAX_DIM +from legate.core import LEGATE_MAX_DIM from utils.generators import mk_seq_array import cunumeric as num @@ -24,48 +24,45 @@ class TestAngleErrors: def test_none_array(self): expected_exc = AttributeError - msg="'int' object has no attribute 'arctan2'" + msg = "'int' object has no attribute 'arctan2'" with pytest.raises(expected_exc, match=msg): np.angle(None) expected_exc = TypeError - msg="can't compute 'angle' for None" + msg = "can't compute 'angle' for None" with pytest.raises(expected_exc, match=msg): num.angle(None) + class TestAngle: def test_empty_array(self): res_np = np.angle([]) res_num = num.angle([]) assert np.array_equal(res_np, res_num) - - + def test_zero_input(self): res_np = np.angle(0) res_num = num.angle(0) assert np.array_equal(res_np, res_num) - - + def test_pure_real_and_imaginary(self): # Testing pure real and pure imaginary numbers assert np.array_equal(num.angle(5), np.angle(5)) assert np.array_equal(num.angle(-5), np.angle(-5)) assert np.array_equal(num.angle(5j), np.angle(5j)) assert np.array_equal(num.angle(-5j), np.angle(-5j)) - - + @pytest.mark.parametrize("ndim", range(1, LEGATE_MAX_DIM + 1)) @pytest.mark.parametrize("in_type", (int, float, complex)) @pytest.mark.parametrize("deg", (False, True)) - def test_basic(self,ndim, in_type, deg): + def test_basic(self, ndim, in_type, deg): shape = (5,) * ndim np_arr = mk_seq_array(np, shape).astype(in_type) num_arr = mk_seq_array(num, shape).astype(in_type) - + res_np = np.angle(np_arr, deg) res_num = num.angle(num_arr, deg) assert np.array_equal(res_num, res_np) - - + @pytest.mark.parametrize( "array", ( @@ -82,13 +79,14 @@ def test_complex_arrays(self, array, deg): res_np = np.angle(array, deg) res_num = num.angle(array, deg) assert np.array_equal(res_num, res_np) - - + def test_edge_cases(self): # Testing angles with large and small numbers assert np.array_equal(np.angle(1e10 + 1e10j), num.angle(1e10 + 1e10j)) - assert np.array_equal(np.angle(1e-10 + 1e-10j), num.angle(1e-10 + 1e-10j)) - + assert np.array_equal( + np.angle(1e-10 + 1e-10j), num.angle(1e-10 + 1e-10j) + ) + def test_nan(self): # Testing behavior with NaN and Inf values assert np.array_equal( diff --git a/tests/integration/test_arg_reduce.py b/tests/integration/test_arg_reduce.py index 1ee1e5dcc4..17c491fbec 100644 --- a/tests/integration/test_arg_reduce.py +++ b/tests/integration/test_arg_reduce.py @@ -15,7 +15,7 @@ import numpy as np import pytest -from legate import LEGATE_MAX_DIM +from legate.core import LEGATE_MAX_DIM from utils.utils import AxisError import cunumeric as num diff --git a/tests/integration/test_array.py b/tests/integration/test_array.py index 0189eb2e3c..43854a42df 100755 --- a/tests/integration/test_array.py +++ b/tests/integration/test_array.py @@ -15,7 +15,7 @@ import numpy as np import pytest -from legate import LEGATE_MAX_DIM +from legate.core import LEGATE_MAX_DIM import cunumeric as num diff --git a/tests/integration/test_atleast_nd.py b/tests/integration/test_atleast_nd.py index 32a2cfb5bb..cac98ad722 100644 --- a/tests/integration/test_atleast_nd.py +++ b/tests/integration/test_atleast_nd.py @@ -15,7 +15,7 @@ import numpy as np import pytest -from legate import LEGATE_MAX_DIM +from legate.core import LEGATE_MAX_DIM from utils.utils import check_module_function import cunumeric as num diff --git a/tests/integration/test_bits.py b/tests/integration/test_bits.py index 50c46bebf3..40882706ee 100644 --- a/tests/integration/test_bits.py +++ b/tests/integration/test_bits.py @@ -16,7 +16,7 @@ import numpy as np import pytest -from legate import LEGATE_MAX_DIM +from legate.core import LEGATE_MAX_DIM import cunumeric as num diff --git a/tests/integration/test_broadcast.py b/tests/integration/test_broadcast.py index 0e3403c91b..a051054aaf 100644 --- a/tests/integration/test_broadcast.py +++ b/tests/integration/test_broadcast.py @@ -15,7 +15,7 @@ import numpy as np import pytest -from legate import LEGATE_MAX_DIM +from legate.core import LEGATE_MAX_DIM import cunumeric as num diff --git a/tests/integration/test_clip.py b/tests/integration/test_clip.py index 96682c28ce..f583398dbb 100644 --- a/tests/integration/test_clip.py +++ b/tests/integration/test_clip.py @@ -15,7 +15,7 @@ import numpy as np import pytest -from legate import LEGATE_MAX_DIM +from legate.core import LEGATE_MAX_DIM from utils.generators import mk_seq_array import cunumeric as num diff --git a/tests/integration/test_compress.py b/tests/integration/test_compress.py index 1510a5cc01..af466f7bf1 100644 --- a/tests/integration/test_compress.py +++ b/tests/integration/test_compress.py @@ -15,7 +15,7 @@ import numpy as np import pytest -from legate import LEGATE_MAX_DIM +from legate.core import LEGATE_MAX_DIM from utils.generators import mk_seq_array import cunumeric as num diff --git a/tests/integration/test_corner_quantiles.py b/tests/integration/test_corner_quantiles.py index 370fbd8100..152bc5d84b 100644 --- a/tests/integration/test_corner_quantiles.py +++ b/tests/integration/test_corner_quantiles.py @@ -16,7 +16,7 @@ import numpy as np import pytest -from legate import LEGATE_MAX_DIM +from legate.core import LEGATE_MAX_DIM from utils.comparisons import allclose import cunumeric as num diff --git a/tests/integration/test_diag_indices.py b/tests/integration/test_diag_indices.py index e97d237f7d..03659d44bb 100644 --- a/tests/integration/test_diag_indices.py +++ b/tests/integration/test_diag_indices.py @@ -15,7 +15,7 @@ import numpy as np import pytest -from legate import LEGATE_MAX_DIM +from legate.core import LEGATE_MAX_DIM import cunumeric as num diff --git a/tests/integration/test_dot.py b/tests/integration/test_dot.py index 8630916432..e3b775145e 100644 --- a/tests/integration/test_dot.py +++ b/tests/integration/test_dot.py @@ -14,7 +14,7 @@ # import numpy as np import pytest -from legate import LEGATE_MAX_DIM +from legate.core import LEGATE_MAX_DIM from utils.contractions import check_default from utils.generators import mk_0to1_array diff --git a/tests/integration/test_einsum.py b/tests/integration/test_einsum.py index 52797fa36d..f79033b1f7 100644 --- a/tests/integration/test_einsum.py +++ b/tests/integration/test_einsum.py @@ -18,7 +18,7 @@ import numpy as np import pytest -from legate.utils import OrderedSet +from legate.core.utils import OrderedSet from utils.comparisons import allclose from utils.generators import mk_0to1_array, permutes_to diff --git a/tests/integration/test_fill_diagonal.py b/tests/integration/test_fill_diagonal.py index 8587f2b185..7482fc4128 100644 --- a/tests/integration/test_fill_diagonal.py +++ b/tests/integration/test_fill_diagonal.py @@ -15,7 +15,7 @@ import numpy as np import pytest -from legate import LEGATE_MAX_DIM +from legate.core import LEGATE_MAX_DIM from utils.generators import mk_seq_array import cunumeric as num diff --git a/tests/integration/test_flip.py b/tests/integration/test_flip.py index 2e4bf9ea7f..97e57ef26b 100644 --- a/tests/integration/test_flip.py +++ b/tests/integration/test_flip.py @@ -16,7 +16,7 @@ import numpy as np import pytest -from legate import LEGATE_MAX_DIM +from legate.core import LEGATE_MAX_DIM from utils.utils import AxisError import cunumeric as num diff --git a/tests/integration/test_gradient.py b/tests/integration/test_gradient.py index 5a6a9e4a08..d5b4804fb9 100644 --- a/tests/integration/test_gradient.py +++ b/tests/integration/test_gradient.py @@ -16,7 +16,7 @@ import numpy as np import pytest -from legate import LEGATE_MAX_DIM +from legate.core import LEGATE_MAX_DIM import cunumeric as cn diff --git a/tests/integration/test_index_routines.py b/tests/integration/test_index_routines.py index 354116ee21..405a6a2d5a 100644 --- a/tests/integration/test_index_routines.py +++ b/tests/integration/test_index_routines.py @@ -17,7 +17,7 @@ import numpy as np import pytest -from legate import LEGATE_MAX_DIM +from legate.core import LEGATE_MAX_DIM from utils.generators import mk_seq_array from utils.utils import AxisError diff --git a/tests/integration/test_indices.py b/tests/integration/test_indices.py index c5969e5433..5a8346bb1a 100644 --- a/tests/integration/test_indices.py +++ b/tests/integration/test_indices.py @@ -15,7 +15,7 @@ import numpy as np import pytest -from legate import LEGATE_MAX_DIM +from legate.core import LEGATE_MAX_DIM import cunumeric as num diff --git a/tests/integration/test_inner.py b/tests/integration/test_inner.py index c18cc39488..d1f27a12f9 100644 --- a/tests/integration/test_inner.py +++ b/tests/integration/test_inner.py @@ -14,7 +14,7 @@ # import pytest -from legate import LEGATE_MAX_DIM +from legate.core import LEGATE_MAX_DIM from utils.contractions import check_default from utils.generators import mk_0to1_array diff --git a/tests/integration/test_intra_array_copy.py b/tests/integration/test_intra_array_copy.py index 6076d3b537..03b1976358 100644 --- a/tests/integration/test_intra_array_copy.py +++ b/tests/integration/test_intra_array_copy.py @@ -15,7 +15,7 @@ import numpy as np import pytest -from legate import LEGATE_MAX_DIM +from legate.core import LEGATE_MAX_DIM from utils.generators import mk_0to1_array import cunumeric as num diff --git a/tests/integration/test_item.py b/tests/integration/test_item.py index 2ef7beab75..a80bc070b9 100644 --- a/tests/integration/test_item.py +++ b/tests/integration/test_item.py @@ -14,7 +14,7 @@ # import numpy as np import pytest -from legate import LEGATE_MAX_DIM +from legate.core import LEGATE_MAX_DIM from utils.generators import generate_item import cunumeric as num diff --git a/tests/integration/test_itemset.py b/tests/integration/test_itemset.py index 859a658850..283d976a8d 100644 --- a/tests/integration/test_itemset.py +++ b/tests/integration/test_itemset.py @@ -14,7 +14,7 @@ # import numpy as np import pytest -from legate import LEGATE_MAX_DIM +from legate.core import LEGATE_MAX_DIM from utils.generators import generate_item import cunumeric as num diff --git a/tests/integration/test_logical.py b/tests/integration/test_logical.py index 3b769e4c68..dac2de22ae 100644 --- a/tests/integration/test_logical.py +++ b/tests/integration/test_logical.py @@ -15,7 +15,7 @@ import numpy as np import pytest -from legate import LEGATE_MAX_DIM +from legate.core import LEGATE_MAX_DIM import cunumeric as num diff --git a/tests/integration/test_logical_reduction.py b/tests/integration/test_logical_reduction.py index ef9c425ec9..21b6216dd0 100644 --- a/tests/integration/test_logical_reduction.py +++ b/tests/integration/test_logical_reduction.py @@ -15,7 +15,7 @@ import numpy as np import pytest -from legate import LEGATE_MAX_DIM +from legate.core import LEGATE_MAX_DIM from utils.generators import mk_seq_array import cunumeric as num diff --git a/tests/integration/test_matmul.py b/tests/integration/test_matmul.py index f80c50d966..0952e4d605 100644 --- a/tests/integration/test_matmul.py +++ b/tests/integration/test_matmul.py @@ -15,7 +15,7 @@ import numpy as np import pytest -from legate import LEGATE_MAX_DIM +from legate.core import LEGATE_MAX_DIM from utils.comparisons import allclose from utils.contractions import ( check_default, diff --git a/tests/integration/test_matrix_power.py b/tests/integration/test_matrix_power.py index bb54e4997b..58508897c7 100644 --- a/tests/integration/test_matrix_power.py +++ b/tests/integration/test_matrix_power.py @@ -15,7 +15,7 @@ import numpy as np import pytest -from legate import LEGATE_MAX_DIM +from legate.core import LEGATE_MAX_DIM from utils.comparisons import allclose from utils.generators import mk_0to1_array diff --git a/tests/integration/test_median.py b/tests/integration/test_median.py index 1e81896f7a..37dec719c9 100644 --- a/tests/integration/test_median.py +++ b/tests/integration/test_median.py @@ -15,7 +15,7 @@ import numpy as np import pytest -from legate import LEGATE_MAX_DIM +from legate.core import LEGATE_MAX_DIM from utils.generators import mk_seq_array import cunumeric as num diff --git a/tests/integration/test_moveaxis.py b/tests/integration/test_moveaxis.py index 001c7dce26..f8a58f7991 100644 --- a/tests/integration/test_moveaxis.py +++ b/tests/integration/test_moveaxis.py @@ -15,7 +15,7 @@ import numpy as np import pytest -from legate import LEGATE_MAX_DIM +from legate.core import LEGATE_MAX_DIM from utils.generators import mk_0to1_array from utils.utils import AxisError diff --git a/tests/integration/test_nan_reduction.py b/tests/integration/test_nan_reduction.py index ba5175a31d..b97b6d012c 100644 --- a/tests/integration/test_nan_reduction.py +++ b/tests/integration/test_nan_reduction.py @@ -18,7 +18,7 @@ import numpy as np import pytest -from legate import LEGATE_MAX_DIM +from legate.core import LEGATE_MAX_DIM import cunumeric as num from cunumeric.settings import settings diff --git a/tests/integration/test_nanarg_reduction.py b/tests/integration/test_nanarg_reduction.py index 1ccc9f07bb..c5f2d66d35 100644 --- a/tests/integration/test_nanarg_reduction.py +++ b/tests/integration/test_nanarg_reduction.py @@ -18,7 +18,7 @@ import numpy as np import pytest -from legate import LEGATE_MAX_DIM +from legate.core import LEGATE_MAX_DIM import cunumeric as num from cunumeric.settings import settings diff --git a/tests/integration/test_ndim.py b/tests/integration/test_ndim.py index 6383a6445f..d520928d3f 100644 --- a/tests/integration/test_ndim.py +++ b/tests/integration/test_ndim.py @@ -15,7 +15,7 @@ import numpy as np import pytest -from legate import LEGATE_MAX_DIM +from legate.core import LEGATE_MAX_DIM import cunumeric as num diff --git a/tests/integration/test_negaxes_quantiles.py b/tests/integration/test_negaxes_quantiles.py index 721e2f95a0..40cec5a7af 100644 --- a/tests/integration/test_negaxes_quantiles.py +++ b/tests/integration/test_negaxes_quantiles.py @@ -16,7 +16,7 @@ import numpy as np import pytest -from legate import LEGATE_MAX_DIM +from legate.core import LEGATE_MAX_DIM from utils.comparisons import allclose import cunumeric as num diff --git a/tests/integration/test_norm.py b/tests/integration/test_norm.py index a5ff0effe7..51f9b4a5ad 100644 --- a/tests/integration/test_norm.py +++ b/tests/integration/test_norm.py @@ -15,7 +15,7 @@ import numpy as np import pytest -from legate import LEGATE_MAX_DIM +from legate.core import LEGATE_MAX_DIM from utils.comparisons import allclose from utils.generators import mk_0to1_array diff --git a/tests/integration/test_put.py b/tests/integration/test_put.py index c88ffa7a9d..ef8c593b9a 100644 --- a/tests/integration/test_put.py +++ b/tests/integration/test_put.py @@ -15,7 +15,7 @@ import numpy as np import pytest -from legate import LEGATE_MAX_DIM +from legate.core import LEGATE_MAX_DIM from utils.generators import mk_seq_array import cunumeric as num diff --git a/tests/integration/test_put_along_axis.py b/tests/integration/test_put_along_axis.py index bf1ca6bff6..89cb3725a5 100644 --- a/tests/integration/test_put_along_axis.py +++ b/tests/integration/test_put_along_axis.py @@ -15,7 +15,7 @@ import numpy as np import pytest -from legate import LEGATE_MAX_DIM +from legate.core import LEGATE_MAX_DIM from utils.generators import ( broadcasts_to, broadcasts_to_along_axis, diff --git a/tests/integration/test_putmask.py b/tests/integration/test_putmask.py index 8c81350adc..fa40f7fb09 100644 --- a/tests/integration/test_putmask.py +++ b/tests/integration/test_putmask.py @@ -15,7 +15,7 @@ import numpy as np import pytest -from legate import LEGATE_MAX_DIM +from legate.core import LEGATE_MAX_DIM from utils.generators import mk_0to1_array, mk_seq_array import cunumeric as num diff --git a/tests/integration/test_repeat.py b/tests/integration/test_repeat.py index ae8c8046af..0df92ff172 100644 --- a/tests/integration/test_repeat.py +++ b/tests/integration/test_repeat.py @@ -14,7 +14,7 @@ # import numpy as np import pytest -from legate import LEGATE_MAX_DIM +from legate.core import LEGATE_MAX_DIM from utils.generators import mk_seq_array from utils.utils import AxisError diff --git a/tests/integration/test_round.py b/tests/integration/test_round.py index 90b5baef25..3ea87bab43 100644 --- a/tests/integration/test_round.py +++ b/tests/integration/test_round.py @@ -15,7 +15,7 @@ import numpy as np import pytest -from legate import LEGATE_MAX_DIM +from legate.core import LEGATE_MAX_DIM from utils.generators import mk_0to1_array import cunumeric as num diff --git a/tests/integration/test_searchsorted.py b/tests/integration/test_searchsorted.py index 281aba206d..8ef77f944b 100644 --- a/tests/integration/test_searchsorted.py +++ b/tests/integration/test_searchsorted.py @@ -15,7 +15,7 @@ import numpy as np import pytest -from legate import LEGATE_MAX_DIM +from legate.core import LEGATE_MAX_DIM import cunumeric as num diff --git a/tests/integration/test_setflags.py b/tests/integration/test_setflags.py index c85900e481..a3bc81699b 100644 --- a/tests/integration/test_setflags.py +++ b/tests/integration/test_setflags.py @@ -14,7 +14,7 @@ # import numpy as np import pytest -from legate import LEGATE_MAX_DIM +from legate.core import LEGATE_MAX_DIM import cunumeric as num diff --git a/tests/integration/test_singleton_access.py b/tests/integration/test_singleton_access.py index 5cadbdfdbe..8d146a35b1 100644 --- a/tests/integration/test_singleton_access.py +++ b/tests/integration/test_singleton_access.py @@ -15,7 +15,7 @@ import numpy as np import pytest -from legate import LEGATE_MAX_DIM +from legate.core import LEGATE_MAX_DIM from utils.generators import mk_0to1_array, scalar_gen import cunumeric as num diff --git a/tests/integration/test_take.py b/tests/integration/test_take.py index 40013c1823..07e34567aa 100644 --- a/tests/integration/test_take.py +++ b/tests/integration/test_take.py @@ -15,7 +15,7 @@ import numpy as np import pytest -from legate import LEGATE_MAX_DIM +from legate.core import LEGATE_MAX_DIM from utils.generators import mk_seq_array import cunumeric as num diff --git a/tests/integration/test_take_along_axis.py b/tests/integration/test_take_along_axis.py index c7e289d662..a2c930e100 100644 --- a/tests/integration/test_take_along_axis.py +++ b/tests/integration/test_take_along_axis.py @@ -15,7 +15,7 @@ import numpy as np import pytest -from legate import LEGATE_MAX_DIM +from legate.core import LEGATE_MAX_DIM from utils.generators import broadcasts_to_along_axis, mk_seq_array import cunumeric as num diff --git a/tests/integration/test_tensordot.py b/tests/integration/test_tensordot.py index e58b2e446b..0091a46277 100644 --- a/tests/integration/test_tensordot.py +++ b/tests/integration/test_tensordot.py @@ -14,7 +14,7 @@ # import pytest -from legate import LEGATE_MAX_DIM +from legate.core import LEGATE_MAX_DIM from utils.contractions import check_default from utils.generators import mk_0to1_array diff --git a/tests/integration/test_trace.py b/tests/integration/test_trace.py index 13514eb972..4423f83a97 100644 --- a/tests/integration/test_trace.py +++ b/tests/integration/test_trace.py @@ -17,7 +17,7 @@ import numpy as np import pytest -from legate import LEGATE_MAX_DIM +from legate.core import LEGATE_MAX_DIM from utils.generators import mk_seq_array import cunumeric as num diff --git a/tests/integration/test_unique.py b/tests/integration/test_unique.py index 4bae6675e0..a6c7013308 100644 --- a/tests/integration/test_unique.py +++ b/tests/integration/test_unique.py @@ -15,7 +15,7 @@ import numpy as np import pytest -from legate import LEGATE_MAX_DIM +from legate.core import LEGATE_MAX_DIM import cunumeric as num diff --git a/tests/integration/test_unravel_index.py b/tests/integration/test_unravel_index.py index a812746759..1fa15aa37d 100644 --- a/tests/integration/test_unravel_index.py +++ b/tests/integration/test_unravel_index.py @@ -15,7 +15,7 @@ import numpy as np import pytest -from legate import LEGATE_MAX_DIM +from legate.core import LEGATE_MAX_DIM from utils.generators import mk_seq_array import cunumeric as num diff --git a/tests/integration/utils/contractions.py b/tests/integration/utils/contractions.py index 2ebd7f553e..641020b4f3 100644 --- a/tests/integration/utils/contractions.py +++ b/tests/integration/utils/contractions.py @@ -14,8 +14,8 @@ # import numpy as np -from legate import LEGATE_MAX_DIM -from legate.utils import OrderedSet +from legate.core import LEGATE_MAX_DIM +from legate.core.utils import OrderedSet import cunumeric as num diff --git a/tests/integration/utils/generators.py b/tests/integration/utils/generators.py index fdc12bf4d7..f96a3cf1c1 100644 --- a/tests/integration/utils/generators.py +++ b/tests/integration/utils/generators.py @@ -16,7 +16,7 @@ from itertools import permutations, product import numpy as np -from legate import LEGATE_MAX_DIM +from legate.core import LEGATE_MAX_DIM def scalar_gen(lib, val): From 887e81004554adb3f362502a4e9d6d4ea4ee013d Mon Sep 17 00:00:00 2001 From: Marcin Zalewski Date: Wed, 16 Oct 2024 17:01:52 -0700 Subject: [PATCH 335/462] Update Legate SHA (#436) --- cmake/versions.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmake/versions.json b/cmake/versions.json index d25dcc6d1e..7c9fc29f83 100644 --- a/cmake/versions.json +++ b/cmake/versions.json @@ -9,7 +9,7 @@ "git_url" : "git@github.com:nv-legate/legate.core.internal.git", "git_shallow": false, "always_download": false, - "git_tag" : "9039cf6f540416c57721cffec696cef2f965b240" + "git_tag" : "70a3d4dba42c20c1eed0874b00c32a661133f92d" } } } From b5b88d7ec72bebf0461e52d9bd71905527a1cb1d Mon Sep 17 00:00:00 2001 From: Wei Wu Date: Mon, 21 Oct 2024 20:59:41 -0700 Subject: [PATCH 336/462] Add stencil_hint function to bloat instances (#444) * add stencil_hint * format * move stencil_hint into thunk * Update cunumeric/_thunk/eager.py Co-authored-by: Manolis Papadakis * Update cunumeric/_array/array.py Co-authored-by: Manolis Papadakis --------- Co-authored-by: Manolis Papadakis --- cunumeric/_array/array.py | 43 ++++++++++++++++++++++++++++++++++++ cunumeric/_thunk/deferred.py | 11 ++++++++- cunumeric/_thunk/eager.py | 8 +++++++ cunumeric/_thunk/thunk.py | 8 +++++++ 4 files changed, 69 insertions(+), 1 deletion(-) diff --git a/cunumeric/_array/array.py b/cunumeric/_array/array.py index 0527c3ba80..27a7af9c36 100644 --- a/cunumeric/_array/array.py +++ b/cunumeric/_array/array.py @@ -3952,3 +3952,46 @@ def _wrap(self, new_len: int) -> ndarray: ) out._thunk._wrap(src=self._thunk, new_len=new_len) return out + + def stencil_hint( + self, + low_offsets: tuple[int, ...], + high_offsets: tuple[int, ...], + ) -> None: + """ + Inform cuNumeric that this array will be used in a stencil computation + in the following code. + + This allows cuNumeric to allocate space for the "ghost" elements ahead + of time, rather than discover the full extent of accesses incrementally, + and thus avoid intermediate copies. + + For example, let's say we have a 1-D array A of size 10 and we want to + partition A across two GPUs. By default, A would be partitioned equally + and each GPU gets an instance of size 5 (GPU0 gets elements 0-4, and + GPU1 gets 5-9 inclusive). Suppose we use A in the stencil computation + `B = A[:9] + A[1:]`. The runtime would now need to adjust the + partitioning such that GPU0 has elements 0-5 and GPU1 has elements 4-9 + inclusive. Since the original instance on GPU0 does not cover index 5, + cuNumeric needs to allocate a full new instance that covers 0-5, leading + to an extra copy. In this case, if the code calls + `A.stencil_hint([1], [1])` to pre-allocate instances that contain the + extra elements before it uses A, the extra copies can be avoided. + + Parameters + ---------- + low_offsets: tuple[int] + Stencil offsets towards the negative direction. + high_offsets: tuple[int] + Stencil offsets towards the positive direction. + + Notes + ----- + This function currently does not behave as expected in the case where + multiple CPU/OpenMP processors use the same system memory. + + Availability + -------- + Multiple CPUs, Multiple GPUs + """ + self._thunk.stencil_hint(low_offsets, high_offsets) diff --git a/cunumeric/_thunk/deferred.py b/cunumeric/_thunk/deferred.py index df44f57065..0a0ae7fcbd 100644 --- a/cunumeric/_thunk/deferred.py +++ b/cunumeric/_thunk/deferred.py @@ -46,8 +46,8 @@ get_legate_runtime, scale, ) -from legate.settings import settings as legate_settings from legate.core.utils import OrderedSet +from legate.settings import settings as legate_settings from .._utils import is_np2 from .._utils.array import ( @@ -3722,3 +3722,12 @@ def histogram(self, src: Any, bins: Any, weights: Any) -> None: task.add_constraint(align(p_src, p_weight)) task.execute() + + def stencil_hint( + self, + low_offsets: tuple[int, ...], + high_offsets: tuple[int, ...], + ) -> None: + legate_runtime.prefetch_bloated_instances( + self.base, low_offsets, high_offsets, False + ) diff --git a/cunumeric/_thunk/eager.py b/cunumeric/_thunk/eager.py index fda0079790..868fb97bf9 100644 --- a/cunumeric/_thunk/eager.py +++ b/cunumeric/_thunk/eager.py @@ -1807,3 +1807,11 @@ def histogram(self, rhs: Any, bins: Any, weights: Any) -> None: cast(EagerArray, bins).array, weights=cast(EagerArray, weights).array, ) + + def stencil_hint( + self, + low_offsets: tuple[int, ...], + high_offsets: tuple[int, ...], + ) -> None: + if self.deferred is not None: + self.deferred.stencil_hint(low_offsets, high_offsets) diff --git a/cunumeric/_thunk/thunk.py b/cunumeric/_thunk/thunk.py index cb96e5d1dc..5dbe09264c 100644 --- a/cunumeric/_thunk/thunk.py +++ b/cunumeric/_thunk/thunk.py @@ -748,3 +748,11 @@ def _wrap(self, src: Any, new_len: int) -> None: @abstractmethod def histogram(self, src: Any, bins: Any, weights: Any) -> None: ... + + @abstractmethod + def stencil_hint( + self, + low_offsets: tuple[int, ...], + high_offsets: tuple[int, ...], + ) -> None: + ... From 5bf526281d94a08fe402eeabbf71d53686b7f639 Mon Sep 17 00:00:00 2001 From: Sandeep Datta <128171450+sandeepd-nv@users.noreply.github.com> Date: Wed, 23 Oct 2024 01:19:28 +0530 Subject: [PATCH 337/462] Build kernels for all-major SM versions. (#448) * Increase logging verbosity. * Change CMAKE_CUDA_ARCHITECTURES to all-major from RAPIDS. --- conda/conda-build/build.sh | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/conda/conda-build/build.sh b/conda/conda-build/build.sh index d90f9d2f0c..c78cbbcca9 100644 --- a/conda/conda-build/build.sh +++ b/conda/conda-build/build.sh @@ -14,6 +14,7 @@ CMAKE_ARGS+=" -DBUILD_SHARED_LIBS=ON -DBUILD_MARCH=${BUILD_MARCH} -DCMAKE_BUILD_TYPE=Release +-DCMAKE_VERBOSE_MAKEFILE=ON -DCMAKE_BUILD_PARALLEL_LEVEL=${JOBS:-$(nproc --ignore=1)}" # We rely on an environment variable to determine if we need to build cpu-only bits @@ -21,7 +22,7 @@ if [ -z "$CPU_ONLY" ]; then # cutensor, relying on the conda cutensor package CMAKE_ARGS+=" -Dcutensor_DIR=$PREFIX --DCMAKE_CUDA_ARCHITECTURES=RAPIDS" +-DCMAKE_CUDA_ARCHITECTURES=all-major" else # When we build without cuda, we need to provide the location of curand CMAKE_ARGS+=" @@ -40,7 +41,7 @@ CUDAFLAGS="-isystem ${PREFIX}/include -L${PREFIX}/lib" export CUDAFLAGS cmake -S . -B build ${CMAKE_ARGS} -DCMAKE_BUILD_PARALLEL_LEVEL=$CPU_COUNT -cmake --build build -j$CPU_COUNT +cmake --build build -j$CPU_COUNT --verbose cmake --install build CMAKE_ARGS=" From da1bb939d41262b224ba1c819c705048ac19643e Mon Sep 17 00:00:00 2001 From: Jacob Faibussowitsch Date: Wed, 23 Oct 2024 11:41:48 -0500 Subject: [PATCH 338/462] Fix legate CCCL incompatibility again (#440) * Fix legate CCCL incompatibility again * Fixes for MacOS --- cmake/versions.json | 2 +- conda/conda-build/meta.yaml | 1 + src/cunumeric/random/bitgenerator.cc | 9 +++------ src/cunumeric/random/randutil/generator_host.cc | 2 +- src/cunumeric/unary/unary_op_util.h | 2 +- 5 files changed, 7 insertions(+), 9 deletions(-) diff --git a/cmake/versions.json b/cmake/versions.json index 7c9fc29f83..3a8b3c6dbb 100644 --- a/cmake/versions.json +++ b/cmake/versions.json @@ -9,7 +9,7 @@ "git_url" : "git@github.com:nv-legate/legate.core.internal.git", "git_shallow": false, "always_download": false, - "git_tag" : "70a3d4dba42c20c1eed0874b00c32a661133f92d" + "git_tag" : "429168e033ea4891c3251bb7ee63b91667b65b6e" } } } diff --git a/conda/conda-build/meta.yaml b/conda/conda-build/meta.yaml index 9ca9968fe7..d7db54b8bc 100644 --- a/conda/conda-build/meta.yaml +++ b/conda/conda-build/meta.yaml @@ -123,6 +123,7 @@ requirements: - libcusolver-dev - libcufft-dev - libcurand-dev + - libcufile-dev - cuda-version ={{ cuda_version }} {% else %} - legate >={{ legate_version }} =*_cpu* diff --git a/src/cunumeric/random/bitgenerator.cc b/src/cunumeric/random/bitgenerator.cc index 3da591d0ee..6c3728b85c 100644 --- a/src/cunumeric/random/bitgenerator.cc +++ b/src/cunumeric/random/bitgenerator.cc @@ -28,7 +28,10 @@ #include "cunumeric/random/rnd_types.h" +#if !LEGATE_DEFINED(CUNUMERIC_USE_STL_RANDOM_ENGINE) #include "cunumeric/random/curand_help.h" +#endif + #include "cunumeric/random/randutil/randutil.h" #include "cunumeric/random/bitgenerator_curand.inl" @@ -50,12 +53,6 @@ void randutil_check_status(rnd_status_t error, std::string_view file, int line) assert(false); } } -// for the STL path: delegate to randutil_check_status(...): -// -void randutil_check_curand(curandStatus_t error, std::string_view file, int line) -{ - randutil_check_status(error, file, line); -} #else void randutil_check_curand(curandStatus_t error, std::string_view file, int line) { diff --git a/src/cunumeric/random/randutil/generator_host.cc b/src/cunumeric/random/randutil/generator_host.cc index 25e6d65741..0752149222 100644 --- a/src/cunumeric/random/randutil/generator_host.cc +++ b/src/cunumeric/random/randutil/generator_host.cc @@ -26,7 +26,7 @@ #include "generator_create.inl" #include "cunumeric/random/rnd_aliases.h" -#if !LEGATE_DEFINED(LEGATE_USE_CUDA) +#if LEGATE_DEFINED(CUNUMERIC_CURAND_FOR_CPU_BUILD) // the host code of cuRAND try to extern these variables out of nowhere, // so we need to define them somewhere. const dim3 blockDim{}; diff --git a/src/cunumeric/unary/unary_op_util.h b/src/cunumeric/unary/unary_op_util.h index 37c0f1419f..4bcd6ec8f6 100644 --- a/src/cunumeric/unary/unary_op_util.h +++ b/src/cunumeric/unary/unary_op_util.h @@ -741,7 +741,7 @@ struct UnaryOp { return std::isinf(x.imag()) || std::isinf(x.real()); } - __CUDA_HD__ bool operator()(const __half& x) const { return isinf(x); } + __CUDA_HD__ bool operator()(const __half& x) const { return isinf(static_cast(x)); } }; template From 8ad81f72a792805fd8d7c3f86ada131cededf160 Mon Sep 17 00:00:00 2001 From: Manolis Papadakis Date: Thu, 24 Oct 2024 23:52:34 -0700 Subject: [PATCH 339/462] Fix cunumeric#1076 (#423) * Fix cunumeric#1076 * Addres review comments --- cunumeric/_array/array.py | 24 +--------- cunumeric/_array/thunk.py | 20 ++++++--- tests/integration/test_nan_reduction.py | 59 ++++++++++++++++--------- tests/integration/test_reduction.py | 16 ++++++- 4 files changed, 70 insertions(+), 49 deletions(-) diff --git a/cunumeric/_array/array.py b/cunumeric/_array/array.py index 27a7af9c36..5a6406057b 100644 --- a/cunumeric/_array/array.py +++ b/cunumeric/_array/array.py @@ -3197,19 +3197,9 @@ def prod( Multiple GPUs, Multiple CPUs """ - if self.dtype.type == bool: - temp = ndarray( - shape=self.shape, - dtype=np.dtype(np.int32), - inputs=(self,), - ) - temp._thunk.convert(self._thunk) - self_array = temp - else: - self_array = self return perform_unary_reduction( UnaryRedCode.PROD, - self_array, + self, axis=axis, dtype=dtype, out=out, @@ -3567,19 +3557,9 @@ def sum( Multiple GPUs, Multiple CPUs """ - if self.dtype.type == bool: - temp = ndarray( - shape=self.shape, - dtype=np.dtype(np.int32), - inputs=(self,), - ) - temp._thunk.convert(self._thunk) - self_array = temp - else: - self_array = self return perform_unary_reduction( UnaryRedCode.SUM, - self_array, + self, axis=axis, dtype=dtype, out=out, diff --git a/cunumeric/_array/thunk.py b/cunumeric/_array/thunk.py index 391c91ba8d..d7f351f869 100644 --- a/cunumeric/_array/thunk.py +++ b/cunumeric/_array/thunk.py @@ -129,6 +129,14 @@ def perform_unary_op( return out +def _need_upcast_for_reduction(op: UnaryRedCode, dtype: np.dtype[Any]) -> bool: + return op in (UnaryRedCode.SUM, UnaryRedCode.PROD) and dtype.kind in ( + "b", + "i", + "u", + ) + + def perform_unary_reduction( op: UnaryRedCode, src: ndarray, @@ -150,16 +158,18 @@ def perform_unary_reduction( assert dtype is None dtype = src.dtype else: - # If 'dtype' exists, that determines both the accumulation dtype - # and the output dtype if dtype is not None: - res_dtype = dtype + # If 'dtype' exists, that determines both the accumulation dtype + # and the output dtype + pass elif out is not None: dtype = out.dtype - res_dtype = out.dtype + elif _need_upcast_for_reduction(op, src.dtype): + # upcast to conserve precision + dtype = np.dtype(np.uint64 if src.dtype.kind == "u" else np.int64) else: dtype = src.dtype - res_dtype = src.dtype + res_dtype = dtype # TODO: Need to require initial to be given when the array is empty # or a where mask is given. diff --git a/tests/integration/test_nan_reduction.py b/tests/integration/test_nan_reduction.py index b97b6d012c..f0a4509974 100644 --- a/tests/integration/test_nan_reduction.py +++ b/tests/integration/test_nan_reduction.py @@ -19,6 +19,7 @@ import numpy as np import pytest from legate.core import LEGATE_MAX_DIM +from utils.comparisons import allclose import cunumeric as num from cunumeric.settings import settings @@ -29,6 +30,12 @@ NDIMS = range(LEGATE_MAX_DIM + 1) +DTYPE = ["l", "L", "f", "d", "h", "i", "H", "I", "?", "b", "B"] + + +def to_dtype(s): + return str(np.dtype(s)) + class TestNanReductions: """ @@ -58,7 +65,7 @@ def test_basic_nan_sum_prod(self, func_name, ndim, keepdims): out_np = func_np(in_np, keepdims=keepdims) out_num = func_num(in_num, keepdims=keepdims) - assert np.allclose(out_num, out_np, rtol=1e-4) + assert allclose(out_num, out_np, rtol=1e-4) @pytest.mark.parametrize("func_name", ("nanmin", "nanmax")) @pytest.mark.parametrize("ndim", range(1, LEGATE_MAX_DIM + 1)) @@ -109,7 +116,7 @@ def test_out(self, func_name, ndim): out_np = np.empty(_shape) func_np(in_np, out=out_np, axis=axis, keepdims=True) - assert np.allclose(out_num, out_np, rtol=1e-4) + assert allclose(out_num, out_np, rtol=1e-4) @pytest.mark.parametrize("ndim", range(1, LEGATE_MAX_DIM + 1)) @pytest.mark.parametrize("dtype", (np.float32, np.float64)) @@ -142,7 +149,7 @@ def test_complex_dtype_nansum(self, ndim, dtype, keepdims): out_num = num.nansum(in_num, keepdims=keepdims) out_np = np.nansum(in_np, keepdims=keepdims) - assert np.allclose(out_num, out_np, rtol=1e-4) + assert allclose(out_num, out_np, rtol=1e-4) @pytest.mark.parametrize("ndim", range(1, LEGATE_MAX_DIM + 1)) @pytest.mark.parametrize("keepdims", [True, False]) @@ -174,7 +181,7 @@ def test_complex_dtype_nanprod(self, ndim, keepdims): out_num = num.nanprod(in_num, keepdims=keepdims) out_np = np.nanprod(in_np, keepdims=keepdims) - assert np.allclose(out_num, out_np, rtol=1e-4) + assert allclose(out_num, out_np, rtol=1e-4) @pytest.mark.parametrize("func_name", ("nanmin", "nanmax")) def test_slice_nan_numpy_compat(self, func_name): @@ -281,17 +288,27 @@ def test_all_nans_nanprod(self, ndim): assert out_num == 1.0 - def test_dtype_nanprod(self) -> None: - in_np = np.arange(1, 10, step=1, dtype=np.int64) + @pytest.mark.parametrize("dtype", DTYPE, ids=to_dtype) + def test_dtype_nanprod(self, dtype) -> None: + in_np = np.arange(1, 10) + if dtype == bool: + in_np %= 2 + in_np = in_np.astype(dtype) out_np = np.nanprod(in_np) - in_num = num.arange(1, 10, 1, dtype=np.int64) + in_num = num.asarray(in_np) out_num = num.nanprod(in_num) - assert out_np == out_num - - def test_dtype_nansum(self) -> None: - arr_num = num.array([1, 2, 3]) - arr_np = np.array([1, 2, 3]) - assert num.nansum(arr_num) == np.nansum(arr_np) + assert allclose(out_np, out_num) + + @pytest.mark.parametrize("dtype", DTYPE, ids=to_dtype) + def test_dtype_nansum(self, dtype) -> None: + in_np = np.arange(1, 10) + if dtype == bool: + in_np %= 2 + in_np = in_np.astype(dtype) + out_np = np.nansum(in_np) + in_num = num.asarray(in_np) + out_num = num.nansum(in_num) + assert allclose(out_np, out_num) @pytest.mark.parametrize("ndim", range(1, LEGATE_MAX_DIM + 1)) def test_all_nans_nansum(self, ndim): @@ -307,11 +324,11 @@ def test_where(self): arr = [[1, np.nan, 3], [2, np.nan, 4]] out_np = np.nansum(arr, where=[False, True, True]) out_num = num.nansum(arr, where=[False, True, True]) - assert np.allclose(out_np, out_num) + assert allclose(out_np, out_num) out_np = np.nanprod(arr, where=[False, True, True]) out_num = num.nanprod(arr, where=[False, True, True]) - assert np.allclose(out_np, out_num) + assert allclose(out_np, out_num) out_np = np.nanmax( arr, where=[[False, True, True], [False, False, True]], initial=-1 @@ -319,7 +336,7 @@ def test_where(self): out_num = num.nanmax( arr, where=[[False, True, True], [False, False, True]], initial=-1 ) - assert np.allclose(out_np, out_num) + assert allclose(out_np, out_num) out_np = np.nanmin( arr, where=[[False, True, True], [False, True, True]], initial=10 @@ -327,24 +344,24 @@ def test_where(self): out_num = num.nanmin( arr, where=[[False, True, True], [False, True, True]], initial=10 ) - assert np.allclose(out_np, out_num) + assert allclose(out_np, out_num) # where is a boolean out_np = np.nansum(arr, where=True) out_num = num.nansum(arr, where=True) - assert np.allclose(out_np, out_num) + assert allclose(out_np, out_num) out_np = np.nanprod(arr, where=False) out_num = num.nanprod(arr, where=False) - assert np.allclose(out_np, out_num) + assert allclose(out_np, out_num) out_np = np.nanmax(arr, where=True, initial=-1) out_num = num.nanmax(arr, where=True, initial=-1) - assert np.allclose(out_np, out_num) + assert allclose(out_np, out_num) out_np = np.nanmin(arr, where=True, initial=10) out_num = num.nanmin(arr, where=True, initial=10) - assert np.allclose(out_np, out_num) + assert allclose(out_np, out_num) class TestCornerCases: diff --git a/tests/integration/test_reduction.py b/tests/integration/test_reduction.py index 2039f815df..ea341ec607 100644 --- a/tests/integration/test_reduction.py +++ b/tests/integration/test_reduction.py @@ -60,8 +60,9 @@ ARR = ([], [[]], [[], []], np.inf, -10.3, 0, 200, 5 + 8j) DTYPE = ["l", "L", "f", "d"] +BOOL_INT_DTYPE = ["h", "i", "H", "I", "?", "b", "B"] COMPLEX_TYPE = ["F", "D"] -NEGATIVE_DTYPE = ["h", "i", "H", "I", "e", "?", "b", "B"] +NEGATIVE_DTYPE = ["e"] def to_dtype(s): @@ -191,6 +192,19 @@ def test_dtype(self, dtype): out_num = num.sum(arr_num) assert allclose(out_np, out_num) + @pytest.mark.parametrize("dtype", BOOL_INT_DTYPE, ids=to_dtype) + @pytest.mark.parametrize("op", ["sum", "prod", "max"]) + def test_bool_int_dtype(self, dtype, op): + size = (5, 5, 5) + arr = np.random.randint(10, size=size) + if dtype == bool: + arr %= 2 + arr_np = np.array(arr, dtype=dtype) + arr_num = num.array(arr_np) + out_np = getattr(np, op)(arr_np) + out_num = getattr(num, op)(arr_num) + assert allclose(out_np, out_num) + @pytest.mark.parametrize("dtype", COMPLEX_TYPE, ids=to_dtype) def test_dtype_complex(self, dtype): arr = num.random.rand(5, 5) * 10 + num.random.rand(5, 5) * 10 * 1.0j From 2e69ee1e2cc7d5185cd9296f4e339be37ba9cf05 Mon Sep 17 00:00:00 2001 From: Irina Demeshko Date: Fri, 25 Oct 2024 11:32:48 -0700 Subject: [PATCH 340/462] changing default for arch (#437) --- install.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/install.py b/install.py index 5f52484754..f58e19ec05 100755 --- a/install.py +++ b/install.py @@ -594,7 +594,7 @@ def driver(): dest="arch", action="store", required=False, - default="native", + default="all-major", help="Specify the target GPU architecture.", ) parser.add_argument( From 1c2b85e34c3183010c776e886d0b85d5e81be38b Mon Sep 17 00:00:00 2001 From: Manolis Papadakis Date: Fri, 25 Oct 2024 12:32:45 -0700 Subject: [PATCH 341/462] Use concurrent task barrier in sort task (#413) * Use concurrent task barrier in sort task * Temporarily enable nightly build and use custom CI to upload to slac channel * Use hash from the concurrent task barrier PR in legate * Add barrier after the AllReduce as well Appears to be necessary to work around hangs, according to Seshu from SLAC * Revert "Temporarily enable nightly build and use custom CI to upload to slac channel" This reverts commit dc23b68bf65e6e4034fc3029e6ac0370d9488204. * Update Legate version to top of concurrent_task_barrier branch * Fix ARM CI --------- Co-authored-by: Marcin Zalewski Co-authored-by: Jacob Faibussowitsch --- cmake/versions.json | 2 +- continuous_integration/scripts/test | 2 +- docs/cunumeric/Makefile | 4 ++-- src/cunumeric/sort/sort.cc | 3 ++- src/cunumeric/sort/sort.cu | 9 +++++++-- src/cunumeric/sort/sort_omp.cc | 3 ++- src/cunumeric/sort/sort_template.inl | 7 ++++--- 7 files changed, 19 insertions(+), 11 deletions(-) diff --git a/cmake/versions.json b/cmake/versions.json index 3a8b3c6dbb..1240dad06f 100644 --- a/cmake/versions.json +++ b/cmake/versions.json @@ -9,7 +9,7 @@ "git_url" : "git@github.com:nv-legate/legate.core.internal.git", "git_shallow": false, "always_download": false, - "git_tag" : "429168e033ea4891c3251bb7ee63b91667b65b6e" + "git_tag" : "32137a65cf40c56db1db9f76bb508ade81da000a" } } } diff --git a/continuous_integration/scripts/test b/continuous_integration/scripts/test index 4057110ed0..60bf105959 100755 --- a/continuous_integration/scripts/test +++ b/continuous_integration/scripts/test @@ -70,7 +70,7 @@ test_cunumeric() { echo "Running Unit tests..." shift; setup_unit_env; - pytest tests/unit + LEGATE_AUTO_CONFIG=0 pytest tests/unit ;; *) echo "Invalid command: $1" diff --git a/docs/cunumeric/Makefile b/docs/cunumeric/Makefile index a92b77d594..9909e05092 100644 --- a/docs/cunumeric/Makefile +++ b/docs/cunumeric/Makefile @@ -41,7 +41,7 @@ clean: html: @start=$$(date +%s) \ - ; $(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) -j $(PARALLEL_BUILD) \ + ; LEGATE_AUTO_CONFIG=0 $(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) -j $(PARALLEL_BUILD) \ && mkdir -p build/html/docs \ && cp -r ../figures build/html/docs/ \ && cp switcher.json build/html/ \ @@ -54,4 +54,4 @@ linkcheck: html && [ -f "$(BUILDDIR)/output.txt" ] && cat "$(BUILDDIR)/output.txt" docserve: - python -m http.server -d build/html/ \ No newline at end of file + python -m http.server -d build/html/ diff --git a/src/cunumeric/sort/sort.cc b/src/cunumeric/sort/sort.cc index fc79137bc9..985fdaa1ac 100644 --- a/src/cunumeric/sort/sort.cc +++ b/src/cunumeric/sort/sort.cc @@ -32,7 +32,8 @@ template struct SortImplBody { using VAL = type_of; - void operator()(const legate::PhysicalStore& input_array, + void operator()(TaskContext& context, + const legate::PhysicalStore& input_array, legate::PhysicalStore& output_array, const Pitches& pitches, const Rect& rect, diff --git a/src/cunumeric/sort/sort.cu b/src/cunumeric/sort/sort.cu index 794e5fbde3..0c23890802 100644 --- a/src/cunumeric/sort/sort.cu +++ b/src/cunumeric/sort/sort.cu @@ -1225,6 +1225,7 @@ void rebalance_data(SegmentMergePiece& merge_buffer, template void sample_sort_nccl_nd( + TaskContext& context, SortPiece> local_sorted, legate::PhysicalStore& output_array_unbound, // only for unbound usage when !rebalance void* output_ptr, @@ -1261,8 +1262,10 @@ void sample_sort_nccl_nd( size_t worker_count = (segment_size_l > 0 ? 1 : 0); CUNUMERIC_CHECK_CUDA(cudaMemcpyAsync( worker_count_d.ptr(0), &worker_count, sizeof(int32_t), cudaMemcpyHostToDevice, stream)); + context.concurrent_task_barrier(); CHECK_NCCL(ncclAllReduce( worker_count_d.ptr(0), worker_count_d.ptr(0), 1, ncclInt32, ncclSum, *comm, stream)); + context.concurrent_task_barrier(); CUNUMERIC_CHECK_CUDA(cudaMemcpyAsync( &worker_count, worker_count_d.ptr(0), sizeof(int32_t), cudaMemcpyDeviceToHost, stream)); CUNUMERIC_CHECK_CUDA(cudaStreamSynchronize(stream)); @@ -1707,7 +1710,8 @@ template struct SortImplBody { using VAL = type_of; - void operator()(const legate::PhysicalStore& input_array, + void operator()(TaskContext& context, + const legate::PhysicalStore& input_array, legate::PhysicalStore& output_array, const Pitches& pitches, const Rect& rect, @@ -1821,7 +1825,8 @@ struct SortImplBody { } } - sample_sort_nccl_nd(local_sorted, + sample_sort_nccl_nd(context, + local_sorted, output_array, output_ptr, local_rank, diff --git a/src/cunumeric/sort/sort_omp.cc b/src/cunumeric/sort/sort_omp.cc index 59086b8a22..373ecfe095 100644 --- a/src/cunumeric/sort/sort_omp.cc +++ b/src/cunumeric/sort/sort_omp.cc @@ -33,7 +33,8 @@ template struct SortImplBody { using VAL = type_of; - void operator()(const legate::PhysicalStore& input_array, + void operator()(TaskContext& context, + const legate::PhysicalStore& input_array, legate::PhysicalStore& output_array, const Pitches& pitches, const Rect& rect, diff --git a/src/cunumeric/sort/sort_template.inl b/src/cunumeric/sort/sort_template.inl index 70bbc5a827..7963310152 100644 --- a/src/cunumeric/sort/sort_template.inl +++ b/src/cunumeric/sort/sort_template.inl @@ -44,7 +44,7 @@ static int get_rank(Domain domain, DomainPoint index_point) template struct SortImpl { template - void operator()(SortArgs& args, std::vector comms) const + void operator()(SortArgs& args, TaskContext& context, std::vector comms) const { auto rect = args.input.shape(); @@ -71,7 +71,8 @@ struct SortImpl { return; } - SortImplBody()(args.input, + SortImplBody()(context, + args.input, args.output, pitches, rect, @@ -108,7 +109,7 @@ static void sort_template(TaskContext& context) num_ranks, num_sort_ranks}; double_dispatch( - args.input.dim(), args.input.code(), SortImpl{}, args, context.communicators()); + args.input.dim(), args.input.code(), SortImpl{}, args, context, context.communicators()); } } // namespace cunumeric From 23c22e64b639b2805c363d0456a098f002c1777e Mon Sep 17 00:00:00 2001 From: Mona Han <129724851+mona-nv@users.noreply.github.com> Date: Fri, 1 Nov 2024 13:30:29 +0800 Subject: [PATCH 342/462] =?UTF-8?q?Update=20API=20interface=20for=20amax?= =?UTF-8?q?=20and=20amin=20and=20add=20tests=20for=20them.=20Also=20f?= =?UTF-8?q?=E2=80=A6=20(#102)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Update API interface for amax and amin and add tests for them. Also fix some build warnings. * Remove build warning * Update code based on review comments * Address review comments --------- Signed-off-by: monah --- src/cunumeric/ndarray.cc | 126 +++++----- src/cunumeric/ndarray.h | 38 +-- src/cunumeric/operators.cc | 46 +++- src/cunumeric/operators.h | 26 +- tests/cpp/integration/test_amax.cc | 339 ++++++++++++++++++++++++++ tests/cpp/integration/test_amin.cc | 339 ++++++++++++++++++++++++++ tests/cpp/integration/test_logical.cc | 21 +- 7 files changed, 837 insertions(+), 98 deletions(-) create mode 100644 tests/cpp/integration/test_amax.cc create mode 100644 tests/cpp/integration/test_amin.cc diff --git a/src/cunumeric/ndarray.cc b/src/cunumeric/ndarray.cc index f43a9ef7a6..6828103e04 100644 --- a/src/cunumeric/ndarray.cc +++ b/src/cunumeric/ndarray.cc @@ -857,9 +857,9 @@ void NDArray::flip(NDArray rhs, std::optional> axis) runtime->submit(std::move(task)); } -NDArray NDArray::all(std::optional> axis, +NDArray NDArray::all(std::vector axis, std::optional out, - std::optional keepdims, + bool keepdims, std::optional initial, std::optional where) { @@ -870,22 +870,53 @@ NDArray NDArray::all(std::optional> axis, legate::bool_(), out, keepdims, - std::nullopt, + {}, initial, where); } NDArray NDArray::_perform_unary_reduction(int32_t op, NDArray src, - std::optional> axis, + const std::vector& axis, std::optional dtype, std::optional res_dtype, std::optional out, - std::optional keepdims, - std::optional> args, + bool keepdims, + const std::vector& args, std::optional initial, std::optional where) { + if (src.size() == 0 && !initial.has_value()) { + if (static_cast(op) == UnaryRedCode::MAX || + static_cast(op) == UnaryRedCode::MIN) { + throw std::invalid_argument("Min/max reduction is not yet supported for empty arrays"); + } + } + + if (src.type() == legate::complex64() || src.type() == legate::complex128()) { + if (static_cast(op) == UnaryRedCode::MAX || + static_cast(op) == UnaryRedCode::MIN || + static_cast(op) == UnaryRedCode::ARGMAX || + static_cast(op) == UnaryRedCode::ARGMIN) { + throw std::runtime_error("(arg)max/min not supported for complex-type arrays"); + } + } + + if (where.has_value() && where.value().type() != legate::bool_()) { + throw std::invalid_argument("where array should be bool"); + } + + if ((dtype.has_value() && !dtype.value().is_primitive()) || + (res_dtype.has_value() && !res_dtype.value().is_primitive())) { + throw std::invalid_argument("dtype and res_dtype should be primitive type"); + } + + // Handle scalar array without any other inputs + if (src.dim() == 0 && !dtype.has_value() && !res_dtype.has_value() && !out.has_value() && + !initial.has_value() && !where.has_value()) { + return src; + } + if (res_dtype.has_value()) { assert(!dtype.has_value()); dtype = src.type(); @@ -901,33 +932,20 @@ NDArray NDArray::_perform_unary_reduction(int32_t op, } } - if (src.type() == legate::complex64() || src.type() == legate::complex128()) { - auto ops = {UnaryRedCode::ARGMAX, UnaryRedCode::ARGMIN, UnaryRedCode::MAX, UnaryRedCode::MIN}; - if (std::find(ops.begin(), ops.end(), static_cast(op)) != ops.end()) { - throw std::runtime_error("(arg)max/min not supported for complex-type arrays"); - } - } - - if (where.has_value()) { - if (where.value().type() != legate::bool_()) { - throw std::invalid_argument("where array should be bool"); - } - } - std::vector axes; - if (!axis.has_value()) { + if (axis.empty()) { for (auto i = 0; i < src.dim(); ++i) { axes.push_back(i); } } else { - axes = normalize_axis_vector(axis.value(), src.dim()); + axes = normalize_axis_vector(axis, src.dim()); } std::vector out_shape; for (auto i = 0; i < src.dim(); ++i) { if (std::find(axes.begin(), axes.end(), i) == axes.end()) { out_shape.push_back(src.shape()[i]); - } else if (keepdims.value_or(false)) { + } else if (keepdims) { out_shape.push_back(1); } } @@ -956,9 +974,10 @@ NDArray NDArray::_perform_unary_reduction(int32_t op, where_array = broadcast_where(where.value(), src); } - std::vector ops = { - UnaryRedCode::ARGMAX, UnaryRedCode::ARGMIN, UnaryRedCode::NANARGMAX, UnaryRedCode::NANARGMIN}; - auto argred = std::find(ops.begin(), ops.end(), static_cast(op)) != ops.end(); + bool argred = static_cast(op) == UnaryRedCode::ARGMAX || + static_cast(op) == UnaryRedCode::ARGMIN || + static_cast(op) == UnaryRedCode::NANARGMAX || + static_cast(op) == UnaryRedCode::NANARGMIN; if (argred) { assert(!initial.has_value()); auto argred_dtype = runtime->get_argred_type(src.type()); @@ -980,10 +999,10 @@ NDArray NDArray::_perform_unary_reduction(int32_t op, void NDArray::unary_reduction(int32_t op, NDArray src, std::optional where, - std::optional> orig_axis, - std::optional> axes, - std::optional keepdims, - std::optional> args, + const std::vector& orig_axis, + const std::vector& axes, + bool keepdims, + const std::vector& args, std::optional initial) { auto lhs_array = *this; @@ -999,12 +1018,10 @@ void NDArray::unary_reduction(int32_t op, lhs_array.fill(get_reduction_identity(op_code, lhs_array.type())); } - auto is_where = where.has_value(); - bool is_keepdims = keepdims.value_or(false); + auto is_where = where.has_value(); if (lhs_array.size() == 1) { - assert(!axes.has_value() || - lhs_array.dim() == - (rhs_array.dim() - (is_keepdims ? 0 : static_cast(axes.value().size())))); + assert(axes.empty() || lhs_array.dim() == (rhs_array.dim() - + (keepdims ? 0 : static_cast(axes.size())))); auto p_lhs = lhs_array.store_; while (p_lhs.dim() > 1) { @@ -1016,34 +1033,35 @@ void NDArray::unary_reduction(int32_t op, task.add_reduction(p_lhs, get_reduction_op(op_code)); auto p_rhs = task.add_input(rhs_array.store_); task.add_scalar_arg(legate::Scalar(op)); - task.add_scalar_arg(legate::Scalar(rhs_array.shape())); + if (rhs_array.dim() > 0) { + task.add_scalar_arg(legate::Scalar(rhs_array.shape())); + } else { + task.add_scalar_arg(legate::Scalar(std::vector({1}))); + } task.add_scalar_arg(legate::Scalar(is_where)); if (is_where) { auto p_where = task.add_input(where.value().store_); task.add_constraint(align(p_rhs, p_where)); } - if (args.has_value()) { - auto arg_array = args.value(); - for (auto& arg : arg_array) { - task.add_input(arg.store_); - } + for (auto& arg : args) { + task.add_input(arg.store_); } runtime->submit(std::move(task)); } else { - assert(axes.has_value()); + assert(!axes.empty()); auto result = lhs_array.store_; - if (is_keepdims) { - for (auto axis : axes.value()) { + if (keepdims) { + for (auto axis : axes) { result = result.project(axis, 0); } } auto rhs_shape = rhs_array.shape(); - for (auto axis : axes.value()) { + for (auto axis : axes) { result = result.promote(axis, rhs_shape[axis]); } - if (axes.value().size() > 1) { + if (axes.size() > 1) { throw std::runtime_error("Need support for reducing multiple dimensions"); } @@ -1051,18 +1069,15 @@ void NDArray::unary_reduction(int32_t op, auto p_lhs = task.add_reduction(result, get_reduction_op(op_code)); auto p_rhs = task.add_input(rhs_array.store_); - task.add_scalar_arg(legate::Scalar(axes.value()[0])); + task.add_scalar_arg(legate::Scalar(axes[0])); task.add_scalar_arg(legate::Scalar(op)); task.add_scalar_arg(legate::Scalar(is_where)); if (is_where) { auto p_where = task.add_input(where.value().store_); task.add_constraint(align(p_rhs, p_where)); } - if (args != std::nullopt) { - auto arg_array = args.value(); - for (auto& arg : arg_array) { - task.add_input(arg.store_); - } + for (auto& arg : args) { + task.add_input(arg.store_); } task.add_constraint(align(p_lhs, p_rhs)); @@ -1127,7 +1142,7 @@ NDArray NDArray::diag_helper(int32_t offset, if (N != s_axes.size()) { throw std::invalid_argument("axes passed to diag_helper should be all different"); } - if (dim() < N) { + if (static_cast(dim()) < N) { throw std::invalid_argument("Dimension of input array shouldn't be less than number of axes"); } std::vector transpose_axes; @@ -1150,7 +1165,7 @@ NDArray NDArray::diag_helper(int32_t offset, offset = -offset; } a = transpose(transpose_axes); - if (offset >= a.shape()[dim() - 1]) { + if (offset >= static_cast(a.shape()[dim() - 1])) { throw std::invalid_argument("'offset' for diag or diagonal must be in range"); } diag_size = std::max(static_cast(0), @@ -1264,9 +1279,8 @@ void NDArray::diag_task(NDArray rhs, int32_t offset, int32_t naxes, bool extract } } } else { - matrix = store_; - diag = rhs.store_; - auto ndim = dim(); + matrix = store_; + diag = rhs.store_; if (offset > 0) { matrix = matrix.slice(1, slice(offset)); } else if (offset < 0) { diff --git a/src/cunumeric/ndarray.h b/src/cunumeric/ndarray.h index 1bc32dceea..fb3ef09f01 100644 --- a/src/cunumeric/ndarray.h +++ b/src/cunumeric/ndarray.h @@ -91,11 +91,11 @@ class NDArray { NDArray transpose(std::vector axes); NDArray argwhere(); NDArray flip(std::optional> axis = std::nullopt); - NDArray all(std::optional> axis = std::nullopt, - std::optional out = std::nullopt, - std::optional keepdims = std::nullopt, - std::optional initial = std::nullopt, - std::optional where = std::nullopt); + NDArray all(std::vector axis = {}, + std::optional out = std::nullopt, + bool keepdims = false, + std::optional initial = std::nullopt, + std::optional where = std::nullopt); void put(NDArray indices, NDArray values, std::string mode = "raise"); NDArray diagonal(int32_t offset = 0, std::optional axis1 = std::nullopt, @@ -121,6 +121,16 @@ class NDArray { NDArray _warn_and_convert(legate::Type const& type); NDArray wrap_indices(Scalar const& n); NDArray clip_indices(Scalar const& min, Scalar const& max); + NDArray _perform_unary_reduction(int32_t op, + NDArray src, + const std::vector& axis, + std::optional dtype, + std::optional res_dtype, + std::optional out, + bool keepdims, + const std::vector& args, + std::optional initial, + std::optional where); NDArray copy(); private: @@ -132,22 +142,12 @@ class NDArray { void unary_reduction(int32_t op, NDArray src, std::optional where, - std::optional> orig_axis, - std::optional> axes, - std::optional keepdims, - std::optional> args, + const std::vector& orig_axis, + const std::vector& axes, + bool keepdims, + const std::vector& args, std::optional initial); NDArray broadcast_where(NDArray where, NDArray source); - NDArray _perform_unary_reduction(int32_t op, - NDArray src, - std::optional> axis = std::nullopt, - std::optional dtype = std::nullopt, - std::optional res_dtype = std::nullopt, - std::optional out = std::nullopt, - std::optional keepdims = std::nullopt, - std::optional> args = std::nullopt, - std::optional initial = std::nullopt, - std::optional where = std::nullopt); void flip(NDArray rhs, std::optional> axis); void diag_task(NDArray rhs, int32_t offset, int32_t naxes, bool extract, bool trace); NDArray diag_helper(int32_t offset, diff --git a/src/cunumeric/operators.cc b/src/cunumeric/operators.cc index 997f8f7731..d68499c81e 100644 --- a/src/cunumeric/operators.cc +++ b/src/cunumeric/operators.cc @@ -292,9 +292,9 @@ NDArray dot(NDArray rhs1, NDArray rhs2) } NDArray all(NDArray input, - std::optional> axis, + std::vector axis, std::optional out, - std::optional keepdims, + bool keepdims, std::optional where) { return input.all(axis, out, keepdims, std::nullopt, where); @@ -302,9 +302,45 @@ NDArray all(NDArray input, NDArray sum(NDArray input) { return unary_reduction(UnaryRedCode::SUM, std::move(input)); } -NDArray amax(NDArray input) { return unary_reduction(UnaryRedCode::MAX, std::move(input)); } - -NDArray amin(NDArray input) { return unary_reduction(UnaryRedCode::MIN, std::move(input)); } +NDArray amax(NDArray input, + std::vector axis, + std::optional dtype, + std::optional out, + bool keepdims, + std::optional initial, + std::optional where) +{ + return input._perform_unary_reduction(static_cast(UnaryRedCode::MAX), + input, + axis, + dtype, + std::nullopt, + out, + keepdims, + {}, + initial, + where); +} + +NDArray amin(NDArray input, + std::vector axis, + std::optional dtype, + std::optional out, + bool keepdims, + std::optional initial, + std::optional where) +{ + return input._perform_unary_reduction(static_cast(UnaryRedCode::MIN), + input, + axis, + dtype, + std::nullopt, + out, + keepdims, + {}, + initial, + where); +} NDArray unique(NDArray input) { return input.unique(); } diff --git a/src/cunumeric/operators.h b/src/cunumeric/operators.h index 4b1c96ed8b..9c2d8b6e49 100644 --- a/src/cunumeric/operators.h +++ b/src/cunumeric/operators.h @@ -48,16 +48,28 @@ NDArray zeros(std::vector shape, std::optional type = st NDArray full(std::vector shape, const Scalar& value); NDArray all(NDArray input, - std::optional> axis = std::nullopt, - std::optional out = std::nullopt, - std::optional keepdims = std::nullopt, - std::optional where = std::nullopt); + std::vector axis = {}, + std::optional out = std::nullopt, + bool keepdims = false, + std::optional where = std::nullopt); NDArray sum(NDArray input); -NDArray amax(NDArray input); - -NDArray amin(NDArray input); +NDArray amax(NDArray input, + std::vector axis = {}, + std::optional dtype = std::nullopt, + std::optional out = std::nullopt, + bool keepdims = false, + std::optional initial = std::nullopt, + std::optional where = std::nullopt); + +NDArray amin(NDArray input, + std::vector axis = {}, + std::optional dtype = std::nullopt, + std::optional out = std::nullopt, + bool keepdims = false, + std::optional initial = std::nullopt, + std::optional where = std::nullopt); NDArray unique(NDArray input); diff --git a/tests/cpp/integration/test_amax.cc b/tests/cpp/integration/test_amax.cc new file mode 100644 index 0000000000..43308eba2e --- /dev/null +++ b/tests/cpp/integration/test_amax.cc @@ -0,0 +1,339 @@ +/* Copyright 2023 NVIDIA Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +#include "common_utils.h" + +template +void test_amax(const std::vector& in_array, + const std::vector& shape, + const std::vector& expect_result, + const std::vector& expect_shape, + std::vector axis = {}, + std::optional dtype = std::nullopt, + std::optional out = std::nullopt, + bool keepdims = false, + std::optional initial = std::nullopt, + std::optional where = std::nullopt) +{ + auto array = cunumeric::mk_array(in_array, shape); + + if (!out.has_value()) { + auto result = cunumeric::amax(array, axis, dtype, std::nullopt, keepdims, initial, where); + cunumeric::check_array(result, expect_result, expect_shape); + } else { + cunumeric::amax(array, axis, dtype, out, keepdims, initial, where); + cunumeric::check_array(out.value(), expect_result, expect_shape); + } +} + +template +void test_amax_each_axis(const std::vector& arr, + const std::vector& shape, + std::map>& expect_results, + std::map>& expect_shapes, + bool keepdims = false, + std::optional initial = std::nullopt) +{ + int32_t dim = shape.size(); + auto df = std::nullopt; + for (int32_t axis = -dim + 1; axis < dim; ++axis) { + auto index = axis < 0 ? axis + dim : axis; + auto exp = expect_results[index]; + auto exp_shape = expect_shapes[index]; + auto axes = {axis}; + test_amax(arr, shape, exp, exp_shape, axes, df, df, keepdims, initial, df); + } +} + +void test_amax_basic() +{ + typedef std::map> IntResult; + typedef std::map> DoubleResult; + typedef std::map> ShapeResult; + + // Test int type - dim=1 + std::vector arr1 = {-1, 4, 5, 2, 0}; + std::vector shape1 = {5}; + ShapeResult exp_shape1 = {{0, {}}}; + ShapeResult exp_shape1_k = {{0, {1}}}; + IntResult exp1 = {{0, {5}}}; + test_amax_each_axis(arr1, shape1, exp1, exp_shape1); + test_amax_each_axis(arr1, shape1, exp1, exp_shape1_k, true); + + // Test int type - dim=2 + std::vector arr2 = {1, 0, 0, 5, 3, 2}; + std::vector shape2 = {3, 2}; + ShapeResult exp_shape2 = {{0, {2}}, {1, {3}}}; + ShapeResult exp_shape2_k = {{0, {1, 2}}, {1, {3, 1}}}; + IntResult exp2 = {{0, {3, 5}}, {1, {1, 5, 3}}}; + test_amax_each_axis(arr2, shape2, exp2, exp_shape2); + test_amax_each_axis(arr2, shape2, exp2, exp_shape2_k, true); + + // Test int type - dim=3 + std::vector arr3 = {0, 11, 2, 3, -4, 0, -6, 7}; + std::vector shape3 = {2, 2, 2}; + ShapeResult exp_shape3 = {{0, {2, 2}}, {1, {2, 2}}, {2, {2, 2}}}; + ShapeResult exp_shape3_k = {{0, {1, 2, 2}}, {1, {2, 1, 2}}, {2, {2, 2, 1}}}; + IntResult exp3 = {{0, {0, 11, 2, 7}}, {1, {2, 11, -4, 7}}, {2, {11, 3, 0, 7}}}; + test_amax_each_axis(arr3, shape3, exp3, exp_shape3); + test_amax_each_axis(arr3, shape3, exp3, exp_shape3_k, true); + + // Test float type - dim=3 + std::vector arr4 = {0.0, -0.99, 10.0, -5.0, 2.999, 1.51, -1.0, 2.99, 3.0}; + std::vector shape4 = {3, 1, 3}; + ShapeResult exp_shape4 = {{0, {1, 3}}, {1, {3, 3}}, {2, {3, 1}}}; + ShapeResult exp_shape4_k = {{0, {1, 1, 3}}, {1, {3, 1, 3}}, {2, {3, 1, 1}}}; + DoubleResult exp4 = {{0, {0.0, 2.999, 10.0}}, {1, arr4}, {2, {10.0, 2.999, 3.0}}}; + test_amax_each_axis(arr4, shape4, exp4, exp_shape4); + test_amax_each_axis(arr4, shape4, exp4, exp_shape4_k, true); +} + +void test_amax_initial_input() +{ + typedef std::map> IntResult; + typedef std::map> DoubleResult; + typedef std::map> ShapeResult; + + std::vector arr1 = {0, 11, 2, 3, -4, 0, -6, 7}; + std::vector shape1 = {2, 2, 2}; + ShapeResult exp_shape1 = {{0, {2, 2}}, {1, {2, 2}}, {2, {2, 2}}}; + ShapeResult exp_shape1_k = {{0, {1, 2, 2}}, {1, {2, 1, 2}}, {2, {2, 2, 1}}}; + // use initial in each axis + auto initial1 = legate::Scalar(6); + IntResult exp1 = {{0, {6, 11, 6, 7}}, {1, {6, 11, 6, 7}}, {2, {11, 6, 6, 7}}}; + test_amax_each_axis(arr1, shape1, exp1, exp_shape1, false, initial1); + test_amax_each_axis(arr1, shape1, exp1, exp_shape1_k, true, initial1); + + std::vector arr2 = {0.0, -0.99, 10.0, -5.0, 2.999, 1.51, -1.0, 2.99, 3.0}; + std::vector shape2 = {3, 3}; + ShapeResult exp_shape2 = {{0, {3}}, {1, {3}}}; + ShapeResult exp_shape2_k = {{0, {1, 3}}, {1, {3, 1}}}; + auto initial2 = legate::Scalar(2.9999); + DoubleResult exp2 = {{0, {2.9999, 2.9999, 10.0}}, {1, {10.0, 2.9999, 3.0}}}; + test_amax_each_axis(arr2, shape2, exp2, exp_shape2, false, initial2); + test_amax_each_axis(arr2, shape2, exp2, exp_shape2_k, true, initial2); +} + +void test_amax_dtype_input() +{ + // int to float + std::vector arr1 = {-1, 4, 5, 2, 0}; + std::vector shape1 = {5}; + std::vector exp_shape1 = {}; + auto dtype1 = legate::float64(); + std::vector exp1 = {5.0}; + test_amax(arr1, shape1, exp1, exp_shape1, {}, dtype1); + + // float to int + std::vector arr2 = {0.0, -0.99, 10.1, -5.6, 2.999, 1.51}; + std::vector shape2 = {3, 2}; + std::vector exp_shape2 = {}; + auto dtype2 = legate::int32(); + std::vector exp2 = {10}; + test_amax(arr2, shape2, exp2, exp_shape2, {}, dtype2); +} + +void test_amax_axis_input() +{ + std::vector arr = {0.0, -0.99, 10.0, -5.0, 2.999, 1.51, -1.0, 2.99, 3.0}; + std::vector shape = {3, 1, 3}; + + std::vector axis = {-1, 0, 1}; + std::vector exp_shape = {}; + std::vector exp = {10.0}; + test_amax(arr, shape, exp, exp_shape, axis); +} + +void test_amax_out_input() +{ + // Test out input with dim-1 and different datatype + std::vector arr = {-1, 4, 5, 2, 0, 3}; + std::vector shape1 = {6}; + std::vector exp_shape1 = {}; + auto df = std::nullopt; + auto out1 = cunumeric::zeros(exp_shape1, legate::int32()); + auto out1_1 = cunumeric::zeros(exp_shape1, legate::float64()); + std::vector exp1 = {5}; + std::vector exp1_1 = {5.0}; + test_amax(arr, shape1, exp1, exp_shape1, {}, df, out1); + test_amax(arr, shape1, exp1_1, exp_shape1, {}, df, out1_1); + + // Test out input with axis, keepdims and initial params + std::vector shape2 = {2, 3}; + std::vector exp_shape2 = {2}; + std::vector exp_shape2_k = {2, 1}; + auto out2 = cunumeric::zeros(exp_shape2, legate::int32()); + auto out2_k = cunumeric::zeros(exp_shape2_k, legate::int32()); + std::vector axis = {-1}; + auto ini = legate::Scalar(2); + std::vector exp2 = {5, 3}; + test_amax(arr, shape2, exp2, exp_shape2, axis, df, out2); + test_amax(arr, shape2, exp2, exp_shape2_k, axis, df, out2_k, true); + + test_amax(arr, shape2, exp2, exp_shape2, axis, df, out2, false, ini); + test_amax(arr, shape2, exp2, exp_shape2_k, axis, df, out2_k, true, ini); +} + +void test_amax_max_dim() +{ + std::vector arr = {14, 10, 3, 12, 5, 13, 2, 4, 16, 8, 9, 7, 6, 11, 1, 15}; + std::vector axis = {-1}; +#if LEGATE_MAX_DIM >= 4 + std::vector shape_4d = {2, 2, 2, 2}; + std::vector exp_shape_4d = {2, 2, 2}; + std::vector exp_4d = {14, 12, 13, 4, 16, 9, 11, 15}; + test_amax(arr, shape_4d, exp_4d, exp_shape_4d, axis); +#endif + +#if LEGATE_MAX_DIM >= 5 + std::vector shape_5d = {1, 2, 2, 1, 4}; + std::vector exp_shape_5d = {1, 2, 2, 1}; + std::vector exp_5d = {14, 13, 16, 15}; + test_amax(arr, shape_5d, exp_5d, exp_shape_5d, axis); +#endif + +#if LEGATE_MAX_DIM >= 6 + std::vector shape_6d = {2, 1, 1, 2, 2, 2}; + std::vector exp_shape_6d = {2, 1, 1, 2, 2}; + std::vector exp_6d = {14, 12, 13, 4, 16, 9, 11, 15}; + test_amax(arr, shape_6d, exp_6d, exp_shape_6d, axis); +#endif + +#if LEGATE_MAX_DIM >= 7 + std::vector shape_7d = {2, 1, 1, 2, 1, 1, 4}; + std::vector exp_shape_7d = {2, 1, 1, 2, 1, 1}; + std::vector exp_7d = {14, 13, 16, 15}; + test_amax(arr, shape_7d, exp_7d, exp_shape_7d, axis); +#endif +} + +void test_amax_large_array() +{ + const int32_t count = 100000; + std::vector shape = {count}; + std::vector exp_shape = {}; + + // Test int type for large array + std::vector arr1(count); + for (int32_t i = 0; i < count; i++) { + arr1[i] = i + 1; + } + std::vector exp1 = {count}; + test_amax(arr1, shape, exp1, exp_shape); + + // Test float type + std::vector arr2(count); + for (int32_t i = 0; i < count; i++) { + arr2[i] = i + 1.1; + } + std::vector exp2 = {count + 0.1}; + test_amax(arr2, shape, exp2, exp_shape); +} + +void test_amax_scalar_array() +{ + std::vector arr = {10}; + std::vector shape = {}; + std::vector exp = {10}; + auto out = cunumeric::zeros(shape, legate::int32()); + auto df = std::nullopt; + test_amax(arr, shape, exp, shape); + test_amax(arr, shape, exp, shape, {}, df, out); + + // Test with initial + auto initial = legate::Scalar(11); + std::vector exp1 = {11}; + test_amax(arr, shape, exp1, shape, {}, df, df, false, initial); +} + +void test_amax_invalid_array() +{ + // Test zero size array + std::vector arr1 = {}; + std::vector shape1 = {0}; + auto arr_emp = cunumeric::mk_array(arr1, shape1); + EXPECT_THROW(cunumeric::amax(arr_emp), std::invalid_argument); + + // Test complex array (not supported now) + std::vector> arr2 = {complex(0, 1), complex(1, 1)}; + std::vector shape2 = {2}; + auto arr_comp = cunumeric::mk_array>(arr2, shape2); + EXPECT_THROW(cunumeric::amax(arr_comp), std::runtime_error); +} + +void test_amax_invalid_axis() +{ + std::vector arr = {1, 2, 3, 4, 5, 6}; + std::vector shape = {1, 3, 2}; + auto array = cunumeric::mk_array(arr, shape); + + // Test out-of-bound + std::vector axis1 = {-4, 3}; + std::vector axis2 = {0, 3}; + EXPECT_THROW(cunumeric::amax(array, axis1), std::invalid_argument); + EXPECT_THROW(cunumeric::amax(array, axis2), std::invalid_argument); + + // Test repeated axes + std::vector axis3 = {1, 1}; + std::vector axis4 = {-1, 2}; + EXPECT_THROW(cunumeric::amax(array, axis3), std::invalid_argument); + EXPECT_THROW(cunumeric::amax(array, axis4), std::invalid_argument); + + // Not reduce to one value (valid but not supported now) + std::vector axis5 = {0, 1}; + EXPECT_THROW(cunumeric::amax(array, axis5), std::runtime_error); +} + +void test_amax_invalid_shape() +{ + std::vector arr = {1, 2, 3, 4, 5, 6}; + std::vector shape = {1, 3, 2}; + auto array = cunumeric::mk_array(arr, shape); + auto df = std::nullopt; + + std::vector out_shape1 = {1}; + auto out1 = cunumeric::zeros(out_shape1, legate::int32()); + EXPECT_THROW(cunumeric::amax(array, {}, df, out1), std::invalid_argument); + + std::vector out_shape2 = {2}; + std::vector axis2 = {1}; + auto out2 = cunumeric::zeros(out_shape2, legate::int32()); + EXPECT_THROW(cunumeric::amax(array, axis2, df, out2), std::invalid_argument); +} + +void test_amax_invalid_dtype() +{ + std::vector arr = {1, 2, 3, 4, 5, 6}; + std::vector shape = {1, 3, 2}; + auto array = cunumeric::mk_array(arr, shape); + + // Test invalid dtype + auto dtype = legate::point_type(2); + EXPECT_THROW(cunumeric::amax(array, {}, dtype), std::invalid_argument); +} + +// void cpp_test() +TEST(Amax, BasicTest) { test_amax_basic(); } +TEST(Amax, InitialInput) { test_amax_initial_input(); } +TEST(Amax, DtypeInput) { test_amax_dtype_input(); } +TEST(Amax, AxisInput) { test_amax_axis_input(); } +TEST(Amax, OutInput) { test_amax_out_input(); } +TEST(Amax, MaxDim) { test_amax_max_dim(); } +TEST(Amax, LargeArray) { test_amax_large_array(); } +TEST(Amax, ScalarArray) { test_amax_scalar_array(); } +TEST(Amax, InvalidArray) { test_amax_invalid_array(); } +TEST(Amax, InvalidAxis) { test_amax_invalid_axis(); } +TEST(Amax, InvalidShape) { test_amax_invalid_shape(); } +TEST(Amax, InvalidDtype) { test_amax_invalid_dtype(); } diff --git a/tests/cpp/integration/test_amin.cc b/tests/cpp/integration/test_amin.cc new file mode 100644 index 0000000000..e82ec50de4 --- /dev/null +++ b/tests/cpp/integration/test_amin.cc @@ -0,0 +1,339 @@ +/* Copyright 2023 NVIDIA Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +#include "common_utils.h" + +template +void test_amin(const std::vector& in_array, + const std::vector& shape, + const std::vector& expect_result, + const std::vector& expect_shape, + std::vector axis = {}, + std::optional dtype = std::nullopt, + std::optional out = std::nullopt, + bool keepdims = false, + std::optional initial = std::nullopt, + std::optional where = std::nullopt) +{ + auto array = cunumeric::mk_array(in_array, shape); + + if (!out.has_value()) { + auto result = cunumeric::amin(array, axis, dtype, std::nullopt, keepdims, initial, where); + cunumeric::check_array(result, expect_result, expect_shape); + } else { + cunumeric::amin(array, axis, dtype, out, keepdims, initial, where); + cunumeric::check_array(out.value(), expect_result, expect_shape); + } +} + +template +void test_amin_each_axis(const std::vector& arr, + const std::vector& shape, + std::map>& expect_results, + std::map>& expect_shapes, + bool keepdims = false, + std::optional initial = std::nullopt) +{ + int32_t dim = shape.size(); + auto df = std::nullopt; + for (int32_t axis = -dim + 1; axis < dim; ++axis) { + auto index = axis < 0 ? axis + dim : axis; + auto exp = expect_results[index]; + auto exp_shape = expect_shapes[index]; + auto axes = {axis}; + test_amin(arr, shape, exp, exp_shape, axes, df, df, keepdims, initial, df); + } +} + +void test_amin_basic() +{ + typedef std::map> IntResult; + typedef std::map> DoubleResult; + typedef std::map> ShapeResult; + + // Test int type - dim=1 + std::vector arr1 = {-1, 4, 5, 2, 0}; + std::vector shape1 = {5}; + ShapeResult exp_shape1 = {{0, {}}}; + ShapeResult exp_shape1_k = {{0, {1}}}; + IntResult exp1 = {{0, {-1}}}; + test_amin_each_axis(arr1, shape1, exp1, exp_shape1); + test_amin_each_axis(arr1, shape1, exp1, exp_shape1_k, true); + + // Test int type - dim=2 + std::vector arr2 = {1, 0, 0, 5, 3, 2}; + std::vector shape2 = {3, 2}; + ShapeResult exp_shape2 = {{0, {2}}, {1, {3}}}; + ShapeResult exp_shape2_k = {{0, {1, 2}}, {1, {3, 1}}}; + IntResult exp2 = {{0, {0, 0}}, {1, {0, 0, 2}}}; + test_amin_each_axis(arr2, shape2, exp2, exp_shape2); + test_amin_each_axis(arr2, shape2, exp2, exp_shape2_k, true); + + // Test int type - dim=3 + std::vector arr3 = {0, 11, 2, 3, -4, 0, -6, 7}; + std::vector shape3 = {2, 2, 2}; + ShapeResult exp_shape3 = {{0, {2, 2}}, {1, {2, 2}}, {2, {2, 2}}}; + ShapeResult exp_shape3_k = {{0, {1, 2, 2}}, {1, {2, 1, 2}}, {2, {2, 2, 1}}}; + IntResult exp3 = {{0, {-4, 0, -6, 3}}, {1, {0, 3, -6, 0}}, {2, {0, 2, -4, -6}}}; + test_amin_each_axis(arr3, shape3, exp3, exp_shape3); + test_amin_each_axis(arr3, shape3, exp3, exp_shape3_k, true); + + // Test float type - dim=3 + std::vector arr4 = {0.0, -0.99, 10.0, -5.0, 2.999, 1.51, -1.0, 2.99, 3.0}; + std::vector shape4 = {3, 1, 3}; + ShapeResult exp_shape4 = {{0, {1, 3}}, {1, {3, 3}}, {2, {3, 1}}}; + ShapeResult exp_shape4_k = {{0, {1, 1, 3}}, {1, {3, 1, 3}}, {2, {3, 1, 1}}}; + DoubleResult exp4 = {{0, {-5.0, -0.99, 1.51}}, {1, arr4}, {2, {-0.99, -5.0, -1.0}}}; + test_amin_each_axis(arr4, shape4, exp4, exp_shape4); + test_amin_each_axis(arr4, shape4, exp4, exp_shape4_k, true); +} + +void test_amin_initial_input() +{ + typedef std::map> IntResult; + typedef std::map> DoubleResult; + typedef std::map> ShapeResult; + + std::vector arr1 = {0, 11, 2, 3, -4, 0, -6, 7}; + std::vector shape1 = {2, 2, 2}; + ShapeResult exp_shape1 = {{0, {2, 2}}, {1, {2, 2}}, {2, {2, 2}}}; + ShapeResult exp_shape1_k = {{0, {1, 2, 2}}, {1, {2, 1, 2}}, {2, {2, 2, 1}}}; + // use initial in each axis + auto initial1 = legate::Scalar(-1); + IntResult exp1 = {{0, {-4, -1, -6, -1}}, {1, {-1, -1, -6, -1}}, {2, {-1, -1, -4, -6}}}; + test_amin_each_axis(arr1, shape1, exp1, exp_shape1, false, initial1); + test_amin_each_axis(arr1, shape1, exp1, exp_shape1_k, true, initial1); + + std::vector arr2 = {0.0, -0.99, 10.0, -5.0, 2.999, 1.51, -1.0, 2.99, 3.0}; + std::vector shape2 = {3, 3}; + ShapeResult exp_shape2 = {{0, {3}}, {1, {3}}}; + ShapeResult exp_shape2_k = {{0, {1, 3}}, {1, {3, 1}}}; + auto initial2 = legate::Scalar(0.0); + DoubleResult exp2 = {{0, {-5.0, -0.99, 0.0}}, {1, {-0.99, -5.0, -1.0}}}; + test_amin_each_axis(arr2, shape2, exp2, exp_shape2, false, initial2); + test_amin_each_axis(arr2, shape2, exp2, exp_shape2_k, true, initial2); +} + +void test_amin_dtype_input() +{ + // int to float + std::vector arr1 = {-1, 4, 5, 2, 0}; + std::vector shape1 = {5}; + std::vector exp_shape1 = {}; + auto dtype1 = legate::float64(); + std::vector exp1 = {-1.0}; + test_amin(arr1, shape1, exp1, exp_shape1, {}, dtype1); + + // float to int + std::vector arr2 = {0.0, -0.99, 10.1, -5.6, 2.999, 1.51}; + std::vector shape2 = {3, 2}; + std::vector exp_shape2 = {}; + auto dtype2 = legate::int32(); + std::vector exp2 = {-5}; + test_amin(arr2, shape2, exp2, exp_shape2, {}, dtype2); +} + +void test_amin_axis_input() +{ + std::vector arr = {0.0, -0.99, 10.0, -5.0, 2.999, 1.51, -1.0, 2.99, 3.0}; + std::vector shape = {3, 1, 3}; + + std::vector axis = {-1, 0, 1}; + std::vector exp_shape = {}; + std::vector exp = {-5.0}; + test_amin(arr, shape, exp, exp_shape, axis); +} + +void test_amin_out_input() +{ + // Test out input with dim-1 and different datatype + std::vector arr = {-1, 4, 5, 2, 0, 3}; + std::vector shape1 = {6}; + std::vector exp_shape1 = {}; + auto df = std::nullopt; + auto out1 = cunumeric::zeros(exp_shape1, legate::int32()); + auto out1_1 = cunumeric::zeros(exp_shape1, legate::float64()); + std::vector exp1 = {-1}; + std::vector exp1_1 = {-1.0}; + test_amin(arr, shape1, exp1, exp_shape1, {}, df, out1); + test_amin(arr, shape1, exp1_1, exp_shape1, {}, df, out1_1); + + // Test out input with axis, keepdims and initial params + std::vector shape2 = {2, 3}; + std::vector exp_shape2 = {2}; + std::vector exp_shape2_k = {2, 1}; + auto out2 = cunumeric::zeros(exp_shape2, legate::int32()); + auto out2_k = cunumeric::zeros(exp_shape2_k, legate::int32()); + std::vector axis = {-1}; + auto ini = legate::Scalar(2); + std::vector exp2 = {-1, 0}; + test_amin(arr, shape2, exp2, exp_shape2, axis, df, out2); + test_amin(arr, shape2, exp2, exp_shape2_k, axis, df, out2_k, true); + + test_amin(arr, shape2, exp2, exp_shape2, axis, df, out2, false, ini); + test_amin(arr, shape2, exp2, exp_shape2_k, axis, df, out2_k, true, ini); +} + +void test_amin_max_dim() +{ + std::vector arr = {14, 10, 3, 12, 5, 13, 2, 4, 16, 8, 9, 7, 6, 11, 1, 15}; + std::vector axis = {-1}; +#if LEGATE_MAX_DIM >= 4 + std::vector shape_4d = {2, 2, 2, 2}; + std::vector exp_shape_4d = {2, 2, 2}; + std::vector exp_4d = {10, 3, 5, 2, 8, 7, 6, 1}; + test_amin(arr, shape_4d, exp_4d, exp_shape_4d, axis); +#endif + +#if LEGATE_MAX_DIM >= 5 + std::vector shape_5d = {1, 2, 2, 1, 4}; + std::vector exp_shape_5d = {1, 2, 2, 1}; + std::vector exp_5d = {3, 2, 7, 1}; + test_amin(arr, shape_5d, exp_5d, exp_shape_5d, axis); +#endif + +#if LEGATE_MAX_DIM >= 6 + std::vector shape_6d = {2, 1, 1, 2, 2, 2}; + std::vector exp_shape_6d = {2, 1, 1, 2, 2}; + std::vector exp_6d = {10, 3, 5, 2, 8, 7, 6, 1}; + test_amin(arr, shape_6d, exp_6d, exp_shape_6d, axis); +#endif + +#if LEGATE_MAX_DIM >= 7 + std::vector shape_7d = {2, 1, 1, 2, 1, 1, 4}; + std::vector exp_shape_7d = {2, 1, 1, 2, 1, 1}; + std::vector exp_7d = {3, 2, 7, 1}; + test_amin(arr, shape_7d, exp_7d, exp_shape_7d, axis); +#endif +} + +void test_amin_large_array() +{ + const int32_t count = 100000; + std::vector shape = {count}; + std::vector exp_shape = {}; + + // Test int type for large array + std::vector arr1(count); + for (int32_t i = 0; i < count; i++) { + arr1[i] = i + 1; + } + std::vector exp1 = {1}; + test_amin(arr1, shape, exp1, exp_shape); + + // Test float type + std::vector arr2(count); + for (int32_t i = 0; i < count; i++) { + arr2[i] = i + 1.1; + } + std::vector exp2 = {1.1}; + test_amin(arr2, shape, exp2, exp_shape); +} + +void test_amin_scalar_array() +{ + std::vector arr = {10}; + std::vector shape = {}; + std::vector exp = {10}; + auto out = cunumeric::zeros(shape, legate::int32()); + auto df = std::nullopt; + test_amin(arr, shape, exp, shape); + test_amin(arr, shape, exp, shape, {}, df, out); + + // Test with initial + auto initial = legate::Scalar(9); + std::vector exp1 = {9}; + test_amin(arr, shape, exp1, shape, {}, df, df, false, initial); +} + +void test_amin_invalid_array() +{ + // Test zero size array + std::vector arr1 = {}; + std::vector shape1 = {0}; + auto arr_emp = cunumeric::mk_array(arr1, shape1); + EXPECT_THROW(cunumeric::amin(arr_emp), std::invalid_argument); + + // Test complex array (not supported now) + std::vector> arr2 = {complex(0, 1), complex(1, 1)}; + std::vector shape2 = {2}; + auto arr_comp = cunumeric::mk_array>(arr2, shape2); + EXPECT_THROW(cunumeric::amin(arr_comp), std::runtime_error); +} + +void test_amin_invalid_axis() +{ + std::vector arr = {1, 2, 3, 4, 5, 6}; + std::vector shape = {1, 3, 2}; + auto array = cunumeric::mk_array(arr, shape); + + // Test out-of-bound + std::vector axis1 = {-4, 3}; + std::vector axis2 = {0, 3}; + EXPECT_THROW(cunumeric::amin(array, axis1), std::invalid_argument); + EXPECT_THROW(cunumeric::amin(array, axis2), std::invalid_argument); + + // Test repeated axes + std::vector axis3 = {1, 1}; + std::vector axis4 = {-1, 2}; + EXPECT_THROW(cunumeric::amin(array, axis3), std::invalid_argument); + EXPECT_THROW(cunumeric::amin(array, axis4), std::invalid_argument); + + // Not reduce to one value (valid but not supported now) + std::vector axis5 = {0, 1}; + EXPECT_THROW(cunumeric::amin(array, axis5), std::runtime_error); +} + +void test_amin_invalid_shape() +{ + std::vector arr = {1, 2, 3, 4, 5, 6}; + std::vector shape = {1, 3, 2}; + auto array = cunumeric::mk_array(arr, shape); + auto df = std::nullopt; + + std::vector out_shape1 = {1}; + auto out1 = cunumeric::zeros(out_shape1, legate::int32()); + EXPECT_THROW(cunumeric::amin(array, {}, df, out1), std::invalid_argument); + + std::vector out_shape2 = {2}; + std::vector axis2 = {1}; + auto out2 = cunumeric::zeros(out_shape2, legate::int32()); + EXPECT_THROW(cunumeric::amin(array, axis2, df, out2), std::invalid_argument); +} + +void test_amin_invalid_dtype() +{ + std::vector arr = {1, 2, 3, 4, 5, 6}; + std::vector shape = {1, 3, 2}; + auto array = cunumeric::mk_array(arr, shape); + + // Test invalid dtype + auto dtype = legate::point_type(2); + EXPECT_THROW(cunumeric::amin(array, {}, dtype), std::invalid_argument); +} + +// void cpp_test() +TEST(Amin, BasicTest) { test_amin_basic(); } +TEST(Amin, InitialInput) { test_amin_initial_input(); } +TEST(Amin, DtypeInput) { test_amin_dtype_input(); } +TEST(Amin, AxisInput) { test_amin_axis_input(); } +TEST(Amin, OutInput) { test_amin_out_input(); } +TEST(Amin, MaxDim) { test_amin_max_dim(); } +TEST(Amin, LargeArray) { test_amin_large_array(); } +TEST(Amin, ScalarArray) { test_amin_scalar_array(); } +TEST(Amin, InvalidArray) { test_amin_invalid_array(); } +TEST(Amin, InvalidAxis) { test_amin_invalid_axis(); } +TEST(Amin, InvalidShape) { test_amin_invalid_shape(); } +TEST(Amin, InvalidDtype) { test_amin_invalid_dtype(); } diff --git a/tests/cpp/integration/test_logical.cc b/tests/cpp/integration/test_logical.cc index cbd7ce7e91..8e6ff6bac6 100644 --- a/tests/cpp/integration/test_logical.cc +++ b/tests/cpp/integration/test_logical.cc @@ -32,10 +32,10 @@ void test_all(std::array& in_array, std::array& expect_result, legate::Type leg_type, std::vector shape, - std::optional> axis = std::nullopt, - std::optional out = std::nullopt, - std::optional keepdims = std::nullopt, - std::optional where = std::nullopt) + std::vector axis = {}, + std::optional out = std::nullopt, + bool keepdims = false, + std::optional where = std::nullopt) { auto A1 = cunumeric::zeros(shape, leg_type); if (in_array.size() != 0) { @@ -227,7 +227,7 @@ void test_all_where_input() std::array expect_val1 = {true}; test_all( - in_array, expect_val1, legate::bool_(), shape, std::nullopt, std::nullopt, false, where_array1); + in_array, expect_val1, legate::bool_(), shape, {}, std::nullopt, false, where_array1); // Test where with single bool value std::array where_in2 = {true}; @@ -236,7 +236,7 @@ void test_all_where_input() std::array expect_val2 = {false}; test_all( - in_array, expect_val2, legate::bool_(), shape, std::nullopt, std::nullopt, false, where_array2); + in_array, expect_val2, legate::bool_(), shape, {}, std::nullopt, false, where_array2); std::array where_in3 = {false}; auto where_array3 = cunumeric::zeros({1}, legate::bool_()); @@ -244,7 +244,7 @@ void test_all_where_input() std::array expect_val3 = {true}; test_all( - in_array, expect_val3, legate::bool_(), shape, std::nullopt, std::nullopt, false, where_array3); + in_array, expect_val3, legate::bool_(), shape, {}, std::nullopt, false, where_array3); } void test_all_out_input() @@ -402,7 +402,7 @@ void test_all_invalid_shape() std::vector out_shape1 = {1}; auto out1 = cunumeric::zeros(out_shape1, legate::int32()); - EXPECT_THROW(cunumeric::all(array, std::nullopt, out1), std::invalid_argument); + EXPECT_THROW(cunumeric::all(array, {}, out1), std::invalid_argument); std::vector out_shape2 = {2}; std::vector axis2 = {1}; @@ -426,15 +426,14 @@ void test_all_invalid_where() std::array in_where1 = {0, 1, 0, 1}; auto where1 = cunumeric::zeros(shape, legate::int32()); assign_values_to_array(where1, in_where1.data(), in_where1.size()); - EXPECT_THROW(cunumeric::all(array, std::nullopt, std::nullopt, false, where1), - std::invalid_argument); + EXPECT_THROW(cunumeric::all(array, {}, std::nullopt, false, where1), std::invalid_argument); // Test where with invalid shape std::vector where_shape = {2, 2, 1}; std::array in_where2 = {false, true, false, true}; auto where2 = cunumeric::zeros(where_shape, legate::bool_()); assign_values_to_array(where2, in_where2.data(), in_where2.size()); - EXPECT_THROW(cunumeric::all(array, std::nullopt, std::nullopt, false, where2), std::exception); + EXPECT_THROW(cunumeric::all(array, {}, std::nullopt, false, where2), std::exception); } // void cpp_test() From 880dd42136bfb09fdebe2e882f25a34720c89531 Mon Sep 17 00:00:00 2001 From: Wonchan Lee Date: Fri, 1 Nov 2024 13:17:32 -0700 Subject: [PATCH 343/462] Add the method parameter to cunumeric.convolve (#449) * Add the method parameter to cunumeric.convolve * Address review comments * Make the not-implemented message more accurate --- cunumeric/_module/math_misc.py | 32 ++++++++++++++++--- cunumeric/_thunk/deferred.py | 14 +++++++- cunumeric/_thunk/eager.py | 20 ++++++++++-- cunumeric/_thunk/thunk.py | 9 +++++- cunumeric/config.py | 11 +++++++ cunumeric/types.py | 2 ++ examples/richardson_lucy.py | 19 +++++++++-- src/cunumeric/convolution/convolve.cc | 3 +- src/cunumeric/convolution/convolve.cu | 32 +++++++++++++------ src/cunumeric/convolution/convolve.h | 1 + src/cunumeric/convolution/convolve_omp.cc | 3 +- .../convolution/convolve_template.inl | 5 ++- src/cunumeric/cunumeric_c.h | 6 ++++ tests/integration/test_convolve.py | 18 +++++++++++ 14 files changed, 150 insertions(+), 25 deletions(-) diff --git a/cunumeric/_module/math_misc.py b/cunumeric/_module/math_misc.py index 251d92eae1..34be8ddde3 100644 --- a/cunumeric/_module/math_misc.py +++ b/cunumeric/_module/math_misc.py @@ -18,15 +18,21 @@ from .._array.array import ndarray from .._array.util import add_boilerplate +from ..config import ConvolveMethod if TYPE_CHECKING: import numpy.typing as npt - from ..types import ConvolveMode + from ..types import ConvolveMethod as ConvolveMethodType, ConvolveMode @add_boilerplate("a", "v") -def convolve(a: ndarray, v: ndarray, mode: ConvolveMode = "full") -> ndarray: +def convolve( + a: ndarray, + v: ndarray, + mode: ConvolveMode = "full", + method: ConvolveMethodType = "auto", +) -> ndarray: """ Returns the discrete, linear convolution of two ndarrays. @@ -52,6 +58,19 @@ def convolve(a: ndarray, v: ndarray, mode: ConvolveMode = "full") -> ndarray: The output consists only of those elements that do not rely on the zero-padding. In 'valid' mode, either `a` or `v` must be at least as large as the other in every dimension. + method : ``{'auto', 'direct', 'fft'}``, optional + A string indicating which method to use to calculate the convolution. + + 'auto': + Automatically chooses direct or Fourier method based on an estimate of + which is faster (default) + + 'direct': + The convolution is determined directly from sums, the definition of + convolution + + 'fft': + The Fourier Transform is used to perform the convolution Returns ------- @@ -74,7 +93,7 @@ def convolve(a: ndarray, v: ndarray, mode: ConvolveMode = "full") -> ndarray: Multiple GPUs, Multiple CPUs """ if mode != "same": - raise NotImplementedError("Need to implement other convolution modes") + raise NotImplementedError("Only support mode='same'") if a.ndim != v.ndim: raise RuntimeError("Arrays should have the same dimensions") @@ -84,6 +103,11 @@ def convolve(a: ndarray, v: ndarray, mode: ConvolveMode = "full") -> ndarray: if a.ndim == 1 and a.size < v.size: v, a = a, v + if not hasattr(ConvolveMethod, method.upper()): + raise ValueError( + "Acceptable method flags are 'auto', 'direct', or 'fft'." + ) + if a.dtype != v.dtype: v = v.astype(a.dtype) out = ndarray( @@ -91,7 +115,7 @@ def convolve(a: ndarray, v: ndarray, mode: ConvolveMode = "full") -> ndarray: dtype=a.dtype, inputs=(a, v), ) - out._thunk.convolve(a._thunk, v._thunk, mode) + out._thunk.convolve(a._thunk, v._thunk, mode, method) return out diff --git a/cunumeric/_thunk/deferred.py b/cunumeric/_thunk/deferred.py index 0a0ae7fcbd..77a1a4d433 100644 --- a/cunumeric/_thunk/deferred.py +++ b/cunumeric/_thunk/deferred.py @@ -62,6 +62,7 @@ BitGeneratorOperation, Bitorder, ConvertCode, + ConvolveMethod, CuNumericOpCode, RandGenCode, UnaryOpCode, @@ -87,6 +88,7 @@ from ..config import BitGeneratorType, FFTDirection, FFTType, WindowOpCode from ..types import ( BitOrder, + ConvolveMethod as ConvolveMethodType, ConvolveMode, NdShape, OrderType, @@ -1291,7 +1293,16 @@ def convert( task.execute() @auto_convert("input", "filter") - def convolve(self, input: Any, filter: Any, mode: ConvolveMode) -> None: + def convolve( + self, + input: Any, + filter: Any, + mode: ConvolveMode, + method: ConvolveMethodType, + ) -> None: + if method != "auto" and runtime.num_gpus == 0: + runtime.warn(f"the method {method} is ignored on CPUs") + task = legate_runtime.create_auto_task( self.library, CuNumericOpCode.CONVOLVE ) @@ -1304,6 +1315,7 @@ def convolve(self, input: Any, filter: Any, mode: ConvolveMode) -> None: p_halo = task.declare_partition() task.add_input(input.base, p_halo) task.add_scalar_arg(input.shape, (ty.int64,)) + task.add_scalar_arg(getattr(ConvolveMethod, method.upper()), ty.int32) task.add_constraint(align(p_out, p_in)) task.add_constraint(bloat(p_out, p_halo, offsets, offsets)) diff --git a/cunumeric/_thunk/eager.py b/cunumeric/_thunk/eager.py index 868fb97bf9..ed4c4157c8 100644 --- a/cunumeric/_thunk/eager.py +++ b/cunumeric/_thunk/eager.py @@ -45,6 +45,7 @@ from ..config import BitGeneratorType, FFTType from ..types import ( BitOrder, + ConvolveMethod, ConvolveMode, NdShape, OrderType, @@ -336,17 +337,30 @@ def conj(self) -> NumPyThunk: return EagerArray(self.array.conj()) - def convolve(self, input: Any, filter: Any, mode: ConvolveMode) -> None: + def convolve( + self, + input: Any, + filter: Any, + mode: ConvolveMode, + method: ConvolveMethod, + ) -> None: self.check_eager_args(input, filter) if self.deferred is not None: - self.deferred.convolve(input, filter, mode) + self.deferred.convolve(input, filter, mode, method) else: if self.ndim == 1: + if method != "auto": + runtime.warn( + f"the method {method} is ignored " + "for the 1D convolution" + ) self.array[:] = np.convolve(input.array, filter.array, mode) else: from scipy.signal import convolve # type: ignore [import] - self.array[...] = convolve(input.array, filter.array, mode) + self.array[...] = convolve( + input.array, filter.array, mode, method + ) def fft( self, diff --git a/cunumeric/_thunk/thunk.py b/cunumeric/_thunk/thunk.py index 5dbe09264c..5edacc3b71 100644 --- a/cunumeric/_thunk/thunk.py +++ b/cunumeric/_thunk/thunk.py @@ -36,6 +36,7 @@ ) from ..types import ( BitOrder, + ConvolveMethod, ConvolveMode, NdShape, OrderType, @@ -93,7 +94,13 @@ def conj(self) -> NumPyThunk: ... @abstractmethod - def convolve(self, input: Any, filter: Any, mode: ConvolveMode) -> None: + def convolve( + self, + input: Any, + filter: Any, + mode: ConvolveMode, + method: ConvolveMethod, + ) -> None: ... @abstractmethod diff --git a/cunumeric/config.py b/cunumeric/config.py index 310ca9f416..288c554a94 100644 --- a/cunumeric/config.py +++ b/cunumeric/config.py @@ -149,6 +149,9 @@ class _CunumericSharedLib: CUNUMERIC_CONVERT_NAN_PROD: int CUNUMERIC_CONVERT_NAN_SUM: int CUNUMERIC_CONVOLVE: int + CUNUMERIC_CONVOLVE_AUTO: int + CUNUMERIC_CONVOLVE_DIRECT: int + CUNUMERIC_CONVOLVE_FFT: int CUNUMERIC_DIAG: int CUNUMERIC_DOT: int CUNUMERIC_EYE: int @@ -649,6 +652,14 @@ class BitGeneratorDistribution(IntEnum): NEGATIVE_BINOMIAL = _cunumeric.CUNUMERIC_BITGENDIST_NEGATIVE_BINOMIAL +# Match these to CuNumericConvolveMethod in cunumeric_c.h +@unique +class ConvolveMethod(IntEnum): + AUTO = _cunumeric.CUNUMERIC_CONVOLVE_AUTO + DIRECT = _cunumeric.CUNUMERIC_CONVOLVE_DIRECT + FFT = _cunumeric.CUNUMERIC_CONVOLVE_FFT + + @unique class TransferType(IntEnum): DONATE = 0 diff --git a/cunumeric/types.py b/cunumeric/types.py index 35f2e012f5..f2fbf83114 100644 --- a/cunumeric/types.py +++ b/cunumeric/types.py @@ -34,4 +34,6 @@ ConvolveMode: TypeAlias = Literal["full", "valid", "same"] +ConvolveMethod: TypeAlias = Literal["auto", "direct", "fft"] + SelectKind: TypeAlias = Literal["introselect"] diff --git a/examples/richardson_lucy.py b/examples/richardson_lucy.py index b024d46e75..25ed2154ee 100644 --- a/examples/richardson_lucy.py +++ b/examples/richardson_lucy.py @@ -22,7 +22,9 @@ # A simplified implementation of Richardson-Lucy deconvolution -def run_richardson_lucy(shape, filter_shape, num_iter, warmup, timing): +def run_richardson_lucy( + shape, filter_shape, num_iter, warmup, timing, conv_method +): image = np.random.rand(*shape).astype(float_type) psf = np.random.rand(*filter_shape).astype(float_type) im_deconv = np.full(image.shape, 0.5, dtype=float_type) @@ -33,13 +35,16 @@ def run_richardson_lucy(shape, filter_shape, num_iter, warmup, timing): for idx in range(num_iter + warmup): if idx == warmup: timer.start() - conv = np.convolve(im_deconv, psf, mode="same") + conv = np.convolve(im_deconv, psf, mode="same", method=conv_method) relative_blur = image / conv - im_deconv *= np.convolve(relative_blur, psf_mirror, mode="same") + im_deconv *= np.convolve( + relative_blur, psf_mirror, mode="same", method=conv_method + ) total = timer.stop() if timing: print("Elapsed Time: " + str(total) + " ms") + return total if __name__ == "__main__": @@ -109,6 +114,13 @@ def run_richardson_lucy(shape, filter_shape, num_iter, warmup, timing): action="store_true", help="perform timing", ) + parser.add_argument( + "--conv-method", + dest="conv_method", + type=str, + default="auto", + help="convolution method (auto by default)", + ) args, np, timer = parse_args(parser) @@ -122,5 +134,6 @@ def run_richardson_lucy(shape, filter_shape, num_iter, warmup, timing): args.I, args.warmup, args.timing, + args.conv_method, ), ) diff --git a/src/cunumeric/convolution/convolve.cc b/src/cunumeric/convolution/convolve.cc index 653933507e..daf21180da 100644 --- a/src/cunumeric/convolution/convolve.cc +++ b/src/cunumeric/convolution/convolve.cc @@ -82,7 +82,8 @@ struct ConvolveImplBody { AccessorRO in, const Rect& root_rect, const Rect& subrect, - const Rect& filter_rect) const + const Rect& filter_rect, + CuNumericConvolveMethod method) const { const Point one = Point::ONES(); Point extents = filter_rect.hi - filter_rect.lo + one; diff --git a/src/cunumeric/convolution/convolve.cu b/src/cunumeric/convolution/convolve.cu index c2c271577a..e473f3ac4f 100644 --- a/src/cunumeric/convolution/convolve.cu +++ b/src/cunumeric/convolution/convolve.cu @@ -1299,7 +1299,8 @@ __host__ static inline void cufft_convolution(AccessorWO out, AccessorRO in, const Rect& root_rect, const Rect& subrect, - const Rect& filter_rect) + const Rect& filter_rect, + CuNumericConvolveMethod method) { int device = get_device_ordinal(); auto& properties = get_device_properties(); @@ -1354,7 +1355,7 @@ __host__ static inline void cufft_convolution(AccessorWO out, for (int d = 0; d < DIM; d++) { smem_size *= (tile[d] + 2 * centers[d]); } - if (smem_size <= max_smem_size) { + if (method != CUNUMERIC_CONVOLVE_FFT && smem_size <= max_smem_size) { launch_small_tile_kernel(out, filter, in, @@ -1515,7 +1516,7 @@ __host__ static inline void cufft_convolution(AccessorWO out, ///////////// template -struct UseCUFFT { +struct CanUseCUFFT { static constexpr bool value = 1 <= DIM && DIM <= 3 && std::is_floating_point::value; }; @@ -1523,24 +1524,34 @@ template struct ConvolveImplBody { using VAL = type_of; - template ::value>* = nullptr> + template ::value>* = nullptr> __host__ void dispatch(AccessorWO<_VAL, _DIM> out, AccessorRO<_VAL, _DIM> filter, AccessorRO<_VAL, _DIM> in, const Rect<_DIM>& root_rect, const Rect<_DIM>& subrect, - const Rect<_DIM>& filter_rect) const + const Rect<_DIM>& filter_rect, + CuNumericConvolveMethod method) const { - cufft_convolution<_VAL, _DIM>(out, filter, in, root_rect, subrect, filter_rect); + if (method == CUNUMERIC_CONVOLVE_DIRECT) { + direct_convolution<_VAL, _DIM>(out, filter, in, root_rect, subrect, filter_rect); + } else { + cufft_convolution<_VAL, _DIM>(out, filter, in, root_rect, subrect, filter_rect, method); + } } - template ::value>* = nullptr> + template ::value>* = nullptr> __host__ void dispatch(AccessorWO<_VAL, _DIM> out, AccessorRO<_VAL, _DIM> filter, AccessorRO<_VAL, _DIM> in, const Rect<_DIM>& root_rect, const Rect<_DIM>& subrect, - const Rect<_DIM>& filter_rect) const + const Rect<_DIM>& filter_rect, + CuNumericConvolveMethod method) const { direct_convolution<_VAL, _DIM>(out, filter, in, root_rect, subrect, filter_rect); } @@ -1550,9 +1561,10 @@ struct ConvolveImplBody { AccessorRO in, const Rect& root_rect, const Rect& subrect, - const Rect& filter_rect) const + const Rect& filter_rect, + CuNumericConvolveMethod method) const { - dispatch(out, filter, in, root_rect, subrect, filter_rect); + dispatch(out, filter, in, root_rect, subrect, filter_rect, method); } }; diff --git a/src/cunumeric/convolution/convolve.h b/src/cunumeric/convolution/convolve.h index e20fe16031..f03d8d0373 100644 --- a/src/cunumeric/convolution/convolve.h +++ b/src/cunumeric/convolution/convolve.h @@ -34,6 +34,7 @@ struct ConvolveArgs { legate::PhysicalStore filter{nullptr}; std::vector inputs; legate::Domain root_domain; + CuNumericConvolveMethod method; }; class ConvolveTask : public CuNumericTask { diff --git a/src/cunumeric/convolution/convolve_omp.cc b/src/cunumeric/convolution/convolve_omp.cc index 6bb80383ba..6bcbc5e4ca 100644 --- a/src/cunumeric/convolution/convolve_omp.cc +++ b/src/cunumeric/convolution/convolve_omp.cc @@ -33,7 +33,8 @@ struct ConvolveImplBody { AccessorRO in, const Rect& root_rect, const Rect& subrect, - const Rect& filter_rect) const + const Rect& filter_rect, + CuNumericConvolveMethod method) const { const Point one = Point::ONES(); Point extents = filter_rect.hi - filter_rect.lo + one; diff --git a/src/cunumeric/convolution/convolve_template.inl b/src/cunumeric/convolution/convolve_template.inl index dd54c0b722..2135260e42 100644 --- a/src/cunumeric/convolution/convolve_template.inl +++ b/src/cunumeric/convolution/convolve_template.inl @@ -54,7 +54,8 @@ struct ConvolveImpl { auto input = args.inputs[0].read_accessor(input_subrect); Rect root_rect(args.root_domain); - ConvolveImplBody()(out, filter, input, root_rect, subrect, filter_rect); + ConvolveImplBody()( + out, filter, input, root_rect, subrect, filter_rect, args.method); } template * = nullptr> @@ -85,6 +86,8 @@ static void convolve_template(TaskContext& context) args.root_domain.rect_data[dim + shape.dim] = shape[dim] - 1; } + args.method = static_cast(context.scalar(1).value()); + double_dispatch(args.out.dim(), args.out.code(), ConvolveImpl{}, args); } diff --git a/src/cunumeric/cunumeric_c.h b/src/cunumeric/cunumeric_c.h index c569f786e1..70cbcfb816 100644 --- a/src/cunumeric/cunumeric_c.h +++ b/src/cunumeric/cunumeric_c.h @@ -318,6 +318,12 @@ enum CuNumericFFTType { CUNUMERIC_FFT_Z2Z = 0x69 // Double-complex to double-complex (interleaved) }; +enum CuNumericConvolveMethod { + CUNUMERIC_CONVOLVE_AUTO, + CUNUMERIC_CONVOLVE_DIRECT, + CUNUMERIC_CONVOLVE_FFT, +}; + // These fft types match CuNumericFFTDirection in config.py and cufftDirection enum CuNumericFFTDirection { CUNUMERIC_FFT_FORWARD = -1, CUNUMERIC_FFT_INVERSE = 1 }; diff --git a/tests/integration/test_convolve.py b/tests/integration/test_convolve.py index 687f11d62e..f2b8535270 100644 --- a/tests/integration/test_convolve.py +++ b/tests/integration/test_convolve.py @@ -193,6 +193,24 @@ def test_ndim(ndim): assert allclose(out_num, out_np) +@pytest.mark.parametrize( + "method", + ("auto", "direct", "fft"), +) +def test_methods(method): + shape = (5,) * 2 + arr1 = num.random.random(shape) + arr2 = num.random.random(shape) + out_num = num.convolve(arr1, arr2, mode="same", method=method) + out_np = sig.convolve(arr1, arr2, mode="same", method=method) + assert allclose(out_num, out_np) + + +def test_invalid_method(): + with pytest.raises(ValueError): + num.convolve([], [], mode="same", method="test") + + if __name__ == "__main__": import sys From e9dc0e1fcba7f25b43edf6d556132435591e98ed Mon Sep 17 00:00:00 2001 From: Robin Wang <104830875+robinwnv@users.noreply.github.com> Date: Sat, 2 Nov 2024 15:10:00 +0800 Subject: [PATCH 344/462] Port and test squeeze. (#111) * Port and test squeeze. * Add const for squeeze as a const member function --- src/cunumeric/ndarray.cc | 33 ++++ src/cunumeric/ndarray.h | 2 + src/cunumeric/operators.cc | 5 + src/cunumeric/operators.h | 3 + tests/cpp/integration/test_squeeze.cc | 212 ++++++++++++++++++++++++++ 5 files changed, 255 insertions(+) create mode 100644 tests/cpp/integration/test_squeeze.cc diff --git a/src/cunumeric/ndarray.cc b/src/cunumeric/ndarray.cc index 6828103e04..d36d6f50f1 100644 --- a/src/cunumeric/ndarray.cc +++ b/src/cunumeric/ndarray.cc @@ -1782,6 +1782,39 @@ NDArray NDArray::reshape(std::vector newshape) return NDArray(std::move(out_store)); } +NDArray NDArray::squeeze( + std::optional const>> axis) const +{ + auto result = store_; + if (!axis.has_value()) { + int shift = 0; + for (int d = 0; d < dim(); d++) { + if (result.extents().data()[d + shift] == 1) { + result = result.project(d + shift, 0); + shift -= 1; + } + } + } else { + auto computed_axis = normalize_axis_vector(axis.value(), dim()); + for (auto ax : computed_axis) { + if (shape()[ax] != 1) { + throw std::invalid_argument("can only select axes to squeeze out with size equal to one"); + } + } + int shift = 0; + for (auto dim : computed_axis) { + result = result.project(dim + shift, 0); + shift -= 1; + } + } + if (result.extents().data() == store_.extents().data()) { + return *this; + } else { + auto runtime = CuNumericRuntime::get_runtime(); + return runtime->create_array(std::move(result)); + } +} + legate::LogicalStore NDArray::get_store() { return store_; } legate::LogicalStore NDArray::broadcast(const std::vector& shape, diff --git a/src/cunumeric/ndarray.h b/src/cunumeric/ndarray.h index fb3ef09f01..d687dc28c7 100644 --- a/src/cunumeric/ndarray.h +++ b/src/cunumeric/ndarray.h @@ -111,6 +111,8 @@ class NDArray { NDArray reshape(std::vector newshape, std::string order); NDArray reshape(std::vector newshape); NDArray ravel(std::string order = "C"); + NDArray squeeze( + std::optional const>> axis = std::nullopt) const; public: NDArray as_type(const legate::Type& type); diff --git a/src/cunumeric/operators.cc b/src/cunumeric/operators.cc index d68499c81e..bdd522b09c 100644 --- a/src/cunumeric/operators.cc +++ b/src/cunumeric/operators.cc @@ -576,4 +576,9 @@ NDArray reshape(NDArray a, std::vector newshape, std::string order) NDArray ravel(NDArray a, std::string order) { return a.ravel(order); } +NDArray squeeze(NDArray a, std::optional const>> axis) +{ + return a.squeeze(axis); +} + } // namespace cunumeric diff --git a/src/cunumeric/operators.h b/src/cunumeric/operators.h index 9c2d8b6e49..b20c0460ec 100644 --- a/src/cunumeric/operators.h +++ b/src/cunumeric/operators.h @@ -159,6 +159,9 @@ NDArray reshape(NDArray a, std::vector newshape, std::string order = "C NDArray ravel(NDArray a, std::string order = "C"); +NDArray squeeze( + NDArray a, std::optional const>> axis = std::nullopt); + template bool vec_is_equal(const std::vector& vec1, const std::vector& vec2) { diff --git a/tests/cpp/integration/test_squeeze.cc b/tests/cpp/integration/test_squeeze.cc new file mode 100644 index 0000000000..116cabec07 --- /dev/null +++ b/tests/cpp/integration/test_squeeze.cc @@ -0,0 +1,212 @@ +/* Copyright 2024 NVIDIA Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +#include "common_utils.h" +#include + +using namespace cunumeric; +namespace { + +typedef std::vector, std::vector>> VEC_SHAPE_AXES; + +std::vector squeeze_result( + const std::vector& shape, + std::optional const>> axes = std::nullopt) +{ + std::vector result; + if (!axes.has_value()) { + for (int i = 0; i < shape.size(); i++) { + if (shape[i] != 1) { + result.push_back(shape[i]); + } + } + } else { + auto computed_axes = normalize_axis_vector(axes.value(), shape.size()); + for (int i = 0; i < shape.size(); i++) { + auto flag = true; + if (shape[i] == 1) { + for (int j = 0; j < computed_axes.size(); j++) { + if (computed_axes[j] == i) { + flag = false; + break; + } + } + } + if (flag) { + result.push_back(shape[i]); + } + } + } + return result; +} + +void test_squeeze( + const std::vector& shape, + std::optional const>> axes = std::nullopt) +{ + auto vec_a = mk_seq_vector(shape); + auto arr_a = mk_array(vec_a, shape); + auto x = axes.has_value() ? squeeze(arr_a, axes) : squeeze(arr_a); + auto result_shape = squeeze_result(shape, axes); + check_array(x, vec_a, result_shape); +} + +static constexpr int32_t DIM = 5; +std::vector> SIZES = { + {}, + { + 0, + }, + {1}, + {DIM}, + {0, 1}, + {1, 0}, + {1, 1}, + {1, DIM}, + {DIM, 1}, + {DIM, DIM}, + {1, 0, 0}, + {1, 1, 0}, + {1, 0, 1}, + {1, 1, 1}, + {DIM, 1, 1}, + {1, DIM, 1}, + {1, 1, DIM}, + {DIM, DIM, DIM}, +}; + +VEC_SHAPE_AXES gen_shape_axes_all() +{ + VEC_SHAPE_AXES shape_axes; + for (auto shape : SIZES) { + std::vector axes; + for (int i = 0; i < shape.size(); i++) { + if (shape[i] == 1) { + axes.push_back(i); + } + } + shape_axes.push_back({shape, axes}); + } + return shape_axes; +} + +VEC_SHAPE_AXES gen_shape_axes_single() +{ + VEC_SHAPE_AXES shape_axes; + for (auto shape : SIZES) { + std::vector axes; + for (int i = 0; i < shape.size(); i++) { + if (shape[i] == 1) { + axes.push_back(i); + } + } + for (int i = 0; i < axes.size(); i++) { + shape_axes.push_back({shape, {axes[i]}}); + } + } + return shape_axes; +} + +VEC_SHAPE_AXES gen_shape_axes_negative() +{ + VEC_SHAPE_AXES shape_axes; + for (auto shape : SIZES) { + std::vector axes; + for (int i = 0; i < shape.size(); i++) { + if (shape[i] == 1) { + axes.push_back(i - shape.size()); + } + } + if (axes.size() > 0) { + shape_axes.push_back({shape, axes}); + } + } + return shape_axes; +} + +TEST(Squeeze, Basic) +{ + for (auto shape : SIZES) { + test_squeeze(shape); + } +} + +TEST(Squeeze, AxesAll) +{ + auto SHAPE_AXES = gen_shape_axes_all(); + for (auto [shape, axes] : SHAPE_AXES) { + test_squeeze(shape, axes); + } +} + +TEST(Squeeze, AxesSingle) +{ + auto SHAPE_AXES = gen_shape_axes_single(); + for (auto [shape, axes] : SHAPE_AXES) { + test_squeeze(shape, axes); + } +} + +TEST(Squeeze, AxesNegative) +{ + auto SHAPE_AXES = gen_shape_axes_negative(); + for (auto [shape, axes] : SHAPE_AXES) { + test_squeeze(shape, axes); + } +} + +TEST(Squeeze, InvalidAxesNotEqualToOne) +{ + std::vector shape = {1, 2, 1}; + std::vector> vec_axes = {{ + 1, + }, + {0, 1}}; + auto vec_a = mk_seq_vector(shape); + auto arr_a = mk_array(vec_a, shape); + for (auto axes : vec_axes) { + EXPECT_THROW(squeeze(arr_a, axes), std::invalid_argument); + } +} + +TEST(Squeeze, InvalidAxesOutOfBound) +{ + std::vector shape = {1, 2, 1}; + std::vector> vec_axes = {{ + 3, + }, + {0, 3}, + {-4}, + {-4, 0}}; + auto vec_a = mk_seq_vector(shape); + auto arr_a = mk_array(vec_a, shape); + for (auto axes : vec_axes) { + EXPECT_THROW(squeeze(arr_a, axes), std::invalid_argument); + } +} + +TEST(Squeeze, InvalidAxesDuplicate) +{ + std::vector shape = {1, 2, 1}; + std::vector> vec_axes = {{0, -3}, {-1, 0, 2}}; + auto vec_a = mk_seq_vector(shape); + auto arr_a = mk_array(vec_a, shape); + for (auto axes : vec_axes) { + EXPECT_THROW(squeeze(arr_a, axes), std::invalid_argument); + } +} + +} // namespace From 2809e951cfaf023e0d9604e8036eda564fb7f73f Mon Sep 17 00:00:00 2001 From: Marcin Zalewski Date: Mon, 4 Nov 2024 18:48:14 -0800 Subject: [PATCH 345/462] Update version to 25.01.00 (#465) --- CMakeLists.txt | 2 +- cmake/versions.json | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 55cd0547c2..7c94756055 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -63,7 +63,7 @@ include(rapids-cuda) include(rapids-export) include(rapids-find) -set(cunumeric_version 24.09.00) +set(cunumeric_version 25.01.00) # For now we want the optimization flags to match on both normal make and cmake # builds so we override the cmake defaults here for release, this changes diff --git a/cmake/versions.json b/cmake/versions.json index 1240dad06f..03aa350349 100644 --- a/cmake/versions.json +++ b/cmake/versions.json @@ -5,11 +5,11 @@ "artifact_name": "${{ inputs.platform }}-${{ inputs.build-type }}-<>-python${{ inputs.python-version }}-${{ inputs.target-device }}-release-with_tests-${{ inputs.network }}-<>", "org": "nv-legate", "nightly_workflow": "ci-gh-nightly-release.yml", - "version": "24.09.00", + "version": "25.01.00", "git_url" : "git@github.com:nv-legate/legate.core.internal.git", "git_shallow": false, "always_download": false, - "git_tag" : "32137a65cf40c56db1db9f76bb508ade81da000a" + "git_tag" : "b6d941c9746c5a79fec92b3fd2997330acee932f" } } } From 892b293d0f7b8973e8a0015fd0deadd21020f4bf Mon Sep 17 00:00:00 2001 From: XiaLuNV <110973296+XiaLuNV@users.noreply.github.com> Date: Tue, 5 Nov 2024 15:25:59 +0800 Subject: [PATCH 346/462] remove no_tril parameter for cholesky (#456) --- cunumeric/_thunk/deferred.py | 4 ++-- cunumeric/_thunk/eager.py | 7 +++---- cunumeric/_thunk/thunk.py | 2 +- cunumeric/linalg/_cholesky.py | 27 +++++++-------------------- cunumeric/linalg/linalg.py | 4 ++-- 5 files changed, 15 insertions(+), 29 deletions(-) diff --git a/cunumeric/_thunk/deferred.py b/cunumeric/_thunk/deferred.py index 77a1a4d433..3bd6c4826f 100644 --- a/cunumeric/_thunk/deferred.py +++ b/cunumeric/_thunk/deferred.py @@ -3458,8 +3458,8 @@ def compute_strides(shape: NdShape) -> tuple[int, ...]: return result @auto_convert("src") - def cholesky(self, src: Any, no_tril: bool = False) -> None: - cholesky_deferred(self, src, no_tril) + def cholesky(self, src: Any) -> None: + cholesky_deferred(self, src) @auto_convert("q", "r") def qr(self, q: Any, r: Any) -> None: diff --git a/cunumeric/_thunk/eager.py b/cunumeric/_thunk/eager.py index ed4c4157c8..74a02214f6 100644 --- a/cunumeric/_thunk/eager.py +++ b/cunumeric/_thunk/eager.py @@ -1675,10 +1675,10 @@ def trilu(self, rhs: Any, k: int, lower: bool) -> None: else: self.array[:] = np.triu(rhs.array, k) - def cholesky(self, src: Any, no_tril: bool) -> None: + def cholesky(self, src: Any) -> None: self.check_eager_args(src) if self.deferred is not None: - self.deferred.cholesky(src, no_tril) + self.deferred.cholesky(src) else: try: result = np.linalg.cholesky(src.array) @@ -1686,8 +1686,7 @@ def cholesky(self, src: Any, no_tril: bool) -> None: from ..linalg import LinAlgError raise LinAlgError(e) from e - if no_tril: - result = np.triu(result.T.conj(), k=1) + result + self.array[:] = result def qr(self, q: Any, r: Any) -> None: diff --git a/cunumeric/_thunk/thunk.py b/cunumeric/_thunk/thunk.py index 5edacc3b71..83812001ff 100644 --- a/cunumeric/_thunk/thunk.py +++ b/cunumeric/_thunk/thunk.py @@ -704,7 +704,7 @@ def where(self, rhs1: Any, rhs2: Any, rhs3: Any) -> None: ... @abstractmethod - def cholesky(self, src: Any, no_tril: bool) -> None: + def cholesky(self, src: Any) -> None: ... @abstractmethod diff --git a/cunumeric/linalg/_cholesky.py b/cunumeric/linalg/_cholesky.py index 3775951dcd..e3f3d2e1fb 100644 --- a/cunumeric/linalg/_cholesky.py +++ b/cunumeric/linalg/_cholesky.py @@ -169,19 +169,16 @@ def gemm( task.execute() -MIN_CHOLESKY_TILE_SIZE = 2048 -MIN_CHOLESKY_MATRIX_SIZE = 8192 +MIN_CHOLESKY_TILE_SIZE = 2 if settings.test() else 2048 +MIN_CHOLESKY_MATRIX_SIZE = 4 if settings.test() else 8192 # TODO: We need a better cost model def choose_color_shape( runtime: Runtime, shape: tuple[int, ...] ) -> tuple[int, ...]: - if settings.test(): - num_tiles = runtime.num_procs * 2 - return (num_tiles, num_tiles) - extent = shape[0] + # If there's only one processor or the matrix is too small, # don't even bother to partition it at all if runtime.num_procs == 1 or extent <= MIN_CHOLESKY_MATRIX_SIZE: @@ -254,16 +251,9 @@ def _batched_cholesky( task.execute() -def cholesky_deferred( - output: DeferredArray, input: DeferredArray, no_tril: bool -) -> None: +def cholesky_deferred(output: DeferredArray, input: DeferredArray) -> None: library = runtime.library if len(input.base.shape) > 2: - if no_tril: - raise NotImplementedError( - "batched cholesky expects to only " - "produce the lower triangular matrix" - ) size = input.base.shape[-1] # Choose 32768 as dimension cutoff for warning # so that for float64 anything larger than @@ -280,8 +270,7 @@ def cholesky_deferred( if runtime.num_procs == 1: transpose_copy_single(library, input.base, output.base) potrf_single(library, output.base) - if not no_tril: - tril_single(library, output.base) + tril_single(library, output.base) return shape = tuple(output.base.shape) @@ -295,8 +284,7 @@ def cholesky_deferred( library, shape[0], MIN_CHOLESKY_TILE_SIZE, input.base, output.base ) - if not no_tril: - tril_single(library, output.base) + tril_single(library, output.base) else: initial_color_shape = choose_color_shape(runtime, shape) tile_shape = _rounding_divide(shape, initial_color_shape) @@ -314,5 +302,4 @@ def cholesky_deferred( syrk(library, p_output, k, i) gemm(library, p_output, k, i, k + 1, n) - if not no_tril: - tril(library, p_output, n) + tril(library, p_output, n) diff --git a/cunumeric/linalg/linalg.py b/cunumeric/linalg/linalg.py index 31f64eca0a..8a47e1bcf3 100644 --- a/cunumeric/linalg/linalg.py +++ b/cunumeric/linalg/linalg.py @@ -700,7 +700,7 @@ def norm( raise ValueError("Improper number of dimensions to norm") -def _thunk_cholesky(a: ndarray, no_tril: bool = False) -> ndarray: +def _thunk_cholesky(a: ndarray) -> ndarray: """Cholesky decomposition. Return the Cholesky decomposition, `L * L.H`, of the square matrix `a`, @@ -744,7 +744,7 @@ def _thunk_cholesky(a: ndarray, no_tril: bool = False) -> ndarray: dtype=input.dtype, inputs=(input,), ) - output._thunk.cholesky(input._thunk, no_tril=no_tril) + output._thunk.cholesky(input._thunk) return output From cdb806ad3d0e50d452576a6d3ede3cf258181c6c Mon Sep 17 00:00:00 2001 From: Parag Kulkarni Date: Tue, 5 Nov 2024 19:11:21 +0530 Subject: [PATCH 347/462] Use legate-gh-ci v1.18 (#452) - Uploads nightly packages to anaconda. --- .github/workflows/gh-build-and-test.yml | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/.github/workflows/gh-build-and-test.yml b/.github/workflows/gh-build-and-test.yml index b9890641a0..fbc6425061 100644 --- a/.github/workflows/gh-build-and-test.yml +++ b/.github/workflows/gh-build-and-test.yml @@ -51,14 +51,14 @@ jobs: needs: setup-build name: "Build (${{ inputs.platform }}, ${{ inputs.target-device }}, ${{ inputs.build-type }}, Python ${{ inputs.python-version }})" uses: - nv-legate/legate-gh-ci/.github/workflows/gh-build.yml@v1.17 + nv-legate/legate-gh-ci/.github/workflows/gh-build.yml@v1.18 with: build-mode: "" build-type: ${{ inputs.build-type }} client-repo: ${{ github.event.repository.name }} dependencies-file: "cmake/versions.json" dependencies-workflow: ${{ inputs.dependencies-workflow }} - legate-gh-ci-tag: "v1.17" + legate-gh-ci-tag: "v1.18" network: "ucx" platform: ${{ inputs.platform }} python-version: ${{ inputs.python-version }} @@ -72,13 +72,13 @@ jobs: needs: setup-build name: "Check if legate.internal nightly exists for SHA specified in versions.json (${{ inputs.platform }}, ${{ inputs.target-device }}, ${{ inputs.build-type }})" uses: - nv-legate/legate-gh-ci/.github/workflows/gh-check-if-nightly-exists-for-all-dependencies.yml@v1.17 + nv-legate/legate-gh-ci/.github/workflows/gh-check-if-nightly-exists-for-all-dependencies.yml@v1.18 with: build-mode: "" build-type: ${{ inputs.build-type }} client-repo: ${{ github.event.repository.name }} dependencies-file: "cmake/versions.json" - legate-gh-ci-tag: "v1.17" + legate-gh-ci-tag: "v1.18" network: "ucx" platform: ${{ inputs.platform }} python-version: ${{ inputs.python-version }} @@ -92,12 +92,12 @@ jobs: if: ${{ github.repository_owner == 'nv-legate' && contains(github.workflow, 'release') && inputs.upload-enabled == true }} name: Upload package to Server uses: - nv-legate/legate-gh-ci/.github/workflows/gh-upload.yml@v1.17 + nv-legate/legate-gh-ci/.github/workflows/gh-upload.yml@v1.18 with: build-mode: "" build-type: ${{ inputs.build-type }} client-repo: ${{ github.event.repository.name }} - legate-gh-ci-tag: "v1.17" + legate-gh-ci-tag: "v1.18" name: Upload package to Server network: "ucx" pkgSubString: "cunumeric-" @@ -180,13 +180,13 @@ jobs: matrix: ${{fromJson(needs.setup-test.outputs.matrix)}} uses: - nv-legate/legate-gh-ci/.github/workflows/gh-test-within-container.yml@v1.17 + nv-legate/legate-gh-ci/.github/workflows/gh-test-within-container.yml@v1.18 with: build-mode: "" build-type: ${{ inputs.build-type }} client-repo: ${{ github.event.repository.name }} has-gpu: ${{ matrix.runner.type == 'gpu' }} - legate-gh-ci-tag: "v1.17" + legate-gh-ci-tag: "v1.18" name: ${{ matrix.test-config.name }} network: "ucx" platform: ${{ inputs.platform }} @@ -202,12 +202,12 @@ jobs: name: Update Test status on Server if: ${{ (github.repository_owner == 'nv-legate') && contains(github.workflow, 'Nightly') && (inputs.upload-enabled == true) }} uses: - nv-legate/legate-gh-ci/.github/workflows/gh-upload.yml@v1.17 + nv-legate/legate-gh-ci/.github/workflows/gh-upload.yml@v1.18 with: build-mode: "" build-type: ${{ inputs.build-type }} client-repo: ${{ github.event.repository.name }} - legate-gh-ci-tag: "v1.17" + legate-gh-ci-tag: "v1.18" name: UpdateTestStatus network: "ucx" pkgSubString: "cunumeric-" From 38fbcbb965ce54eaebc4faa5271182add1b968c3 Mon Sep 17 00:00:00 2001 From: Manolis Papadakis Date: Tue, 5 Nov 2024 21:51:34 -0800 Subject: [PATCH 348/462] Clean up docs, bump to latest Legate (#470) --- cmake/versions.json | 2 +- docs/cunumeric/source/index.rst | 5 -- docs/cunumeric/source/user/usage.rst | 106 +-------------------------- docs/cunumeric/source/versions.rst | 14 ---- docs/cunumeric/switcher.json | 45 ++++++++++++ 5 files changed, 50 insertions(+), 122 deletions(-) delete mode 100644 docs/cunumeric/source/versions.rst diff --git a/cmake/versions.json b/cmake/versions.json index 03aa350349..683231ef2d 100644 --- a/cmake/versions.json +++ b/cmake/versions.json @@ -9,7 +9,7 @@ "git_url" : "git@github.com:nv-legate/legate.core.internal.git", "git_shallow": false, "always_download": false, - "git_tag" : "b6d941c9746c5a79fec92b3fd2997330acee932f" + "git_tag" : "06b1a529f84ec86ce425b0ba453f3344b90f1090" } } } diff --git a/docs/cunumeric/source/index.rst b/docs/cunumeric/source/index.rst index afd32f6530..8666081688 100644 --- a/docs/cunumeric/source/index.rst +++ b/docs/cunumeric/source/index.rst @@ -22,11 +22,6 @@ Using cuNumeric you do things like run the final example of the faqs developer/index -.. toctree:: - :maxdepth: 1 - - versions - Indices and tables ------------------ diff --git a/docs/cunumeric/source/user/usage.rst b/docs/cunumeric/source/user/usage.rst index 384e8d74ab..3cec1c12d3 100644 --- a/docs/cunumeric/source/user/usage.rst +++ b/docs/cunumeric/source/user/usage.rst @@ -3,9 +3,6 @@ Usage ===== -Running cuNumeric programs --------------------------- - Using cuNumeric as a replacement for NumPy is simple. Replace your NumPy import statement with cuNumeric: @@ -46,103 +43,8 @@ And run the program, like this python main.py -By default, this command will use 4 CPUs to run the program, but is -configurable through the LEGATE_CONFIG environment variable. For -example, to use 2 GPUs instead, run the following - -.. code-block:: sh - - LEGATE_CONFIG="--gpus 2" python main.py - -For execution with multiple nodes (assuming Legate is installed -with networking support) users can supply the `--nodes` option. - - -For more information on how resources can be allocated using this -environment variable, see `Using LEGATE_CONFIG`_. - -.. note:: - - Usage of standard Python is intended as a quick on-ramp for users to try - out cuNumeric more easily. Several legate command line configuration - options, especially for multi-node execution, are not available when - running programs with standard Python. See the output of ``legate --help`` - for more details. - -To fully utilize the power of cuNumeric and overcome these restrictions, we -recommend requesting resource allocation using Legate. - -Resource allocation -------------------- - -Legate allows you to prescribe the resources required to successfully execute -your application. Applications can be run on three different types of -processors, also known as task variants: CPU, OMP, and GPU. The OMP variant -will use OpenMP threads to parallelize your application while the CPU variant -will use individual processes per processor. In addition to the number or -processors, you can also specify the amount of memory required for your -application on each of these processors. - -Check the relevant command line arguments to legate and their default values -before using them. In summary, if you want to change the number of processors, -make sure to check out the following arguments in the documentation for legate: -``--cpus``, ``--omps``, ``--ompthreads``, and ``--gpus``. Similarly, if you -need to change the amount of memory required for your application, check the -following arguments: ``--sysmem``, ``--numamem``, and ``--fbmem``. - -Legate reserves a fraction of the requested memory, denoted by -``--eager-alloc-percentage``, to be used eagerly, with the rest used for -deferred allocations. Reducing this typically helps you run larger problems. - -If you encounter errors related to resource allocation, check out our -:ref:`faqs` to debug them. - -Using legate launcher -~~~~~~~~~~~~~~~~~~~~~ - -To run the above program using four OpenMP threads using the Legate launcher, -run the following command - -.. code-block:: sh - - legate --omps 1 --ompthreads 4 --sysmem 40000 --eager-alloc-percentage 10 ./main.py - -This will use one OpenMP group and two OpenMP threads to parallelize the -application. We defer discussions on changing the OpenMP group to a later -section. - -To run on 8 CPUs and use 40GB of system memory with 10% of that memory reserved -for eager allocations, use the following command: - -.. code-block:: sh - - legate --cpus 8 --sysmem 40000 --eager-alloc-percentage 10 ./main.py - -To run on multiple GPUs and use 40GB of framebuffer memory per GPU with 10% -of that memory reserved for eager allocations, use the following command: - -.. code-block:: sh - - legate --gpus 2 --fbmem 40000 --eager-alloc-percentage 10 ./main.py - -Using LEGATE_CONFIG -~~~~~~~~~~~~~~~~~~~ - -All of the above commands can also be passed through the environment variable -``LEGATE_CONFIG`` as shown below: - -.. code-block:: sh - - LEGATE_CONFIG="--omps 1 --ompthreads 4 --sysmem 40000 --eager-alloc-percentage 10" legate main.py - -.. code-block:: sh - - LEGATE_CONFIG="--cpus 8 --sysmem 40000 --eager-alloc-percentage 10" legate main.py - -.. code-block:: sh - - LEGATE_CONFIG="--gpus 2 --fbmem 40000 --eager-alloc-percentage 10" legate main.py +By default this invocation will use all the hardware resources (e.g. CPU cores, +RAM, GPUs) available on the current machine. -Using the environment variable might be useful for users using the same set of -resources for their runs where they can just set the environment variable once -and use ``legate main.py`` for all subsequent runs. +For more information on controlling the resource allocation, running on multiple +nodes etc. see https://docs.nvidia.com/legate/latest/usage.html. diff --git a/docs/cunumeric/source/versions.rst b/docs/cunumeric/source/versions.rst deleted file mode 100644 index 1760786d8e..0000000000 --- a/docs/cunumeric/source/versions.rst +++ /dev/null @@ -1,14 +0,0 @@ -Versions -======== - -.. toctree:: - :caption: Versions: - - 22.05 - 22.08 - 22.10 - 23.01 - 23.03 - 23.07 - 23.09 - 23.11 diff --git a/docs/cunumeric/switcher.json b/docs/cunumeric/switcher.json index e62a26d440..2a0dbc64ca 100644 --- a/docs/cunumeric/switcher.json +++ b/docs/cunumeric/switcher.json @@ -1,7 +1,52 @@ [ + { + "name": "22.05", + "version": "22.05", + "url": "https://nv-legate.github.io/cunumeric/22.05/" + }, + { + "name": "22.08", + "version": "22.08", + "url": "https://nv-legate.github.io/cunumeric/22.08/" + }, + { + "name": "22.10", + "version": "22.10", + "url": "https://nv-legate.github.io/cunumeric/22.10/" + }, + { + "name": "23.01", + "version": "23.01", + "url": "https://nv-legate.github.io/cunumeric/23.01/" + }, + { + "name": "23.03", + "version": "23.03", + "url": "https://nv-legate.github.io/cunumeric/23.03/" + }, + { + "name": "23.07", + "version": "23.07", + "url": "https://nv-legate.github.io/cunumeric/23.07/" + }, + { + "name": "23.09", + "version": "23.09", + "url": "https://nv-legate.github.io/cunumeric/23.09/" + }, + { + "name": "23.11", + "version": "23.11", + "url": "https://nv-legate.github.io/cunumeric/23.11/" + }, { "name": "24.06", "version": "24.06", "url": "https://docs.nvidia.com/cunumeric/24.06/" + }, + { + "name": "24.11", + "version": "24.11", + "url": "https://docs.nvidia.com/cunumeric/24.11/" } ] \ No newline at end of file From 919ccc04e3881601313952da1e2bee2cdedfd1c6 Mon Sep 17 00:00:00 2001 From: Bryan Van de Ven Date: Wed, 6 Nov 2024 08:37:35 -0800 Subject: [PATCH 349/462] More Numpy 2 fixes (#454) * handle different np2 exception type * fix should_wrap * checkpoint * use implicit callable check unconditionally * restore RandomState test * remove deprecated 0d test -- align with np2 * handle overflow errors * fix up nonzero 0d error tests * follow np2 behavior for overflow --- cunumeric/_module/creation_shape.py | 4 ++++ cunumeric/_utils/coverage.py | 16 ++++++------- tests/integration/test_array_creation.py | 5 ++++ tests/integration/test_nonzero.py | 30 +++++++----------------- tests/integration/test_random.py | 14 +++++------ tests/integration/test_reduction.py | 2 +- 6 files changed, 32 insertions(+), 39 deletions(-) diff --git a/cunumeric/_module/creation_shape.py b/cunumeric/_module/creation_shape.py index b208bc57bd..6c51de532f 100644 --- a/cunumeric/_module/creation_shape.py +++ b/cunumeric/_module/creation_shape.py @@ -351,6 +351,8 @@ def full( else: dtype = np.dtype(dtype) val = np.array(value, dtype=dtype) + if np.dtype(dtype).itemsize == 1 and value > 255: + raise OverflowError(f"Value {value} out of bounds for {dtype}") result = empty(shape, dtype=val.dtype) result._thunk.fill(val) return result @@ -395,6 +397,8 @@ def full_like( dtype = np.dtype(dtype) else: dtype = a.dtype + if np.dtype(dtype).itemsize == 1 and value > 255: + raise OverflowError(f"Value {value} out of bounds for {dtype}") result = empty_like(a, dtype=dtype, shape=shape) val = np.array(value).astype(dtype) result._thunk.fill(val) diff --git a/cunumeric/_utils/coverage.py b/cunumeric/_utils/coverage.py index 3b87bb89f6..af43a6b640 100644 --- a/cunumeric/_utils/coverage.py +++ b/cunumeric/_utils/coverage.py @@ -17,13 +17,7 @@ import warnings from dataclasses import dataclass from functools import WRAPPER_ASSIGNMENTS, wraps -from types import ( - BuiltinFunctionType, - FunctionType, - MethodDescriptorType, - MethodType, - ModuleType, -) +from types import BuiltinFunctionType, FunctionType, ModuleType from typing import Any, Callable, Container, Iterable, Mapping, Protocol, cast from legate.core import track_provenance @@ -306,7 +300,13 @@ def clone_module( def should_wrap(obj: object) -> bool: - return isinstance(obj, (FunctionType, MethodType, MethodDescriptorType)) + # custom callables, e.g. cython used in np2, do not inherit anything. See + # https://github.com/nv-legate/cunumeric.internal/issues/179#issuecomment-2423813051 + return ( + callable(obj) + and hasattr(obj, "__get__") + and not hasattr(obj, "__set__") + ) def clone_class( diff --git a/tests/integration/test_array_creation.py b/tests/integration/test_array_creation.py index 65e9b3c821..d9bc1e76a5 100644 --- a/tests/integration/test_array_creation.py +++ b/tests/integration/test_array_creation.py @@ -195,6 +195,11 @@ def test_func_like(fn, x_np, dtype, shape): @pytest.mark.parametrize("x_np, dtype", DATA_ARGS) @pytest.mark.parametrize("shape", SHAPE_ARG) def test_full_like(x_np, dtype, value, shape): + if np.dtype(dtype).itemsize == 1 and value > 255: + with pytest.raises(OverflowError): + num.full_like(x_np, value, dtype=dtype, shape=shape) + return + shape = shape if shape is None else x_np.reshape(shape).shape x = num.array(x_np) diff --git a/tests/integration/test_nonzero.py b/tests/integration/test_nonzero.py index 9c4c44b97a..990e35fa43 100644 --- a/tests/integration/test_nonzero.py +++ b/tests/integration/test_nonzero.py @@ -18,6 +18,7 @@ from utils.utils import AxisError import cunumeric as num +from cunumeric._utils import is_np2 # cunumeric.count_nonzero(a: ndarray, # axis: Optional[Union[int, tuple[int, ...]]] = None) → Union[int, ndarray] @@ -51,6 +52,13 @@ SIZES = NO_EMPTY_SIZE + EMPTY_SIZES +@pytest.mark.skipif(not is_np2, reason="numpy 1.0 does not raise") +@pytest.mark.parametrize("value", (0, 1, 2, 7)) +def test_0d_error(value): + with pytest.raises(ValueError): + num.nonzero(value) + + @pytest.mark.parametrize("size", EMPTY_SIZES) def test_empty(size): arr_np = np.random.randint(-5, 5, size=size) @@ -164,28 +172,6 @@ def test_flatnonzero(size): np.array_equal(res_np, res_num) -def test_deprecated_0d(): - with pytest.deprecated_call(): - assert num.count_nonzero(num.array(0)) == 0 - assert num.count_nonzero(num.array(0, dtype="?")) == 0 - assert_equal(num.nonzero(0), np.nonzero(0)) - - with pytest.deprecated_call(): - assert num.count_nonzero(num.array(1)) == 1 - assert num.count_nonzero(num.array(1, dtype="?")) == 1 - assert_equal(num.nonzero(1), np.nonzero(1)) - - with pytest.deprecated_call(): - assert_equal(num.nonzero(0), ([],)) - - with pytest.deprecated_call(): - assert_equal(num.nonzero(1), ([0],)) - - x_np = np.array([True, True]) - x = num.array(x_np) - assert np.array_equal(x_np.nonzero(), x.nonzero()) - - if __name__ == "__main__": import sys diff --git a/tests/integration/test_random.py b/tests/integration/test_random.py index e238fee2ca..6ec69e7b59 100644 --- a/tests/integration/test_random.py +++ b/tests/integration/test_random.py @@ -16,8 +16,9 @@ import cunumeric as num + @pytest.mark.xfail( - reason = "https://github.com/nv-legate/cunumeric.internal/issues/199" + reason="https://github.com/nv-legate/cunumeric.internal/issues/199" ) def test_basic_num() -> None: num.random.seed(10) @@ -28,7 +29,7 @@ def test_basic_num() -> None: @pytest.mark.xfail( - reason = "numpy failures in random.mtrand.RandomState.standard_normal" + reason="numpy failures in random.mtrand.RandomState.standard_normal" ) def test_basic_np() -> None: np.random.seed(10) @@ -44,7 +45,7 @@ def test_basic_np() -> None: @pytest.mark.xfail( - reason = "https://github.com/nv-legate/cunumeric.internal/issues/199" + reason="https://github.com/nv-legate/cunumeric.internal/issues/199" ) def test_none_num() -> None: num.random.seed() @@ -60,7 +61,7 @@ def test_none_num() -> None: @pytest.mark.xfail( - reason = "numpy failures in random.mtrand.RandomState.standard_normal" + reason="numpy failures in random.mtrand.RandomState.standard_normal" ) def test_none_np() -> None: np.random.seed() @@ -76,7 +77,7 @@ def test_none_np() -> None: @pytest.mark.xfail( - reason = "numpy failures in random.mtrand.RandomState.standard_normal" + reason="numpy failures in random.mtrand.RandomState.standard_normal" ) def test_basic_num_np() -> None: np.random.seed(10) @@ -86,9 +87,6 @@ def test_basic_num_np() -> None: assert not np.array_equal(L1, L2) -@pytest.mark.xfail( - reason = "https://github.com/nv-legate/cunumeric.internal/issues/199" -) def test_RandomState() -> None: rdm_num = num.random.RandomState(10) L1 = rdm_num.randn(3, 3) diff --git a/tests/integration/test_reduction.py b/tests/integration/test_reduction.py index ea341ec607..698d145723 100644 --- a/tests/integration/test_reduction.py +++ b/tests/integration/test_reduction.py @@ -140,7 +140,7 @@ def test_initial_scalar_list(self): def test_initial_list(self): arr = [[1, 2], [3, 4]] initial_value = [2, 3] - with pytest.raises(ValueError): + with pytest.raises((ValueError, TypeError)): num.sum(arr, initial=initial_value) @pytest.mark.xfail From 8ddf1a8e44aac67c2da911346cab951c6903dad1 Mon Sep 17 00:00:00 2001 From: Robin Wang <104830875+robinwnv@users.noreply.github.com> Date: Thu, 7 Nov 2024 09:02:44 +0800 Subject: [PATCH 350/462] Port and test where. (#95) * Port and test where. * Address comments. --- src/cunumeric/ndarray.cc | 42 ++++- src/cunumeric/ndarray.h | 7 +- src/cunumeric/operators.cc | 32 +++- src/cunumeric/operators.h | 9 +- tests/cpp/integration/common_utils.h | 2 +- tests/cpp/integration/test_where.cc | 273 +++++++++++++++++++++++++++ 6 files changed, 358 insertions(+), 7 deletions(-) create mode 100644 tests/cpp/integration/test_where.cc diff --git a/src/cunumeric/ndarray.cc b/src/cunumeric/ndarray.cc index d36d6f50f1..34631c5cda 100644 --- a/src/cunumeric/ndarray.cc +++ b/src/cunumeric/ndarray.cc @@ -712,7 +712,7 @@ NDArray NDArray::swapaxes(int32_t axis1, int32_t axis2) return runtime->create_array(std::move(transposed)); } -NDArray NDArray::as_type(const legate::Type& type) +NDArray NDArray::as_type(const legate::Type& type) const { auto runtime = CuNumericRuntime::get_runtime(); @@ -1815,10 +1815,48 @@ NDArray NDArray::squeeze( } } +void NDArray::where(NDArray rhs1, NDArray rhs2, NDArray rhs3) +{ + const auto& out_shape = shape(); + auto rhs1_store = broadcast(out_shape, rhs1.store_); + auto rhs2_store = broadcast(out_shape, rhs2.store_); + auto rhs3_store = broadcast(out_shape, rhs3.store_); + assert(store_.type() == rhs2.store_.type()); + assert(store_.type() == rhs3.store_.type()); + + auto runtime = CuNumericRuntime::get_runtime(); + auto task = runtime->create_task(CuNumericOpCode::CUNUMERIC_WHERE); + + auto p_lhs = task.declare_partition(); + auto p_rhs1 = task.declare_partition(); + auto p_rhs2 = task.declare_partition(); + auto p_rhs3 = task.declare_partition(); + + task.add_output(store_, p_lhs); + task.add_input(rhs1_store, p_rhs1); + task.add_input(rhs2_store, p_rhs2); + task.add_input(rhs3_store, p_rhs3); + + task.add_constraint(legate::align(p_lhs, p_rhs1)); + task.add_constraint(legate::align(p_lhs, p_rhs2)); + task.add_constraint(legate::align(p_lhs, p_rhs3)); + + runtime->submit(std::move(task)); +} + +NDArray NDArray::_maybe_convert(const legate::Type& type) const +{ + if (type == store_.type()) { + return *this; + } else { + return as_type(type); + } +} + legate::LogicalStore NDArray::get_store() { return store_; } legate::LogicalStore NDArray::broadcast(const std::vector& shape, - legate::LogicalStore& store) + legate::LogicalStore& store) const { int32_t diff = static_cast(shape.size()) - store.dim(); diff --git a/src/cunumeric/ndarray.h b/src/cunumeric/ndarray.h index d687dc28c7..2f5a4a0d82 100644 --- a/src/cunumeric/ndarray.h +++ b/src/cunumeric/ndarray.h @@ -113,9 +113,10 @@ class NDArray { NDArray ravel(std::string order = "C"); NDArray squeeze( std::optional const>> axis = std::nullopt) const; + void where(NDArray rhs1, NDArray rhs2, NDArray rhs3); public: - NDArray as_type(const legate::Type& type); + NDArray as_type(const legate::Type& type) const; legate::LogicalStore get_store(); void sort(NDArray rhs, bool argsort, std::optional axis = -1, bool stable = false); NDArray _convert_future_to_regionfield(bool change_shape = false); @@ -134,9 +135,11 @@ class NDArray { std::optional initial, std::optional where); NDArray copy(); + NDArray _maybe_convert(const legate::Type& type) const; private: - legate::LogicalStore broadcast(const std::vector& shape, legate::LogicalStore& store); + legate::LogicalStore broadcast(const std::vector& shape, + legate::LogicalStore& store) const; legate::LogicalStore broadcast(NDArray rhs1, NDArray rhs2); void sort_task(NDArray rhs, bool argsort, bool stable); void sort_swapped(NDArray rhs, bool argsort, int32_t sort_axis, bool stable); diff --git a/src/cunumeric/operators.cc b/src/cunumeric/operators.cc index bdd522b09c..c03c0ae0db 100644 --- a/src/cunumeric/operators.cc +++ b/src/cunumeric/operators.cc @@ -477,7 +477,7 @@ int32_t normalize_axis_index(int32_t axis, int32_t ndim) return axis; } -std::vector normalize_axis_vector(std::vector axis, +std::vector normalize_axis_vector(const std::vector& axis, int32_t ndim, bool allow_duplicate) { @@ -581,4 +581,34 @@ NDArray squeeze(NDArray a, std::optional where(NDArray a) { return nonzero(a); } + +NDArray where(NDArray a, NDArray x, NDArray y) +{ + auto rhs1 = a._maybe_convert(legate::bool_()); + auto common_type = find_common_type({x, y}); + auto rhs2 = x._maybe_convert(common_type); + auto rhs3 = y._maybe_convert(common_type); + + auto out_shape = broadcast_shapes({rhs1, rhs2, rhs3}); + auto runtime = CuNumericRuntime::get_runtime(); + auto out = runtime->create_array(std::move(out_shape), common_type); + out.where(std::move(rhs1), std::move(rhs2), std::move(rhs3)); + return out; +} + +legate::Type find_common_type(const std::vector& arrays) +{ + legate::Type max_type = legate::bool_(); + for (auto arr : arrays) { + if (!arr.type().is_primitive()) { + throw std::invalid_argument("Type must be a primitive type"); + } + if (arr.type().code() > max_type.code()) { + max_type = arr.type(); + } + } + return max_type; +} + } // namespace cunumeric diff --git a/src/cunumeric/operators.h b/src/cunumeric/operators.h index b20c0460ec..8048211950 100644 --- a/src/cunumeric/operators.h +++ b/src/cunumeric/operators.h @@ -136,7 +136,7 @@ NDArray repeat(NDArray a, int64_t repeats, std::optional axis = std::nu // helper methods int32_t normalize_axis_index(int32_t axis, int32_t ndim); -std::vector normalize_axis_vector(std::vector axis, +std::vector normalize_axis_vector(const std::vector& axis, int32_t ndim, bool allow_duplicate = false); @@ -162,6 +162,13 @@ NDArray ravel(NDArray a, std::string order = "C"); NDArray squeeze( NDArray a, std::optional const>> axis = std::nullopt); +std::vector where(NDArray a); + +NDArray where(NDArray a, NDArray x, NDArray y); + +// helper methods +legate::Type find_common_type(const std::vector& arrays); + template bool vec_is_equal(const std::vector& vec1, const std::vector& vec2) { diff --git a/tests/cpp/integration/common_utils.h b/tests/cpp/integration/common_utils.h index e919c50c17..d2d89544b9 100644 --- a/tests/cpp/integration/common_utils.h +++ b/tests/cpp/integration/common_utils.h @@ -81,7 +81,7 @@ void check_and_wrap(NDArray& a, const std::vector& values, std::vector); + ASSERT_EQ(a.type().code(), legate::type_code_of_v); if (a.dim() > 1) { a = a._wrap(a.size()); diff --git a/tests/cpp/integration/test_where.cc b/tests/cpp/integration/test_where.cc new file mode 100644 index 0000000000..5abec00cfc --- /dev/null +++ b/tests/cpp/integration/test_where.cc @@ -0,0 +1,273 @@ +/* Copyright 2024 NVIDIA Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +#include +#include +#include + +#include +#include "legate.h" +#include "cunumeric.h" +#include "common_utils.h" + +using namespace cunumeric; + +template +void test_where_basic(std::vector in_a, + std::vector>& exp_vec, + std::vector in_shape) +{ + auto A = mk_array(in_a, in_shape); + auto B = where(A); + assert(exp_vec.size() == B.size()); + for (size_t i = 0; i < B.size(); i++) { + auto exp_arr = exp_vec[i]; + std::vector exp_shape = {exp_arr.size()}; + check_array(B[i], exp_arr, exp_shape); + } +} + +template +void test_where_full( + NDArray A, NDArray X, NDArray Y, std::vector exp_arr, std::vector exp_shape) +{ + auto B = where(A, X, Y); + check_array(B, exp_arr, exp_shape); +} + +TEST(Where, Basic) +{ + std::vector in_a = {-1, 54, 4, 4, 0, 45, 5, 58, 0, 9, 0, 4, 0, 0, 0, 5, 0, 1}; + std::vector> test_shapes = {{18}, {6, 3}, {3, 2, 3}}; + + std::vector exp_vec1_1 = {0, 1, 2, 3, 5, 6, 7, 9, 11, 15, 17}; + std::vector> exp_vec1 = {exp_vec1_1}; + test_where_basic(in_a, exp_vec1, test_shapes[0]); + + std::vector exp_vec2_1 = {0, 0, 0, 1, 1, 2, 2, 3, 3, 5, 5}; + std::vector exp_vec2_2 = {0, 1, 2, 0, 2, 0, 1, 0, 2, 0, 2}; + std::vector> exp_vec2 = {exp_vec2_1, exp_vec2_2}; + test_where_basic(in_a, exp_vec2, test_shapes[1]); + + std::vector exp_vec3_1 = {0, 0, 0, 0, 0, 1, 1, 1, 1, 2, 2}; + std::vector exp_vec3_2 = {0, 0, 0, 1, 1, 0, 0, 1, 1, 1, 1}; + std::vector exp_vec3_3 = {0, 1, 2, 0, 2, 0, 1, 0, 2, 0, 2}; + std::vector> exp_vec3 = {exp_vec3_1, exp_vec3_2, exp_vec3_3}; + test_where_basic(in_a, exp_vec3, test_shapes[2]); +} + +TEST(Where, Condition) +{ + std::vector shape = {2, 2}; + auto X = mk_array({1, 2, 3, 4}, shape); + auto Y = mk_array({9, 8, 7, 6}, shape); + + auto A1 = mk_array({true, false, true, true}, shape); + test_where_full(A1, X, Y, {1, 8, 3, 4}, shape); + + auto A2 = mk_array({true, false}, {1, 2}); + test_where_full(A2, X, Y, {1, 8, 3, 6}, shape); + + auto A3 = mk_array({true, false}, + { + 2, + }); + test_where_full(A3, X, Y, {1, 8, 3, 6}, shape); + + auto A4 = mk_array({0.0, 1.0, 0, -2}, shape); + test_where_full(A4, X, Y, {9, 2, 7, 4}, shape); +} + +TEST(Where, Type) +{ + std::vector shape = {2, 2}; + auto A = mk_array({true, false, true, true}, shape); + auto X_BOOL = mk_array({true, false, true, false}, shape); + auto X_INT = mk_array({1, 2, 3, 4}, shape); + auto X_FLOAT = mk_array({1, 2, 3, 4}, shape); + auto X_COMPLEX128 = mk_array>({1, 2, 3, 4}, shape); + auto Y_BOOL = mk_array({false, true, true, false}, shape); + auto Y_INT = mk_array({9, 8, 7, 6}, shape); + auto Y_FLOAT = mk_array({9, 8, 7, 6}, shape); + auto Y_COMPLEX128 = mk_array>({9, 8, 7, 6}, shape); + + test_where_full(A, X_BOOL, Y_BOOL, {true, true, true, false}, shape); + + test_where_full(A, X_BOOL, Y_INT, {1, 8, 1, 0}, shape); + test_where_full(A, X_INT, Y_INT, {1, 8, 3, 4}, shape); + test_where_full(A, Y_INT, X_BOOL, {9, 0, 7, 6}, shape); + + test_where_full(A, X_BOOL, Y_FLOAT, {1, 8, 1, 0}, shape); + test_where_full(A, X_INT, Y_FLOAT, {1, 8, 3, 4}, shape); + test_where_full(A, X_FLOAT, Y_FLOAT, {1, 8, 3, 4}, shape); + test_where_full(A, Y_FLOAT, X_BOOL, {9, 0, 7, 6}, shape); + test_where_full(A, Y_FLOAT, X_INT, {9, 2, 7, 6}, shape); + + test_where_full>(A, X_BOOL, Y_COMPLEX128, {1, 8, 1, 0}, shape); + test_where_full>(A, X_INT, Y_COMPLEX128, {1, 8, 3, 4}, shape); + test_where_full>(A, X_FLOAT, Y_COMPLEX128, {1, 8, 3, 4}, shape); + test_where_full>(A, X_COMPLEX128, Y_COMPLEX128, {1, 8, 3, 4}, shape); + test_where_full>(A, Y_COMPLEX128, X_BOOL, {9, 0, 7, 6}, shape); + test_where_full>(A, Y_COMPLEX128, X_INT, {9, 2, 7, 6}, shape); + test_where_full>(A, Y_COMPLEX128, X_FLOAT, {9, 2, 7, 6}, shape); +} + +TEST(Where, BroadcastShape) +{ + auto X = mk_array({1, 2, 3, 4, 5, 6, 7, 8, 9}, {3, 3}); + auto Y = mk_array({10, 20, 30}, {1, 3}); + + auto A1 = mk_array({false}, {1}); + test_where_full(A1, X, Y, {10, 20, 30, 10, 20, 30, 10, 20, 30}, {3, 3}); + + auto A2 = mk_array({false, true, true}, {3}); + test_where_full(A2, X, Y, {10, 2, 3, 10, 5, 6, 10, 8, 9}, {3, 3}); + + auto A3 = mk_array({false, true, true}, {1, 3}); + test_where_full(A3, X, Y, {10, 2, 3, 10, 5, 6, 10, 8, 9}, {3, 3}); + + auto A4 = mk_array({false, true, true, true, false, false, true, false, false}, {3, 3}); + test_where_full(A4, X, Y, {10, 2, 3, 4, 20, 30, 7, 20, 30}, {3, 3}); + + auto A5 = mk_array({false, + true, + true, + true, + false, + false, + true, + false, + false, + false, + true, + true, + true, + false, + false, + true, + false, + false}, + {2, 3, 3}); + test_where_full( + A5, X, Y, {10, 2, 3, 4, 20, 30, 7, 20, 30, 10, 2, 3, 4, 20, 30, 7, 20, 30}, {2, 3, 3}); +} + +TEST(Where, EmptyAndScalar) +{ + auto A = mk_array({true}, + { + 1, + }); + auto A_SCALAR = mk_array({false}, {}); + auto A_EMPTY = mk_array({}, + { + 0, + }); + auto X = mk_array({10}, + { + 1, + }); + auto Y = mk_array({20}, + { + 1, + }); + auto X_SCALAR = mk_array({10}, {}); + auto Y_SCALAR = mk_array({20}, {}); + auto EMPTY = mk_array({}, + { + 0, + }); + + auto B1 = where(A_EMPTY, X, Y); + check_array(B1, + {}, + { + 0, + }); + + auto B2 = where(A_EMPTY, X_SCALAR, Y_SCALAR); + check_array(B2, + {}, + { + 0, + }); + + auto B3 = where(A, EMPTY, Y_SCALAR); + check_array(B3, + {}, + { + 0, + }); + + auto B4 = where(A, EMPTY, EMPTY); + check_array(B4, + {}, + { + 0, + }); + + auto B5 = where(A_EMPTY, EMPTY, EMPTY); + check_array(B5, + {}, + { + 0, + }); + + auto B6 = where(A_SCALAR, X, Y_SCALAR); + check_array(B6, + {20}, + { + 1, + }); + + auto B7 = where(A_SCALAR, X_SCALAR, Y_SCALAR); + check_array(B7, {20}, {}); + + auto B8 = where(A, X_SCALAR, Y_SCALAR); + check_array(B8, + {10}, + { + 1, + }); + + auto B9 = where(A, X_SCALAR, Y); + check_array(B9, + {10}, + { + 1, + }); +} + +TEST(Where, InvalidShape) +{ + auto A = mk_array({false, true, true, true, false, false, true, false, false}, {3, 3}); + auto X = mk_array({1, 2, 3, 4, 5, 6, 7, 8, 9}, {3, 3}); + + auto Y1 = mk_array({10, 20}, + { + 2, + }); + auto Y2 = mk_array({10, 20}, {1, 2}); + auto Y3 = mk_array({10, 20, 30, 40}, {4, 1}); + auto Y4 = mk_array({}, + { + 0, + }); + + for (auto Y : {Y1, Y2, Y3, Y4}) { + EXPECT_THROW(where(A, X, Y), std::exception); + } +} From 72b0d135268a62938818fcb90034e83fce868e1e Mon Sep 17 00:00:00 2001 From: Sandeep Datta <128171450+sandeepd-nv@users.noreply.github.com> Date: Fri, 8 Nov 2024 01:23:15 +0530 Subject: [PATCH 351/462] Removed the dependencies-workflow param. (#406) * Removed param dependencies-workflow. * Reintroduce artifact_workflow in versions.json. --- .github/workflows/ci-gh-nightly-release.yml | 1 - .github/workflows/gh-build-and-test.yml | 26 +++++++++------------ cmake/versions.json | 1 + 3 files changed, 12 insertions(+), 16 deletions(-) diff --git a/.github/workflows/ci-gh-nightly-release.yml b/.github/workflows/ci-gh-nightly-release.yml index 0b214d2c63..bae423f10d 100644 --- a/.github/workflows/ci-gh-nightly-release.yml +++ b/.github/workflows/ci-gh-nightly-release.yml @@ -31,7 +31,6 @@ jobs: ./.github/workflows/gh-build-and-test.yml with: build-type: release - dependencies-workflow: ci-gh-nightly-release.yml platform: ${{ matrix.platform }} python-version: ${{ matrix.python-version }} target-device: ${{ matrix.target-device }} diff --git a/.github/workflows/gh-build-and-test.yml b/.github/workflows/gh-build-and-test.yml index fbc6425061..e1b9e43da6 100644 --- a/.github/workflows/gh-build-and-test.yml +++ b/.github/workflows/gh-build-and-test.yml @@ -21,10 +21,7 @@ on: required: false type: string default: "3.12" - dependencies-workflow: - required: false - type: string - default: ci-gh.yml + jobs: setup-build: @@ -51,14 +48,13 @@ jobs: needs: setup-build name: "Build (${{ inputs.platform }}, ${{ inputs.target-device }}, ${{ inputs.build-type }}, Python ${{ inputs.python-version }})" uses: - nv-legate/legate-gh-ci/.github/workflows/gh-build.yml@v1.18 + nv-legate/legate-gh-ci/.github/workflows/gh-build.yml@remove_param_dependencies_workflow with: build-mode: "" build-type: ${{ inputs.build-type }} client-repo: ${{ github.event.repository.name }} dependencies-file: "cmake/versions.json" - dependencies-workflow: ${{ inputs.dependencies-workflow }} - legate-gh-ci-tag: "v1.18" + legate-gh-ci-tag: "remove_param_dependencies_workflow" network: "ucx" platform: ${{ inputs.platform }} python-version: ${{ inputs.python-version }} @@ -72,13 +68,13 @@ jobs: needs: setup-build name: "Check if legate.internal nightly exists for SHA specified in versions.json (${{ inputs.platform }}, ${{ inputs.target-device }}, ${{ inputs.build-type }})" uses: - nv-legate/legate-gh-ci/.github/workflows/gh-check-if-nightly-exists-for-all-dependencies.yml@v1.18 + nv-legate/legate-gh-ci/.github/workflows/gh-check-if-nightly-exists-for-all-dependencies.yml@remove_param_dependencies_workflow with: build-mode: "" build-type: ${{ inputs.build-type }} client-repo: ${{ github.event.repository.name }} dependencies-file: "cmake/versions.json" - legate-gh-ci-tag: "v1.18" + legate-gh-ci-tag: "remove_param_dependencies_workflow" network: "ucx" platform: ${{ inputs.platform }} python-version: ${{ inputs.python-version }} @@ -92,12 +88,12 @@ jobs: if: ${{ github.repository_owner == 'nv-legate' && contains(github.workflow, 'release') && inputs.upload-enabled == true }} name: Upload package to Server uses: - nv-legate/legate-gh-ci/.github/workflows/gh-upload.yml@v1.18 + nv-legate/legate-gh-ci/.github/workflows/gh-upload.yml@remove_param_dependencies_workflow with: build-mode: "" build-type: ${{ inputs.build-type }} client-repo: ${{ github.event.repository.name }} - legate-gh-ci-tag: "v1.18" + legate-gh-ci-tag: "remove_param_dependencies_workflow" name: Upload package to Server network: "ucx" pkgSubString: "cunumeric-" @@ -180,13 +176,13 @@ jobs: matrix: ${{fromJson(needs.setup-test.outputs.matrix)}} uses: - nv-legate/legate-gh-ci/.github/workflows/gh-test-within-container.yml@v1.18 + nv-legate/legate-gh-ci/.github/workflows/gh-test-within-container.yml@remove_param_dependencies_workflow with: build-mode: "" build-type: ${{ inputs.build-type }} client-repo: ${{ github.event.repository.name }} has-gpu: ${{ matrix.runner.type == 'gpu' }} - legate-gh-ci-tag: "v1.18" + legate-gh-ci-tag: "remove_param_dependencies_workflow" name: ${{ matrix.test-config.name }} network: "ucx" platform: ${{ inputs.platform }} @@ -202,12 +198,12 @@ jobs: name: Update Test status on Server if: ${{ (github.repository_owner == 'nv-legate') && contains(github.workflow, 'Nightly') && (inputs.upload-enabled == true) }} uses: - nv-legate/legate-gh-ci/.github/workflows/gh-upload.yml@v1.18 + nv-legate/legate-gh-ci/.github/workflows/gh-upload.yml@remove_param_dependencies_workflow with: build-mode: "" build-type: ${{ inputs.build-type }} client-repo: ${{ github.event.repository.name }} - legate-gh-ci-tag: "v1.18" + legate-gh-ci-tag: "remove_param_dependencies_workflow" name: UpdateTestStatus network: "ucx" pkgSubString: "cunumeric-" diff --git a/cmake/versions.json b/cmake/versions.json index 683231ef2d..72bfa5cfad 100644 --- a/cmake/versions.json +++ b/cmake/versions.json @@ -4,6 +4,7 @@ "repo": "legate.core.internal", "artifact_name": "${{ inputs.platform }}-${{ inputs.build-type }}-<>-python${{ inputs.python-version }}-${{ inputs.target-device }}-release-with_tests-${{ inputs.network }}-<>", "org": "nv-legate", + "artifact_workflow": "ci-gh.yml", "nightly_workflow": "ci-gh-nightly-release.yml", "version": "25.01.00", "git_url" : "git@github.com:nv-legate/legate.core.internal.git", From c69bf6033c9279600d176f7020a9821f246b0c13 Mon Sep 17 00:00:00 2001 From: Sandeep Datta <128171450+sandeepd-nv@users.noreply.github.com> Date: Sun, 10 Nov 2024 01:10:59 +0530 Subject: [PATCH 352/462] Update legate-gh-ci to v1.19. (#476) Ignoring known failures. --- .github/workflows/gh-build-and-test.yml | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/.github/workflows/gh-build-and-test.yml b/.github/workflows/gh-build-and-test.yml index e1b9e43da6..7f0b3d6ba9 100644 --- a/.github/workflows/gh-build-and-test.yml +++ b/.github/workflows/gh-build-and-test.yml @@ -48,13 +48,13 @@ jobs: needs: setup-build name: "Build (${{ inputs.platform }}, ${{ inputs.target-device }}, ${{ inputs.build-type }}, Python ${{ inputs.python-version }})" uses: - nv-legate/legate-gh-ci/.github/workflows/gh-build.yml@remove_param_dependencies_workflow + nv-legate/legate-gh-ci/.github/workflows/gh-build.yml@v1.19 with: build-mode: "" build-type: ${{ inputs.build-type }} client-repo: ${{ github.event.repository.name }} dependencies-file: "cmake/versions.json" - legate-gh-ci-tag: "remove_param_dependencies_workflow" + legate-gh-ci-tag: "v1.19" network: "ucx" platform: ${{ inputs.platform }} python-version: ${{ inputs.python-version }} @@ -68,13 +68,13 @@ jobs: needs: setup-build name: "Check if legate.internal nightly exists for SHA specified in versions.json (${{ inputs.platform }}, ${{ inputs.target-device }}, ${{ inputs.build-type }})" uses: - nv-legate/legate-gh-ci/.github/workflows/gh-check-if-nightly-exists-for-all-dependencies.yml@remove_param_dependencies_workflow + nv-legate/legate-gh-ci/.github/workflows/gh-check-if-nightly-exists-for-all-dependencies.yml@v1.19 with: build-mode: "" build-type: ${{ inputs.build-type }} client-repo: ${{ github.event.repository.name }} dependencies-file: "cmake/versions.json" - legate-gh-ci-tag: "remove_param_dependencies_workflow" + legate-gh-ci-tag: "v1.19" network: "ucx" platform: ${{ inputs.platform }} python-version: ${{ inputs.python-version }} @@ -88,12 +88,12 @@ jobs: if: ${{ github.repository_owner == 'nv-legate' && contains(github.workflow, 'release') && inputs.upload-enabled == true }} name: Upload package to Server uses: - nv-legate/legate-gh-ci/.github/workflows/gh-upload.yml@remove_param_dependencies_workflow + nv-legate/legate-gh-ci/.github/workflows/gh-upload.yml@v1.19 with: build-mode: "" build-type: ${{ inputs.build-type }} client-repo: ${{ github.event.repository.name }} - legate-gh-ci-tag: "remove_param_dependencies_workflow" + legate-gh-ci-tag: "v1.19" name: Upload package to Server network: "ucx" pkgSubString: "cunumeric-" @@ -176,13 +176,13 @@ jobs: matrix: ${{fromJson(needs.setup-test.outputs.matrix)}} uses: - nv-legate/legate-gh-ci/.github/workflows/gh-test-within-container.yml@remove_param_dependencies_workflow + nv-legate/legate-gh-ci/.github/workflows/gh-test-within-container.yml@v1.19 with: build-mode: "" build-type: ${{ inputs.build-type }} client-repo: ${{ github.event.repository.name }} has-gpu: ${{ matrix.runner.type == 'gpu' }} - legate-gh-ci-tag: "remove_param_dependencies_workflow" + legate-gh-ci-tag: "v1.19" name: ${{ matrix.test-config.name }} network: "ucx" platform: ${{ inputs.platform }} @@ -198,12 +198,12 @@ jobs: name: Update Test status on Server if: ${{ (github.repository_owner == 'nv-legate') && contains(github.workflow, 'Nightly') && (inputs.upload-enabled == true) }} uses: - nv-legate/legate-gh-ci/.github/workflows/gh-upload.yml@remove_param_dependencies_workflow + nv-legate/legate-gh-ci/.github/workflows/gh-upload.yml@v1.19 with: build-mode: "" build-type: ${{ inputs.build-type }} client-repo: ${{ github.event.repository.name }} - legate-gh-ci-tag: "remove_param_dependencies_workflow" + legate-gh-ci-tag: "v1.19" name: UpdateTestStatus network: "ucx" pkgSubString: "cunumeric-" From 6ceedc504aa8608f28c270b838f743d6b8a5261b Mon Sep 17 00:00:00 2001 From: XiaLuNV <110973296+XiaLuNV@users.noreply.github.com> Date: Tue, 12 Nov 2024 16:47:38 +0800 Subject: [PATCH 353/462] enhance einsum (#478) --- tests/integration/test_einsum.py | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/tests/integration/test_einsum.py b/tests/integration/test_einsum.py index f79033b1f7..1268c4fee5 100644 --- a/tests/integration/test_einsum.py +++ b/tests/integration/test_einsum.py @@ -309,6 +309,17 @@ def test_order(order): assert allclose(np_res, num_res) +def test_negative() -> None: + a = np.random.rand(256, 256) + b = np.random.rand(256, 256) + msg = r"invalid subscript" + with pytest.raises(ValueError, match=msg): + np.einsum("ik,1j->ij", a, b) + msg = r"Non-alphabetic mode labels" + with pytest.raises(NotImplementedError, match=msg): + num.einsum("ik,1j->ij", a, b) + + if __name__ == "__main__": import sys From 51e1ea4874da44f156619df5f65c3637bc8b2653 Mon Sep 17 00:00:00 2001 From: Bryan Van de Ven Date: Tue, 12 Nov 2024 14:32:00 -0800 Subject: [PATCH 354/462] update to public nv theme (#472) * update to public nv theme * bump legate hash * quick fix to install nv-theme --- cmake/versions.json | 2 +- continuous_integration/scripts/test | 2 +- docs/cunumeric/source/conf.py | 37 ++++++----------------------- 3 files changed, 9 insertions(+), 32 deletions(-) diff --git a/cmake/versions.json b/cmake/versions.json index 72bfa5cfad..a02b56e32d 100644 --- a/cmake/versions.json +++ b/cmake/versions.json @@ -10,7 +10,7 @@ "git_url" : "git@github.com:nv-legate/legate.core.internal.git", "git_shallow": false, "always_download": false, - "git_tag" : "06b1a529f84ec86ce425b0ba453f3344b90f1090" + "git_tag" : "387cd4f12ba601eb350ed8b032737e2fb0a9346b" } } } diff --git a/continuous_integration/scripts/test b/continuous_integration/scripts/test index 60bf105959..3bd5192128 100755 --- a/continuous_integration/scripts/test +++ b/continuous_integration/scripts/test @@ -22,7 +22,7 @@ setup_test_env() { setup_docs_env() { mamba install -y pandoc doxygen - pip install ipython jinja2 "markdown<3.4.0" "pydata-sphinx-theme>=0.13" myst-parser nbsphinx sphinx-copybutton "sphinx>=4.4.0" + pip install ipython jinja2 "markdown<3.4.0" myst-parser nbsphinx sphinx-copybutton "sphinx>=8" nvidia-sphinx-theme } setup_mypy_env() { diff --git a/docs/cunumeric/source/conf.py b/docs/cunumeric/source/conf.py index 592defb55c..3f70bb91e2 100644 --- a/docs/cunumeric/source/conf.py +++ b/docs/cunumeric/source/conf.py @@ -60,38 +60,15 @@ html_static_path = ["_static"] -# This is pretty kludgy but the nv theme is not publicly available to -# install on CI, etc. We will use the pydata theme in those situations -if getenv("NV_THEME") == "1": - html_theme = "nvidia_sphinx_theme" - - html_theme_options = { - "switcher": { - "json_url": JSON_URL, - "navbar_start": ["navbar-logo", "version-switcher"], - "version_match": ".".join(__version__.split(".", 2)[:2]), - } - } +html_theme = "nvidia_sphinx_theme" -else: - html_theme = "pydata_sphinx_theme" - - html_theme_options = { - "footer_start": ["copyright"], - "github_url": "https://github.com/nv-legate/cunumeric", - # https://github.com/pydata/pydata-sphinx-theme/issues/1220 - "icon_links": [], - "logo": { - "text": project, - "link": "https://nv-legate.github.io/cunumeric", - }, - "navbar_align": "left", - "navbar_end": ["navbar-icon-links", "theme-switcher"], - "primary_sidebar_end": ["indices.html"], - "secondary_sidebar_items": ["page-toc"], - "show_nav_level": 2, - "show_toc_level": 2, +html_theme_options = { + "switcher": { + "json_url": JSON_URL, + "navbar_start": ["navbar-logo", "version-switcher"], + "version_match": ".".join(__version__.split(".", 2)[:2]), } +} templates_path = ["_templates"] From c109eaeeb72d789a138941faa23d83ec681f77f2 Mon Sep 17 00:00:00 2001 From: Bryan Van de Ven Date: Wed, 13 Nov 2024 14:11:54 -0800 Subject: [PATCH 355/462] add trademark disclaimer (#482) --- README.md | 2 ++ docs/cunumeric/source/_templates/layout.html | 8 +------- docs/cunumeric/source/conf.py | 6 +++++- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index 7e42b9e92d..e184fbac36 100644 --- a/README.md +++ b/README.md @@ -15,6 +15,8 @@ limitations under the License. --> +*This project, i.e., cuPyNumeric, is separate and independent of the CuPy project. CuPy is a registered trademark of Preferred Networks.* + [![Build Nightly release package](https://github.com/nv-legate/cunumeric.internal/actions/workflows/ci-gh-nightly-release.yml/badge.svg)](https://github.com/nv-legate/cunumeric.internal/actions/workflows/ci-gh-nightly-release.yml) # cuNumeric diff --git a/docs/cunumeric/source/_templates/layout.html b/docs/cunumeric/source/_templates/layout.html index 2f473f38ee..c84d8e5e56 100644 --- a/docs/cunumeric/source/_templates/layout.html +++ b/docs/cunumeric/source/_templates/layout.html @@ -4,10 +4,4 @@ -{% endblock %} - -{% block footer %} - - - -{% endblock %} +{% endblock %} \ No newline at end of file diff --git a/docs/cunumeric/source/conf.py b/docs/cunumeric/source/conf.py index 3f70bb91e2..442a3edbb7 100644 --- a/docs/cunumeric/source/conf.py +++ b/docs/cunumeric/source/conf.py @@ -67,7 +67,11 @@ "json_url": JSON_URL, "navbar_start": ["navbar-logo", "version-switcher"], "version_match": ".".join(__version__.split(".", 2)[:2]), - } + }, + "extra_footer": [ + "This project, i.e., cuPyNumeric, is separate and independent of the CuPy project. CuPy is a registered trademark of Preferred Networks.", # NOQA + '', # NOQA + ], } templates_path = ["_templates"] From c65e521f31103c22d002e473950998a7e20c9045 Mon Sep 17 00:00:00 2001 From: Bryan Van de Ven Date: Wed, 13 Nov 2024 18:09:42 -0800 Subject: [PATCH 356/462] cuNumeric -> cuPyNumeric (#477) * cuNumeric -> cuPyNumeric * restore .dockerigore link * update legate commit hash --- .../workflows/ci-gh-validate-legate-sha.yml | 4 +- .gitignore | 8 +- .pre-commit-config.yaml | 4 +- CMakeLists.txt | 22 +- CONTRIBUTING.md | 12 +- MANIFEST.in | 4 +- README.md | 24 +- cmake/generate_install_info_py.cmake | 8 +- cmake/thirdparty/get_legate.cmake | 42 +- cmake/thirdparty/get_openblas.cmake | 36 +- cmake/thirdparty/get_tblis.cmake | 28 +- cmake/versions.json | 2 +- conda/conda-build/build.sh | 6 +- conda/conda-build/meta.yaml | 10 +- continuous_integration/scripts/build | 22 +- ...unumeric-conda => build-cupynumeric-conda} | 24 +- ...ld-cunumeric-cpp => build-cupynumeric-cpp} | 6 +- ...unumeric-wheel => build-cupynumeric-wheel} | 10 +- continuous_integration/scripts/test | 10 +- cunumeric/config.py | 835 ------------------ cunumeric_cpp.cmake | 565 ------------ {cunumeric => cupynumeric}/__init__.py | 2 +- {cunumeric => cupynumeric}/_array/__init__.py | 0 {cunumeric => cupynumeric}/_array/array.py | 220 ++--- {cunumeric => cupynumeric}/_array/flags.py | 6 +- {cunumeric => cupynumeric}/_array/thunk.py | 0 {cunumeric => cupynumeric}/_array/util.py | 20 +- .../_module/__init__.py | 4 +- .../_module/_unary_red_utils.py | 0 .../_module/array_basic.py | 0 .../_module/array_dimension.py | 12 +- .../_module/array_joining.py | 22 +- .../_module/array_rearrange.py | 6 +- .../_module/array_shape.py | 0 .../_module/array_splitting.py | 4 +- .../_module/array_tiling.py | 6 +- .../_module/array_transpose.py | 0 .../_module/binary_bit_packing.py | 0 .../_module/creation_data.py | 0 .../_module/creation_matrices.py | 2 +- .../_module/creation_ranges.py | 2 +- .../_module/creation_shape.py | 9 +- .../_module/indexing.py | 46 +- .../_module/io_numpy.py | 2 +- .../_module/linalg_mvp.py | 25 +- .../_module/logic_array_contents.py | 10 +- .../_module/logic_array_type.py | 14 +- .../_module/logic_comparison.py | 6 +- .../_module/logic_truth.py | 0 .../_module/math_complex.py | 1 - .../_module/math_extrema.py | 10 +- .../_module/math_misc.py | 2 +- .../_module/math_rounding.py | 0 .../_module/math_sum_prod_diff.py | 96 +- .../_module/sets_making.py | 0 .../_module/ssc_counting.py | 0 .../_module/ssc_searching.py | 8 +- .../_module/ssc_sorting.py | 4 +- .../_module/stats_avgs_vars.py | 0 .../_module/stats_correlating.py | 0 .../_module/stats_histograms.py | 2 +- .../_module/stats_order.py | 2 +- {cunumeric => cupynumeric}/_module/window.py | 0 .../_sphinxext/__init__.py | 0 .../_sphinxext/_comparison_config.py | 0 .../_sphinxext/_comparison_util.py | 8 +- .../_sphinxext/_cupynumeric_directive.py | 2 +- .../_sphinxext/_templates.py | 0 .../_templates/comparison_table.rst | 6 +- .../_sphinxext/comparison_table.py | 4 +- .../_sphinxext/implemented_index.py | 8 +- .../_sphinxext/missing_refs.py | 42 +- .../_sphinxext/ufunc_formatter.py | 2 +- {cunumeric => cupynumeric}/_thunk/__init__.py | 0 {cunumeric => cupynumeric}/_thunk/_sort.py | 4 +- {cunumeric => cupynumeric}/_thunk/deferred.py | 104 +-- {cunumeric => cupynumeric}/_thunk/eager.py | 0 {cunumeric => cupynumeric}/_thunk/thunk.py | 2 +- {cunumeric => cupynumeric}/_ufunc/__init__.py | 0 .../_ufunc/bit_twiddling.py | 0 .../_ufunc/comparison.py | 6 +- {cunumeric => cupynumeric}/_ufunc/floating.py | 0 {cunumeric => cupynumeric}/_ufunc/math.py | 0 .../_ufunc/trigonometric.py | 0 {cunumeric => cupynumeric}/_ufunc/ufunc.py | 10 +- {cunumeric => cupynumeric}/_utils/__init__.py | 0 {cunumeric => cupynumeric}/_utils/array.py | 4 +- {cunumeric => cupynumeric}/_utils/coverage.py | 20 +- {cunumeric => cupynumeric}/_utils/linalg.py | 0 {cunumeric => cupynumeric}/_utils/stack.py | 4 +- .../_utils/structure.py | 0 {cunumeric => cupynumeric}/_version.py | 4 +- cupynumeric/config.py | 835 ++++++++++++++++++ {cunumeric => cupynumeric}/fft/__init__.py | 0 {cunumeric => cupynumeric}/fft/fft.py | 1 - {cunumeric => cupynumeric}/install_info.py.in | 10 +- {cunumeric => cupynumeric}/linalg/__init__.py | 0 .../linalg/_cholesky.py | 24 +- .../linalg/_exception.py | 0 {cunumeric => cupynumeric}/linalg/_qr.py | 4 +- {cunumeric => cupynumeric}/linalg/_solve.py | 6 +- {cunumeric => cupynumeric}/linalg/_svd.py | 6 +- {cunumeric => cupynumeric}/linalg/linalg.py | 16 +- {cunumeric => cupynumeric}/ma/__init__.py | 0 .../ma/_masked_array.py | 0 {cunumeric => cupynumeric}/patch.py | 10 +- {cunumeric => cupynumeric}/py.typed | 0 {cunumeric => cupynumeric}/random/__init__.py | 0 .../random/_bitgenerator.py | 0 .../random/_generator.py | 4 +- {cunumeric => cupynumeric}/random/_random.py | 2 +- {cunumeric => cupynumeric}/runtime.py | 36 +- {cunumeric => cupynumeric}/settings.py | 42 +- {cunumeric => cupynumeric}/types.py | 0 cupynumeric_cpp.cmake | 565 ++++++++++++ ...c_python.cmake => cupynumeric_python.cmake | 50 +- docs/cunumeric/source/api/comparison.rst | 12 - docs/cunumeric/source/api/settings.rst | 8 - .../source/developer/CONTRIBUTING.md | 1 - docs/cunumeric/switcher.json | 52 -- docs/{cunumeric => cupynumeric}/Makefile | 0 docs/{cunumeric => cupynumeric}/make.bat | 0 .../source/_images/developer-build.png | Bin .../source/_implemented.rst | 2 +- .../source/_static/.keep | 0 .../source/_templates/layout.html | 0 .../source/api/_bitgenerator.rst | 4 +- .../source/api/_generator.rst | 4 +- .../source/api/_grouped.rst | 0 .../source/api/_ndarray.rst | 4 +- .../source/api/binary.rst | 2 +- .../source/api/broadcast.rst | 4 +- .../source/api/classes.rst | 0 docs/cupynumeric/source/api/comparison.rst | 12 + .../source/api/creation.rst | 2 +- .../source/api/datatype.rst | 2 +- .../source/api/fft.rst | 4 +- .../source/api/index.rst | 2 +- .../source/api/indexing.rst | 2 +- .../source/api/io.rst | 2 +- .../source/api/linalg.rst | 6 +- .../source/api/logic.rst | 2 +- .../source/api/manipulation.rst | 4 +- .../source/api/math.rst | 2 +- .../source/api/ndarray.rst | 4 +- .../source/api/random.rst | 4 +- .../source/api/routines.rst | 0 .../source/api/set.rst | 2 +- docs/cupynumeric/source/api/settings.rst | 8 + .../source/api/sorting.rst | 2 +- .../source/api/statistics.rst | 2 +- .../source/api/window.rst | 2 +- .../{cunumeric => cupynumeric}/source/conf.py | 16 +- .../source/developer/CONTRIBUTING.md | 72 ++ .../source/developer/building.rst | 22 +- .../source/developer/index.rst | 0 .../source/developer/testing.rst | 2 +- .../source/examples/black_scholes.ipynb | 6 +- .../source/examples/cholesky.ipynb | 10 +- .../examples/compact_finite_difference.ipynb | 0 .../source/examples/edge_detection.ipynb | 8 +- .../source/examples/image.png | Bin .../source/examples/index.rst | 0 .../source/examples/kmeans.ipynb | 2 +- .../source/examples/newton_raphson_2d.ipynb | 8 +- .../source/examples/stencil.ipynb | 4 +- .../source/faqs.rst | 38 +- .../source/index.rst | 6 +- .../source/installation.rst | 18 +- .../source/oss-licenses.rst | 0 .../source/user/advanced.rst | 2 +- .../source/user/differences.rst | 14 +- .../source/user/howtos/benchmarking.rst | 4 +- .../source/user/howtos/index.rst | 0 .../source/user/howtos/jupyter.rst | 0 .../source/user/howtos/measuring.rst | 28 +- .../source/user/howtos/patching.rst | 10 +- .../source/user/index.rst | 0 .../source/user/practices.rst | 34 +- .../source/user/usage.rst | 10 +- docs/cupynumeric/switcher.json | 52 ++ examples/benchmark.py | 6 +- examples/cpp/stencil/CMakeLists.txt | 4 +- examples/cpp/stencil/build.sh | 6 +- examples/cpp/stencil/stencil.cc | 14 +- examples/scan.py | 2 +- install.py | 38 +- pyproject.toml | 4 +- scripts/api_compare.py | 18 +- scripts/conda-build.sh | 8 +- setup.cfg | 8 +- setup.py | 8 +- src/cunumeric/cunumeric_c.h | 355 -------- src/cunumeric/random/bitgenerator_util.h | 98 -- src/{cunumeric.h => cupynumeric.h} | 6 +- src/{cunumeric => cupynumeric}/arg.h | 6 +- src/{cunumeric => cupynumeric}/arg.inl | 4 +- .../arg_redop_register.cc | 10 +- .../arg_redop_register.cu | 6 +- .../arg_redop_register.h | 10 +- .../binary/binary_op.cc | 8 +- .../binary/binary_op.cu | 12 +- .../binary/binary_op.h | 12 +- .../binary/binary_op_omp.cc | 8 +- .../binary/binary_op_template.inl | 10 +- .../binary/binary_op_util.cc | 8 +- .../binary/binary_op_util.h | 78 +- .../binary/binary_red.cc | 8 +- .../binary/binary_red.cu | 12 +- .../binary/binary_red.h | 12 +- .../binary/binary_red_omp.cc | 8 +- .../binary/binary_red_template.inl | 10 +- .../bits/bits_util.h | 10 +- .../bits/packbits.cc | 8 +- .../bits/packbits.cu | 12 +- .../bits/packbits.h | 12 +- .../bits/packbits_omp.cc | 8 +- .../bits/packbits_template.inl | 12 +- .../bits/unpackbits.cc | 8 +- .../bits/unpackbits.cu | 12 +- .../bits/unpackbits.h | 12 +- .../bits/unpackbits_omp.cc | 8 +- .../bits/unpackbits_template.inl | 8 +- .../cephes/chbevl.cc | 0 src/{cunumeric => cupynumeric}/cephes/i0.cc | 0 .../convolution/convolve.cc | 12 +- .../convolution/convolve.cu | 94 +- .../convolution/convolve.h | 12 +- .../convolution/convolve_omp.cc | 12 +- .../convolution/convolve_template.inl | 10 +- src/{cunumeric => cupynumeric}/cuda_help.h | 106 +-- src/{cunumeric => cupynumeric}/cudalibs.cu | 38 +- src/{cunumeric => cupynumeric}/cudalibs.h | 10 +- .../cupynumeric.cc} | 40 +- .../cupynumeric.cu} | 6 +- src/cupynumeric/cupynumeric_c.h | 355 ++++++++ .../cupynumeric_task.h} | 14 +- .../device_scalar_reduction_buffer.h | 12 +- src/{cunumeric => cupynumeric}/divmod.h | 4 +- .../indexing/parallel_loop.cuh | 12 +- .../execution_policy/indexing/parallel_loop.h | 6 +- .../indexing/parallel_loop_omp.h | 10 +- .../reduction/scalar_reduction.cuh | 10 +- .../reduction/scalar_reduction.h | 6 +- .../reduction/scalar_reduction_omp.h | 8 +- src/{cunumeric => cupynumeric}/fft/fft.cu | 60 +- src/{cunumeric => cupynumeric}/fft/fft.h | 16 +- .../fft/fft_template.inl | 20 +- src/{cunumeric => cupynumeric}/fft/fft_util.h | 48 +- .../index/advanced_indexing.cc | 8 +- .../index/advanced_indexing.cu | 16 +- .../index/advanced_indexing.h | 10 +- .../index/advanced_indexing_omp.cc | 10 +- .../index/advanced_indexing_template.inl | 10 +- .../index/choose.cc | 12 +- .../index/choose.cu | 12 +- src/{cunumeric => cupynumeric}/index/choose.h | 10 +- .../index/choose_omp.cc | 10 +- .../index/choose_template.inl | 8 +- .../index/putmask.cc | 8 +- .../index/putmask.cu | 10 +- .../index/putmask.h | 10 +- .../index/putmask_omp.cc | 10 +- .../index/putmask_template.inl | 10 +- .../index/repeat.cc | 8 +- .../index/repeat.cu | 18 +- src/{cunumeric => cupynumeric}/index/repeat.h | 10 +- .../index/repeat_omp.cc | 10 +- .../index/repeat_template.inl | 8 +- .../index/select.cc | 10 +- .../index/select.cu | 14 +- src/{cunumeric => cupynumeric}/index/select.h | 10 +- .../index/select_omp.cc | 10 +- .../index/select_template.inl | 12 +- src/{cunumeric => cupynumeric}/index/wrap.cc | 8 +- src/{cunumeric => cupynumeric}/index/wrap.cu | 14 +- src/{cunumeric => cupynumeric}/index/wrap.h | 10 +- .../index/wrap_omp.cc | 8 +- .../index/wrap_template.inl | 12 +- src/{cunumeric => cupynumeric}/index/zip.cc | 10 +- src/{cunumeric => cupynumeric}/index/zip.cu | 16 +- src/{cunumeric => cupynumeric}/index/zip.h | 10 +- .../index/zip_omp.cc | 10 +- .../index/zip_template.inl | 8 +- src/{cunumeric => cupynumeric}/item/read.cc | 8 +- src/{cunumeric => cupynumeric}/item/read.cu | 12 +- src/{cunumeric => cupynumeric}/item/read.h | 10 +- .../item/read_template.inl | 6 +- src/{cunumeric => cupynumeric}/item/write.cc | 8 +- src/{cunumeric => cupynumeric}/item/write.cu | 12 +- src/{cunumeric => cupynumeric}/item/write.h | 10 +- .../item/write_template.inl | 6 +- src/{cunumeric => cupynumeric}/mapper.cc | 60 +- src/{cunumeric => cupynumeric}/mapper.h | 8 +- .../matrix/batched_cholesky.cc | 10 +- .../matrix/batched_cholesky.cu | 14 +- .../matrix/batched_cholesky.h | 12 +- .../matrix/batched_cholesky_omp.cc | 10 +- .../matrix/batched_cholesky_template.inl | 14 +- .../matrix/contract.cc | 10 +- .../matrix/contract.cu | 12 +- .../matrix/contract.h | 10 +- .../matrix/contract_omp.cc | 10 +- .../matrix/contract_template.inl | 8 +- src/{cunumeric => cupynumeric}/matrix/diag.cc | 8 +- src/{cunumeric => cupynumeric}/matrix/diag.cu | 14 +- src/{cunumeric => cupynumeric}/matrix/diag.h | 10 +- .../matrix/diag_omp.cc | 8 +- .../matrix/diag_template.inl | 12 +- src/{cunumeric => cupynumeric}/matrix/dot.cc | 8 +- src/{cunumeric => cupynumeric}/matrix/dot.cu | 12 +- src/{cunumeric => cupynumeric}/matrix/dot.h | 10 +- .../matrix/dot_omp.cc | 10 +- .../matrix/dot_template.inl | 6 +- src/{cunumeric => cupynumeric}/matrix/gemm.cc | 8 +- src/{cunumeric => cupynumeric}/matrix/gemm.cu | 14 +- src/{cunumeric => cupynumeric}/matrix/gemm.h | 10 +- .../matrix/gemm_omp.cc | 8 +- .../matrix/gemm_template.inl | 6 +- .../matrix/matmul.cc | 10 +- .../matrix/matmul.cu | 20 +- .../matrix/matmul.h | 10 +- .../matrix/matmul_cpu.inl | 10 +- .../matrix/matmul_omp.cc | 10 +- .../matrix/matmul_template.inl | 12 +- .../matrix/matvecmul.cc | 10 +- .../matrix/matvecmul.cu | 20 +- .../matrix/matvecmul.h | 10 +- .../matrix/matvecmul_cpu.inl | 10 +- .../matrix/matvecmul_omp.cc | 10 +- .../matrix/matvecmul_template.inl | 10 +- .../matrix/mp_potrf.cu | 12 +- .../matrix/mp_potrf.h | 10 +- .../matrix/mp_potrf_template.inl | 10 +- .../matrix/mp_solve.cu | 12 +- .../matrix/mp_solve.h | 10 +- .../matrix/mp_solve_template.inl | 10 +- .../matrix/potrf.cc | 8 +- .../matrix/potrf.cu | 14 +- src/{cunumeric => cupynumeric}/matrix/potrf.h | 10 +- .../matrix/potrf_omp.cc | 8 +- .../matrix/potrf_template.inl | 6 +- src/{cunumeric => cupynumeric}/matrix/qr.cc | 10 +- src/{cunumeric => cupynumeric}/matrix/qr.cu | 28 +- src/{cunumeric => cupynumeric}/matrix/qr.h | 10 +- .../matrix/qr_cpu.inl | 4 +- .../matrix/qr_omp.cc | 10 +- .../matrix/qr_template.inl | 12 +- .../matrix/solve.cc | 10 +- .../matrix/solve.cu | 16 +- src/{cunumeric => cupynumeric}/matrix/solve.h | 10 +- .../matrix/solve_cpu.inl | 4 +- .../matrix/solve_omp.cc | 10 +- .../matrix/solve_template.inl | 20 +- src/{cunumeric => cupynumeric}/matrix/svd.cc | 10 +- src/{cunumeric => cupynumeric}/matrix/svd.cu | 18 +- src/{cunumeric => cupynumeric}/matrix/svd.h | 10 +- .../matrix/svd_cpu.inl | 4 +- .../matrix/svd_omp.cc | 10 +- .../matrix/svd_template.inl | 12 +- src/{cunumeric => cupynumeric}/matrix/syrk.cc | 8 +- src/{cunumeric => cupynumeric}/matrix/syrk.cu | 12 +- src/{cunumeric => cupynumeric}/matrix/syrk.h | 10 +- .../matrix/syrk_omp.cc | 8 +- .../matrix/syrk_template.inl | 6 +- src/{cunumeric => cupynumeric}/matrix/tile.cc | 8 +- src/{cunumeric => cupynumeric}/matrix/tile.cu | 12 +- src/{cunumeric => cupynumeric}/matrix/tile.h | 10 +- .../matrix/tile_omp.cc | 8 +- .../matrix/tile_template.inl | 8 +- .../matrix/transpose.cc | 8 +- .../matrix/transpose.cu | 12 +- .../matrix/transpose.h | 10 +- .../matrix/transpose_omp.cc | 8 +- .../matrix/transpose_template.inl | 6 +- .../matrix/trilu.cc | 8 +- .../matrix/trilu.cu | 12 +- src/{cunumeric => cupynumeric}/matrix/trilu.h | 10 +- .../matrix/trilu_omp.cc | 8 +- .../matrix/trilu_template.inl | 8 +- src/{cunumeric => cupynumeric}/matrix/trsm.cc | 8 +- src/{cunumeric => cupynumeric}/matrix/trsm.cu | 12 +- src/{cunumeric => cupynumeric}/matrix/trsm.h | 10 +- .../matrix/trsm_omp.cc | 8 +- .../matrix/trsm_template.inl | 6 +- src/{cunumeric => cupynumeric}/matrix/util.cc | 12 +- src/{cunumeric => cupynumeric}/matrix/util.h | 4 +- src/{cunumeric => cupynumeric}/ndarray.cc | 188 ++-- src/{cunumeric => cupynumeric}/ndarray.h | 12 +- src/{cunumeric => cupynumeric}/ndarray.inl | 4 +- .../nullary/arange.cc | 8 +- .../nullary/arange.cu | 12 +- .../nullary/arange.h | 10 +- .../nullary/arange_omp.cc | 8 +- .../nullary/arange_template.inl | 12 +- src/{cunumeric => cupynumeric}/nullary/eye.cc | 8 +- src/{cunumeric => cupynumeric}/nullary/eye.cu | 12 +- src/{cunumeric => cupynumeric}/nullary/eye.h | 10 +- .../nullary/eye_omp.cc | 8 +- .../nullary/eye_template.inl | 12 +- .../nullary/fill.cc | 8 +- .../nullary/fill.cu | 12 +- src/{cunumeric => cupynumeric}/nullary/fill.h | 10 +- .../nullary/fill_omp.cc | 8 +- .../nullary/fill_template.inl | 12 +- .../nullary/window.cc | 8 +- .../nullary/window.cu | 12 +- .../nullary/window.h | 10 +- .../nullary/window_omp.cc | 8 +- .../nullary/window_template.inl | 8 +- .../nullary/window_util.h | 16 +- src/{cunumeric => cupynumeric}/omp_help.h | 4 +- src/{cunumeric => cupynumeric}/operators.cc | 60 +- src/{cunumeric => cupynumeric}/operators.h | 12 +- src/{cunumeric => cupynumeric}/operators.inl | 8 +- src/{cunumeric => cupynumeric}/pitches.h | 4 +- .../random/bitgenerator.cc | 28 +- .../random/bitgenerator.cu | 20 +- .../random/bitgenerator.h | 12 +- .../random/bitgenerator_curand.inl | 14 +- .../random/bitgenerator_template.inl | 8 +- src/cupynumeric/random/bitgenerator_util.h | 98 ++ .../random/curand_help.h | 18 +- .../random/philox.h | 4 +- src/{cunumeric => cupynumeric}/random/rand.cc | 8 +- src/{cunumeric => cupynumeric}/random/rand.cu | 12 +- src/{cunumeric => cupynumeric}/random/rand.h | 12 +- .../random/rand_omp.cc | 8 +- .../random/rand_template.inl | 12 +- .../random/rand_util.h | 8 +- .../random/randutil/generator.cuh | 28 +- .../random/randutil/generator.h | 8 +- .../random/randutil/generator_beta.inl | 0 .../random/randutil/generator_binomial.inl | 0 .../random/randutil/generator_cauchy.inl | 0 .../random/randutil/generator_chisquare.inl | 0 .../random/randutil/generator_create.inl | 0 .../random/randutil/generator_device.cu | 0 .../randutil/generator_device_advanced.cu | 0 .../generator_device_straightforward.cu | 0 .../random/randutil/generator_exponential.inl | 0 .../random/randutil/generator_f.inl | 0 .../random/randutil/generator_gamma.inl | 0 .../random/randutil/generator_geometric.inl | 0 .../random/randutil/generator_gumbel.inl | 8 +- .../random/randutil/generator_host.cc | 8 +- .../randutil/generator_host_advanced.cc | 4 +- .../generator_host_straightforward.cc | 4 +- .../randutil/generator_hypergeometric.inl | 0 .../random/randutil/generator_integers.inl | 0 .../random/randutil/generator_laplace.inl | 0 .../random/randutil/generator_logistic.inl | 8 +- .../random/randutil/generator_lognormal.inl | 0 .../random/randutil/generator_logseries.inl | 0 .../randutil/generator_negative_binomial.inl | 0 .../random/randutil/generator_normal.inl | 0 .../random/randutil/generator_pareto.inl | 0 .../random/randutil/generator_poisson.inl | 0 .../random/randutil/generator_power.inl | 0 .../random/randutil/generator_raw.inl | 0 .../random/randutil/generator_rayleigh.inl | 0 .../random/randutil/generator_standard_t.inl | 0 .../random/randutil/generator_triangular.inl | 0 .../random/randutil/generator_uniform.inl | 0 .../random/randutil/generator_vonmises.inl | 0 .../random/randutil/generator_wald.inl | 0 .../random/randutil/generator_weibull.inl | 8 +- .../random/randutil/generator_zipf.inl | 0 .../random/randutil/random_distributions.h | 140 ++- .../random/randutil/randomizer.h | 10 +- .../random/randutil/randutil.h | 2 +- .../random/randutil/randutil_curand.h | 0 .../random/randutil/randutil_impl.h | 0 .../random/rnd_aliases.h | 2 +- .../random/rnd_types.h | 24 +- src/{cunumeric => cupynumeric}/runtime.cc | 75 +- src/{cunumeric => cupynumeric}/runtime.h | 20 +- .../scan/scan_global.cc | 8 +- .../scan/scan_global.cu | 14 +- .../scan/scan_global.h | 12 +- .../scan/scan_global_omp.cc | 8 +- .../scan/scan_global_template.inl | 8 +- .../scan/scan_local.cc | 12 +- .../scan/scan_local.cu | 20 +- .../scan/scan_local.h | 12 +- .../scan/scan_local_omp.cc | 12 +- .../scan/scan_local_template.inl | 8 +- .../scan/scan_local_util.h | 10 +- .../scan/scan_util.h | 10 +- .../search/argwhere.cc | 8 +- .../search/argwhere.cu | 16 +- .../search/argwhere.h | 10 +- .../search/argwhere_omp.cc | 10 +- .../search/argwhere_template.inl | 8 +- .../search/nonzero.cc | 8 +- .../search/nonzero.cu | 12 +- .../search/nonzero.cuh | 10 +- .../search/nonzero.h | 10 +- .../search/nonzero_omp.cc | 10 +- .../search/nonzero_template.inl | 8 +- src/{cunumeric => cupynumeric}/set/unique.cc | 8 +- src/{cunumeric => cupynumeric}/set/unique.cu | 32 +- src/{cunumeric => cupynumeric}/set/unique.h | 10 +- .../set/unique_omp.cc | 8 +- .../set/unique_reduce.cc | 8 +- .../set/unique_reduce.h | 10 +- .../set/unique_reduce_omp.cc | 8 +- .../set/unique_reduce_template.inl | 8 +- .../set/unique_template.inl | 8 +- src/{cunumeric => cupynumeric}/slice.h | 4 +- .../sort/cub_sort.cuh | 10 +- .../sort/cub_sort.h | 4 +- .../sort/cub_sort_bool.cu | 6 +- .../sort/cub_sort_double.cu | 6 +- .../sort/cub_sort_float.cu | 6 +- .../sort/cub_sort_half.cu | 6 +- .../sort/cub_sort_int16.cu | 6 +- .../sort/cub_sort_int32.cu | 6 +- .../sort/cub_sort_int64.cu | 6 +- .../sort/cub_sort_int8.cu | 6 +- .../sort/cub_sort_uint16.cu | 6 +- .../sort/cub_sort_uint32.cu | 6 +- .../sort/cub_sort_uint64.cu | 6 +- .../sort/cub_sort_uint8.cu | 6 +- .../sort/searchsorted.cc | 8 +- .../sort/searchsorted.cu | 12 +- .../sort/searchsorted.h | 10 +- .../sort/searchsorted_omp.cc | 8 +- .../sort/searchsorted_template.inl | 8 +- src/{cunumeric => cupynumeric}/sort/sort.cc | 10 +- src/{cunumeric => cupynumeric}/sort/sort.cu | 150 ++-- src/{cunumeric => cupynumeric}/sort/sort.h | 10 +- .../sort/sort_cpu.inl | 14 +- .../sort/sort_omp.cc | 10 +- .../sort/sort_template.inl | 8 +- .../sort/thrust_sort.cuh | 14 +- .../sort/thrust_sort.h | 4 +- .../sort/thrust_sort_bool.cu | 6 +- .../sort/thrust_sort_complex128.cu | 6 +- .../sort/thrust_sort_complex64.cu | 6 +- .../sort/thrust_sort_double.cu | 6 +- .../sort/thrust_sort_float.cu | 6 +- .../sort/thrust_sort_half.cu | 6 +- .../sort/thrust_sort_int16.cu | 6 +- .../sort/thrust_sort_int32.cu | 6 +- .../sort/thrust_sort_int64.cu | 6 +- .../sort/thrust_sort_int8.cu | 6 +- .../sort/thrust_sort_uint16.cu | 6 +- .../sort/thrust_sort_uint32.cu | 6 +- .../sort/thrust_sort_uint64.cu | 6 +- .../sort/thrust_sort_uint8.cu | 6 +- .../stat/bincount.cc | 8 +- .../stat/bincount.cu | 14 +- .../stat/bincount.h | 10 +- .../stat/bincount_omp.cc | 8 +- .../stat/bincount_template.inl | 6 +- .../stat/histogram.cc | 12 +- .../stat/histogram.cu | 16 +- .../stat/histogram.cuh | 10 +- .../stat/histogram.h | 10 +- .../stat/histogram_cpu.h | 8 +- .../stat/histogram_gen.h | 4 +- .../stat/histogram_impl.h | 6 +- .../stat/histogram_omp.cc | 12 +- .../stat/histogram_template.inl | 6 +- .../ternary/where.cc | 8 +- .../ternary/where.cu | 12 +- .../ternary/where.h | 10 +- .../ternary/where_omp.cc | 8 +- .../ternary/where_template.inl | 8 +- .../transform/flip.cc | 8 +- .../transform/flip.cu | 12 +- .../transform/flip.h | 10 +- .../transform/flip_omp.cc | 8 +- .../transform/flip_template.inl | 8 +- src/{cunumeric => cupynumeric}/typedefs.h | 4 +- .../unary/convert.cc | 8 +- .../unary/convert.cu | 12 +- .../unary/convert.h | 12 +- .../unary/convert_omp.cc | 8 +- .../unary/convert_template.inl | 10 +- .../unary/convert_util.h | 46 +- src/{cunumeric => cupynumeric}/unary/isnan.h | 4 +- .../unary/scalar_unary_red.cc | 8 +- .../unary/scalar_unary_red.cu | 12 +- .../unary/scalar_unary_red.h | 12 +- .../unary/scalar_unary_red_omp.cc | 10 +- .../unary/scalar_unary_red_template.inl | 14 +- .../unary/unary_op.cc | 8 +- .../unary/unary_op.cu | 16 +- .../unary/unary_op.h | 32 +- .../unary/unary_op_omp.cc | 8 +- .../unary/unary_op_template.inl | 10 +- .../unary/unary_op_util.h | 108 +-- .../unary/unary_red.cc | 8 +- .../unary/unary_red.cu | 12 +- .../unary/unary_red.h | 12 +- .../unary/unary_red_omp.cc | 8 +- .../unary/unary_red_template.inl | 14 +- .../unary/unary_red_util.h | 48 +- .../utilities/repartition.cc | 4 +- .../utilities/repartition.cu | 28 +- .../utilities/repartition.h | 8 +- .../utilities/thrust_allocator.h | 4 +- .../utilities/thrust_util.h | 0 src/env_defaults.h | 2 +- tests/cpp/CMakeLists.txt | 8 +- tests/cpp/integration/common_utils.cc | 8 +- tests/cpp/integration/common_utils.h | 12 +- tests/cpp/integration/test_amax.cc | 66 +- tests/cpp/integration/test_amin.cc | 66 +- tests/cpp/integration/test_arange.cc | 16 +- tests/cpp/integration/test_argsort.cc | 14 +- tests/cpp/integration/test_argwhere.cc | 8 +- tests/cpp/integration/test_bincount.cc | 50 +- tests/cpp/integration/test_convolve.cc | 2 +- tests/cpp/integration/test_diagonal.cc | 72 +- tests/cpp/integration/test_dot.cc | 8 +- tests/cpp/integration/test_eye.cc | 44 +- tests/cpp/integration/test_fill.cc | 2 +- tests/cpp/integration/test_flip.cc | 16 +- tests/cpp/integration/test_logical.cc | 64 +- tests/cpp/integration/test_moveaxis.cc | 42 +- tests/cpp/integration/test_msort.cc | 6 +- tests/cpp/integration/test_nonzero.cc | 6 +- tests/cpp/integration/test_put.cc | 2 +- tests/cpp/integration/test_repartition.cc | 64 +- tests/cpp/integration/test_repeat.cc | 4 +- tests/cpp/integration/test_reshape.cc | 2 +- tests/cpp/integration/test_sort.cc | 14 +- tests/cpp/integration/test_sort_complex.cc | 6 +- tests/cpp/integration/test_squeeze.cc | 2 +- tests/cpp/integration/test_swapaxes.cc | 42 +- tests/cpp/integration/test_transpose.cc | 23 +- tests/cpp/integration/test_trilu.cc | 2 +- tests/cpp/integration/test_unique.cc | 2 +- tests/cpp/integration/test_where.cc | 4 +- tests/cpp/integration/test_window.cc | 12 +- tests/cpp/integration/test_zeros.cc | 2 +- tests/cpp/integration/util.inl | 10 +- tests/cpp/main.cc | 4 +- tests/cpp/run.py | 8 +- tests/integration/test_0d_store.py | 2 +- tests/integration/test_advanced_indexing.py | 2 +- tests/integration/test_allclose.py | 10 +- tests/integration/test_amax_amin.py | 10 +- tests/integration/test_angle.py | 2 +- tests/integration/test_append.py | 2 +- tests/integration/test_arg_reduce.py | 2 +- tests/integration/test_argsort.py | 14 +- tests/integration/test_array.py | 6 +- tests/integration/test_array_creation.py | 2 +- tests/integration/test_array_dunders.py | 2 +- tests/integration/test_array_equal.py | 6 +- tests/integration/test_array_fallback.py | 6 +- tests/integration/test_array_split.py | 2 +- tests/integration/test_astype.py | 8 +- tests/integration/test_atleast_nd.py | 10 +- tests/integration/test_average.py | 2 +- tests/integration/test_binary_op_broadcast.py | 2 +- tests/integration/test_binary_op_complex.py | 2 +- tests/integration/test_binary_op_typing.py | 8 +- tests/integration/test_binary_ufunc.py | 10 +- tests/integration/test_bincount.py | 2 +- tests/integration/test_bits.py | 10 +- tests/integration/test_block.py | 24 +- tests/integration/test_broadcast.py | 6 +- tests/integration/test_cholesky.py | 2 +- tests/integration/test_clip.py | 10 +- tests/integration/test_complex_ops.py | 6 +- tests/integration/test_compress.py | 12 +- tests/integration/test_concatenate_stack.py | 8 +- tests/integration/test_contains.py | 2 +- tests/integration/test_convolve.py | 8 +- tests/integration/test_copy.py | 2 +- tests/integration/test_corner_quantiles.py | 10 +- tests/integration/test_data_interface.py | 4 +- tests/integration/test_diag_indices.py | 2 +- tests/integration/test_diff.py | 2 +- tests/integration/test_digitize.py | 6 +- tests/integration/test_dot.py | 6 +- tests/integration/test_einsum.py | 4 +- tests/integration/test_einsum_path.py | 6 +- tests/integration/test_exp.py | 8 +- tests/integration/test_expand_dims.py | 4 +- tests/integration/test_extract.py | 8 +- tests/integration/test_eye.py | 12 +- tests/integration/test_fallback.py | 4 +- tests/integration/test_fft_c2c.py | 4 +- tests/integration/test_fft_c2r.py | 2 +- tests/integration/test_fft_hermitian.py | 2 +- tests/integration/test_fft_r2c.py | 2 +- tests/integration/test_fftshift.py | 2 +- tests/integration/test_file.py | 2 +- tests/integration/test_fill.py | 12 +- tests/integration/test_fill_diagonal.py | 5 +- tests/integration/test_flags.py | 2 +- tests/integration/test_flatten.py | 4 +- tests/integration/test_flip.py | 2 +- tests/integration/test_floating.py | 2 +- tests/integration/test_get_item.py | 2 +- tests/integration/test_gradient.py | 2 +- tests/integration/test_histogram.py | 2 +- tests/integration/test_identity.py | 2 +- tests/integration/test_index_routines.py | 40 +- tests/integration/test_indices.py | 4 +- .../test_inlinemap-keeps-region-alive.py | 2 +- tests/integration/test_inner.py | 4 +- tests/integration/test_input_output.py | 2 +- tests/integration/test_intra_array_copy.py | 2 +- tests/integration/test_item.py | 8 +- tests/integration/test_itemset.py | 12 +- tests/integration/test_jacobi.py | 2 +- tests/integration/test_length.py | 2 +- tests/integration/test_linspace.py | 14 +- tests/integration/test_logic.py | 6 +- tests/integration/test_logical.py | 6 +- tests/integration/test_logical_reduction.py | 4 +- tests/integration/test_lstm_backward_test.py | 2 +- tests/integration/test_lstm_simple_forward.py | 2 +- tests/integration/test_map_reduce.py | 2 +- tests/integration/test_mask.py | 2 +- tests/integration/test_mask_indices.py | 8 +- tests/integration/test_matmul.py | 10 +- tests/integration/test_matrix_power.py | 6 +- tests/integration/test_mean.py | 4 +- tests/integration/test_median.py | 2 +- tests/integration/test_meshgrid.py | 2 +- tests/integration/test_min_on_gpu.py | 2 +- tests/integration/test_moveaxis.py | 2 +- tests/integration/test_msort.py | 6 +- tests/integration/test_multi_dot.py | 6 +- tests/integration/test_nan_reduction.py | 10 +- tests/integration/test_nanarg_reduction.py | 16 +- tests/integration/test_nanmean.py | 4 +- tests/integration/test_nanpercentiles.py | 6 +- tests/integration/test_nanquantiles.py | 6 +- tests/integration/test_nd_convolve.py | 66 +- tests/integration/test_ndim.py | 2 +- tests/integration/test_negaxes_quantiles.py | 7 +- tests/integration/test_nonzero.py | 20 +- tests/integration/test_norm.py | 10 +- tests/integration/test_ones.py | 8 +- tests/integration/test_outer.py | 2 +- tests/integration/test_overlap.py | 2 +- tests/integration/test_overwrite_slice.py | 2 +- tests/integration/test_partition.py | 6 +- tests/integration/test_percentiles.py | 6 +- tests/integration/test_prod.py | 22 +- tests/integration/test_put.py | 2 +- tests/integration/test_put_along_axis.py | 4 +- tests/integration/test_putmask.py | 6 +- tests/integration/test_qr.py | 2 +- tests/integration/test_quantiles.py | 10 +- tests/integration/test_randint.py | 2 +- tests/integration/test_random.py | 8 +- tests/integration/test_random_advanced.py | 8 +- tests/integration/test_random_beta.py | 4 +- tests/integration/test_random_bitgenerator.py | 6 +- tests/integration/test_random_creation.py | 68 +- tests/integration/test_random_gamma.py | 4 +- .../test_random_straightforward.py | 6 +- tests/integration/test_reduction.py | 8 +- tests/integration/test_repeat.py | 6 +- tests/integration/test_reshape.py | 10 +- tests/integration/test_roll.py | 2 +- tests/integration/test_rot90.py | 2 +- tests/integration/test_round.py | 2 +- tests/integration/test_scan.py | 10 +- tests/integration/test_searchsorted.py | 10 +- tests/integration/test_set_item.py | 2 +- tests/integration/test_setflags.py | 6 +- tests/integration/test_shape.py | 2 +- tests/integration/test_singleton_access.py | 6 +- tests/integration/test_slicing.py | 2 +- tests/integration/test_solve.py | 2 +- tests/integration/test_sort.py | 16 +- tests/integration/test_sort_complex.py | 4 +- tests/integration/test_split.py | 22 +- tests/integration/test_squeeze.py | 2 +- tests/integration/test_stack.py | 2 +- tests/integration/test_stats.py | 6 +- tests/integration/test_svd.py | 2 +- tests/integration/test_swapaxes.py | 2 +- tests/integration/test_take.py | 8 +- tests/integration/test_take_along_axis.py | 4 +- tests/integration/test_tensordot.py | 6 +- tests/integration/test_tile.py | 2 +- tests/integration/test_trace.py | 8 +- tests/integration/test_transpose.py | 26 +- tests/integration/test_tri.py | 18 +- tests/integration/test_trilu.py | 6 +- tests/integration/test_trilu_indices.py | 18 +- .../test_unary_functions_2d_complex.py | 2 +- tests/integration/test_unary_ufunc.py | 8 +- tests/integration/test_unique.py | 4 +- tests/integration/test_unravel_index.py | 2 +- tests/integration/test_update.py | 2 +- tests/integration/test_vdot.py | 6 +- tests/integration/test_view.py | 2 +- tests/integration/test_where.py | 10 +- tests/integration/test_window.py | 2 +- tests/integration/utils/comparisons.py | 6 +- tests/integration/utils/contractions.py | 2 +- tests/integration/utils/random.py | 2 +- tests/integration/utils/utils.py | 14 +- tests/todo/2d_reduction_complex.py | 2 +- tests/todo/assign_slice.py | 2 +- tests/todo/complex_test.py | 2 +- tests/todo/dot.py | 2 +- tests/todo/indirect.py | 2 +- tests/todo/kmeans_test.py | 2 +- tests/todo/lstm_batch.py | 2 +- tests/todo/lstm_simple_backward.py | 2 +- .../{cunumeric => cupynumeric}/__init__.py | 0 .../_array/__init__.py | 0 .../_array/test_util.py | 6 +- .../_sphinxext/__init__.py | 0 .../_sphinxext/test__comparison_util.py | 4 +- .../_utils/__init__.py | 0 .../_utils/test_array.py | 2 +- .../_utils/test_coverage.py | 74 +- .../_utils/test_linalg.py | 2 +- .../random/__init__.py | 0 .../random/test_bitgenerator.py | 4 +- .../{cunumeric => cupynumeric}/test_config.py | 28 +- .../{cunumeric => cupynumeric}/test_nptest.py | 8 +- .../{cunumeric => cupynumeric}/test_patch.py | 4 +- .../test_settings.py | 8 +- 829 files changed, 6324 insertions(+), 6137 deletions(-) rename continuous_integration/scripts/{build-cunumeric-conda => build-cupynumeric-conda} (79%) rename continuous_integration/scripts/{build-cunumeric-cpp => build-cupynumeric-cpp} (88%) rename continuous_integration/scripts/{build-cunumeric-wheel => build-cupynumeric-wheel} (68%) delete mode 100644 cunumeric/config.py delete mode 100644 cunumeric_cpp.cmake rename {cunumeric => cupynumeric}/__init__.py (99%) rename {cunumeric => cupynumeric}/_array/__init__.py (100%) rename {cunumeric => cupynumeric}/_array/array.py (94%) rename {cunumeric => cupynumeric}/_array/flags.py (91%) rename {cunumeric => cupynumeric}/_array/thunk.py (100%) rename {cunumeric => cupynumeric}/_array/util.py (90%) rename {cunumeric => cupynumeric}/_module/__init__.py (96%) rename {cunumeric => cupynumeric}/_module/_unary_red_utils.py (100%) rename {cunumeric => cupynumeric}/_module/array_basic.py (100%) rename {cunumeric => cupynumeric}/_module/array_dimension.py (96%) rename {cunumeric => cupynumeric}/_module/array_joining.py (96%) rename {cunumeric => cupynumeric}/_module/array_rearrange.py (97%) rename {cunumeric => cupynumeric}/_module/array_shape.py (100%) rename {cunumeric => cupynumeric}/_module/array_splitting.py (98%) rename {cunumeric => cupynumeric}/_module/array_tiling.py (97%) rename {cunumeric => cupynumeric}/_module/array_transpose.py (100%) rename {cunumeric => cupynumeric}/_module/binary_bit_packing.py (100%) rename {cunumeric => cupynumeric}/_module/creation_data.py (100%) rename {cunumeric => cupynumeric}/_module/creation_matrices.py (98%) rename {cunumeric => cupynumeric}/_module/creation_ranges.py (99%) rename {cunumeric => cupynumeric}/_module/creation_shape.py (97%) rename {cunumeric => cupynumeric}/_module/indexing.py (95%) rename {cunumeric => cupynumeric}/_module/io_numpy.py (96%) rename {cunumeric => cupynumeric}/_module/linalg_mvp.py (97%) rename {cunumeric => cupynumeric}/_module/logic_array_contents.py (92%) rename {cunumeric => cupynumeric}/_module/logic_array_type.py (93%) rename {cunumeric => cupynumeric}/_module/logic_comparison.py (95%) rename {cunumeric => cupynumeric}/_module/logic_truth.py (100%) rename {cunumeric => cupynumeric}/_module/math_complex.py (98%) rename {cunumeric => cupynumeric}/_module/math_extrema.py (93%) rename {cunumeric => cupynumeric}/_module/math_misc.py (98%) rename {cunumeric => cupynumeric}/_module/math_rounding.py (100%) rename {cunumeric => cupynumeric}/_module/math_sum_prod_diff.py (93%) rename {cunumeric => cupynumeric}/_module/sets_making.py (100%) rename {cunumeric => cupynumeric}/_module/ssc_counting.py (100%) rename {cunumeric => cupynumeric}/_module/ssc_searching.py (97%) rename {cunumeric => cupynumeric}/_module/ssc_sorting.py (98%) rename {cunumeric => cupynumeric}/_module/stats_avgs_vars.py (100%) rename {cunumeric => cupynumeric}/_module/stats_correlating.py (100%) rename {cunumeric => cupynumeric}/_module/stats_histograms.py (99%) rename {cunumeric => cupynumeric}/_module/stats_order.py (99%) rename {cunumeric => cupynumeric}/_module/window.py (100%) rename {cunumeric => cupynumeric}/_sphinxext/__init__.py (100%) rename {cunumeric => cupynumeric}/_sphinxext/_comparison_config.py (100%) rename {cunumeric => cupynumeric}/_sphinxext/_comparison_util.py (95%) rename cunumeric/_sphinxext/_cunumeric_directive.py => cupynumeric/_sphinxext/_cupynumeric_directive.py (96%) rename {cunumeric => cupynumeric}/_sphinxext/_templates.py (100%) rename {cunumeric => cupynumeric}/_sphinxext/_templates/comparison_table.rst (69%) rename {cunumeric => cupynumeric}/_sphinxext/comparison_table.py (94%) rename {cunumeric => cupynumeric}/_sphinxext/implemented_index.py (90%) rename {cunumeric => cupynumeric}/_sphinxext/missing_refs.py (70%) rename {cunumeric => cupynumeric}/_sphinxext/ufunc_formatter.py (97%) rename {cunumeric => cupynumeric}/_thunk/__init__.py (100%) rename {cunumeric => cupynumeric}/_thunk/_sort.py (98%) rename {cunumeric => cupynumeric}/_thunk/deferred.py (97%) rename {cunumeric => cupynumeric}/_thunk/eager.py (100%) rename {cunumeric => cupynumeric}/_thunk/thunk.py (99%) rename {cunumeric => cupynumeric}/_ufunc/__init__.py (100%) rename {cunumeric => cupynumeric}/_ufunc/bit_twiddling.py (100%) rename {cunumeric => cupynumeric}/_ufunc/comparison.py (97%) rename {cunumeric => cupynumeric}/_ufunc/floating.py (100%) rename {cunumeric => cupynumeric}/_ufunc/math.py (100%) rename {cunumeric => cupynumeric}/_ufunc/trigonometric.py (100%) rename {cunumeric => cupynumeric}/_ufunc/ufunc.py (99%) rename {cunumeric => cupynumeric}/_utils/__init__.py (100%) rename {cunumeric => cupynumeric}/_utils/array.py (96%) rename {cunumeric => cupynumeric}/_utils/coverage.py (94%) rename {cunumeric => cupynumeric}/_utils/linalg.py (100%) rename {cunumeric => cupynumeric}/_utils/stack.py (91%) rename {cunumeric => cupynumeric}/_utils/structure.py (100%) rename {cunumeric => cupynumeric}/_version.py (99%) create mode 100644 cupynumeric/config.py rename {cunumeric => cupynumeric}/fft/__init__.py (100%) rename {cunumeric => cupynumeric}/fft/fft.py (99%) rename {cunumeric => cupynumeric}/install_info.py.in (79%) rename {cunumeric => cupynumeric}/linalg/__init__.py (100%) rename {cunumeric => cupynumeric}/linalg/_cholesky.py (91%) rename {cunumeric => cupynumeric}/linalg/_exception.py (100%) rename {cunumeric => cupynumeric}/linalg/_qr.py (91%) rename {cunumeric => cupynumeric}/linalg/_solve.py (95%) rename {cunumeric => cupynumeric}/linalg/_svd.py (90%) rename {cunumeric => cupynumeric}/linalg/linalg.py (97%) rename {cunumeric => cupynumeric}/ma/__init__.py (100%) rename {cunumeric => cupynumeric}/ma/_masked_array.py (100%) rename {cunumeric => cupynumeric}/patch.py (83%) rename {cunumeric => cupynumeric}/py.typed (100%) rename {cunumeric => cupynumeric}/random/__init__.py (100%) rename {cunumeric => cupynumeric}/random/_bitgenerator.py (100%) rename {cunumeric => cupynumeric}/random/_generator.py (98%) rename {cunumeric => cupynumeric}/random/_random.py (99%) rename {cunumeric => cupynumeric}/runtime.py (95%) rename {cunumeric => cupynumeric}/settings.py (84%) rename {cunumeric => cupynumeric}/types.py (100%) create mode 100644 cupynumeric_cpp.cmake rename cunumeric_python.cmake => cupynumeric_python.cmake (69%) delete mode 100644 docs/cunumeric/source/api/comparison.rst delete mode 100644 docs/cunumeric/source/api/settings.rst delete mode 120000 docs/cunumeric/source/developer/CONTRIBUTING.md delete mode 100644 docs/cunumeric/switcher.json rename docs/{cunumeric => cupynumeric}/Makefile (100%) rename docs/{cunumeric => cupynumeric}/make.bat (100%) rename docs/{cunumeric => cupynumeric}/source/_images/developer-build.png (100%) rename docs/{cunumeric => cupynumeric}/source/_implemented.rst (73%) rename docs/{cunumeric => cupynumeric}/source/_static/.keep (100%) rename docs/{cunumeric => cupynumeric}/source/_templates/layout.html (100%) rename docs/{cunumeric => cupynumeric}/source/api/_bitgenerator.rst (65%) rename docs/{cunumeric => cupynumeric}/source/api/_generator.rst (65%) rename docs/{cunumeric => cupynumeric}/source/api/_grouped.rst (100%) rename docs/{cunumeric => cupynumeric}/source/api/_ndarray.rst (96%) rename docs/{cunumeric => cupynumeric}/source/api/binary.rst (90%) rename docs/{cunumeric => cupynumeric}/source/api/broadcast.rst (52%) rename docs/{cunumeric => cupynumeric}/source/api/classes.rst (100%) create mode 100644 docs/cupynumeric/source/api/comparison.rst rename docs/{cunumeric => cupynumeric}/source/api/creation.rst (94%) rename docs/{cunumeric => cupynumeric}/source/api/datatype.rst (81%) rename docs/{cunumeric => cupynumeric}/source/api/fft.rst (85%) rename docs/{cunumeric => cupynumeric}/source/api/index.rst (77%) rename docs/{cunumeric => cupynumeric}/source/api/indexing.rst (95%) rename docs/{cunumeric => cupynumeric}/source/api/io.rst (82%) rename docs/{cunumeric => cupynumeric}/source/api/linalg.rst (85%) rename docs/{cunumeric => cupynumeric}/source/api/logic.rst (95%) rename docs/{cunumeric => cupynumeric}/source/api/manipulation.rst (93%) rename docs/{cunumeric => cupynumeric}/source/api/math.rst (98%) rename docs/{cunumeric => cupynumeric}/source/api/ndarray.rst (98%) rename docs/{cunumeric => cupynumeric}/source/api/random.rst (93%) rename docs/{cunumeric => cupynumeric}/source/api/routines.rst (100%) rename docs/{cunumeric => cupynumeric}/source/api/set.rst (79%) create mode 100644 docs/cupynumeric/source/api/settings.rst rename docs/{cunumeric => cupynumeric}/source/api/sorting.rst (93%) rename docs/{cunumeric => cupynumeric}/source/api/statistics.rst (94%) rename docs/{cunumeric => cupynumeric}/source/api/window.rst (85%) rename docs/{cunumeric => cupynumeric}/source/conf.py (87%) create mode 100644 docs/cupynumeric/source/developer/CONTRIBUTING.md rename docs/{cunumeric => cupynumeric}/source/developer/building.rst (70%) rename docs/{cunumeric => cupynumeric}/source/developer/index.rst (100%) rename docs/{cunumeric => cupynumeric}/source/developer/testing.rst (97%) rename docs/{cunumeric => cupynumeric}/source/examples/black_scholes.ipynb (99%) rename docs/{cunumeric => cupynumeric}/source/examples/cholesky.ipynb (87%) rename docs/{cunumeric => cupynumeric}/source/examples/compact_finite_difference.ipynb (100%) rename docs/{cunumeric => cupynumeric}/source/examples/edge_detection.ipynb (99%) rename docs/{cunumeric => cupynumeric}/source/examples/image.png (100%) rename docs/{cunumeric => cupynumeric}/source/examples/index.rst (100%) rename docs/{cunumeric => cupynumeric}/source/examples/kmeans.ipynb (99%) rename docs/{cunumeric => cupynumeric}/source/examples/newton_raphson_2d.ipynb (91%) rename docs/{cunumeric => cupynumeric}/source/examples/stencil.ipynb (99%) rename docs/{cunumeric => cupynumeric}/source/faqs.rst (82%) rename docs/{cunumeric => cupynumeric}/source/index.rst (83%) rename docs/{cunumeric => cupynumeric}/source/installation.rst (72%) rename docs/{cunumeric => cupynumeric}/source/oss-licenses.rst (100%) rename docs/{cunumeric => cupynumeric}/source/user/advanced.rst (92%) rename docs/{cunumeric => cupynumeric}/source/user/differences.rst (77%) rename docs/{cunumeric => cupynumeric}/source/user/howtos/benchmarking.rst (94%) rename docs/{cunumeric => cupynumeric}/source/user/howtos/index.rst (100%) rename docs/{cunumeric => cupynumeric}/source/user/howtos/jupyter.rst (100%) rename docs/{cunumeric => cupynumeric}/source/user/howtos/measuring.rst (56%) rename docs/{cunumeric => cupynumeric}/source/user/howtos/patching.rst (64%) rename docs/{cunumeric => cupynumeric}/source/user/index.rst (100%) rename docs/{cunumeric => cupynumeric}/source/user/practices.rst (93%) rename docs/{cunumeric => cupynumeric}/source/user/usage.rst (79%) create mode 100644 docs/cupynumeric/switcher.json delete mode 100644 src/cunumeric/cunumeric_c.h delete mode 100644 src/cunumeric/random/bitgenerator_util.h rename src/{cunumeric.h => cupynumeric.h} (85%) rename src/{cunumeric => cupynumeric}/arg.h (96%) rename src/{cunumeric => cupynumeric}/arg.inl (98%) rename src/{cunumeric => cupynumeric}/arg_redop_register.cc (89%) rename src/{cunumeric => cupynumeric}/arg_redop_register.cu (79%) rename src/{cunumeric => cupynumeric}/arg_redop_register.h (89%) rename src/{cunumeric => cupynumeric}/binary/binary_op.cc (92%) rename src/{cunumeric => cupynumeric}/binary/binary_op.cu (92%) rename src/{cunumeric => cupynumeric}/binary/binary_op.h (79%) rename src/{cunumeric => cupynumeric}/binary/binary_op_omp.cc (92%) rename src/{cunumeric => cupynumeric}/binary/binary_op_template.inl (94%) rename src/{cunumeric => cupynumeric}/binary/binary_op_util.cc (90%) rename src/{cunumeric => cupynumeric}/binary/binary_op_util.h (94%) rename src/{cunumeric => cupynumeric}/binary/binary_red.cc (92%) rename src/{cunumeric => cupynumeric}/binary/binary_red.cu (92%) rename src/{cunumeric => cupynumeric}/binary/binary_red.h (79%) rename src/{cunumeric => cupynumeric}/binary/binary_red_omp.cc (92%) rename src/{cunumeric => cupynumeric}/binary/binary_red_template.inl (94%) rename src/{cunumeric => cupynumeric}/bits/bits_util.h (79%) rename src/{cunumeric => cupynumeric}/bits/packbits.cc (93%) rename src/{cunumeric => cupynumeric}/bits/packbits.cu (93%) rename src/{cunumeric => cupynumeric}/bits/packbits.h (92%) rename src/{cunumeric => cupynumeric}/bits/packbits_omp.cc (93%) rename src/{cunumeric => cupynumeric}/bits/packbits_template.inl (95%) rename src/{cunumeric => cupynumeric}/bits/unpackbits.cc (90%) rename src/{cunumeric => cupynumeric}/bits/unpackbits.cu (89%) rename src/{cunumeric => cupynumeric}/bits/unpackbits.h (87%) rename src/{cunumeric => cupynumeric}/bits/unpackbits_omp.cc (90%) rename src/{cunumeric => cupynumeric}/bits/unpackbits_template.inl (94%) rename src/{cunumeric => cupynumeric}/cephes/chbevl.cc (100%) rename src/{cunumeric => cupynumeric}/cephes/i0.cc (100%) rename src/{cunumeric => cupynumeric}/convolution/convolve.cc (97%) rename src/{cunumeric => cupynumeric}/convolution/convolve.cu (94%) rename src/{cunumeric => cupynumeric}/convolution/convolve.h (84%) rename src/{cunumeric => cupynumeric}/convolution/convolve_omp.cc (97%) rename src/{cunumeric => cupynumeric}/convolution/convolve_template.inl (98%) rename src/{cunumeric => cupynumeric}/cuda_help.h (82%) rename src/{cunumeric => cupynumeric}/cudalibs.cu (92%) rename src/{cunumeric => cupynumeric}/cudalibs.h (89%) rename src/{cunumeric/cunumeric.cc => cupynumeric/cupynumeric.cc} (55%) rename src/{cunumeric/cunumeric.cu => cupynumeric/cupynumeric.cu} (95%) create mode 100644 src/cupynumeric/cupynumeric_c.h rename src/{cunumeric/cunumeric_task.h => cupynumeric/cupynumeric_task.h} (75%) rename src/{cunumeric => cupynumeric}/device_scalar_reduction_buffer.h (88%) rename src/{cunumeric => cupynumeric}/divmod.h (99%) rename src/{cunumeric => cupynumeric}/execution_policy/indexing/parallel_loop.cuh (85%) rename src/{cunumeric => cupynumeric}/execution_policy/indexing/parallel_loop.h (91%) rename src/{cunumeric => cupynumeric}/execution_policy/indexing/parallel_loop_omp.h (83%) rename src/{cunumeric => cupynumeric}/execution_policy/reduction/scalar_reduction.cuh (93%) rename src/{cunumeric => cupynumeric}/execution_policy/reduction/scalar_reduction.h (94%) rename src/{cunumeric => cupynumeric}/execution_policy/reduction/scalar_reduction_omp.h (89%) rename src/{cunumeric => cupynumeric}/fft/fft.cu (86%) rename src/{cunumeric => cupynumeric}/fft/fft.h (73%) rename src/{cunumeric => cupynumeric}/fft/fft_template.inl (85%) rename src/{cunumeric => cupynumeric}/fft/fft_util.h (53%) rename src/{cunumeric => cupynumeric}/index/advanced_indexing.cc (95%) rename src/{cunumeric => cupynumeric}/index/advanced_indexing.cu (94%) rename src/{cunumeric => cupynumeric}/index/advanced_indexing.h (83%) rename src/{cunumeric => cupynumeric}/index/advanced_indexing_omp.cc (95%) rename src/{cunumeric => cupynumeric}/index/advanced_indexing_template.inl (94%) rename src/{cunumeric => cupynumeric}/index/choose.cc (91%) rename src/{cunumeric => cupynumeric}/index/choose.cu (93%) rename src/{cunumeric => cupynumeric}/index/choose.h (81%) rename src/{cunumeric => cupynumeric}/index/choose_omp.cc (91%) rename src/{cunumeric => cupynumeric}/index/choose_template.inl (94%) rename src/{cunumeric => cupynumeric}/index/putmask.cc (85%) rename src/{cunumeric => cupynumeric}/index/putmask.cu (77%) rename src/{cunumeric => cupynumeric}/index/putmask.h (81%) rename src/{cunumeric => cupynumeric}/index/putmask_omp.cc (77%) rename src/{cunumeric => cupynumeric}/index/putmask_template.inl (93%) rename src/{cunumeric => cupynumeric}/index/repeat.cc (96%) rename src/{cunumeric => cupynumeric}/index/repeat.cu (94%) rename src/{cunumeric => cupynumeric}/index/repeat.h (82%) rename src/{cunumeric => cupynumeric}/index/repeat_omp.cc (95%) rename src/{cunumeric => cupynumeric}/index/repeat_template.inl (94%) rename src/{cunumeric => cupynumeric}/index/select.cc (93%) rename src/{cunumeric => cupynumeric}/index/select.cu (94%) rename src/{cunumeric => cupynumeric}/index/select.h (82%) rename src/{cunumeric => cupynumeric}/index/select_omp.cc (93%) rename src/{cunumeric => cupynumeric}/index/select_template.inl (93%) rename src/{cunumeric => cupynumeric}/index/wrap.cc (94%) rename src/{cunumeric => cupynumeric}/index/wrap.cu (96%) rename src/{cunumeric => cupynumeric}/index/wrap.h (92%) rename src/{cunumeric => cupynumeric}/index/wrap_omp.cc (94%) rename src/{cunumeric => cupynumeric}/index/wrap_template.inl (93%) rename src/{cunumeric => cupynumeric}/index/zip.cc (94%) rename src/{cunumeric => cupynumeric}/index/zip.cu (96%) rename src/{cunumeric => cupynumeric}/index/zip.h (89%) rename src/{cunumeric => cupynumeric}/index/zip_omp.cc (95%) rename src/{cunumeric => cupynumeric}/index/zip_template.inl (96%) rename src/{cunumeric => cupynumeric}/item/read.cc (88%) rename src/{cunumeric => cupynumeric}/item/read.cu (84%) rename src/{cunumeric => cupynumeric}/item/read.h (80%) rename src/{cunumeric => cupynumeric}/item/read_template.inl (93%) rename src/{cunumeric => cupynumeric}/item/write.cc (89%) rename src/{cunumeric => cupynumeric}/item/write.cu (84%) rename src/{cunumeric => cupynumeric}/item/write.h (80%) rename src/{cunumeric => cupynumeric}/item/write_template.inl (93%) rename src/{cunumeric => cupynumeric}/mapper.cc (86%) rename src/{cunumeric => cupynumeric}/mapper.h (87%) rename src/{cunumeric => cupynumeric}/matrix/batched_cholesky.cc (91%) rename src/{cunumeric => cupynumeric}/matrix/batched_cholesky.cu (91%) rename src/{cunumeric => cupynumeric}/matrix/batched_cholesky.h (75%) rename src/{cunumeric => cupynumeric}/matrix/batched_cholesky_omp.cc (91%) rename src/{cunumeric => cupynumeric}/matrix/batched_cholesky_template.inl (94%) rename src/{cunumeric => cupynumeric}/matrix/contract.cc (98%) rename src/{cunumeric => cupynumeric}/matrix/contract.cu (98%) rename src/{cunumeric => cupynumeric}/matrix/contract.h (83%) rename src/{cunumeric => cupynumeric}/matrix/contract_omp.cc (98%) rename src/{cunumeric => cupynumeric}/matrix/contract_template.inl (98%) rename src/{cunumeric => cupynumeric}/matrix/diag.cc (94%) rename src/{cunumeric => cupynumeric}/matrix/diag.cu (93%) rename src/{cunumeric => cupynumeric}/matrix/diag.h (82%) rename src/{cunumeric => cupynumeric}/matrix/diag_omp.cc (94%) rename src/{cunumeric => cupynumeric}/matrix/diag_template.inl (95%) rename src/{cunumeric => cupynumeric}/matrix/dot.cc (92%) rename src/{cunumeric => cupynumeric}/matrix/dot.cu (93%) rename src/{cunumeric => cupynumeric}/matrix/dot.h (82%) rename src/{cunumeric => cupynumeric}/matrix/dot_omp.cc (93%) rename src/{cunumeric => cupynumeric}/matrix/dot_template.inl (95%) rename src/{cunumeric => cupynumeric}/matrix/gemm.cc (96%) rename src/{cunumeric => cupynumeric}/matrix/gemm.cu (93%) rename src/{cunumeric => cupynumeric}/matrix/gemm.h (80%) rename src/{cunumeric => cupynumeric}/matrix/gemm_omp.cc (95%) rename src/{cunumeric => cupynumeric}/matrix/gemm_template.inl (97%) rename src/{cunumeric => cupynumeric}/matrix/matmul.cc (85%) rename src/{cunumeric => cupynumeric}/matrix/matmul.cu (95%) rename src/{cunumeric => cupynumeric}/matrix/matmul.h (81%) rename src/{cunumeric => cupynumeric}/matrix/matmul_cpu.inl (97%) rename src/{cunumeric => cupynumeric}/matrix/matmul_omp.cc (81%) rename src/{cunumeric => cupynumeric}/matrix/matmul_template.inl (95%) rename src/{cunumeric => cupynumeric}/matrix/matvecmul.cc (84%) rename src/{cunumeric => cupynumeric}/matrix/matvecmul.cu (95%) rename src/{cunumeric => cupynumeric}/matrix/matvecmul.h (81%) rename src/{cunumeric => cupynumeric}/matrix/matvecmul_cpu.inl (96%) rename src/{cunumeric => cupynumeric}/matrix/matvecmul_omp.cc (81%) rename src/{cunumeric => cupynumeric}/matrix/matvecmul_template.inl (95%) rename src/{cunumeric => cupynumeric}/matrix/mp_potrf.cu (95%) rename src/{cunumeric => cupynumeric}/matrix/mp_potrf.h (76%) rename src/{cunumeric => cupynumeric}/matrix/mp_potrf_template.inl (96%) rename src/{cunumeric => cupynumeric}/matrix/mp_solve.cu (97%) rename src/{cunumeric => cupynumeric}/matrix/mp_solve.h (76%) rename src/{cunumeric => cupynumeric}/matrix/mp_solve_template.inl (97%) rename src/{cunumeric => cupynumeric}/matrix/potrf.cc (95%) rename src/{cunumeric => cupynumeric}/matrix/potrf.cu (91%) rename src/{cunumeric => cupynumeric}/matrix/potrf.h (80%) rename src/{cunumeric => cupynumeric}/matrix/potrf_omp.cc (95%) rename src/{cunumeric => cupynumeric}/matrix/potrf_template.inl (96%) rename src/{cunumeric => cupynumeric}/matrix/qr.cc (85%) rename src/{cunumeric => cupynumeric}/matrix/qr.cu (90%) rename src/{cunumeric => cupynumeric}/matrix/qr.h (81%) rename src/{cunumeric => cupynumeric}/matrix/qr_cpu.inl (98%) rename src/{cunumeric => cupynumeric}/matrix/qr_omp.cc (81%) rename src/{cunumeric => cupynumeric}/matrix/qr_template.inl (94%) rename src/{cunumeric => cupynumeric}/matrix/solve.cc (85%) rename src/{cunumeric => cupynumeric}/matrix/solve.cu (92%) rename src/{cunumeric => cupynumeric}/matrix/solve.h (80%) rename src/{cunumeric => cupynumeric}/matrix/solve_cpu.inl (97%) rename src/{cunumeric => cupynumeric}/matrix/solve_omp.cc (81%) rename src/{cunumeric => cupynumeric}/matrix/solve_template.inl (92%) rename src/{cunumeric => cupynumeric}/matrix/svd.cc (85%) rename src/{cunumeric => cupynumeric}/matrix/svd.cu (95%) rename src/{cunumeric => cupynumeric}/matrix/svd.h (81%) rename src/{cunumeric => cupynumeric}/matrix/svd_cpu.inl (99%) rename src/{cunumeric => cupynumeric}/matrix/svd_omp.cc (81%) rename src/{cunumeric => cupynumeric}/matrix/svd_template.inl (96%) rename src/{cunumeric => cupynumeric}/matrix/syrk.cc (95%) rename src/{cunumeric => cupynumeric}/matrix/syrk.cu (92%) rename src/{cunumeric => cupynumeric}/matrix/syrk.h (80%) rename src/{cunumeric => cupynumeric}/matrix/syrk_omp.cc (94%) rename src/{cunumeric => cupynumeric}/matrix/syrk_template.inl (96%) rename src/{cunumeric => cupynumeric}/matrix/tile.cc (91%) rename src/{cunumeric => cupynumeric}/matrix/tile.cu (90%) rename src/{cunumeric => cupynumeric}/matrix/tile.h (81%) rename src/{cunumeric => cupynumeric}/matrix/tile_omp.cc (91%) rename src/{cunumeric => cupynumeric}/matrix/tile_template.inl (95%) rename src/{cunumeric => cupynumeric}/matrix/transpose.cc (92%) rename src/{cunumeric => cupynumeric}/matrix/transpose.cu (93%) rename src/{cunumeric => cupynumeric}/matrix/transpose.h (80%) rename src/{cunumeric => cupynumeric}/matrix/transpose_omp.cc (91%) rename src/{cunumeric => cupynumeric}/matrix/transpose_template.inl (93%) rename src/{cunumeric => cupynumeric}/matrix/trilu.cc (92%) rename src/{cunumeric => cupynumeric}/matrix/trilu.cu (90%) rename src/{cunumeric => cupynumeric}/matrix/trilu.h (81%) rename src/{cunumeric => cupynumeric}/matrix/trilu_omp.cc (92%) rename src/{cunumeric => cupynumeric}/matrix/trilu_template.inl (95%) rename src/{cunumeric => cupynumeric}/matrix/trsm.cc (95%) rename src/{cunumeric => cupynumeric}/matrix/trsm.cu (92%) rename src/{cunumeric => cupynumeric}/matrix/trsm.h (80%) rename src/{cunumeric => cupynumeric}/matrix/trsm_omp.cc (95%) rename src/{cunumeric => cupynumeric}/matrix/trsm_template.inl (96%) rename src/{cunumeric => cupynumeric}/matrix/util.cc (96%) rename src/{cunumeric => cupynumeric}/matrix/util.h (97%) rename src/{cunumeric => cupynumeric}/ndarray.cc (90%) rename src/{cunumeric => cupynumeric}/ndarray.h (97%) rename src/{cunumeric => cupynumeric}/ndarray.inl (94%) rename src/{cunumeric => cupynumeric}/nullary/arange.cc (89%) rename src/{cunumeric => cupynumeric}/nullary/arange.cu (88%) rename src/{cunumeric => cupynumeric}/nullary/arange.h (81%) rename src/{cunumeric => cupynumeric}/nullary/arange_omp.cc (88%) rename src/{cunumeric => cupynumeric}/nullary/arange_template.inl (88%) rename src/{cunumeric => cupynumeric}/nullary/eye.cc (89%) rename src/{cunumeric => cupynumeric}/nullary/eye.cu (87%) rename src/{cunumeric => cupynumeric}/nullary/eye.h (81%) rename src/{cunumeric => cupynumeric}/nullary/eye_omp.cc (88%) rename src/{cunumeric => cupynumeric}/nullary/eye_template.inl (92%) rename src/{cunumeric => cupynumeric}/nullary/fill.cc (91%) rename src/{cunumeric => cupynumeric}/nullary/fill.cu (90%) rename src/{cunumeric => cupynumeric}/nullary/fill.h (81%) rename src/{cunumeric => cupynumeric}/nullary/fill_omp.cc (92%) rename src/{cunumeric => cupynumeric}/nullary/fill_template.inl (90%) rename src/{cunumeric => cupynumeric}/nullary/window.cc (90%) rename src/{cunumeric => cupynumeric}/nullary/window.cu (90%) rename src/{cunumeric => cupynumeric}/nullary/window.h (79%) rename src/{cunumeric => cupynumeric}/nullary/window_omp.cc (90%) rename src/{cunumeric => cupynumeric}/nullary/window_template.inl (92%) rename src/{cunumeric => cupynumeric}/nullary/window_util.h (91%) rename src/{cunumeric => cupynumeric}/omp_help.h (96%) rename src/{cunumeric => cupynumeric}/operators.cc (91%) rename src/{cunumeric => cupynumeric}/operators.h (96%) rename src/{cunumeric => cupynumeric}/operators.inl (85%) rename src/{cunumeric => cupynumeric}/pitches.h (98%) rename src/{cunumeric => cupynumeric}/random/bitgenerator.cc (80%) rename src/{cunumeric => cupynumeric}/random/bitgenerator.cu (83%) rename src/{cunumeric => cupynumeric}/random/bitgenerator.h (90%) rename src/{cunumeric => cupynumeric}/random/bitgenerator_curand.inl (99%) rename src/{cunumeric => cupynumeric}/random/bitgenerator_template.inl (97%) create mode 100644 src/cupynumeric/random/bitgenerator_util.h rename src/{cunumeric => cupynumeric}/random/curand_help.h (75%) rename src/{cunumeric => cupynumeric}/random/philox.h (98%) rename src/{cunumeric => cupynumeric}/random/rand.cc (91%) rename src/{cunumeric => cupynumeric}/random/rand.cu (89%) rename src/{cunumeric => cupynumeric}/random/rand.h (80%) rename src/{cunumeric => cupynumeric}/random/rand_omp.cc (91%) rename src/{cunumeric => cupynumeric}/random/rand_template.inl (92%) rename src/{cunumeric => cupynumeric}/random/rand_util.h (98%) rename src/{cunumeric => cupynumeric}/random/randutil/generator.cuh (84%) rename src/{cunumeric => cupynumeric}/random/randutil/generator.h (96%) rename src/{cunumeric => cupynumeric}/random/randutil/generator_beta.inl (100%) rename src/{cunumeric => cupynumeric}/random/randutil/generator_binomial.inl (100%) rename src/{cunumeric => cupynumeric}/random/randutil/generator_cauchy.inl (100%) rename src/{cunumeric => cupynumeric}/random/randutil/generator_chisquare.inl (100%) rename src/{cunumeric => cupynumeric}/random/randutil/generator_create.inl (100%) rename src/{cunumeric => cupynumeric}/random/randutil/generator_device.cu (100%) rename src/{cunumeric => cupynumeric}/random/randutil/generator_device_advanced.cu (100%) rename src/{cunumeric => cupynumeric}/random/randutil/generator_device_straightforward.cu (100%) rename src/{cunumeric => cupynumeric}/random/randutil/generator_exponential.inl (100%) rename src/{cunumeric => cupynumeric}/random/randutil/generator_f.inl (100%) rename src/{cunumeric => cupynumeric}/random/randutil/generator_gamma.inl (100%) rename src/{cunumeric => cupynumeric}/random/randutil/generator_geometric.inl (100%) rename src/{cunumeric => cupynumeric}/random/randutil/generator_gumbel.inl (93%) rename src/{cunumeric => cupynumeric}/random/randutil/generator_host.cc (96%) rename src/{cunumeric => cupynumeric}/random/randutil/generator_host_advanced.cc (98%) rename src/{cunumeric => cupynumeric}/random/randutil/generator_host_straightforward.cc (98%) rename src/{cunumeric => cupynumeric}/random/randutil/generator_hypergeometric.inl (100%) rename src/{cunumeric => cupynumeric}/random/randutil/generator_integers.inl (100%) rename src/{cunumeric => cupynumeric}/random/randutil/generator_laplace.inl (100%) rename src/{cunumeric => cupynumeric}/random/randutil/generator_logistic.inl (94%) rename src/{cunumeric => cupynumeric}/random/randutil/generator_lognormal.inl (100%) rename src/{cunumeric => cupynumeric}/random/randutil/generator_logseries.inl (100%) rename src/{cunumeric => cupynumeric}/random/randutil/generator_negative_binomial.inl (100%) rename src/{cunumeric => cupynumeric}/random/randutil/generator_normal.inl (100%) rename src/{cunumeric => cupynumeric}/random/randutil/generator_pareto.inl (100%) rename src/{cunumeric => cupynumeric}/random/randutil/generator_poisson.inl (100%) rename src/{cunumeric => cupynumeric}/random/randutil/generator_power.inl (100%) rename src/{cunumeric => cupynumeric}/random/randutil/generator_raw.inl (100%) rename src/{cunumeric => cupynumeric}/random/randutil/generator_rayleigh.inl (100%) rename src/{cunumeric => cupynumeric}/random/randutil/generator_standard_t.inl (100%) rename src/{cunumeric => cupynumeric}/random/randutil/generator_triangular.inl (100%) rename src/{cunumeric => cupynumeric}/random/randutil/generator_uniform.inl (100%) rename src/{cunumeric => cupynumeric}/random/randutil/generator_vonmises.inl (100%) rename src/{cunumeric => cupynumeric}/random/randutil/generator_wald.inl (100%) rename src/{cunumeric => cupynumeric}/random/randutil/generator_weibull.inl (93%) rename src/{cunumeric => cupynumeric}/random/randutil/generator_zipf.inl (100%) rename src/{cunumeric => cupynumeric}/random/randutil/random_distributions.h (90%) rename src/{cunumeric => cupynumeric}/random/randutil/randomizer.h (94%) rename src/{cunumeric => cupynumeric}/random/randutil/randutil.h (99%) rename src/{cunumeric => cupynumeric}/random/randutil/randutil_curand.h (100%) rename src/{cunumeric => cupynumeric}/random/randutil/randutil_impl.h (100%) rename src/{cunumeric => cupynumeric}/random/rnd_aliases.h (98%) rename src/{cunumeric => cupynumeric}/random/rnd_types.h (66%) rename src/{cunumeric => cupynumeric}/runtime.cc (55%) rename src/{cunumeric => cupynumeric}/runtime.h (79%) rename src/{cunumeric => cupynumeric}/scan/scan_global.cc (94%) rename src/{cunumeric => cupynumeric}/scan/scan_global.cu (91%) rename src/{cunumeric => cupynumeric}/scan/scan_global.h (79%) rename src/{cunumeric => cupynumeric}/scan/scan_global_omp.cc (94%) rename src/{cunumeric => cupynumeric}/scan/scan_global_template.inl (95%) rename src/{cunumeric => cupynumeric}/scan/scan_local.cc (93%) rename src/{cunumeric => cupynumeric}/scan/scan_local.cu (90%) rename src/{cunumeric => cupynumeric}/scan/scan_local.h (79%) rename src/{cunumeric => cupynumeric}/scan/scan_local_omp.cc (93%) rename src/{cunumeric => cupynumeric}/scan/scan_local_template.inl (96%) rename src/{cunumeric => cupynumeric}/scan/scan_local_util.h (92%) rename src/{cunumeric => cupynumeric}/scan/scan_util.h (94%) rename src/{cunumeric => cupynumeric}/search/argwhere.cc (92%) rename src/{cunumeric => cupynumeric}/search/argwhere.cu (88%) rename src/{cunumeric => cupynumeric}/search/argwhere.h (80%) rename src/{cunumeric => cupynumeric}/search/argwhere_omp.cc (93%) rename src/{cunumeric => cupynumeric}/search/argwhere_template.inl (92%) rename src/{cunumeric => cupynumeric}/search/nonzero.cc (93%) rename src/{cunumeric => cupynumeric}/search/nonzero.cu (93%) rename src/{cunumeric => cupynumeric}/search/nonzero.cuh (94%) rename src/{cunumeric => cupynumeric}/search/nonzero.h (81%) rename src/{cunumeric => cupynumeric}/search/nonzero_omp.cc (93%) rename src/{cunumeric => cupynumeric}/search/nonzero_template.inl (92%) rename src/{cunumeric => cupynumeric}/set/unique.cc (93%) rename src/{cunumeric => cupynumeric}/set/unique.cu (89%) rename src/{cunumeric => cupynumeric}/set/unique.h (79%) rename src/{cunumeric => cupynumeric}/set/unique_omp.cc (94%) rename src/{cunumeric => cupynumeric}/set/unique_reduce.cc (85%) rename src/{cunumeric => cupynumeric}/set/unique_reduce.h (76%) rename src/{cunumeric => cupynumeric}/set/unique_reduce_omp.cc (83%) rename src/{cunumeric => cupynumeric}/set/unique_reduce_template.inl (94%) rename src/{cunumeric => cupynumeric}/set/unique_template.inl (93%) rename src/{cunumeric => cupynumeric}/slice.h (93%) rename src/{cunumeric => cupynumeric}/sort/cub_sort.cuh (97%) rename src/{cunumeric => cupynumeric}/sort/cub_sort.h (98%) rename src/{cunumeric => cupynumeric}/sort/cub_sort_bool.cu (91%) rename src/{cunumeric => cupynumeric}/sort/cub_sort_double.cu (91%) rename src/{cunumeric => cupynumeric}/sort/cub_sort_float.cu (91%) rename src/{cunumeric => cupynumeric}/sort/cub_sort_half.cu (91%) rename src/{cunumeric => cupynumeric}/sort/cub_sort_int16.cu (91%) rename src/{cunumeric => cupynumeric}/sort/cub_sort_int32.cu (91%) rename src/{cunumeric => cupynumeric}/sort/cub_sort_int64.cu (91%) rename src/{cunumeric => cupynumeric}/sort/cub_sort_int8.cu (91%) rename src/{cunumeric => cupynumeric}/sort/cub_sort_uint16.cu (91%) rename src/{cunumeric => cupynumeric}/sort/cub_sort_uint32.cu (91%) rename src/{cunumeric => cupynumeric}/sort/cub_sort_uint64.cu (91%) rename src/{cunumeric => cupynumeric}/sort/cub_sort_uint8.cu (91%) rename src/{cunumeric => cupynumeric}/sort/searchsorted.cc (95%) rename src/{cunumeric => cupynumeric}/sort/searchsorted.cu (95%) rename src/{cunumeric => cupynumeric}/sort/searchsorted.h (81%) rename src/{cunumeric => cupynumeric}/sort/searchsorted_omp.cc (95%) rename src/{cunumeric => cupynumeric}/sort/searchsorted_template.inl (95%) rename src/{cunumeric => cupynumeric}/sort/sort.cc (93%) rename src/{cunumeric => cupynumeric}/sort/sort.cu (94%) rename src/{cunumeric => cupynumeric}/sort/sort.h (92%) rename src/{cunumeric => cupynumeric}/sort/sort_cpu.inl (99%) rename src/{cunumeric => cupynumeric}/sort/sort_omp.cc (93%) rename src/{cunumeric => cupynumeric}/sort/sort_template.inl (97%) rename src/{cunumeric => cupynumeric}/sort/thrust_sort.cuh (94%) rename src/{cunumeric => cupynumeric}/sort/thrust_sort.h (99%) rename src/{cunumeric => cupynumeric}/sort/thrust_sort_bool.cu (91%) rename src/{cunumeric => cupynumeric}/sort/thrust_sort_complex128.cu (92%) rename src/{cunumeric => cupynumeric}/sort/thrust_sort_complex64.cu (92%) rename src/{cunumeric => cupynumeric}/sort/thrust_sort_double.cu (91%) rename src/{cunumeric => cupynumeric}/sort/thrust_sort_float.cu (91%) rename src/{cunumeric => cupynumeric}/sort/thrust_sort_half.cu (91%) rename src/{cunumeric => cupynumeric}/sort/thrust_sort_int16.cu (91%) rename src/{cunumeric => cupynumeric}/sort/thrust_sort_int32.cu (91%) rename src/{cunumeric => cupynumeric}/sort/thrust_sort_int64.cu (91%) rename src/{cunumeric => cupynumeric}/sort/thrust_sort_int8.cu (91%) rename src/{cunumeric => cupynumeric}/sort/thrust_sort_uint16.cu (91%) rename src/{cunumeric => cupynumeric}/sort/thrust_sort_uint32.cu (91%) rename src/{cunumeric => cupynumeric}/sort/thrust_sort_uint64.cu (91%) rename src/{cunumeric => cupynumeric}/sort/thrust_sort_uint8.cu (91%) rename src/{cunumeric => cupynumeric}/stat/bincount.cc (92%) rename src/{cunumeric => cupynumeric}/stat/bincount.cu (96%) rename src/{cunumeric => cupynumeric}/stat/bincount.h (82%) rename src/{cunumeric => cupynumeric}/stat/bincount_omp.cc (96%) rename src/{cunumeric => cupynumeric}/stat/bincount_template.inl (96%) rename src/{cunumeric => cupynumeric}/stat/histogram.cc (89%) rename src/{cunumeric => cupynumeric}/stat/histogram.cu (86%) rename src/{cunumeric => cupynumeric}/stat/histogram.cuh (93%) rename src/{cunumeric => cupynumeric}/stat/histogram.h (81%) rename src/{cunumeric => cupynumeric}/stat/histogram_cpu.h (95%) rename src/{cunumeric => cupynumeric}/stat/histogram_gen.h (98%) rename src/{cunumeric => cupynumeric}/stat/histogram_impl.h (98%) rename src/{cunumeric => cupynumeric}/stat/histogram_omp.cc (94%) rename src/{cunumeric => cupynumeric}/stat/histogram_template.inl (96%) rename src/{cunumeric => cupynumeric}/ternary/where.cc (92%) rename src/{cunumeric => cupynumeric}/ternary/where.cu (92%) rename src/{cunumeric => cupynumeric}/ternary/where.h (82%) rename src/{cunumeric => cupynumeric}/ternary/where_omp.cc (92%) rename src/{cunumeric => cupynumeric}/ternary/where_template.inl (94%) rename src/{cunumeric => cupynumeric}/transform/flip.cc (91%) rename src/{cunumeric => cupynumeric}/transform/flip.cu (91%) rename src/{cunumeric => cupynumeric}/transform/flip.h (82%) rename src/{cunumeric => cupynumeric}/transform/flip_omp.cc (91%) rename src/{cunumeric => cupynumeric}/transform/flip_template.inl (92%) rename src/{cunumeric => cupynumeric}/typedefs.h (93%) rename src/{cunumeric => cupynumeric}/unary/convert.cc (92%) rename src/{cunumeric => cupynumeric}/unary/convert.cu (91%) rename src/{cunumeric => cupynumeric}/unary/convert.h (78%) rename src/{cunumeric => cupynumeric}/unary/convert_omp.cc (92%) rename src/{cunumeric => cupynumeric}/unary/convert_template.inl (95%) rename src/{cunumeric => cupynumeric}/unary/convert_util.h (81%) rename src/{cunumeric => cupynumeric}/unary/isnan.h (95%) rename src/{cunumeric => cupynumeric}/unary/scalar_unary_red.cc (84%) rename src/{cunumeric => cupynumeric}/unary/scalar_unary_red.cu (73%) rename src/{cunumeric => cupynumeric}/unary/scalar_unary_red.h (79%) rename src/{cunumeric => cupynumeric}/unary/scalar_unary_red_omp.cc (76%) rename src/{cunumeric => cupynumeric}/unary/scalar_unary_red_template.inl (95%) rename src/{cunumeric => cupynumeric}/unary/unary_op.cc (96%) rename src/{cunumeric => cupynumeric}/unary/unary_op.cu (95%) rename src/{cunumeric => cupynumeric}/unary/unary_op.h (72%) rename src/{cunumeric => cupynumeric}/unary/unary_op_omp.cc (96%) rename src/{cunumeric => cupynumeric}/unary/unary_op_template.inl (96%) rename src/{cunumeric => cupynumeric}/unary/unary_op_util.h (94%) rename src/{cunumeric => cupynumeric}/unary/unary_red.cc (92%) rename src/{cunumeric => cupynumeric}/unary/unary_red.cu (98%) rename src/{cunumeric => cupynumeric}/unary/unary_red.h (79%) rename src/{cunumeric => cupynumeric}/unary/unary_red_omp.cc (95%) rename src/{cunumeric => cupynumeric}/unary/unary_red_template.inl (92%) rename src/{cunumeric => cupynumeric}/unary/unary_red_util.h (94%) rename src/{cunumeric => cupynumeric}/utilities/repartition.cc (98%) rename src/{cunumeric => cupynumeric}/utilities/repartition.cu (98%) rename src/{cunumeric => cupynumeric}/utilities/repartition.h (94%) rename src/{cunumeric => cupynumeric}/utilities/thrust_allocator.h (95%) rename src/{cunumeric => cupynumeric}/utilities/thrust_util.h (100%) rename tests/unit/{cunumeric => cupynumeric}/__init__.py (100%) rename tests/unit/{cunumeric => cupynumeric}/_array/__init__.py (100%) rename tests/unit/{cunumeric => cupynumeric}/_array/test_util.py (97%) rename tests/unit/{cunumeric => cupynumeric}/_sphinxext/__init__.py (100%) rename tests/unit/{cunumeric => cupynumeric}/_sphinxext/test__comparison_util.py (94%) rename tests/unit/{cunumeric => cupynumeric}/_utils/__init__.py (100%) rename tests/unit/{cunumeric => cupynumeric}/_utils/test_array.py (98%) rename tests/unit/{cunumeric => cupynumeric}/_utils/test_coverage.py (86%) rename tests/unit/{cunumeric => cupynumeric}/_utils/test_linalg.py (99%) rename tests/unit/{cunumeric => cupynumeric}/random/__init__.py (100%) rename tests/unit/{cunumeric => cupynumeric}/random/test_bitgenerator.py (96%) rename tests/unit/{cunumeric => cupynumeric}/test_config.py (84%) rename tests/unit/{cunumeric => cupynumeric}/test_nptest.py (81%) rename tests/unit/{cunumeric => cupynumeric}/test_patch.py (84%) rename tests/unit/{cunumeric => cupynumeric}/test_settings.py (94%) diff --git a/.github/workflows/ci-gh-validate-legate-sha.yml b/.github/workflows/ci-gh-validate-legate-sha.yml index d15982ca3c..9e2309a233 100644 --- a/.github/workflows/ci-gh-validate-legate-sha.yml +++ b/.github/workflows/ci-gh-validate-legate-sha.yml @@ -20,7 +20,7 @@ jobs: - name: Checkout repository uses: actions/checkout@v4 with: - path: cunumeric.internal + path: cupynumeric.internal - name: Set up environment run: | @@ -30,7 +30,7 @@ jobs: - name: Parse versions.json shell: bash --noprofile --norc -xeuo pipefail {0} run: | - DEPENDENCIES_FILE="cunumeric.internal/cmake/versions.json" + DEPENDENCIES_FILE="cupynumeric.internal/cmake/versions.json" GIT_REPO=$(jq -r '.packages.legate.repo' ${DEPENDENCIES_FILE}) GIT_ORG=$(jq -r '.packages.legate.org' ${DEPENDENCIES_FILE}) GIT_TAG=$(jq -r '.packages.legate.git_tag' ${DEPENDENCIES_FILE}) diff --git a/.gitignore b/.gitignore index 84244ce827..d4ccc950aa 100644 --- a/.gitignore +++ b/.gitignore @@ -27,11 +27,11 @@ legion gasnet* legion_defines.h realm_defines.h -cunumeric/install_info.py +cupynumeric/install_info.py /build/* -/docs/cunumeric/build -/docs/cunumeric/source/api/generated -/docs/cunumeric/source/comparison/comparison_table.rst.inc +/docs/cupynumeric/build +/docs/cupynumeric/source/api/generated +/docs/cupynumeric/source/comparison/comparison_table.rst.inc *.egg-info .cache .vscode diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index c8b84bdb52..47514166f3 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -5,7 +5,7 @@ repos: - id: mypy language: system pass_filenames: false - args: ['cunumeric'] + args: ['cupynumeric'] - repo: https://github.com/PyCQA/isort rev: 5.12.0 hooks: @@ -40,7 +40,7 @@ repos: 'types_or': [c++, c, cuda] require_serial: false stages: [pre-commit] - exclude: '^src/cunumeric/cunumeric_c\.h$' + exclude: '^src/cupynumeric/cunumeric_c\.h$' ci: skip: [mypy] diff --git a/CMakeLists.txt b/CMakeLists.txt index 7c94756055..9a063e2c3b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -63,7 +63,7 @@ include(rapids-cuda) include(rapids-export) include(rapids-find) -set(cunumeric_version 25.01.00) +set(cupynumeric_version 25.01.00) # For now we want the optimization flags to match on both normal make and cmake # builds so we override the cmake defaults here for release, this changes @@ -78,40 +78,40 @@ set(CMAKE_CXX_FLAGS_RELWITHDEBINFO "-O2 -g") set(CMAKE_CUDA_FLAGS_RELWITHDEBINFO "-O2 -g") if(NOT SKBUILD) - project(cunumeric VERSION ${cunumeric_version} LANGUAGES C CXX) - include(cunumeric_cpp.cmake) + project(cupynumeric VERSION ${cupynumeric_version} LANGUAGES C CXX) + include(cupynumeric_cpp.cmake) else() project( - cunumeric_python - VERSION ${cunumeric_version} + cupynumeric_python + VERSION ${cupynumeric_version} LANGUAGES # TODO: Building Python extension modules via the python_extension_module requires the C # language to be enabled here. The test project that is built in scikit-build to verify # various linking options for the python library is hardcoded to build with C, so until # that is fixed we need to keep C. C CXX) - include(cunumeric_python.cmake) + include(cupynumeric_python.cmake) endif() if(CMAKE_GENERATOR STREQUAL "Ninja") - function(add_touch_cunumeric_ninja_build_target) + function(add_touch_cupynumeric_ninja_build_target) set(_suf ) if(SKBUILD) set(_suf "_python") endif() - add_custom_target("touch_cunumeric${_suf}_ninja_build" ALL + add_custom_target("touch_cupynumeric${_suf}_ninja_build" ALL COMMAND ${CMAKE_COMMAND} -E touch_nocreate "${CMAKE_CURRENT_BINARY_DIR}/build.ninja" COMMENT "touch build.ninja so ninja doesn't re-run CMake on rebuild" VERBATIM ) - foreach(_dep IN ITEMS cunumeric cunumeric_python + foreach(_dep IN ITEMS cupynumeric cupynumeric_python legate legate_python Legion LegionRuntime Realm RealmRuntime Regent) if(TARGET ${_dep}) - add_dependencies("touch_cunumeric${_suf}_ninja_build" ${_dep}) + add_dependencies("touch_cupynumeric${_suf}_ninja_build" ${_dep}) endif() endforeach() endfunction() - add_touch_cunumeric_ninja_build_target() + add_touch_cupynumeric_ninja_build_target() endif() diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index e083cc3c0c..b4ac11a6a5 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,16 +1,16 @@ -# Contributing to cuNumeric +# Contributing to cuPyNumeric -CuNumeric is an open-source project released under the [Apache license, version 2.0](https://www.apache.org/licenses/LICENSE-2.0). We welcome any and all contributions, and we hope that you can help us develop a strong community. +CuPyNumeric is an open-source project released under the [Apache license, version 2.0](https://www.apache.org/licenses/LICENSE-2.0). We welcome any and all contributions, and we hope that you can help us develop a strong community. ## How to begin -Most of the time, the best thing is to begin by [opening an issue](https://github.com/nv-legate/cunumeric/issues). This gives us a chance to discuss the contribution and to define the problem or feature that it addresses. Often, opening of the issue first may help prevent you from doing unnecessary work or to enhance and further develop your idea. +Most of the time, the best thing is to begin by [opening an issue](https://github.com/nv-legate/cupynumeric/issues). This gives us a chance to discuss the contribution and to define the problem or feature that it addresses. Often, opening of the issue first may help prevent you from doing unnecessary work or to enhance and further develop your idea. Once you are ready to start development, we ask you to work on a [fork](https://docs.github.com/en/get-started/quickstart/fork-a-repo) of our repository. The next step is to create a (pull request)[https://help.github.com/en/articles/about-pull-requests]. Feel free to open the pull request as soon as you begin your development (just mark it [as a draft](https://github.blog/2019-02-14-introducing-draft-pull-requests/)) or when you are ready to have your contribution merged. ## The Legalese: Developer Certificate of Origin -CuNumeric is released under the open-source [Apache license, version 2.0](https://www.apache.org/licenses/LICENSE-2.0), and is free to use, modify, and redistribute. To ensure that the license can be exercised without encumbrance, we ask you that you only contribute your own work or work to which you have the intellectual rights. To that end, we employ the Developer's Certificate of Origin (DCO), which is the lightweight mechanism for you to certify that you are legally able to make your contribution. Here is the full text of the certificate (also available at [DeveloperCertificate.org](https://developercertificate.org/): +CuPyNumeric is released under the open-source [Apache license, version 2.0](https://www.apache.org/licenses/LICENSE-2.0), and is free to use, modify, and redistribute. To ensure that the license can be exercised without encumbrance, we ask you that you only contribute your own work or work to which you have the intellectual rights. To that end, we employ the Developer's Certificate of Origin (DCO), which is the lightweight mechanism for you to certify that you are legally able to make your contribution. Here is the full text of the certificate (also available at [DeveloperCertificate.org](https://developercertificate.org/): ```` Developer Certificate of Origin @@ -61,12 +61,12 @@ Please use your real name and a valid email address at which you can be reached. ## Review Process -We are really grateful that you are thinking of contributing to cuNumeric. We will make every effort to review your contributions as soon as possible. +We are really grateful that you are thinking of contributing to cuPyNumeric. We will make every effort to review your contributions as soon as possible. As we suggested at the beginning of this document, it will be really helpful to start with an issue unless your proposed change is really trivial. An issue will help to save work in the review process (e.g., maybe somebody is already working on exactly the same thing you want to work on). After you open your pull request (PR), there usually will be a community feedback that often will require further changes to your contribution (the usual open-source process). Usually, this will conclude in the PR being merged by a maintainer, but on rare occasions a PR may be rejected. This may happen, for example, if the PR appears abandoned (no response to the community feedback) or if the PR does not seem to be approaching community acceptance in a reasonable time frame. In any case, an explanation will always be given why a PR is closed. Even if a PR is closed for some reason, it may always be reopened if the situation evolves (feel free to comment on closed PRs to discuss reopening them). ## Code Formatting Requirements -CuNumeric has a set of coding standards that are expected from all the code merged into the project. The coding standards are defined by the set of tools we use to format our code. We use the [pre-commit](https://pre-commit.com/) framework to run our formatting tools. The easiest way to meet the coding standards is to simply use the pre-commit framework to run all the checks for you. Please visit the [pre-commit project page](https://pre-commit.com/) for pre-commit installation and usage instructions. Once pre-commit is installed in the cuNumeric repo, all the checks and formatting will be run on every commit, but one can also run the checks explicitly as detailed in pre-commit documentation. +CuPyNumeric has a set of coding standards that are expected from all the code merged into the project. The coding standards are defined by the set of tools we use to format our code. We use the [pre-commit](https://pre-commit.com/) framework to run our formatting tools. The easiest way to meet the coding standards is to simply use the pre-commit framework to run all the checks for you. Please visit the [pre-commit project page](https://pre-commit.com/) for pre-commit installation and usage instructions. Once pre-commit is installed in the cuPyNumeric repo, all the checks and formatting will be run on every commit, but one can also run the checks explicitly as detailed in pre-commit documentation. We hope that the automation of our formatting checks will make it easy to comply with our coding standards. If you encounter problems with code formatting, however, please let us know in a comment on your PR, and we will do our best to help. diff --git a/MANIFEST.in b/MANIFEST.in index 8f77ed2002..3eb2279b7b 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -1,3 +1,3 @@ include versioneer.py -include cunumeric/_version.py -include cunumeric/py.typed +include cupynumeric/_version.py +include cupynumeric/py.typed diff --git a/README.md b/README.md index e184fbac36..44bc15c90e 100644 --- a/README.md +++ b/README.md @@ -17,14 +17,14 @@ limitations under the License. *This project, i.e., cuPyNumeric, is separate and independent of the CuPy project. CuPy is a registered trademark of Preferred Networks.* -[![Build Nightly release package](https://github.com/nv-legate/cunumeric.internal/actions/workflows/ci-gh-nightly-release.yml/badge.svg)](https://github.com/nv-legate/cunumeric.internal/actions/workflows/ci-gh-nightly-release.yml) +[![Build Nightly release package](https://github.com/nv-legate/cupynumeric.internal/actions/workflows/ci-gh-nightly-release.yml/badge.svg)](https://github.com/nv-legate/cupynumeric.internal/actions/workflows/ci-gh-nightly-release.yml) -# cuNumeric +# cuPyNumeric -cuNumeric is a [Legate](https://github.com/nv-legate/legate.core) library +cuPyNumeric is a [Legate](https://github.com/nv-legate/legate.core) library that aims to provide a distributed and accelerated drop-in replacement for the [NumPy API](https://numpy.org/doc/stable/reference/) on top of the -[Legion](https://legion.stanford.edu) runtime. Using cuNumeric you can do things like run +[Legion](https://legion.stanford.edu) runtime. Using cuPyNumeric you can do things like run [the final example of the Python CFD course](https://github.com/barbagroup/CFDPython/blob/master/lessons/15_Step_12.ipynb) completely unmodified on 2048 A100 GPUs in a [DGX SuperPOD](https://www.nvidia.com/en-us/data-center/dgx-superpod/) @@ -32,7 +32,7 @@ and achieve good weak scaling. drawing -cuNumeric works best for programs that have very large arrays of data +cuPyNumeric works best for programs that have very large arrays of data that cannot fit in the memory of a single GPU or a single node and need to span multiple nodes and GPUs. While our implementation of the current NumPy API is still incomplete, programs that use unimplemented features @@ -41,16 +41,16 @@ canonical NumPy implementation. ## Installation -cuNumeric is available from [conda](https://docs.conda.io/projects/conda/en/latest/index.html) -on the [legate channel](https://anaconda.org/legate/cunumeric). -See https://docs.nvidia.com/cunumeric/latest/installation.html for +cuPyNumeric is available from [conda](https://docs.conda.io/projects/conda/en/latest/index.html) +on the [legate channel](https://anaconda.org/legate/cupynumeric). +See https://docs.nvidia.com/cupynumeric/latest/installation.html for details about different install configurations, or building -cuNumeric from source. +cuPyNumeric from source. ## Documentation -The cuNumeric documentation can be found -[here](https://docs.nvidia.com/cunumeric). +The cuPyNumeric documentation can be found +[here](https://docs.nvidia.com/cupynumeric). ## Contributing @@ -58,7 +58,7 @@ See the discussion on contributing in [CONTRIBUTING.md](CONTRIBUTING.md). ## Contact -For technical questions about Cunumeric and Legate-based tools, please visit +For technical questions about cuPyNumeric and Legate-based tools, please visit the [community discussion forum](https://github.com/nv-legate/discussion). If you have other questions, please contact us at legate(at)nvidia.com. diff --git a/cmake/generate_install_info_py.cmake b/cmake/generate_install_info_py.cmake index 190641a463..724640cbb7 100644 --- a/cmake/generate_install_info_py.cmake +++ b/cmake/generate_install_info_py.cmake @@ -17,8 +17,8 @@ execute_process( COMMAND ${CMAKE_C_COMPILER} -E -DLEGATE_USE_PYTHON_CFFI - -I "${CMAKE_CURRENT_LIST_DIR}/../src/cunumeric" - -P "${CMAKE_CURRENT_LIST_DIR}/../src/cunumeric/cunumeric_c.h" + -I "${CMAKE_CURRENT_LIST_DIR}/../src/cupynumeric" + -P "${CMAKE_CURRENT_LIST_DIR}/../src/cupynumeric/cupynumeric_c.h" ECHO_ERROR_VARIABLE OUTPUT_VARIABLE header COMMAND_ERROR_IS_FATAL ANY @@ -26,6 +26,6 @@ execute_process( set(libpath "") configure_file( - "${CMAKE_CURRENT_LIST_DIR}/../cunumeric/install_info.py.in" - "${CMAKE_CURRENT_LIST_DIR}/../cunumeric/install_info.py" + "${CMAKE_CURRENT_LIST_DIR}/../cupynumeric/install_info.py.in" + "${CMAKE_CURRENT_LIST_DIR}/../cupynumeric/install_info.py" @ONLY) diff --git a/cmake/thirdparty/get_legate.cmake b/cmake/thirdparty/get_legate.cmake index b8fcb1c356..68db8207b3 100644 --- a/cmake/thirdparty/get_legate.cmake +++ b/cmake/thirdparty/get_legate.cmake @@ -35,8 +35,8 @@ function(find_or_configure_legate) set(FIND_PKG_ARGS GLOBAL_TARGETS legate::legate - BUILD_EXPORT_SET cunumeric-exports - INSTALL_EXPORT_SET cunumeric-exports) + BUILD_EXPORT_SET cupynumeric-exports + INSTALL_EXPORT_SET cupynumeric-exports) # First try to find legate via find_package() # so the `Legion_USE_*` variables are visible @@ -55,11 +55,11 @@ function(find_or_configure_legate) include(${CMAKE_CURRENT_SOURCE_DIR}/cmake/Modules/cpm_helpers.cmake) get_cpm_git_args(legate_cpm_git_args REPOSITORY ${git_repo} BRANCH ${git_branch}) - message(VERBOSE "cunumeric: legate version: ${version}") - message(VERBOSE "cunumeric: legate git_repo: ${git_repo}") - message(VERBOSE "cunumeric: legate git_branch: ${git_branch}") - message(VERBOSE "cunumeric: legate exclude_from_all: ${exclude_from_all}") - message(VERBOSE "cunumeric: legate legate_cpm_git_args: ${legate_cpm_git_args}") + message(VERBOSE "cupynumeric: legate version: ${version}") + message(VERBOSE "cupynumeric: legate git_repo: ${git_repo}") + message(VERBOSE "cupynumeric: legate git_branch: ${git_branch}") + message(VERBOSE "cupynumeric: legate exclude_from_all: ${exclude_from_all}") + message(VERBOSE "cupynumeric: legate legate_cpm_git_args: ${legate_cpm_git_args}") rapids_cpm_find(legate ${version} ${FIND_PKG_ARGS} CPM_ARGS @@ -78,27 +78,27 @@ function(find_or_configure_legate) message(VERBOSE "Legion_BOUNDS_CHECKS=${Legion_BOUNDS_CHECKS}") endfunction() -foreach(_var IN ITEMS "cunumeric_LEGATE_VERSION" - "cunumeric_LEGATE_BRANCH" - "cunumeric_LEGATE_REPOSITORY" - "cunumeric_EXCLUDE_LEGATE_FROM_ALL") +foreach(_var IN ITEMS "cupynumeric_LEGATE_VERSION" + "cupynumeric_LEGATE_BRANCH" + "cupynumeric_LEGATE_REPOSITORY" + "cupynumeric_EXCLUDE_LEGATE_FROM_ALL") if(DEFINED ${_var}) - # Create a cunumeric_LEGATE_BRANCH variable in the current scope either from the existing + # Create a cupynumeric_LEGATE_BRANCH variable in the current scope either from the existing # current-scope variable, or the cache variable. set(${_var} "${${_var}}") - # Remove cunumeric_LEGATE_BRANCH from the CMakeCache.txt. This ensures reconfiguring the same - # build dir without passing `-Dcunumeric_LEGATE_BRANCH=` reverts to the value in versions.json - # instead of reusing the previous `-Dcunumeric_LEGATE_BRANCH=` value. + # Remove cupynumeric_LEGATE_BRANCH from the CMakeCache.txt. This ensures reconfiguring the same + # build dir without passing `-Dcupynumeric_LEGATE_BRANCH=` reverts to the value in versions.json + # instead of reusing the previous `-Dcupynumeric_LEGATE_BRANCH=` value. unset(${_var} CACHE) endif() endforeach() -if(NOT DEFINED cunumeric_LEGATE_VERSION) - set(cunumeric_LEGATE_VERSION "${cunumeric_VERSION}") +if(NOT DEFINED cupynumeric_LEGATE_VERSION) + set(cupynumeric_LEGATE_VERSION "${cupynumeric_VERSION}") endif() -find_or_configure_legate(VERSION ${cunumeric_LEGATE_VERSION} - REPOSITORY ${cunumeric_LEGATE_REPOSITORY} - BRANCH ${cunumeric_LEGATE_BRANCH} - EXCLUDE_FROM_ALL ${cunumeric_EXCLUDE_LEGATE_FROM_ALL} +find_or_configure_legate(VERSION ${cupynumeric_LEGATE_VERSION} + REPOSITORY ${cupynumeric_LEGATE_REPOSITORY} + BRANCH ${cupynumeric_LEGATE_BRANCH} + EXCLUDE_FROM_ALL ${cupynumeric_EXCLUDE_LEGATE_FROM_ALL} ) diff --git a/cmake/thirdparty/get_openblas.cmake b/cmake/thirdparty/get_openblas.cmake index d4e4454a09..aa7030ca56 100644 --- a/cmake/thirdparty/get_openblas.cmake +++ b/cmake/thirdparty/get_openblas.cmake @@ -22,7 +22,7 @@ function(find_or_configure_OpenBLAS) set(BLAS_name "OpenBLAS") set(BLAS_target "openblas") - # cuNumeric presently requires OpenBLAS + # cuPyNumeric presently requires OpenBLAS set(BLA_VENDOR OpenBLAS) # TODO: should we find (or build) 64-bit BLAS? @@ -35,8 +35,8 @@ function(find_or_configure_OpenBLAS) set(FIND_PKG_ARGS ${PKG_VERSION} GLOBAL_TARGETS ${BLAS_target} - BUILD_EXPORT_SET cunumeric-exports - INSTALL_EXPORT_SET cunumeric-exports) + BUILD_EXPORT_SET cupynumeric-exports + INSTALL_EXPORT_SET cupynumeric-exports) include(${CMAKE_CURRENT_SOURCE_DIR}/cmake/Modules/cpm_helpers.cmake) if(PKG_BRANCH) @@ -105,35 +105,35 @@ function(find_or_configure_OpenBLAS) FINAL_CODE_BLOCK code_string) # Do `CPMFindPackage(BLAS)` in build dir - rapids_export_package(BUILD BLAS cunumeric-exports + rapids_export_package(BUILD BLAS cupynumeric-exports VERSION ${PKG_VERSION} GLOBAL_TARGETS ${BLAS_target}) # Tell cmake where it can find the generated blas-config.cmake include("${rapids-cmake-dir}/export/find_package_root.cmake") - rapids_export_find_package_root(BUILD BLAS [=[${CMAKE_CURRENT_LIST_DIR}]=] EXPORT_SET cunumeric-exports) + rapids_export_find_package_root(BUILD BLAS [=[${CMAKE_CURRENT_LIST_DIR}]=] EXPORT_SET cupynumeric-exports) endif() endfunction() -if(NOT DEFINED cunumeric_OPENBLAS_VERSION) +if(NOT DEFINED cupynumeric_OPENBLAS_VERSION) # Before v0.3.18, OpenBLAS's throws CMake errors when configuring - set(cunumeric_OPENBLAS_VERSION "0.3.20") + set(cupynumeric_OPENBLAS_VERSION "0.3.20") endif() -if(NOT DEFINED cunumeric_OPENBLAS_BRANCH) - set(cunumeric_OPENBLAS_BRANCH "") +if(NOT DEFINED cupynumeric_OPENBLAS_BRANCH) + set(cupynumeric_OPENBLAS_BRANCH "") endif() -if(NOT DEFINED cunumeric_OPENBLAS_TAG) - set(cunumeric_OPENBLAS_TAG v${cunumeric_OPENBLAS_VERSION}) +if(NOT DEFINED cupynumeric_OPENBLAS_TAG) + set(cupynumeric_OPENBLAS_TAG v${cupynumeric_OPENBLAS_VERSION}) endif() -if(NOT DEFINED cunumeric_OPENBLAS_REPOSITORY) - set(cunumeric_OPENBLAS_REPOSITORY https://github.com/xianyi/OpenBLAS.git) +if(NOT DEFINED cupynumeric_OPENBLAS_REPOSITORY) + set(cupynumeric_OPENBLAS_REPOSITORY https://github.com/xianyi/OpenBLAS.git) endif() -find_or_configure_OpenBLAS(VERSION ${cunumeric_OPENBLAS_VERSION} - REPOSITORY ${cunumeric_OPENBLAS_REPOSITORY} - BRANCH ${cunumeric_OPENBLAS_BRANCH} - PINNED_TAG ${cunumeric_OPENBLAS_TAG} - EXCLUDE_FROM_ALL ${cunumeric_EXCLUDE_OPENBLAS_FROM_ALL} +find_or_configure_OpenBLAS(VERSION ${cupynumeric_OPENBLAS_VERSION} + REPOSITORY ${cupynumeric_OPENBLAS_REPOSITORY} + BRANCH ${cupynumeric_OPENBLAS_BRANCH} + PINNED_TAG ${cupynumeric_OPENBLAS_TAG} + EXCLUDE_FROM_ALL ${cupynumeric_EXCLUDE_OPENBLAS_FROM_ALL} ) diff --git a/cmake/thirdparty/get_tblis.cmake b/cmake/thirdparty/get_tblis.cmake index dbe0d4e935..164923601b 100644 --- a/cmake/thirdparty/get_tblis.cmake +++ b/cmake/thirdparty/get_tblis.cmake @@ -34,14 +34,14 @@ function(find_or_configure_tblis) HEADER_NAMES "tblis/tblis.h" LIBRARY_NAMES "libtblis${lib_suffix}" NO_CONFIG - BUILD_EXPORT_SET cunumeric-exports - INSTALL_EXPORT_SET cunumeric-exports + BUILD_EXPORT_SET cupynumeric-exports + INSTALL_EXPORT_SET cupynumeric-exports ) rapids_cpm_find(tblis ${PKG_VERSION} GLOBAL_TARGETS tblis::tblis - BUILD_EXPORT_SET cunumeric-exports - INSTALL_EXPORT_SET cunumeric-exports + BUILD_EXPORT_SET cupynumeric-exports + INSTALL_EXPORT_SET cupynumeric-exports CPM_ARGS ${tblis_cpm_git_args} EXCLUDE_FROM_ALL ${PKG_EXCLUDE_FROM_ALL} @@ -95,8 +95,8 @@ function(find_or_configure_tblis) set(ENV{CC} "${_CC}") set(ENV{CXX} "${_CXX}") - message(VERBOSE "cunumeric: ENV{CC}=\"$ENV{CC}\"") - message(VERBOSE "cunumeric: ENV{CXX}=\"$ENV{CXX}\"") + message(VERBOSE "cupynumeric: ENV{CC}=\"$ENV{CC}\"") + message(VERBOSE "cupynumeric: ENV{CXX}=\"$ENV{CXX}\"") set(tblis_verbosity "--enable-silent-rules") if(CMAKE_VERSION VERSION_GREATER_EQUAL "3.25") @@ -167,20 +167,20 @@ function(find_or_configure_tblis) endif() set(tblis_BINARY_DIR ${tblis_BINARY_DIR} PARENT_SCOPE) - set(cunumeric_INSTALL_TBLIS ${should_build_tblis} PARENT_SCOPE) + set(cupynumeric_INSTALL_TBLIS ${should_build_tblis} PARENT_SCOPE) endfunction() -if(NOT DEFINED cunumeric_TBLIS_BRANCH) - set(cunumeric_TBLIS_BRANCH arm-build) +if(NOT DEFINED cupynumeric_TBLIS_BRANCH) + set(cupynumeric_TBLIS_BRANCH arm-build) endif() -if(NOT DEFINED cunumeric_TBLIS_REPOSITORY) - set(cunumeric_TBLIS_REPOSITORY https://github.com/nv-legate/tblis.git) +if(NOT DEFINED cupynumeric_TBLIS_REPOSITORY) + set(cupynumeric_TBLIS_REPOSITORY https://github.com/nv-legate/tblis.git) endif() find_or_configure_tblis(VERSION 1.2.0 - REPOSITORY ${cunumeric_TBLIS_REPOSITORY} - BRANCH ${cunumeric_TBLIS_BRANCH} - EXCLUDE_FROM_ALL ${cunumeric_EXCLUDE_TBLIS_FROM_ALL} + REPOSITORY ${cupynumeric_TBLIS_REPOSITORY} + BRANCH ${cupynumeric_TBLIS_BRANCH} + EXCLUDE_FROM_ALL ${cupynumeric_EXCLUDE_TBLIS_FROM_ALL} USE_OPENMP ${Legion_USE_OpenMP} ) diff --git a/cmake/versions.json b/cmake/versions.json index a02b56e32d..4cafa0dabe 100644 --- a/cmake/versions.json +++ b/cmake/versions.json @@ -10,7 +10,7 @@ "git_url" : "git@github.com:nv-legate/legate.core.internal.git", "git_shallow": false, "always_download": false, - "git_tag" : "387cd4f12ba601eb350ed8b032737e2fb0a9346b" + "git_tag" : "838956e08b544fa3b63280a87dea0cf8020bef82" } } } diff --git a/conda/conda-build/build.sh b/conda/conda-build/build.sh index c78cbbcca9..2a7b6589fc 100644 --- a/conda/conda-build/build.sh +++ b/conda/conda-build/build.sh @@ -26,7 +26,7 @@ if [ -z "$CPU_ONLY" ]; then else # When we build without cuda, we need to provide the location of curand CMAKE_ARGS+=" --Dcunumeric_cuRAND_INCLUDE_DIR=$PREFIX/targets/x86_64-linux/include" +-Dcupynumeric_cuRAND_INCLUDE_DIR=$PREFIX/targets/x86_64-linux/include" fi export CMAKE_GENERATOR=Ninja @@ -45,8 +45,8 @@ cmake --build build -j$CPU_COUNT --verbose cmake --install build CMAKE_ARGS=" --DFIND_CUNUMERIC_CPP=ON --Dcunumeric_ROOT=$PREFIX" +-DFIND_CUPYNUMERIC_CPP=ON +-Dcupynumeric_ROOT=$PREFIX" SKBUILD_BUILD_OPTIONS=-j$CPU_COUNT \ $PYTHON -m pip install \ diff --git a/conda/conda-build/meta.yaml b/conda/conda-build/meta.yaml index d7db54b8bc..2aaddb22b1 100644 --- a/conda/conda-build/meta.yaml +++ b/conda/conda-build/meta.yaml @@ -1,4 +1,4 @@ -{% set name = "cunumeric" %} +{% set name = "cupynumeric" %} {% if gpu_enabled == "true" %} {% set gpu_enabled_bool = true %} {% elif gpu_enabled == "false" %} @@ -148,16 +148,16 @@ requirements: - __glibc >=2.17 # [linux] about: - home: https://github.com/nv-legate/cunumeric + home: https://github.com/nv-legate/cupynumeric license: Apache-2.0 license_file: LICENSE summary: 'Drop-in Replacment for NumPy' description: | - cuNumeric is a Legate library that aims to provide + cuPyNumeric is a Legate library that aims to provide a distributed and accelerated drop-in replacement for the NumPy API on top of the Legion runtime. - doc_url: https://github.com/nv-legate/cunumeric - dev_url: https://github.com/nv-legate/cunumeric + doc_url: https://github.com/nv-legate/cupynumeric + dev_url: https://github.com/nv-legate/cupynumeric extra: recipe-maintainers: diff --git a/continuous_integration/scripts/build b/continuous_integration/scripts/build index f68c5ba800..bf95d91e7d 100755 --- a/continuous_integration/scripts/build +++ b/continuous_integration/scripts/build @@ -17,7 +17,7 @@ build_release_product() { conda_build_args+=(-c conda-forge); conda_build_args+=(--override-channels); conda_build_args+=(-c file:///tmp/conda-build/legate); - conda_build_args+=(--croot /tmp/conda-build/cunumeric); + conda_build_args+=(--croot /tmp/conda-build/cupynumeric); conda_build_args+=(--numpy 1.22); conda_build_args+=(--no-test); conda_build_args+=(--no-verify); @@ -36,7 +36,7 @@ build_release_product() { conda_build_args+=(--variants "$variantOpts") - # https://github.com/nv-legate/cunumeric.internal/pull/351#issuecomment-2286922486 + # https://github.com/nv-legate/cupynumeric.internal/pull/351#issuecomment-2286922486 export CONDA_OVERRIDE_CUDA="${CUDA_VERSION}" conda mambabuild "${conda_build_args[@]}" "${REPO_DIR}/conda/conda-build"; @@ -65,25 +65,25 @@ copy_ci_artifacts() { build_ci_product() { set -xeuo pipefail; - printf "\n\n\n\n********* BUILDING CUNUMERIC CPP *********\n" - build-cunumeric-cpp; + printf "\n\n\n\n********* BUILDING CUPYNUMERIC CPP *********\n" + build-cupynumeric-cpp; - printf "\n\n\n\n********* BUILDING CUNUMERIC WHEEL *********\n" - build-cunumeric-wheel; + printf "\n\n\n\n********* BUILDING CUPYNUMERIC WHEEL *********\n" + build-cupynumeric-wheel; - printf "\n\n\n\n********* BUILDING CUNUMERIC CONDA *********\n" - build-cunumeric-conda; + printf "\n\n\n\n********* BUILDING CUPYNUMERIC CONDA *********\n" + build-cupynumeric-conda; copy_ci_artifacts; } -build_cunumeric_fake() { +build_cupynumeric_fake() { set -xeuo pipefail; - mkdir -p /tmp/out /tmp/conda-build/legate /tmp/conda-build/cunumeric + mkdir -p /tmp/out /tmp/conda-build/legate /tmp/conda-build/cupynumeric touch /tmp/out/legate-23.11.00-dummy.tar.bz2 touch /tmp/conda-build/legate/dummy.txt - touch /tmp/conda-build/cunumeric/dummy.txt + touch /tmp/conda-build/cupynumeric/dummy.txt copy_ci_artifacts } diff --git a/continuous_integration/scripts/build-cunumeric-conda b/continuous_integration/scripts/build-cupynumeric-conda similarity index 79% rename from continuous_integration/scripts/build-cunumeric-conda rename to continuous_integration/scripts/build-cupynumeric-conda index e1b83ca699..4a6a1b2469 100755 --- a/continuous_integration/scripts/build-cunumeric-conda +++ b/continuous_integration/scripts/build-cupynumeric-conda @@ -1,6 +1,6 @@ #!/usr/bin/env bash -build_cunumeric_conda_package() { +build_cupynumeric_conda_package() { set -xeuo pipefail; local python_version="${PYTHON_VERSION:-}"; @@ -18,7 +18,7 @@ build_cunumeric_conda_package() { # the ucx channel is only necessary as a WAR until the real ucx 1.17 package is available on conda-forge conda_build_args+=(-c https://github.com/nv-legate/ucx-package/raw/main); conda_build_args+=(-c file:///tmp/conda-build/legate); - conda_build_args+=(--croot /tmp/conda-build/cunumeric); + conda_build_args+=(--croot /tmp/conda-build/cupynumeric); conda_build_args+=(--numpy 1.22); conda_build_args+=(--python ${python_version}); conda_build_args+=(--no-test); @@ -36,10 +36,10 @@ build_cunumeric_conda_package() { conda_build_args+=(--variants "{gpu_enabled:${GPU_ENABLED},python:${python_version}}"); - rm -rf /tmp/conda-build/cunumeric; - mkdir -p /tmp/conda-build/cunumeric; + rm -rf /tmp/conda-build/cupynumeric; + mkdir -p /tmp/conda-build/cupynumeric; - # Synthesize new cunumeric conda-build build.sh script + # Synthesize new cupynumeric conda-build build.sh script cat < "${REPO_DIR}/conda/conda-build/conda_build_config.yaml" gpu_enabled: @@ -68,17 +68,17 @@ package_version: EOF cat <<"EOF" > "${REPO_DIR}/conda/conda-build/build.sh" -# Install cunumeric C++ libs -tar -C "$PREFIX" --exclude="*.a" --strip-components=1 -xvf /tmp/out/cunumeric-*-Linux.tar.gz; +# Install cupynumeric C++ libs +tar -C "$PREFIX" --exclude="*.a" --strip-components=1 -xvf /tmp/out/cupynumeric-*-Linux.tar.gz; -# Install cunumeric Python wheel -pip install --no-deps --root / --prefix "$PREFIX" /tmp/out/cunumeric-*.whl; +# Install cupynumeric Python wheel +pip install --no-deps --root / --prefix "$PREFIX" /tmp/out/cupynumeric-*.whl; EOF git -C "${REPO_DIR}" add .; git -C "${REPO_DIR}" commit --allow-empty --allow-empty-message -n -m ""; - # Build cuNumeric conda package + # Build cuPyNumeric conda package set +ux eval "$(conda shell.bash hook)" conda deactivate @@ -92,9 +92,9 @@ EOF git -C "${REPO_DIR}" reset --hard HEAD~1; - cp /tmp/conda-build/cunumeric/linux-64/cunumeric-*.tar.bz2 /tmp/out/; + cp /tmp/conda-build/cupynumeric/linux-64/cupynumeric-*.tar.bz2 /tmp/out/; { set +x; } 2>/dev/null; } -(build_cunumeric_conda_package "$@"); +(build_cupynumeric_conda_package "$@"); diff --git a/continuous_integration/scripts/build-cunumeric-cpp b/continuous_integration/scripts/build-cupynumeric-cpp similarity index 88% rename from continuous_integration/scripts/build-cunumeric-cpp rename to continuous_integration/scripts/build-cupynumeric-cpp index e608ec385d..1133f05352 100755 --- a/continuous_integration/scripts/build-cunumeric-cpp +++ b/continuous_integration/scripts/build-cupynumeric-cpp @@ -1,9 +1,9 @@ #!/usr/bin/env bash -build_cunumeric_cpp() { +build_cupynumeric_cpp() { set -xeuo pipefail; - # Build + package cuNumeric C++ libs + # Build + package cuPyNumeric C++ libs local cmake_args=(${CMAKE_ARGS:-}); cmake_args+=(-DBUILD_SHARED_LIBS=ON); cmake_args+=(-DBUILD_MARCH=${BUILD_MARCH}); @@ -30,4 +30,4 @@ build_cunumeric_cpp() { { set +x; } 2>/dev/null; } -(build_cunumeric_cpp "$@"); +(build_cupynumeric_cpp "$@"); diff --git a/continuous_integration/scripts/build-cunumeric-wheel b/continuous_integration/scripts/build-cupynumeric-wheel similarity index 68% rename from continuous_integration/scripts/build-cunumeric-wheel rename to continuous_integration/scripts/build-cupynumeric-wheel index 93ae353118..93a0259006 100755 --- a/continuous_integration/scripts/build-cunumeric-wheel +++ b/continuous_integration/scripts/build-cupynumeric-wheel @@ -1,6 +1,6 @@ #!/usr/bin/env bash -build_cunumeric_wheel() { +build_cupynumeric_wheel() { set -xeuo pipefail; mkdir -p /tmp/out; @@ -14,19 +14,19 @@ build_cunumeric_wheel() { fi local cmake_args=(${CMAKE_ARGS:-}); - cmake_args+=("-DFIND_CUNUMERIC_CPP=ON"); + cmake_args+=("-DFIND_CUPYNUMERIC_CPP=ON"); pwd echo $REPO_DIR ls -lahR $REPO_DIR - cmake_args+=("-Dcunumeric_ROOT=$REPO_DIR/build"); + cmake_args+=("-Dcupynumeric_ROOT=$REPO_DIR/build"); - # Build + package cuNumeric Python wheel + # Build + package cuPyNumeric Python wheel CMAKE_ARGS="${cmake_args[@]}" \ pip wheel ${pip_args[@]} "${REPO_DIR}"; { set +x; } 2>/dev/null; } -(build_cunumeric_wheel "$@"); +(build_cupynumeric_wheel "$@"); diff --git a/continuous_integration/scripts/test b/continuous_integration/scripts/test index 3bd5192128..72a5f67ec3 100755 --- a/continuous_integration/scripts/test +++ b/continuous_integration/scripts/test @@ -11,7 +11,7 @@ setup_env() { apt-get update apt-get install -y numactl make - mamba create -yn legate -c "${ARTIFACTS_DIR}/conda-build/legate" -c "${ARTIFACTS_DIR}/conda-build/cunumeric" -c conda-forge legate cunumeric + mamba create -yn legate -c "${ARTIFACTS_DIR}/conda-build/legate" -c "${ARTIFACTS_DIR}/conda-build/cupynumeric" -c conda-forge legate cupynumeric } setup_test_env() { @@ -33,7 +33,7 @@ setup_unit_env() { mamba install -y pytest pytest-mock mock } -test_cunumeric() { +test_cupynumeric() { set -xeo pipefail . conda-utils; @@ -57,13 +57,13 @@ test_cunumeric() { echo "Installing and executing mypy..." shift; setup_mypy_env; - mypy cunumeric + mypy cupynumeric ;; "docs") echo "Building docs..." shift; setup_docs_env; - cd docs/cunumeric + cd docs/cupynumeric make clean html ;; "unit") @@ -79,4 +79,4 @@ test_cunumeric() { esac } -(test_cunumeric "$@"); +(test_cupynumeric "$@"); diff --git a/cunumeric/config.py b/cunumeric/config.py deleted file mode 100644 index 288c554a94..0000000000 --- a/cunumeric/config.py +++ /dev/null @@ -1,835 +0,0 @@ -# Copyright 2024 NVIDIA Corporation -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# -from __future__ import annotations - -import os -import platform -from abc import abstractmethod -from ctypes import CDLL, RTLD_GLOBAL -from enum import IntEnum, unique -from typing import TYPE_CHECKING, Any, cast - -import cffi # type: ignore -import numpy as np - -if TYPE_CHECKING: - import numpy.typing as npt - - -class _ReductionOpIds: - argmax_redop_id: int - argmin_redop_id: int - - -class _CunumericSharedLib: - CUNUMERIC_ADVANCED_INDEXING: int - CUNUMERIC_ARANGE: int - CUNUMERIC_ARGWHERE: int - CUNUMERIC_BATCHED_CHOLESKY: int - CUNUMERIC_BINARY_OP: int - CUNUMERIC_BINARY_RED: int - CUNUMERIC_BINCOUNT: int - CUNUMERIC_BINOP_ADD: int - CUNUMERIC_BINOP_ARCTAN2: int - CUNUMERIC_BINOP_BITWISE_AND: int - CUNUMERIC_BINOP_BITWISE_OR: int - CUNUMERIC_BINOP_BITWISE_XOR: int - CUNUMERIC_BINOP_COPYSIGN: int - CUNUMERIC_BINOP_DIVIDE: int - CUNUMERIC_BINOP_EQUAL: int - CUNUMERIC_BINOP_FLOAT_POWER: int - CUNUMERIC_BINOP_FLOOR_DIVIDE: int - CUNUMERIC_BINOP_FMOD: int - CUNUMERIC_BINOP_GCD: int - CUNUMERIC_BINOP_GREATER: int - CUNUMERIC_BINOP_GREATER_EQUAL: int - CUNUMERIC_BINOP_HYPOT: int - CUNUMERIC_BINOP_ISCLOSE: int - CUNUMERIC_BINOP_LCM: int - CUNUMERIC_BINOP_LDEXP: int - CUNUMERIC_BINOP_LEFT_SHIFT: int - CUNUMERIC_BINOP_LESS: int - CUNUMERIC_BINOP_LESS_EQUAL: int - CUNUMERIC_BINOP_LOGADDEXP2: int - CUNUMERIC_BINOP_LOGADDEXP: int - CUNUMERIC_BINOP_LOGICAL_AND: int - CUNUMERIC_BINOP_LOGICAL_OR: int - CUNUMERIC_BINOP_LOGICAL_XOR: int - CUNUMERIC_BINOP_MAXIMUM: int - CUNUMERIC_BINOP_MINIMUM: int - CUNUMERIC_BINOP_MOD: int - CUNUMERIC_BINOP_MULTIPLY: int - CUNUMERIC_BINOP_NEXTAFTER: int - CUNUMERIC_BINOP_NOT_EQUAL: int - CUNUMERIC_BINOP_POWER: int - CUNUMERIC_BINOP_RIGHT_SHIFT: int - CUNUMERIC_BINOP_SUBTRACT: int - CUNUMERIC_BITGENERATOR: int - CUNUMERIC_BITGENOP_DISTRIBUTION: int - CUNUMERIC_BITGENTYPE_DEFAULT: int - CUNUMERIC_BITGENTYPE_XORWOW: int - CUNUMERIC_BITGENTYPE_MRG32K3A: int - CUNUMERIC_BITGENTYPE_MTGP32: int - CUNUMERIC_BITGENTYPE_MT19937: int - CUNUMERIC_BITGENTYPE_PHILOX4_32_10: int - CUNUMERIC_BITGENDIST_INTEGERS_16: int - CUNUMERIC_BITGENDIST_INTEGERS_32: int - CUNUMERIC_BITGENDIST_INTEGERS_64: int - CUNUMERIC_BITGENDIST_UNIFORM_32: int - CUNUMERIC_BITGENDIST_UNIFORM_64: int - CUNUMERIC_BITGENDIST_LOGNORMAL_32: int - CUNUMERIC_BITGENDIST_LOGNORMAL_64: int - CUNUMERIC_BITGENDIST_NORMAL_32: int - CUNUMERIC_BITGENDIST_NORMAL_64: int - CUNUMERIC_BITGENDIST_POISSON: int - CUNUMERIC_BITGENDIST_EXPONENTIAL_32: int - CUNUMERIC_BITGENDIST_EXPONENTIAL_64: int - CUNUMERIC_BITGENDIST_GUMBEL_32: int - CUNUMERIC_BITGENDIST_GUMBEL_64: int - CUNUMERIC_BITGENDIST_LAPLACE_32: int - CUNUMERIC_BITGENDIST_LAPLACE_64: int - CUNUMERIC_BITGENDIST_LOGISTIC_32: int - CUNUMERIC_BITGENDIST_LOGISTIC_64: int - CUNUMERIC_BITGENDIST_PARETO_32: int - CUNUMERIC_BITGENDIST_PARETO_64: int - CUNUMERIC_BITGENDIST_POWER_32: int - CUNUMERIC_BITGENDIST_POWER_64: int - CUNUMERIC_BITGENDIST_RAYLEIGH_32: int - CUNUMERIC_BITGENDIST_RAYLEIGH_64: int - CUNUMERIC_BITGENDIST_CAUCHY_32: int - CUNUMERIC_BITGENDIST_CAUCHY_64: int - CUNUMERIC_BITGENDIST_TRIANGULAR_32: int - CUNUMERIC_BITGENDIST_TRIANGULAR_64: int - CUNUMERIC_BITGENDIST_WEIBULL_32: int - CUNUMERIC_BITGENDIST_WEIBULL_64: int - CUNUMERIC_BITGENDIST_BYTES: int - CUNUMERIC_BITGENDIST_BETA_32: int - CUNUMERIC_BITGENDIST_BETA_64: int - CUNUMERIC_BITGENDIST_F_32: int - CUNUMERIC_BITGENDIST_F_64: int - CUNUMERIC_BITGENDIST_LOGSERIES: int - CUNUMERIC_BITGENDIST_NONCENTRAL_F_32: int - CUNUMERIC_BITGENDIST_NONCENTRAL_F_64: int - CUNUMERIC_BITGENDIST_CHISQUARE_32: int - CUNUMERIC_BITGENDIST_CHISQUARE_64: int - CUNUMERIC_BITGENDIST_GAMMA_32: int - CUNUMERIC_BITGENDIST_GAMMA_64: int - CUNUMERIC_BITGENDIST_STANDARD_T_32: int - CUNUMERIC_BITGENDIST_STANDARD_T_64: int - CUNUMERIC_BITGENDIST_HYPERGEOMETRIC: int - CUNUMERIC_BITGENDIST_VONMISES_32: int - CUNUMERIC_BITGENDIST_VONMISES_64: int - CUNUMERIC_BITGENDIST_ZIPF: int - CUNUMERIC_BITGENDIST_GEOMETRIC: int - CUNUMERIC_BITGENDIST_WALD_32: int - CUNUMERIC_BITGENDIST_WALD_64: int - CUNUMERIC_BITGENDIST_BINOMIAL: int - CUNUMERIC_BITGENDIST_NEGATIVE_BINOMIAL: int - CUNUMERIC_BITGENOP_CREATE: int - CUNUMERIC_BITGENOP_DESTROY: int - CUNUMERIC_BITGENOP_RAND_RAW: int - CUNUMERIC_BITORDER_BIG: int - CUNUMERIC_BITORDER_LITTLE: int - CUNUMERIC_CHOOSE: int - CUNUMERIC_CONTRACT: int - CUNUMERIC_CONVERT: int - CUNUMERIC_CONVERT_NAN_NOOP: int - CUNUMERIC_CONVERT_NAN_PROD: int - CUNUMERIC_CONVERT_NAN_SUM: int - CUNUMERIC_CONVOLVE: int - CUNUMERIC_CONVOLVE_AUTO: int - CUNUMERIC_CONVOLVE_DIRECT: int - CUNUMERIC_CONVOLVE_FFT: int - CUNUMERIC_DIAG: int - CUNUMERIC_DOT: int - CUNUMERIC_EYE: int - CUNUMERIC_FFT: int - CUNUMERIC_FFT_C2C: int - CUNUMERIC_FFT_C2R: int - CUNUMERIC_FFT_D2Z: int - CUNUMERIC_FFT_FORWARD: int - CUNUMERIC_FFT_INVERSE: int - CUNUMERIC_FFT_R2C: int - CUNUMERIC_FFT_Z2D: int - CUNUMERIC_FFT_Z2Z: int - CUNUMERIC_FILL: int - CUNUMERIC_FLIP: int - CUNUMERIC_GEMM: int - CUNUMERIC_HISTOGRAM: int - CUNUMERIC_LOAD_CUDALIBS: int - CUNUMERIC_MATMUL: int - CUNUMERIC_MATVECMUL: int - CUNUMERIC_MAX_MAPPERS: int - CUNUMERIC_MAX_REDOPS: int - CUNUMERIC_MAX_TASKS: int - CUNUMERIC_MP_POTRF: int - CUNUMERIC_MP_SOLVE: int - CUNUMERIC_NONZERO: int - CUNUMERIC_PACKBITS: int - CUNUMERIC_POTRF: int - CUNUMERIC_PUTMASK: int - CUNUMERIC_QR: int - CUNUMERIC_RAND: int - CUNUMERIC_READ: int - CUNUMERIC_RED_ALL: int - CUNUMERIC_RED_ANY: int - CUNUMERIC_RED_ARGMAX: int - CUNUMERIC_RED_ARGMIN: int - CUNUMERIC_RED_CONTAINS: int - CUNUMERIC_RED_COUNT_NONZERO: int - CUNUMERIC_RED_MAX: int - CUNUMERIC_RED_MIN: int - CUNUMERIC_RED_NANARGMAX: int - CUNUMERIC_RED_NANARGMIN: int - CUNUMERIC_RED_NANMAX: int - CUNUMERIC_RED_NANMIN: int - CUNUMERIC_RED_NANPROD: int - CUNUMERIC_RED_NANSUM: int - CUNUMERIC_RED_PROD: int - CUNUMERIC_RED_SUM: int - CUNUMERIC_RED_SUM_SQUARES: int - CUNUMERIC_RED_VARIANCE: int - CUNUMERIC_REPEAT: int - CUNUMERIC_SCALAR_UNARY_RED: int - CUNUMERIC_SCAN_GLOBAL: int - CUNUMERIC_SCAN_LOCAL: int - CUNUMERIC_SCAN_PROD: int - CUNUMERIC_SCAN_SUM: int - CUNUMERIC_SEARCHSORTED: int - CUNUMERIC_SELECT: int - CUNUMERIC_SOLVE: int - CUNUMERIC_SORT: int - CUNUMERIC_SVD: int - CUNUMERIC_SYRK: int - CUNUMERIC_TILE: int - CUNUMERIC_TRANSPOSE_COPY_2D: int - CUNUMERIC_TRILU: int - CUNUMERIC_TRSM: int - CUNUMERIC_UNARY_OP: int - CUNUMERIC_UNARY_RED: int - CUNUMERIC_UNIQUE: int - CUNUMERIC_UNIQUE_REDUCE: int - CUNUMERIC_UNLOAD_CUDALIBS: int - CUNUMERIC_UNPACKBITS: int - CUNUMERIC_UOP_ABSOLUTE: int - CUNUMERIC_UOP_ANGLE: int - CUNUMERIC_UOP_ARCCOS: int - CUNUMERIC_UOP_ARCCOSH: int - CUNUMERIC_UOP_ARCSIN: int - CUNUMERIC_UOP_ARCSINH: int - CUNUMERIC_UOP_ARCTAN: int - CUNUMERIC_UOP_ARCTANH: int - CUNUMERIC_UOP_CBRT: int - CUNUMERIC_UOP_CEIL: int - CUNUMERIC_UOP_CLIP: int - CUNUMERIC_UOP_CONJ: int - CUNUMERIC_UOP_COPY: int - CUNUMERIC_UOP_COS: int - CUNUMERIC_UOP_COSH: int - CUNUMERIC_UOP_DEG2RAD: int - CUNUMERIC_UOP_EXP2: int - CUNUMERIC_UOP_EXP: int - CUNUMERIC_UOP_EXPM1: int - CUNUMERIC_UOP_FLOOR: int - CUNUMERIC_UOP_FREXP: int - CUNUMERIC_UOP_GETARG: int - CUNUMERIC_UOP_IMAG: int - CUNUMERIC_UOP_INVERT: int - CUNUMERIC_UOP_ISFINITE: int - CUNUMERIC_UOP_ISINF: int - CUNUMERIC_UOP_ISNAN: int - CUNUMERIC_UOP_LOG10: int - CUNUMERIC_UOP_LOG1P: int - CUNUMERIC_UOP_LOG2: int - CUNUMERIC_UOP_LOG: int - CUNUMERIC_UOP_LOGICAL_NOT: int - CUNUMERIC_UOP_MODF: int - CUNUMERIC_UOP_NEGATIVE: int - CUNUMERIC_UOP_POSITIVE: int - CUNUMERIC_UOP_RAD2DEG: int - CUNUMERIC_UOP_REAL: int - CUNUMERIC_UOP_RECIPROCAL: int - CUNUMERIC_UOP_RINT: int - CUNUMERIC_UOP_ROUND: int - CUNUMERIC_UOP_SIGN: int - CUNUMERIC_UOP_SIGNBIT: int - CUNUMERIC_UOP_SIN: int - CUNUMERIC_UOP_SINH: int - CUNUMERIC_UOP_SQRT: int - CUNUMERIC_UOP_SQUARE: int - CUNUMERIC_UOP_TAN: int - CUNUMERIC_UOP_TANH: int - CUNUMERIC_UOP_TRUNC: int - CUNUMERIC_WHERE: int - CUNUMERIC_WINDOW: int - CUNUMERIC_WINDOW_BARLETT: int - CUNUMERIC_WINDOW_BLACKMAN: int - CUNUMERIC_WINDOW_HAMMING: int - CUNUMERIC_WINDOW_HANNING: int - CUNUMERIC_WINDOW_KAISER: int - CUNUMERIC_WRAP: int - CUNUMERIC_WRITE: int - CUNUMERIC_ZIP: int - - @abstractmethod - def cunumeric_has_cusolvermp(self) -> bool: - ... - - @abstractmethod - def cunumeric_max_eager_volume(self) -> int: - ... - - @abstractmethod - def cunumeric_register_reduction_ops(self, code: int) -> _ReductionOpIds: - ... - - -def dlopen_no_autoclose(ffi: Any, lib_path: str) -> Any: - # Use an already-opened library handle, which cffi will convert to a - # regular FFI object (using the definitions previously added using - # ffi.cdef), but will not automatically dlclose() on collection. - lib = CDLL(lib_path, mode=RTLD_GLOBAL) - return ffi.dlopen(ffi.cast("void *", lib._handle)) - - -# Load the cuNumeric library first so we have a shard object that -# we can use to initialize all these configuration enumerations -class CuNumericLib: - def __init__(self, name: str) -> None: - self.name = name - - shared_lib_path = self.get_shared_library() - assert shared_lib_path is not None - header = self.get_c_header() - ffi = cffi.FFI() - if header is not None: - ffi.cdef(header) - # Don't use ffi.dlopen(), because that will call dlclose() - # automatically when the object gets collected, thus removing - # symbols that may be needed when destroying C++ objects later - # (e.g. vtable entries, which will be queried for virtual - # destructors), causing errors at shutdown. - shared_lib = dlopen_no_autoclose(ffi, shared_lib_path) - self.shared_object = cast(_CunumericSharedLib, shared_lib) - - def register(self) -> None: - from legate.core import get_legate_runtime - - # We need to make sure that the runtime is started - get_legate_runtime() - - callback = getattr( - self.shared_object, "cunumeric_perform_registration" - ) - callback() - - def get_shared_library(self) -> str: - from .install_info import libpath - - return os.path.join( - libpath, "libcunumeric" + self.get_library_extension() - ) - - def get_c_header(self) -> str: - from .install_info import header - - return header - - @staticmethod - def get_library_extension() -> str: - os_name = platform.system() - if os_name == "Linux": - return ".so" - elif os_name == "Darwin": - return ".dylib" - raise RuntimeError(f"unknown platform {os_name!r}") - - -CUNUMERIC_LIB_NAME = "cunumeric" -cunumeric_lib = CuNumericLib(CUNUMERIC_LIB_NAME) -cunumeric_lib.register() -_cunumeric = cunumeric_lib.shared_object - - -# Match these to CuNumericOpCode in cunumeric_c.h -@unique -class CuNumericOpCode(IntEnum): - ADVANCED_INDEXING = _cunumeric.CUNUMERIC_ADVANCED_INDEXING - ARANGE = _cunumeric.CUNUMERIC_ARANGE - ARGWHERE = _cunumeric.CUNUMERIC_ARGWHERE - BATCHED_CHOLESKY = _cunumeric.CUNUMERIC_BATCHED_CHOLESKY - BINARY_OP = _cunumeric.CUNUMERIC_BINARY_OP - BINARY_RED = _cunumeric.CUNUMERIC_BINARY_RED - BINCOUNT = _cunumeric.CUNUMERIC_BINCOUNT - BITGENERATOR = _cunumeric.CUNUMERIC_BITGENERATOR - CHOOSE = _cunumeric.CUNUMERIC_CHOOSE - CONTRACT = _cunumeric.CUNUMERIC_CONTRACT - CONVERT = _cunumeric.CUNUMERIC_CONVERT - CONVOLVE = _cunumeric.CUNUMERIC_CONVOLVE - DIAG = _cunumeric.CUNUMERIC_DIAG - DOT = _cunumeric.CUNUMERIC_DOT - EYE = _cunumeric.CUNUMERIC_EYE - FFT = _cunumeric.CUNUMERIC_FFT - FILL = _cunumeric.CUNUMERIC_FILL - FLIP = _cunumeric.CUNUMERIC_FLIP - GEMM = _cunumeric.CUNUMERIC_GEMM - HISTOGRAM = _cunumeric.CUNUMERIC_HISTOGRAM - LOAD_CUDALIBS = _cunumeric.CUNUMERIC_LOAD_CUDALIBS - MATMUL = _cunumeric.CUNUMERIC_MATMUL - MATVECMUL = _cunumeric.CUNUMERIC_MATVECMUL - MP_POTRF = _cunumeric.CUNUMERIC_MP_POTRF - MP_SOLVE = _cunumeric.CUNUMERIC_MP_SOLVE - NONZERO = _cunumeric.CUNUMERIC_NONZERO - PACKBITS = _cunumeric.CUNUMERIC_PACKBITS - POTRF = _cunumeric.CUNUMERIC_POTRF - PUTMASK = _cunumeric.CUNUMERIC_PUTMASK - QR = _cunumeric.CUNUMERIC_QR - RAND = _cunumeric.CUNUMERIC_RAND - READ = _cunumeric.CUNUMERIC_READ - REPEAT = _cunumeric.CUNUMERIC_REPEAT - SCALAR_UNARY_RED = _cunumeric.CUNUMERIC_SCALAR_UNARY_RED - SCAN_GLOBAL = _cunumeric.CUNUMERIC_SCAN_GLOBAL - SCAN_LOCAL = _cunumeric.CUNUMERIC_SCAN_LOCAL - SEARCHSORTED = _cunumeric.CUNUMERIC_SEARCHSORTED - SELECT = _cunumeric.CUNUMERIC_SELECT - SOLVE = _cunumeric.CUNUMERIC_SOLVE - SORT = _cunumeric.CUNUMERIC_SORT - SVD = _cunumeric.CUNUMERIC_SVD - SYRK = _cunumeric.CUNUMERIC_SYRK - TILE = _cunumeric.CUNUMERIC_TILE - TRANSPOSE_COPY_2D = _cunumeric.CUNUMERIC_TRANSPOSE_COPY_2D - TRILU = _cunumeric.CUNUMERIC_TRILU - TRSM = _cunumeric.CUNUMERIC_TRSM - UNARY_OP = _cunumeric.CUNUMERIC_UNARY_OP - UNARY_RED = _cunumeric.CUNUMERIC_UNARY_RED - UNIQUE = _cunumeric.CUNUMERIC_UNIQUE - UNIQUE_REDUCE = _cunumeric.CUNUMERIC_UNIQUE_REDUCE - UNLOAD_CUDALIBS = _cunumeric.CUNUMERIC_UNLOAD_CUDALIBS - UNPACKBITS = _cunumeric.CUNUMERIC_UNPACKBITS - WHERE = _cunumeric.CUNUMERIC_WHERE - WINDOW = _cunumeric.CUNUMERIC_WINDOW - WRAP = _cunumeric.CUNUMERIC_WRAP - WRITE = _cunumeric.CUNUMERIC_WRITE - ZIP = _cunumeric.CUNUMERIC_ZIP - - -# Match these to CuNumericUnaryOpCode in cunumeric_c.h -@unique -class UnaryOpCode(IntEnum): - ABSOLUTE = _cunumeric.CUNUMERIC_UOP_ABSOLUTE - ANGLE = _cunumeric.CUNUMERIC_UOP_ANGLE - ARCCOS = _cunumeric.CUNUMERIC_UOP_ARCCOS - ARCCOSH = _cunumeric.CUNUMERIC_UOP_ARCCOSH - ARCSIN = _cunumeric.CUNUMERIC_UOP_ARCSIN - ARCSINH = _cunumeric.CUNUMERIC_UOP_ARCSINH - ARCTAN = _cunumeric.CUNUMERIC_UOP_ARCTAN - ARCTANH = _cunumeric.CUNUMERIC_UOP_ARCTANH - CBRT = _cunumeric.CUNUMERIC_UOP_CBRT - CEIL = _cunumeric.CUNUMERIC_UOP_CEIL - CLIP = _cunumeric.CUNUMERIC_UOP_CLIP - CONJ = _cunumeric.CUNUMERIC_UOP_CONJ - COPY = _cunumeric.CUNUMERIC_UOP_COPY - COS = _cunumeric.CUNUMERIC_UOP_COS - COSH = _cunumeric.CUNUMERIC_UOP_COSH - DEG2RAD = _cunumeric.CUNUMERIC_UOP_DEG2RAD - EXP = _cunumeric.CUNUMERIC_UOP_EXP - EXP2 = _cunumeric.CUNUMERIC_UOP_EXP2 - EXPM1 = _cunumeric.CUNUMERIC_UOP_EXPM1 - FLOOR = _cunumeric.CUNUMERIC_UOP_FLOOR - FREXP = _cunumeric.CUNUMERIC_UOP_FREXP - GETARG = _cunumeric.CUNUMERIC_UOP_GETARG - IMAG = _cunumeric.CUNUMERIC_UOP_IMAG - INVERT = _cunumeric.CUNUMERIC_UOP_INVERT - ISFINITE = _cunumeric.CUNUMERIC_UOP_ISFINITE - ISINF = _cunumeric.CUNUMERIC_UOP_ISINF - ISNAN = _cunumeric.CUNUMERIC_UOP_ISNAN - LOG = _cunumeric.CUNUMERIC_UOP_LOG - LOG10 = _cunumeric.CUNUMERIC_UOP_LOG10 - LOG1P = _cunumeric.CUNUMERIC_UOP_LOG1P - LOG2 = _cunumeric.CUNUMERIC_UOP_LOG2 - LOGICAL_NOT = _cunumeric.CUNUMERIC_UOP_LOGICAL_NOT - MODF = _cunumeric.CUNUMERIC_UOP_MODF - NEGATIVE = _cunumeric.CUNUMERIC_UOP_NEGATIVE - POSITIVE = _cunumeric.CUNUMERIC_UOP_POSITIVE - RAD2DEG = _cunumeric.CUNUMERIC_UOP_RAD2DEG - REAL = _cunumeric.CUNUMERIC_UOP_REAL - RECIPROCAL = _cunumeric.CUNUMERIC_UOP_RECIPROCAL - RINT = _cunumeric.CUNUMERIC_UOP_RINT - ROUND = _cunumeric.CUNUMERIC_UOP_ROUND - SIGN = _cunumeric.CUNUMERIC_UOP_SIGN - SIGNBIT = _cunumeric.CUNUMERIC_UOP_SIGNBIT - SIN = _cunumeric.CUNUMERIC_UOP_SIN - SINH = _cunumeric.CUNUMERIC_UOP_SINH - SQRT = _cunumeric.CUNUMERIC_UOP_SQRT - SQUARE = _cunumeric.CUNUMERIC_UOP_SQUARE - TAN = _cunumeric.CUNUMERIC_UOP_TAN - TANH = _cunumeric.CUNUMERIC_UOP_TANH - TRUNC = _cunumeric.CUNUMERIC_UOP_TRUNC - - -# Match these to CuNumericUnaryRedCode in cunumeric_c.h -@unique -class UnaryRedCode(IntEnum): - ALL = _cunumeric.CUNUMERIC_RED_ALL - ANY = _cunumeric.CUNUMERIC_RED_ANY - ARGMAX = _cunumeric.CUNUMERIC_RED_ARGMAX - ARGMIN = _cunumeric.CUNUMERIC_RED_ARGMIN - CONTAINS = _cunumeric.CUNUMERIC_RED_CONTAINS - COUNT_NONZERO = _cunumeric.CUNUMERIC_RED_COUNT_NONZERO - MAX = _cunumeric.CUNUMERIC_RED_MAX - MIN = _cunumeric.CUNUMERIC_RED_MIN - NANARGMAX = _cunumeric.CUNUMERIC_RED_NANARGMAX - NANARGMIN = _cunumeric.CUNUMERIC_RED_NANARGMIN - NANMAX = _cunumeric.CUNUMERIC_RED_NANMAX - NANMIN = _cunumeric.CUNUMERIC_RED_NANMIN - NANPROD = _cunumeric.CUNUMERIC_RED_NANPROD - NANSUM = _cunumeric.CUNUMERIC_RED_NANSUM - PROD = _cunumeric.CUNUMERIC_RED_PROD - SUM = _cunumeric.CUNUMERIC_RED_SUM - SUM_SQUARES = _cunumeric.CUNUMERIC_RED_SUM_SQUARES - VARIANCE = _cunumeric.CUNUMERIC_RED_VARIANCE - - -# Match these to CuNumericBinaryOpCode in cunumeric_c.h -@unique -class BinaryOpCode(IntEnum): - ADD = _cunumeric.CUNUMERIC_BINOP_ADD - ARCTAN2 = _cunumeric.CUNUMERIC_BINOP_ARCTAN2 - BITWISE_AND = _cunumeric.CUNUMERIC_BINOP_BITWISE_AND - BITWISE_OR = _cunumeric.CUNUMERIC_BINOP_BITWISE_OR - BITWISE_XOR = _cunumeric.CUNUMERIC_BINOP_BITWISE_XOR - COPYSIGN = _cunumeric.CUNUMERIC_BINOP_COPYSIGN - DIVIDE = _cunumeric.CUNUMERIC_BINOP_DIVIDE - EQUAL = _cunumeric.CUNUMERIC_BINOP_EQUAL - FLOAT_POWER = _cunumeric.CUNUMERIC_BINOP_FLOAT_POWER - FLOOR_DIVIDE = _cunumeric.CUNUMERIC_BINOP_FLOOR_DIVIDE - FMOD = _cunumeric.CUNUMERIC_BINOP_FMOD - GCD = _cunumeric.CUNUMERIC_BINOP_GCD - GREATER = _cunumeric.CUNUMERIC_BINOP_GREATER - GREATER_EQUAL = _cunumeric.CUNUMERIC_BINOP_GREATER_EQUAL - HYPOT = _cunumeric.CUNUMERIC_BINOP_HYPOT - ISCLOSE = _cunumeric.CUNUMERIC_BINOP_ISCLOSE - LCM = _cunumeric.CUNUMERIC_BINOP_LCM - LDEXP = _cunumeric.CUNUMERIC_BINOP_LDEXP - LEFT_SHIFT = _cunumeric.CUNUMERIC_BINOP_LEFT_SHIFT - LESS = _cunumeric.CUNUMERIC_BINOP_LESS - LESS_EQUAL = _cunumeric.CUNUMERIC_BINOP_LESS_EQUAL - LOGADDEXP = _cunumeric.CUNUMERIC_BINOP_LOGADDEXP - LOGADDEXP2 = _cunumeric.CUNUMERIC_BINOP_LOGADDEXP2 - LOGICAL_AND = _cunumeric.CUNUMERIC_BINOP_LOGICAL_AND - LOGICAL_OR = _cunumeric.CUNUMERIC_BINOP_LOGICAL_OR - LOGICAL_XOR = _cunumeric.CUNUMERIC_BINOP_LOGICAL_XOR - MAXIMUM = _cunumeric.CUNUMERIC_BINOP_MAXIMUM - MINIMUM = _cunumeric.CUNUMERIC_BINOP_MINIMUM - MOD = _cunumeric.CUNUMERIC_BINOP_MOD - MULTIPLY = _cunumeric.CUNUMERIC_BINOP_MULTIPLY - NEXTAFTER = _cunumeric.CUNUMERIC_BINOP_NEXTAFTER - NOT_EQUAL = _cunumeric.CUNUMERIC_BINOP_NOT_EQUAL - POWER = _cunumeric.CUNUMERIC_BINOP_POWER - RIGHT_SHIFT = _cunumeric.CUNUMERIC_BINOP_RIGHT_SHIFT - SUBTRACT = _cunumeric.CUNUMERIC_BINOP_SUBTRACT - - -@unique -class WindowOpCode(IntEnum): - BARLETT = _cunumeric.CUNUMERIC_WINDOW_BARLETT - BLACKMAN = _cunumeric.CUNUMERIC_WINDOW_BLACKMAN - HAMMING = _cunumeric.CUNUMERIC_WINDOW_HAMMING - HANNING = _cunumeric.CUNUMERIC_WINDOW_HANNING - KAISER = _cunumeric.CUNUMERIC_WINDOW_KAISER - - -# Match these to RandGenCode in rand_util.h -@unique -class RandGenCode(IntEnum): - UNIFORM = 1 - NORMAL = 2 - INTEGER = 3 - - -# Match these to CuNumericScanCode in cunumeric_c.h -@unique -class ScanCode(IntEnum): - PROD = _cunumeric.CUNUMERIC_SCAN_PROD - SUM = _cunumeric.CUNUMERIC_SCAN_SUM - - -# Match these to CuNumericConvertCode in cunumeric_c.h -@unique -class ConvertCode(IntEnum): - NOOP = _cunumeric.CUNUMERIC_CONVERT_NAN_NOOP - PROD = _cunumeric.CUNUMERIC_CONVERT_NAN_PROD - SUM = _cunumeric.CUNUMERIC_CONVERT_NAN_SUM - - -# Match these to BitGeneratorOperation in cunumeric_c.h -@unique -class BitGeneratorOperation(IntEnum): - CREATE = _cunumeric.CUNUMERIC_BITGENOP_CREATE - DESTROY = _cunumeric.CUNUMERIC_BITGENOP_DESTROY - RAND_RAW = _cunumeric.CUNUMERIC_BITGENOP_RAND_RAW - DISTRIBUTION = _cunumeric.CUNUMERIC_BITGENOP_DISTRIBUTION - - -# Match these to BitGeneratorType in cunumeric_c.h -@unique -class BitGeneratorType(IntEnum): - DEFAULT = _cunumeric.CUNUMERIC_BITGENTYPE_DEFAULT - XORWOW = _cunumeric.CUNUMERIC_BITGENTYPE_XORWOW - MRG32K3A = _cunumeric.CUNUMERIC_BITGENTYPE_MRG32K3A - MTGP32 = _cunumeric.CUNUMERIC_BITGENTYPE_MTGP32 - MT19937 = _cunumeric.CUNUMERIC_BITGENTYPE_MT19937 - PHILOX4_32_10 = _cunumeric.CUNUMERIC_BITGENTYPE_PHILOX4_32_10 - - -# Match these to BitGeneratorDistribution in cunumeric_c.h -@unique -class BitGeneratorDistribution(IntEnum): - INTEGERS_16 = _cunumeric.CUNUMERIC_BITGENDIST_INTEGERS_16 - INTEGERS_32 = _cunumeric.CUNUMERIC_BITGENDIST_INTEGERS_32 - INTEGERS_64 = _cunumeric.CUNUMERIC_BITGENDIST_INTEGERS_64 - UNIFORM_32 = _cunumeric.CUNUMERIC_BITGENDIST_UNIFORM_32 - UNIFORM_64 = _cunumeric.CUNUMERIC_BITGENDIST_UNIFORM_64 - LOGNORMAL_32 = _cunumeric.CUNUMERIC_BITGENDIST_LOGNORMAL_32 - LOGNORMAL_64 = _cunumeric.CUNUMERIC_BITGENDIST_LOGNORMAL_64 - NORMAL_32 = _cunumeric.CUNUMERIC_BITGENDIST_NORMAL_32 - NORMAL_64 = _cunumeric.CUNUMERIC_BITGENDIST_NORMAL_64 - POISSON = _cunumeric.CUNUMERIC_BITGENDIST_POISSON - EXPONENTIAL_32 = _cunumeric.CUNUMERIC_BITGENDIST_EXPONENTIAL_32 - EXPONENTIAL_64 = _cunumeric.CUNUMERIC_BITGENDIST_EXPONENTIAL_64 - GUMBEL_32 = _cunumeric.CUNUMERIC_BITGENDIST_GUMBEL_32 - GUMBEL_64 = _cunumeric.CUNUMERIC_BITGENDIST_GUMBEL_64 - LAPLACE_32 = _cunumeric.CUNUMERIC_BITGENDIST_LAPLACE_32 - LAPLACE_64 = _cunumeric.CUNUMERIC_BITGENDIST_LAPLACE_64 - LOGISTIC_32 = _cunumeric.CUNUMERIC_BITGENDIST_LOGISTIC_32 - LOGISTIC_64 = _cunumeric.CUNUMERIC_BITGENDIST_LOGISTIC_64 - PARETO_32 = _cunumeric.CUNUMERIC_BITGENDIST_PARETO_32 - PARETO_64 = _cunumeric.CUNUMERIC_BITGENDIST_PARETO_64 - POWER_32 = _cunumeric.CUNUMERIC_BITGENDIST_POWER_32 - POWER_64 = _cunumeric.CUNUMERIC_BITGENDIST_POWER_64 - RAYLEIGH_32 = _cunumeric.CUNUMERIC_BITGENDIST_RAYLEIGH_32 - RAYLEIGH_64 = _cunumeric.CUNUMERIC_BITGENDIST_RAYLEIGH_64 - CAUCHY_32 = _cunumeric.CUNUMERIC_BITGENDIST_CAUCHY_32 - CAUCHY_64 = _cunumeric.CUNUMERIC_BITGENDIST_CAUCHY_64 - TRIANGULAR_32 = _cunumeric.CUNUMERIC_BITGENDIST_TRIANGULAR_32 - TRIANGULAR_64 = _cunumeric.CUNUMERIC_BITGENDIST_TRIANGULAR_64 - WEIBULL_32 = _cunumeric.CUNUMERIC_BITGENDIST_WEIBULL_32 - WEIBULL_64 = _cunumeric.CUNUMERIC_BITGENDIST_WEIBULL_64 - BYTES = _cunumeric.CUNUMERIC_BITGENDIST_BYTES - BETA_32 = _cunumeric.CUNUMERIC_BITGENDIST_BETA_32 - BETA_64 = _cunumeric.CUNUMERIC_BITGENDIST_BETA_64 - F_32 = _cunumeric.CUNUMERIC_BITGENDIST_F_32 - F_64 = _cunumeric.CUNUMERIC_BITGENDIST_F_64 - LOGSERIES = _cunumeric.CUNUMERIC_BITGENDIST_LOGSERIES - NONCENTRAL_F_32 = _cunumeric.CUNUMERIC_BITGENDIST_NONCENTRAL_F_32 - NONCENTRAL_F_64 = _cunumeric.CUNUMERIC_BITGENDIST_NONCENTRAL_F_64 - CHISQUARE_32 = _cunumeric.CUNUMERIC_BITGENDIST_CHISQUARE_32 - CHISQUARE_64 = _cunumeric.CUNUMERIC_BITGENDIST_CHISQUARE_64 - GAMMA_32 = _cunumeric.CUNUMERIC_BITGENDIST_GAMMA_32 - GAMMA_64 = _cunumeric.CUNUMERIC_BITGENDIST_GAMMA_64 - STANDARD_T_32 = _cunumeric.CUNUMERIC_BITGENDIST_STANDARD_T_32 - STANDARD_T_64 = _cunumeric.CUNUMERIC_BITGENDIST_STANDARD_T_64 - HYPERGEOMETRIC = _cunumeric.CUNUMERIC_BITGENDIST_HYPERGEOMETRIC - VONMISES_32 = _cunumeric.CUNUMERIC_BITGENDIST_VONMISES_32 - VONMISES_64 = _cunumeric.CUNUMERIC_BITGENDIST_VONMISES_64 - ZIPF = _cunumeric.CUNUMERIC_BITGENDIST_ZIPF - GEOMETRIC = _cunumeric.CUNUMERIC_BITGENDIST_GEOMETRIC - WALD_32 = _cunumeric.CUNUMERIC_BITGENDIST_WALD_32 - WALD_64 = _cunumeric.CUNUMERIC_BITGENDIST_WALD_64 - BINOMIAL = _cunumeric.CUNUMERIC_BITGENDIST_BINOMIAL - NEGATIVE_BINOMIAL = _cunumeric.CUNUMERIC_BITGENDIST_NEGATIVE_BINOMIAL - - -# Match these to CuNumericConvolveMethod in cunumeric_c.h -@unique -class ConvolveMethod(IntEnum): - AUTO = _cunumeric.CUNUMERIC_CONVOLVE_AUTO - DIRECT = _cunumeric.CUNUMERIC_CONVOLVE_DIRECT - FFT = _cunumeric.CUNUMERIC_CONVOLVE_FFT - - -@unique -class TransferType(IntEnum): - DONATE = 0 - MAKE_COPY = 1 - SHARE = 2 - - -# Match these to fftType in fft_util.h -class FFTType: - def __init__( - self, - name: str, - type_id: int, - input_dtype: npt.DTypeLike, - output_dtype: npt.DTypeLike, - single_precision: bool, - complex_type: FFTType | None = None, - ) -> None: - self._name = name - self._type_id = type_id - self._complex_type = self if complex_type is None else complex_type - self._input_dtype = input_dtype - self._output_dtype = output_dtype - self._single_precision = single_precision - - def __str__(self) -> str: - return self._name - - def __repr__(self) -> str: - return str(self) - - @property - def type_id(self) -> int: - return self._type_id - - @property - def complex(self) -> FFTType: - return self._complex_type - - @property - def input_dtype(self) -> npt.DTypeLike: - return self._input_dtype - - @property - def output_dtype(self) -> npt.DTypeLike: - return self._output_dtype - - @property - def is_single_precision(self) -> bool: - return self._single_precision - - -FFT_C2C = FFTType( - "C2C", - _cunumeric.CUNUMERIC_FFT_C2C, - np.complex64, - np.complex64, - True, -) - -FFT_Z2Z = FFTType( - "Z2Z", - _cunumeric.CUNUMERIC_FFT_Z2Z, - np.complex128, - np.complex128, - False, -) - -FFT_R2C = FFTType( - "R2C", - _cunumeric.CUNUMERIC_FFT_R2C, - np.float32, - np.complex64, - True, - FFT_C2C, -) - -FFT_C2R = FFTType( - "C2R", - _cunumeric.CUNUMERIC_FFT_C2R, - np.complex64, - np.float32, - True, - FFT_C2C, -) - -FFT_D2Z = FFTType( - "D2Z", - _cunumeric.CUNUMERIC_FFT_D2Z, - np.float64, - np.complex128, - False, - FFT_Z2Z, -) - -FFT_Z2D = FFTType( - "Z2D", - _cunumeric.CUNUMERIC_FFT_Z2D, - np.complex128, - np.float64, - False, - FFT_Z2Z, -) - - -class FFTCode: - @staticmethod - def real_to_complex_code(dtype: npt.DTypeLike) -> FFTType: - if dtype == np.float64: - return FFT_D2Z - elif dtype == np.float32: - return FFT_R2C - else: - raise TypeError( - ( - "Data type for FFT not supported " - "(supported types are float32 and float64)" - ) - ) - - @staticmethod - def complex_to_real_code(dtype: npt.DTypeLike) -> FFTType: - if dtype == np.complex128: - return FFT_Z2D - elif dtype == np.complex64: - return FFT_C2R - else: - raise TypeError( - ( - "Data type for FFT not supported " - "(supported types are complex64 and complex128)" - ) - ) - - -@unique -class FFTDirection(IntEnum): - FORWARD = _cunumeric.CUNUMERIC_FFT_FORWARD - INVERSE = _cunumeric.CUNUMERIC_FFT_INVERSE - - -# Match these to CuNumericBitorder in cunumeric_c.h -@unique -class Bitorder(IntEnum): - BIG = _cunumeric.CUNUMERIC_BITORDER_BIG - LITTLE = _cunumeric.CUNUMERIC_BITORDER_LITTLE - - -@unique -class FFTNormalization(IntEnum): - FORWARD = 1 - INVERSE = 2 - ORTHOGONAL = 3 - - @staticmethod - def from_string(in_string: str) -> FFTNormalization | None: - if in_string == "forward": - return FFTNormalization.FORWARD - elif in_string == "ortho": - return FFTNormalization.ORTHOGONAL - elif in_string == "backward" or in_string is None: - return FFTNormalization.INVERSE - else: - return None - - @staticmethod - def reverse(in_string: str | None) -> str: - if in_string == "forward": - return "backward" - elif in_string == "backward" or in_string is None: - return "forward" - else: - return in_string diff --git a/cunumeric_cpp.cmake b/cunumeric_cpp.cmake deleted file mode 100644 index 5c324f1dc3..0000000000 --- a/cunumeric_cpp.cmake +++ /dev/null @@ -1,565 +0,0 @@ -#============================================================================= -# Copyright 2024 NVIDIA Corporation -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -#============================================================================= - -############################################################################## -# - User Options ------------------------------------------------------------ - -option(BUILD_SHARED_LIBS "Build cuNumeric shared libraries" ON) -option(cunumeric_EXCLUDE_TBLIS_FROM_ALL "Exclude tblis targets from cuNumeric's 'all' target" OFF) -option(cunumeric_EXCLUDE_OPENBLAS_FROM_ALL "Exclude OpenBLAS targets from cuNumeric's 'all' target" OFF) -option(cunumeric_EXCLUDE_LEGATE_FROM_ALL "Exclude legate targets from cuNumeric's 'all' target" OFF) - -############################################################################## -# - Project definition ------------------------------------------------------- - -# Write the version header -rapids_cmake_write_version_file(include/cunumeric/version_config.hpp) - -# Needed to integrate with LLVM/clang tooling -set(CMAKE_EXPORT_COMPILE_COMMANDS ON) - -############################################################################## -# - Build Type --------------------------------------------------------------- - -# Set a default build type if none was specified -rapids_cmake_build_type(Release) - -############################################################################## -# - conda environment -------------------------------------------------------- - -rapids_cmake_support_conda_env(conda_env MODIFY_PREFIX_PATH) - -# We're building python extension libraries, which must always be installed -# under lib/, even if the system normally uses lib64/. Rapids-cmake currently -# doesn't realize this when we're going through scikit-build, see -# https://github.com/rapidsai/rapids-cmake/issues/426 -if(TARGET conda_env) - set(CMAKE_INSTALL_LIBDIR "lib") -endif() - -############################################################################## -# - Dependencies ------------------------------------------------------------- - -# add third party dependencies using CPM -rapids_cpm_init(OVERRIDE ${CMAKE_CURRENT_SOURCE_DIR}/cmake/versions.json) - -rapids_find_package(OpenMP GLOBAL_TARGETS OpenMP::OpenMP_CXX) - -option(Legion_USE_CUDA "Use CUDA" ON) -option(Legion_USE_OpenMP "Use OpenMP" ${OpenMP_FOUND}) -option(Legion_BOUNDS_CHECKS "Build cuNumeric with bounds checks (expensive)" OFF) - -# If legate has CUDA support, then including it in a project will automatically call -# enable_language(CUDA). However, this does not play nice with the rapids-cmake CUDA utils -# which support a wider range of values for CMAKE_CUDA_ARCHITECTURES than cmake does. You -# end up with the following error: -# -# CMAKE_CUDA_ARCHITECTURES: -# -# RAPIDS -# -# is not one of the following: -# -# * a semicolon-separated list of integers, each optionally -# followed by '-real' or '-virtual' -# * a special value: all, all-major, native -# -set(cmake_cuda_arch_backup "${CMAKE_CUDA_ARCHITECTURES}") -set(cmake_cuda_arch_cache_backup "$CACHE{CMAKE_CUDA_ARCHITECTURES}") -if(("${CMAKE_CUDA_ARCHITECTURES}" STREQUAL "RAPIDS") OR ("${CMAKE_CUDA_ARCHITECTURES}" STREQUAL "NATIVE")) - unset(CMAKE_CUDA_ARCHITECTURES) - unset(CMAKE_CUDA_ARCHITECTURES CACHE) -endif() - -### -# If we find legate already configured on the system, it will report -# whether it was compiled with bounds checking (Legion_BOUNDS_CHECKS), -# CUDA (Legion_USE_CUDA), and OpenMP (Legion_USE_OpenMP). -# -# We use the same variables as legate because we want to enable/disable -# each of these features based on how legate was configured (it doesn't -# make sense to build cuNumeric's CUDA bindings if legate wasn't built -# with CUDA support). -### -include(cmake/thirdparty/get_legate.cmake) - -set(CMAKE_CUDA_ARCHITECTURES "${cmake_cuda_arch_cache_backup}" CACHE STRING "" FORCE) -set(CMAKE_CUDA_ARCHITECTURES "${cmake_cuda_arch_backup}") -unset(cmake_cuda_arch_backup) -unset(cmake_cuda_arch_cache_backup) - -if(Legion_USE_CUDA) - include(${CMAKE_CURRENT_SOURCE_DIR}/cmake/Modules/cuda_arch_helpers.cmake) - # Needs to run before `rapids_cuda_init_architectures` - set_cuda_arch_from_names() - # Needs to run before `enable_language(CUDA)` - rapids_cuda_init_architectures(cunumeric) - enable_language(CUDA) - # Since cunumeric only enables CUDA optionally we need to manually include - # the file that rapids_cuda_init_architectures relies on `project` calling - if(CMAKE_PROJECT_cunumeric_INCLUDE) - include("${CMAKE_PROJECT_cunumeric_INCLUDE}") - endif() - - # Must come after enable_language(CUDA) - # Use `-isystem ` instead of `-isystem=` - # because the former works with clangd intellisense - set(CMAKE_INCLUDE_SYSTEM_FLAG_CUDA "-isystem ") - - rapids_find_package( - CUDAToolkit REQUIRED - BUILD_EXPORT_SET cunumeric-exports - INSTALL_EXPORT_SET cunumeric-exports - ) - - include(cmake/thirdparty/get_nccl.cmake) - include(cmake/thirdparty/get_cutensor.cmake) -endif() - -include(cmake/thirdparty/get_openblas.cmake) - -include(cmake/thirdparty/get_tblis.cmake) - -############################################################################## -# - cuNumeric ---------------------------------------------------------------- - -set(cunumeric_SOURCES "") -set(cunumeric_CXX_DEFS "") -set(cunumeric_CUDA_DEFS "") -set(cunumeric_CXX_OPTIONS "") -set(cunumeric_CUDA_OPTIONS "") - -include(cmake/Modules/set_cpu_arch_flags.cmake) -set_cpu_arch_flags(cunumeric_CXX_OPTIONS) - -# Add `src/cunumeric.mk` sources -list(APPEND cunumeric_SOURCES - src/cunumeric/ternary/where.cc - src/cunumeric/scan/scan_global.cc - src/cunumeric/scan/scan_local.cc - src/cunumeric/binary/binary_op.cc - src/cunumeric/binary/binary_op_util.cc - src/cunumeric/binary/binary_red.cc - src/cunumeric/bits/packbits.cc - src/cunumeric/bits/unpackbits.cc - src/cunumeric/unary/scalar_unary_red.cc - src/cunumeric/unary/unary_op.cc - src/cunumeric/unary/unary_red.cc - src/cunumeric/unary/convert.cc - src/cunumeric/nullary/arange.cc - src/cunumeric/nullary/eye.cc - src/cunumeric/nullary/fill.cc - src/cunumeric/nullary/window.cc - src/cunumeric/index/advanced_indexing.cc - src/cunumeric/index/choose.cc - src/cunumeric/index/putmask.cc - src/cunumeric/index/repeat.cc - src/cunumeric/index/select.cc - src/cunumeric/index/wrap.cc - src/cunumeric/index/zip.cc - src/cunumeric/item/read.cc - src/cunumeric/item/write.cc - src/cunumeric/matrix/batched_cholesky.cc - src/cunumeric/matrix/contract.cc - src/cunumeric/matrix/diag.cc - src/cunumeric/matrix/gemm.cc - src/cunumeric/matrix/matmul.cc - src/cunumeric/matrix/matvecmul.cc - src/cunumeric/matrix/dot.cc - src/cunumeric/matrix/potrf.cc - src/cunumeric/matrix/qr.cc - src/cunumeric/matrix/solve.cc - src/cunumeric/matrix/svd.cc - src/cunumeric/matrix/syrk.cc - src/cunumeric/matrix/tile.cc - src/cunumeric/matrix/transpose.cc - src/cunumeric/matrix/trilu.cc - src/cunumeric/matrix/trsm.cc - src/cunumeric/matrix/util.cc - src/cunumeric/random/bitgenerator.cc - src/cunumeric/random/randutil/generator_host.cc - src/cunumeric/random/randutil/generator_host_straightforward.cc - src/cunumeric/random/randutil/generator_host_advanced.cc - src/cunumeric/random/rand.cc - src/cunumeric/search/argwhere.cc - src/cunumeric/search/nonzero.cc - src/cunumeric/set/unique.cc - src/cunumeric/set/unique_reduce.cc - src/cunumeric/stat/bincount.cc - src/cunumeric/convolution/convolve.cc - src/cunumeric/transform/flip.cc - src/cunumeric/utilities/repartition.cc - src/cunumeric/arg_redop_register.cc - src/cunumeric/mapper.cc - src/cunumeric/ndarray.cc - src/cunumeric/operators.cc - src/cunumeric/runtime.cc - src/cunumeric/cephes/chbevl.cc - src/cunumeric/cephes/i0.cc - src/cunumeric/stat/histogram.cc -) - -if(Legion_USE_OpenMP) - list(APPEND cunumeric_SOURCES - src/cunumeric/ternary/where_omp.cc - src/cunumeric/scan/scan_global_omp.cc - src/cunumeric/scan/scan_local_omp.cc - src/cunumeric/binary/binary_op_omp.cc - src/cunumeric/binary/binary_red_omp.cc - src/cunumeric/bits/packbits_omp.cc - src/cunumeric/bits/unpackbits_omp.cc - src/cunumeric/unary/unary_op_omp.cc - src/cunumeric/unary/scalar_unary_red_omp.cc - src/cunumeric/unary/unary_red_omp.cc - src/cunumeric/unary/convert_omp.cc - src/cunumeric/nullary/arange_omp.cc - src/cunumeric/nullary/eye_omp.cc - src/cunumeric/nullary/fill_omp.cc - src/cunumeric/nullary/window_omp.cc - src/cunumeric/index/advanced_indexing_omp.cc - src/cunumeric/index/choose_omp.cc - src/cunumeric/index/putmask_omp.cc - src/cunumeric/index/repeat_omp.cc - src/cunumeric/index/select_omp.cc - src/cunumeric/index/wrap_omp.cc - src/cunumeric/index/zip_omp.cc - src/cunumeric/matrix/batched_cholesky_omp.cc - src/cunumeric/matrix/contract_omp.cc - src/cunumeric/matrix/diag_omp.cc - src/cunumeric/matrix/gemm_omp.cc - src/cunumeric/matrix/matmul_omp.cc - src/cunumeric/matrix/matvecmul_omp.cc - src/cunumeric/matrix/dot_omp.cc - src/cunumeric/matrix/potrf_omp.cc - src/cunumeric/matrix/qr_omp.cc - src/cunumeric/matrix/solve_omp.cc - src/cunumeric/matrix/svd_omp.cc - src/cunumeric/matrix/syrk_omp.cc - src/cunumeric/matrix/tile_omp.cc - src/cunumeric/matrix/transpose_omp.cc - src/cunumeric/matrix/trilu_omp.cc - src/cunumeric/matrix/trsm_omp.cc - src/cunumeric/random/rand_omp.cc - src/cunumeric/search/argwhere_omp.cc - src/cunumeric/search/nonzero_omp.cc - src/cunumeric/set/unique_omp.cc - src/cunumeric/set/unique_reduce_omp.cc - src/cunumeric/stat/bincount_omp.cc - src/cunumeric/convolution/convolve_omp.cc - src/cunumeric/transform/flip_omp.cc - src/cunumeric/stat/histogram_omp.cc - ) -endif() - -if(Legion_USE_CUDA) - list(APPEND cunumeric_SOURCES - src/cunumeric/ternary/where.cu - src/cunumeric/scan/scan_global.cu - src/cunumeric/scan/scan_local.cu - src/cunumeric/binary/binary_op.cu - src/cunumeric/binary/binary_red.cu - src/cunumeric/bits/packbits.cu - src/cunumeric/bits/unpackbits.cu - src/cunumeric/unary/scalar_unary_red.cu - src/cunumeric/unary/unary_red.cu - src/cunumeric/unary/unary_op.cu - src/cunumeric/unary/convert.cu - src/cunumeric/nullary/arange.cu - src/cunumeric/nullary/eye.cu - src/cunumeric/nullary/fill.cu - src/cunumeric/nullary/window.cu - src/cunumeric/index/advanced_indexing.cu - src/cunumeric/index/choose.cu - src/cunumeric/index/putmask.cu - src/cunumeric/index/repeat.cu - src/cunumeric/index/select.cu - src/cunumeric/index/wrap.cu - src/cunumeric/index/zip.cu - src/cunumeric/item/read.cu - src/cunumeric/item/write.cu - src/cunumeric/matrix/batched_cholesky.cu - src/cunumeric/matrix/contract.cu - src/cunumeric/matrix/diag.cu - src/cunumeric/matrix/gemm.cu - src/cunumeric/matrix/matmul.cu - src/cunumeric/matrix/matvecmul.cu - src/cunumeric/matrix/dot.cu - src/cunumeric/matrix/potrf.cu - src/cunumeric/matrix/qr.cu - src/cunumeric/matrix/solve.cu - src/cunumeric/matrix/svd.cu - src/cunumeric/matrix/syrk.cu - src/cunumeric/matrix/tile.cu - src/cunumeric/matrix/transpose.cu - src/cunumeric/matrix/trilu.cu - src/cunumeric/matrix/trsm.cu - src/cunumeric/random/rand.cu - src/cunumeric/search/argwhere.cu - src/cunumeric/search/nonzero.cu - src/cunumeric/set/unique.cu - src/cunumeric/stat/bincount.cu - src/cunumeric/convolution/convolve.cu - src/cunumeric/fft/fft.cu - src/cunumeric/transform/flip.cu - src/cunumeric/utilities/repartition.cu - src/cunumeric/arg_redop_register.cu - src/cunumeric/cudalibs.cu - src/cunumeric/stat/histogram.cu - ) -endif() - -# Add `src/cunumeric/sort/sort.mk` sources -list(APPEND cunumeric_SOURCES - src/cunumeric/sort/sort.cc - src/cunumeric/sort/searchsorted.cc -) - -if(Legion_USE_OpenMP) - list(APPEND cunumeric_SOURCES - src/cunumeric/sort/sort_omp.cc - src/cunumeric/sort/searchsorted_omp.cc - ) -endif() - -if(Legion_USE_CUDA) - list(APPEND cunumeric_SOURCES - src/cunumeric/sort/sort.cu - src/cunumeric/sort/searchsorted.cu - src/cunumeric/sort/cub_sort_bool.cu - src/cunumeric/sort/cub_sort_int8.cu - src/cunumeric/sort/cub_sort_int16.cu - src/cunumeric/sort/cub_sort_int32.cu - src/cunumeric/sort/cub_sort_int64.cu - src/cunumeric/sort/cub_sort_uint8.cu - src/cunumeric/sort/cub_sort_uint16.cu - src/cunumeric/sort/cub_sort_uint32.cu - src/cunumeric/sort/cub_sort_uint64.cu - src/cunumeric/sort/cub_sort_half.cu - src/cunumeric/sort/cub_sort_float.cu - src/cunumeric/sort/cub_sort_double.cu - src/cunumeric/sort/thrust_sort_bool.cu - src/cunumeric/sort/thrust_sort_int8.cu - src/cunumeric/sort/thrust_sort_int16.cu - src/cunumeric/sort/thrust_sort_int32.cu - src/cunumeric/sort/thrust_sort_int64.cu - src/cunumeric/sort/thrust_sort_uint8.cu - src/cunumeric/sort/thrust_sort_uint16.cu - src/cunumeric/sort/thrust_sort_uint32.cu - src/cunumeric/sort/thrust_sort_uint64.cu - src/cunumeric/sort/thrust_sort_half.cu - src/cunumeric/sort/thrust_sort_float.cu - src/cunumeric/sort/thrust_sort_double.cu - src/cunumeric/sort/thrust_sort_complex64.cu - src/cunumeric/sort/thrust_sort_complex128.cu - ) -endif() - -# Add `src/cunumeric/random/random.mk` sources -if(Legion_USE_CUDA) - list(APPEND cunumeric_SOURCES - src/cunumeric/random/bitgenerator.cu - src/cunumeric/random/randutil/generator_device.cu - src/cunumeric/random/randutil/generator_device_straightforward.cu - src/cunumeric/random/randutil/generator_device_advanced.cu -) -endif() - -# add sources for cusolverMp -if(Legion_USE_CUDA AND CUSOLVERMP_DIR) - list(APPEND cunumeric_SOURCES - src/cunumeric/matrix/mp_potrf.cu - src/cunumeric/matrix/mp_solve.cu - ) -endif() - -list(APPEND cunumeric_SOURCES - # This must always be the last file! - # It guarantees we do our registration callback - # only after all task variants are recorded - src/cunumeric/cunumeric.cc -) - -if(CMAKE_BUILD_TYPE STREQUAL "Debug") - list(APPEND cunumeric_CXX_DEFS DEBUG_CUNUMERIC) - list(APPEND cunumeric_CUDA_DEFS DEBUG_CUNUMERIC) -endif() - -if(Legion_BOUNDS_CHECKS) - list(APPEND cunumeric_CXX_DEFS BOUNDS_CHECKS) - list(APPEND cunumeric_CUDA_DEFS BOUNDS_CHECKS) -endif() - -list(APPEND cunumeric_CUDA_OPTIONS -Xfatbin=-compress-all) -list(APPEND cunumeric_CUDA_OPTIONS --expt-extended-lambda) -list(APPEND cunumeric_CUDA_OPTIONS --expt-relaxed-constexpr) -list(APPEND cunumeric_CXX_OPTIONS -Wno-deprecated-declarations) -list(APPEND cunumeric_CUDA_OPTIONS -Wno-deprecated-declarations) - -add_library(cunumeric ${cunumeric_SOURCES}) -add_library(cunumeric::cunumeric ALIAS cunumeric) - -if (CMAKE_SYSTEM_NAME STREQUAL "Linux") - set(platform_rpath_origin "\$ORIGIN") -elseif (CMAKE_SYSTEM_NAME STREQUAL "Darwin") - set(platform_rpath_origin "@loader_path") -endif () - -set_target_properties(cunumeric - PROPERTIES BUILD_RPATH "${platform_rpath_origin}" - INSTALL_RPATH "${platform_rpath_origin}" - CXX_STANDARD 17 - CXX_STANDARD_REQUIRED ON - POSITION_INDEPENDENT_CODE ON - INTERFACE_POSITION_INDEPENDENT_CODE ON - CUDA_STANDARD 17 - CUDA_STANDARD_REQUIRED ON - LIBRARY_OUTPUT_DIRECTORY lib) - -target_link_libraries(cunumeric - PUBLIC legate::legate - $ - PRIVATE BLAS::BLAS - tblis::tblis - # Add Conda library and include paths - $ - $ - $ - $ - $ - $) - -if(NOT Legion_USE_CUDA AND cunumeric_cuRAND_INCLUDE_DIR) - list(APPEND cunumeric_CXX_DEFS CUNUMERIC_CURAND_FOR_CPU_BUILD) - target_include_directories(cunumeric PRIVATE ${cunumeric_cuRAND_INCLUDE_DIR}) -endif() - -if(Legion_USE_CUDA AND CUSOLVERMP_DIR) - message(VERBOSE "cunumeric: CUSOLVERMP_DIR ${CUSOLVERMP_DIR}") - list(APPEND cunumeric_CXX_DEFS CUNUMERIC_USE_CUSOLVERMP) - list(APPEND cunumeric_CUDA_DEFS CUNUMERIC_USE_CUSOLVERMP) - target_include_directories(cunumeric PRIVATE ${CUSOLVERMP_DIR}/include) - target_link_libraries(cunumeric PRIVATE ${CUSOLVERMP_DIR}/lib/libcusolverMp.so) -endif() - -target_compile_options(cunumeric - PRIVATE "$<$:${cunumeric_CXX_OPTIONS}>" - "$<$:${cunumeric_CUDA_OPTIONS}>") - -target_compile_definitions(cunumeric - PUBLIC "$<$:${cunumeric_CXX_DEFS}>" - "$<$:${cunumeric_CUDA_DEFS}>") - -target_include_directories(cunumeric - PUBLIC - $ - INTERFACE - $ -) - -if(Legion_USE_CUDA) - file(WRITE "${CMAKE_CURRENT_BINARY_DIR}/fatbin.ld" -[=[ -SECTIONS -{ -.nvFatBinSegment : { *(.nvFatBinSegment) } -.nv_fatbin : { *(.nv_fatbin) } -} -]=]) - - # ensure CUDA symbols aren't relocated to the middle of the debug build binaries - target_link_options(cunumeric PRIVATE "${CMAKE_CURRENT_BINARY_DIR}/fatbin.ld") -endif() - -############################################################################## -# - install targets----------------------------------------------------------- - -include(CPack) -include(GNUInstallDirs) -rapids_cmake_install_lib_dir(lib_dir) - -install(TARGETS cunumeric - DESTINATION ${lib_dir} - EXPORT cunumeric-exports) - -install( - FILES src/cunumeric.h - ${CMAKE_CURRENT_BINARY_DIR}/include/cunumeric/version_config.hpp - DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/cunumeric) - -install( - FILES src/cunumeric/cunumeric_c.h - src/cunumeric/ndarray.h - src/cunumeric/ndarray.inl - src/cunumeric/operators.h - src/cunumeric/operators.inl - src/cunumeric/runtime.h - src/cunumeric/slice.h - src/cunumeric/typedefs.h - DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/cunumeric/cunumeric) - -if(cunumeric_INSTALL_TBLIS) - install(DIRECTORY ${tblis_BINARY_DIR}/lib/ DESTINATION ${lib_dir}) - install(DIRECTORY ${tblis_BINARY_DIR}/include/ DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}) -endif() - -############################################################################## -# - install export ----------------------------------------------------------- - -set(doc_string - [=[ -Provide targets for cuNumeric, an aspiring drop-in replacement for NumPy at scale. - -Imported Targets: - - cunumeric::cunumeric - -]=]) - -string(JOIN "\n" code_string - "set(Legion_USE_CUDA ${Legion_USE_CUDA})" - "set(Legion_USE_OpenMP ${Legion_USE_OpenMP})" - "set(Legion_BOUNDS_CHECKS ${Legion_BOUNDS_CHECKS})" -) - -if(DEFINED Legion_USE_Python) - string(APPEND code_string "\nset(Legion_USE_Python ${Legion_USE_Python})") -endif() - -if(DEFINED Legion_NETWORKS) - string(APPEND code_string "\nset(Legion_NETWORKS ${Legion_NETWORKS})") -endif() - -rapids_export( - INSTALL cunumeric - EXPORT_SET cunumeric-exports - GLOBAL_TARGETS cunumeric - NAMESPACE cunumeric:: - DOCUMENTATION doc_string - FINAL_CODE_BLOCK code_string) - -# build export targets -rapids_export( - BUILD cunumeric - EXPORT_SET cunumeric-exports - GLOBAL_TARGETS cunumeric - NAMESPACE cunumeric:: - DOCUMENTATION doc_string - FINAL_CODE_BLOCK code_string) - -if(cunumeric_BUILD_TESTS) - include(CTest) - - add_subdirectory(tests/cpp) -endif() diff --git a/cunumeric/__init__.py b/cupynumeric/__init__.py similarity index 99% rename from cunumeric/__init__.py rename to cupynumeric/__init__.py index 4f0458cf2a..01037dfa36 100644 --- a/cunumeric/__init__.py +++ b/cupynumeric/__init__.py @@ -14,7 +14,7 @@ # """ -cuNumeric +cuPyNumeric ===== Provides a distributed task-parallel implementation of the Numpy interface diff --git a/cunumeric/_array/__init__.py b/cupynumeric/_array/__init__.py similarity index 100% rename from cunumeric/_array/__init__.py rename to cupynumeric/_array/__init__.py diff --git a/cunumeric/_array/array.py b/cupynumeric/_array/array.py similarity index 94% rename from cunumeric/_array/array.py rename to cupynumeric/_array/array.py index 5a6406057b..3252714425 100644 --- a/cunumeric/_array/array.py +++ b/cupynumeric/_array/array.py @@ -52,7 +52,7 @@ add_boilerplate, broadcast_where, check_writeable, - convert_to_cunumeric_ndarray, + convert_to_cupynumeric_ndarray, maybe_convert_to_np_ndarray, sanitize_shape, tuple_pop, @@ -108,7 +108,7 @@ def __init__( inputs: Any | None = None, writeable: bool = True, ) -> None: - # `inputs` being a cuNumeric ndarray is definitely a bug + # `inputs` being a cuPyNumeric ndarray is definitely a bug assert not isinstance(inputs, ndarray) if thunk is None: assert shape is not None @@ -161,7 +161,7 @@ def __legate_data_interface__(self) -> dict[str, Any]: array = LogicalArray.from_store(deferred_thunk.base) self._legate_data = dict() self._legate_data["version"] = 1 - field = Field("cuNumeric Array", dtype) + field = Field("cuPyNumeric Array", dtype) self._legate_data["data"] = {field: array} return self._legate_data @@ -186,7 +186,7 @@ def __legate_data_interface__(self) -> dict[str, Any]: def __array_function__( self, func: Any, types: Any, args: tuple[Any], kwargs: dict[str, Any] ) -> Any: - import cunumeric as cn + import cupynumeric as cn what = func.__name__ @@ -197,19 +197,19 @@ def __array_function__( return NotImplemented # We are wrapping all NumPy modules, so we can expect to find every - # NumPy API call in cuNumeric, even if just an "unimplemented" stub. + # NumPy API call in cuPyNumeric, even if just an "unimplemented" stub. module = reduce(getattr, func.__module__.split(".")[1:], cn) cn_func = getattr(module, func.__name__) - # We can't immediately forward to the corresponding cuNumeric + # We can't immediately forward to the corresponding cuPyNumeric # entrypoint. Say that we reached this point because the user code - # invoked `np.foo(x, bar=True)` where `x` is a `cunumeric.ndarray`. If - # our implementation of `foo` is not complete, and cannot handle + # invoked `np.foo(x, bar=True)` where `x` is a `cupynumeric.ndarray`. + # If our implementation of `foo` is not complete, and cannot handle # `bar=True`, then forwarding this call to `cn.foo` would fail. This # goes against the semantics of `__array_function__`, which shouldn't # fail if the custom implementation cannot handle the provided # arguments. Conversely, if the user calls `cn.foo(x, bar=True)` - # directly, that means they requested the cuNumeric implementation + # directly, that means they requested the cuPyNumeric implementation # specifically, and the `NotImplementedError` should not be hidden. if is_implemented(cn_func): try: @@ -285,7 +285,7 @@ def T(self) -> ndarray: See Also -------- - cunumeric.transpose + cupynumeric.transpose ndarray.transpose """ @@ -297,8 +297,8 @@ def base(self) -> npt.NDArray[Any] | None: Base object if memory is from some other object. """ raise NotImplementedError( - "cunumeric.ndarray doesn't keep track of the array view hierarchy " - "yet" + "cupynumeric.ndarray doesn't keep track of the array view " + "hierarchy yet" ) @property @@ -332,9 +332,9 @@ def flags(self) -> Any: """ Information about the memory layout of the array. - These flags don't reflect the properties of the cuNumeric array, but - rather the NumPy array that will be produced if the cuNumeric array is - materialized on a single node. + These flags don't reflect the properties of the cuPyNumeric array, but + rather the NumPy array that will be produced if the cuPyNumeric array + is materialized on a single node. Attributes ---------- @@ -734,7 +734,7 @@ def __divmod__(self, rhs: Any) -> ndarray: """ raise NotImplementedError( - "cunumeric.ndarray doesn't support __divmod__ yet" + "cupynumeric.ndarray doesn't support __divmod__ yet" ) def __eq__(self, rhs: object) -> ndarray: # type: ignore [override] @@ -787,7 +787,7 @@ def __ge__(self, rhs: Any) -> ndarray: # __getattribute__ def _convert_key(self, key: Any, first: bool = True) -> Any: - # Convert any arrays stored in a key to a cuNumeric array + # Convert any arrays stored in a key to a cuPyNumeric array if isinstance(key, slice): key = slice( operator.index(key.start) if key.start is not None else None, @@ -804,9 +804,9 @@ def _convert_key(self, key: Any, first: bool = True) -> Any: elif isinstance(key, tuple) and first: return tuple(self._convert_key(k, first=False) for k in key) else: - # Otherwise convert it to a cuNumeric array, check types + # Otherwise convert it to a cuPyNumeric array, check types # and get the thunk - key = convert_to_cunumeric_ndarray(key) + key = convert_to_cupynumeric_ndarray(key) if key.dtype != bool and not np.issubdtype(key.dtype, np.integer): raise TypeError("index arrays should be int or bool type") if key.dtype != bool: @@ -837,7 +837,7 @@ def __gt__(self, rhs: Any) -> ndarray: return _ufunc.greater(self, rhs) def __hash__(self) -> int: - raise TypeError("unhashable type: cunumeric.ndarray") + raise TypeError("unhashable type: cupynumeric.ndarray") def __iadd__(self, rhs: Any) -> ndarray: """a.__iadd__(value, /) @@ -1154,11 +1154,11 @@ def nonzero(self) -> tuple[ndarray, ...]: Return the indices of the elements that are non-zero. - Refer to :func:`cunumeric.nonzero` for full documentation. + Refer to :func:`cupynumeric.nonzero` for full documentation. See Also -------- - cunumeric.nonzero : equivalent function + cupynumeric.nonzero : equivalent function Availability -------- @@ -1254,7 +1254,7 @@ def __rdivmod__(self, lhs: Any) -> ndarray: """ raise NotImplementedError( - "cunumeric.ndarray doesn't support __rdivmod__ yet" + "cupynumeric.ndarray doesn't support __rdivmod__ yet" ) def __reduce__(self, *args: Any, **kwargs: Any) -> str | tuple[str, ...]: @@ -1505,11 +1505,11 @@ def all( Returns True if all elements evaluate to True. - Refer to :func:`cunumeric.all` for full documentation. + Refer to :func:`cupynumeric.all` for full documentation. See Also -------- - cunumeric.all : equivalent function + cupynumeric.all : equivalent function Availability -------- @@ -1540,11 +1540,11 @@ def any( Returns True if any of the elements of `a` evaluate to True. - Refer to :func:`cunumeric.any` for full documentation. + Refer to :func:`cupynumeric.any` for full documentation. See Also -------- - cunumeric.any : equivalent function + cupynumeric.any : equivalent function Availability -------- @@ -1573,11 +1573,11 @@ def argmax( Return indices of the maximum values along the given axis. - Refer to :func:`cunumeric.argmax` for full documentation. + Refer to :func:`cupynumeric.argmax` for full documentation. See Also -------- - cunumeric.argmax : equivalent function + cupynumeric.argmax : equivalent function Availability -------- @@ -1608,11 +1608,11 @@ def argmin( Return indices of the minimum values along the given axis. - Refer to :func:`cunumeric.argmin` for detailed documentation. + Refer to :func:`cupynumeric.argmin` for detailed documentation. See Also -------- - cunumeric.argmin : equivalent function + cupynumeric.argmin : equivalent function Availability -------- @@ -1741,11 +1741,11 @@ def take( Take elements from an array along an axis. - Refer to :func:`cunumeric.take` for full documentation. + Refer to :func:`cupynumeric.take` for full documentation. See Also -------- - cunumeric.take : equivalent function + cupynumeric.take : equivalent function Availability -------- @@ -1755,7 +1755,7 @@ def take( if not np.isscalar(indices): # if indices is a tuple or list, bring sub-tuples to the same shape # and concatenate them - indices = convert_to_cunumeric_ndarray(indices) + indices = convert_to_cupynumeric_ndarray(indices) if axis is None: self = self.ravel() @@ -1821,11 +1821,11 @@ def choose( Use an index array to construct a new array from a set of choices. - Refer to :func:`cunumeric.choose` for full documentation. + Refer to :func:`cupynumeric.choose` for full documentation. See Also -------- - cunumeric.choose : equivalent function + cupynumeric.choose : equivalent function Availability -------- @@ -1843,12 +1843,12 @@ def choose( dtypes = [ch.dtype for ch in choices] ch_dtype = np.result_type(*dtypes) choices = tuple( - convert_to_cunumeric_ndarray(choices[i]).astype(ch_dtype) + convert_to_cupynumeric_ndarray(choices[i]).astype(ch_dtype) for i in range(n) ) else: - choices = convert_to_cunumeric_ndarray(choices) + choices = convert_to_cupynumeric_ndarray(choices) n = choices.shape[0] ch_dtype = choices.dtype choices = tuple(choices[i, ...] for i in range(n)) @@ -1922,11 +1922,11 @@ def compress( Return selected slices of an array along given axis. - Refer to :func:`cunumeric.compress` for full documentation. + Refer to :func:`cupynumeric.compress` for full documentation. See Also -------- - cunumeric.compress : equivalent function + cupynumeric.compress : equivalent function Availability -------- @@ -1985,11 +1985,11 @@ def clip( One of max or min must be given. - Refer to :func:`cunumeric.clip` for full documentation. + Refer to :func:`cupynumeric.clip` for full documentation. See Also -------- - cunumeric.clip : equivalent function + cupynumeric.clip : equivalent function Availability -------- @@ -2005,7 +2005,7 @@ def clip( ) if args[0].size != 1 or args[1].size != 1: runtime.warn( - "cuNumeric has not implemented clip with array-like " + "cuPyNumeric has not implemented clip with array-like " "arguments and is falling back to canonical numpy. You " "may notice significantly decreased performance for this " "function call.", @@ -2015,7 +2015,7 @@ def clip( self.__array__().clip(args[0], args[1], out=out.__array__()) return out else: - return convert_to_cunumeric_ndarray( + return convert_to_cupynumeric_ndarray( self.__array__().clip(args[0], args[1]) ) core_dtype = to_core_type(self.dtype) @@ -2034,7 +2034,7 @@ def round( Return a with each element rounded to the given number of decimals. - Refer to :func:`cunumeric.round` for full documentation. + Refer to :func:`cupynumeric.round` for full documentation. Availability -------- @@ -2054,11 +2054,11 @@ def conj(self) -> ndarray: Complex-conjugate all elements. - Refer to :func:`cunumeric.conjugate` for full documentation. + Refer to :func:`cupynumeric.conjugate` for full documentation. See Also -------- - cunumeric.conjugate : equivalent function + cupynumeric.conjugate : equivalent function Availability -------- @@ -2076,11 +2076,11 @@ def conjugate(self) -> ndarray: Return the complex conjugate, element-wise. - Refer to :func:`cunumeric.conjugate` for full documentation. + Refer to :func:`cupynumeric.conjugate` for full documentation. See Also -------- - cunumeric.conjugate : equivalent function + cupynumeric.conjugate : equivalent function Availability -------- @@ -2099,7 +2099,7 @@ def copy(self, order: OrderType = "C") -> ndarray: Multiple GPUs, Multiple CPUs """ - # We don't care about dimension order in cuNumeric + # We don't care about dimension order in cuPyNumeric return self.__copy__() @add_boilerplate() @@ -2306,11 +2306,11 @@ def diagonal( Return specified diagonals. - Refer to :func:`cunumeric.diagonal` for full documentation. + Refer to :func:`cupynumeric.diagonal` for full documentation. See Also -------- - cunumeric.diagonal : equivalent function + cupynumeric.diagonal : equivalent function Availability -------- @@ -2332,11 +2332,11 @@ def put( """ Replaces specified elements of the array with given values. - Refer to :func:`cunumeric.put` for full documentation. + Refer to :func:`cupynumeric.put` for full documentation. See Also -------- - cunumeric.put : equivalent function + cupynumeric.put : equivalent function Availability -------- @@ -2395,11 +2395,11 @@ def trace( Return the sum along diagonals of the array. - Refer to :func:`cunumeric.trace` for full documentation. + Refer to :func:`cupynumeric.trace` for full documentation. See Also -------- - cunumeric.trace : equivalent function + cupynumeric.trace : equivalent function Availability -------- @@ -2436,11 +2436,11 @@ def dot(self, rhs: ndarray, out: ndarray | None = None) -> ndarray: Return the dot product of this array with ``rhs``. - Refer to :func:`cunumeric.dot` for full documentation. + Refer to :func:`cupynumeric.dot` for full documentation. See Also -------- - cunumeric.dot : equivalent function + cupynumeric.dot : equivalent function Availability -------- @@ -2469,7 +2469,7 @@ def dump(self, file: str | Path) -> None: Dump a pickle of the array to the specified file. - The array can be read back with pickle.load or cunumeric.load. + The array can be read back with pickle.load or cupynumeric.load. Parameters ---------- @@ -2538,7 +2538,7 @@ def fft( Return the ``kind`` ``direction`` FFT of this array with normalization ``norm``. - Common entrypoint for FFT functionality in cunumeric.fft module. + Common entrypoint for FFT functionality in cupynumeric.fft module. Notes ----- @@ -2546,7 +2546,7 @@ def fft( See Also -------- - cunumeric.fft : FFT functions for different ``kind`` and + cupynumeric.fft : FFT functions for different ``kind`` and ``direction`` arguments Availability @@ -2693,7 +2693,7 @@ def flatten(self, order: OrderType = "C") -> ndarray: def getfield(self, dtype: np.dtype[Any], offset: int = 0) -> None: raise NotImplementedError( - "cuNumeric does not currently support type reinterpretation " + "cuPyNumeric does not currently support type reinterpretation " "for ndarray.getfield" ) @@ -2815,11 +2815,11 @@ def max( Return the maximum along a given axis. - Refer to :func:`cunumeric.amax` for full documentation. + Refer to :func:`cupynumeric.amax` for full documentation. See Also -------- - cunumeric.amax : equivalent function + cupynumeric.amax : equivalent function Availability -------- @@ -2906,11 +2906,11 @@ def mean( Returns the average of the array elements along given axis. - Refer to :func:`cunumeric.mean` for full documentation. + Refer to :func:`cupynumeric.mean` for full documentation. See Also -------- - cunumeric.mean : equivalent function + cupynumeric.mean : equivalent function Availability -------- @@ -2919,7 +2919,7 @@ def mean( """ if axis is not None and not isinstance(axis, int): raise NotImplementedError( - "cunumeric.mean only supports int types for " + "cupynumeric.mean only supports int types for " "`axis` currently" ) @@ -2994,11 +2994,11 @@ def var( Returns the variance of the array elements along given axis. - Refer to :func:`cunumeric.var` for full documentation. + Refer to :func:`cupynumeric.var` for full documentation. See Also -------- - cunumeric.var : equivalent function + cupynumeric.var : equivalent function Availability -------- @@ -3007,7 +3007,7 @@ def var( """ if axis is not None and not isinstance(axis, int): raise NotImplementedError( - "cunumeric.var only supports int types for `axis` currently" + "cupynumeric.var only supports int types for `axis` currently" ) # this could be computed as a single pass through the array @@ -3017,7 +3017,7 @@ def var( # directly as <(x-mu)^2>, which then requires two passes through the # data to first compute the mean and then compute the variance # see https://en.wikipedia.org/wiki/Algorithms_for_calculating_variance - # TODO(https://github.com/nv-legate/cunumeric/issues/590) + # TODO(https://github.com/nv-legate/cupynumeric/issues/590) dtype = self._summation_dtype(dtype) # calculate the mean, but keep the dimensions so that the @@ -3044,7 +3044,7 @@ def var( args=(Scalar(mu.__array__(), to_core_type(self.dtype)),), ) else: - # TODO(https://github.com/nv-legate/cunumeric/issues/591) + # TODO(https://github.com/nv-legate/cupynumeric/issues/591) # there isn't really support for generic binary reductions # right now all of the current binary reductions are boolean # reductions like allclose. To implement this a single pass would @@ -3088,11 +3088,11 @@ def min( Return the minimum along a given axis. - Refer to :func:`cunumeric.amin` for full documentation. + Refer to :func:`cupynumeric.amin` for full documentation. See Also -------- - cunumeric.amin : equivalent function + cupynumeric.amin : equivalent function Availability -------- @@ -3121,11 +3121,11 @@ def partition( Partition of an array in-place. - Refer to :func:`cunumeric.partition` for full documentation. + Refer to :func:`cupynumeric.partition` for full documentation. See Also -------- - cunumeric.partition : equivalent function + cupynumeric.partition : equivalent function Availability -------- @@ -3149,11 +3149,11 @@ def argpartition( Returns the indices that would partition this array. - Refer to :func:`cunumeric.argpartition` for full documentation. + Refer to :func:`cupynumeric.argpartition` for full documentation. See Also -------- - cunumeric.argpartition : equivalent function + cupynumeric.argpartition : equivalent function Availability -------- @@ -3186,11 +3186,11 @@ def prod( Return the product of the array elements over the given axis - Refer to :func:`cunumeric.prod` for full documentation. + Refer to :func:`cupynumeric.prod` for full documentation. See Also -------- - cunumeric.prod : equivalent function + cupynumeric.prod : equivalent function Availability -------- @@ -3213,11 +3213,11 @@ def ravel(self, order: OrderType = "C") -> ndarray: Return a flattened array. - Refer to :func:`cunumeric.ravel` for full documentation. + Refer to :func:`cupynumeric.ravel` for full documentation. See Also -------- - cunumeric.ravel : equivalent function + cupynumeric.ravel : equivalent function ndarray.flat : a flat iterator on the array. Availability @@ -3232,11 +3232,11 @@ def reshape(self, *args: Any, order: OrderType = "C") -> ndarray: Returns an array containing the same data with a new shape. - Refer to :func:`cunumeric.reshape` for full documentation. + Refer to :func:`cupynumeric.reshape` for full documentation. See Also -------- - cunumeric.reshape : equivalent function + cupynumeric.reshape : equivalent function Availability @@ -3307,7 +3307,7 @@ def setfield( self, val: Any, dtype: npt.DTypeLike, offset: int = 0 ) -> None: raise NotImplementedError( - "cuNumeric does not currently support type reinterpretation " + "cuPyNumeric does not currently support type reinterpretation " "for ndarray.setfield" ) @@ -3415,7 +3415,7 @@ def searchsorted( raise ValueError("Dimension mismatch: self must be a 1D array") # this is needed in case v is a scalar - v_ndarray = convert_to_cunumeric_ndarray(v) + v_ndarray = convert_to_cupynumeric_ndarray(v) a = self # in case we have different dtypes we ned to find a common type @@ -3459,11 +3459,11 @@ def sort( Sort an array in-place. - Refer to :func:`cunumeric.sort` for full documentation. + Refer to :func:`cupynumeric.sort` for full documentation. See Also -------- - cunumeric.sort : equivalent function + cupynumeric.sort : equivalent function Availability -------- @@ -3483,11 +3483,11 @@ def argsort( Returns the indices that would sort this array. - Refer to :func:`cunumeric.argsort` for full documentation. + Refer to :func:`cupynumeric.argsort` for full documentation. See Also -------- - cunumeric.argsort : equivalent function + cupynumeric.argsort : equivalent function Availability -------- @@ -3505,11 +3505,11 @@ def squeeze(self, axis: Any = None) -> ndarray: Remove axes of length one from `a`. - Refer to :func:`cunumeric.squeeze` for full documentation. + Refer to :func:`cupynumeric.squeeze` for full documentation. See Also -------- - cunumeric.squeeze : equivalent function + cupynumeric.squeeze : equivalent function Availability -------- @@ -3546,11 +3546,11 @@ def sum( Return the sum of the array elements over the given axis. - Refer to :func:`cunumeric.sum` for full documentation. + Refer to :func:`cupynumeric.sum` for full documentation. See Also -------- - cunumeric.sum : equivalent function + cupynumeric.sum : equivalent function Availability -------- @@ -3601,11 +3601,11 @@ def swapaxes(self, axis1: Any, axis2: Any) -> ndarray: Return a view of the array with `axis1` and `axis2` interchanged. - Refer to :func:`cunumeric.swapaxes` for full documentation. + Refer to :func:`cupynumeric.swapaxes` for full documentation. See Also -------- - cunumeric.swapaxes : equivalent function + cupynumeric.swapaxes : equivalent function Availability -------- @@ -3703,7 +3703,7 @@ def tolist(self) -> Any: Return a copy of the array data as a (nested) Python list. Data items are converted to the nearest compatible builtin Python - type, via the `~cunumeric.ndarray.item` function. + type, via the `~cupynumeric.ndarray.item` function. If ``a.ndim`` is 0, then since the depth of the nested list is 0, it will not be a list at all, but a simple Python scalar. @@ -3720,7 +3720,7 @@ def tolist(self) -> Any: Notes ----- - The array may be recreated via ``a = cunumeric.array(a.tolist())``, + The array may be recreated via ``a = cupynumeric.array(a.tolist())``, although this may sometimes lose precision. Availability @@ -3856,7 +3856,7 @@ def view( Notes ----- - cuNumeric does not currently support type reinterpretation, or + cuPyNumeric does not currently support type reinterpretation, or conversion to ndarray sub-classes; use :func:`ndarray.__array__()` to convert to `numpy.ndarray`. @@ -3870,11 +3870,11 @@ def view( """ if dtype is not None and dtype != self.dtype: raise NotImplementedError( - "cuNumeric does not currently support type reinterpretation" + "cuPyNumeric does not currently support type reinterpretation" ) if type is not None: raise NotImplementedError( - "cuNumeric does not currently support conversion to ndarray " + "cuPyNumeric does not currently support conversion to ndarray " "sub-classes; use __array__() to convert to numpy.ndarray" ) return ndarray( @@ -3889,11 +3889,11 @@ def unique(self) -> ndarray: Find the unique elements of an array. - Refer to :func:`cunumeric.unique` for full documentation. + Refer to :func:`cupynumeric.unique` for full documentation. See Also -------- - cunumeric.unique : equivalent function + cupynumeric.unique : equivalent function Availability -------- @@ -3939,12 +3939,12 @@ def stencil_hint( high_offsets: tuple[int, ...], ) -> None: """ - Inform cuNumeric that this array will be used in a stencil computation - in the following code. + Inform cuPyNumeric that this array will be used in a stencil + computation in the following code. - This allows cuNumeric to allocate space for the "ghost" elements ahead - of time, rather than discover the full extent of accesses incrementally, - and thus avoid intermediate copies. + This allows cuPyNumeric to allocate space for the "ghost" elements + ahead of time, rather than discovering the full extent of accesses + incrementally, and thus avoid intermediate copies. For example, let's say we have a 1-D array A of size 10 and we want to partition A across two GPUs. By default, A would be partitioned equally @@ -3953,8 +3953,8 @@ def stencil_hint( `B = A[:9] + A[1:]`. The runtime would now need to adjust the partitioning such that GPU0 has elements 0-5 and GPU1 has elements 4-9 inclusive. Since the original instance on GPU0 does not cover index 5, - cuNumeric needs to allocate a full new instance that covers 0-5, leading - to an extra copy. In this case, if the code calls + cuPyNumeric needs to allocate a full new instance that covers 0-5, + leading to an extra copy. In this case, if the code calls `A.stencil_hint([1], [1])` to pre-allocate instances that contain the extra elements before it uses A, the extra copies can be avoided. diff --git a/cunumeric/_array/flags.py b/cupynumeric/_array/flags.py similarity index 91% rename from cunumeric/_array/flags.py rename to cupynumeric/_array/flags.py index 0ed9c81e31..d58a5480ab 100644 --- a/cunumeric/_array/flags.py +++ b/cupynumeric/_array/flags.py @@ -24,8 +24,8 @@ class flagsobj: """ Information about the memory layout of the array. - These flags don't reflect the properties of the cuNumeric array, but - rather the NumPy array that will be produced if the cuNumeric array is + These flags don't reflect the properties of the cuPyNumeric array, but + rather the NumPy array that will be produced if the cuPyNumeric array is materialized on a single node. """ @@ -78,5 +78,5 @@ def __setitem__(self, key: str, value: Any) -> None: def _check_writeable(self, value: Any) -> None: if value and not self._array._writeable: raise ValueError( - "non-writeable cunumeric arrays cannot be made writeable" + "non-writeable cupynumeric arrays cannot be made writeable" ) diff --git a/cunumeric/_array/thunk.py b/cupynumeric/_array/thunk.py similarity index 100% rename from cunumeric/_array/thunk.py rename to cupynumeric/_array/thunk.py diff --git a/cunumeric/_array/util.py b/cupynumeric/_array/util.py similarity index 90% rename from cunumeric/_array/util.py rename to cupynumeric/_array/util.py index 6dc3f68a0e..e0096db857 100644 --- a/cunumeric/_array/util.py +++ b/cupynumeric/_array/util.py @@ -47,11 +47,11 @@ def add_boilerplate( *array_params: str, ) -> Callable[[Callable[P, R]], Callable[P, R]]: """ - Adds required boilerplate to the wrapped cunumeric.ndarray or module-level - function. + Adds required boilerplate to the wrapped cupynumeric.ndarray or + module-level function. Every time the wrapped function is called, this wrapper will convert all - specified array-like parameters to cuNumeric ndarrays. Additionally, any + specified array-like parameters to cuPyNumeric ndarrays. Additionally, any "out" or "where" arguments will also always be automatically converted. """ to_convert = set(array_params) @@ -86,11 +86,11 @@ def wrapper(*args: Any, **kwargs: Any) -> R: for idx, arg in enumerate(args): if idx in indices and arg is not None: if idx == out_idx: - arg = convert_to_cunumeric_ndarray(arg, share=True) + arg = convert_to_cupynumeric_ndarray(arg, share=True) if not arg.flags.writeable: raise ValueError("out is not writeable") else: - arg = convert_to_cunumeric_ndarray(arg) + arg = convert_to_cupynumeric_ndarray(arg) converted_args.append(arg) args = tuple(converted_args) @@ -99,11 +99,13 @@ def wrapper(*args: Any, **kwargs: Any) -> R: for k, v in kwargs.items(): if k in to_convert and v is not None: if k == "out": - kwargs[k] = convert_to_cunumeric_ndarray(v, share=True) + kwargs[k] = convert_to_cupynumeric_ndarray( + v, share=True + ) if not kwargs[k].flags.writeable: raise ValueError("out is not writeable") else: - kwargs[k] = convert_to_cunumeric_ndarray(v) + kwargs[k] = convert_to_cupynumeric_ndarray(v) return func(*args, **kwargs) @@ -120,7 +122,7 @@ def broadcast_where(where: ndarray | None, shape: NdShape) -> ndarray | None: return where -def convert_to_cunumeric_ndarray(obj: Any, share: bool = False) -> ndarray: +def convert_to_cupynumeric_ndarray(obj: Any, share: bool = False) -> ndarray: from .array import ndarray # If this is an instance of one of our ndarrays then we're done @@ -136,7 +138,7 @@ def convert_to_cunumeric_ndarray(obj: Any, share: bool = False) -> ndarray: def maybe_convert_to_np_ndarray(obj: Any) -> Any: """ - Converts cuNumeric arrays into NumPy arrays, otherwise has no effect. + Converts cuPyNumeric arrays into NumPy arrays, otherwise has no effect. """ from ..ma import MaskedArray from .array import ndarray diff --git a/cunumeric/_module/__init__.py b/cupynumeric/_module/__init__.py similarity index 96% rename from cunumeric/_module/__init__.py rename to cupynumeric/_module/__init__.py index 86a4105bb0..e96566d914 100644 --- a/cunumeric/_module/__init__.py +++ b/cupynumeric/_module/__init__.py @@ -140,7 +140,7 @@ def test(*args: Any, **kw: Any) -> None: warn( - "cuNumeric cannot execute numpy.test() due to reliance " + "cuPyNumeric cannot execute numpy.test() due to reliance " "on Numpy internals. For information about running the " - "cuNumeric test suite, see: https://docs.nvidia.com/cunumeric/latest/developer/index.html" + "cuPyNumeric test suite, see: https://docs.nvidia.com/cupynumeric/latest/developer/index.html" ) diff --git a/cunumeric/_module/_unary_red_utils.py b/cupynumeric/_module/_unary_red_utils.py similarity index 100% rename from cunumeric/_module/_unary_red_utils.py rename to cupynumeric/_module/_unary_red_utils.py diff --git a/cunumeric/_module/array_basic.py b/cupynumeric/_module/array_basic.py similarity index 100% rename from cunumeric/_module/array_basic.py rename to cupynumeric/_module/array_basic.py diff --git a/cunumeric/_module/array_dimension.py b/cupynumeric/_module/array_dimension.py similarity index 96% rename from cunumeric/_module/array_dimension.py rename to cupynumeric/_module/array_dimension.py index b75bf45404..01629b2cb9 100644 --- a/cunumeric/_module/array_dimension.py +++ b/cupynumeric/_module/array_dimension.py @@ -19,7 +19,7 @@ import numpy as np from .._array.array import ndarray -from .._array.util import add_boilerplate, convert_to_cunumeric_ndarray +from .._array.util import add_boilerplate, convert_to_cupynumeric_ndarray from .._utils import is_np2 from .creation_data import array @@ -45,7 +45,7 @@ def _reshape_recur(ndim: int, arr: ndarray) -> tuple[int, ...]: def _atleast_nd(ndim: int, arys: Sequence[ndarray]) -> list[ndarray] | ndarray: - inputs = list(convert_to_cunumeric_ndarray(arr) for arr in arys) + inputs = list(convert_to_cupynumeric_ndarray(arr) for arr in arys) # 'reshape' change the shape of arrays # only when arr.shape != _reshape_recur(ndim,arr) result = list(arr.reshape(_reshape_recur(ndim, arr)) for arr in inputs) @@ -251,7 +251,7 @@ def broadcast_to( The shape of the desired array. A single integer i is interpreted as (i,). subok : bool, optional - This option is ignored by cuNumeric. + This option is ignored by cuPyNumeric. Returns ------- @@ -298,7 +298,7 @@ def broadcast_arrays(*args: Any, subok: bool = False) -> list[ndarray]: The arrays to broadcast. subok : bool, optional - This option is ignored by cuNumeric + This option is ignored by cuPyNumeric Returns ------- @@ -314,7 +314,7 @@ def broadcast_arrays(*args: Any, subok: bool = False) -> list[ndarray]: Multiple GPUs, Multiple CPUs """ - arrs = [convert_to_cunumeric_ndarray(arr) for arr in args] + arrs = [convert_to_cupynumeric_ndarray(arr) for arr in args] return _broadcast_arrays(arrs, subok=subok) @@ -337,7 +337,7 @@ class broadcast: """ def __init__(self, *arrays: Any) -> None: - arrs = [convert_to_cunumeric_ndarray(arr) for arr in arrays] + arrs = [convert_to_cupynumeric_ndarray(arr) for arr in arrays] broadcasted = _broadcast_arrays(arrs) self._iters = tuple(arr.flat for arr in broadcasted) self._index = 0 diff --git a/cunumeric/_module/array_joining.py b/cupynumeric/_module/array_joining.py similarity index 96% rename from cunumeric/_module/array_joining.py rename to cupynumeric/_module/array_joining.py index 13956a7aad..fbdf2adda4 100644 --- a/cunumeric/_module/array_joining.py +++ b/cupynumeric/_module/array_joining.py @@ -20,7 +20,7 @@ import numpy as np from .._array.array import ndarray -from .._array.util import convert_to_cunumeric_ndarray +from .._array.util import convert_to_cupynumeric_ndarray from .._utils import is_np2 from .array_dimension import _atleast_nd @@ -82,7 +82,7 @@ def check_list_depth(arr: Any, prefix: NdShape = (0,)) -> int: "List depths are mismatched. First element was at depth " f"{first_depth}, but there is an element at" f" depth {other_depth}, " - f"arrays{convert_to_array_form(prefix+(idx+1,))}" + f"arrays{convert_to_array_form(prefix + (idx + 1,))}" ) return depths[0] + 1 @@ -121,7 +121,7 @@ def check_shape_dtype_without_axis( if len(inputs) == 0: raise ValueError("need at least one array to concatenate") - inputs = list(convert_to_cunumeric_ndarray(inp) for inp in inputs) + inputs = list(convert_to_cupynumeric_ndarray(inp) for inp in inputs) ndim = inputs[0].ndim shape = inputs[0].shape @@ -184,7 +184,7 @@ def _block_collect_slices( # flatten lists of slices into a single list slices = list(chain(*updated_slices)) else: - arrays = list(convert_to_cunumeric_ndarray(inp) for inp in arr) + arrays = list(convert_to_cupynumeric_ndarray(inp) for inp in arr) common_shape = arrays[0].shape if len(arr) > 1: arrays, common_info = check_shape_dtype_without_axis( @@ -248,7 +248,7 @@ def _concatenate( shape=out_shape, dtype=common_info.dtype, inputs=inputs ) else: - out = convert_to_cunumeric_ndarray(out) + out = convert_to_cupynumeric_ndarray(out) if not isinstance(out, ndarray): raise TypeError("out should be ndarray") elif list(out.shape) != out_shape: @@ -295,8 +295,8 @@ def append(arr: ndarray, values: ndarray, axis: int | None = None) -> ndarray: Multiple GPUs, Multiple CPUs """ - # Check to see if we can build a new tuple of cuNumeric arrays - inputs = list(convert_to_cunumeric_ndarray(inp) for inp in [arr, values]) + # Check to see if we can build a new tuple of cuPyNumeric arrays + inputs = list(convert_to_cupynumeric_ndarray(inp) for inp in [arr, values]) return concatenate(inputs, axis) @@ -427,14 +427,14 @@ def concatenate( inputs = list(inp.ravel() for inp in reshaped) axis = 0 - # Check to see if we can build a new tuple of cuNumeric arrays - cunumeric_inputs, common_info = check_shape_dtype_without_axis( + # Check to see if we can build a new tuple of cuPyNumeric arrays + cupynumeric_inputs, common_info = check_shape_dtype_without_axis( inputs, concatenate.__name__, dtype, casting ) - check_shape_with_axis(cunumeric_inputs, concatenate.__name__, axis) + check_shape_with_axis(cupynumeric_inputs, concatenate.__name__, axis) return _concatenate( - cunumeric_inputs, + cupynumeric_inputs, common_info, axis, out, diff --git a/cunumeric/_module/array_rearrange.py b/cupynumeric/_module/array_rearrange.py similarity index 97% rename from cunumeric/_module/array_rearrange.py rename to cupynumeric/_module/array_rearrange.py index ea30e08746..7f27075835 100644 --- a/cunumeric/_module/array_rearrange.py +++ b/cupynumeric/_module/array_rearrange.py @@ -68,7 +68,7 @@ def flip(m: ndarray, axis: NdShapeLike | None = None) -> ndarray: Notes ----- - cuNumeric implementation doesn't return a view, it returns a new array + cuPyNumeric implementation doesn't return a view, it returns a new array """ return m.flip(axis=axis) @@ -101,7 +101,7 @@ def flipud(m: ndarray) -> ndarray: Notes ----- - cuNumeric implementation doesn't return a view, it returns a new array + cuPyNumeric implementation doesn't return a view, it returns a new array """ if m.ndim < 1: raise ValueError("Input must be >= 1-d.") @@ -137,7 +137,7 @@ def fliplr(m: ndarray) -> ndarray: Notes ----- - cuNumeric implementation doesn't return a view, it returns a new array + cuPyNumeric implementation doesn't return a view, it returns a new array """ if m.ndim < 2: raise ValueError("Input must be >= 2-d.") diff --git a/cunumeric/_module/array_shape.py b/cupynumeric/_module/array_shape.py similarity index 100% rename from cunumeric/_module/array_shape.py rename to cupynumeric/_module/array_shape.py diff --git a/cunumeric/_module/array_splitting.py b/cupynumeric/_module/array_splitting.py similarity index 98% rename from cunumeric/_module/array_splitting.py rename to cupynumeric/_module/array_splitting.py index dd4a9e2b1d..4462ee5e69 100644 --- a/cunumeric/_module/array_splitting.py +++ b/cupynumeric/_module/array_splitting.py @@ -19,7 +19,7 @@ import numpy as np from .._array.array import ndarray -from .._array.util import convert_to_cunumeric_ndarray +from .._array.util import convert_to_cupynumeric_ndarray if TYPE_CHECKING: import numpy.typing as npt @@ -99,7 +99,7 @@ def array_split( -------- Multiple GPUs, Multiple CPUs """ - array = convert_to_cunumeric_ndarray(a) + array = convert_to_cupynumeric_ndarray(a) split_pts = [] if axis >= array.ndim: raise ValueError( diff --git a/cunumeric/_module/array_tiling.py b/cupynumeric/_module/array_tiling.py similarity index 97% rename from cunumeric/_module/array_tiling.py rename to cupynumeric/_module/array_tiling.py index 72e5287bc2..6dca2939d6 100644 --- a/cunumeric/_module/array_tiling.py +++ b/cupynumeric/_module/array_tiling.py @@ -19,7 +19,7 @@ import numpy as np from .._array.array import ndarray -from .._array.util import add_boilerplate, convert_to_cunumeric_ndarray +from .._array.util import add_boilerplate, convert_to_cupynumeric_ndarray from .._utils import is_np2 from ..runtime import runtime from .creation_shape import full @@ -183,9 +183,9 @@ def repeat(a: ndarray, repeats: Any, axis: int | None = None) -> ndarray: ) # array is an array - array = convert_to_cunumeric_ndarray(a) + array = convert_to_cupynumeric_ndarray(a) if np.ndim(repeats) == 1: - repeats = convert_to_cunumeric_ndarray(repeats) + repeats = convert_to_cupynumeric_ndarray(repeats) # if no axes specified, flatten array if axis is None: diff --git a/cunumeric/_module/array_transpose.py b/cupynumeric/_module/array_transpose.py similarity index 100% rename from cunumeric/_module/array_transpose.py rename to cupynumeric/_module/array_transpose.py diff --git a/cunumeric/_module/binary_bit_packing.py b/cupynumeric/_module/binary_bit_packing.py similarity index 100% rename from cunumeric/_module/binary_bit_packing.py rename to cupynumeric/_module/binary_bit_packing.py diff --git a/cunumeric/_module/creation_data.py b/cupynumeric/_module/creation_data.py similarity index 100% rename from cunumeric/_module/creation_data.py rename to cupynumeric/_module/creation_data.py diff --git a/cunumeric/_module/creation_matrices.py b/cupynumeric/_module/creation_matrices.py similarity index 98% rename from cunumeric/_module/creation_matrices.py rename to cupynumeric/_module/creation_matrices.py index 7b97ef488f..540276c532 100644 --- a/cunumeric/_module/creation_matrices.py +++ b/cupynumeric/_module/creation_matrices.py @@ -30,7 +30,7 @@ def diag(v: ndarray, k: int = 0) -> ndarray: Extract a diagonal or construct a diagonal array. - See the more detailed documentation for ``cunumeric.diagonal`` if you use + See the more detailed documentation for ``cupynumeric.diagonal`` if you use this function to extract a diagonal and wish to write to the resulting array; whether it returns a copy or a view depends on what version of numpy you are using. diff --git a/cunumeric/_module/creation_ranges.py b/cupynumeric/_module/creation_ranges.py similarity index 99% rename from cunumeric/_module/creation_ranges.py rename to cupynumeric/_module/creation_ranges.py index ca72f401e4..d04d94b535 100644 --- a/cunumeric/_module/creation_ranges.py +++ b/cupynumeric/_module/creation_ranges.py @@ -49,7 +49,7 @@ def arange( `range` function, but returns an ndarray rather than a list. When using a non-integer step, such as 0.1, the results will often not - be consistent. It is better to use `cunumeric.linspace` for these cases. + be consistent. It is better to use `cupynumeric.linspace` for these cases. Parameters ---------- diff --git a/cunumeric/_module/creation_shape.py b/cupynumeric/_module/creation_shape.py similarity index 97% rename from cunumeric/_module/creation_shape.py rename to cupynumeric/_module/creation_shape.py index 6c51de532f..d14aa7298d 100644 --- a/cunumeric/_module/creation_shape.py +++ b/cupynumeric/_module/creation_shape.py @@ -38,7 +38,8 @@ def empty(shape: NdShapeLike, dtype: npt.DTypeLike = np.float64) -> ndarray: shape : int or tuple[int] Shape of the empty array. dtype : data-type, optional - Desired output data-type for the array. Default is `cunumeric.float64`. + Desired output data-type for the array. Default is + ``cupynumeric.float64``. Returns ------- @@ -189,7 +190,7 @@ def ones(shape: NdShapeLike, dtype: npt.DTypeLike = np.float64) -> ndarray: shape : int or tuple[int] Shape of the new array. dtype : data-type, optional - The desired data-type for the array. Default is `cunumeric.float64`. + The desired data-type for the array. Default is `cupynumeric.float64`. Returns ------- @@ -256,7 +257,7 @@ def zeros(shape: NdShapeLike, dtype: npt.DTypeLike = np.float64) -> ndarray: shape : int or tuple[int] Shape of the new array. dtype : data-type, optional - The desired data-type for the array. Default is `cunumeric.float64`. + The desired data-type for the array. Default is `cupynumeric.float64`. Returns ------- @@ -331,7 +332,7 @@ def full( Fill value. dtype : data-type, optional The desired data-type for the array The default, None, means - `cunumeric.array(fill_value).dtype`. + `cupynumeric.array(fill_value).dtype`. Returns ------- diff --git a/cunumeric/_module/indexing.py b/cupynumeric/_module/indexing.py similarity index 95% rename from cunumeric/_module/indexing.py rename to cupynumeric/_module/indexing.py index 30f4c1633b..3af4622565 100644 --- a/cunumeric/_module/indexing.py +++ b/cupynumeric/_module/indexing.py @@ -22,7 +22,7 @@ from .._array.util import ( add_boilerplate, check_writeable, - convert_to_cunumeric_ndarray, + convert_to_cupynumeric_ndarray, ) from .._utils import is_np2 from .._utils.array import calculate_volume @@ -195,7 +195,7 @@ def mask_indices( Assume `mask_func` is a function that, for a square array a of size ``(n, n)`` with a possible offset argument `k`, when called as ``mask_func(a, k)`` returns a new array with zeros in certain locations - (functions like :func:`cunumeric.triu` or :func:`cunumeric.tril` + (functions like :func:`cupynumeric.triu` or :func:`cupynumeric.tril` do precisely this). Then this function returns the indices where the non-zero values would be located. @@ -205,12 +205,12 @@ def mask_indices( The returned indices will be valid to access arrays of shape (n, n). mask_func : callable A function whose call signature is similar to that of - :func:`cunumeric.triu`, :func:`cunumeric.tril`. + :func:`cupynumeric.triu`, :func:`cupynumeric.tril`. That is, ``mask_func(x, k)`` returns a boolean array, shaped like `x`. `k` is an optional argument to the function. k : scalar An optional argument which is passed through to `mask_func`. Functions - like :func:`cunumeric.triu`, :func:`cunumeric,tril` + like :func:`cupynumeric.triu`, :func:`cupynumeric,tril` take a second argument that is interpreted as an offset. Returns @@ -225,10 +225,10 @@ def mask_indices( Notes ----- - WARNING: `mask_indices` expects `mask_function` to call cuNumeric functions - for good performance. In case non-cuNumeric functions are called by - `mask_function`, cuNumeric will have to materialize all data on the host - which might result in running out of system memory. + WARNING: ``mask_indices`` expects ``mask_function`` to call cuPyNumeric + functions for good performance. In case non-cuPyNumeric functions are + called by ``mask_function``, cuPyNumeric will have to materialize all data + on the host which might result in running out of system memory. Availability -------- @@ -238,7 +238,7 @@ def mask_indices( a = ones((n, n), dtype=bool) if not is_implemented(mask_func): runtime.warn( - "Calling non-cuNumeric functions in mask_func can result in bad " + "Calling non-cuPyNumeric functions in mask_func can result in bad " "performance", category=UserWarning, ) @@ -389,7 +389,7 @@ def tril_indices( The row dimension of the arrays for which the returned indices will be valid. k : int, optional - Diagonal offset (see :func:`cunumeric.tril` for details). + Diagonal offset (see :func:`cupynumeric.tril` for details). m : int, optional The column dimension of the arrays for which the returned indices will be valid. @@ -422,7 +422,7 @@ def tril_indices_from(arr: ndarray, k: int = 0) -> tuple[ndarray, ...]: """ Return the indices for the lower-triangle of arr. - See :func:`cunumeric.tril_indices` for full details. + See :func:`cupynumeric.tril_indices` for full details. Parameters ---------- @@ -430,7 +430,7 @@ def tril_indices_from(arr: ndarray, k: int = 0) -> tuple[ndarray, ...]: The indices will be valid for arrays whose dimensions are the same as arr. k : int, optional - Diagonal offset (see :func:`cunumeric.tril` for details). + Diagonal offset (see :func:`cupynumeric.tril` for details). Returns ------- @@ -468,7 +468,7 @@ def triu_indices( The size of the arrays for which the returned indices will be valid. k : int, optional - Diagonal offset (see :func:`cunumeric.triu` for details). + Diagonal offset (see :func:`cupynumeric.triu` for details). m : int, optional The column dimension of the arrays for which the returned arrays will be valid. @@ -501,7 +501,7 @@ def triu_indices_from(arr: ndarray, k: int = 0) -> tuple[ndarray, ...]: """ Return the indices for the upper-triangle of arr. - See :func:`cunumeric.triu_indices` for full details. + See :func:`cupynumeric.triu_indices` for full details. Parameters ---------- @@ -509,7 +509,7 @@ def triu_indices_from(arr: ndarray, k: int = 0) -> tuple[ndarray, ...]: The indices will be valid for arrays whose dimensions are the same as arr. k : int, optional - Diagonal offset (see :func:`cunumeric.triu` for details). + Diagonal offset (see :func:`cupynumeric.triu` for details). Returns ------- @@ -674,7 +674,7 @@ def take_along_axis(a: ndarray, indices: ndarray, axis: int | None) -> ndarray: latter. These slices can be different lengths. Functions returning an index along an axis, like - :func:`cunumeric.argsort` and :func:`cunumeric.argpartition`, + :func:`cupynumeric.argsort` and :func:`cupynumeric.argpartition`, produce suitable indices for this function. Parameters @@ -688,7 +688,7 @@ def take_along_axis(a: ndarray, indices: ndarray, axis: int | None) -> ndarray: axis : int The axis to take 1d slices along. If axis is None, the input array is treated as if it had first been flattened to 1d, for consistency with - :func:`cunumeric.sort` and :func:`cunumeric.argsort`. + :func:`cupynumeric.sort` and :func:`cupynumeric.argsort`. Returns ------- @@ -738,9 +738,9 @@ def put_along_axis( the index and data arrays, and uses the former to place values into the latter. These slices can be different lengths. - Functions returning an index along an axis, like :func:`cunumeric.argsort` - and :func:`cunumeric.argpartition`, produce suitable indices for - this function. + Functions returning an index along an axis, like + :func:`cupynumeric.argsort` and :func:`cupynumeric.argpartition`, produce + suitable indices for this function. Parameters ---------- @@ -924,14 +924,14 @@ def select( if len(condlist) == 0: raise ValueError("select with an empty condition list is not possible") - condlist_ = tuple(convert_to_cunumeric_ndarray(c) for c in condlist) + condlist_ = tuple(convert_to_cupynumeric_ndarray(c) for c in condlist) for i, c in enumerate(condlist_): if c.dtype != bool: raise TypeError( f"invalid entry {i} in condlist: should be boolean ndarray" ) - choicelist_ = tuple(convert_to_cunumeric_ndarray(c) for c in choicelist) + choicelist_ = tuple(convert_to_cupynumeric_ndarray(c) for c in choicelist) common_type = np.result_type(*choicelist_, default) args = condlist_ + choicelist_ choicelist_ = tuple( @@ -1065,7 +1065,7 @@ def diagonal( Notes ----- - Unlike NumPy's, the cuNumeric implementation always returns a copy + Unlike NumPy's, the cuPyNumeric implementation always returns a copy See Also -------- diff --git a/cunumeric/_module/io_numpy.py b/cupynumeric/_module/io_numpy.py similarity index 96% rename from cunumeric/_module/io_numpy.py rename to cupynumeric/_module/io_numpy.py index 67ea13c051..42d4ebdf53 100644 --- a/cunumeric/_module/io_numpy.py +++ b/cupynumeric/_module/io_numpy.py @@ -61,7 +61,7 @@ def load( Notes ----- - cuNumeric does not currently support ``.npz`` and pickled files. + cuPyNumeric does not currently support ``.npz`` and pickled files. Availability -------- diff --git a/cunumeric/_module/linalg_mvp.py b/cupynumeric/_module/linalg_mvp.py similarity index 97% rename from cunumeric/_module/linalg_mvp.py rename to cupynumeric/_module/linalg_mvp.py index dd764c04ec..8650b1b00c 100644 --- a/cunumeric/_module/linalg_mvp.py +++ b/cupynumeric/_module/linalg_mvp.py @@ -25,7 +25,7 @@ from .._array.array import ndarray from .._array.util import ( add_boilerplate, - convert_to_cunumeric_ndarray, + convert_to_cupynumeric_ndarray, find_common_type, ) from .._ufunc.math import multiply @@ -72,7 +72,7 @@ def inner(a: ndarray, b: ndarray, out: ndarray | None = None) -> ndarray: Notes ----- - The cuNumeric implementation is a little more liberal than NumPy in terms + The cuPyNumeric implementation is a little more liberal than NumPy in terms of allowed broadcasting, e.g. ``inner(ones((1,)), ones((4,)))`` is allowed. See Also @@ -109,7 +109,7 @@ def dot(a: ndarray, b: ndarray, out: ndarray | None = None) -> ndarray: but using ``a @ b`` is preferred. - If either `a` or `b` is 0-D (scalar), it is equivalent to - :func:`multiply` and using ``cunumeric.multiply(a, b)`` or ``a * b`` is + :func:`multiply` and using ``cupynumeric.multiply(a, b)`` or ``a * b`` is preferred. - If `a` is an N-D array and `b` is a 1-D array, it is a sum product over @@ -139,7 +139,7 @@ def dot(a: ndarray, b: ndarray, out: ndarray | None = None) -> ndarray: Notes ----- - The cuNumeric implementation is a little more liberal than NumPy in terms + The cuPyNumeric implementation is a little more liberal than NumPy in terms of allowed broadcasting, e.g. ``dot(ones((3,1)), ones((4,5)))`` is allowed. Except for the inner-product case, only floating-point types are supported. @@ -227,7 +227,7 @@ def matmul( (9, 5, 7, 3) >>> # n is 7, k is 4, m is 3 - The cuNumeric implementation is a little more liberal than NumPy in terms + The cuPyNumeric implementation is a little more liberal than NumPy in terms of allowed broadcasting, e.g. ``matmul(ones((3,1)), ones((4,5)))`` is allowed. @@ -290,7 +290,7 @@ def vdot(a: ndarray, b: ndarray, out: ndarray | None = None) -> ndarray: Notes ----- - The cuNumeric implementation is a little more liberal than NumPy in terms + The cuPyNumeric implementation is a little more liberal than NumPy in terms of allowed broadcasting, e.g. ``vdot(ones((1,)), ones((4,)))`` is allowed. See Also @@ -389,7 +389,7 @@ def tensordot( Notes ----- - The cuNumeric implementation is a little more liberal than NumPy in terms + The cuPyNumeric implementation is a little more liberal than NumPy in terms of allowed broadcasting, e.g. ``tensordot(ones((3,1)), ones((1,4)))`` is allowed. @@ -710,8 +710,9 @@ def einsum( optimize : ``{False, True, 'greedy', 'optimal'}``, optional Controls if intermediate optimization should occur. If False then arrays will be contracted in input order, one at a time. True (the - default) will use the 'greedy' algorithm. See ``cunumeric.einsum_path`` - for more information on the available optimization algorithms. + default) will use the 'greedy' algorithm. See + ``cupynumeric.einsum_path`` for more information on the available + optimization algorithms. Returns ------- @@ -730,10 +731,10 @@ def einsum( -------- Multiple GPUs, Multiple CPUs """ - operands_list = [convert_to_cunumeric_ndarray(op) for op in operands] + operands_list = [convert_to_cupynumeric_ndarray(op) for op in operands] if out is not None: - out = convert_to_cunumeric_ndarray(out, share=True) + out = convert_to_cupynumeric_ndarray(out, share=True) if optimize is True: optimize = "greedy" @@ -841,7 +842,7 @@ def einsum_path( -------- Multiple GPUs, Multiple CPUs """ - computed_operands = [convert_to_cunumeric_ndarray(op) for op in operands] + computed_operands = [convert_to_cupynumeric_ndarray(op) for op in operands] memory_limit = _builtin_max(op.size for op in computed_operands) if isinstance(optimize, tuple): if len(optimize) != 2: diff --git a/cunumeric/_module/logic_array_contents.py b/cupynumeric/_module/logic_array_contents.py similarity index 92% rename from cunumeric/_module/logic_array_contents.py rename to cupynumeric/_module/logic_array_contents.py index a1fe574b98..e5bb9bd9ee 100644 --- a/cunumeric/_module/logic_array_contents.py +++ b/cupynumeric/_module/logic_array_contents.py @@ -16,7 +16,7 @@ from typing import TYPE_CHECKING -from .._array.util import convert_to_cunumeric_ndarray +from .._array.util import convert_to_cupynumeric_ndarray from .._ufunc.comparison import logical_and from .._ufunc.floating import isinf, signbit @@ -61,9 +61,9 @@ def isneginf(x: ndarray, out: ndarray | None = None) -> ndarray: Multiple GPUs, Multiple CPUs """ - x = convert_to_cunumeric_ndarray(x) + x = convert_to_cupynumeric_ndarray(x) if out is not None: - out = convert_to_cunumeric_ndarray(out, share=True) + out = convert_to_cupynumeric_ndarray(out, share=True) rhs1 = isinf(x) rhs2 = signbit(x) return logical_and(rhs1, rhs2, out=out) @@ -106,9 +106,9 @@ def isposinf(x: ndarray, out: ndarray | None = None) -> ndarray: Multiple GPUs, Multiple CPUs """ - x = convert_to_cunumeric_ndarray(x) + x = convert_to_cupynumeric_ndarray(x) if out is not None: - out = convert_to_cunumeric_ndarray(out, share=True) + out = convert_to_cupynumeric_ndarray(out, share=True) rhs1 = isinf(x) rhs2 = ~signbit(x) return logical_and(rhs1, rhs2, out=out) diff --git a/cunumeric/_module/logic_array_type.py b/cupynumeric/_module/logic_array_type.py similarity index 93% rename from cunumeric/_module/logic_array_type.py rename to cupynumeric/_module/logic_array_type.py index 2c8553078c..1e39754a7b 100644 --- a/cunumeric/_module/logic_array_type.py +++ b/cupynumeric/_module/logic_array_type.py @@ -19,7 +19,7 @@ import numpy as np from .._array.array import ndarray -from .._array.util import convert_to_cunumeric_ndarray +from .._array.util import convert_to_cupynumeric_ndarray from .creation_shape import full if TYPE_CHECKING: @@ -53,7 +53,7 @@ def iscomplex(x: ndarray | npt.NDArray[Any]) -> ndarray: Multiple GPUs, Multiple CPUs """ - x = convert_to_cunumeric_ndarray(x) + x = convert_to_cupynumeric_ndarray(x) if x.dtype.kind != "c": return full(x.shape, False, dtype=bool) else: @@ -121,7 +121,7 @@ def isreal(x: ndarray | npt.NDArray[Any]) -> ndarray: Multiple GPUs, Multiple CPUs """ - x = convert_to_cunumeric_ndarray(x) + x = convert_to_cupynumeric_ndarray(x) if x.dtype.kind != "c": return full(x.shape, True, dtype=bool) else: @@ -179,7 +179,7 @@ def isscalar(x: ndarray | npt.NDArray[Any]) -> bool: Notes ----- - This function falls back to NumPy for all object types but cuNumeric's + This function falls back to NumPy for all object types but cuPyNumeric's ndarray, which always returns `False`. Availability @@ -187,9 +187,9 @@ def isscalar(x: ndarray | npt.NDArray[Any]) -> bool: Multiple GPUs, Multiple CPUs """ - # Since the input can be any value, we can't just convert it to cunumeric - # ndarray. Instead we check if the input is cunumeric ndarray and, if not, - # fall back to Numpy + # Since the input can be any value, we can't just convert it to cupynumeric + # ndarray. Instead we check if the input is cupynumeric ndarray and, if + # not, fall back to Numpy if isinstance(x, ndarray): return False else: diff --git a/cunumeric/_module/logic_comparison.py b/cupynumeric/_module/logic_comparison.py similarity index 95% rename from cunumeric/_module/logic_comparison.py rename to cupynumeric/_module/logic_comparison.py index dad4782027..46c6410a4a 100644 --- a/cunumeric/_module/logic_comparison.py +++ b/cupynumeric/_module/logic_comparison.py @@ -84,7 +84,7 @@ def allclose( """ if equal_nan: raise NotImplementedError( - "cuNumeric does not support `equal_nan` yet for allclose" + "cuPyNumeric does not support `equal_nan` yet for allclose" ) args = (Scalar(rtol, ty.float64), Scalar(atol, ty.float64)) return perform_binary_reduction( @@ -145,7 +145,7 @@ def isclose( """ if equal_nan: raise NotImplementedError( - "cuNumeric does not support `equal_nan` yet for isclose" + "cuPyNumeric does not support `equal_nan` yet for isclose" ) out_shape = np.broadcast_shapes(a.shape, b.shape) @@ -191,7 +191,7 @@ def array_equal( """ if equal_nan: raise NotImplementedError( - "cuNumeric does not support `equal_nan` yet for `array_equal`" + "cuPyNumeric does not support `equal_nan` yet for `array_equal`" ) if a1.shape != a2.shape: diff --git a/cunumeric/_module/logic_truth.py b/cupynumeric/_module/logic_truth.py similarity index 100% rename from cunumeric/_module/logic_truth.py rename to cupynumeric/_module/logic_truth.py diff --git a/cunumeric/_module/math_complex.py b/cupynumeric/_module/math_complex.py similarity index 98% rename from cunumeric/_module/math_complex.py rename to cupynumeric/_module/math_complex.py index 3d05580ad2..29f3787f75 100644 --- a/cunumeric/_module/math_complex.py +++ b/cupynumeric/_module/math_complex.py @@ -20,7 +20,6 @@ from .._array.thunk import perform_unary_op from .._array.util import add_boilerplate -from .._utils.array import to_core_type from ..config import UnaryOpCode if TYPE_CHECKING: diff --git a/cunumeric/_module/math_extrema.py b/cupynumeric/_module/math_extrema.py similarity index 93% rename from cunumeric/_module/math_extrema.py rename to cupynumeric/_module/math_extrema.py index ad805c00f6..0b576684d7 100644 --- a/cunumeric/_module/math_extrema.py +++ b/cupynumeric/_module/math_extrema.py @@ -66,10 +66,11 @@ def amax( initial : scalar, optional The minimum value of an output element. Must be present to allow - computation on empty slice. See `~cunumeric.ufunc.reduce` for details. + computation on empty slice. See `~cupynumeric.ufunc.reduce` for + details. where : array_like[bool], optional - Elements to compare for the maximum. See `~cunumeric.ufunc.reduce` + Elements to compare for the maximum. See `~cupynumeric.ufunc.reduce` for details. Returns @@ -142,10 +143,11 @@ def amin( initial : scalar, optional The maximum value of an output element. Must be present to allow - computation on empty slice. See `~cunumeric.ufunc.reduce` for details. + computation on empty slice. See `~cupynumeric.ufunc.reduce` for + details. where : array_like[bool], optional - Elements to compare for the minimum. See `~cunumeric.ufunc.reduce` + Elements to compare for the minimum. See `~cupynumeric.ufunc.reduce` for details. Returns diff --git a/cunumeric/_module/math_misc.py b/cupynumeric/_module/math_misc.py similarity index 98% rename from cunumeric/_module/math_misc.py rename to cupynumeric/_module/math_misc.py index 34be8ddde3..a91e3faccc 100644 --- a/cunumeric/_module/math_misc.py +++ b/cupynumeric/_module/math_misc.py @@ -85,7 +85,7 @@ def convolve( ----- The current implementation only supports the 'same' mode. - Unlike `numpy.convolve`, `cunumeric.convolve` supports N-dimensional + Unlike `numpy.convolve`, `cupynumeric.convolve` supports N-dimensional inputs, but it follows NumPy's behavior for 1-D inputs. Availability diff --git a/cunumeric/_module/math_rounding.py b/cupynumeric/_module/math_rounding.py similarity index 100% rename from cunumeric/_module/math_rounding.py rename to cupynumeric/_module/math_rounding.py diff --git a/cunumeric/_module/math_sum_prod_diff.py b/cupynumeric/_module/math_sum_prod_diff.py similarity index 93% rename from cunumeric/_module/math_sum_prod_diff.py rename to cupynumeric/_module/math_sum_prod_diff.py index 8a18f57bf7..6027fb1c1a 100644 --- a/cunumeric/_module/math_sum_prod_diff.py +++ b/cupynumeric/_module/math_sum_prod_diff.py @@ -26,7 +26,7 @@ from .._ufunc.math import add, multiply, subtract from .._utils import is_np2 from ..config import ScanCode, UnaryRedCode -from ..settings import settings as cunumeric_settings +from ..settings import settings as cupynumeric_settings from ._unary_red_utils import get_non_nan_unary_red_code from .array_dimension import broadcast_to from .array_joining import concatenate @@ -93,12 +93,12 @@ def prod( sub-class' method does not implement `keepdims` any exceptions will be raised. initial : scalar, optional - The starting value for this product. See `~cunumeric.ufunc.reduce` for - details. + The starting value for this product. See `~cupynumeric.ufunc.reduce` + for details. where : array_like[bool], optional - Elements to include in the product. See `~cunumeric.ufunc.reduce` for - details. + Elements to include in the product. See `~cupynumeric.ufunc.reduce` + for details. Returns ------- @@ -177,10 +177,11 @@ def sum( sub-class' method does not implement `keepdims` any exceptions will be raised. initial : scalar, optional - Starting value for the sum. See `~cunumeric.ufunc.reduce` for details. + Starting value for the sum. See `~cupynumeric.ufunc.reduce` for + details. where : array_like[bool], optional - Elements to include in the sum. See `~cunumeric.ufunc.reduce` for + Elements to include in the sum. See `~cupynumeric.ufunc.reduce` for details. Returns @@ -253,13 +254,14 @@ def cumprod( Notes ----- - CuNumeric's parallel implementation may yield different results from NumPy - with floating point and complex types. For example, when boundary values - such as inf occur they may not propagate as expected. Consider the float32 - array ``[3e+37, 1, 100, 0.01]``. NumPy's cumprod will return a result of - ``[3e+37, 3e+37, inf, inf]``. However, cuNumeric might internally partition - the array such that partition 0 has ``[3e+37, 1]`` and partition 1 has - ``[100, 0.01]``, returning the result ``[3e+37, 3e+37, inf, 3e+37]``. + cuPyNumeric's parallel implementation may yield different results from + NumPy with floating point and complex types. For example, when boundary + values such as inf occur they may not propagate as expected. Consider the + float32 array ``[3e+37, 1, 100, 0.01]``. NumPy's cumprod will return a + result ofc``[3e+37, 3e+37, inf, inf]``. However, cuPyNumeric might + internally partition the array such that partition 0 has ``[3e+37, 1]`` + and partition 1 has ``[100, 0.01]``, returning the result + ``[3e+37, 3e+37, inf, 3e+37]``. Availability -------- @@ -318,10 +320,10 @@ def cumsum( Notes ----- - CuNumeric's parallel implementation may yield different results from NumPy - with floating point and complex types. For example, when boundary values - such as inf occur they may not propagate as expected. For more explanation - check cunumeric.cumprod. + CuPyNumeric's parallel implementation may yield different results from + NumPy with floating point and complex types. For example, when boundary + values such as inf occur they may not propagate as expected. For more + explanation check cupynumeric.cumprod. Availability -------- @@ -379,10 +381,10 @@ def nancumprod( Notes ----- - CuNumeric's parallel implementation may yield different results from NumPy - with floating point and complex types. For example, when boundary values - such as inf occur they may not propagate as expected. For more explanation - check cunumeric.cumprod. + CuPyNumeric's parallel implementation may yield different results from + NumPy with floating point and complex types. For example, when boundary + values such as inf occur they may not propagate as expected. For more + explanation check cupynumeric.cumprod. Availability -------- @@ -440,10 +442,10 @@ def nancumsum( Notes ----- - CuNumeric's parallel implementation may yield different results from NumPy - with floating point and complex types. For example, when boundary values - such as inf occur they may not propagate as expected. For more explanation - check cunumeric.cumprod. + CuPyNumeric's parallel implementation may yield different results from + NumPy with floating point and complex types. For example, when boundary + values such as inf occur they may not propagate as expected. For more + explanation check cupynumeric.cumprod. Availability -------- @@ -465,7 +467,7 @@ def nanargmax( """ Return the indices of the maximum values in the specified axis ignoring NaNs. For empty arrays, ValueError is raised. For all-NaN slices, - ValueError is raised only when CUNUMERIC_NUMPY_COMPATIBILITY + ValueError is raised only when CUPYNUMERIC_NUMPY_COMPATIBILITY environment variable is set, otherwise identity is returned. Warning: results cannot be trusted if a slice contains only NaNs @@ -504,7 +506,7 @@ def nanargmax( if a.size == 0: raise ValueError("attempt to get nanargmax of an empty sequence") - if cunumeric_settings.numpy_compat() and a.dtype.kind == "f": + if cupynumeric_settings.numpy_compat() and a.dtype.kind == "f": if any(all(isnan(a), axis=axis)): raise ValueError("Array/Slice contains only NaNs") @@ -533,7 +535,7 @@ def nanargmin( """ Return the indices of the minimum values in the specified axis ignoring NaNs. For empty arrays, ValueError is raised. For all-NaN slices, - ValueError is raised only when CUNUMERIC_NUMPY_COMPATIBILITY + ValueError is raised only when CUPYNUMERIC_NUMPY_COMPATIBILITY environment variable is set, otherwise identity is returned. Warning: results cannot be trusted if a slice contains only NaNs @@ -572,7 +574,7 @@ def nanargmin( if a.size == 0: raise ValueError("attempt to get nanargmin of an empty sequence") - if cunumeric_settings.numpy_compat() and a.dtype.kind == "f": + if cupynumeric_settings.numpy_compat() and a.dtype.kind == "f": if any(all(isnan(a), axis=axis)): raise ValueError("Array/Slice contains only NaNs") @@ -602,7 +604,7 @@ def nanmin( """ Return minimum of an array or minimum along an axis, ignoring any NaNs. When all-NaN slices are encountered, a NaN is returned - for that slice only when CUNUMERIC_NUMPY_COMPATIBILITY environment + for that slice only when CUPYNUMERIC_NUMPY_COMPATIBILITY environment variable is set, otherwise identity is returned. Empty slices will raise a ValueError @@ -633,10 +635,11 @@ def nanmin( initial : scalar, optional The maximum value of an output element. Must be present to allow - computation on empty slice. See `~cunumeric.ufunc.reduce` for details. + computation on empty slice. See `~cupynumeric.ufunc.reduce` for + details. where : array_like[bool], optional - Elements to compare for the minimum. See `~cunumeric.ufunc.reduce` + Elements to compare for the minimum. See `~cupynumeric.ufunc.reduce` for details. Returns @@ -648,7 +651,7 @@ def nanmin( Notes ----- - CuNumeric's implementation will not raise a Runtime Warning for + CuPyNumeric's implementation will not raise a Runtime Warning for slices with all-NaNs See Also @@ -675,7 +678,7 @@ def nanmin( where=where, ) - if cunumeric_settings.numpy_compat() and a.dtype.kind == "f": + if cupynumeric_settings.numpy_compat() and a.dtype.kind == "f": all_nan = all(isnan(a), axis=axis, keepdims=keepdims, where=where) putmask(out_array, all_nan, np.nan) # type: ignore @@ -694,7 +697,7 @@ def nanmax( """ Return the maximum of an array or maximum along an axis, ignoring any NaNs. When all-NaN slices are encountered, a NaN is returned - for that slice only when CUNUMERIC_NUMPY_COMPATIBILITY environment + for that slice only when CUPYNUMERIC_NUMPY_COMPATIBILITY environment variable is set, otherwise identity is returned. Empty slices will raise a ValueError @@ -728,10 +731,11 @@ def nanmax( initial : scalar, optional The minimum value of an output element. Must be present to allow - computation on empty slice. See `~cunumeric.ufunc.reduce` for details. + computation on empty slice. See `~cupynumeric.ufunc.reduce` for + details. where : array_like[bool], optional - Elements to compare for the maximum. See `~cunumeric.ufunc.reduce` + Elements to compare for the maximum. See `~cupynumeric.ufunc.reduce` for details. Returns @@ -743,7 +747,7 @@ def nanmax( Notes ----- - CuNumeric's implementation will not raise a Runtime Warning for + CuPyNumeric's implementation will not raise a Runtime Warning for slices with all-NaNs See Also @@ -770,7 +774,7 @@ def nanmax( where=where, ) - if cunumeric_settings.numpy_compat() and a.dtype.kind == "f": + if cupynumeric_settings.numpy_compat() and a.dtype.kind == "f": all_nan = all(isnan(a), axis=axis, keepdims=keepdims, where=where) putmask(out_array, all_nan, np.nan) # type: ignore @@ -825,11 +829,11 @@ def nanprod( sub-class' method does not implement `keepdims` any exceptions will be raised. initial : scalar, optional - The starting value for this product. See `~cunumeric.ufunc.reduce` for - details. + The starting value for this product. See `~cupynumeric.ufunc.reduce` + for details. where : array_like[bool], optional - Elements to include in the product. See `~cunumeric.ufunc.reduce` for - details. + Elements to include in the product. See `~cupynumeric.ufunc.reduce` + for details. Returns ------- @@ -924,11 +928,11 @@ def nansum( the result will broadcast correctly against the input array. initial : scalar, optional - Starting value for the sum. See `~cunumeric.ufunc.reduce` for + Starting value for the sum. See `~cupynumeric.ufunc.reduce` for details. where : array_like[bool], optional - Elements to include in the sum. See `~cunumeric.ufunc.reduce` for + Elements to include in the sum. See `~cupynumeric.ufunc.reduce` for details. Returns diff --git a/cunumeric/_module/sets_making.py b/cupynumeric/_module/sets_making.py similarity index 100% rename from cunumeric/_module/sets_making.py rename to cupynumeric/_module/sets_making.py diff --git a/cunumeric/_module/ssc_counting.py b/cupynumeric/_module/ssc_counting.py similarity index 100% rename from cunumeric/_module/ssc_counting.py rename to cupynumeric/_module/ssc_counting.py diff --git a/cunumeric/_module/ssc_searching.py b/cupynumeric/_module/ssc_searching.py similarity index 97% rename from cunumeric/_module/ssc_searching.py rename to cupynumeric/_module/ssc_searching.py index de8319ca6b..c64261eb2b 100644 --- a/cunumeric/_module/ssc_searching.py +++ b/cupynumeric/_module/ssc_searching.py @@ -107,8 +107,8 @@ def argmax( Notes ----- - CuNumeric's parallel implementation may yield different results from NumPy - when the array contains NaN(s). + cuPyNumeric's parallel implementation may yield different results from + NumPy when the array contains NaN(s). Availability -------- @@ -156,8 +156,8 @@ def argmin( Notes ----- - CuNumeric's parallel implementation may yield different results from NumPy - when the array contains NaN(s). + cuPyNumeric's parallel implementation may yield different results from + NumPy when the array contains NaN(s). Availability -------- diff --git a/cunumeric/_module/ssc_sorting.py b/cupynumeric/_module/ssc_sorting.py similarity index 98% rename from cunumeric/_module/ssc_sorting.py rename to cupynumeric/_module/ssc_sorting.py index 1ee86e0d02..4f32d0194f 100644 --- a/cunumeric/_module/ssc_sorting.py +++ b/cupynumeric/_module/ssc_sorting.py @@ -219,7 +219,7 @@ def argpartition( Notes ----- - The current implementation falls back to `cunumeric.argsort`. + The current implementation falls back to `cupynumeric.argsort`. See Also -------- @@ -274,7 +274,7 @@ def partition( Notes ----- - The current implementation falls back to `cunumeric.sort`. + The current implementation falls back to `cupynumeric.sort`. See Also -------- diff --git a/cunumeric/_module/stats_avgs_vars.py b/cupynumeric/_module/stats_avgs_vars.py similarity index 100% rename from cunumeric/_module/stats_avgs_vars.py rename to cupynumeric/_module/stats_avgs_vars.py diff --git a/cunumeric/_module/stats_correlating.py b/cupynumeric/_module/stats_correlating.py similarity index 100% rename from cunumeric/_module/stats_correlating.py rename to cupynumeric/_module/stats_correlating.py diff --git a/cunumeric/_module/stats_histograms.py b/cupynumeric/_module/stats_histograms.py similarity index 99% rename from cunumeric/_module/stats_histograms.py rename to cupynumeric/_module/stats_histograms.py index d6397760f2..05ab4e9289 100644 --- a/cunumeric/_module/stats_histograms.py +++ b/cupynumeric/_module/stats_histograms.py @@ -64,7 +64,7 @@ def bincount( ------- out : ndarray[int] The result of binning the input array. - The length of `out` is equal to ``cunumeric.amax(x)+1``. + The length of `out` is equal to ``cupynumeric.amax(x)+1``. Raises ------ diff --git a/cunumeric/_module/stats_order.py b/cupynumeric/_module/stats_order.py similarity index 99% rename from cunumeric/_module/stats_order.py rename to cupynumeric/_module/stats_order.py index 7c70424761..7d7564a3df 100644 --- a/cunumeric/_module/stats_order.py +++ b/cupynumeric/_module/stats_order.py @@ -720,7 +720,7 @@ def nanquantile_impl( assert qs_all[qindex].shape == remaining_shape # TODO(aschaffer): Vectorize this operation, see - # github.com/nv-legate/cunumeric/pull/1121#discussion_r1484731763 + # github.com/nv-legate/cupynumeric/pull/1121#discussion_r1484731763 gamma = None for aindex, n in np.ndenumerate(non_nan_counts): # TODO (2024-08): `n` should be an integral type, but wasn't: diff --git a/cunumeric/_module/window.py b/cupynumeric/_module/window.py similarity index 100% rename from cunumeric/_module/window.py rename to cupynumeric/_module/window.py diff --git a/cunumeric/_sphinxext/__init__.py b/cupynumeric/_sphinxext/__init__.py similarity index 100% rename from cunumeric/_sphinxext/__init__.py rename to cupynumeric/_sphinxext/__init__.py diff --git a/cunumeric/_sphinxext/_comparison_config.py b/cupynumeric/_sphinxext/_comparison_config.py similarity index 100% rename from cunumeric/_sphinxext/_comparison_config.py rename to cupynumeric/_sphinxext/_comparison_config.py diff --git a/cunumeric/_sphinxext/_comparison_util.py b/cupynumeric/_sphinxext/_comparison_util.py similarity index 95% rename from cunumeric/_sphinxext/_comparison_util.py rename to cupynumeric/_sphinxext/_comparison_util.py index ddd9bab2b4..a76d1cf676 100644 --- a/cunumeric/_sphinxext/_comparison_util.py +++ b/cupynumeric/_sphinxext/_comparison_util.py @@ -66,7 +66,7 @@ def _lgref(name: str, obj: Any, implemented: bool) -> str: if isinstance(obj, ModuleType): full_name = f"{obj.__name__}.{name}" else: - full_name = f"cunumeric.{obj.__name__}.{name}" + full_name = f"cupynumeric.{obj.__name__}.{name}" role = "meth" if "ndarray" in full_name else "obj" @@ -109,12 +109,12 @@ def get_item(name: str, np_obj: Any, lg_obj: Any) -> ItemDetail: def get_namespaces(attr: str | None) -> tuple[Any, Any]: import numpy - import cunumeric + import cupynumeric if attr is None: - return numpy, cunumeric + return numpy, cupynumeric - return getattr(numpy, attr), getattr(cunumeric, attr) + return getattr(numpy, attr), getattr(cupynumeric, attr) def generate_section(config: SectionConfig) -> SectionDetail: diff --git a/cunumeric/_sphinxext/_cunumeric_directive.py b/cupynumeric/_sphinxext/_cupynumeric_directive.py similarity index 96% rename from cunumeric/_sphinxext/_cunumeric_directive.py rename to cupynumeric/_sphinxext/_cupynumeric_directive.py index 62b7c9672d..593d25b241 100644 --- a/cunumeric/_sphinxext/_cunumeric_directive.py +++ b/cupynumeric/_sphinxext/_cupynumeric_directive.py @@ -20,7 +20,7 @@ from sphinx.util.nodes import nested_parse_with_titles -class CunumericDirective(SphinxDirective): +class CupynumericDirective(SphinxDirective): def parse(self, rst_text: str, annotation: str) -> list[nodes.Node]: result = StringList() for line in rst_text.split("\n"): diff --git a/cunumeric/_sphinxext/_templates.py b/cupynumeric/_sphinxext/_templates.py similarity index 100% rename from cunumeric/_sphinxext/_templates.py rename to cupynumeric/_sphinxext/_templates.py diff --git a/cunumeric/_sphinxext/_templates/comparison_table.rst b/cupynumeric/_sphinxext/_templates/comparison_table.rst similarity index 69% rename from cunumeric/_sphinxext/_templates/comparison_table.rst rename to cupynumeric/_sphinxext/_templates/comparison_table.rst index 3a4211100d..55d1d583f3 100644 --- a/cunumeric/_sphinxext/_templates/comparison_table.rst +++ b/cupynumeric/_sphinxext/_templates/comparison_table.rst @@ -3,13 +3,13 @@ {{ section.title }} {{ "~" * section.title|length }} -.. currentmodule:: cunumeric +.. currentmodule:: cupynumeric .. autosummary:: :toctree: generated/ .. csv-table:: - :header: NumPy, cunumeric, single-GPU/CPU, multi-GPU/CPU + :header: NumPy, cupynumeric, single-GPU/CPU, multi-GPU/CPU {% for item in section.items -%} {{ item.np_ref }}, {{ item.lg_ref }}, {{ item.single }}, {{ item.multi }} @@ -19,6 +19,6 @@ Number of NumPy functions: {{ section.np_count }} -Number of functions covered by cunumeric: {{ section.lg_count }} +Number of functions covered by cupynumeric: {{ section.lg_count }} {% endfor %} \ No newline at end of file diff --git a/cunumeric/_sphinxext/comparison_table.py b/cupynumeric/_sphinxext/comparison_table.py similarity index 94% rename from cunumeric/_sphinxext/comparison_table.py rename to cupynumeric/_sphinxext/comparison_table.py index a00d4bca7a..baa62a53d8 100644 --- a/cunumeric/_sphinxext/comparison_table.py +++ b/cupynumeric/_sphinxext/comparison_table.py @@ -22,13 +22,13 @@ from . import PARALLEL_SAFE, SphinxParallelSpec from ._comparison_config import GROUPED_CONFIGS, NUMPY_CONFIGS from ._comparison_util import generate_section -from ._cunumeric_directive import CunumericDirective +from ._cupynumeric_directive import CupynumericDirective from ._templates import COMPARISON_TABLE log = getLogger(__name__) -class ComparisonTable(CunumericDirective): +class ComparisonTable(CupynumericDirective): has_content = False required_arguments = 0 optional_arguments = 1 diff --git a/cunumeric/_sphinxext/implemented_index.py b/cupynumeric/_sphinxext/implemented_index.py similarity index 90% rename from cunumeric/_sphinxext/implemented_index.py rename to cupynumeric/_sphinxext/implemented_index.py index 175e12d693..f0e9598bc7 100644 --- a/cunumeric/_sphinxext/implemented_index.py +++ b/cupynumeric/_sphinxext/implemented_index.py @@ -20,11 +20,11 @@ from sphinx.application import Sphinx from sphinx.util.logging import getLogger -import cunumeric as cn +import cupynumeric as cn from .._utils.coverage import is_implemented from . import PARALLEL_SAFE, SphinxParallelSpec -from ._cunumeric_directive import CunumericDirective +from ._cupynumeric_directive import CupynumericDirective log = getLogger(__name__) @@ -45,7 +45,7 @@ def _filter(x: Any) -> bool: ) -class ImplementedIndex(CunumericDirective): +class ImplementedIndex(CupynumericDirective): has_content = False required_arguments = 0 optional_arguments = 0 @@ -59,7 +59,7 @@ def run(self) -> list[nodes.Node]: if _filter(x) ] refs += [ - f"* :obj:`cunumeric.ndarray.{x.__name__}`" + f"* :obj:`cupynumeric.ndarray.{x.__name__}`" for x in cn.ndarray.__dict__.values() if _filter(x) ] diff --git a/cunumeric/_sphinxext/missing_refs.py b/cupynumeric/_sphinxext/missing_refs.py similarity index 70% rename from cunumeric/_sphinxext/missing_refs.py rename to cupynumeric/_sphinxext/missing_refs.py index bd55cb5d41..99938b80dd 100644 --- a/cunumeric/_sphinxext/missing_refs.py +++ b/cupynumeric/_sphinxext/missing_refs.py @@ -28,25 +28,25 @@ log = getLogger(__name__) SKIP = ( - "cunumeric.cast", - "cunumeric.ndarray.__array_function__", - "cunumeric.ndarray.__array_ufunc__", - "cunumeric.ndarray.__format__", - "cunumeric.ndarray.__hash__", - "cunumeric.ndarray.__iter__", - "cunumeric.ndarray.__radd__", - "cunumeric.ndarray.__rand__", - "cunumeric.ndarray.__rdivmod__", - "cunumeric.ndarray.__reduce_ex__", - "cunumeric.ndarray.__rfloordiv__", - "cunumeric.ndarray.__rmod__", - "cunumeric.ndarray.__rmul__", - "cunumeric.ndarray.__ror__", - "cunumeric.ndarray.__rpow__", - "cunumeric.ndarray.__rsub__", - "cunumeric.ndarray.__rtruediv__", - "cunumeric.ndarray.__rxor__", - "cunumeric.ndarray.__sizeof__", + "cupynumeric.cast", + "cupynumeric.ndarray.__array_function__", + "cupynumeric.ndarray.__array_ufunc__", + "cupynumeric.ndarray.__format__", + "cupynumeric.ndarray.__hash__", + "cupynumeric.ndarray.__iter__", + "cupynumeric.ndarray.__radd__", + "cupynumeric.ndarray.__rand__", + "cupynumeric.ndarray.__rdivmod__", + "cupynumeric.ndarray.__reduce_ex__", + "cupynumeric.ndarray.__rfloordiv__", + "cupynumeric.ndarray.__rmod__", + "cupynumeric.ndarray.__rmul__", + "cupynumeric.ndarray.__ror__", + "cupynumeric.ndarray.__rpow__", + "cupynumeric.ndarray.__rsub__", + "cupynumeric.ndarray.__rtruediv__", + "cupynumeric.ndarray.__rxor__", + "cupynumeric.ndarray.__sizeof__", ) MISSING: list[tuple[str, str]] = [] @@ -62,7 +62,7 @@ def run(self, **kwargs: Any) -> None: def _check_target(self, node: Any) -> None: target = node["reftarget"] - if not target.startswith("cunumeric.") or target in SKIP: + if not target.startswith("cupynumeric.") or target in SKIP: return domain = self.env.domains[node["refdomain"]] @@ -85,7 +85,7 @@ def _check_target(self, node: Any) -> None: if uri is None: loc = get_node_location(node) log.warning( - f"Cunumeric reference missing a target: {loc}: {target}", + f"cuPyNumeric reference missing a target: {loc}: {target}", type="ref", ) diff --git a/cunumeric/_sphinxext/ufunc_formatter.py b/cupynumeric/_sphinxext/ufunc_formatter.py similarity index 97% rename from cunumeric/_sphinxext/ufunc_formatter.py rename to cupynumeric/_sphinxext/ufunc_formatter.py index 05cac694e6..6f574d7541 100644 --- a/cunumeric/_sphinxext/ufunc_formatter.py +++ b/cupynumeric/_sphinxext/ufunc_formatter.py @@ -19,7 +19,7 @@ from sphinx.application import Sphinx from sphinx.ext.autodoc import FunctionDocumenter -from cunumeric import ufunc +from cupynumeric import ufunc from . import PARALLEL_SAFE, SphinxParallelSpec diff --git a/cunumeric/_thunk/__init__.py b/cupynumeric/_thunk/__init__.py similarity index 100% rename from cunumeric/_thunk/__init__.py rename to cupynumeric/_thunk/__init__.py diff --git a/cunumeric/_thunk/_sort.py b/cupynumeric/_thunk/_sort.py similarity index 98% rename from cunumeric/_thunk/_sort.py rename to cupynumeric/_thunk/_sort.py index b97a8eba0b..82ab738479 100644 --- a/cunumeric/_thunk/_sort.py +++ b/cupynumeric/_thunk/_sort.py @@ -19,7 +19,7 @@ from legate.core import get_legate_runtime, types as ty from .._utils import is_np2 -from ..config import CuNumericOpCode +from ..config import CuPyNumericOpCode from ..runtime import runtime if is_np2: @@ -92,7 +92,7 @@ def sort_task( ) -> None: legate_runtime = get_legate_runtime() task = legate_runtime.create_auto_task( - output.library, CuNumericOpCode.SORT + output.library, CuPyNumericOpCode.SORT ) uses_unbound_output = runtime.num_procs > 1 and input.ndim == 1 diff --git a/cunumeric/_thunk/deferred.py b/cupynumeric/_thunk/deferred.py similarity index 97% rename from cunumeric/_thunk/deferred.py rename to cupynumeric/_thunk/deferred.py index 3bd6c4826f..b94a16aeda 100644 --- a/cunumeric/_thunk/deferred.py +++ b/cupynumeric/_thunk/deferred.py @@ -63,7 +63,7 @@ Bitorder, ConvertCode, ConvolveMethod, - CuNumericOpCode, + CuPyNumericOpCode, RandGenCode, UnaryOpCode, UnaryRedCode, @@ -431,7 +431,7 @@ def _zip_indices( # call ZIP function to combine index arrays into a singe array task = legate_runtime.create_auto_task( - self.library, CuNumericOpCode.ZIP + self.library, CuPyNumericOpCode.ZIP ) task.throws_exception(IndexError) p_out = task.add_output(output_arr.base) @@ -648,7 +648,7 @@ def _advanced_indexing_with_boolean_array( task = legate_runtime.create_auto_task( self.library, - CuNumericOpCode.ADVANCED_INDEXING, + CuPyNumericOpCode.ADVANCED_INDEXING, ) task.add_output(out.base) p_rhs = task.add_input(rhs.base) @@ -933,7 +933,7 @@ def get_item(self, key: Any) -> NumPyThunk: ) task = legate_runtime.create_auto_task( - self.library, CuNumericOpCode.READ + self.library, CuPyNumericOpCode.READ ) task.add_input(input.base) task.add_output(result.base) # type: ignore @@ -1004,7 +1004,7 @@ def set_item(self, key: Any, rhs: Any) -> None: assert rhs.size == 1 task = legate_runtime.create_auto_task( - self.library, CuNumericOpCode.WRITE + self.library, CuPyNumericOpCode.WRITE ) # Since we pass the view with write discard privilege, # we should make sure that the mapper either creates a fresh @@ -1017,7 +1017,7 @@ def set_item(self, key: Any, rhs: Any) -> None: # In Python, any inplace update of form arr[key] op= value # goes through three steps: 1) __getitem__ fetching the object # for the key, 2) __iop__ for the update, and 3) __setitem__ - # to set the result back. In cuNumeric, the object we + # to set the result back. In cuPyNumeric, the object we # return in step (1) is actually a subview to the array arr # through which we make updates in place, so after step (2) is # done, the effect of inplace update is already reflected @@ -1042,7 +1042,7 @@ def reshape(self, newshape: NdShape, order: OrderType) -> NumPyThunk: if order != "C": # If we don't have a transform then we need to make a copy runtime.warn( - "cuNumeric has not implemented reshape using Fortran-like " + "cuPyNumeric has not implemented reshape using Fortran-like " "index order and is falling back to canonical numpy. You may " "notice significantly decreased performance for this " "function call.", @@ -1271,7 +1271,7 @@ def convert( if warn: runtime.warn( - "cuNumeric performing implicit type conversion from " + "cuPyNumeric performing implicit type conversion from " + str(rhs_array.dtype) + " to " + str(lhs_array.dtype), @@ -1282,7 +1282,7 @@ def convert( rhs = rhs_array.base task = legate_runtime.create_auto_task( - self.library, CuNumericOpCode.CONVERT + self.library, CuPyNumericOpCode.CONVERT ) p_lhs = task.add_output(lhs) p_rhs = task.add_input(rhs) @@ -1304,7 +1304,7 @@ def convolve( runtime.warn(f"the method {method} is ignored on CPUs") task = legate_runtime.create_auto_task( - self.library, CuNumericOpCode.CONVOLVE + self.library, CuPyNumericOpCode.CONVOLVE ) offsets = tuple((ext + 1) // 2 for ext in filter.shape) @@ -1345,7 +1345,7 @@ def fft( output = lhs.base task = legate_runtime.create_auto_task( - self.library, CuNumericOpCode.FFT + self.library, CuPyNumericOpCode.FFT ) p_output = task.add_output(output) @@ -1375,7 +1375,7 @@ def fft( task.execute() - # Fill the cuNumeric array with the value in the numpy array + # Fill the cuPyNumeric array with the value in the numpy array def _fill(self, value: LogicalStore | Scalar) -> None: assert self.base is not None @@ -1391,7 +1391,7 @@ def _fill(self, value: LogicalStore | Scalar) -> None: # If this is a fill for an arg value, make sure to pass # the value dtype so that we get it packed correctly task = legate_runtime.create_auto_task( - self.library, CuNumericOpCode.FILL + self.library, CuPyNumericOpCode.FILL ) task.add_output(self.base) task.add_input(value) @@ -1520,7 +1520,7 @@ def contract( if blas_op == BlasOperation.VV: # Vector dot product task = legate_runtime.create_auto_task( - self.library, CuNumericOpCode.DOT + self.library, CuPyNumericOpCode.DOT ) task.add_reduction(lhs, ReductionOpKind.ADD) p_rhs1 = task.add_input(rhs1) @@ -1545,7 +1545,7 @@ def contract( lhs = lhs.promote(1, n) task = legate_runtime.create_auto_task( - self.library, CuNumericOpCode.MATVECMUL + self.library, CuPyNumericOpCode.MATVECMUL ) p_lhs = task.add_reduction(lhs, ReductionOpKind.ADD) p_rhs1 = task.add_input(rhs1) @@ -1654,7 +1654,7 @@ def run_matmul_for_batch( i: int, ) -> None: manual_task = legate_runtime.create_manual_task( - self.library, CuNumericOpCode.MATMUL, color_shape + self.library, CuPyNumericOpCode.MATMUL, color_shape ) manual_task.add_output(tiled_lhs) @@ -1726,7 +1726,7 @@ def add_mode( # Prepare the launch task = legate_runtime.create_auto_task( - self.library, CuNumericOpCode.CONTRACT + self.library, CuPyNumericOpCode.CONTRACT ) p_lhs = task.add_reduction(lhs, ReductionOpKind.ADD) p_rhs1 = task.add_input(rhs1) @@ -1752,7 +1752,7 @@ def choose(self, rhs: Any, *args: Any) -> None: ch_tuple = tuple(c._broadcast(tuple(out_arr.shape)) for c in ch_def) task = legate_runtime.create_auto_task( - self.library, CuNumericOpCode.CHOOSE + self.library, CuPyNumericOpCode.CHOOSE ) p_out = task.add_output(out_arr) p_ind = task.add_input(index) @@ -1776,7 +1776,7 @@ def select( ) task = legate_runtime.create_auto_task( - self.library, CuNumericOpCode.SELECT + self.library, CuPyNumericOpCode.SELECT ) out_arr = self.base task.add_output(out_arr) @@ -1841,7 +1841,7 @@ def _diag_helper( diag = diag.promote(0, matrix.shape[0]) task = legate_runtime.create_auto_task( - self.library, CuNumericOpCode.DIAG + self.library, CuPyNumericOpCode.DIAG ) if extract: @@ -1895,7 +1895,7 @@ def put(self, indices: Any, values: Any, check_bounds: bool) -> None: shape = self_tmp.shape task = legate_runtime.create_auto_task( - self.library, CuNumericOpCode.WRAP + self.library, CuPyNumericOpCode.WRAP ) p_indirect = task.add_output(indirect.base) task.add_scalar_arg(shape, (ty.int64,)) @@ -1922,7 +1922,7 @@ def putmask(self, mask: Any, values: Any) -> None: else: values_new = values.base task = legate_runtime.create_auto_task( - self.library, CuNumericOpCode.PUTMASK + self.library, CuPyNumericOpCode.PUTMASK ) p_self = task.add_input(self.base) p_mask = task.add_input(mask.base) @@ -1947,7 +1947,7 @@ def eye(self, k: int) -> None: # tells the runtime that it can throw away the previous contents of the # entire region. task = legate_runtime.create_auto_task( - self.library, CuNumericOpCode.EYE + self.library, CuPyNumericOpCode.EYE ) task.add_input(self.base) task.add_output(self.base) @@ -1964,7 +1964,7 @@ def arange(self, start: float, stop: float, step: float) -> None: return task = legate_runtime.create_auto_task( - self.library, CuNumericOpCode.ARANGE + self.library, CuPyNumericOpCode.ARANGE ) task.add_output(self.base) task.add_scalar_arg(start, self.base.type) @@ -1984,7 +1984,7 @@ def tile(self, rhs: Any, reps: Any | Sequence[int]) -> None: return task = legate_runtime.create_auto_task( - self.library, CuNumericOpCode.TILE + self.library, CuPyNumericOpCode.TILE ) task.add_output(self.base) @@ -2008,7 +2008,7 @@ def trilu(self, rhs: Any, k: int, lower: bool) -> None: rhs = rhs._broadcast(lhs.shape) task = legate_runtime.create_auto_task( - self.library, CuNumericOpCode.TRILU + self.library, CuPyNumericOpCode.TRILU ) p_lhs = task.add_output(lhs) @@ -2025,7 +2025,7 @@ def repeat( self, repeats: Any, axis: int, scalar_repeats: bool ) -> DeferredArray: task = legate_runtime.create_auto_task( - self.library, CuNumericOpCode.REPEAT + self.library, CuPyNumericOpCode.REPEAT ) if scalar_repeats: out_shape = tuple( @@ -2080,7 +2080,7 @@ def flip(self, rhs: Any, axes: int | tuple[int, ...] | None) -> None: axes = normalize_axis_tuple(axes, self.ndim) task = legate_runtime.create_auto_task( - self.library, CuNumericOpCode.FLIP + self.library, CuPyNumericOpCode.FLIP ) p_out = task.add_output(output) p_in = task.add_input(input) @@ -2107,7 +2107,7 @@ def bincount(self, rhs: Any, weights: NumPyThunk | None = None) -> None: dst_array.fill(np.array(0, dst_array.dtype)) task = legate_runtime.create_auto_task( - self.library, CuNumericOpCode.BINCOUNT + self.library, CuPyNumericOpCode.BINCOUNT ) p_dst = task.add_reduction(dst_array.base, ReductionOpKind.ADD) p_src = task.add_input(src_array.base) @@ -2125,7 +2125,7 @@ def nonzero(self) -> tuple[NumPyThunk, ...]: ) task = legate_runtime.create_auto_task( - self.library, CuNumericOpCode.NONZERO + self.library, CuPyNumericOpCode.NONZERO ) p_self = task.add_input(self.base) @@ -2146,7 +2146,7 @@ def bitgenerator_random_raw( flags: int, ) -> None: task = legate_runtime.create_auto_task( - self.library, CuNumericOpCode.BITGENERATOR + self.library, CuPyNumericOpCode.BITGENERATOR ) task.add_output(self.base) @@ -2174,7 +2174,7 @@ def bitgenerator_distribution( doubleparams: tuple[float, ...], ) -> None: task = legate_runtime.create_auto_task( - self.library, CuNumericOpCode.BITGENERATOR + self.library, CuPyNumericOpCode.BITGENERATOR ) task.add_output(self.base) @@ -3136,7 +3136,7 @@ def bitgenerator_negative_binomial( def random(self, gen_code: Any, args: tuple[Scalar, ...] = ()) -> None: task = legate_runtime.create_auto_task( - self.library, CuNumericOpCode.RAND + self.library, CuPyNumericOpCode.RAND ) task.add_output(self.base) @@ -3182,7 +3182,7 @@ def unary_op( with Annotation({"OpCode": op.name}): task = legate_runtime.create_auto_task( - self.library, CuNumericOpCode.UNARY_OP + self.library, CuPyNumericOpCode.UNARY_OP ) p_lhs = task.add_output(lhs) p_rhs = task.add_input(rhs) @@ -3254,7 +3254,7 @@ def unary_reduction( with Annotation({"OpCode": op.name, "ArgRed?": str(argred)}): task = legate_runtime.create_auto_task( - self.library, CuNumericOpCode.SCALAR_UNARY_RED + self.library, CuPyNumericOpCode.SCALAR_UNARY_RED ) task.add_reduction(lhs, _UNARY_RED_TO_REDUCTION_OPS[op]) @@ -3300,7 +3300,7 @@ def unary_reduction( with Annotation({"OpCode": op.name, "ArgRed?": str(argred)}): task = legate_runtime.create_auto_task( - self.library, CuNumericOpCode.UNARY_RED + self.library, CuPyNumericOpCode.UNARY_RED ) p_rhs = task.add_input(rhs_array.base) @@ -3357,7 +3357,7 @@ def binary_op( with Annotation({"OpCode": op_code.name}): # Populate the Legate launcher task = legate_runtime.create_auto_task( - self.library, CuNumericOpCode.BINARY_OP + self.library, CuPyNumericOpCode.BINARY_OP ) p_lhs = task.add_output(lhs) p_rhs1 = task.add_input(rhs1) @@ -3397,7 +3397,7 @@ def binary_reduction( redop = ReductionOpKind.MUL self.fill(np.array(True)) task = legate_runtime.create_auto_task( - self.library, CuNumericOpCode.BINARY_RED + self.library, CuPyNumericOpCode.BINARY_RED ) task.add_reduction(lhs, redop) p_rhs1 = task.add_input(rhs1) @@ -3419,7 +3419,7 @@ def where(self, src1: Any, src2: Any, src3: Any) -> None: # Populate the Legate launcher task = legate_runtime.create_auto_task( - self.library, CuNumericOpCode.WHERE + self.library, CuPyNumericOpCode.WHERE ) p_lhs = task.add_output(lhs) p_rhs1 = task.add_input(rhs1) @@ -3436,7 +3436,7 @@ def argwhere(self) -> NumPyThunk: result = runtime.create_unbound_thunk(ty.int64, ndim=2) task = legate_runtime.create_auto_task( - self.library, CuNumericOpCode.ARGWHERE + self.library, CuPyNumericOpCode.ARGWHERE ) task.add_output(result.base) @@ -3501,7 +3501,7 @@ def scan( output = input task = legate_runtime.create_auto_task( - self.library, CuNumericOpCode.SCAN_LOCAL + self.library, CuPyNumericOpCode.SCAN_LOCAL ) p_out = task.add_output(output.base) p_in = task.add_input(input.base) @@ -3517,7 +3517,7 @@ def scan( # NOTE: Each node will do a sum up to its index, alternatively could # do one centralized scan and broadcast (slightly less redundant work) task = legate_runtime.create_auto_task( - self.library, CuNumericOpCode.SCAN_GLOBAL + self.library, CuPyNumericOpCode.SCAN_GLOBAL ) task.add_input(output.base) p_temp = task.add_input(temp.base) @@ -3538,7 +3538,7 @@ def unique(self) -> NumPyThunk: result = runtime.create_unbound_thunk(self.base.type) task = legate_runtime.create_auto_task( - self.library, CuNumericOpCode.UNIQUE + self.library, CuPyNumericOpCode.UNIQUE ) task.add_output(result.base) @@ -3551,7 +3551,7 @@ def unique(self) -> NumPyThunk: if runtime.num_gpus == 0 and runtime.num_procs > 1: result.base = legate_runtime.tree_reduce( - self.library, CuNumericOpCode.UNIQUE_REDUCE, result.base + self.library, CuPyNumericOpCode.UNIQUE_REDUCE, result.base ) return result @@ -3559,7 +3559,7 @@ def unique(self) -> NumPyThunk: @auto_convert("rhs", "v") def searchsorted(self, rhs: Any, v: Any, side: SortSide = "left") -> None: task = legate_runtime.create_auto_task( - self.library, CuNumericOpCode.SEARCHSORTED + self.library, CuPyNumericOpCode.SEARCHSORTED ) is_left = side == "left" @@ -3599,7 +3599,7 @@ def sort( if order is not None: raise NotImplementedError( - "cuNumeric does not support sorting with 'order' as " + "cuPyNumeric does not support sorting with 'order' as " "ndarray only supports numeric values" ) if axis is not None and (axis >= rhs.ndim or axis < -rhs.ndim): @@ -3619,7 +3619,7 @@ def partition( ) -> None: if order is not None: raise NotImplementedError( - "cuNumeric does not support partitioning with 'order' as " + "cuPyNumeric does not support partitioning with 'order' as " "ndarray only supports numeric values" ) if axis is not None and (axis >= rhs.ndim or axis < -rhs.ndim): @@ -3630,7 +3630,7 @@ def partition( def create_window(self, op_code: WindowOpCode, M: int, *args: Any) -> None: task = legate_runtime.create_auto_task( - self.library, CuNumericOpCode.WINDOW + self.library, CuPyNumericOpCode.WINDOW ) task.add_output(self.base) task.add_scalar_arg(op_code, ty.int32) @@ -3643,7 +3643,7 @@ def create_window(self, op_code: WindowOpCode, M: int, *args: Any) -> None: def packbits(self, src: Any, axis: int | None, bitorder: BitOrder) -> None: bitorder_code = getattr(Bitorder, bitorder.upper()) task = legate_runtime.create_auto_task( - self.library, CuNumericOpCode.PACKBITS + self.library, CuPyNumericOpCode.PACKBITS ) p_out = task.declare_partition() p_in = task.declare_partition() @@ -3661,7 +3661,7 @@ def unpackbits( ) -> None: bitorder_code = getattr(Bitorder, bitorder.upper()) task = legate_runtime.create_auto_task( - self.library, CuNumericOpCode.UNPACKBITS + self.library, CuPyNumericOpCode.UNPACKBITS ) p_out = task.declare_partition() p_in = task.declare_partition() @@ -3694,7 +3694,7 @@ def _wrap(self, src: Any, new_len: int) -> None: ) task = legate_runtime.create_auto_task( - self.library, CuNumericOpCode.WRAP + self.library, CuPyNumericOpCode.WRAP ) task.add_output(indirect.base) task.add_scalar_arg(src.shape, (ty.int64,)) @@ -3722,7 +3722,7 @@ def histogram(self, src: Any, bins: Any, weights: Any) -> None: dst_array.fill(np.array(0, dst_array.dtype)) task = legate_runtime.create_auto_task( - self.library, CuNumericOpCode.HISTOGRAM + self.library, CuPyNumericOpCode.HISTOGRAM ) p_dst = task.add_reduction(dst_array.base, ReductionOpKind.ADD) p_src = task.add_input(src_array.base) diff --git a/cunumeric/_thunk/eager.py b/cupynumeric/_thunk/eager.py similarity index 100% rename from cunumeric/_thunk/eager.py rename to cupynumeric/_thunk/eager.py diff --git a/cunumeric/_thunk/thunk.py b/cupynumeric/_thunk/thunk.py similarity index 99% rename from cunumeric/_thunk/thunk.py rename to cupynumeric/_thunk/thunk.py index 83812001ff..37f1578819 100644 --- a/cunumeric/_thunk/thunk.py +++ b/cupynumeric/_thunk/thunk.py @@ -49,7 +49,7 @@ class NumPyThunk(ABC): """This is the base class for NumPy computations. It has methods for all the kinds of computations and operations that can be done - on cuNumeric ndarrays. + on cuPyNumeric ndarrays. :meta private: """ diff --git a/cunumeric/_ufunc/__init__.py b/cupynumeric/_ufunc/__init__.py similarity index 100% rename from cunumeric/_ufunc/__init__.py rename to cupynumeric/_ufunc/__init__.py diff --git a/cunumeric/_ufunc/bit_twiddling.py b/cupynumeric/_ufunc/bit_twiddling.py similarity index 100% rename from cunumeric/_ufunc/bit_twiddling.py rename to cupynumeric/_ufunc/bit_twiddling.py diff --git a/cunumeric/_ufunc/comparison.py b/cupynumeric/_ufunc/comparison.py similarity index 97% rename from cunumeric/_ufunc/comparison.py rename to cupynumeric/_ufunc/comparison.py index 089aa7f0fe..148854fad0 100644 --- a/cunumeric/_ufunc/comparison.py +++ b/cupynumeric/_ufunc/comparison.py @@ -18,7 +18,7 @@ import numpy as np -from .._array.util import convert_to_cunumeric_ndarray +from .._array.util import convert_to_cupynumeric_ndarray from ..config import BinaryOpCode, UnaryOpCode, UnaryRedCode from .ufunc import ( all_dtypes, @@ -74,7 +74,7 @@ def _post_resolution_check( if truthiness is not None: # Replace with an always-true/always-false operation - arr_x = convert_to_cunumeric_ndarray( + arr_x = convert_to_cupynumeric_ndarray( np.array(iinfo.min, dtype=arr_x.dtype) ) op_code = ( @@ -98,7 +98,7 @@ def _post_resolution_check( if truthiness is not None: # Replace with an always-true/always-false operation - arr_y = convert_to_cunumeric_ndarray( + arr_y = convert_to_cupynumeric_ndarray( np.array(iinfo.min, dtype=arr_y.dtype) ) op_code = ( diff --git a/cunumeric/_ufunc/floating.py b/cupynumeric/_ufunc/floating.py similarity index 100% rename from cunumeric/_ufunc/floating.py rename to cupynumeric/_ufunc/floating.py diff --git a/cunumeric/_ufunc/math.py b/cupynumeric/_ufunc/math.py similarity index 100% rename from cunumeric/_ufunc/math.py rename to cupynumeric/_ufunc/math.py diff --git a/cunumeric/_ufunc/trigonometric.py b/cupynumeric/_ufunc/trigonometric.py similarity index 100% rename from cunumeric/_ufunc/trigonometric.py rename to cupynumeric/_ufunc/trigonometric.py diff --git a/cunumeric/_ufunc/ufunc.py b/cupynumeric/_ufunc/ufunc.py similarity index 99% rename from cunumeric/_ufunc/ufunc.py rename to cupynumeric/_ufunc/ufunc.py index 74b4f8badf..552079d722 100644 --- a/cunumeric/_ufunc/ufunc.py +++ b/cupynumeric/_ufunc/ufunc.py @@ -23,7 +23,7 @@ from .._array.util import ( add_boilerplate, check_writeable, - convert_to_cunumeric_ndarray, + convert_to_cupynumeric_ndarray, ) from ..config import BinaryOpCode, UnaryOpCode, UnaryRedCode from ..types import NdShape @@ -322,7 +322,7 @@ def _maybe_cast_output(out: ndarray | None, result: ndarray) -> ndarray: return out @staticmethod - def _maybe_convert_output_to_cunumeric_ndarray( + def _maybe_convert_output_to_cupynumeric_ndarray( out: ndarray | npt.NDArray[Any] | None, ) -> ndarray | None: from .._array.array import ndarray @@ -332,7 +332,7 @@ def _maybe_convert_output_to_cunumeric_ndarray( if isinstance(out, ndarray): return out if isinstance(out, np.ndarray): - return convert_to_cunumeric_ndarray(out, share=True) + return convert_to_cupynumeric_ndarray(out, share=True) raise TypeError("return arrays must be of ArrayType") def _prepare_operands( @@ -354,7 +354,7 @@ def _prepare_operands( ) inputs = tuple( - convert_to_cunumeric_ndarray(arr) for arr in args[: self.nin] + convert_to_cupynumeric_ndarray(arr) for arr in args[: self.nin] ) if len(args) > self.nin: @@ -374,7 +374,7 @@ def _prepare_operands( computed_out = out outputs = tuple( - self._maybe_convert_output_to_cunumeric_ndarray(arr) + self._maybe_convert_output_to_cupynumeric_ndarray(arr) for arr in computed_out ) diff --git a/cunumeric/_utils/__init__.py b/cupynumeric/_utils/__init__.py similarity index 100% rename from cunumeric/_utils/__init__.py rename to cupynumeric/_utils/__init__.py diff --git a/cunumeric/_utils/array.py b/cupynumeric/_utils/array.py similarity index 96% rename from cunumeric/_utils/array.py rename to cupynumeric/_utils/array.py index 6e35735d30..eda134045e 100644 --- a/cunumeric/_utils/array.py +++ b/cupynumeric/_utils/array.py @@ -42,7 +42,7 @@ def is_supported_dtype(dtype: str | np.dtype[Any]) -> bool: """ - Whether a NumPy dtype is supported by cuNumeric + Whether a NumPy dtype is supported by cuPyNumeric Parameters ---------- @@ -60,7 +60,7 @@ def is_supported_dtype(dtype: str | np.dtype[Any]) -> bool: def to_core_type(dtype: str | np.dtype[Any]) -> ty.Type: core_dtype = SUPPORTED_DTYPES.get(np.dtype(dtype)) if core_dtype is None: - raise TypeError(f"cuNumeric does not support dtype={dtype}") + raise TypeError(f"cuPyNumeric does not support dtype={dtype}") return core_dtype diff --git a/cunumeric/_utils/coverage.py b/cupynumeric/_utils/coverage.py similarity index 94% rename from cunumeric/_utils/coverage.py rename to cupynumeric/_utils/coverage.py index af43a6b640..d0d0f0eba4 100644 --- a/cunumeric/_utils/coverage.py +++ b/cupynumeric/_utils/coverage.py @@ -31,7 +31,7 @@ __all__ = ("clone_module", "clone_class") FALLBACK_WARNING = ( - "cuNumeric has not implemented {what} " + "cuPyNumeric has not implemented {what} " + "and is falling back to canonical NumPy. " + "You may notice significantly decreased performance " + "for this function call." @@ -69,7 +69,7 @@ class CuWrapperMetadata: class CuWrapped(AnyCallable, Protocol): - _cunumeric: CuWrapperMetadata + _cupynumeric: CuWrapperMetadata __wrapped__: AnyCallable __name__: str __qualname__: str @@ -116,7 +116,7 @@ def wrapper(*args: Any, **kwargs: Any) -> Any: multi = "Multiple GPUs" in (getattr(func, "__doc__", None) or "") single = "Single GPU" in (getattr(func, "__doc__", None) or "") or multi - wrapper._cunumeric = CuWrapperMetadata( + wrapper._cupynumeric = CuWrapperMetadata( implemented=True, single=single, multi=multi ) @@ -141,7 +141,7 @@ def unimplemented( # all array-like arguments to `numpy.ndarray` through `__array__()` (taking # some care to skip the `__array_function__` dispatch logic, to avoid # infinite loops). However, it appears that this behavior is inconsistent - # in NumPy, so we will instead convert any `cunumeric.ndarray`s manually + # in NumPy, so we will instead convert any `cupynumeric.ndarray`s manually # before calling into NumPy. wrapper: CuWrapped @@ -179,13 +179,13 @@ def wrapper(*args: Any, **kwargs: Any) -> Any: return func(*args, **kwargs) wrapper.__doc__ = f""" - cuNumeric has not implemented this function, and will fall back to NumPy. + cuPyNumeric has not implemented this function, and will fall back to NumPy. See Also -------- {name} """ - wrapper._cunumeric = CuWrapperMetadata(implemented=False) + wrapper._cupynumeric = CuWrapperMetadata(implemented=False) return wrapper @@ -301,7 +301,7 @@ def clone_module( def should_wrap(obj: object) -> bool: # custom callables, e.g. cython used in np2, do not inherit anything. See - # https://github.com/nv-legate/cunumeric.internal/issues/179#issuecomment-2423813051 + # https://github.com/nv-legate/cupynumeric.internal/issues/179#issuecomment-2423813051 return ( callable(obj) and hasattr(obj, "__get__") @@ -364,12 +364,12 @@ def _clone_class(cls: type) -> type: def is_implemented(obj: Any) -> bool: - return hasattr(obj, "_cunumeric") and obj._cunumeric.implemented + return hasattr(obj, "_cupynumeric") and obj._cupynumeric.implemented def is_single(obj: Any) -> bool: - return hasattr(obj, "_cunumeric") and obj._cunumeric.single + return hasattr(obj, "_cupynumeric") and obj._cupynumeric.single def is_multi(obj: Any) -> bool: - return hasattr(obj, "_cunumeric") and obj._cunumeric.multi + return hasattr(obj, "_cupynumeric") and obj._cupynumeric.multi diff --git a/cunumeric/_utils/linalg.py b/cupynumeric/_utils/linalg.py similarity index 100% rename from cunumeric/_utils/linalg.py rename to cupynumeric/_utils/linalg.py diff --git a/cunumeric/_utils/stack.py b/cupynumeric/_utils/stack.py similarity index 91% rename from cunumeric/_utils/stack.py rename to cupynumeric/_utils/stack.py index 470cf77750..f5e714a3c6 100644 --- a/cunumeric/_utils/stack.py +++ b/cupynumeric/_utils/stack.py @@ -21,7 +21,7 @@ def find_last_user_stacklevel() -> int: stacklevel = 1 for frame, _ in traceback.walk_stack(None): - if not frame.f_globals["__name__"].startswith("cunumeric"): + if not frame.f_globals["__name__"].startswith("cupynumeric"): break stacklevel += 1 return stacklevel @@ -36,7 +36,7 @@ def find_last_user_frames(top_only: bool = True) -> str: if "__name__" not in last.f_globals: continue name = last.f_globals["__name__"] - if not any(name.startswith(pkg) for pkg in ("cunumeric", "legate")): + if not any(name.startswith(pkg) for pkg in ("cupynumeric", "legate")): break if top_only: diff --git a/cunumeric/_utils/structure.py b/cupynumeric/_utils/structure.py similarity index 100% rename from cunumeric/_utils/structure.py rename to cupynumeric/_utils/structure.py diff --git a/cunumeric/_version.py b/cupynumeric/_version.py similarity index 99% rename from cunumeric/_version.py rename to cupynumeric/_version.py index 7c006fdc15..9d05050897 100644 --- a/cunumeric/_version.py +++ b/cupynumeric/_version.py @@ -43,8 +43,8 @@ def get_config(): cfg.VCS = "git" cfg.style = "pep440" cfg.tag_prefix = "v" - cfg.parentdir_prefix = "cunumeric-" - cfg.versionfile_source = "cunumeric/_version.py" + cfg.parentdir_prefix = "cupynumeric-" + cfg.versionfile_source = "cupynumeric/_version.py" cfg.verbose = False return cfg diff --git a/cupynumeric/config.py b/cupynumeric/config.py new file mode 100644 index 0000000000..b3cac0573b --- /dev/null +++ b/cupynumeric/config.py @@ -0,0 +1,835 @@ +# Copyright 2024 NVIDIA Corporation +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +from __future__ import annotations + +import os +import platform +from abc import abstractmethod +from ctypes import CDLL, RTLD_GLOBAL +from enum import IntEnum, unique +from typing import TYPE_CHECKING, Any, cast + +import cffi # type: ignore +import numpy as np + +if TYPE_CHECKING: + import numpy.typing as npt + + +class _ReductionOpIds: + argmax_redop_id: int + argmin_redop_id: int + + +class _CupynumericSharedLib: + CUPYNUMERIC_ADVANCED_INDEXING: int + CUPYNUMERIC_ARANGE: int + CUPYNUMERIC_ARGWHERE: int + CUPYNUMERIC_BATCHED_CHOLESKY: int + CUPYNUMERIC_BINARY_OP: int + CUPYNUMERIC_BINARY_RED: int + CUPYNUMERIC_BINCOUNT: int + CUPYNUMERIC_BINOP_ADD: int + CUPYNUMERIC_BINOP_ARCTAN2: int + CUPYNUMERIC_BINOP_BITWISE_AND: int + CUPYNUMERIC_BINOP_BITWISE_OR: int + CUPYNUMERIC_BINOP_BITWISE_XOR: int + CUPYNUMERIC_BINOP_COPYSIGN: int + CUPYNUMERIC_BINOP_DIVIDE: int + CUPYNUMERIC_BINOP_EQUAL: int + CUPYNUMERIC_BINOP_FLOAT_POWER: int + CUPYNUMERIC_BINOP_FLOOR_DIVIDE: int + CUPYNUMERIC_BINOP_FMOD: int + CUPYNUMERIC_BINOP_GCD: int + CUPYNUMERIC_BINOP_GREATER: int + CUPYNUMERIC_BINOP_GREATER_EQUAL: int + CUPYNUMERIC_BINOP_HYPOT: int + CUPYNUMERIC_BINOP_ISCLOSE: int + CUPYNUMERIC_BINOP_LCM: int + CUPYNUMERIC_BINOP_LDEXP: int + CUPYNUMERIC_BINOP_LEFT_SHIFT: int + CUPYNUMERIC_BINOP_LESS: int + CUPYNUMERIC_BINOP_LESS_EQUAL: int + CUPYNUMERIC_BINOP_LOGADDEXP2: int + CUPYNUMERIC_BINOP_LOGADDEXP: int + CUPYNUMERIC_BINOP_LOGICAL_AND: int + CUPYNUMERIC_BINOP_LOGICAL_OR: int + CUPYNUMERIC_BINOP_LOGICAL_XOR: int + CUPYNUMERIC_BINOP_MAXIMUM: int + CUPYNUMERIC_BINOP_MINIMUM: int + CUPYNUMERIC_BINOP_MOD: int + CUPYNUMERIC_BINOP_MULTIPLY: int + CUPYNUMERIC_BINOP_NEXTAFTER: int + CUPYNUMERIC_BINOP_NOT_EQUAL: int + CUPYNUMERIC_BINOP_POWER: int + CUPYNUMERIC_BINOP_RIGHT_SHIFT: int + CUPYNUMERIC_BINOP_SUBTRACT: int + CUPYNUMERIC_BITGENERATOR: int + CUPYNUMERIC_BITGENOP_DISTRIBUTION: int + CUPYNUMERIC_BITGENTYPE_DEFAULT: int + CUPYNUMERIC_BITGENTYPE_XORWOW: int + CUPYNUMERIC_BITGENTYPE_MRG32K3A: int + CUPYNUMERIC_BITGENTYPE_MTGP32: int + CUPYNUMERIC_BITGENTYPE_MT19937: int + CUPYNUMERIC_BITGENTYPE_PHILOX4_32_10: int + CUPYNUMERIC_BITGENDIST_INTEGERS_16: int + CUPYNUMERIC_BITGENDIST_INTEGERS_32: int + CUPYNUMERIC_BITGENDIST_INTEGERS_64: int + CUPYNUMERIC_BITGENDIST_UNIFORM_32: int + CUPYNUMERIC_BITGENDIST_UNIFORM_64: int + CUPYNUMERIC_BITGENDIST_LOGNORMAL_32: int + CUPYNUMERIC_BITGENDIST_LOGNORMAL_64: int + CUPYNUMERIC_BITGENDIST_NORMAL_32: int + CUPYNUMERIC_BITGENDIST_NORMAL_64: int + CUPYNUMERIC_BITGENDIST_POISSON: int + CUPYNUMERIC_BITGENDIST_EXPONENTIAL_32: int + CUPYNUMERIC_BITGENDIST_EXPONENTIAL_64: int + CUPYNUMERIC_BITGENDIST_GUMBEL_32: int + CUPYNUMERIC_BITGENDIST_GUMBEL_64: int + CUPYNUMERIC_BITGENDIST_LAPLACE_32: int + CUPYNUMERIC_BITGENDIST_LAPLACE_64: int + CUPYNUMERIC_BITGENDIST_LOGISTIC_32: int + CUPYNUMERIC_BITGENDIST_LOGISTIC_64: int + CUPYNUMERIC_BITGENDIST_PARETO_32: int + CUPYNUMERIC_BITGENDIST_PARETO_64: int + CUPYNUMERIC_BITGENDIST_POWER_32: int + CUPYNUMERIC_BITGENDIST_POWER_64: int + CUPYNUMERIC_BITGENDIST_RAYLEIGH_32: int + CUPYNUMERIC_BITGENDIST_RAYLEIGH_64: int + CUPYNUMERIC_BITGENDIST_CAUCHY_32: int + CUPYNUMERIC_BITGENDIST_CAUCHY_64: int + CUPYNUMERIC_BITGENDIST_TRIANGULAR_32: int + CUPYNUMERIC_BITGENDIST_TRIANGULAR_64: int + CUPYNUMERIC_BITGENDIST_WEIBULL_32: int + CUPYNUMERIC_BITGENDIST_WEIBULL_64: int + CUPYNUMERIC_BITGENDIST_BYTES: int + CUPYNUMERIC_BITGENDIST_BETA_32: int + CUPYNUMERIC_BITGENDIST_BETA_64: int + CUPYNUMERIC_BITGENDIST_F_32: int + CUPYNUMERIC_BITGENDIST_F_64: int + CUPYNUMERIC_BITGENDIST_LOGSERIES: int + CUPYNUMERIC_BITGENDIST_NONCENTRAL_F_32: int + CUPYNUMERIC_BITGENDIST_NONCENTRAL_F_64: int + CUPYNUMERIC_BITGENDIST_CHISQUARE_32: int + CUPYNUMERIC_BITGENDIST_CHISQUARE_64: int + CUPYNUMERIC_BITGENDIST_GAMMA_32: int + CUPYNUMERIC_BITGENDIST_GAMMA_64: int + CUPYNUMERIC_BITGENDIST_STANDARD_T_32: int + CUPYNUMERIC_BITGENDIST_STANDARD_T_64: int + CUPYNUMERIC_BITGENDIST_HYPERGEOMETRIC: int + CUPYNUMERIC_BITGENDIST_VONMISES_32: int + CUPYNUMERIC_BITGENDIST_VONMISES_64: int + CUPYNUMERIC_BITGENDIST_ZIPF: int + CUPYNUMERIC_BITGENDIST_GEOMETRIC: int + CUPYNUMERIC_BITGENDIST_WALD_32: int + CUPYNUMERIC_BITGENDIST_WALD_64: int + CUPYNUMERIC_BITGENDIST_BINOMIAL: int + CUPYNUMERIC_BITGENDIST_NEGATIVE_BINOMIAL: int + CUPYNUMERIC_BITGENOP_CREATE: int + CUPYNUMERIC_BITGENOP_DESTROY: int + CUPYNUMERIC_BITGENOP_RAND_RAW: int + CUPYNUMERIC_BITORDER_BIG: int + CUPYNUMERIC_BITORDER_LITTLE: int + CUPYNUMERIC_CHOOSE: int + CUPYNUMERIC_CONTRACT: int + CUPYNUMERIC_CONVERT: int + CUPYNUMERIC_CONVERT_NAN_NOOP: int + CUPYNUMERIC_CONVERT_NAN_PROD: int + CUPYNUMERIC_CONVERT_NAN_SUM: int + CUPYNUMERIC_CONVOLVE: int + CUPYNUMERIC_CONVOLVE_AUTO: int + CUPYNUMERIC_CONVOLVE_DIRECT: int + CUPYNUMERIC_CONVOLVE_FFT: int + CUPYNUMERIC_DIAG: int + CUPYNUMERIC_DOT: int + CUPYNUMERIC_EYE: int + CUPYNUMERIC_FFT: int + CUPYNUMERIC_FFT_C2C: int + CUPYNUMERIC_FFT_C2R: int + CUPYNUMERIC_FFT_D2Z: int + CUPYNUMERIC_FFT_FORWARD: int + CUPYNUMERIC_FFT_INVERSE: int + CUPYNUMERIC_FFT_R2C: int + CUPYNUMERIC_FFT_Z2D: int + CUPYNUMERIC_FFT_Z2Z: int + CUPYNUMERIC_FILL: int + CUPYNUMERIC_FLIP: int + CUPYNUMERIC_GEMM: int + CUPYNUMERIC_HISTOGRAM: int + CUPYNUMERIC_LOAD_CUDALIBS: int + CUPYNUMERIC_MATMUL: int + CUPYNUMERIC_MATVECMUL: int + CUPYNUMERIC_MAX_MAPPERS: int + CUPYNUMERIC_MAX_REDOPS: int + CUPYNUMERIC_MAX_TASKS: int + CUPYNUMERIC_MP_POTRF: int + CUPYNUMERIC_MP_SOLVE: int + CUPYNUMERIC_NONZERO: int + CUPYNUMERIC_PACKBITS: int + CUPYNUMERIC_POTRF: int + CUPYNUMERIC_PUTMASK: int + CUPYNUMERIC_QR: int + CUPYNUMERIC_RAND: int + CUPYNUMERIC_READ: int + CUPYNUMERIC_RED_ALL: int + CUPYNUMERIC_RED_ANY: int + CUPYNUMERIC_RED_ARGMAX: int + CUPYNUMERIC_RED_ARGMIN: int + CUPYNUMERIC_RED_CONTAINS: int + CUPYNUMERIC_RED_COUNT_NONZERO: int + CUPYNUMERIC_RED_MAX: int + CUPYNUMERIC_RED_MIN: int + CUPYNUMERIC_RED_NANARGMAX: int + CUPYNUMERIC_RED_NANARGMIN: int + CUPYNUMERIC_RED_NANMAX: int + CUPYNUMERIC_RED_NANMIN: int + CUPYNUMERIC_RED_NANPROD: int + CUPYNUMERIC_RED_NANSUM: int + CUPYNUMERIC_RED_PROD: int + CUPYNUMERIC_RED_SUM: int + CUPYNUMERIC_RED_SUM_SQUARES: int + CUPYNUMERIC_RED_VARIANCE: int + CUPYNUMERIC_REPEAT: int + CUPYNUMERIC_SCALAR_UNARY_RED: int + CUPYNUMERIC_SCAN_GLOBAL: int + CUPYNUMERIC_SCAN_LOCAL: int + CUPYNUMERIC_SCAN_PROD: int + CUPYNUMERIC_SCAN_SUM: int + CUPYNUMERIC_SEARCHSORTED: int + CUPYNUMERIC_SELECT: int + CUPYNUMERIC_SOLVE: int + CUPYNUMERIC_SORT: int + CUPYNUMERIC_SVD: int + CUPYNUMERIC_SYRK: int + CUPYNUMERIC_TILE: int + CUPYNUMERIC_TRANSPOSE_COPY_2D: int + CUPYNUMERIC_TRILU: int + CUPYNUMERIC_TRSM: int + CUPYNUMERIC_UNARY_OP: int + CUPYNUMERIC_UNARY_RED: int + CUPYNUMERIC_UNIQUE: int + CUPYNUMERIC_UNIQUE_REDUCE: int + CUPYNUMERIC_UNLOAD_CUDALIBS: int + CUPYNUMERIC_UNPACKBITS: int + CUPYNUMERIC_UOP_ABSOLUTE: int + CUPYNUMERIC_UOP_ANGLE: int + CUPYNUMERIC_UOP_ARCCOS: int + CUPYNUMERIC_UOP_ARCCOSH: int + CUPYNUMERIC_UOP_ARCSIN: int + CUPYNUMERIC_UOP_ARCSINH: int + CUPYNUMERIC_UOP_ARCTAN: int + CUPYNUMERIC_UOP_ARCTANH: int + CUPYNUMERIC_UOP_CBRT: int + CUPYNUMERIC_UOP_CEIL: int + CUPYNUMERIC_UOP_CLIP: int + CUPYNUMERIC_UOP_CONJ: int + CUPYNUMERIC_UOP_COPY: int + CUPYNUMERIC_UOP_COS: int + CUPYNUMERIC_UOP_COSH: int + CUPYNUMERIC_UOP_DEG2RAD: int + CUPYNUMERIC_UOP_EXP2: int + CUPYNUMERIC_UOP_EXP: int + CUPYNUMERIC_UOP_EXPM1: int + CUPYNUMERIC_UOP_FLOOR: int + CUPYNUMERIC_UOP_FREXP: int + CUPYNUMERIC_UOP_GETARG: int + CUPYNUMERIC_UOP_IMAG: int + CUPYNUMERIC_UOP_INVERT: int + CUPYNUMERIC_UOP_ISFINITE: int + CUPYNUMERIC_UOP_ISINF: int + CUPYNUMERIC_UOP_ISNAN: int + CUPYNUMERIC_UOP_LOG10: int + CUPYNUMERIC_UOP_LOG1P: int + CUPYNUMERIC_UOP_LOG2: int + CUPYNUMERIC_UOP_LOG: int + CUPYNUMERIC_UOP_LOGICAL_NOT: int + CUPYNUMERIC_UOP_MODF: int + CUPYNUMERIC_UOP_NEGATIVE: int + CUPYNUMERIC_UOP_POSITIVE: int + CUPYNUMERIC_UOP_RAD2DEG: int + CUPYNUMERIC_UOP_REAL: int + CUPYNUMERIC_UOP_RECIPROCAL: int + CUPYNUMERIC_UOP_RINT: int + CUPYNUMERIC_UOP_ROUND: int + CUPYNUMERIC_UOP_SIGN: int + CUPYNUMERIC_UOP_SIGNBIT: int + CUPYNUMERIC_UOP_SIN: int + CUPYNUMERIC_UOP_SINH: int + CUPYNUMERIC_UOP_SQRT: int + CUPYNUMERIC_UOP_SQUARE: int + CUPYNUMERIC_UOP_TAN: int + CUPYNUMERIC_UOP_TANH: int + CUPYNUMERIC_UOP_TRUNC: int + CUPYNUMERIC_WHERE: int + CUPYNUMERIC_WINDOW: int + CUPYNUMERIC_WINDOW_BARLETT: int + CUPYNUMERIC_WINDOW_BLACKMAN: int + CUPYNUMERIC_WINDOW_HAMMING: int + CUPYNUMERIC_WINDOW_HANNING: int + CUPYNUMERIC_WINDOW_KAISER: int + CUPYNUMERIC_WRAP: int + CUPYNUMERIC_WRITE: int + CUPYNUMERIC_ZIP: int + + @abstractmethod + def cupynumeric_has_cusolvermp(self) -> bool: + ... + + @abstractmethod + def cupynumeric_max_eager_volume(self) -> int: + ... + + @abstractmethod + def cupynumeric_register_reduction_ops(self, code: int) -> _ReductionOpIds: + ... + + +def dlopen_no_autoclose(ffi: Any, lib_path: str) -> Any: + # Use an already-opened library handle, which cffi will convert to a + # regular FFI object (using the definitions previously added using + # ffi.cdef), but will not automatically dlclose() on collection. + lib = CDLL(lib_path, mode=RTLD_GLOBAL) + return ffi.dlopen(ffi.cast("void *", lib._handle)) + + +# Load the cuPyNumeric library first so we have a shard object that +# we can use to initialize all these configuration enumerations +class CuPyNumericLib: + def __init__(self, name: str) -> None: + self.name = name + + shared_lib_path = self.get_shared_library() + assert shared_lib_path is not None + header = self.get_c_header() + ffi = cffi.FFI() + if header is not None: + ffi.cdef(header) + # Don't use ffi.dlopen(), because that will call dlclose() + # automatically when the object gets collected, thus removing + # symbols that may be needed when destroying C++ objects later + # (e.g. vtable entries, which will be queried for virtual + # destructors), causing errors at shutdown. + shared_lib = dlopen_no_autoclose(ffi, shared_lib_path) + self.shared_object = cast(_CupynumericSharedLib, shared_lib) + + def register(self) -> None: + from legate.core import get_legate_runtime + + # We need to make sure that the runtime is started + get_legate_runtime() + + callback = getattr( + self.shared_object, "cupynumeric_perform_registration" + ) + callback() + + def get_shared_library(self) -> str: + from .install_info import libpath + + return os.path.join( + libpath, "libcupynumeric" + self.get_library_extension() + ) + + def get_c_header(self) -> str: + from .install_info import header + + return header + + @staticmethod + def get_library_extension() -> str: + os_name = platform.system() + if os_name == "Linux": + return ".so" + elif os_name == "Darwin": + return ".dylib" + raise RuntimeError(f"unknown platform {os_name!r}") + + +CUPYNUMERIC_LIB_NAME = "cupynumeric" +cupynumeric_lib = CuPyNumericLib(CUPYNUMERIC_LIB_NAME) +cupynumeric_lib.register() +_cupynumeric = cupynumeric_lib.shared_object + + +# Match these to CuPyNumericOpCode in cupynumeric_c.h +@unique +class CuPyNumericOpCode(IntEnum): + ADVANCED_INDEXING = _cupynumeric.CUPYNUMERIC_ADVANCED_INDEXING + ARANGE = _cupynumeric.CUPYNUMERIC_ARANGE + ARGWHERE = _cupynumeric.CUPYNUMERIC_ARGWHERE + BATCHED_CHOLESKY = _cupynumeric.CUPYNUMERIC_BATCHED_CHOLESKY + BINARY_OP = _cupynumeric.CUPYNUMERIC_BINARY_OP + BINARY_RED = _cupynumeric.CUPYNUMERIC_BINARY_RED + BINCOUNT = _cupynumeric.CUPYNUMERIC_BINCOUNT + BITGENERATOR = _cupynumeric.CUPYNUMERIC_BITGENERATOR + CHOOSE = _cupynumeric.CUPYNUMERIC_CHOOSE + CONTRACT = _cupynumeric.CUPYNUMERIC_CONTRACT + CONVERT = _cupynumeric.CUPYNUMERIC_CONVERT + CONVOLVE = _cupynumeric.CUPYNUMERIC_CONVOLVE + DIAG = _cupynumeric.CUPYNUMERIC_DIAG + DOT = _cupynumeric.CUPYNUMERIC_DOT + EYE = _cupynumeric.CUPYNUMERIC_EYE + FFT = _cupynumeric.CUPYNUMERIC_FFT + FILL = _cupynumeric.CUPYNUMERIC_FILL + FLIP = _cupynumeric.CUPYNUMERIC_FLIP + GEMM = _cupynumeric.CUPYNUMERIC_GEMM + HISTOGRAM = _cupynumeric.CUPYNUMERIC_HISTOGRAM + LOAD_CUDALIBS = _cupynumeric.CUPYNUMERIC_LOAD_CUDALIBS + MATMUL = _cupynumeric.CUPYNUMERIC_MATMUL + MATVECMUL = _cupynumeric.CUPYNUMERIC_MATVECMUL + MP_POTRF = _cupynumeric.CUPYNUMERIC_MP_POTRF + MP_SOLVE = _cupynumeric.CUPYNUMERIC_MP_SOLVE + NONZERO = _cupynumeric.CUPYNUMERIC_NONZERO + PACKBITS = _cupynumeric.CUPYNUMERIC_PACKBITS + POTRF = _cupynumeric.CUPYNUMERIC_POTRF + PUTMASK = _cupynumeric.CUPYNUMERIC_PUTMASK + QR = _cupynumeric.CUPYNUMERIC_QR + RAND = _cupynumeric.CUPYNUMERIC_RAND + READ = _cupynumeric.CUPYNUMERIC_READ + REPEAT = _cupynumeric.CUPYNUMERIC_REPEAT + SCALAR_UNARY_RED = _cupynumeric.CUPYNUMERIC_SCALAR_UNARY_RED + SCAN_GLOBAL = _cupynumeric.CUPYNUMERIC_SCAN_GLOBAL + SCAN_LOCAL = _cupynumeric.CUPYNUMERIC_SCAN_LOCAL + SEARCHSORTED = _cupynumeric.CUPYNUMERIC_SEARCHSORTED + SELECT = _cupynumeric.CUPYNUMERIC_SELECT + SOLVE = _cupynumeric.CUPYNUMERIC_SOLVE + SORT = _cupynumeric.CUPYNUMERIC_SORT + SVD = _cupynumeric.CUPYNUMERIC_SVD + SYRK = _cupynumeric.CUPYNUMERIC_SYRK + TILE = _cupynumeric.CUPYNUMERIC_TILE + TRANSPOSE_COPY_2D = _cupynumeric.CUPYNUMERIC_TRANSPOSE_COPY_2D + TRILU = _cupynumeric.CUPYNUMERIC_TRILU + TRSM = _cupynumeric.CUPYNUMERIC_TRSM + UNARY_OP = _cupynumeric.CUPYNUMERIC_UNARY_OP + UNARY_RED = _cupynumeric.CUPYNUMERIC_UNARY_RED + UNIQUE = _cupynumeric.CUPYNUMERIC_UNIQUE + UNIQUE_REDUCE = _cupynumeric.CUPYNUMERIC_UNIQUE_REDUCE + UNLOAD_CUDALIBS = _cupynumeric.CUPYNUMERIC_UNLOAD_CUDALIBS + UNPACKBITS = _cupynumeric.CUPYNUMERIC_UNPACKBITS + WHERE = _cupynumeric.CUPYNUMERIC_WHERE + WINDOW = _cupynumeric.CUPYNUMERIC_WINDOW + WRAP = _cupynumeric.CUPYNUMERIC_WRAP + WRITE = _cupynumeric.CUPYNUMERIC_WRITE + ZIP = _cupynumeric.CUPYNUMERIC_ZIP + + +# Match these to CuPyNumericUnaryOpCode in cupynumeric_c.h +@unique +class UnaryOpCode(IntEnum): + ABSOLUTE = _cupynumeric.CUPYNUMERIC_UOP_ABSOLUTE + ANGLE = _cupynumeric.CUPYNUMERIC_UOP_ANGLE + ARCCOS = _cupynumeric.CUPYNUMERIC_UOP_ARCCOS + ARCCOSH = _cupynumeric.CUPYNUMERIC_UOP_ARCCOSH + ARCSIN = _cupynumeric.CUPYNUMERIC_UOP_ARCSIN + ARCSINH = _cupynumeric.CUPYNUMERIC_UOP_ARCSINH + ARCTAN = _cupynumeric.CUPYNUMERIC_UOP_ARCTAN + ARCTANH = _cupynumeric.CUPYNUMERIC_UOP_ARCTANH + CBRT = _cupynumeric.CUPYNUMERIC_UOP_CBRT + CEIL = _cupynumeric.CUPYNUMERIC_UOP_CEIL + CLIP = _cupynumeric.CUPYNUMERIC_UOP_CLIP + CONJ = _cupynumeric.CUPYNUMERIC_UOP_CONJ + COPY = _cupynumeric.CUPYNUMERIC_UOP_COPY + COS = _cupynumeric.CUPYNUMERIC_UOP_COS + COSH = _cupynumeric.CUPYNUMERIC_UOP_COSH + DEG2RAD = _cupynumeric.CUPYNUMERIC_UOP_DEG2RAD + EXP = _cupynumeric.CUPYNUMERIC_UOP_EXP + EXP2 = _cupynumeric.CUPYNUMERIC_UOP_EXP2 + EXPM1 = _cupynumeric.CUPYNUMERIC_UOP_EXPM1 + FLOOR = _cupynumeric.CUPYNUMERIC_UOP_FLOOR + FREXP = _cupynumeric.CUPYNUMERIC_UOP_FREXP + GETARG = _cupynumeric.CUPYNUMERIC_UOP_GETARG + IMAG = _cupynumeric.CUPYNUMERIC_UOP_IMAG + INVERT = _cupynumeric.CUPYNUMERIC_UOP_INVERT + ISFINITE = _cupynumeric.CUPYNUMERIC_UOP_ISFINITE + ISINF = _cupynumeric.CUPYNUMERIC_UOP_ISINF + ISNAN = _cupynumeric.CUPYNUMERIC_UOP_ISNAN + LOG = _cupynumeric.CUPYNUMERIC_UOP_LOG + LOG10 = _cupynumeric.CUPYNUMERIC_UOP_LOG10 + LOG1P = _cupynumeric.CUPYNUMERIC_UOP_LOG1P + LOG2 = _cupynumeric.CUPYNUMERIC_UOP_LOG2 + LOGICAL_NOT = _cupynumeric.CUPYNUMERIC_UOP_LOGICAL_NOT + MODF = _cupynumeric.CUPYNUMERIC_UOP_MODF + NEGATIVE = _cupynumeric.CUPYNUMERIC_UOP_NEGATIVE + POSITIVE = _cupynumeric.CUPYNUMERIC_UOP_POSITIVE + RAD2DEG = _cupynumeric.CUPYNUMERIC_UOP_RAD2DEG + REAL = _cupynumeric.CUPYNUMERIC_UOP_REAL + RECIPROCAL = _cupynumeric.CUPYNUMERIC_UOP_RECIPROCAL + RINT = _cupynumeric.CUPYNUMERIC_UOP_RINT + ROUND = _cupynumeric.CUPYNUMERIC_UOP_ROUND + SIGN = _cupynumeric.CUPYNUMERIC_UOP_SIGN + SIGNBIT = _cupynumeric.CUPYNUMERIC_UOP_SIGNBIT + SIN = _cupynumeric.CUPYNUMERIC_UOP_SIN + SINH = _cupynumeric.CUPYNUMERIC_UOP_SINH + SQRT = _cupynumeric.CUPYNUMERIC_UOP_SQRT + SQUARE = _cupynumeric.CUPYNUMERIC_UOP_SQUARE + TAN = _cupynumeric.CUPYNUMERIC_UOP_TAN + TANH = _cupynumeric.CUPYNUMERIC_UOP_TANH + TRUNC = _cupynumeric.CUPYNUMERIC_UOP_TRUNC + + +# Match these to CuPyNumericUnaryRedCode in cupynumeric_c.h +@unique +class UnaryRedCode(IntEnum): + ALL = _cupynumeric.CUPYNUMERIC_RED_ALL + ANY = _cupynumeric.CUPYNUMERIC_RED_ANY + ARGMAX = _cupynumeric.CUPYNUMERIC_RED_ARGMAX + ARGMIN = _cupynumeric.CUPYNUMERIC_RED_ARGMIN + CONTAINS = _cupynumeric.CUPYNUMERIC_RED_CONTAINS + COUNT_NONZERO = _cupynumeric.CUPYNUMERIC_RED_COUNT_NONZERO + MAX = _cupynumeric.CUPYNUMERIC_RED_MAX + MIN = _cupynumeric.CUPYNUMERIC_RED_MIN + NANARGMAX = _cupynumeric.CUPYNUMERIC_RED_NANARGMAX + NANARGMIN = _cupynumeric.CUPYNUMERIC_RED_NANARGMIN + NANMAX = _cupynumeric.CUPYNUMERIC_RED_NANMAX + NANMIN = _cupynumeric.CUPYNUMERIC_RED_NANMIN + NANPROD = _cupynumeric.CUPYNUMERIC_RED_NANPROD + NANSUM = _cupynumeric.CUPYNUMERIC_RED_NANSUM + PROD = _cupynumeric.CUPYNUMERIC_RED_PROD + SUM = _cupynumeric.CUPYNUMERIC_RED_SUM + SUM_SQUARES = _cupynumeric.CUPYNUMERIC_RED_SUM_SQUARES + VARIANCE = _cupynumeric.CUPYNUMERIC_RED_VARIANCE + + +# Match these to CuPyNumericBinaryOpCode in cupynumeric_c.h +@unique +class BinaryOpCode(IntEnum): + ADD = _cupynumeric.CUPYNUMERIC_BINOP_ADD + ARCTAN2 = _cupynumeric.CUPYNUMERIC_BINOP_ARCTAN2 + BITWISE_AND = _cupynumeric.CUPYNUMERIC_BINOP_BITWISE_AND + BITWISE_OR = _cupynumeric.CUPYNUMERIC_BINOP_BITWISE_OR + BITWISE_XOR = _cupynumeric.CUPYNUMERIC_BINOP_BITWISE_XOR + COPYSIGN = _cupynumeric.CUPYNUMERIC_BINOP_COPYSIGN + DIVIDE = _cupynumeric.CUPYNUMERIC_BINOP_DIVIDE + EQUAL = _cupynumeric.CUPYNUMERIC_BINOP_EQUAL + FLOAT_POWER = _cupynumeric.CUPYNUMERIC_BINOP_FLOAT_POWER + FLOOR_DIVIDE = _cupynumeric.CUPYNUMERIC_BINOP_FLOOR_DIVIDE + FMOD = _cupynumeric.CUPYNUMERIC_BINOP_FMOD + GCD = _cupynumeric.CUPYNUMERIC_BINOP_GCD + GREATER = _cupynumeric.CUPYNUMERIC_BINOP_GREATER + GREATER_EQUAL = _cupynumeric.CUPYNUMERIC_BINOP_GREATER_EQUAL + HYPOT = _cupynumeric.CUPYNUMERIC_BINOP_HYPOT + ISCLOSE = _cupynumeric.CUPYNUMERIC_BINOP_ISCLOSE + LCM = _cupynumeric.CUPYNUMERIC_BINOP_LCM + LDEXP = _cupynumeric.CUPYNUMERIC_BINOP_LDEXP + LEFT_SHIFT = _cupynumeric.CUPYNUMERIC_BINOP_LEFT_SHIFT + LESS = _cupynumeric.CUPYNUMERIC_BINOP_LESS + LESS_EQUAL = _cupynumeric.CUPYNUMERIC_BINOP_LESS_EQUAL + LOGADDEXP = _cupynumeric.CUPYNUMERIC_BINOP_LOGADDEXP + LOGADDEXP2 = _cupynumeric.CUPYNUMERIC_BINOP_LOGADDEXP2 + LOGICAL_AND = _cupynumeric.CUPYNUMERIC_BINOP_LOGICAL_AND + LOGICAL_OR = _cupynumeric.CUPYNUMERIC_BINOP_LOGICAL_OR + LOGICAL_XOR = _cupynumeric.CUPYNUMERIC_BINOP_LOGICAL_XOR + MAXIMUM = _cupynumeric.CUPYNUMERIC_BINOP_MAXIMUM + MINIMUM = _cupynumeric.CUPYNUMERIC_BINOP_MINIMUM + MOD = _cupynumeric.CUPYNUMERIC_BINOP_MOD + MULTIPLY = _cupynumeric.CUPYNUMERIC_BINOP_MULTIPLY + NEXTAFTER = _cupynumeric.CUPYNUMERIC_BINOP_NEXTAFTER + NOT_EQUAL = _cupynumeric.CUPYNUMERIC_BINOP_NOT_EQUAL + POWER = _cupynumeric.CUPYNUMERIC_BINOP_POWER + RIGHT_SHIFT = _cupynumeric.CUPYNUMERIC_BINOP_RIGHT_SHIFT + SUBTRACT = _cupynumeric.CUPYNUMERIC_BINOP_SUBTRACT + + +@unique +class WindowOpCode(IntEnum): + BARLETT = _cupynumeric.CUPYNUMERIC_WINDOW_BARLETT + BLACKMAN = _cupynumeric.CUPYNUMERIC_WINDOW_BLACKMAN + HAMMING = _cupynumeric.CUPYNUMERIC_WINDOW_HAMMING + HANNING = _cupynumeric.CUPYNUMERIC_WINDOW_HANNING + KAISER = _cupynumeric.CUPYNUMERIC_WINDOW_KAISER + + +# Match these to RandGenCode in rand_util.h +@unique +class RandGenCode(IntEnum): + UNIFORM = 1 + NORMAL = 2 + INTEGER = 3 + + +# Match these to CuPyNumericScanCode in cupynumeric_c.h +@unique +class ScanCode(IntEnum): + PROD = _cupynumeric.CUPYNUMERIC_SCAN_PROD + SUM = _cupynumeric.CUPYNUMERIC_SCAN_SUM + + +# Match these to CuPyNumericConvertCode in cupynumeric_c.h +@unique +class ConvertCode(IntEnum): + NOOP = _cupynumeric.CUPYNUMERIC_CONVERT_NAN_NOOP + PROD = _cupynumeric.CUPYNUMERIC_CONVERT_NAN_PROD + SUM = _cupynumeric.CUPYNUMERIC_CONVERT_NAN_SUM + + +# Match these to BitGeneratorOperation in cupynumeric_c.h +@unique +class BitGeneratorOperation(IntEnum): + CREATE = _cupynumeric.CUPYNUMERIC_BITGENOP_CREATE + DESTROY = _cupynumeric.CUPYNUMERIC_BITGENOP_DESTROY + RAND_RAW = _cupynumeric.CUPYNUMERIC_BITGENOP_RAND_RAW + DISTRIBUTION = _cupynumeric.CUPYNUMERIC_BITGENOP_DISTRIBUTION + + +# Match these to BitGeneratorType in cupynumeric_c.h +@unique +class BitGeneratorType(IntEnum): + DEFAULT = _cupynumeric.CUPYNUMERIC_BITGENTYPE_DEFAULT + XORWOW = _cupynumeric.CUPYNUMERIC_BITGENTYPE_XORWOW + MRG32K3A = _cupynumeric.CUPYNUMERIC_BITGENTYPE_MRG32K3A + MTGP32 = _cupynumeric.CUPYNUMERIC_BITGENTYPE_MTGP32 + MT19937 = _cupynumeric.CUPYNUMERIC_BITGENTYPE_MT19937 + PHILOX4_32_10 = _cupynumeric.CUPYNUMERIC_BITGENTYPE_PHILOX4_32_10 + + +# Match these to BitGeneratorDistribution in cupynumeric_c.h +@unique +class BitGeneratorDistribution(IntEnum): + INTEGERS_16 = _cupynumeric.CUPYNUMERIC_BITGENDIST_INTEGERS_16 + INTEGERS_32 = _cupynumeric.CUPYNUMERIC_BITGENDIST_INTEGERS_32 + INTEGERS_64 = _cupynumeric.CUPYNUMERIC_BITGENDIST_INTEGERS_64 + UNIFORM_32 = _cupynumeric.CUPYNUMERIC_BITGENDIST_UNIFORM_32 + UNIFORM_64 = _cupynumeric.CUPYNUMERIC_BITGENDIST_UNIFORM_64 + LOGNORMAL_32 = _cupynumeric.CUPYNUMERIC_BITGENDIST_LOGNORMAL_32 + LOGNORMAL_64 = _cupynumeric.CUPYNUMERIC_BITGENDIST_LOGNORMAL_64 + NORMAL_32 = _cupynumeric.CUPYNUMERIC_BITGENDIST_NORMAL_32 + NORMAL_64 = _cupynumeric.CUPYNUMERIC_BITGENDIST_NORMAL_64 + POISSON = _cupynumeric.CUPYNUMERIC_BITGENDIST_POISSON + EXPONENTIAL_32 = _cupynumeric.CUPYNUMERIC_BITGENDIST_EXPONENTIAL_32 + EXPONENTIAL_64 = _cupynumeric.CUPYNUMERIC_BITGENDIST_EXPONENTIAL_64 + GUMBEL_32 = _cupynumeric.CUPYNUMERIC_BITGENDIST_GUMBEL_32 + GUMBEL_64 = _cupynumeric.CUPYNUMERIC_BITGENDIST_GUMBEL_64 + LAPLACE_32 = _cupynumeric.CUPYNUMERIC_BITGENDIST_LAPLACE_32 + LAPLACE_64 = _cupynumeric.CUPYNUMERIC_BITGENDIST_LAPLACE_64 + LOGISTIC_32 = _cupynumeric.CUPYNUMERIC_BITGENDIST_LOGISTIC_32 + LOGISTIC_64 = _cupynumeric.CUPYNUMERIC_BITGENDIST_LOGISTIC_64 + PARETO_32 = _cupynumeric.CUPYNUMERIC_BITGENDIST_PARETO_32 + PARETO_64 = _cupynumeric.CUPYNUMERIC_BITGENDIST_PARETO_64 + POWER_32 = _cupynumeric.CUPYNUMERIC_BITGENDIST_POWER_32 + POWER_64 = _cupynumeric.CUPYNUMERIC_BITGENDIST_POWER_64 + RAYLEIGH_32 = _cupynumeric.CUPYNUMERIC_BITGENDIST_RAYLEIGH_32 + RAYLEIGH_64 = _cupynumeric.CUPYNUMERIC_BITGENDIST_RAYLEIGH_64 + CAUCHY_32 = _cupynumeric.CUPYNUMERIC_BITGENDIST_CAUCHY_32 + CAUCHY_64 = _cupynumeric.CUPYNUMERIC_BITGENDIST_CAUCHY_64 + TRIANGULAR_32 = _cupynumeric.CUPYNUMERIC_BITGENDIST_TRIANGULAR_32 + TRIANGULAR_64 = _cupynumeric.CUPYNUMERIC_BITGENDIST_TRIANGULAR_64 + WEIBULL_32 = _cupynumeric.CUPYNUMERIC_BITGENDIST_WEIBULL_32 + WEIBULL_64 = _cupynumeric.CUPYNUMERIC_BITGENDIST_WEIBULL_64 + BYTES = _cupynumeric.CUPYNUMERIC_BITGENDIST_BYTES + BETA_32 = _cupynumeric.CUPYNUMERIC_BITGENDIST_BETA_32 + BETA_64 = _cupynumeric.CUPYNUMERIC_BITGENDIST_BETA_64 + F_32 = _cupynumeric.CUPYNUMERIC_BITGENDIST_F_32 + F_64 = _cupynumeric.CUPYNUMERIC_BITGENDIST_F_64 + LOGSERIES = _cupynumeric.CUPYNUMERIC_BITGENDIST_LOGSERIES + NONCENTRAL_F_32 = _cupynumeric.CUPYNUMERIC_BITGENDIST_NONCENTRAL_F_32 + NONCENTRAL_F_64 = _cupynumeric.CUPYNUMERIC_BITGENDIST_NONCENTRAL_F_64 + CHISQUARE_32 = _cupynumeric.CUPYNUMERIC_BITGENDIST_CHISQUARE_32 + CHISQUARE_64 = _cupynumeric.CUPYNUMERIC_BITGENDIST_CHISQUARE_64 + GAMMA_32 = _cupynumeric.CUPYNUMERIC_BITGENDIST_GAMMA_32 + GAMMA_64 = _cupynumeric.CUPYNUMERIC_BITGENDIST_GAMMA_64 + STANDARD_T_32 = _cupynumeric.CUPYNUMERIC_BITGENDIST_STANDARD_T_32 + STANDARD_T_64 = _cupynumeric.CUPYNUMERIC_BITGENDIST_STANDARD_T_64 + HYPERGEOMETRIC = _cupynumeric.CUPYNUMERIC_BITGENDIST_HYPERGEOMETRIC + VONMISES_32 = _cupynumeric.CUPYNUMERIC_BITGENDIST_VONMISES_32 + VONMISES_64 = _cupynumeric.CUPYNUMERIC_BITGENDIST_VONMISES_64 + ZIPF = _cupynumeric.CUPYNUMERIC_BITGENDIST_ZIPF + GEOMETRIC = _cupynumeric.CUPYNUMERIC_BITGENDIST_GEOMETRIC + WALD_32 = _cupynumeric.CUPYNUMERIC_BITGENDIST_WALD_32 + WALD_64 = _cupynumeric.CUPYNUMERIC_BITGENDIST_WALD_64 + BINOMIAL = _cupynumeric.CUPYNUMERIC_BITGENDIST_BINOMIAL + NEGATIVE_BINOMIAL = _cupynumeric.CUPYNUMERIC_BITGENDIST_NEGATIVE_BINOMIAL + + +# Match these to CuPyNumericConvolveMethod in cupynumeric_c.h +@unique +class ConvolveMethod(IntEnum): + AUTO = _cupynumeric.CUPYNUMERIC_CONVOLVE_AUTO + DIRECT = _cupynumeric.CUPYNUMERIC_CONVOLVE_DIRECT + FFT = _cupynumeric.CUPYNUMERIC_CONVOLVE_FFT + + +@unique +class TransferType(IntEnum): + DONATE = 0 + MAKE_COPY = 1 + SHARE = 2 + + +# Match these to fftType in fft_util.h +class FFTType: + def __init__( + self, + name: str, + type_id: int, + input_dtype: npt.DTypeLike, + output_dtype: npt.DTypeLike, + single_precision: bool, + complex_type: FFTType | None = None, + ) -> None: + self._name = name + self._type_id = type_id + self._complex_type = self if complex_type is None else complex_type + self._input_dtype = input_dtype + self._output_dtype = output_dtype + self._single_precision = single_precision + + def __str__(self) -> str: + return self._name + + def __repr__(self) -> str: + return str(self) + + @property + def type_id(self) -> int: + return self._type_id + + @property + def complex(self) -> FFTType: + return self._complex_type + + @property + def input_dtype(self) -> npt.DTypeLike: + return self._input_dtype + + @property + def output_dtype(self) -> npt.DTypeLike: + return self._output_dtype + + @property + def is_single_precision(self) -> bool: + return self._single_precision + + +FFT_C2C = FFTType( + "C2C", + _cupynumeric.CUPYNUMERIC_FFT_C2C, + np.complex64, + np.complex64, + True, +) + +FFT_Z2Z = FFTType( + "Z2Z", + _cupynumeric.CUPYNUMERIC_FFT_Z2Z, + np.complex128, + np.complex128, + False, +) + +FFT_R2C = FFTType( + "R2C", + _cupynumeric.CUPYNUMERIC_FFT_R2C, + np.float32, + np.complex64, + True, + FFT_C2C, +) + +FFT_C2R = FFTType( + "C2R", + _cupynumeric.CUPYNUMERIC_FFT_C2R, + np.complex64, + np.float32, + True, + FFT_C2C, +) + +FFT_D2Z = FFTType( + "D2Z", + _cupynumeric.CUPYNUMERIC_FFT_D2Z, + np.float64, + np.complex128, + False, + FFT_Z2Z, +) + +FFT_Z2D = FFTType( + "Z2D", + _cupynumeric.CUPYNUMERIC_FFT_Z2D, + np.complex128, + np.float64, + False, + FFT_Z2Z, +) + + +class FFTCode: + @staticmethod + def real_to_complex_code(dtype: npt.DTypeLike) -> FFTType: + if dtype == np.float64: + return FFT_D2Z + elif dtype == np.float32: + return FFT_R2C + else: + raise TypeError( + ( + "Data type for FFT not supported " + "(supported types are float32 and float64)" + ) + ) + + @staticmethod + def complex_to_real_code(dtype: npt.DTypeLike) -> FFTType: + if dtype == np.complex128: + return FFT_Z2D + elif dtype == np.complex64: + return FFT_C2R + else: + raise TypeError( + ( + "Data type for FFT not supported " + "(supported types are complex64 and complex128)" + ) + ) + + +@unique +class FFTDirection(IntEnum): + FORWARD = _cupynumeric.CUPYNUMERIC_FFT_FORWARD + INVERSE = _cupynumeric.CUPYNUMERIC_FFT_INVERSE + + +# Match these to CuPyNumericBitorder in cupynumeric_c.h +@unique +class Bitorder(IntEnum): + BIG = _cupynumeric.CUPYNUMERIC_BITORDER_BIG + LITTLE = _cupynumeric.CUPYNUMERIC_BITORDER_LITTLE + + +@unique +class FFTNormalization(IntEnum): + FORWARD = 1 + INVERSE = 2 + ORTHOGONAL = 3 + + @staticmethod + def from_string(in_string: str) -> FFTNormalization | None: + if in_string == "forward": + return FFTNormalization.FORWARD + elif in_string == "ortho": + return FFTNormalization.ORTHOGONAL + elif in_string == "backward" or in_string is None: + return FFTNormalization.INVERSE + else: + return None + + @staticmethod + def reverse(in_string: str | None) -> str: + if in_string == "forward": + return "backward" + elif in_string == "backward" or in_string is None: + return "forward" + else: + return in_string diff --git a/cunumeric/fft/__init__.py b/cupynumeric/fft/__init__.py similarity index 100% rename from cunumeric/fft/__init__.py rename to cupynumeric/fft/__init__.py diff --git a/cunumeric/fft/fft.py b/cupynumeric/fft/fft.py similarity index 99% rename from cunumeric/fft/fft.py rename to cupynumeric/fft/fft.py index ad6b7caafd..a1fd699488 100644 --- a/cunumeric/fft/fft.py +++ b/cupynumeric/fft/fft.py @@ -20,7 +20,6 @@ from .._array.util import add_boilerplate from .._module.array_rearrange import roll -from .._module.creation_data import asarray from ..config import FFT_C2C, FFT_Z2Z, FFTCode, FFTDirection, FFTNormalization if TYPE_CHECKING: diff --git a/cunumeric/install_info.py.in b/cupynumeric/install_info.py.in similarity index 79% rename from cunumeric/install_info.py.in rename to cupynumeric/install_info.py.in index cc683b2252..c582492df4 100644 --- a/cunumeric/install_info.py.in +++ b/cupynumeric/install_info.py.in @@ -30,15 +30,15 @@ def get_libpath(): "Windows": ".dll" }[platform.system()] - def find_libcunumeric(libdir): - if exists(join(libdir, f"libcunumeric{so_ext}")): + def find_libcupynumeric(libdir): + if exists(join(libdir, f"libcupynumeric{so_ext}")): return libdir return None return ( - find_libcunumeric(join(cn_path, "build", "lib")) or - find_libcunumeric(join(dirname(dirname(dirname(cn_path))), "lib")) or - find_libcunumeric(join(dirname(dirname(sys.executable)), "lib")) or + find_libcupynumeric(join(cn_path, "build", "lib")) or + find_libcupynumeric(join(dirname(dirname(dirname(cn_path))), "lib")) or + find_libcupynumeric(join(dirname(dirname(sys.executable)), "lib")) or "" ) diff --git a/cunumeric/linalg/__init__.py b/cupynumeric/linalg/__init__.py similarity index 100% rename from cunumeric/linalg/__init__.py rename to cupynumeric/linalg/__init__.py diff --git a/cunumeric/linalg/_cholesky.py b/cupynumeric/linalg/_cholesky.py similarity index 91% rename from cunumeric/linalg/_cholesky.py rename to cupynumeric/linalg/_cholesky.py index e3f3d2e1fb..a99ae68117 100644 --- a/cunumeric/linalg/_cholesky.py +++ b/cupynumeric/linalg/_cholesky.py @@ -25,7 +25,7 @@ ) from legate.settings import settings -from ..config import CuNumericOpCode +from ..config import CuPyNumericOpCode from ..runtime import runtime from ._exception import LinAlgError @@ -42,7 +42,7 @@ def transpose_copy_single( library: Library, input: LogicalStore, output: LogicalStore ) -> None: task = legate_runtime.create_auto_task( - library, CuNumericOpCode.TRANSPOSE_COPY_2D + library, CuPyNumericOpCode.TRANSPOSE_COPY_2D ) p_out = task.add_output(output) p_in = task.add_input(input) @@ -63,7 +63,7 @@ def transpose_copy( ) -> None: task = legate_runtime.create_manual_task( library, - CuNumericOpCode.TRANSPOSE_COPY_2D, + CuPyNumericOpCode.TRANSPOSE_COPY_2D, launch_domain, ) task.add_output(p_output) @@ -75,7 +75,7 @@ def transpose_copy( def potrf_single(library: Library, output: LogicalStore) -> None: - task = legate_runtime.create_auto_task(library, CuNumericOpCode.POTRF) + task = legate_runtime.create_auto_task(library, CuPyNumericOpCode.POTRF) task.throws_exception(LinAlgError) task.add_output(output) task.add_input(output) @@ -89,7 +89,7 @@ def mp_potrf( input: LogicalStore, output: LogicalStore, ) -> None: - task = legate_runtime.create_auto_task(library, CuNumericOpCode.MP_POTRF) + task = legate_runtime.create_auto_task(library, CuPyNumericOpCode.MP_POTRF) task.throws_exception(LinAlgError) task.add_input(input) task.add_output(output) @@ -103,7 +103,7 @@ def mp_potrf( def potrf(library: Library, p_output: LogicalStorePartition, i: int) -> None: task = legate_runtime.create_manual_task( - library, CuNumericOpCode.POTRF, (i + 1, i + 1), lower_bounds=(i, i) + library, CuPyNumericOpCode.POTRF, (i + 1, i + 1), lower_bounds=(i, i) ) task.throws_exception(LinAlgError) task.add_output(p_output) @@ -121,7 +121,7 @@ def trsm( lhs = p_output task = legate_runtime.create_manual_task( - library, CuNumericOpCode.TRSM, (hi, i + 1), lower_bounds=(lo, i) + library, CuPyNumericOpCode.TRSM, (hi, i + 1), lower_bounds=(lo, i) ) task.add_output(lhs) task.add_input(rhs) @@ -136,7 +136,7 @@ def syrk( lhs = p_output task = legate_runtime.create_manual_task( - library, CuNumericOpCode.SYRK, (k + 1, k + 1), lower_bounds=(k, k) + library, CuPyNumericOpCode.SYRK, (k + 1, k + 1), lower_bounds=(k, k) ) task.add_output(lhs) task.add_input(rhs) @@ -160,7 +160,7 @@ def gemm( rhs1 = p_output task = legate_runtime.create_manual_task( - library, CuNumericOpCode.GEMM, (hi, k + 1), lower_bounds=(lo, k) + library, CuPyNumericOpCode.GEMM, (hi, k + 1), lower_bounds=(lo, k) ) task.add_output(lhs) task.add_input(rhs1, (dimension(0), constant(i))) @@ -198,7 +198,7 @@ def choose_color_shape( def tril_single(library: Library, output: LogicalStore) -> None: - task = legate_runtime.create_auto_task(library, CuNumericOpCode.TRILU) + task = legate_runtime.create_auto_task(library, CuPyNumericOpCode.TRILU) task.add_output(output) task.add_input(output) task.add_scalar_arg(True, ty.bool_) @@ -211,7 +211,7 @@ def tril_single(library: Library, output: LogicalStore) -> None: def tril(library: Library, p_output: LogicalStorePartition, n: int) -> None: task = legate_runtime.create_manual_task( - library, CuNumericOpCode.TRILU, (n, n) + library, CuPyNumericOpCode.TRILU, (n, n) ) task.add_output(p_output) @@ -239,7 +239,7 @@ def _batched_cholesky( # Just use a fixed cutoff to provide some sensible warning. # TODO: find a better way to inform the user dims are too big task = legate_runtime.create_auto_task( - library, CuNumericOpCode.BATCHED_CHOLESKY + library, CuPyNumericOpCode.BATCHED_CHOLESKY ) task.add_input(input.base) task.add_output(output.base) diff --git a/cunumeric/linalg/_exception.py b/cupynumeric/linalg/_exception.py similarity index 100% rename from cunumeric/linalg/_exception.py rename to cupynumeric/linalg/_exception.py diff --git a/cunumeric/linalg/_qr.py b/cupynumeric/linalg/_qr.py similarity index 91% rename from cunumeric/linalg/_qr.py rename to cupynumeric/linalg/_qr.py index aa2c38e1cb..4b20d5fe62 100644 --- a/cunumeric/linalg/_qr.py +++ b/cupynumeric/linalg/_qr.py @@ -18,7 +18,7 @@ from legate.core import get_legate_runtime -from cunumeric.config import CuNumericOpCode +from cupynumeric.config import CuPyNumericOpCode from ._exception import LinAlgError @@ -31,7 +31,7 @@ def qr_single( library: Library, a: LogicalStore, q: LogicalStore, r: LogicalStore ) -> None: - task = get_legate_runtime().create_auto_task(library, CuNumericOpCode.QR) + task = get_legate_runtime().create_auto_task(library, CuPyNumericOpCode.QR) task.throws_exception(LinAlgError) task.add_input(a) task.add_output(q) diff --git a/cunumeric/linalg/_solve.py b/cupynumeric/linalg/_solve.py similarity index 95% rename from cunumeric/linalg/_solve.py rename to cupynumeric/linalg/_solve.py index 7681444ac3..325fe301de 100644 --- a/cunumeric/linalg/_solve.py +++ b/cupynumeric/linalg/_solve.py @@ -19,7 +19,7 @@ import legate.core.types as ty from legate.core import broadcast, get_legate_runtime -from ..config import CuNumericOpCode +from ..config import CuPyNumericOpCode from ..runtime import runtime from ._cholesky import transpose_copy_single from ._exception import LinAlgError @@ -32,7 +32,7 @@ def solve_single(library: Library, a: LogicalStore, b: LogicalStore) -> None: task = get_legate_runtime().create_auto_task( - library, CuNumericOpCode.SOLVE + library, CuPyNumericOpCode.SOLVE ) task.throws_exception(LinAlgError) p_a = task.add_input(a) @@ -60,7 +60,7 @@ def mp_solve( output: LogicalStore, ) -> None: task = get_legate_runtime().create_auto_task( - library, CuNumericOpCode.MP_SOLVE + library, CuPyNumericOpCode.MP_SOLVE ) task.throws_exception(LinAlgError) task.add_input(a) diff --git a/cunumeric/linalg/_svd.py b/cupynumeric/linalg/_svd.py similarity index 90% rename from cunumeric/linalg/_svd.py rename to cupynumeric/linalg/_svd.py index 9579f06849..a9be94924d 100644 --- a/cunumeric/linalg/_svd.py +++ b/cupynumeric/linalg/_svd.py @@ -18,7 +18,7 @@ from legate.core import get_legate_runtime -from cunumeric.config import CuNumericOpCode +from cupynumeric.config import CuPyNumericOpCode from ._exception import LinAlgError @@ -35,7 +35,9 @@ def svd_single( s: LogicalStore, vh: LogicalStore, ) -> None: - task = get_legate_runtime().create_auto_task(library, CuNumericOpCode.SVD) + task = get_legate_runtime().create_auto_task( + library, CuPyNumericOpCode.SVD + ) task.throws_exception(LinAlgError) task.add_input(a) task.add_output(u) diff --git a/cunumeric/linalg/linalg.py b/cupynumeric/linalg/linalg.py similarity index 97% rename from cunumeric/linalg/linalg.py rename to cupynumeric/linalg/linalg.py index 8a47e1bcf3..25f9ed964a 100644 --- a/cunumeric/linalg/linalg.py +++ b/cupynumeric/linalg/linalg.py @@ -31,7 +31,7 @@ normalize_axis_tuple, ) -from .._array.util import add_boilerplate, convert_to_cunumeric_ndarray +from .._array.util import add_boilerplate, convert_to_cupynumeric_ndarray from .._module import dot, empty_like, eye, matmul, ndarray from .._ufunc.math import add, sqrt as _sqrt from ._exception import LinAlgError @@ -134,7 +134,7 @@ def qr(a: ndarray) -> tuple[ndarray, ...]: ) if len(shape) > 2: raise NotImplementedError( - "cuNumeric does not yet support stacked 2d arrays" + "cuPyNumeric does not yet support stacked 2d arrays" ) if np.dtype("e") == a.dtype: raise TypeError("array type float16 is unsupported in linalg") @@ -194,7 +194,7 @@ def solve(a: ndarray, b: ndarray, out: ndarray | None = None) -> ndarray: raise TypeError("array type float16 is unsupported in linalg") if a.ndim > 2 or b.ndim > 2: raise NotImplementedError( - "cuNumeric does not yet support stacked 2d arrays" + "cuPyNumeric does not yet support stacked 2d arrays" ) if a.shape[-2] != a.shape[-1]: raise LinAlgError("Last 2 dimensions of the array must be square") @@ -265,10 +265,10 @@ def svd(a: ndarray, full_matrices: bool = True) -> tuple[ndarray, ...]: ) if len(shape) > 2: raise NotImplementedError( - "cuNumeric does not yet support stacked 2d arrays" + "cuPyNumeric does not yet support stacked 2d arrays" ) if shape[0] < shape[1]: - raise NotImplementedError("cuNumeric only supports M >= N") + raise NotImplementedError("cuPyNumeric only supports M >= N") if np.dtype("e") == a.dtype: raise TypeError("array type float16 is unsupported in linalg") return _thunk_svd(a, full_matrices) @@ -323,7 +323,7 @@ def matrix_power(a: ndarray, n: int) -> ndarray: # Invert if necessary if n < 0: - # TODO: Add this once cunumeric.inv is implemented + # TODO: Add this once cupynumeric.inv is implemented # a = inv(a) # n = abs(n) raise NotImplementedError("Negative exponent in matrix_power") @@ -385,9 +385,9 @@ def multi_dot( -------- Multiple GPUs, Multiple CPUs """ - arrays = [convert_to_cunumeric_ndarray(x) for x in arrays] + arrays = [convert_to_cupynumeric_ndarray(x) for x in arrays] if out is not None: - out = convert_to_cunumeric_ndarray(out, share=True) + out = convert_to_cupynumeric_ndarray(out, share=True) n = len(arrays) # optimization only makes sense for len(arrays) > 2 diff --git a/cunumeric/ma/__init__.py b/cupynumeric/ma/__init__.py similarity index 100% rename from cunumeric/ma/__init__.py rename to cupynumeric/ma/__init__.py diff --git a/cunumeric/ma/_masked_array.py b/cupynumeric/ma/_masked_array.py similarity index 100% rename from cunumeric/ma/_masked_array.py rename to cupynumeric/ma/_masked_array.py diff --git a/cunumeric/patch.py b/cupynumeric/patch.py similarity index 83% rename from cunumeric/patch.py rename to cupynumeric/patch.py index 2cc72266e1..b92a7f7e32 100644 --- a/cunumeric/patch.py +++ b/cupynumeric/patch.py @@ -13,12 +13,12 @@ # limitations under the License. # """ This module may be imported in order to globably replace NumPy with -CuNumeric. +cuPyNumeric. In order to function properly, this module must be imported early (ideally at the very start of a script). The ``numpy`` module in ``sys.modules`` -will be replaced with ``cunumeric`` so that any subsequent use of the -``numpy`` module will use ``cunumeric`` instead. +will be replaced with ``cupynumeric`` so that any subsequent use of the +``numpy`` module will use ``cupynumeric`` instead. This module is primarily intended for quick demonstrations or proofs of concept. @@ -28,6 +28,6 @@ import sys -import cunumeric +import cupynumeric -sys.modules["numpy"] = cunumeric +sys.modules["numpy"] = cupynumeric diff --git a/cunumeric/py.typed b/cupynumeric/py.typed similarity index 100% rename from cunumeric/py.typed rename to cupynumeric/py.typed diff --git a/cunumeric/random/__init__.py b/cupynumeric/random/__init__.py similarity index 100% rename from cunumeric/random/__init__.py rename to cupynumeric/random/__init__.py diff --git a/cunumeric/random/_bitgenerator.py b/cupynumeric/random/_bitgenerator.py similarity index 100% rename from cunumeric/random/_bitgenerator.py rename to cupynumeric/random/_bitgenerator.py diff --git a/cunumeric/random/_generator.py b/cupynumeric/random/_generator.py similarity index 98% rename from cunumeric/random/_generator.py rename to cupynumeric/random/_generator.py index c84cce39de..b2d22a1fb9 100644 --- a/cunumeric/random/_generator.py +++ b/cupynumeric/random/_generator.py @@ -43,8 +43,8 @@ def __init__(self, bit_generator: BitGenerator) -> None: then an array with that shape is filled and returned. - The function :func:`cunumeric.random.default_rng` will instantiate - a `Generator` with cuNumeric's default `BitGenerator`. + The function :func:`cupynumeric.random.default_rng` will instantiate + a `Generator` with cuPyNumeric's default `BitGenerator`. Parameters ---------- diff --git a/cunumeric/random/_random.py b/cupynumeric/random/_random.py similarity index 99% rename from cunumeric/random/_random.py rename to cupynumeric/random/_random.py index 8299da0608..6879e9053b 100644 --- a/cunumeric/random/_random.py +++ b/cupynumeric/random/_random.py @@ -1713,7 +1713,7 @@ def _random_state_fallback(obj: Any) -> Any: # wrapped vanilla NumPy RandomState if isinstance(obj, RandomState): return obj._np_random_state - # eagerly convert any cuNumeric ndarrays to NumPy + # eagerly convert any cuPyNumeric ndarrays to NumPy if isinstance(obj, ndarray): return obj.__array__() return obj diff --git a/cunumeric/runtime.py b/cupynumeric/runtime.py similarity index 95% rename from cunumeric/runtime.py rename to cupynumeric/runtime.py index 85cc7d9548..7af064d463 100644 --- a/cunumeric/runtime.py +++ b/cupynumeric/runtime.py @@ -28,12 +28,12 @@ from ._utils.stack import find_last_user_stacklevel from .config import ( BitGeneratorOperation, - CuNumericOpCode, + CuPyNumericOpCode, TransferType, - cunumeric_lib, + cupynumeric_lib, ) -# We need to be careful about importing from other cunumeric modules here. The +# We need to be careful about importing from other cupynumeric modules. The # runtime is global and used in many places, but also depends on many of the # other modules. Things like config and utils are OK, but imports for thunks, # array types, etc. need to be deferred in order to avoid circular imports. @@ -75,7 +75,7 @@ def cached_thunk_from_scalar( class Runtime(object): def __init__(self) -> None: - self.library = legate_runtime.find_library(cunumeric_lib.name) + self.library = legate_runtime.find_library(cupynumeric_lib.name) self.current_random_epoch = 0 self.current_random_bitgenid = 0 self.current_random_bitgen_zombies: tuple[Any, ...] = () @@ -83,14 +83,14 @@ def __init__(self) -> None: self.api_calls: list[tuple[str, str, bool]] = [] max_eager_volume = ( - cunumeric_lib.shared_object.cunumeric_max_eager_volume() + cupynumeric_lib.shared_object.cupynumeric_max_eager_volume() ) self.max_eager_volume = int(np.asarray(max_eager_volume)) - assert cunumeric_lib.shared_object is not None - self.cunumeric_lib = cunumeric_lib.shared_object + assert cupynumeric_lib.shared_object is not None + self.cupynumeric_lib = cupynumeric_lib.shared_object self.has_cusolvermp = ( - cunumeric_lib.shared_object.cunumeric_has_cusolvermp() + cupynumeric_lib.shared_object.cupynumeric_has_cusolvermp() ) from .settings import settings @@ -122,7 +122,7 @@ def record_api_call( def _load_cudalibs(self) -> None: task = legate_runtime.create_manual_task( self.library, - CuNumericOpCode.LOAD_CUDALIBS, + CuPyNumericOpCode.LOAD_CUDALIBS, [self.num_gpus], ) task.execute() @@ -134,7 +134,7 @@ def get_argred_type(self, value_dtype: ty.Type) -> ty.Type: return cached argred_dtype = ty.struct_type([ty.int64, value_dtype], True) self._cached_argred_types[value_dtype] = argred_dtype - ids = self.cunumeric_lib.cunumeric_register_reduction_ops( + ids = self.cupynumeric_lib.cupynumeric_register_reduction_ops( value_dtype.code ) argred_dtype.record_reduction_op( @@ -150,10 +150,10 @@ def _report_coverage(self) -> None: implemented = sum(int(impl) for (_, _, impl) in self.api_calls) if total == 0: - print("cuNumeric API coverage: 0/0") + print("cuPyNumeric API coverage: 0/0") else: print( - f"cuNumeric API coverage: {implemented}/{total} " + f"cuPyNumeric API coverage: {implemented}/{total} " f"({implemented / total * 100}%)" ) @@ -199,7 +199,7 @@ def bitgenerator_create( if forceCreate: task = legate_runtime.create_manual_task( self.library, - CuNumericOpCode.BITGENERATOR, + CuPyNumericOpCode.BITGENERATOR, (self.num_procs,), ) self.bitgenerator_populate_task( @@ -229,7 +229,7 @@ def bitgenerator_destroy( legate_runtime.issue_execution_fence() task = legate_runtime.create_manual_task( self.library, - CuNumericOpCode.BITGENERATOR, + CuPyNumericOpCode.BITGENERATOR, (self.num_procs,), ) self.bitgenerator_populate_task( @@ -395,7 +395,9 @@ def find_or_create_array_thunk( assert isinstance(array, np.ndarray) if not is_supported_dtype(array.dtype): - raise TypeError(f"cuNumeric does not support dtype={array.dtype}") + raise TypeError( + f"cuPyNumeric does not support dtype={array.dtype}" + ) # We have to be really careful here to handle the case of # aliased numpy arrays that are passed in from the application @@ -412,7 +414,7 @@ def find_or_create_array_thunk( if key is None: # This base array wasn't made with a view raise NotImplementedError( - "cuNumeric does not currently know " + "cuPyNumeric does not currently know " + "how to attach to array views that are not affine " + "transforms of their parent array." ) @@ -514,7 +516,7 @@ def is_eager_shape(self, shape: NdShape) -> bool: from .settings import settings - # CUNUMERIC_FORCE_THUNK == "eager" + # CUPYNUMERIC_FORCE_THUNK == "eager" if settings.force_thunk() == "eager": return True diff --git a/cunumeric/settings.py b/cupynumeric/settings.py similarity index 84% rename from cunumeric/settings.py rename to cupynumeric/settings.py index 292699d260..d73eee2616 100644 --- a/cunumeric/settings.py +++ b/cupynumeric/settings.py @@ -25,21 +25,21 @@ __all__ = ("settings",) -class CunumericRuntimeSettings(Settings): +class CupynumericRuntimeSettings(Settings): preload_cudalibs: PrioritizedSetting[bool] = PrioritizedSetting( "preload_cudalibs", - "CUNUMERIC_PRELOAD_CUDALIBS", + "CUPYNUMERIC_PRELOAD_CUDALIBS", default=False, convert=convert_bool, help=""" Preload and initialize handles of all CUDA libraries (cuBLAS, cuSOLVER, - etc.) used in cuNumeric. + etc.) used in cuPyNumeric. """, ) warn: PrioritizedSetting[bool] = PrioritizedSetting( "warn", - "CUNUMERIC_WARN", + "CUPYNUMERIC_WARN", default=False, convert=convert_bool, help=""" @@ -49,27 +49,27 @@ class CunumericRuntimeSettings(Settings): report_coverage: PrioritizedSetting[bool] = PrioritizedSetting( "report_coverage", - "CUNUMERIC_REPORT_COVERAGE", + "CUPYNUMERIC_REPORT_COVERAGE", default=False, convert=convert_bool, help=""" - Print an overall percentage of cunumeric coverage. + Print an overall percentage of cupynumeric coverage. """, ) report_dump_callstack: PrioritizedSetting[bool] = PrioritizedSetting( "report_dump_callstack", - "CUNUMERIC_REPORT_DUMP_CALLSTACK", + "CUPYNUMERIC_REPORT_DUMP_CALLSTACK", default=False, convert=convert_bool, help=""" - Print an overall percentage of cunumeric coverage with call stack info. + Print an overall percentage of cupynumeric coverage with a call stack. """, ) report_dump_csv: PrioritizedSetting[str | None] = PrioritizedSetting( "report_dump_csv", - "CUNUMERIC_REPORT_DUMP_CSV", + "CUPYNUMERIC_REPORT_DUMP_CSV", default=None, help=""" Save a coverage report to a specified CSV file. @@ -78,11 +78,11 @@ class CunumericRuntimeSettings(Settings): numpy_compat: PrioritizedSetting[bool] = PrioritizedSetting( "numpy_compat", - "CUNUMERIC_NUMPY_COMPATIBILITY", + "CUPYNUMERIC_NUMPY_COMPATIBILITY", default=False, convert=convert_bool, help=""" - cuNumeric will issue additional tasks to match numpy's results + cuPyNumeric will issue additional tasks to match numpy's results and behavior. This is currently used in the following APIs: nanmin, nanmax, nanargmin, nanargmax """, @@ -90,7 +90,7 @@ class CunumericRuntimeSettings(Settings): fast_math: EnvOnlySetting[int] = EnvOnlySetting( "fast_math", - "CUNUMERIC_FAST_MATH", + "CUPYNUMERIC_FAST_MATH", default=False, convert=convert_bool, help=""" @@ -105,7 +105,7 @@ class CunumericRuntimeSettings(Settings): min_gpu_chunk: EnvOnlySetting[int] = EnvOnlySetting( "min_gpu_chunk", - "CUNUMERIC_MIN_GPU_CHUNK", + "CUPYNUMERIC_MIN_GPU_CHUNK", default=65536, # 1 << 16 test_default=2, convert=convert_int, @@ -121,7 +121,7 @@ class CunumericRuntimeSettings(Settings): min_cpu_chunk: EnvOnlySetting[int] = EnvOnlySetting( "min_cpu_chunk", - "CUNUMERIC_MIN_CPU_CHUNK", + "CUPYNUMERIC_MIN_CPU_CHUNK", default=1024, # 1 << 10 test_default=2, convert=convert_int, @@ -137,7 +137,7 @@ class CunumericRuntimeSettings(Settings): min_omp_chunk: EnvOnlySetting[int] = EnvOnlySetting( "min_omp_chunk", - "CUNUMERIC_MIN_OMP_CHUNK", + "CUPYNUMERIC_MIN_OMP_CHUNK", default=8192, # 1 << 13 test_default=2, convert=convert_int, @@ -153,15 +153,15 @@ class CunumericRuntimeSettings(Settings): force_thunk: EnvOnlySetting[str | None] = EnvOnlySetting( "force_thunk", - "CUNUMERIC_FORCE_THUNK", + "CUPYNUMERIC_FORCE_THUNK", default=None, test_default="deferred", help=""" - Force cuNumeric to always use a specific strategy for backing + Force cuPyNumeric to always use a specific strategy for backing ndarrays: "deferred", i.e. managed by the Legate runtime, which enables distribution and accelerated operations, but has some up-front offloading overhead, or "eager", i.e. falling back to - using a vanilla NumPy array. By default cuNumeric will decide + using a vanilla NumPy array. By default cuPyNumeric will decide this on a per-array basis, based on the size of the array and the accelerator in use. @@ -171,12 +171,12 @@ class CunumericRuntimeSettings(Settings): matmul_cache_size: EnvOnlySetting[int] = EnvOnlySetting( "matmul_cache_size", - "CUNUMERIC_MATMUL_CACHE_SIZE", + "CUPYNUMERIC_MATMUL_CACHE_SIZE", default=134217728, # 128MB test_default=4096, # 4KB convert=convert_int, help=""" - Force cuNumeric to keep temporary task slices during matmul + Force cuPyNumeric to keep temporary task slices during matmul computations smaller than this threshold. Whenever the temporary space needed during computation would exceed this value the task will be batched over 'k' to fulfill the requirement. @@ -186,4 +186,4 @@ class CunumericRuntimeSettings(Settings): ) -settings = CunumericRuntimeSettings() +settings = CupynumericRuntimeSettings() diff --git a/cunumeric/types.py b/cupynumeric/types.py similarity index 100% rename from cunumeric/types.py rename to cupynumeric/types.py diff --git a/cupynumeric_cpp.cmake b/cupynumeric_cpp.cmake new file mode 100644 index 0000000000..f9d7cbb011 --- /dev/null +++ b/cupynumeric_cpp.cmake @@ -0,0 +1,565 @@ +#============================================================================= +# Copyright 2024 NVIDIA Corporation +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +#============================================================================= + +############################################################################## +# - User Options ------------------------------------------------------------ + +option(BUILD_SHARED_LIBS "Build cuPyNumeric shared libraries" ON) +option(cupynumeric_EXCLUDE_TBLIS_FROM_ALL "Exclude tblis targets from cuPyNumeric's 'all' target" OFF) +option(cupynumeric_EXCLUDE_OPENBLAS_FROM_ALL "Exclude OpenBLAS targets from cuPyNumeric's 'all' target" OFF) +option(cupynumeric_EXCLUDE_LEGATE_FROM_ALL "Exclude legate targets from cuPyNumeric's 'all' target" OFF) + +############################################################################## +# - Project definition ------------------------------------------------------- + +# Write the version header +rapids_cmake_write_version_file(include/cupynumeric/version_config.hpp) + +# Needed to integrate with LLVM/clang tooling +set(CMAKE_EXPORT_COMPILE_COMMANDS ON) + +############################################################################## +# - Build Type --------------------------------------------------------------- + +# Set a default build type if none was specified +rapids_cmake_build_type(Release) + +############################################################################## +# - conda environment -------------------------------------------------------- + +rapids_cmake_support_conda_env(conda_env MODIFY_PREFIX_PATH) + +# We're building python extension libraries, which must always be installed +# under lib/, even if the system normally uses lib64/. Rapids-cmake currently +# doesn't realize this when we're going through scikit-build, see +# https://github.com/rapidsai/rapids-cmake/issues/426 +if(TARGET conda_env) + set(CMAKE_INSTALL_LIBDIR "lib") +endif() + +############################################################################## +# - Dependencies ------------------------------------------------------------- + +# add third party dependencies using CPM +rapids_cpm_init(OVERRIDE ${CMAKE_CURRENT_SOURCE_DIR}/cmake/versions.json) + +rapids_find_package(OpenMP GLOBAL_TARGETS OpenMP::OpenMP_CXX) + +option(Legion_USE_CUDA "Use CUDA" ON) +option(Legion_USE_OpenMP "Use OpenMP" ${OpenMP_FOUND}) +option(Legion_BOUNDS_CHECKS "Build cuPyNumeric with bounds checks (expensive)" OFF) + +# If legate has CUDA support, then including it in a project will automatically call +# enable_language(CUDA). However, this does not play nice with the rapids-cmake CUDA utils +# which support a wider range of values for CMAKE_CUDA_ARCHITECTURES than cmake does. You +# end up with the following error: +# +# CMAKE_CUDA_ARCHITECTURES: +# +# RAPIDS +# +# is not one of the following: +# +# * a semicolon-separated list of integers, each optionally +# followed by '-real' or '-virtual' +# * a special value: all, all-major, native +# +set(cmake_cuda_arch_backup "${CMAKE_CUDA_ARCHITECTURES}") +set(cmake_cuda_arch_cache_backup "$CACHE{CMAKE_CUDA_ARCHITECTURES}") +if(("${CMAKE_CUDA_ARCHITECTURES}" STREQUAL "RAPIDS") OR ("${CMAKE_CUDA_ARCHITECTURES}" STREQUAL "NATIVE")) + unset(CMAKE_CUDA_ARCHITECTURES) + unset(CMAKE_CUDA_ARCHITECTURES CACHE) +endif() + +### +# If we find legate already configured on the system, it will report +# whether it was compiled with bounds checking (Legion_BOUNDS_CHECKS), +# CUDA (Legion_USE_CUDA), and OpenMP (Legion_USE_OpenMP). +# +# We use the same variables as legate because we want to enable/disable +# each of these features based on how legate was configured (it doesn't +# make sense to build cuPyNumeric's CUDA bindings if legate wasn't built +# with CUDA support). +### +include(cmake/thirdparty/get_legate.cmake) + +set(CMAKE_CUDA_ARCHITECTURES "${cmake_cuda_arch_cache_backup}" CACHE STRING "" FORCE) +set(CMAKE_CUDA_ARCHITECTURES "${cmake_cuda_arch_backup}") +unset(cmake_cuda_arch_backup) +unset(cmake_cuda_arch_cache_backup) + +if(Legion_USE_CUDA) + include(${CMAKE_CURRENT_SOURCE_DIR}/cmake/Modules/cuda_arch_helpers.cmake) + # Needs to run before `rapids_cuda_init_architectures` + set_cuda_arch_from_names() + # Needs to run before `enable_language(CUDA)` + rapids_cuda_init_architectures(cupynumeric) + enable_language(CUDA) + # Since cupynumeric only enables CUDA optionally we need to manually include + # the file that rapids_cuda_init_architectures relies on `project` calling + if(CMAKE_PROJECT_cupynumeric_INCLUDE) + include("${CMAKE_PROJECT_cupynumeric_INCLUDE}") + endif() + + # Must come after enable_language(CUDA) + # Use `-isystem ` instead of `-isystem=` + # because the former works with clangd intellisense + set(CMAKE_INCLUDE_SYSTEM_FLAG_CUDA "-isystem ") + + rapids_find_package( + CUDAToolkit REQUIRED + BUILD_EXPORT_SET cupynumeric-exports + INSTALL_EXPORT_SET cupynumeric-exports + ) + + include(cmake/thirdparty/get_nccl.cmake) + include(cmake/thirdparty/get_cutensor.cmake) +endif() + +include(cmake/thirdparty/get_openblas.cmake) + +include(cmake/thirdparty/get_tblis.cmake) + +############################################################################## +# - cuPyNumeric ---------------------------------------------------------------- + +set(cupynumeric_SOURCES "") +set(cupynumeric_CXX_DEFS "") +set(cupynumeric_CUDA_DEFS "") +set(cupynumeric_CXX_OPTIONS "") +set(cupynumeric_CUDA_OPTIONS "") + +include(cmake/Modules/set_cpu_arch_flags.cmake) +set_cpu_arch_flags(cupynumeric_CXX_OPTIONS) + +# Add `src/cupynumeric.mk` sources +list(APPEND cupynumeric_SOURCES + src/cupynumeric/ternary/where.cc + src/cupynumeric/scan/scan_global.cc + src/cupynumeric/scan/scan_local.cc + src/cupynumeric/binary/binary_op.cc + src/cupynumeric/binary/binary_op_util.cc + src/cupynumeric/binary/binary_red.cc + src/cupynumeric/bits/packbits.cc + src/cupynumeric/bits/unpackbits.cc + src/cupynumeric/unary/scalar_unary_red.cc + src/cupynumeric/unary/unary_op.cc + src/cupynumeric/unary/unary_red.cc + src/cupynumeric/unary/convert.cc + src/cupynumeric/nullary/arange.cc + src/cupynumeric/nullary/eye.cc + src/cupynumeric/nullary/fill.cc + src/cupynumeric/nullary/window.cc + src/cupynumeric/index/advanced_indexing.cc + src/cupynumeric/index/choose.cc + src/cupynumeric/index/putmask.cc + src/cupynumeric/index/repeat.cc + src/cupynumeric/index/select.cc + src/cupynumeric/index/wrap.cc + src/cupynumeric/index/zip.cc + src/cupynumeric/item/read.cc + src/cupynumeric/item/write.cc + src/cupynumeric/matrix/batched_cholesky.cc + src/cupynumeric/matrix/contract.cc + src/cupynumeric/matrix/diag.cc + src/cupynumeric/matrix/gemm.cc + src/cupynumeric/matrix/matmul.cc + src/cupynumeric/matrix/matvecmul.cc + src/cupynumeric/matrix/dot.cc + src/cupynumeric/matrix/potrf.cc + src/cupynumeric/matrix/qr.cc + src/cupynumeric/matrix/solve.cc + src/cupynumeric/matrix/svd.cc + src/cupynumeric/matrix/syrk.cc + src/cupynumeric/matrix/tile.cc + src/cupynumeric/matrix/transpose.cc + src/cupynumeric/matrix/trilu.cc + src/cupynumeric/matrix/trsm.cc + src/cupynumeric/matrix/util.cc + src/cupynumeric/random/bitgenerator.cc + src/cupynumeric/random/randutil/generator_host.cc + src/cupynumeric/random/randutil/generator_host_straightforward.cc + src/cupynumeric/random/randutil/generator_host_advanced.cc + src/cupynumeric/random/rand.cc + src/cupynumeric/search/argwhere.cc + src/cupynumeric/search/nonzero.cc + src/cupynumeric/set/unique.cc + src/cupynumeric/set/unique_reduce.cc + src/cupynumeric/stat/bincount.cc + src/cupynumeric/convolution/convolve.cc + src/cupynumeric/transform/flip.cc + src/cupynumeric/utilities/repartition.cc + src/cupynumeric/arg_redop_register.cc + src/cupynumeric/mapper.cc + src/cupynumeric/ndarray.cc + src/cupynumeric/operators.cc + src/cupynumeric/runtime.cc + src/cupynumeric/cephes/chbevl.cc + src/cupynumeric/cephes/i0.cc + src/cupynumeric/stat/histogram.cc +) + +if(Legion_USE_OpenMP) + list(APPEND cupynumeric_SOURCES + src/cupynumeric/ternary/where_omp.cc + src/cupynumeric/scan/scan_global_omp.cc + src/cupynumeric/scan/scan_local_omp.cc + src/cupynumeric/binary/binary_op_omp.cc + src/cupynumeric/binary/binary_red_omp.cc + src/cupynumeric/bits/packbits_omp.cc + src/cupynumeric/bits/unpackbits_omp.cc + src/cupynumeric/unary/unary_op_omp.cc + src/cupynumeric/unary/scalar_unary_red_omp.cc + src/cupynumeric/unary/unary_red_omp.cc + src/cupynumeric/unary/convert_omp.cc + src/cupynumeric/nullary/arange_omp.cc + src/cupynumeric/nullary/eye_omp.cc + src/cupynumeric/nullary/fill_omp.cc + src/cupynumeric/nullary/window_omp.cc + src/cupynumeric/index/advanced_indexing_omp.cc + src/cupynumeric/index/choose_omp.cc + src/cupynumeric/index/putmask_omp.cc + src/cupynumeric/index/repeat_omp.cc + src/cupynumeric/index/select_omp.cc + src/cupynumeric/index/wrap_omp.cc + src/cupynumeric/index/zip_omp.cc + src/cupynumeric/matrix/batched_cholesky_omp.cc + src/cupynumeric/matrix/contract_omp.cc + src/cupynumeric/matrix/diag_omp.cc + src/cupynumeric/matrix/gemm_omp.cc + src/cupynumeric/matrix/matmul_omp.cc + src/cupynumeric/matrix/matvecmul_omp.cc + src/cupynumeric/matrix/dot_omp.cc + src/cupynumeric/matrix/potrf_omp.cc + src/cupynumeric/matrix/qr_omp.cc + src/cupynumeric/matrix/solve_omp.cc + src/cupynumeric/matrix/svd_omp.cc + src/cupynumeric/matrix/syrk_omp.cc + src/cupynumeric/matrix/tile_omp.cc + src/cupynumeric/matrix/transpose_omp.cc + src/cupynumeric/matrix/trilu_omp.cc + src/cupynumeric/matrix/trsm_omp.cc + src/cupynumeric/random/rand_omp.cc + src/cupynumeric/search/argwhere_omp.cc + src/cupynumeric/search/nonzero_omp.cc + src/cupynumeric/set/unique_omp.cc + src/cupynumeric/set/unique_reduce_omp.cc + src/cupynumeric/stat/bincount_omp.cc + src/cupynumeric/convolution/convolve_omp.cc + src/cupynumeric/transform/flip_omp.cc + src/cupynumeric/stat/histogram_omp.cc + ) +endif() + +if(Legion_USE_CUDA) + list(APPEND cupynumeric_SOURCES + src/cupynumeric/ternary/where.cu + src/cupynumeric/scan/scan_global.cu + src/cupynumeric/scan/scan_local.cu + src/cupynumeric/binary/binary_op.cu + src/cupynumeric/binary/binary_red.cu + src/cupynumeric/bits/packbits.cu + src/cupynumeric/bits/unpackbits.cu + src/cupynumeric/unary/scalar_unary_red.cu + src/cupynumeric/unary/unary_red.cu + src/cupynumeric/unary/unary_op.cu + src/cupynumeric/unary/convert.cu + src/cupynumeric/nullary/arange.cu + src/cupynumeric/nullary/eye.cu + src/cupynumeric/nullary/fill.cu + src/cupynumeric/nullary/window.cu + src/cupynumeric/index/advanced_indexing.cu + src/cupynumeric/index/choose.cu + src/cupynumeric/index/putmask.cu + src/cupynumeric/index/repeat.cu + src/cupynumeric/index/select.cu + src/cupynumeric/index/wrap.cu + src/cupynumeric/index/zip.cu + src/cupynumeric/item/read.cu + src/cupynumeric/item/write.cu + src/cupynumeric/matrix/batched_cholesky.cu + src/cupynumeric/matrix/contract.cu + src/cupynumeric/matrix/diag.cu + src/cupynumeric/matrix/gemm.cu + src/cupynumeric/matrix/matmul.cu + src/cupynumeric/matrix/matvecmul.cu + src/cupynumeric/matrix/dot.cu + src/cupynumeric/matrix/potrf.cu + src/cupynumeric/matrix/qr.cu + src/cupynumeric/matrix/solve.cu + src/cupynumeric/matrix/svd.cu + src/cupynumeric/matrix/syrk.cu + src/cupynumeric/matrix/tile.cu + src/cupynumeric/matrix/transpose.cu + src/cupynumeric/matrix/trilu.cu + src/cupynumeric/matrix/trsm.cu + src/cupynumeric/random/rand.cu + src/cupynumeric/search/argwhere.cu + src/cupynumeric/search/nonzero.cu + src/cupynumeric/set/unique.cu + src/cupynumeric/stat/bincount.cu + src/cupynumeric/convolution/convolve.cu + src/cupynumeric/fft/fft.cu + src/cupynumeric/transform/flip.cu + src/cupynumeric/utilities/repartition.cu + src/cupynumeric/arg_redop_register.cu + src/cupynumeric/cudalibs.cu + src/cupynumeric/stat/histogram.cu + ) +endif() + +# Add `src/cupynumeric/sort/sort.mk` sources +list(APPEND cupynumeric_SOURCES + src/cupynumeric/sort/sort.cc + src/cupynumeric/sort/searchsorted.cc +) + +if(Legion_USE_OpenMP) + list(APPEND cupynumeric_SOURCES + src/cupynumeric/sort/sort_omp.cc + src/cupynumeric/sort/searchsorted_omp.cc + ) +endif() + +if(Legion_USE_CUDA) + list(APPEND cupynumeric_SOURCES + src/cupynumeric/sort/sort.cu + src/cupynumeric/sort/searchsorted.cu + src/cupynumeric/sort/cub_sort_bool.cu + src/cupynumeric/sort/cub_sort_int8.cu + src/cupynumeric/sort/cub_sort_int16.cu + src/cupynumeric/sort/cub_sort_int32.cu + src/cupynumeric/sort/cub_sort_int64.cu + src/cupynumeric/sort/cub_sort_uint8.cu + src/cupynumeric/sort/cub_sort_uint16.cu + src/cupynumeric/sort/cub_sort_uint32.cu + src/cupynumeric/sort/cub_sort_uint64.cu + src/cupynumeric/sort/cub_sort_half.cu + src/cupynumeric/sort/cub_sort_float.cu + src/cupynumeric/sort/cub_sort_double.cu + src/cupynumeric/sort/thrust_sort_bool.cu + src/cupynumeric/sort/thrust_sort_int8.cu + src/cupynumeric/sort/thrust_sort_int16.cu + src/cupynumeric/sort/thrust_sort_int32.cu + src/cupynumeric/sort/thrust_sort_int64.cu + src/cupynumeric/sort/thrust_sort_uint8.cu + src/cupynumeric/sort/thrust_sort_uint16.cu + src/cupynumeric/sort/thrust_sort_uint32.cu + src/cupynumeric/sort/thrust_sort_uint64.cu + src/cupynumeric/sort/thrust_sort_half.cu + src/cupynumeric/sort/thrust_sort_float.cu + src/cupynumeric/sort/thrust_sort_double.cu + src/cupynumeric/sort/thrust_sort_complex64.cu + src/cupynumeric/sort/thrust_sort_complex128.cu + ) +endif() + +# Add `src/cupynumeric/random/random.mk` sources +if(Legion_USE_CUDA) + list(APPEND cupynumeric_SOURCES + src/cupynumeric/random/bitgenerator.cu + src/cupynumeric/random/randutil/generator_device.cu + src/cupynumeric/random/randutil/generator_device_straightforward.cu + src/cupynumeric/random/randutil/generator_device_advanced.cu +) +endif() + +# add sources for cusolverMp +if(Legion_USE_CUDA AND CUSOLVERMP_DIR) + list(APPEND cupynumeric_SOURCES + src/cupynumeric/matrix/mp_potrf.cu + src/cupynumeric/matrix/mp_solve.cu + ) +endif() + +list(APPEND cupynumeric_SOURCES + # This must always be the last file! + # It guarantees we do our registration callback + # only after all task variants are recorded + src/cupynumeric/cupynumeric.cc +) + +if(CMAKE_BUILD_TYPE STREQUAL "Debug") + list(APPEND cupynumeric_CXX_DEFS DEBUG_CUPYNUMERIC) + list(APPEND cupynumeric_CUDA_DEFS DEBUG_CUPYNUMERIC) +endif() + +if(Legion_BOUNDS_CHECKS) + list(APPEND cupynumeric_CXX_DEFS BOUNDS_CHECKS) + list(APPEND cupynumeric_CUDA_DEFS BOUNDS_CHECKS) +endif() + +list(APPEND cupynumeric_CUDA_OPTIONS -Xfatbin=-compress-all) +list(APPEND cupynumeric_CUDA_OPTIONS --expt-extended-lambda) +list(APPEND cupynumeric_CUDA_OPTIONS --expt-relaxed-constexpr) +list(APPEND cupynumeric_CXX_OPTIONS -Wno-deprecated-declarations) +list(APPEND cupynumeric_CUDA_OPTIONS -Wno-deprecated-declarations) + +add_library(cupynumeric ${cupynumeric_SOURCES}) +add_library(cupynumeric::cupynumeric ALIAS cupynumeric) + +if (CMAKE_SYSTEM_NAME STREQUAL "Linux") + set(platform_rpath_origin "\$ORIGIN") +elseif (CMAKE_SYSTEM_NAME STREQUAL "Darwin") + set(platform_rpath_origin "@loader_path") +endif () + +set_target_properties(cupynumeric + PROPERTIES BUILD_RPATH "${platform_rpath_origin}" + INSTALL_RPATH "${platform_rpath_origin}" + CXX_STANDARD 17 + CXX_STANDARD_REQUIRED ON + POSITION_INDEPENDENT_CODE ON + INTERFACE_POSITION_INDEPENDENT_CODE ON + CUDA_STANDARD 17 + CUDA_STANDARD_REQUIRED ON + LIBRARY_OUTPUT_DIRECTORY lib) + +target_link_libraries(cupynumeric + PUBLIC legate::legate + $ + PRIVATE BLAS::BLAS + tblis::tblis + # Add Conda library and include paths + $ + $ + $ + $ + $ + $) + +if(NOT Legion_USE_CUDA AND cupynumeric_cuRAND_INCLUDE_DIR) + list(APPEND cupynumeric_CXX_DEFS CUPYNUMERIC_CURAND_FOR_CPU_BUILD) + target_include_directories(cupynumeric PRIVATE ${cupynumeric_cuRAND_INCLUDE_DIR}) +endif() + +if(Legion_USE_CUDA AND CUSOLVERMP_DIR) + message(VERBOSE "cupynumeric: CUSOLVERMP_DIR ${CUSOLVERMP_DIR}") + list(APPEND cupynumeric_CXX_DEFS CUPYNUMERIC_USE_CUSOLVERMP) + list(APPEND cupynumeric_CUDA_DEFS CUPYNUMERIC_USE_CUSOLVERMP) + target_include_directories(cupynumeric PRIVATE ${CUSOLVERMP_DIR}/include) + target_link_libraries(cupynumeric PRIVATE ${CUSOLVERMP_DIR}/lib/libcusolverMp.so) +endif() + +target_compile_options(cupynumeric + PRIVATE "$<$:${cupynumeric_CXX_OPTIONS}>" + "$<$:${cupynumeric_CUDA_OPTIONS}>") + +target_compile_definitions(cupynumeric + PUBLIC "$<$:${cupynumeric_CXX_DEFS}>" + "$<$:${cupynumeric_CUDA_DEFS}>") + +target_include_directories(cupynumeric + PUBLIC + $ + INTERFACE + $ +) + +if(Legion_USE_CUDA) + file(WRITE "${CMAKE_CURRENT_BINARY_DIR}/fatbin.ld" +[=[ +SECTIONS +{ +.nvFatBinSegment : { *(.nvFatBinSegment) } +.nv_fatbin : { *(.nv_fatbin) } +} +]=]) + + # ensure CUDA symbols aren't relocated to the middle of the debug build binaries + target_link_options(cupynumeric PRIVATE "${CMAKE_CURRENT_BINARY_DIR}/fatbin.ld") +endif() + +############################################################################## +# - install targets----------------------------------------------------------- + +include(CPack) +include(GNUInstallDirs) +rapids_cmake_install_lib_dir(lib_dir) + +install(TARGETS cupynumeric + DESTINATION ${lib_dir} + EXPORT cupynumeric-exports) + +install( + FILES src/cupynumeric.h + ${CMAKE_CURRENT_BINARY_DIR}/include/cupynumeric/version_config.hpp + DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/cupynumeric) + +install( + FILES src/cupynumeric/cupynumeric_c.h + src/cupynumeric/ndarray.h + src/cupynumeric/ndarray.inl + src/cupynumeric/operators.h + src/cupynumeric/operators.inl + src/cupynumeric/runtime.h + src/cupynumeric/slice.h + src/cupynumeric/typedefs.h + DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/cupynumeric/cupynumeric) + +if(cupynumeric_INSTALL_TBLIS) + install(DIRECTORY ${tblis_BINARY_DIR}/lib/ DESTINATION ${lib_dir}) + install(DIRECTORY ${tblis_BINARY_DIR}/include/ DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}) +endif() + +############################################################################## +# - install export ----------------------------------------------------------- + +set(doc_string + [=[ +Provide targets for cuPyNumeric, an aspiring drop-in replacement for NumPy at scale. + +Imported Targets: + - cupynumeric::cupynumeric + +]=]) + +string(JOIN "\n" code_string + "set(Legion_USE_CUDA ${Legion_USE_CUDA})" + "set(Legion_USE_OpenMP ${Legion_USE_OpenMP})" + "set(Legion_BOUNDS_CHECKS ${Legion_BOUNDS_CHECKS})" +) + +if(DEFINED Legion_USE_Python) + string(APPEND code_string "\nset(Legion_USE_Python ${Legion_USE_Python})") +endif() + +if(DEFINED Legion_NETWORKS) + string(APPEND code_string "\nset(Legion_NETWORKS ${Legion_NETWORKS})") +endif() + +rapids_export( + INSTALL cupynumeric + EXPORT_SET cupynumeric-exports + GLOBAL_TARGETS cupynumeric + NAMESPACE cupynumeric:: + DOCUMENTATION doc_string + FINAL_CODE_BLOCK code_string) + +# build export targets +rapids_export( + BUILD cupynumeric + EXPORT_SET cupynumeric-exports + GLOBAL_TARGETS cupynumeric + NAMESPACE cupynumeric:: + DOCUMENTATION doc_string + FINAL_CODE_BLOCK code_string) + +if(cupynumeric_BUILD_TESTS) + include(CTest) + + add_subdirectory(tests/cpp) +endif() diff --git a/cunumeric_python.cmake b/cupynumeric_python.cmake similarity index 69% rename from cunumeric_python.cmake rename to cupynumeric_python.cmake index 3c4b891cfd..1be5b35c62 100644 --- a/cunumeric_python.cmake +++ b/cupynumeric_python.cmake @@ -17,25 +17,25 @@ ############################################################################## # - User Options ------------------------------------------------------------ -option(FIND_CUNUMERIC_CPP "Search for existing cuNumeric C++ installations before defaulting to local files" +option(FIND_CUPYNUMERIC_CPP "Search for existing cuPyNumeric C++ installations before defaulting to local files" OFF) ############################################################################## # - Dependencies ------------------------------------------------------------- -# If the user requested it we attempt to find cunumeric. -if(FIND_CUNUMERIC_CPP) +# If the user requested it we attempt to find cupynumeric. +if(FIND_CUPYNUMERIC_CPP) include("${rapids-cmake-dir}/export/detail/parse_version.cmake") - rapids_export_parse_version(${cunumeric_version} cunumeric parsed_ver) - rapids_find_package(cunumeric ${parsed_ver} EXACT CONFIG - GLOBAL_TARGETS cunumeric::cunumeric - BUILD_EXPORT_SET cunumeric-python-exports - INSTALL_EXPORT_SET cunumeric-python-exports) + rapids_export_parse_version(${cupynumeric_version} cupynumeric parsed_ver) + rapids_find_package(cupynumeric ${parsed_ver} EXACT CONFIG + GLOBAL_TARGETS cupynumeric::cupynumeric + BUILD_EXPORT_SET cupynumeric-python-exports + INSTALL_EXPORT_SET cupynumeric-python-exports) else() - set(cunumeric_FOUND OFF) + set(cupynumeric_FOUND OFF) endif() -if(NOT cunumeric_FOUND) +if(NOT cupynumeric_FOUND) set(SKBUILD OFF) set(Legion_USE_Python ON) set(Legion_BUILD_BINDINGS ON) @@ -51,9 +51,9 @@ add_custom_target("generate_install_info_py" ALL VERBATIM ) -add_library(cunumeric_python INTERFACE) -add_library(cunumeric::cunumeric_python ALIAS cunumeric_python) -target_link_libraries(cunumeric_python INTERFACE legate::legate) +add_library(cupynumeric_python INTERFACE) +add_library(cupynumeric::cupynumeric_python ALIAS cupynumeric_python) +target_link_libraries(cupynumeric_python INTERFACE legate::legate) # ############################################################################ # - conda environment -------------------------------------------------------- @@ -75,37 +75,37 @@ include(CPack) include(GNUInstallDirs) rapids_cmake_install_lib_dir(lib_dir) -install(TARGETS cunumeric_python +install(TARGETS cupynumeric_python DESTINATION ${lib_dir} - EXPORT cunumeric-python-exports) + EXPORT cupynumeric-python-exports) ############################################################################## # - install export ----------------------------------------------------------- set(doc_string [=[ -Provide Python targets for cuNumeric, an aspiring drop-in replacement for NumPy at scale. +Provide Python targets for cuPyNumeric, an aspiring drop-in replacement for NumPy at scale. Imported Targets: - - cunumeric::cunumeric_python + - cupynumeric::cupynumeric_python ]=]) set(code_string "") rapids_export( - INSTALL cunumeric_python - EXPORT_SET cunumeric-python-exports - GLOBAL_TARGETS cunumeric_python - NAMESPACE cunumeric:: + INSTALL cupynumeric_python + EXPORT_SET cupynumeric-python-exports + GLOBAL_TARGETS cupynumeric_python + NAMESPACE cupynumeric:: DOCUMENTATION doc_string FINAL_CODE_BLOCK code_string) # build export targets rapids_export( - BUILD cunumeric_python - EXPORT_SET cunumeric-python-exports - GLOBAL_TARGETS cunumeric_python - NAMESPACE cunumeric:: + BUILD cupynumeric_python + EXPORT_SET cupynumeric-python-exports + GLOBAL_TARGETS cupynumeric_python + NAMESPACE cupynumeric:: DOCUMENTATION doc_string FINAL_CODE_BLOCK code_string) diff --git a/docs/cunumeric/source/api/comparison.rst b/docs/cunumeric/source/api/comparison.rst deleted file mode 100644 index 139a02d76e..0000000000 --- a/docs/cunumeric/source/api/comparison.rst +++ /dev/null @@ -1,12 +0,0 @@ -Project comparisons -=================== - -Here is a list of NumPy APIs and corresponding cuNumeric implementations. - -A dot in the cunumeric column denotes that cuNumeric implementation -is not provided yet. We welcome contributions for these functions. - -NumPy vs cuNumeric APIs ------------------------ - -.. comparison-table:: diff --git a/docs/cunumeric/source/api/settings.rst b/docs/cunumeric/source/api/settings.rst deleted file mode 100644 index abc807f0b4..0000000000 --- a/docs/cunumeric/source/api/settings.rst +++ /dev/null @@ -1,8 +0,0 @@ -Settings -======== - -cuNumeric has a number of runtime settings that can be configured through -environment variables. - -.. settings:: settings - :module: cunumeric.settings \ No newline at end of file diff --git a/docs/cunumeric/source/developer/CONTRIBUTING.md b/docs/cunumeric/source/developer/CONTRIBUTING.md deleted file mode 120000 index 069558fad2..0000000000 --- a/docs/cunumeric/source/developer/CONTRIBUTING.md +++ /dev/null @@ -1 +0,0 @@ -../../../../CONTRIBUTING.md \ No newline at end of file diff --git a/docs/cunumeric/switcher.json b/docs/cunumeric/switcher.json deleted file mode 100644 index 2a0dbc64ca..0000000000 --- a/docs/cunumeric/switcher.json +++ /dev/null @@ -1,52 +0,0 @@ -[ - { - "name": "22.05", - "version": "22.05", - "url": "https://nv-legate.github.io/cunumeric/22.05/" - }, - { - "name": "22.08", - "version": "22.08", - "url": "https://nv-legate.github.io/cunumeric/22.08/" - }, - { - "name": "22.10", - "version": "22.10", - "url": "https://nv-legate.github.io/cunumeric/22.10/" - }, - { - "name": "23.01", - "version": "23.01", - "url": "https://nv-legate.github.io/cunumeric/23.01/" - }, - { - "name": "23.03", - "version": "23.03", - "url": "https://nv-legate.github.io/cunumeric/23.03/" - }, - { - "name": "23.07", - "version": "23.07", - "url": "https://nv-legate.github.io/cunumeric/23.07/" - }, - { - "name": "23.09", - "version": "23.09", - "url": "https://nv-legate.github.io/cunumeric/23.09/" - }, - { - "name": "23.11", - "version": "23.11", - "url": "https://nv-legate.github.io/cunumeric/23.11/" - }, - { - "name": "24.06", - "version": "24.06", - "url": "https://docs.nvidia.com/cunumeric/24.06/" - }, - { - "name": "24.11", - "version": "24.11", - "url": "https://docs.nvidia.com/cunumeric/24.11/" - } -] \ No newline at end of file diff --git a/docs/cunumeric/Makefile b/docs/cupynumeric/Makefile similarity index 100% rename from docs/cunumeric/Makefile rename to docs/cupynumeric/Makefile diff --git a/docs/cunumeric/make.bat b/docs/cupynumeric/make.bat similarity index 100% rename from docs/cunumeric/make.bat rename to docs/cupynumeric/make.bat diff --git a/docs/cunumeric/source/_images/developer-build.png b/docs/cupynumeric/source/_images/developer-build.png similarity index 100% rename from docs/cunumeric/source/_images/developer-build.png rename to docs/cupynumeric/source/_images/developer-build.png diff --git a/docs/cunumeric/source/_implemented.rst b/docs/cupynumeric/source/_implemented.rst similarity index 73% rename from docs/cunumeric/source/_implemented.rst rename to docs/cupynumeric/source/_implemented.rst index f3a76189da..03181433ed 100644 --- a/docs/cunumeric/source/_implemented.rst +++ b/docs/cupynumeric/source/_implemented.rst @@ -1,4 +1,4 @@ -.. This page exists to collect references to all cunumeric functions and +.. This page exists to collect references to all cupynumeric functions and .. methods that are "implemented". Doing so, any implemented functions or .. methods that are not present in the docs (but should be) will result in .. docs build errors diff --git a/docs/cunumeric/source/_static/.keep b/docs/cupynumeric/source/_static/.keep similarity index 100% rename from docs/cunumeric/source/_static/.keep rename to docs/cupynumeric/source/_static/.keep diff --git a/docs/cunumeric/source/_templates/layout.html b/docs/cupynumeric/source/_templates/layout.html similarity index 100% rename from docs/cunumeric/source/_templates/layout.html rename to docs/cupynumeric/source/_templates/layout.html diff --git a/docs/cunumeric/source/api/_bitgenerator.rst b/docs/cupynumeric/source/api/_bitgenerator.rst similarity index 65% rename from docs/cunumeric/source/api/_bitgenerator.rst rename to docs/cupynumeric/source/api/_bitgenerator.rst index 32854eff96..e269e9872e 100644 --- a/docs/cunumeric/source/api/_bitgenerator.rst +++ b/docs/cupynumeric/source/api/_bitgenerator.rst @@ -1,7 +1,7 @@ -cunumeric.random.BitGenerator +cupynumeric.random.BitGenerator ============================= -.. currentmodule:: cunumeric.random +.. currentmodule:: cupynumeric.random .. autoclass:: BitGenerator diff --git a/docs/cunumeric/source/api/_generator.rst b/docs/cupynumeric/source/api/_generator.rst similarity index 65% rename from docs/cunumeric/source/api/_generator.rst rename to docs/cupynumeric/source/api/_generator.rst index 539a3c0014..c734812327 100644 --- a/docs/cunumeric/source/api/_generator.rst +++ b/docs/cupynumeric/source/api/_generator.rst @@ -1,7 +1,7 @@ -cunumeric.random.Generator +cupynumeric.random.Generator ========================== -.. currentmodule:: cunumeric.random +.. currentmodule:: cupynumeric.random .. autoclass:: Generator diff --git a/docs/cunumeric/source/api/_grouped.rst b/docs/cupynumeric/source/api/_grouped.rst similarity index 100% rename from docs/cunumeric/source/api/_grouped.rst rename to docs/cupynumeric/source/api/_grouped.rst diff --git a/docs/cunumeric/source/api/_ndarray.rst b/docs/cupynumeric/source/api/_ndarray.rst similarity index 96% rename from docs/cunumeric/source/api/_ndarray.rst rename to docs/cupynumeric/source/api/_ndarray.rst index 8e3f03de7d..ea6b57a328 100644 --- a/docs/cunumeric/source/api/_ndarray.rst +++ b/docs/cupynumeric/source/api/_ndarray.rst @@ -1,7 +1,7 @@ -cunumeric.ndarray +cupynumeric.ndarray ================= -.. currentmodule:: cunumeric +.. currentmodule:: cupynumeric .. autoclass:: ndarray diff --git a/docs/cunumeric/source/api/binary.rst b/docs/cupynumeric/source/api/binary.rst similarity index 90% rename from docs/cunumeric/source/api/binary.rst rename to docs/cupynumeric/source/api/binary.rst index 237fdc071c..38b0260ab8 100644 --- a/docs/cunumeric/source/api/binary.rst +++ b/docs/cupynumeric/source/api/binary.rst @@ -1,7 +1,7 @@ Binary operations ================= -.. currentmodule:: cunumeric +.. currentmodule:: cupynumeric Elementwise bit operations -------------------------- diff --git a/docs/cunumeric/source/api/broadcast.rst b/docs/cupynumeric/source/api/broadcast.rst similarity index 52% rename from docs/cunumeric/source/api/broadcast.rst rename to docs/cupynumeric/source/api/broadcast.rst index 50d329a2e8..9e093e79ea 100644 --- a/docs/cunumeric/source/api/broadcast.rst +++ b/docs/cupynumeric/source/api/broadcast.rst @@ -1,6 +1,6 @@ -.. currentmodule:: cunumeric +.. currentmodule:: cupynumeric -cunumeric.broadcast +cupynumeric.broadcast =================== .. autoclass:: broadcast diff --git a/docs/cunumeric/source/api/classes.rst b/docs/cupynumeric/source/api/classes.rst similarity index 100% rename from docs/cunumeric/source/api/classes.rst rename to docs/cupynumeric/source/api/classes.rst diff --git a/docs/cupynumeric/source/api/comparison.rst b/docs/cupynumeric/source/api/comparison.rst new file mode 100644 index 0000000000..db552f54e6 --- /dev/null +++ b/docs/cupynumeric/source/api/comparison.rst @@ -0,0 +1,12 @@ +Project comparisons +=================== + +Here is a list of NumPy APIs and corresponding cuPyNumeric implementations. + +A dot in the cupynumeric column denotes that cuPyNumeric implementation +is not provided yet. We welcome contributions for these functions. + +NumPy vs cuPyNumeric APIs +----------------------- + +.. comparison-table:: diff --git a/docs/cunumeric/source/api/creation.rst b/docs/cupynumeric/source/api/creation.rst similarity index 94% rename from docs/cunumeric/source/api/creation.rst rename to docs/cupynumeric/source/api/creation.rst index 153db24475..e35f6ab4cb 100644 --- a/docs/cunumeric/source/api/creation.rst +++ b/docs/cupynumeric/source/api/creation.rst @@ -1,7 +1,7 @@ Array creation routines ======================= -.. currentmodule:: cunumeric +.. currentmodule:: cupynumeric From shape or value ------------------- diff --git a/docs/cunumeric/source/api/datatype.rst b/docs/cupynumeric/source/api/datatype.rst similarity index 81% rename from docs/cunumeric/source/api/datatype.rst rename to docs/cupynumeric/source/api/datatype.rst index 1e4d521e95..bb5667fd05 100644 --- a/docs/cunumeric/source/api/datatype.rst +++ b/docs/cupynumeric/source/api/datatype.rst @@ -1,7 +1,7 @@ Data type routines ================== -.. currentmodule:: cunumeric +.. currentmodule:: cupynumeric Data type testing ----------------- diff --git a/docs/cunumeric/source/api/fft.rst b/docs/cupynumeric/source/api/fft.rst similarity index 85% rename from docs/cunumeric/source/api/fft.rst rename to docs/cupynumeric/source/api/fft.rst index 4dce08d136..8a656a2538 100644 --- a/docs/cunumeric/source/api/fft.rst +++ b/docs/cupynumeric/source/api/fft.rst @@ -1,6 +1,6 @@ -.. module:: cunumeric.fft +.. module:: cupynumeric.fft -Discrete Fourier Transform (:mod:`cunumeric.fft`) +Discrete Fourier Transform (:mod:`cupynumeric.fft`) ================================================== Standard FFTs diff --git a/docs/cunumeric/source/api/index.rst b/docs/cupynumeric/source/api/index.rst similarity index 77% rename from docs/cunumeric/source/api/index.rst rename to docs/cupynumeric/source/api/index.rst index ea740628ec..d57ccba21e 100644 --- a/docs/cunumeric/source/api/index.rst +++ b/docs/cupynumeric/source/api/index.rst @@ -1,7 +1,7 @@ API Reference ============= -.. currentmodule:: cunumeric +.. currentmodule:: cupynumeric .. toctree:: :maxdepth: 2 diff --git a/docs/cunumeric/source/api/indexing.rst b/docs/cupynumeric/source/api/indexing.rst similarity index 95% rename from docs/cunumeric/source/api/indexing.rst rename to docs/cupynumeric/source/api/indexing.rst index 2723a2d317..3468e893ee 100644 --- a/docs/cunumeric/source/api/indexing.rst +++ b/docs/cupynumeric/source/api/indexing.rst @@ -1,7 +1,7 @@ Indexing routines ================= -.. currentmodule:: cunumeric +.. currentmodule:: cupynumeric Generating index arrays ----------------------- diff --git a/docs/cunumeric/source/api/io.rst b/docs/cupynumeric/source/api/io.rst similarity index 82% rename from docs/cunumeric/source/api/io.rst rename to docs/cupynumeric/source/api/io.rst index 0fd4ee4b3a..a5ba6f6709 100644 --- a/docs/cunumeric/source/api/io.rst +++ b/docs/cupynumeric/source/api/io.rst @@ -1,7 +1,7 @@ Input and output ================ -.. currentmodule:: cunumeric +.. currentmodule:: cupynumeric NumPy binary files (npy, npz) ----------------------------- diff --git a/docs/cunumeric/source/api/linalg.rst b/docs/cupynumeric/source/api/linalg.rst similarity index 85% rename from docs/cunumeric/source/api/linalg.rst rename to docs/cupynumeric/source/api/linalg.rst index 5d94889803..0ccd9d4e67 100644 --- a/docs/cunumeric/source/api/linalg.rst +++ b/docs/cupynumeric/source/api/linalg.rst @@ -1,9 +1,9 @@ -.. module:: cunumeric.linalg +.. module:: cupynumeric.linalg -Linear algebra (:mod:`cunumeric.linalg`) +Linear algebra (:mod:`cupynumeric.linalg`) ======================================== -.. currentmodule:: cunumeric +.. currentmodule:: cupynumeric Matrix and vector products -------------------------- diff --git a/docs/cunumeric/source/api/logic.rst b/docs/cupynumeric/source/api/logic.rst similarity index 95% rename from docs/cunumeric/source/api/logic.rst rename to docs/cupynumeric/source/api/logic.rst index abc016c653..1ab6c7873c 100644 --- a/docs/cunumeric/source/api/logic.rst +++ b/docs/cupynumeric/source/api/logic.rst @@ -1,7 +1,7 @@ Logic functions =============== -.. currentmodule:: cunumeric +.. currentmodule:: cupynumeric Truth value testing ------------------- diff --git a/docs/cunumeric/source/api/manipulation.rst b/docs/cupynumeric/source/api/manipulation.rst similarity index 93% rename from docs/cunumeric/source/api/manipulation.rst rename to docs/cupynumeric/source/api/manipulation.rst index 6f8bf6f33f..b1d3f54c32 100644 --- a/docs/cunumeric/source/api/manipulation.rst +++ b/docs/cupynumeric/source/api/manipulation.rst @@ -1,7 +1,7 @@ Array manipulation routines =========================== -.. currentmodule:: cunumeric +.. currentmodule:: cupynumeric Basic operations ---------------- @@ -32,7 +32,7 @@ Transpose-like operations swapaxes transpose -See also :attr:`cunumeric.ndarray.T` property. +See also :attr:`cupynumeric.ndarray.T` property. Changing number of dimensions ----------------------------- diff --git a/docs/cunumeric/source/api/math.rst b/docs/cupynumeric/source/api/math.rst similarity index 98% rename from docs/cunumeric/source/api/math.rst rename to docs/cupynumeric/source/api/math.rst index ef40212852..5764a93727 100644 --- a/docs/cunumeric/source/api/math.rst +++ b/docs/cupynumeric/source/api/math.rst @@ -1,7 +1,7 @@ Mathematical functions ====================== -.. currentmodule:: cunumeric +.. currentmodule:: cupynumeric Trigonometric functions ----------------------- diff --git a/docs/cunumeric/source/api/ndarray.rst b/docs/cupynumeric/source/api/ndarray.rst similarity index 98% rename from docs/cunumeric/source/api/ndarray.rst rename to docs/cupynumeric/source/api/ndarray.rst index 4efec7e0a1..a9d17a648b 100644 --- a/docs/cunumeric/source/api/ndarray.rst +++ b/docs/cupynumeric/source/api/ndarray.rst @@ -1,6 +1,6 @@ -.. currentmodule:: cunumeric +.. currentmodule:: cupynumeric -The N-Dimensional array (:class:`cunumeric.ndarray`) +The N-Dimensional array (:class:`cupynumeric.ndarray`) ==================================================== Constructing arrays diff --git a/docs/cunumeric/source/api/random.rst b/docs/cupynumeric/source/api/random.rst similarity index 93% rename from docs/cunumeric/source/api/random.rst rename to docs/cupynumeric/source/api/random.rst index 0cf5a61a99..22036b5349 100644 --- a/docs/cunumeric/source/api/random.rst +++ b/docs/cupynumeric/source/api/random.rst @@ -1,6 +1,6 @@ -.. module:: cunumeric.random +.. module:: cupynumeric.random -Random sampling (:mod:`cunumeric.random`) +Random sampling (:mod:`cupynumeric.random`) ========================================= Random Generator diff --git a/docs/cunumeric/source/api/routines.rst b/docs/cupynumeric/source/api/routines.rst similarity index 100% rename from docs/cunumeric/source/api/routines.rst rename to docs/cupynumeric/source/api/routines.rst diff --git a/docs/cunumeric/source/api/set.rst b/docs/cupynumeric/source/api/set.rst similarity index 79% rename from docs/cunumeric/source/api/set.rst rename to docs/cupynumeric/source/api/set.rst index c4299e870d..e797379d13 100644 --- a/docs/cunumeric/source/api/set.rst +++ b/docs/cupynumeric/source/api/set.rst @@ -1,7 +1,7 @@ Set routines ============ -.. currentmodule:: cunumeric +.. currentmodule:: cupynumeric Making proper sets ------------------ diff --git a/docs/cupynumeric/source/api/settings.rst b/docs/cupynumeric/source/api/settings.rst new file mode 100644 index 0000000000..6a424f0fbc --- /dev/null +++ b/docs/cupynumeric/source/api/settings.rst @@ -0,0 +1,8 @@ +Settings +======== + +cuPyNumeric has a number of runtime settings that can be configured through +environment variables. + +.. settings:: settings + :module: cupynumeric.settings \ No newline at end of file diff --git a/docs/cunumeric/source/api/sorting.rst b/docs/cupynumeric/source/api/sorting.rst similarity index 93% rename from docs/cunumeric/source/api/sorting.rst rename to docs/cupynumeric/source/api/sorting.rst index 86d8e65dc0..ab5570cfde 100644 --- a/docs/cunumeric/source/api/sorting.rst +++ b/docs/cupynumeric/source/api/sorting.rst @@ -1,7 +1,7 @@ Sorting, searching, and counting ================================ -.. currentmodule:: cunumeric +.. currentmodule:: cupynumeric Sorting ------- diff --git a/docs/cunumeric/source/api/statistics.rst b/docs/cupynumeric/source/api/statistics.rst similarity index 94% rename from docs/cunumeric/source/api/statistics.rst rename to docs/cupynumeric/source/api/statistics.rst index 5fb0cdc95f..9430ea3240 100644 --- a/docs/cunumeric/source/api/statistics.rst +++ b/docs/cupynumeric/source/api/statistics.rst @@ -1,7 +1,7 @@ Statistics ========== -.. currentmodule:: cunumeric +.. currentmodule:: cupynumeric Order statistics ---------------- diff --git a/docs/cunumeric/source/api/window.rst b/docs/cupynumeric/source/api/window.rst similarity index 85% rename from docs/cunumeric/source/api/window.rst rename to docs/cupynumeric/source/api/window.rst index 28058d21fd..e50dc58984 100644 --- a/docs/cunumeric/source/api/window.rst +++ b/docs/cupynumeric/source/api/window.rst @@ -1,7 +1,7 @@ Window functions ====================== -.. currentmodule:: cunumeric +.. currentmodule:: cupynumeric Various windows ----------------------- diff --git a/docs/cunumeric/source/conf.py b/docs/cupynumeric/source/conf.py similarity index 87% rename from docs/cunumeric/source/conf.py rename to docs/cupynumeric/source/conf.py index 442a3edbb7..a0dcd2af8b 100644 --- a/docs/cunumeric/source/conf.py +++ b/docs/cupynumeric/source/conf.py @@ -15,15 +15,15 @@ from os import getenv -from cunumeric import __version__ +from cupynumeric import __version__ -SWITCHER_PROD = "https://docs.nvidia.com/cunumeric/switcher.json" +SWITCHER_PROD = "https://docs.nvidia.com/cupynumeric/switcher.json" SWITCHER_DEV = "http://localhost:8000/switcher.json" JSON_URL = SWITCHER_DEV if getenv("SWITCHER_DEV") == "1" else SWITCHER_PROD # -- Project information ----------------------------------------------------- -project = "NVIDIA cuNumeric" +project = "NVIDIA cuPyNumeric" if "dev" in __version__: project += f" ({__version__})" copyright = "2024, NVIDIA" @@ -42,10 +42,10 @@ "myst_parser", "nbsphinx", "legate._sphinxext.settings", - "cunumeric._sphinxext.comparison_table", - "cunumeric._sphinxext.implemented_index", - "cunumeric._sphinxext.missing_refs", - "cunumeric._sphinxext.ufunc_formatter", + "cupynumeric._sphinxext.comparison_table", + "cupynumeric._sphinxext.implemented_index", + "cupynumeric._sphinxext.missing_refs", + "cupynumeric._sphinxext.ufunc_formatter", ] source_suffix = {".rst": "restructuredtext", ".md": "markdown"} @@ -55,7 +55,7 @@ html_context = { # "default_mode": "light", "AUTHOR": author, - "DESCRIPTION": "cuNumeric documentation site.", + "DESCRIPTION": "cuPyNumeric documentation site.", } html_static_path = ["_static"] diff --git a/docs/cupynumeric/source/developer/CONTRIBUTING.md b/docs/cupynumeric/source/developer/CONTRIBUTING.md new file mode 100644 index 0000000000..8dacfa72c3 --- /dev/null +++ b/docs/cupynumeric/source/developer/CONTRIBUTING.md @@ -0,0 +1,72 @@ +# Contributing to cuPyNumeric + +cuPyNumeric is an open-source project released under the [Apache license, version 2.0](https://www.apache.org/licenses/LICENSE-2.0). We welcome any and all contributions, and we hope that you can help us develop a strong community. + +## How to begin + +Most of the time, the best thing is to begin by [opening an issue](https://github.com/nv-legate/cupynumeric/issues). This gives us a chance to discuss the contribution and to define the problem or feature that it addresses. Often, opening of the issue first may help prevent you from doing unnecessary work or to enhance and further develop your idea. + +Once you are ready to start development, we ask you to work on a [fork](https://docs.github.com/en/get-started/quickstart/fork-a-repo) of our repository. The next step is to create a (pull request)[https://help.github.com/en/articles/about-pull-requests]. Feel free to open the pull request as soon as you begin your development (just mark it [as a draft](https://github.blog/2019-02-14-introducing-draft-pull-requests/)) or when you are ready to have your contribution merged. + +## The Legalese: Developer Certificate of Origin + +cuPyNumeric is released under the open-source [Apache license, version 2.0](https://www.apache.org/licenses/LICENSE-2.0), and is free to use, modify, and redistribute. To ensure that the license can be exercised without encumbrance, we ask you that you only contribute your own work or work to which you have the intellectual rights. To that end, we employ the Developer's Certificate of Origin (DCO), which is the lightweight mechanism for you to certify that you are legally able to make your contribution. Here is the full text of the certificate (also available at [DeveloperCertificate.org](https://developercertificate.org/): + +```` +Developer Certificate of Origin +Version 1.1 + +Copyright (C) 2004, 2006 The Linux Foundation and its contributors. + +Everyone is permitted to copy and distribute verbatim copies of this +license document, but changing it is not allowed. + + +Developer's Certificate of Origin 1.1 + +By making a contribution to this project, I certify that: + +(a) The contribution was created in whole or in part by me and I + have the right to submit it under the open source license + indicated in the file; or + +(b) The contribution is based upon previous work that, to the best + of my knowledge, is covered under an appropriate open source + license and I have the right under that license to submit that + work with modifications, whether created in whole or in part + by me, under the same open source license (unless I am + permitted to submit under a different license), as indicated + in the file; or + +(c) The contribution was provided directly to me by some other + person who certified (a), (b) or (c) and I have not modified + it. + +(d) I understand and agree that this project and the contribution + are public and that a record of the contribution (including all + personal information I submit with it, including my sign-off) is + maintained indefinitely and may be redistributed consistent with + this project or the open source license(s) involved. +```` + +### How Do I Sign the DCO? + +Fortunately, it does not take much work to sign the DCO. The only thing that you have to do is to mark all your commits with a `Signed-off-by` line that looks like that: + +```` +Signed-off-by: Your Name +```` + +Please use your real name and a valid email address at which you can be reached. For legal reasons, we will not be able to accept contributions that use pseudonyms in the signature. You can simply add this line at the end of all your commits manually, or you can use the `-s` or the `--signoff` options provided by Git to automatically tack on the signature. + +## Review Process + +We are really grateful that you are thinking of contributing to cuPyNumeric. We will make every effort to review your contributions as soon as possible. + +As we suggested at the beginning of this document, it will be really helpful to start with an issue unless your proposed change is really trivial. An issue will help to save work in the review process (e.g., maybe somebody is already working on exactly the same thing you want to work on). After you open your pull request (PR), there usually will be a community feedback that often will require further changes to your contribution (the usual open-source process). Usually, this will conclude in the PR being merged by a maintainer, but on rare occasions a PR may be rejected. This may happen, for example, if the PR appears abandoned (no response to the community feedback) or if the PR does not seem to be approaching community acceptance in a reasonable time frame. In any case, an explanation will always be given why a PR is closed. Even if a PR is closed for some reason, it may always be reopened if the situation evolves (feel free to comment on closed PRs to discuss reopening them). + +## Code Formatting Requirements + +cuPyNumeric has a set of coding standards that are expected from all the code merged into the project. The coding standards are defined by the set of tools we use to format our code. We use the [pre-commit](https://pre-commit.com/) framework to run our formatting tools. The easiest way to meet the coding standards is to simply use the pre-commit framework to run all the checks for you. Please visit the [pre-commit project page](https://pre-commit.com/) for pre-commit installation and usage instructions. Once pre-commit is installed in the cuPyNumeric repo, all the checks and formatting will be run on every commit, but one can also run the checks explicitly as detailed in pre-commit documentation. + +We hope that the automation of our formatting checks will make it easy to comply with our coding standards. If you encounter problems with code formatting, however, please let us know in a comment on your PR, and we will do our best to help. diff --git a/docs/cunumeric/source/developer/building.rst b/docs/cupynumeric/source/developer/building.rst similarity index 70% rename from docs/cunumeric/source/developer/building.rst rename to docs/cupynumeric/source/developer/building.rst index b4ba151e99..9c4f747650 100644 --- a/docs/cunumeric/source/developer/building.rst +++ b/docs/cupynumeric/source/developer/building.rst @@ -1,4 +1,4 @@ -.. _building cunumeric from source: +.. _building cupynumeric from source: Building from source ==================== @@ -7,15 +7,15 @@ Basic build ----------- Users must have a working installation of the `Legate`_ library prior to -installing cuNumeric. -**Installing cuNumeric by itself will not automatically install Legate.** +installing cuPyNumeric. +**Installing cuPyNumeric by itself will not automatically install Legate.** As for other dependencies, the Dependencies section on the -`Legate build instructions`_ also covers cuNumeric, so no additional +`Legate build instructions`_ also covers cuPyNumeric, so no additional packages are required. Once Legate is installed, you can simply invoke ``./install.py`` from the -cuNumeric top-level directory. The build will automatically pick up the +cuPyNumeric top-level directory. The build will automatically pick up the configuration used when building Legate (e.g. the CUDA Toolkit directory). Advanced topics @@ -24,7 +24,7 @@ Advanced topics Building through pip & cmake ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -cuNumeric uses the same cmake/scikit-build-based build workflow as Legate. +cuPyNumeric uses the same cmake/scikit-build-based build workflow as Legate. See the `Legate build instructions`_ for an overview. There are several examples in the ``scripts`` folder. We walk through the steps in @@ -39,7 +39,7 @@ First, the CMake build needs to be configured: $ cmake -S . -B build -GNinja -D legate_ROOT:STRING=path/to/legate/build -We point cuNumeric to the Legate *build* tree, not an installation. +We point cuPyNumeric to the Legate *build* tree, not an installation. This generates all build-dependent headers and Python files. Once configured, we can build the C++ libraries: @@ -53,18 +53,18 @@ Once the C++ libraries are available, we can do an editable (development) pip in .. code:: sh - $ SKBUILD_BUILD_OPTIONS="-D FIND_CUNUMERIC_CPP=ON -D cunumeric_ROOT=$(pwd)/build" \ + $ SKBUILD_BUILD_OPTIONS="-D FIND_CUPYNUMERIC_CPP=ON -D cupynumeric_ROOT=$(pwd)/build" \ python3 -m pip install \ --root / --no-deps --no-build-isolation --editable . The Python source tree and CMake build tree are now available with the environment Python -for running cuNumeric programs. The diagram below illustrates the -complete workflow for building both Legate and cuNumeric. +for running cuPyNumeric programs. The diagram below illustrates the +complete workflow for building both Legate and cuPyNumeric. .. image:: /_images/developer-build.png :width: 600 - :alt: "notional diagram of cunumeric build process" + :alt: "notional diagram of cupynumeric build process" .. _Legate: https://github.com/nv-legate/legate.core .. _Legate build instructions: https://github.com/nv-legate/legate.core/blob/HEAD/BUILD.md diff --git a/docs/cunumeric/source/developer/index.rst b/docs/cupynumeric/source/developer/index.rst similarity index 100% rename from docs/cunumeric/source/developer/index.rst rename to docs/cupynumeric/source/developer/index.rst diff --git a/docs/cunumeric/source/developer/testing.rst b/docs/cupynumeric/source/developer/testing.rst similarity index 97% rename from docs/cunumeric/source/developer/testing.rst rename to docs/cupynumeric/source/developer/testing.rst index f5485b7874..55aa39e366 100644 --- a/docs/cunumeric/source/developer/testing.rst +++ b/docs/cupynumeric/source/developer/testing.rst @@ -4,7 +4,7 @@ Running tests Basic usage ----------- -The simplest way to run the cuNumeric test suite is to use the ``test.py`` +The simplest way to run the cuPyNumeric test suite is to use the ``test.py`` test driver script. .. code-block:: sh diff --git a/docs/cunumeric/source/examples/black_scholes.ipynb b/docs/cupynumeric/source/examples/black_scholes.ipynb similarity index 99% rename from docs/cunumeric/source/examples/black_scholes.ipynb rename to docs/cupynumeric/source/examples/black_scholes.ipynb index a091201f63..e5868463a1 100644 --- a/docs/cunumeric/source/examples/black_scholes.ipynb +++ b/docs/cupynumeric/source/examples/black_scholes.ipynb @@ -41,7 +41,7 @@ "id": "5b787e94-e440-4e1c-bd66-29faf9b59041", "metadata": {}, "source": [ - "To get started, `import cunumeric as np` (just the same way we would import `numpy`)" + "To get started, `import cupynumeric as np` (just the same way we would import `numpy`)" ] }, { @@ -51,7 +51,7 @@ "metadata": {}, "outputs": [], "source": [ - "import cunumeric as np # instead of numpy" + "import cupynumeric as np # instead of numpy" ] }, { @@ -162,7 +162,7 @@ "name": "stdout", "output_type": "stream", "text": [ - "/home/bryan/work/legate.core/legate/core/context.py:280: RuntimeWarning: cuNumeric has not implemented numpy.result_type and is falling back to canonical numpy. You may notice significantly decreased performance for this function call.\n", + "/home/bryan/work/legate.core/legate/core/context.py:280: RuntimeWarning: cuPyNumeric has not implemented numpy.result_type and is falling back to canonical numpy. You may notice significantly decreased performance for this function call.\n", " result = func(*args, **kwargs)\n", "Elapsed Time: 45.659 ms\n" ] diff --git a/docs/cunumeric/source/examples/cholesky.ipynb b/docs/cupynumeric/source/examples/cholesky.ipynb similarity index 87% rename from docs/cunumeric/source/examples/cholesky.ipynb rename to docs/cupynumeric/source/examples/cholesky.ipynb index 0e82d20ee2..ee39c6ec07 100644 --- a/docs/cunumeric/source/examples/cholesky.ipynb +++ b/docs/cupynumeric/source/examples/cholesky.ipynb @@ -9,7 +9,7 @@ "\n", "A [Cholesky decomposition](https://en.wikipedia.org/wiki/Cholesky_decomposition) is a useful factorization of Hermitian, positive-definite matrices into the product of a lower triangular matrix $L$ with its conjugate transpose $L^{*}$.\n", "\n", - "Numpy has a function [numpy.linalg.cholesky](https://numpy.org/doc/stable/reference/generated/numpy.linalg.cholesky.html) built-in for computing Cholesky decompositions. Cunumeric also implements this function, and it can be used as an immediate drop-in replacement.\n", + "Numpy has a function [numpy.linalg.cholesky](https://numpy.org/doc/stable/reference/generated/numpy.linalg.cholesky.html) built-in for computing Cholesky decompositions. cuPyNumeric also implements this function, and it can be used as an immediate drop-in replacement.\n", "\n", "
\n", "License\n", @@ -37,7 +37,7 @@ "id": "389cd191-ccda-4597-8e08-8d01ac226bee", "metadata": {}, "source": [ - "To get started, `import cunumeric as np` (just the same way we would import `numpy`)\n" + "To get started, `import cupynumeric as np` (just the same way we would import `numpy`)\n" ] }, { @@ -49,7 +49,7 @@ }, "outputs": [], "source": [ - "import cunumeric as np # instead of numpy" + "import cupynumeric as np # instead of numpy" ] }, { @@ -57,7 +57,7 @@ "id": "9ef2bc57-e703-40ce-8aaa-d45408259c7a", "metadata": {}, "source": [ - "At this point we can call `np.linalg.cholesky`, exactly how we would with Numpy, but will get the result computed by Cunumeric's `cholesky` function. Let's quickly try it out with a simple identitity matrix:" + "At this point we can call `np.linalg.cholesky`, exactly how we would with Numpy, but will get the result computed by cuPyNumeric's `cholesky` function. Let's quickly try it out with a simple identitity matrix:" ] }, { @@ -96,7 +96,7 @@ "tags": [] }, "source": [ - "We'd like to get some information about how well Cunumeric's `cholesky` function performs. In order to obtain accurate timings, we need to use the `time` function from `legate.timing`. Let's define a helper function `cholesky_timed` that calls the `time` function for us, and prints out the results as well:" + "We'd like to get some information about how well cuPyNumeric's `cholesky` function performs. In order to obtain accurate timings, we need to use the `time` function from `legate.timing`. Let's define a helper function `cholesky_timed` that calls the `time` function for us, and prints out the results as well:" ] }, { diff --git a/docs/cunumeric/source/examples/compact_finite_difference.ipynb b/docs/cupynumeric/source/examples/compact_finite_difference.ipynb similarity index 100% rename from docs/cunumeric/source/examples/compact_finite_difference.ipynb rename to docs/cupynumeric/source/examples/compact_finite_difference.ipynb diff --git a/docs/cunumeric/source/examples/edge_detection.ipynb b/docs/cupynumeric/source/examples/edge_detection.ipynb similarity index 99% rename from docs/cunumeric/source/examples/edge_detection.ipynb rename to docs/cupynumeric/source/examples/edge_detection.ipynb index 6836020ee9..c83093f02c 100644 --- a/docs/cunumeric/source/examples/edge_detection.ipynb +++ b/docs/cupynumeric/source/examples/edge_detection.ipynb @@ -16,7 +16,7 @@ "## Learning Outcomes\n", "This example identifies edges in an image using Sobol edge detection algorithm and is implemented using NumPy and SciPy. An edge is defined as an abrupt change in intensity of the image. The Sobol edge detection algorithm uses a kernel in each direction to compute derivative of intensity of the image. The gradient of the intensity will help us determine the locations where changes in intensity are abrupt, which can then be used to detect edges in an image.\n", "\n", - "This example uses the following packages in addition to NumPy/cuNumeric: Scipy, Matplotlib, PIL" + "This example uses the following packages in addition to NumPy/cuPyNumeric: Scipy, Matplotlib, PIL" ] }, { @@ -68,7 +68,7 @@ "id": "78273013-cea0-4c28-a376-c3c40e681276", "metadata": {}, "source": [ - "Since NumPy's `convolve` API does not allow two-dimensional arrays and our image is represented in an two-dimensional array, we will use the `convolve` API from SciPy for this example. cuNumeric's implementation of `convolve` permits two-dimensional array and will be used if `cuNumeric` is imported instead of `NumPy`. Try changing the import statement from \"import numpy as np\" to \"import cunumeric as np\"!" + "Since NumPy's `convolve` API does not allow two-dimensional arrays and our image is represented in an two-dimensional array, we will use the `convolve` API from SciPy for this example. cuPyNumeric's implementation of `convolve` permits two-dimensional array and will be used if `cuPyNumeric` is imported instead of `NumPy`. Try changing the import statement from \"import numpy as np\" to \"import cupynumeric as np\"!" ] }, { @@ -85,7 +85,7 @@ " kernel: ndarray\n", " Kernel to compute the gradient in x or y as per Sobel Edge Detector\n", " mode: str\n", - " The default convolution mode. Note that cuNumeric only\n", + " The default convolution mode. Note that cuPyNumeric only\n", " supports the convolution mode \"same\".\n", "\n", " Notes:\n", @@ -95,7 +95,7 @@ " The image was taken from:\n", " https://docs.nvidia.com/vpi/algo_canny_edge_detector.html\n", " \"\"\"\n", - " if np.__name__ == \"cunumeric\":\n", + " if np.__name__ == \"cupynumeric\":\n", " return np.convolve(array, kernel, mode)\n", " return convolve(array, kernel, mode)" ] diff --git a/docs/cunumeric/source/examples/image.png b/docs/cupynumeric/source/examples/image.png similarity index 100% rename from docs/cunumeric/source/examples/image.png rename to docs/cupynumeric/source/examples/image.png diff --git a/docs/cunumeric/source/examples/index.rst b/docs/cupynumeric/source/examples/index.rst similarity index 100% rename from docs/cunumeric/source/examples/index.rst rename to docs/cupynumeric/source/examples/index.rst diff --git a/docs/cunumeric/source/examples/kmeans.ipynb b/docs/cupynumeric/source/examples/kmeans.ipynb similarity index 99% rename from docs/cunumeric/source/examples/kmeans.ipynb rename to docs/cupynumeric/source/examples/kmeans.ipynb index 5118b4ef18..29e78003c0 100644 --- a/docs/cunumeric/source/examples/kmeans.ipynb +++ b/docs/cupynumeric/source/examples/kmeans.ipynb @@ -14,7 +14,7 @@ "metadata": {}, "source": [ "## Learning Outcomes\n", - "This example teaches how to implement k-means clustering algorithm using NumPy and is based on the k-means example in cuNumeric. \n", + "This example teaches how to implement k-means clustering algorithm using NumPy and is based on the k-means example in cuPyNumeric. \n", "\n", "In this example, you will learn:\n", "* how to compute pairwise distances using `newaxis`\n", diff --git a/docs/cunumeric/source/examples/newton_raphson_2d.ipynb b/docs/cupynumeric/source/examples/newton_raphson_2d.ipynb similarity index 91% rename from docs/cunumeric/source/examples/newton_raphson_2d.ipynb rename to docs/cupynumeric/source/examples/newton_raphson_2d.ipynb index 3ab628a284..43a7edf747 100644 --- a/docs/cunumeric/source/examples/newton_raphson_2d.ipynb +++ b/docs/cupynumeric/source/examples/newton_raphson_2d.ipynb @@ -16,7 +16,7 @@ "## Learning Outcomes\n", "This example teaches how to compute the solution for systems of equations in two variables using NumPy. There are two equations, $f_{1}(x,y)$ and $f_{2}(x, y)$, with two variables each, $x$ and $y$. We seek to find a solution that satisfies these two equations using Newton's method. To understand Newton's method in multiple dimensions, please see [this](https://wiki.math.ntnu.no/_media/tma4125/2017v/newton.pdf) note by Markus Grasmair.\n", "\n", - "The example also teaches how to interpret a warning from cuNumeric when the import statement is changed from importing numpy to importing cuNumeric.\n", + "The example also teaches how to interpret a warning from cuPyNumeric when the import statement is changed from importing numpy to importing cuPyNumeric.\n", "\n", "---" ] @@ -106,15 +106,15 @@ "id": "a91752f1-5ca8-44dd-9a26-525cdf87ab51", "metadata": {}, "source": [ - "When you switch the import statement from importing to importing cunumeric, you might see a warning like this:\n", + "When you switch the import statement from importing to importing cupynumeric, you might see a warning like this:\n", "\n", "---\n", "\n", - "*RuntimeWarning: cuNumeric has not implemented inv and is falling back to canonical NumPy. You may notice significantly decreased performance for this function call.*\n", + "*RuntimeWarning: cuPyNumeric has not implemented inv and is falling back to canonical NumPy. You may notice significantly decreased performance for this function call.*\n", "\n", "---\n", "\n", - "This means that cuNumeric has not implemented the `linalg.inv` API and is falling back to NumPy's implementation. This means that the API would be *eagerly* executed using NumPy's single-threaded implementation. If the API was intended to be invoked from a GPU, the data will get transferred from the GPU to the CPU before the API is executed. This can have performance implications, as indicated by the warning." + "This means that cuPyNumeric has not implemented the `linalg.inv` API and is falling back to NumPy's implementation. This means that the API would be *eagerly* executed using NumPy's single-threaded implementation. If the API was intended to be invoked from a GPU, the data will get transferred from the GPU to the CPU before the API is executed. This can have performance implications, as indicated by the warning." ] }, { diff --git a/docs/cunumeric/source/examples/stencil.ipynb b/docs/cupynumeric/source/examples/stencil.ipynb similarity index 99% rename from docs/cunumeric/source/examples/stencil.ipynb rename to docs/cupynumeric/source/examples/stencil.ipynb index 95b91744c6..72a635efae 100644 --- a/docs/cunumeric/source/examples/stencil.ipynb +++ b/docs/cupynumeric/source/examples/stencil.ipynb @@ -33,7 +33,7 @@ "id": "35c48e6f-1bde-4aac-af55-b7218cc22491", "metadata": {}, "source": [ - "To get started, `import cunumeric as np` (just the same way we would import `numpy`)\n" + "To get started, `import cupynumeric as np` (just the same way we would import `numpy`)\n" ] }, { @@ -45,7 +45,7 @@ }, "outputs": [], "source": [ - "import cunumeric as np # instead of numpy" + "import cupynumeric as np # instead of numpy" ] }, { diff --git a/docs/cunumeric/source/faqs.rst b/docs/cupynumeric/source/faqs.rst similarity index 82% rename from docs/cunumeric/source/faqs.rst rename to docs/cupynumeric/source/faqs.rst index 553bc16710..1165d1bf22 100644 --- a/docs/cunumeric/source/faqs.rst +++ b/docs/cupynumeric/source/faqs.rst @@ -10,20 +10,20 @@ What are the different task variants available in Legate? Legate offers three different task variants: CPU, OMP, and GPU. A task variant determines the type of processor Legate chooses to perform the computations. -What is the difference between Legate and cuNumeric? +What is the difference between Legate and cuPyNumeric? ---------------------------------------------------- Legate is a task-based runtime software stack that enables development of scalable and composable libraries for distributed and accelerated computing. -cuNumeric is one of the foundational libraries built using Legate and aspires +cuPyNumeric is one of the foundational libraries built using Legate and aspires to be a distributed and accelerated drop-in replacement library for NumPy, an -array programming library widely used in scientific computing. cuNumeric scales +array programming library widely used in scientific computing. cuPyNumeric scales idiomatic NumPy programs to multiple GPUs and CPUs and seamlessly interoperates with other Legate libraries. -Check out this `blog post `_ -to learn more about cuNumeric. +Check out this `blog post `_ +to learn more about cuPyNumeric. When to use python vs legate? ----------------------------- @@ -45,9 +45,9 @@ What does this warning mean? .. code-block:: text - RuntimeWarning: cuNumeric has not implemented and is falling back to canonical NumPy. You may notice significantly decreased performance for this function call. + RuntimeWarning: cuPyNumeric has not implemented and is falling back to canonical NumPy. You may notice significantly decreased performance for this function call. -This means that the NumPy has not been implemented in cuNumeric and that +This means that the NumPy has not been implemented in cuPyNumeric and that the Legate runtime is falling back to using NumPy’s implementation which will be single-threaded execution and can lead to decreased performance for that function call. @@ -101,7 +101,7 @@ How to handle Out-Of-Memory errors? .. code-block:: text - [0 - 7fb9fc426000] 0.985000 {5}{cunumeric.mapper}: Mapper cunumeric on Node 0 failed to allocate 144000000 bytes on memory 1e00000000000000 (of kind SYSTEM_MEM: Visible to all processors on a node) for region requirement 1 of Task cunumeric::WhereTask[./script.py:90] (UID 39). + [0 - 7fb9fc426000] 0.985000 {5}{cupynumeric.mapper}: Mapper cupynumeric on Node 0 failed to allocate 144000000 bytes on memory 1e00000000000000 (of kind SYSTEM_MEM: Visible to all processors on a node) for region requirement 1 of Task cupynumeric::WhereTask[./script.py:90] (UID 39). The above error indicates that the application ran out of memory during execution. More granular details on the type of memory, the task that triggered @@ -121,12 +121,12 @@ Why are the results different from NumPy? While a majority of the APIs will give the same result as NumPy, some APIs might be implemented differently from that of NumPy which might lead to differences in results. One such example is, :ref:`reshape`, which returns a -copy of the array in cuNumeric but returns a view in NumPy. Another example +copy of the array in cuPyNumeric but returns a view in NumPy. Another example is :ref:`astype` which does *not* return a copy by default, where NumPy does. Such differences in implementation are noted in the documentation of the -cuNumeric APIs, please review them before opening an issue on the -`cuNumeric issue tracker `_. +cuPyNumeric APIs, please review them before opening an issue on the +`cuPyNumeric issue tracker `_. Why doesn’t Legate use my GPU? ------------------------------ @@ -148,20 +148,20 @@ How do I time the execution of my application? ---------------------------------------------- Check out the :ref:`benchmarking` section for information on how to accurately -measure cuNumeric execution. +measure cuPyNumeric execution. -Why is cuNumeric slower than NumPy on my laptop? +Why is cuPyNumeric slower than NumPy on my laptop? ------------------------------------------------ -For small problem sizes, cuNumeric might be slower than NumPy. We suggest you +For small problem sizes, cuPyNumeric might be slower than NumPy. We suggest you increase the problem size and correspondingly increase the resources needed for the problem size as described in the Usage section. Take a look at our :ref:`practices` on how to do that. -Why is cuNumeric slower than cuPy on my laptop? +Why is cuPyNumeric slower than cuPy on my laptop? ----------------------------------------------- -For small problem sizes, cuNumeric might be slower than cuPy. We suggest you +For small problem sizes, cuPyNumeric might be slower than cuPy. We suggest you increase the problem size and correspondingly increase the resources needed for the problem size as described in the :ref:`Usage` section. Take a look at performance :ref:`practices`. @@ -194,13 +194,13 @@ Legate's documentation. Are there resources where I can read more about Legate? ------------------------------------------------------- -Check out this `blog post `_ -to learn more about cuNumeric. +Check out this `blog post `_ +to learn more about cuPyNumeric. Technical questions? -------------------- -For technical questions about Cunumeric and Legate-based tools, please visit +For technical questions about cuPyNumeric and Legate-based tools, please visit the `community discussion forum `_. Other questions? diff --git a/docs/cunumeric/source/index.rst b/docs/cupynumeric/source/index.rst similarity index 83% rename from docs/cunumeric/source/index.rst rename to docs/cupynumeric/source/index.rst index 8666081688..c7a8401c6e 100644 --- a/docs/cunumeric/source/index.rst +++ b/docs/cupynumeric/source/index.rst @@ -1,13 +1,13 @@ :html_theme.sidebar_secondary.remove: -NVIDIA cuNumeric +NVIDIA cuPyNumeric ================ -cuNumeric is a `Legate`_ library that aims to provide a distributed and +cuPyNumeric is a `Legate`_ library that aims to provide a distributed and accelerated drop-in replacement for the `NumPy API`_ on top of the `Legion`_ runtime. -Using cuNumeric you do things like run the final example of the +Using cuPyNumeric you do things like run the final example of the `Python CFD course`_ completely unmodified on 2048 A100 GPUs in a `DGX SuperPOD`_ and achieve good weak scaling. diff --git a/docs/cunumeric/source/installation.rst b/docs/cupynumeric/source/installation.rst similarity index 72% rename from docs/cunumeric/source/installation.rst rename to docs/cupynumeric/source/installation.rst index 7e4a3d4720..690fab6c8e 100644 --- a/docs/cunumeric/source/installation.rst +++ b/docs/cupynumeric/source/installation.rst @@ -4,21 +4,21 @@ Installation Default conda install --------------------- -cuNumeric is available from +cuPyNumeric is available from `conda `_ -on the `legate channel `_. +on the `legate channel `_. Please make sure you have at least conda version 24.1 installed, then create -a new environment containing cuNumeric: +a new environment containing cuPyNumeric: .. code-block:: sh - conda create -n myenv -c conda-forge -c legate cunumeric + conda create -n myenv -c conda-forge -c legate cupynumeric or install it into an existing environment: .. code-block:: sh - conda install -c conda-forge -c legate cunumeric + conda install -c conda-forge -c legate cupynumeric Packages with GPU support are available, and will be chosen automatically by ``conda install`` on systems with GPUs. @@ -30,10 +30,10 @@ environment, use environment variable ``CONDA_OVERRIDE_CUDA``: .. code-block:: sh CONDA_OVERRIDE_CUDA="12.2" \ - conda install -c conda-forge -c legate cunumeric + conda install -c conda-forge -c legate cupynumeric Once installed, you can verify the installation by running one of the examples -from the cuNumeric repository, for instance: +from the cuPyNumeric repository, for instance: .. code-block:: sh @@ -44,8 +44,8 @@ from the cuNumeric repository, for instance: Building from source --------------------- -See :ref:`building cunumeric from source` for instructions on building -cuNumeric manually. +See :ref:`building cupynumeric from source` for instructions on building +cuPyNumeric manually. Licenses -------- diff --git a/docs/cunumeric/source/oss-licenses.rst b/docs/cupynumeric/source/oss-licenses.rst similarity index 100% rename from docs/cunumeric/source/oss-licenses.rst rename to docs/cupynumeric/source/oss-licenses.rst diff --git a/docs/cunumeric/source/user/advanced.rst b/docs/cupynumeric/source/user/advanced.rst similarity index 92% rename from docs/cunumeric/source/user/advanced.rst rename to docs/cupynumeric/source/user/advanced.rst index 2fdd96d974..b6bbc31fc6 100644 --- a/docs/cunumeric/source/user/advanced.rst +++ b/docs/cupynumeric/source/user/advanced.rst @@ -9,7 +9,7 @@ Multi-node execution Using ``legate`` ~~~~~~~~~~~~~~~~ -Cunumeric programs can be run in parallel by using the ``--nodes`` option to +cuPyNumeric programs can be run in parallel by using the ``--nodes`` option to the ``legate`` driver, followed by the number of nodes to be used. When running on 2+ nodes, a task launcher must be specified. diff --git a/docs/cunumeric/source/user/differences.rst b/docs/cupynumeric/source/user/differences.rst similarity index 77% rename from docs/cunumeric/source/user/differences.rst rename to docs/cupynumeric/source/user/differences.rst index 5195ccdd37..efab90df11 100644 --- a/docs/cunumeric/source/user/differences.rst +++ b/docs/cupynumeric/source/user/differences.rst @@ -3,10 +3,10 @@ Differences with Numpy Supported shapes and datatypes ------------------------------ -cuNumeric natively supports arrays of dimensionality only up to the maximum +cuPyNumeric natively supports arrays of dimensionality only up to the maximum number of dimensions supported by the linked build of Legate. -cuNumeric natively supports only numerical datatypes, and doesn't support +cuPyNumeric natively supports only numerical datatypes, and doesn't support extended-precision floats (e.g. `np.float128`). Trying to use an unsupported number of dimensions or datatype will trigger a @@ -15,7 +15,7 @@ fallback to base NumPy. Returning a copy instead of a view ---------------------------------- -Some functions that return a view in Numpy return a copy in cuNumeric. These +Some functions that return a view in Numpy return a copy in cuPyNumeric. These include: * ``np.diag`` @@ -46,21 +46,21 @@ Scalar return values -------------------- NumPy will occasionally convert a 0d array to a python-level scalar, but -cuNumeric avoids doing that, because in our system an array value can +cuPyNumeric avoids doing that, because in our system an array value can potentially represent an asynchronous computation. As a result, sometimes -cuNumeric will return 0d arrays (possibly deferred), in cases where NumPy +cuPyNumeric will return 0d arrays (possibly deferred), in cases where NumPy returns a scalar. Indexing behavior ----------------- -``x[:,True]`` works differently from NumPy. cuNumeric broadcasts it up to the +``x[:,True]`` works differently from NumPy. cuPyNumeric broadcasts it up to the corresponding dimension, whereas NumPy adds a dimension. Additionally ``[]`` does not work for advanced indexing since ``[]`` is ``float64`` by default. -cuNumeric doesn't support non-unit steps on index expressions, e.g. `arr[::2]`. +cuPyNumeric doesn't support non-unit steps on index expressions, e.g. `arr[::2]`. Duplicate indices on advanced indexing expressions produce undefined behavior. This is also the case in NumPy but the current NumPy implementation happens diff --git a/docs/cunumeric/source/user/howtos/benchmarking.rst b/docs/cupynumeric/source/user/howtos/benchmarking.rst similarity index 94% rename from docs/cunumeric/source/user/howtos/benchmarking.rst rename to docs/cupynumeric/source/user/howtos/benchmarking.rst index f744e10683..2be87f8483 100644 --- a/docs/cunumeric/source/user/howtos/benchmarking.rst +++ b/docs/cupynumeric/source/user/howtos/benchmarking.rst @@ -7,7 +7,7 @@ Using Legate timing tools ------------------------- Use legate's timing API to measure elapsed time, rather than standard Python -timers. cuNumeric executes work asynchronously when possible, and a standard +timers. cuPyNumeric executes work asynchronously when possible, and a standard Python timer will only measure the time taken to launch the work, not the time spent in actual computation. @@ -18,7 +18,7 @@ Here is an example of how to measure elapsed time in milliseconds: .. code-block:: python - import cunumeric as np + import cupynumeric as np from legate.timing import time init() # Initialization step diff --git a/docs/cunumeric/source/user/howtos/index.rst b/docs/cupynumeric/source/user/howtos/index.rst similarity index 100% rename from docs/cunumeric/source/user/howtos/index.rst rename to docs/cupynumeric/source/user/howtos/index.rst diff --git a/docs/cunumeric/source/user/howtos/jupyter.rst b/docs/cupynumeric/source/user/howtos/jupyter.rst similarity index 100% rename from docs/cunumeric/source/user/howtos/jupyter.rst rename to docs/cupynumeric/source/user/howtos/jupyter.rst diff --git a/docs/cunumeric/source/user/howtos/measuring.rst b/docs/cupynumeric/source/user/howtos/measuring.rst similarity index 56% rename from docs/cunumeric/source/user/howtos/measuring.rst rename to docs/cupynumeric/source/user/howtos/measuring.rst index 3513a86287..4e146a3868 100644 --- a/docs/cunumeric/source/user/howtos/measuring.rst +++ b/docs/cupynumeric/source/user/howtos/measuring.rst @@ -3,42 +3,42 @@ Measure API coverage ==================== -cuNumeric does not currently implment all of NumPy's APIs. If necessary, -cuNumeric will fall back to using NumPy directly to complete a compuation. -When running applications that use cuNumeric, the command line options below +cuPyNumeric does not currently implment all of NumPy's APIs. If necessary, +cuPyNumeric will fall back to using NumPy directly to complete a compuation. +When running applications that use cuPyNumeric, the command line options below may be used to generate coverage reports that show which APIs are implemented -and optimized by cuNumeric and which APIs required falling back to NumPy. +and optimized by cuPyNumeric and which APIs required falling back to NumPy. Overall coverage report ~~~~~~~~~~~~~~~~~~~~~~~ -The environment variable ``CUNUMERIC_REPORT_COVERAGE`` may be used to print an -overall percentage of cunumeric coverage: +The environment variable ``CUPYNUMERIC_REPORT_COVERAGE`` may be used to print an +overall percentage of cupynumeric coverage: .. code-block:: sh - CUNUMERIC_REPORT_COVERAGE=1 legate test.py + CUPYNUMERIC_REPORT_COVERAGE=1 legate test.py After execution completes, the percentage of NumPy API calls that were handled -by cunumeric is printed: +by cupynumeric is printed: .. code-block:: - cuNumeric API coverage: 26/26 (100.0%) + cuPyNumeric API coverage: 26/26 (100.0%) Detailed coverage report ~~~~~~~~~~~~~~~~~~~~~~~~ -The environment variable ``CUNUMERIC_REPORT_DUMP_CSV`` may be used to save a +The environment variable ``CUPYNUMERIC_REPORT_DUMP_CSV`` may be used to save a detailed coverage report: .. code-block:: sh - CUNUMERIC_REPORT_COVERAGE=1 CUNUMERIC_REPORT_DUMP_CSV="out.csv" legate test.py + CUPYNUMERIC_REPORT_COVERAGE=1 CUPYNUMERIC_REPORT_DUMP_CSV="out.csv" legate test.py After execution completes, a CSV file will be saved to the specified location (in this case ``out.csv``). The file shows exactly what NumPy API functions -were called, whether the are implemented by cunumeric, and the location of +were called, whether the are implemented by cupynumeric, and the location of the call site: .. code-block:: @@ -56,12 +56,12 @@ the call site: Call stack reporting ~~~~~~~~~~~~~~~~~~~~ -The environment variable ``CUNUMERIC_REPORT_DUMP_CALLSTACK`` may be added to +The environment variable ``CUPYNUMERIC_REPORT_DUMP_CALLSTACK`` may be added to include full call stack information in a CSV report: .. code-block:: sh - CUNUMERIC_REPORT_COVERAGE=1 CUNUMERIC_REPORT_DUMP_CALLSTACK=1 CUNUMERIC_REPORT_DUMP_CALLSTACK=1 legate test.py + CUPYNUMERIC_REPORT_COVERAGE=1 CUPYNUMERIC_REPORT_DUMP_CALLSTACK=1 CUPYNUMERIC_REPORT_DUMP_CALLSTACK=1 legate test.py After execution completes, the CSV output file have full call stack information in the location column, with individual stack frames separated diff --git a/docs/cunumeric/source/user/howtos/patching.rst b/docs/cupynumeric/source/user/howtos/patching.rst similarity index 64% rename from docs/cunumeric/source/user/howtos/patching.rst rename to docs/cupynumeric/source/user/howtos/patching.rst index cdac9223cf..576e9396c1 100644 --- a/docs/cunumeric/source/user/howtos/patching.rst +++ b/docs/cupynumeric/source/user/howtos/patching.rst @@ -2,7 +2,7 @@ Trying Numpy code without changes ================================= The ``lgpatch`` script (in the same location as the ``legate`` executable) can -help facilitate quick demonstrations of ``cunumeric`` on existing codebases +help facilitate quick demonstrations of ``cupynumeric`` on existing codebases that make use of ``numpy``. To use this tool, invoke it as shown below, with the name of the program to @@ -23,13 +23,13 @@ For example, here is a small ``test.py`` program that imports and uses various input = np.eye(10, dtype=np.float32) np.linalg.cholesky(input) -You can invoke ``lgpatch`` to run ``test.py`` using ``cunumeric`` functions +You can invoke ``lgpatch`` to run ``test.py`` using ``cupynumeric`` functions instead, without any changes to the original source code. Any standard -``cunumeric`` runtime options (e.g. for :ref:`measuring api coverage`) may +``cupynumeric`` runtime options (e.g. for :ref:`measuring api coverage`) may also be used: .. code-block:: sh - $ CUNUMERIC_REPORT_COVERAGE=1 LEGATE_CONFIG="--cpus 4" lgpatch test.py -patch numpy - cuNumeric API coverage: 4/4 (100.0%) + $ CUPYNUMERIC_REPORT_COVERAGE=1 LEGATE_CONFIG="--cpus 4" lgpatch test.py -patch numpy + cuPyNumeric API coverage: 4/4 (100.0%) diff --git a/docs/cunumeric/source/user/index.rst b/docs/cupynumeric/source/user/index.rst similarity index 100% rename from docs/cunumeric/source/user/index.rst rename to docs/cupynumeric/source/user/index.rst diff --git a/docs/cunumeric/source/user/practices.rst b/docs/cupynumeric/source/user/practices.rst similarity index 93% rename from docs/cunumeric/source/user/practices.rst rename to docs/cupynumeric/source/user/practices.rst index c064fe8f6e..263e347ce9 100644 --- a/docs/cunumeric/source/user/practices.rst +++ b/docs/cupynumeric/source/user/practices.rst @@ -8,7 +8,7 @@ General Recommendations Following the basics of numpy as documented `here `_ is highly recommended. -Here we highlight some of the anti-patterns and best practices for cuNumeric +Here we highlight some of the anti-patterns and best practices for cuPyNumeric to avoid commonly encountered problems related to performance. In general, array-based computations are recommended. @@ -16,14 +16,14 @@ Availability of each API (e.g., single CPU or Multiple GPUs/Multiple CPUs, etc.) is noted in the docstring of the API. This would be useful to know while designing the application since it can impact the scalability. -Guidelines on using cuNumeric APIs +Guidelines on using cuPyNumeric APIs ---------------------------------- -Use cuNumeric or NumPy arrays, AVOID native lists +Use cuPyNumeric or NumPy arrays, AVOID native lists ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -Create a cuNumeric array from data structures native to Python like lists, -tuples, etc., and operate on the cuNumeric array, as shown in the example +Create a cuPyNumeric array from data structures native to Python like lists, +tuples, etc., and operate on the cuPyNumeric array, as shown in the example below. Find more details on this here: .. https://numpy.org/doc/stable/user/basics.creation.html @@ -37,7 +37,7 @@ below. Find more details on this here: for val in x: y.append(val + 2) - # Recommended: Create a cuNumeric array and use array-based operations + # Recommended: Create a cuPyNumeric array and use array-based operations y = np.array(x) y = x + 2 @@ -48,7 +48,7 @@ thus performing an array-based operation. .. code-block:: python - import cunumeric as np + import cupynumeric as np def transform(input): return (input + 3) * 4 @@ -121,7 +121,7 @@ performance. .. code-block:: python - import cunumeric as np + import cupynumeric as np # Not recommended: don't use nonzero to get indices indices = np.nonzero(h < 0) @@ -141,7 +141,7 @@ condition is met, which can be described using the ``putmask`` API. .. code-block:: python - import cunumeric as np + import cupynumeric as np # We need to update elements of x from y based on a condition cond = y < tol @@ -177,12 +177,12 @@ Use mathematical functions, AVOID element-wise loops ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ When there are nested element-wise operations, it is recommended that they -are translated to array-based operations using equivalent cuNumeric APIs, if +are translated to array-based operations using equivalent cuPyNumeric APIs, if possible. Here is an example: .. code-block:: python - import cunumeric as np + import cupynumeric as np # Not recommended: Naive element-wise implementation for i in range(ny): @@ -208,14 +208,14 @@ can also make it run slower, so we recommend using it as sparingly as possible. .. code-block:: python - import cunumeric as np + import cupynumeric as np x = np.ones((3,4)) y = x.reshape((12,)) y[0] = 42 - assert x[0,0] == 42 # succeeds in NumPy, fails in cuNumeric + assert x[0,0] == 42 # succeeds in NumPy, fails in cuPyNumeric Stack results in a performance penalty ...................................... @@ -231,7 +231,7 @@ Faster I/O Routines As of 23.07, we recommend using `h5py `_ to perform I/O. -Guidelines on designing cuNumeric applications +Guidelines on designing cuPyNumeric applications ---------------------------------------------- Use output arguments to reduce memory allocation @@ -242,7 +242,7 @@ intermediate array in our implementation. .. code-block:: python - import cunumeric as np + import cupynumeric as np # Acceptable x = x + y @@ -338,10 +338,10 @@ here. .. code-block:: python - import cunumeric as np + import cupynumeric as np # compute() does some computations and returns a multi-dimensional - # cuNumeric array. The application stops after the iterative computation + # cuPyNumeric array. The application stops after the iterative computation # is converged # Acceptable: Performing convergence checks every iteration diff --git a/docs/cunumeric/source/user/usage.rst b/docs/cupynumeric/source/user/usage.rst similarity index 79% rename from docs/cunumeric/source/user/usage.rst rename to docs/cupynumeric/source/user/usage.rst index 3cec1c12d3..aebdad2763 100644 --- a/docs/cunumeric/source/user/usage.rst +++ b/docs/cupynumeric/source/user/usage.rst @@ -3,8 +3,8 @@ Usage ===== -Using cuNumeric as a replacement for NumPy is simple. Replace your NumPy import -statement with cuNumeric: +Using cuPyNumeric as a replacement for NumPy is simple. Replace your NumPy import +statement with cuPyNumeric: .. code-block:: python @@ -14,7 +14,7 @@ becomes .. code-block:: python - import cunumeric as np + import cupynumeric as np Then, run the application like you usually do. For example, if you had a script ``main.py`` written in NumPy that adds two vectors, @@ -27,11 +27,11 @@ Then, run the application like you usually do. For example, if you had a script z = x + y print(z) -change the import statement to use cuNumeric like below, +change the import statement to use cuPyNumeric like below, .. code-block:: python - import cunumeric as np + import cupynumeric as np x = np.array([1.0, 2.0, 3.0, 4.0]) y = np.array([4.0, 3.0, 2.0, 1.0]) z = x + y diff --git a/docs/cupynumeric/switcher.json b/docs/cupynumeric/switcher.json new file mode 100644 index 0000000000..ed1ae384e9 --- /dev/null +++ b/docs/cupynumeric/switcher.json @@ -0,0 +1,52 @@ +[ + { + "name": "22.05", + "version": "22.05", + "url": "https://nv-legate.github.io/cupynumeric/22.05/" + }, + { + "name": "22.08", + "version": "22.08", + "url": "https://nv-legate.github.io/cupynumeric/22.08/" + }, + { + "name": "22.10", + "version": "22.10", + "url": "https://nv-legate.github.io/cupynumeric/22.10/" + }, + { + "name": "23.01", + "version": "23.01", + "url": "https://nv-legate.github.io/cupynumeric/23.01/" + }, + { + "name": "23.03", + "version": "23.03", + "url": "https://nv-legate.github.io/cupynumeric/23.03/" + }, + { + "name": "23.07", + "version": "23.07", + "url": "https://nv-legate.github.io/cupynumeric/23.07/" + }, + { + "name": "23.09", + "version": "23.09", + "url": "https://nv-legate.github.io/cupynumeric/23.09/" + }, + { + "name": "23.11", + "version": "23.11", + "url": "https://nv-legate.github.io/cupynumeric/23.11/" + }, + { + "name": "24.06", + "version": "24.06", + "url": "https://docs.nvidia.com/cupynumeric/24.06/" + }, + { + "name": "24.11", + "version": "24.11", + "url": "https://docs.nvidia.com/cupynumeric/24.11/" + } +] \ No newline at end of file diff --git a/examples/benchmark.py b/examples/benchmark.py index d882e120fb..98ae1249dd 100644 --- a/examples/benchmark.py +++ b/examples/benchmark.py @@ -32,7 +32,7 @@ def stop(self): ... -class CuNumericTimer(Timer): +class CuPyNumericTimer(Timer): def __init__(self): self._start_time = None @@ -112,9 +112,9 @@ def parse_args(parser): ) args, _ = parser.parse_known_args() if args.package == "legate": - import cunumeric as np + import cupynumeric as np - timer = CuNumericTimer() + timer = CuPyNumericTimer() elif args.package == "cupy": import cupy as np diff --git a/examples/cpp/stencil/CMakeLists.txt b/examples/cpp/stencil/CMakeLists.txt index d17920c4a3..3def9488f9 100644 --- a/examples/cpp/stencil/CMakeLists.txt +++ b/examples/cpp/stencil/CMakeLists.txt @@ -22,10 +22,10 @@ if (NOT CMAKE_CXX_STANDARD) set(CMAKE_CXX_STANDARD 17) endif() -find_package(cunumeric REQUIRED) +find_package(cupynumeric REQUIRED) add_executable(stencil stencil.cc) -target_link_libraries(stencil PRIVATE cunumeric::cunumeric) +target_link_libraries(stencil PRIVATE cupynumeric::cupynumeric) install(TARGETS stencil DESTINATION "${CMAKE_CURRENT_BINARY_DIR}/cmake-install") diff --git a/examples/cpp/stencil/build.sh b/examples/cpp/stencil/build.sh index 485365ae3c..1eac0fe8d9 100755 --- a/examples/cpp/stencil/build.sh +++ b/examples/cpp/stencil/build.sh @@ -16,7 +16,7 @@ legate_root=`python -c 'import legate.install_info as i; from pathlib import Path; print(Path(i.libpath).parent.resolve())'` echo "Using Legate at $legate_root" -cunumeric_root=`python -c 'import cunumeric.install_info as i; from pathlib import Path; print(Path(i.libpath).parent.resolve())'` -echo "Using cuNumeric at $cunumeric_root" -cmake -S . -B build -D legate_ROOT="$legate_root" -D cunumeric_ROOT="$cunumeric_root" -D CMAKE_BUILD_TYPE=Debug +cupynumeric_root=`python -c 'import cupynumeric.install_info as i; from pathlib import Path; print(Path(i.libpath).parent.resolve())'` +echo "Using cuPyNumeric at $cupynumeric_root" +cmake -S . -B build -D legate_ROOT="$legate_root" -D cupynumeric_ROOT="$cupynumeric_root" -D CMAKE_BUILD_TYPE=Debug cmake --build build --parallel 8 diff --git a/examples/cpp/stencil/stencil.cc b/examples/cpp/stencil/stencil.cc index 600535123c..022b3f222c 100644 --- a/examples/cpp/stencil/stencil.cc +++ b/examples/cpp/stencil/stencil.cc @@ -15,15 +15,15 @@ */ #include "legate.h" -#include "cunumeric.h" +#include "cupynumeric.h" #include "realm/cmdline.h" #include namespace stencil { -using cunumeric::open; -using cunumeric::slice; +using cupynumeric::open; +using cupynumeric::slice; struct Config { bool timing{false}; @@ -32,7 +32,7 @@ struct Config { uint64_t N{100}; }; -void print_array(cunumeric::NDArray array) +void print_array(cupynumeric::NDArray array) { auto acc = array.get_read_accessor(); auto& shape = array.shape(); @@ -49,9 +49,9 @@ void print_array(cunumeric::NDArray array) std::cerr << std::move(ss).str(); } -cunumeric::NDArray initialize(uint64_t N) +cupynumeric::NDArray initialize(uint64_t N) { - auto grid = cunumeric::zeros({N + 2, N + 2}); + auto grid = cupynumeric::zeros({N + 2, N + 2}); grid[{slice(), slice(0, 1)}].assign(legate::Scalar{-273.15}); grid[{slice(), slice(-1, open)}].assign(legate::Scalar{-273.15}); grid[{slice(-1, open), slice()}].assign(legate::Scalar{-273.15}); @@ -84,7 +84,7 @@ int main(int argc, char** argv) auto result = legate::start(argc, argv); assert(result == 0); - cunumeric::initialize(argc, argv); + cupynumeric::initialize(argc, argv); stencil::Config config{}; diff --git a/examples/scan.py b/examples/scan.py index 09acad0608..00907a60af 100644 --- a/examples/scan.py +++ b/examples/scan.py @@ -62,7 +62,7 @@ def check_scan(OP, A, B, ax): else: print("FAIL!") print(f"INPUT : {A}") - print(f"CUNUMERIC: {B}") + print(f"CUPYNUMERIC: {B}") print(f"NUMPY : {C}") assert False diff --git a/install.py b/install.py index f58e19ec05..ce4912edbe 100755 --- a/install.py +++ b/install.py @@ -154,8 +154,8 @@ def find_legate_cmake_dir() -> Path: # conda env. return path - # Possibly installed in an editable installation, in which case legate-config.cmake - # and friends will live in the root binary directory. + # Possibly installed in an editable installation, in which case legate + # config.cmake and friends will live in the root binary directory. root_path = path.root assert isinstance(root_path, str) while not any(p.name == "legate-config.cmake" for p in path.iterdir()): @@ -329,7 +329,7 @@ def validate_path(path): ignore_errors=True, ) - # Configure and build cuNumeric via setup.py + # Configure and build cuPyNumeric via setup.py pip_install_cmd = [sys.executable, "-m", "pip", "install"] install_dir = None @@ -376,8 +376,8 @@ def validate_path(path): cmake_flags += f"""\ -DCMAKE_BUILD_TYPE={( - "Debug" if debug else "RelWithDebInfo" if debug_release else "Release" -)} + "Debug" if debug else "RelWithDebInfo" if debug_release else "Release" + )} -DBUILD_SHARED_LIBS=ON -DCMAKE_CUDA_ARCHITECTURES={str(arch)} -DLegion_MAX_DIM={str(maxdim)} @@ -437,14 +437,14 @@ def validate_path(path): def driver(): - parser = argparse.ArgumentParser(description="Install cuNumeric.") + parser = argparse.ArgumentParser(description="Install cuPyNumeric.") parser.add_argument( "--debug", dest="debug", action="store_true", required=False, default=os.environ.get("DEBUG", "0") == "1", - help="Build cuNumeric with no optimizations.", + help="Build cuPyNumeric with no optimizations.", ) parser.add_argument( "--debug-release", @@ -452,7 +452,7 @@ def driver(): action="store_true", required=False, default=os.environ.get("DEBUG_RELEASE", "0") == "1", - help="Build cuNumeric with optimizations, but include debugging " + help="Build cuPyNumeric with optimizations, but include debugging " "symbols.", ) parser.add_argument( @@ -461,7 +461,7 @@ def driver(): action="store_true", required=False, default=False, - help="Build cuNumeric tests.", + help="Build cuPyNumeric tests.", ) parser.add_argument( "--check-bounds", @@ -469,21 +469,21 @@ def driver(): action="store_true", required=False, default=False, - help="Build cuNumeric with bounds checks.", + help="Build cuPyNumeric with bounds checks.", ) parser.add_argument( "--max-dim", dest="maxdim", type=int, default=int(os.environ.get("LEGION_MAX_DIM", 4)), - help="Maximum number of dimensions that cuNumeric will support", + help="Maximum number of dimensions that cuPyNumeric will support", ) parser.add_argument( "--max-fields", dest="maxfields", type=int, default=int(os.environ.get("LEGION_MAX_FIELDS", 256)), - help="Maximum number of fields that cuNumeric will support", + help="Maximum number of fields that cuPyNumeric will support", ) parser.add_argument( "--network", @@ -510,7 +510,7 @@ def driver(): default=os.environ.get("OPENBLAS_PATH"), help="Path to OpenBLAS installation directory. Note that providing a " "user-defined BLAS library may lead to dynamic library conflicts with " - "BLAS loaded by Python's Numpy. When using cuNumeric's BLAS, this " + "BLAS loaded by Python's Numpy. When using cuPyNumeric's BLAS, this " "issue is prevented by a custom library name.", ) parser.add_argument( @@ -579,7 +579,7 @@ def driver(): "--cuda", action=BooleanFlag, default=os.environ.get("USE_CUDA", "0") == "1", - help="Build cuNumeric with CUDA support.", + help="Build cuPyNumeric with CUDA support.", ) parser.add_argument( "--with-cuda", @@ -601,7 +601,7 @@ def driver(): "--openmp", action=BooleanFlag, default=os.environ.get("USE_OPENMP", "0") == "1", - help="Build cuNumeric with OpenMP support.", + help="Build cuPyNumeric with OpenMP support.", ) parser.add_argument( "--march", @@ -616,7 +616,7 @@ def driver(): action="store_true", required=False, default=os.environ.get("USE_LLVM", "0") == "1", - help="Build cuNumeric with LLVM support.", + help="Build cuPyNumeric with LLVM support.", ) parser.add_argument( "--hdf5", @@ -625,7 +625,7 @@ def driver(): action="store_true", required=False, default=os.environ.get("USE_HDF", "0") == "1", - help="Build cuNumeric with HDF support.", + help="Build cuPyNumeric with HDF support.", ) parser.add_argument( "--spy", @@ -633,7 +633,7 @@ def driver(): action="store_true", required=False, default=os.environ.get("USE_SPY", "0") == "1", - help="Build cuNumeric with detailed Legion Spy enabled.", + help="Build cuPyNumeric with detailed Legion Spy enabled.", ) parser.add_argument( "--conduit", @@ -645,7 +645,7 @@ def driver(): # See https://github.com/nv-legate/legate.core/issues/294. choices=["ibv", "ucx", "aries", "mpi"], default=os.environ.get("CONDUIT"), - help="Build cuNumeric with specified GASNet conduit.", + help="Build cuPyNumeric with specified GASNet conduit.", ) parser.add_argument( "--clean", diff --git a/pyproject.toml b/pyproject.toml index 022a0f0a97..cc807dbb1d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -90,8 +90,8 @@ warn_unused_configs = true # legate files need to be listed here for now # since they are included in the type check module = [ - "cunumeric.install_info", - "cunumeric._version", + "cupynumeric.install_info", + "cupynumeric._version", "legate._version", "legate.__main__", "legate.install_info", diff --git a/scripts/api_compare.py b/scripts/api_compare.py index 37923157e2..7f3561fa22 100644 --- a/scripts/api_compare.py +++ b/scripts/api_compare.py @@ -18,9 +18,9 @@ import sys from dataclasses import astuple, dataclass -from cunumeric._sphinxext._comparison_config import GROUPED_CONFIGS -from cunumeric._sphinxext._comparison_util import filter_names -from cunumeric.coverage import is_implemented +from cupynumeric._sphinxext._comparison_config import GROUPED_CONFIGS +from cupynumeric._sphinxext._comparison_util import filter_names +from cupynumeric.coverage import is_implemented @dataclass @@ -35,16 +35,20 @@ def get_namespaces(attr): import cupy import numpy - import cunumeric + import cupynumeric if attr is None: - return numpy, cunumeric, cupy + return numpy, cupynumeric, cupy - return getattr(numpy, attr), getattr(cunumeric, attr), getattr(cupy, attr) + return ( + getattr(numpy, attr), + getattr(cupynumeric, attr), + getattr(cupy, attr), + ) def write_rows(rows): - headers = ("group", "numpy", "cunumeric", "cupy") + headers = ("group", "numpy", "cupynumeric", "cupy") writer = csv.writer(sys.stdout) writer.writerow(headers) for row in rows: diff --git a/scripts/conda-build.sh b/scripts/conda-build.sh index 47a4528274..a01c27ef29 100755 --- a/scripts/conda-build.sh +++ b/scripts/conda-build.sh @@ -1,11 +1,11 @@ #! /usr/bin/env bash -# mamba create -n cunumeric_build python=$PYTHON_VERSION boa git +# mamba create -n cupynumeric_build python=$PYTHON_VERSION boa git cd $(dirname "$(realpath "$0")")/.. -mkdir -p /tmp/conda-build/cunumeric -rm -rf /tmp/conda-build/cunumeric/* +mkdir -p /tmp/conda-build/cupynumeric +rm -rf /tmp/conda-build/cupynumeric/* PYTHON_VERSION="${PYTHON_VERSION:-3.10}" @@ -15,7 +15,7 @@ conda mambabuild \ --override-channels \ -c conda-forge -c https://github.com/nv-legate/ucx-package/raw/main \ -c file:///tmp/conda-build/legate_core \ - --croot /tmp/conda-build/cunumeric \ + --croot /tmp/conda-build/cupynumeric \ --no-test \ --no-verify \ --no-build-id \ diff --git a/setup.cfg b/setup.cfg index fb6cf969a2..fd1da9c82a 100644 --- a/setup.cfg +++ b/setup.cfg @@ -3,10 +3,10 @@ [versioneer] VCS = git style = pep440 -versionfile_source = cunumeric/_version.py -versionfile_build = cunumeric/_version.py +versionfile_source = cupynumeric/_version.py +versionfile_build = cupynumeric/_version.py tag_prefix = v -parentdir_prefix = cunumeric- +parentdir_prefix = cupynumeric- [flake8] exclude = __init__.py @@ -31,7 +31,7 @@ known_legion= legion_cffi legion_top known_first_party= - cunumeric + cupynumeric default_section=THIRDPARTY sections=FUTURE,STDLIB,THIRDPARTY,LEGION,FIRSTPARTY,LOCALFOLDER skip= diff --git a/setup.py b/setup.py index 530216c86b..0f4019067c 100644 --- a/setup.py +++ b/setup.py @@ -21,10 +21,10 @@ import versioneer setup( - name="cunumeric", + name="cupynumeric", version=versioneer.get_version(), description="An Aspiring Drop-In Replacement for NumPy at Scale", - url="https://github.com/nv-legate/cunumeric", + url="https://github.com/nv-legate/cupynumeric", author="NVIDIA Corporation", license="Apache 2.0", classifiers=[ @@ -39,9 +39,9 @@ ], packages=find_packages( where=".", - include=["cunumeric*"], + include=["cupynumeric*"], ), - package_data={"cunumeric": ["_sphinxext/_templates/*.rst"]}, + package_data={"cupynumeric": ["_sphinxext/_templates/*.rst"]}, include_package_data=True, cmdclass=versioneer.get_cmdclass(), install_requires=["numpy>=1.22,<2"], diff --git a/src/cunumeric/cunumeric_c.h b/src/cunumeric/cunumeric_c.h deleted file mode 100644 index 70cbcfb816..0000000000 --- a/src/cunumeric/cunumeric_c.h +++ /dev/null @@ -1,355 +0,0 @@ -/* Copyright 2024 NVIDIA Corporation - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * - */ - -#ifndef __CUNUMERIC_C_H__ -#define __CUNUMERIC_C_H__ - -// Match these to CuNumericOpCode in config.py -// Also, sort these alphabetically except the first one for easy lookup later -enum CuNumericOpCode { - _CUNUMERIC_OP_CODE_BASE = 0, - CUNUMERIC_ADVANCED_INDEXING, - CUNUMERIC_ARANGE, - CUNUMERIC_ARGWHERE, - CUNUMERIC_BATCHED_CHOLESKY, - CUNUMERIC_BINARY_OP, - CUNUMERIC_BINARY_RED, - CUNUMERIC_BINCOUNT, - CUNUMERIC_BITGENERATOR, - CUNUMERIC_CHOOSE, - CUNUMERIC_CONTRACT, - CUNUMERIC_CONVERT, - CUNUMERIC_CONVOLVE, - CUNUMERIC_SCAN_GLOBAL, - CUNUMERIC_SCAN_LOCAL, - CUNUMERIC_DIAG, - CUNUMERIC_DOT, - CUNUMERIC_EYE, - CUNUMERIC_FFT, - CUNUMERIC_FILL, - CUNUMERIC_FLIP, - CUNUMERIC_GEMM, - CUNUMERIC_HISTOGRAM, - CUNUMERIC_LOAD_CUDALIBS, - CUNUMERIC_MATMUL, - CUNUMERIC_MATVECMUL, - CUNUMERIC_MP_POTRF, - CUNUMERIC_MP_SOLVE, - CUNUMERIC_NONZERO, - CUNUMERIC_PACKBITS, - CUNUMERIC_POTRF, - CUNUMERIC_PUTMASK, - CUNUMERIC_QR, - CUNUMERIC_RAND, - CUNUMERIC_READ, - CUNUMERIC_REPEAT, - CUNUMERIC_SCALAR_UNARY_RED, - CUNUMERIC_SEARCHSORTED, - CUNUMERIC_SELECT, - CUNUMERIC_SOLVE, - CUNUMERIC_SORT, - CUNUMERIC_SVD, - CUNUMERIC_SYRK, - CUNUMERIC_TILE, - CUNUMERIC_TRANSPOSE_COPY_2D, - CUNUMERIC_TRILU, - CUNUMERIC_TRSM, - CUNUMERIC_UNARY_OP, - CUNUMERIC_UNARY_RED, - CUNUMERIC_UNIQUE, - CUNUMERIC_UNIQUE_REDUCE, - CUNUMERIC_UNLOAD_CUDALIBS, - CUNUMERIC_UNPACKBITS, - CUNUMERIC_WHERE, - CUNUMERIC_WINDOW, - CUNUMERIC_WRAP, - CUNUMERIC_WRITE, - CUNUMERIC_ZIP, -}; - -// Match these to UnaryOpCode in config.py -// Also, sort these alphabetically for easy lookup later -enum CuNumericUnaryOpCode { - CUNUMERIC_UOP_ABSOLUTE = 1, - CUNUMERIC_UOP_ANGLE, - CUNUMERIC_UOP_ARCCOS, - CUNUMERIC_UOP_ARCCOSH, - CUNUMERIC_UOP_ARCSIN, - CUNUMERIC_UOP_ARCSINH, - CUNUMERIC_UOP_ARCTAN, - CUNUMERIC_UOP_ARCTANH, - CUNUMERIC_UOP_CBRT, - CUNUMERIC_UOP_CEIL, - CUNUMERIC_UOP_CLIP, - CUNUMERIC_UOP_CONJ, - CUNUMERIC_UOP_COPY, - CUNUMERIC_UOP_COS, - CUNUMERIC_UOP_COSH, - CUNUMERIC_UOP_DEG2RAD, - CUNUMERIC_UOP_EXP, - CUNUMERIC_UOP_EXP2, - CUNUMERIC_UOP_EXPM1, - CUNUMERIC_UOP_FLOOR, - CUNUMERIC_UOP_FREXP, - CUNUMERIC_UOP_GETARG, - CUNUMERIC_UOP_IMAG, - CUNUMERIC_UOP_INVERT, - CUNUMERIC_UOP_ISFINITE, - CUNUMERIC_UOP_ISINF, - CUNUMERIC_UOP_ISNAN, - CUNUMERIC_UOP_LOG, - CUNUMERIC_UOP_LOG10, - CUNUMERIC_UOP_LOG1P, - CUNUMERIC_UOP_LOG2, - CUNUMERIC_UOP_LOGICAL_NOT, - CUNUMERIC_UOP_MODF, - CUNUMERIC_UOP_NEGATIVE, - CUNUMERIC_UOP_POSITIVE, - CUNUMERIC_UOP_RAD2DEG, - CUNUMERIC_UOP_REAL, - CUNUMERIC_UOP_RECIPROCAL, - CUNUMERIC_UOP_RINT, - CUNUMERIC_UOP_ROUND, - CUNUMERIC_UOP_SIGN, - CUNUMERIC_UOP_SIGNBIT, - CUNUMERIC_UOP_SIN, - CUNUMERIC_UOP_SINH, - CUNUMERIC_UOP_SQRT, - CUNUMERIC_UOP_SQUARE, - CUNUMERIC_UOP_TAN, - CUNUMERIC_UOP_TANH, - CUNUMERIC_UOP_TRUNC, -}; - -// Match these to UnaryRedCode in config.py -// Also, sort these alphabetically for easy lookup later -enum CuNumericUnaryRedCode { - CUNUMERIC_RED_ALL = 1, - CUNUMERIC_RED_ANY, - CUNUMERIC_RED_ARGMAX, - CUNUMERIC_RED_ARGMIN, - CUNUMERIC_RED_CONTAINS, - CUNUMERIC_RED_COUNT_NONZERO, - CUNUMERIC_RED_MAX, - CUNUMERIC_RED_MIN, - CUNUMERIC_RED_NANARGMAX, - CUNUMERIC_RED_NANARGMIN, - CUNUMERIC_RED_NANMAX, - CUNUMERIC_RED_NANMIN, - CUNUMERIC_RED_NANPROD, - CUNUMERIC_RED_NANSUM, - CUNUMERIC_RED_PROD, - CUNUMERIC_RED_SUM, - CUNUMERIC_RED_SUM_SQUARES, - CUNUMERIC_RED_VARIANCE -}; - -// Match these to BinaryOpCode in config.py -// Also, sort these alphabetically for easy lookup later -enum CuNumericBinaryOpCode { - CUNUMERIC_BINOP_ADD = 1, - CUNUMERIC_BINOP_ARCTAN2, - CUNUMERIC_BINOP_BITWISE_AND, - CUNUMERIC_BINOP_BITWISE_OR, - CUNUMERIC_BINOP_BITWISE_XOR, - CUNUMERIC_BINOP_COPYSIGN, - CUNUMERIC_BINOP_DIVIDE, - CUNUMERIC_BINOP_EQUAL, - CUNUMERIC_BINOP_FLOAT_POWER, - CUNUMERIC_BINOP_FLOOR_DIVIDE, - CUNUMERIC_BINOP_FMOD, - CUNUMERIC_BINOP_GCD, - CUNUMERIC_BINOP_GREATER, - CUNUMERIC_BINOP_GREATER_EQUAL, - CUNUMERIC_BINOP_HYPOT, - CUNUMERIC_BINOP_ISCLOSE, - CUNUMERIC_BINOP_LCM, - CUNUMERIC_BINOP_LDEXP, - CUNUMERIC_BINOP_LEFT_SHIFT, - CUNUMERIC_BINOP_LESS, - CUNUMERIC_BINOP_LESS_EQUAL, - CUNUMERIC_BINOP_LOGADDEXP, - CUNUMERIC_BINOP_LOGADDEXP2, - CUNUMERIC_BINOP_LOGICAL_AND, - CUNUMERIC_BINOP_LOGICAL_OR, - CUNUMERIC_BINOP_LOGICAL_XOR, - CUNUMERIC_BINOP_MAXIMUM, - CUNUMERIC_BINOP_MINIMUM, - CUNUMERIC_BINOP_MOD, - CUNUMERIC_BINOP_MULTIPLY, - CUNUMERIC_BINOP_NEXTAFTER, - CUNUMERIC_BINOP_NOT_EQUAL, - CUNUMERIC_BINOP_POWER, - CUNUMERIC_BINOP_RIGHT_SHIFT, - CUNUMERIC_BINOP_SUBTRACT, -}; - -// Match these to WindowOpCode in config.py -// Also, sort these alphabetically for easy lookup later -enum CuNumericWindowOpCode { - CUNUMERIC_WINDOW_BARLETT = 1, - CUNUMERIC_WINDOW_BLACKMAN, - CUNUMERIC_WINDOW_HAMMING, - CUNUMERIC_WINDOW_HANNING, - CUNUMERIC_WINDOW_KAISER, -}; - -// Match these to CuNumericRedopCode in config.py -enum CuNumericRedopID { - CUNUMERIC_ARGMAX_REDOP = 1, - CUNUMERIC_ARGMIN_REDOP = 2, -}; - -enum CuNumericBounds { - CUNUMERIC_MAX_REDOPS = 1024, - CUNUMERIC_MAX_TASKS = 1048576, -}; - -// Match these to ScanCode in config.py -// Also, sort these alphabetically for easy lookup later -enum CuNumericScanCode { - CUNUMERIC_SCAN_PROD = 1, - CUNUMERIC_SCAN_SUM, -}; - -// Match these to ConvertCode in config.py -// Also, sort these alphabetically for easy lookup later -enum CuNumericConvertCode { - CUNUMERIC_CONVERT_NAN_NOOP = 1, - CUNUMERIC_CONVERT_NAN_PROD, - CUNUMERIC_CONVERT_NAN_SUM, -}; - -// Match these to BitGeneratorOperation in config.py -enum CuNumericBitGeneratorOperation { - CUNUMERIC_BITGENOP_CREATE = 1, - CUNUMERIC_BITGENOP_DESTROY = 2, - CUNUMERIC_BITGENOP_RAND_RAW = 3, - CUNUMERIC_BITGENOP_DISTRIBUTION = 4, -}; - -// Match these to BitGeneratorType in config.py -enum CuNumericBitGeneratorType { - CUNUMERIC_BITGENTYPE_DEFAULT = 0, - CUNUMERIC_BITGENTYPE_XORWOW = 1, - CUNUMERIC_BITGENTYPE_MRG32K3A = 2, - CUNUMERIC_BITGENTYPE_MTGP32 = 3, - CUNUMERIC_BITGENTYPE_MT19937 = 4, - CUNUMERIC_BITGENTYPE_PHILOX4_32_10 = 5, -}; - -// Match these to BitGeneratorDistribution in config.py -enum CuNumericBitGeneratorDistribution { - CUNUMERIC_BITGENDIST_INTEGERS_16 = 1, - CUNUMERIC_BITGENDIST_INTEGERS_32, - CUNUMERIC_BITGENDIST_INTEGERS_64, - CUNUMERIC_BITGENDIST_UNIFORM_32, - CUNUMERIC_BITGENDIST_UNIFORM_64, - CUNUMERIC_BITGENDIST_LOGNORMAL_32, - CUNUMERIC_BITGENDIST_LOGNORMAL_64, - CUNUMERIC_BITGENDIST_NORMAL_32, - CUNUMERIC_BITGENDIST_NORMAL_64, - CUNUMERIC_BITGENDIST_POISSON, - CUNUMERIC_BITGENDIST_EXPONENTIAL_32, - CUNUMERIC_BITGENDIST_EXPONENTIAL_64, - CUNUMERIC_BITGENDIST_GUMBEL_32, - CUNUMERIC_BITGENDIST_GUMBEL_64, - CUNUMERIC_BITGENDIST_LAPLACE_32, - CUNUMERIC_BITGENDIST_LAPLACE_64, - CUNUMERIC_BITGENDIST_LOGISTIC_32, - CUNUMERIC_BITGENDIST_LOGISTIC_64, - CUNUMERIC_BITGENDIST_PARETO_32, - CUNUMERIC_BITGENDIST_PARETO_64, - CUNUMERIC_BITGENDIST_POWER_32, - CUNUMERIC_BITGENDIST_POWER_64, - CUNUMERIC_BITGENDIST_RAYLEIGH_32, - CUNUMERIC_BITGENDIST_RAYLEIGH_64, - CUNUMERIC_BITGENDIST_CAUCHY_32, - CUNUMERIC_BITGENDIST_CAUCHY_64, - CUNUMERIC_BITGENDIST_TRIANGULAR_32, - CUNUMERIC_BITGENDIST_TRIANGULAR_64, - CUNUMERIC_BITGENDIST_WEIBULL_32, - CUNUMERIC_BITGENDIST_WEIBULL_64, - CUNUMERIC_BITGENDIST_BYTES, - CUNUMERIC_BITGENDIST_BETA_32, - CUNUMERIC_BITGENDIST_BETA_64, - CUNUMERIC_BITGENDIST_F_32, - CUNUMERIC_BITGENDIST_F_64, - CUNUMERIC_BITGENDIST_LOGSERIES, - CUNUMERIC_BITGENDIST_NONCENTRAL_F_32, - CUNUMERIC_BITGENDIST_NONCENTRAL_F_64, - CUNUMERIC_BITGENDIST_CHISQUARE_32, - CUNUMERIC_BITGENDIST_CHISQUARE_64, - CUNUMERIC_BITGENDIST_GAMMA_32, - CUNUMERIC_BITGENDIST_GAMMA_64, - CUNUMERIC_BITGENDIST_STANDARD_T_32, - CUNUMERIC_BITGENDIST_STANDARD_T_64, - CUNUMERIC_BITGENDIST_HYPERGEOMETRIC, - CUNUMERIC_BITGENDIST_VONMISES_32, - CUNUMERIC_BITGENDIST_VONMISES_64, - CUNUMERIC_BITGENDIST_ZIPF, - CUNUMERIC_BITGENDIST_GEOMETRIC, - CUNUMERIC_BITGENDIST_WALD_32, - CUNUMERIC_BITGENDIST_WALD_64, - CUNUMERIC_BITGENDIST_BINOMIAL, - CUNUMERIC_BITGENDIST_NEGATIVE_BINOMIAL, -}; - -// These fft types match CuNumericFFTType in config.py and cufftType -enum CuNumericFFTType { - CUNUMERIC_FFT_R2C = 0x2a, // Real to complex (interleaved) - CUNUMERIC_FFT_C2R = 0x2c, // Complex (interleaved) to real - CUNUMERIC_FFT_C2C = 0x29, // Complex to complex (interleaved) - CUNUMERIC_FFT_D2Z = 0x6a, // Double to double-complex (interleaved) - CUNUMERIC_FFT_Z2D = 0x6c, // Double-complex (interleaved) to double - CUNUMERIC_FFT_Z2Z = 0x69 // Double-complex to double-complex (interleaved) -}; - -enum CuNumericConvolveMethod { - CUNUMERIC_CONVOLVE_AUTO, - CUNUMERIC_CONVOLVE_DIRECT, - CUNUMERIC_CONVOLVE_FFT, -}; - -// These fft types match CuNumericFFTDirection in config.py and cufftDirection -enum CuNumericFFTDirection { CUNUMERIC_FFT_FORWARD = -1, CUNUMERIC_FFT_INVERSE = 1 }; - -// Match these to Bitorder in config.py -enum CuNumericBitorder { CUNUMERIC_BITORDER_BIG = 0, CUNUMERIC_BITORDER_LITTLE = 1 }; - -#ifdef __cplusplus -extern "C" { -#endif - -typedef struct ReductionOpIds { - int argmax_redop_id; - int argmin_redop_id; -} ReductionOpIds; - -void cunumeric_perform_registration(); -bool cunumeric_has_cusolvermp(); - -unsigned cunumeric_max_eager_volume(); - -unsigned cunumeric_matmul_cache_size(); - -struct ReductionOpIds cunumeric_register_reduction_ops(int code); - -#ifdef __cplusplus -} -#endif - -#endif // __CUNUMERIC_C_H__ diff --git a/src/cunumeric/random/bitgenerator_util.h b/src/cunumeric/random/bitgenerator_util.h deleted file mode 100644 index 0a726a9f08..0000000000 --- a/src/cunumeric/random/bitgenerator_util.h +++ /dev/null @@ -1,98 +0,0 @@ -/* Copyright 2024 NVIDIA Corporation - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * - */ - -#pragma once - -#include "cunumeric/cunumeric_task.h" - -namespace cunumeric { - -// Match these to BitGeneratorOperation in config.py -enum class BitGeneratorOperation : int32_t { - CREATE = CUNUMERIC_BITGENOP_CREATE, - DESTROY = CUNUMERIC_BITGENOP_DESTROY, - RAND_RAW = CUNUMERIC_BITGENOP_RAND_RAW, - DISTRIBUTION = CUNUMERIC_BITGENOP_DISTRIBUTION, -}; - -// Match these to BitGeneratorType in config.py -enum class BitGeneratorType : uint32_t { - DEFAULT = CUNUMERIC_BITGENTYPE_DEFAULT, - XORWOW = CUNUMERIC_BITGENTYPE_XORWOW, - MRG32K3A = CUNUMERIC_BITGENTYPE_MRG32K3A, - MTGP32 = CUNUMERIC_BITGENTYPE_MTGP32, - MT19937 = CUNUMERIC_BITGENTYPE_MT19937, - PHILOX4_32_10 = CUNUMERIC_BITGENTYPE_PHILOX4_32_10, -}; - -// Match these to BitGeneratorDistribution in config.py -enum class BitGeneratorDistribution : int32_t { - INTEGERS_16 = CUNUMERIC_BITGENDIST_INTEGERS_16, - INTEGERS_32 = CUNUMERIC_BITGENDIST_INTEGERS_32, - INTEGERS_64 = CUNUMERIC_BITGENDIST_INTEGERS_64, - UNIFORM_32 = CUNUMERIC_BITGENDIST_UNIFORM_32, - UNIFORM_64 = CUNUMERIC_BITGENDIST_UNIFORM_64, - LOGNORMAL_32 = CUNUMERIC_BITGENDIST_LOGNORMAL_32, - LOGNORMAL_64 = CUNUMERIC_BITGENDIST_LOGNORMAL_64, - NORMAL_32 = CUNUMERIC_BITGENDIST_NORMAL_32, - NORMAL_64 = CUNUMERIC_BITGENDIST_NORMAL_64, - POISSON = CUNUMERIC_BITGENDIST_POISSON, - EXPONENTIAL_32 = CUNUMERIC_BITGENDIST_EXPONENTIAL_32, - EXPONENTIAL_64 = CUNUMERIC_BITGENDIST_EXPONENTIAL_64, - GUMBEL_32 = CUNUMERIC_BITGENDIST_GUMBEL_32, - GUMBEL_64 = CUNUMERIC_BITGENDIST_GUMBEL_64, - LAPLACE_32 = CUNUMERIC_BITGENDIST_LAPLACE_32, - LAPLACE_64 = CUNUMERIC_BITGENDIST_LAPLACE_64, - LOGISTIC_32 = CUNUMERIC_BITGENDIST_LOGISTIC_32, - LOGISTIC_64 = CUNUMERIC_BITGENDIST_LOGISTIC_64, - PARETO_32 = CUNUMERIC_BITGENDIST_PARETO_32, - PARETO_64 = CUNUMERIC_BITGENDIST_PARETO_64, - POWER_32 = CUNUMERIC_BITGENDIST_POWER_32, - POWER_64 = CUNUMERIC_BITGENDIST_POWER_64, - RAYLEIGH_32 = CUNUMERIC_BITGENDIST_RAYLEIGH_32, - RAYLEIGH_64 = CUNUMERIC_BITGENDIST_RAYLEIGH_64, - CAUCHY_32 = CUNUMERIC_BITGENDIST_CAUCHY_32, - CAUCHY_64 = CUNUMERIC_BITGENDIST_CAUCHY_64, - TRIANGULAR_32 = CUNUMERIC_BITGENDIST_TRIANGULAR_32, - TRIANGULAR_64 = CUNUMERIC_BITGENDIST_TRIANGULAR_64, - WEIBULL_32 = CUNUMERIC_BITGENDIST_WEIBULL_32, - WEIBULL_64 = CUNUMERIC_BITGENDIST_WEIBULL_64, - BYTES = CUNUMERIC_BITGENDIST_BYTES, - BETA_32 = CUNUMERIC_BITGENDIST_BETA_32, - BETA_64 = CUNUMERIC_BITGENDIST_BETA_64, - F_32 = CUNUMERIC_BITGENDIST_F_32, - F_64 = CUNUMERIC_BITGENDIST_F_64, - LOGSERIES = CUNUMERIC_BITGENDIST_LOGSERIES, - NONCENTRAL_F_32 = CUNUMERIC_BITGENDIST_NONCENTRAL_F_32, - NONCENTRAL_F_64 = CUNUMERIC_BITGENDIST_NONCENTRAL_F_64, - CHISQUARE_32 = CUNUMERIC_BITGENDIST_CHISQUARE_32, - CHISQUARE_64 = CUNUMERIC_BITGENDIST_CHISQUARE_64, - GAMMA_32 = CUNUMERIC_BITGENDIST_GAMMA_32, - GAMMA_64 = CUNUMERIC_BITGENDIST_GAMMA_64, - STANDARD_T_32 = CUNUMERIC_BITGENDIST_STANDARD_T_32, - STANDARD_T_64 = CUNUMERIC_BITGENDIST_STANDARD_T_64, - HYPERGEOMETRIC = CUNUMERIC_BITGENDIST_HYPERGEOMETRIC, - VONMISES_32 = CUNUMERIC_BITGENDIST_VONMISES_32, - VONMISES_64 = CUNUMERIC_BITGENDIST_VONMISES_64, - ZIPF = CUNUMERIC_BITGENDIST_ZIPF, - GEOMETRIC = CUNUMERIC_BITGENDIST_GEOMETRIC, - WALD_32 = CUNUMERIC_BITGENDIST_WALD_32, - WALD_64 = CUNUMERIC_BITGENDIST_WALD_64, - BINOMIAL = CUNUMERIC_BITGENDIST_BINOMIAL, - NEGATIVE_BINOMIAL = CUNUMERIC_BITGENDIST_NEGATIVE_BINOMIAL, -}; - -} // namespace cunumeric diff --git a/src/cunumeric.h b/src/cupynumeric.h similarity index 85% rename from src/cunumeric.h rename to src/cupynumeric.h index dfd752c834..fe598bd438 100644 --- a/src/cunumeric.h +++ b/src/cupynumeric.h @@ -14,6 +14,6 @@ * */ -#include "cunumeric/ndarray.h" -#include "cunumeric/operators.h" -#include "cunumeric/slice.h" +#include "cupynumeric/ndarray.h" +#include "cupynumeric/operators.h" +#include "cupynumeric/slice.h" diff --git a/src/cunumeric/arg.h b/src/cupynumeric/arg.h similarity index 96% rename from src/cunumeric/arg.h rename to src/cupynumeric/arg.h index 1dd91b12b1..70803223d4 100644 --- a/src/cunumeric/arg.h +++ b/src/cupynumeric/arg.h @@ -18,7 +18,7 @@ #include "legate.h" -namespace cunumeric { +namespace cupynumeric { template class Argval { @@ -95,6 +95,6 @@ class ArgminReduction { } }; -} // namespace cunumeric +} // namespace cupynumeric -#include "cunumeric/arg.inl" +#include "cupynumeric/arg.inl" diff --git a/src/cunumeric/arg.inl b/src/cupynumeric/arg.inl similarity index 98% rename from src/cunumeric/arg.inl rename to src/cupynumeric/arg.inl index 5c0ba9b689..995b516486 100644 --- a/src/cunumeric/arg.inl +++ b/src/cupynumeric/arg.inl @@ -19,7 +19,7 @@ // Useful for IDEs #include "arg.h" -namespace cunumeric { +namespace cupynumeric { template __CUDA_HD__ Argval::Argval(T v) : arg(LLONG_MIN), arg_value(v) @@ -143,4 +143,4 @@ DECLARE_IDENTITIES(uint64_t) #undef DECLARE_ARGMIN_IDENTITY #undef DECLARE_ARGMAX_IDENTITY -} // namespace cunumeric +} // namespace cupynumeric diff --git a/src/cunumeric/arg_redop_register.cc b/src/cupynumeric/arg_redop_register.cc similarity index 89% rename from src/cunumeric/arg_redop_register.cc rename to src/cupynumeric/arg_redop_register.cc index 7fdf450ac3..2e7372bb06 100644 --- a/src/cunumeric/arg_redop_register.cc +++ b/src/cupynumeric/arg_redop_register.cc @@ -14,11 +14,11 @@ * */ -#include "cunumeric/arg_redop_register.h" +#include "cupynumeric/arg_redop_register.h" #include -namespace cunumeric { +namespace cupynumeric { #define DEFINE_ARGMAX_IDENTITY(TYPE) \ template <> \ @@ -58,15 +58,15 @@ register_reduction_op_fn::register_reduction_op_fn::next_reduction_operator_id() return legate::LocalRedopID{next_redop_id++}; } -} // namespace cunumeric +} // namespace cupynumeric #if !LEGATE_DEFINED(LEGATE_USE_CUDA) extern "C" { -ReductionOpIds cunumeric_register_reduction_ops(int code) +ReductionOpIds cupynumeric_register_reduction_ops(int code) { return legate::type_dispatch(static_cast(code), - cunumeric::register_reduction_op_fn{}); + cupynumeric::register_reduction_op_fn{}); } } #endif diff --git a/src/cunumeric/arg_redop_register.cu b/src/cupynumeric/arg_redop_register.cu similarity index 79% rename from src/cunumeric/arg_redop_register.cu rename to src/cupynumeric/arg_redop_register.cu index 076d02a029..c48ed286a3 100644 --- a/src/cunumeric/arg_redop_register.cu +++ b/src/cupynumeric/arg_redop_register.cu @@ -14,13 +14,13 @@ * */ -#include "cunumeric/arg_redop_register.h" +#include "cupynumeric/arg_redop_register.h" extern "C" { -ReductionOpIds cunumeric_register_reduction_ops(int code) +ReductionOpIds cupynumeric_register_reduction_ops(int code) { return legate::type_dispatch(static_cast(code), - cunumeric::register_reduction_op_fn{}); + cupynumeric::register_reduction_op_fn{}); } } diff --git a/src/cunumeric/arg_redop_register.h b/src/cupynumeric/arg_redop_register.h similarity index 89% rename from src/cunumeric/arg_redop_register.h rename to src/cupynumeric/arg_redop_register.h index 05b764c8e0..68e6e65a63 100644 --- a/src/cunumeric/arg_redop_register.h +++ b/src/cupynumeric/arg_redop_register.h @@ -17,10 +17,10 @@ #pragma once #include "legate.h" -#include "cunumeric/cunumeric_c.h" -#include "cunumeric/arg.h" +#include "cupynumeric/cupynumeric_c.h" +#include "cupynumeric/arg.h" -namespace cunumeric { +namespace cupynumeric { struct register_reduction_op_fn { template ::value>* = nullptr> @@ -29,7 +29,7 @@ struct register_reduction_op_fn { using VAL = legate::type_of; ReductionOpIds result; auto runtime = legate::Runtime::get_runtime(); - auto context = runtime->find_library("cunumeric"); + auto context = runtime->find_library("cupynumeric"); result.argmax_redop_id = static_cast( context.register_reduction_operator>(next_reduction_operator_id())); result.argmin_redop_id = static_cast( @@ -47,4 +47,4 @@ struct register_reduction_op_fn { static legate::LocalRedopID next_reduction_operator_id(); }; -} // namespace cunumeric +} // namespace cupynumeric diff --git a/src/cunumeric/binary/binary_op.cc b/src/cupynumeric/binary/binary_op.cc similarity index 92% rename from src/cunumeric/binary/binary_op.cc rename to src/cupynumeric/binary/binary_op.cc index 64a810d981..f72ca85204 100644 --- a/src/cunumeric/binary/binary_op.cc +++ b/src/cupynumeric/binary/binary_op.cc @@ -14,10 +14,10 @@ * */ -#include "cunumeric/binary/binary_op.h" -#include "cunumeric/binary/binary_op_template.inl" +#include "cupynumeric/binary/binary_op.h" +#include "cupynumeric/binary/binary_op_template.inl" -namespace cunumeric { +namespace cupynumeric { using namespace legate; @@ -63,4 +63,4 @@ namespace // unnamed static void __attribute__((constructor)) register_tasks(void) { BinaryOpTask::register_variants(); } } // namespace -} // namespace cunumeric +} // namespace cupynumeric diff --git a/src/cunumeric/binary/binary_op.cu b/src/cupynumeric/binary/binary_op.cu similarity index 92% rename from src/cunumeric/binary/binary_op.cu rename to src/cupynumeric/binary/binary_op.cu index d00fa66e7d..ea7f68f4c9 100644 --- a/src/cunumeric/binary/binary_op.cu +++ b/src/cupynumeric/binary/binary_op.cu @@ -14,12 +14,12 @@ * */ -#include "cunumeric/binary/binary_op.h" -#include "cunumeric/binary/binary_op_template.inl" +#include "cupynumeric/binary/binary_op.h" +#include "cupynumeric/binary/binary_op_template.inl" -#include "cunumeric/cuda_help.h" +#include "cupynumeric/cuda_help.h" -namespace cunumeric { +namespace cupynumeric { template static __global__ void __launch_bounds__(THREADS_PER_BLOCK, MIN_CTAS_PER_SM) @@ -82,7 +82,7 @@ struct BinaryOpImplBody { generic_kernel<<>>( volume, func, out, in1, in2, pitches, rect); } - CUNUMERIC_CHECK_CUDA_STREAM(stream); + CUPYNUMERIC_CHECK_CUDA_STREAM(stream); } }; @@ -91,4 +91,4 @@ struct BinaryOpImplBody { binary_op_template(context); } -} // namespace cunumeric +} // namespace cupynumeric diff --git a/src/cunumeric/binary/binary_op.h b/src/cupynumeric/binary/binary_op.h similarity index 79% rename from src/cunumeric/binary/binary_op.h rename to src/cupynumeric/binary/binary_op.h index 8bdf29d7d5..2088a2f4da 100644 --- a/src/cunumeric/binary/binary_op.h +++ b/src/cupynumeric/binary/binary_op.h @@ -16,10 +16,10 @@ #pragma once -#include "cunumeric/cunumeric_task.h" -#include "cunumeric/binary/binary_op_util.h" +#include "cupynumeric/cupynumeric_task.h" +#include "cupynumeric/binary/binary_op_util.h" -namespace cunumeric { +namespace cupynumeric { struct BinaryOpArgs { legate::PhysicalStore in1; @@ -29,9 +29,9 @@ struct BinaryOpArgs { std::vector args; }; -class BinaryOpTask : public CuNumericTask { +class BinaryOpTask : public CuPyNumericTask { public: - static constexpr auto TASK_ID = legate::LocalTaskID{CUNUMERIC_BINARY_OP}; + static constexpr auto TASK_ID = legate::LocalTaskID{CUPYNUMERIC_BINARY_OP}; public: static void cpu_variant(legate::TaskContext context); @@ -43,4 +43,4 @@ class BinaryOpTask : public CuNumericTask { #endif }; -} // namespace cunumeric +} // namespace cupynumeric diff --git a/src/cunumeric/binary/binary_op_omp.cc b/src/cupynumeric/binary/binary_op_omp.cc similarity index 92% rename from src/cunumeric/binary/binary_op_omp.cc rename to src/cupynumeric/binary/binary_op_omp.cc index 684296a53a..9d4542d5f9 100644 --- a/src/cunumeric/binary/binary_op_omp.cc +++ b/src/cupynumeric/binary/binary_op_omp.cc @@ -14,10 +14,10 @@ * */ -#include "cunumeric/binary/binary_op.h" -#include "cunumeric/binary/binary_op_template.inl" +#include "cupynumeric/binary/binary_op.h" +#include "cupynumeric/binary/binary_op_template.inl" -namespace cunumeric { +namespace cupynumeric { using namespace legate; @@ -60,4 +60,4 @@ struct BinaryOpImplBody { binary_op_template(context); } -} // namespace cunumeric +} // namespace cupynumeric diff --git a/src/cunumeric/binary/binary_op_template.inl b/src/cupynumeric/binary/binary_op_template.inl similarity index 94% rename from src/cunumeric/binary/binary_op_template.inl rename to src/cupynumeric/binary/binary_op_template.inl index e3f5acbf44..01869a1922 100644 --- a/src/cunumeric/binary/binary_op_template.inl +++ b/src/cupynumeric/binary/binary_op_template.inl @@ -17,11 +17,11 @@ #pragma once // Useful for IDEs -#include "cunumeric/binary/binary_op.h" -#include "cunumeric/binary/binary_op_util.h" -#include "cunumeric/pitches.h" +#include "cupynumeric/binary/binary_op.h" +#include "cupynumeric/binary/binary_op_util.h" +#include "cupynumeric/pitches.h" -namespace cunumeric { +namespace cupynumeric { using namespace legate; @@ -94,4 +94,4 @@ static void binary_op_template(TaskContext& context) op_dispatch(args.op_code, BinaryOpDispatch{}, args); } -} // namespace cunumeric +} // namespace cupynumeric diff --git a/src/cunumeric/binary/binary_op_util.cc b/src/cupynumeric/binary/binary_op_util.cc similarity index 90% rename from src/cunumeric/binary/binary_op_util.cc rename to src/cupynumeric/binary/binary_op_util.cc index 180c9d9c02..0f90b40b12 100644 --- a/src/cunumeric/binary/binary_op_util.cc +++ b/src/cupynumeric/binary/binary_op_util.cc @@ -14,13 +14,13 @@ * */ -#include "cunumeric/binary/binary_op_util.h" +#include "cupynumeric/binary/binary_op_util.h" -namespace cunumeric { +namespace cupynumeric { std::vector broadcast_shapes(std::vector arrays) { -#ifdef DEBUG_CUNUMERIC +#ifdef DEBUG_CUPYNUMERIC assert(!arrays.empty()); #endif int32_t dim = 0; @@ -46,4 +46,4 @@ std::vector broadcast_shapes(std::vector arrays) return result; } -} // namespace cunumeric +} // namespace cupynumeric diff --git a/src/cunumeric/binary/binary_op_util.h b/src/cupynumeric/binary/binary_op_util.h similarity index 94% rename from src/cunumeric/binary/binary_op_util.h rename to src/cupynumeric/binary/binary_op_util.h index 55189409ea..84c8a88cdb 100644 --- a/src/cunumeric/binary/binary_op_util.h +++ b/src/cupynumeric/binary/binary_op_util.h @@ -16,47 +16,47 @@ #pragma once -#include "cunumeric/cunumeric_task.h" -#include "cunumeric/ndarray.h" +#include "cupynumeric/cupynumeric_task.h" +#include "cupynumeric/ndarray.h" -namespace cunumeric { +namespace cupynumeric { enum class BinaryOpCode : int { - ADD = CUNUMERIC_BINOP_ADD, - ARCTAN2 = CUNUMERIC_BINOP_ARCTAN2, - BITWISE_AND = CUNUMERIC_BINOP_BITWISE_AND, - BITWISE_OR = CUNUMERIC_BINOP_BITWISE_OR, - BITWISE_XOR = CUNUMERIC_BINOP_BITWISE_XOR, - COPYSIGN = CUNUMERIC_BINOP_COPYSIGN, - DIVIDE = CUNUMERIC_BINOP_DIVIDE, - EQUAL = CUNUMERIC_BINOP_EQUAL, - FLOAT_POWER = CUNUMERIC_BINOP_FLOAT_POWER, - FLOOR_DIVIDE = CUNUMERIC_BINOP_FLOOR_DIVIDE, - FMOD = CUNUMERIC_BINOP_FMOD, - GCD = CUNUMERIC_BINOP_GCD, - GREATER = CUNUMERIC_BINOP_GREATER, - GREATER_EQUAL = CUNUMERIC_BINOP_GREATER_EQUAL, - HYPOT = CUNUMERIC_BINOP_HYPOT, - ISCLOSE = CUNUMERIC_BINOP_ISCLOSE, - LCM = CUNUMERIC_BINOP_LCM, - LDEXP = CUNUMERIC_BINOP_LDEXP, - LEFT_SHIFT = CUNUMERIC_BINOP_LEFT_SHIFT, - LESS = CUNUMERIC_BINOP_LESS, - LESS_EQUAL = CUNUMERIC_BINOP_LESS_EQUAL, - LOGADDEXP = CUNUMERIC_BINOP_LOGADDEXP, - LOGADDEXP2 = CUNUMERIC_BINOP_LOGADDEXP2, - LOGICAL_AND = CUNUMERIC_BINOP_LOGICAL_AND, - LOGICAL_OR = CUNUMERIC_BINOP_LOGICAL_OR, - LOGICAL_XOR = CUNUMERIC_BINOP_LOGICAL_XOR, - MAXIMUM = CUNUMERIC_BINOP_MAXIMUM, - MINIMUM = CUNUMERIC_BINOP_MINIMUM, - MOD = CUNUMERIC_BINOP_MOD, - MULTIPLY = CUNUMERIC_BINOP_MULTIPLY, - NEXTAFTER = CUNUMERIC_BINOP_NEXTAFTER, - NOT_EQUAL = CUNUMERIC_BINOP_NOT_EQUAL, - POWER = CUNUMERIC_BINOP_POWER, - RIGHT_SHIFT = CUNUMERIC_BINOP_RIGHT_SHIFT, - SUBTRACT = CUNUMERIC_BINOP_SUBTRACT, + ADD = CUPYNUMERIC_BINOP_ADD, + ARCTAN2 = CUPYNUMERIC_BINOP_ARCTAN2, + BITWISE_AND = CUPYNUMERIC_BINOP_BITWISE_AND, + BITWISE_OR = CUPYNUMERIC_BINOP_BITWISE_OR, + BITWISE_XOR = CUPYNUMERIC_BINOP_BITWISE_XOR, + COPYSIGN = CUPYNUMERIC_BINOP_COPYSIGN, + DIVIDE = CUPYNUMERIC_BINOP_DIVIDE, + EQUAL = CUPYNUMERIC_BINOP_EQUAL, + FLOAT_POWER = CUPYNUMERIC_BINOP_FLOAT_POWER, + FLOOR_DIVIDE = CUPYNUMERIC_BINOP_FLOOR_DIVIDE, + FMOD = CUPYNUMERIC_BINOP_FMOD, + GCD = CUPYNUMERIC_BINOP_GCD, + GREATER = CUPYNUMERIC_BINOP_GREATER, + GREATER_EQUAL = CUPYNUMERIC_BINOP_GREATER_EQUAL, + HYPOT = CUPYNUMERIC_BINOP_HYPOT, + ISCLOSE = CUPYNUMERIC_BINOP_ISCLOSE, + LCM = CUPYNUMERIC_BINOP_LCM, + LDEXP = CUPYNUMERIC_BINOP_LDEXP, + LEFT_SHIFT = CUPYNUMERIC_BINOP_LEFT_SHIFT, + LESS = CUPYNUMERIC_BINOP_LESS, + LESS_EQUAL = CUPYNUMERIC_BINOP_LESS_EQUAL, + LOGADDEXP = CUPYNUMERIC_BINOP_LOGADDEXP, + LOGADDEXP2 = CUPYNUMERIC_BINOP_LOGADDEXP2, + LOGICAL_AND = CUPYNUMERIC_BINOP_LOGICAL_AND, + LOGICAL_OR = CUPYNUMERIC_BINOP_LOGICAL_OR, + LOGICAL_XOR = CUPYNUMERIC_BINOP_LOGICAL_XOR, + MAXIMUM = CUPYNUMERIC_BINOP_MAXIMUM, + MINIMUM = CUPYNUMERIC_BINOP_MINIMUM, + MOD = CUPYNUMERIC_BINOP_MOD, + MULTIPLY = CUPYNUMERIC_BINOP_MULTIPLY, + NEXTAFTER = CUPYNUMERIC_BINOP_NEXTAFTER, + NOT_EQUAL = CUPYNUMERIC_BINOP_NOT_EQUAL, + POWER = CUPYNUMERIC_BINOP_POWER, + RIGHT_SHIFT = CUPYNUMERIC_BINOP_RIGHT_SHIFT, + SUBTRACT = CUPYNUMERIC_BINOP_SUBTRACT, }; template @@ -913,4 +913,4 @@ using rhs2_of_binary_op = typename RHS2OfBinaryOp::type; std::vector broadcast_shapes(std::vector arrays); -} // namespace cunumeric +} // namespace cupynumeric diff --git a/src/cunumeric/binary/binary_red.cc b/src/cupynumeric/binary/binary_red.cc similarity index 92% rename from src/cunumeric/binary/binary_red.cc rename to src/cupynumeric/binary/binary_red.cc index 576347b37d..dbd9cf87ac 100644 --- a/src/cunumeric/binary/binary_red.cc +++ b/src/cupynumeric/binary/binary_red.cc @@ -14,10 +14,10 @@ * */ -#include "cunumeric/binary/binary_red.h" -#include "cunumeric/binary/binary_red_template.inl" +#include "cupynumeric/binary/binary_red.h" +#include "cupynumeric/binary/binary_red_template.inl" -namespace cunumeric { +namespace cupynumeric { using namespace legate; @@ -72,4 +72,4 @@ static void __attribute__((constructor)) register_tasks(void) } } // namespace -} // namespace cunumeric +} // namespace cupynumeric diff --git a/src/cunumeric/binary/binary_red.cu b/src/cupynumeric/binary/binary_red.cu similarity index 92% rename from src/cunumeric/binary/binary_red.cu rename to src/cupynumeric/binary/binary_red.cu index 4623e43bdc..47544a5ab4 100644 --- a/src/cunumeric/binary/binary_red.cu +++ b/src/cupynumeric/binary/binary_red.cu @@ -14,12 +14,12 @@ * */ -#include "cunumeric/binary/binary_red.h" -#include "cunumeric/binary/binary_red_template.inl" +#include "cupynumeric/binary/binary_red.h" +#include "cupynumeric/binary/binary_red_template.inl" -#include "cunumeric/cuda_help.h" +#include "cupynumeric/cuda_help.h" -namespace cunumeric { +namespace cupynumeric { template static __global__ void __launch_bounds__(THREADS_PER_BLOCK, MIN_CTAS_PER_SM) @@ -82,7 +82,7 @@ struct BinaryRedImplBody { } copy_kernel<<<1, 1, 0, stream>>>(result, out); - CUNUMERIC_CHECK_CUDA_STREAM(stream); + CUPYNUMERIC_CHECK_CUDA_STREAM(stream); } }; @@ -91,4 +91,4 @@ struct BinaryRedImplBody { binary_red_template(context); } -} // namespace cunumeric +} // namespace cupynumeric diff --git a/src/cunumeric/binary/binary_red.h b/src/cupynumeric/binary/binary_red.h similarity index 79% rename from src/cunumeric/binary/binary_red.h rename to src/cupynumeric/binary/binary_red.h index 28ca9f030f..ec6cfce30a 100644 --- a/src/cunumeric/binary/binary_red.h +++ b/src/cupynumeric/binary/binary_red.h @@ -16,10 +16,10 @@ #pragma once -#include "cunumeric/cunumeric_task.h" -#include "cunumeric/binary/binary_op_util.h" +#include "cupynumeric/cupynumeric_task.h" +#include "cupynumeric/binary/binary_op_util.h" -namespace cunumeric { +namespace cupynumeric { struct BinaryRedArgs { legate::PhysicalStore out; @@ -29,9 +29,9 @@ struct BinaryRedArgs { std::vector args; }; -class BinaryRedTask : public CuNumericTask { +class BinaryRedTask : public CuPyNumericTask { public: - static constexpr auto TASK_ID = legate::LocalTaskID{CUNUMERIC_BINARY_RED}; + static constexpr auto TASK_ID = legate::LocalTaskID{CUPYNUMERIC_BINARY_RED}; public: static void cpu_variant(legate::TaskContext context); @@ -43,4 +43,4 @@ class BinaryRedTask : public CuNumericTask { #endif }; -} // namespace cunumeric +} // namespace cupynumeric diff --git a/src/cunumeric/binary/binary_red_omp.cc b/src/cupynumeric/binary/binary_red_omp.cc similarity index 92% rename from src/cunumeric/binary/binary_red_omp.cc rename to src/cupynumeric/binary/binary_red_omp.cc index f3823c2031..021f99943b 100644 --- a/src/cunumeric/binary/binary_red_omp.cc +++ b/src/cupynumeric/binary/binary_red_omp.cc @@ -14,10 +14,10 @@ * */ -#include "cunumeric/binary/binary_red.h" -#include "cunumeric/binary/binary_red_template.inl" +#include "cupynumeric/binary/binary_red.h" +#include "cupynumeric/binary/binary_red_template.inl" -namespace cunumeric { +namespace cupynumeric { using namespace legate; @@ -65,4 +65,4 @@ struct BinaryRedImplBody { binary_red_template(context); } -} // namespace cunumeric +} // namespace cupynumeric diff --git a/src/cunumeric/binary/binary_red_template.inl b/src/cupynumeric/binary/binary_red_template.inl similarity index 94% rename from src/cunumeric/binary/binary_red_template.inl rename to src/cupynumeric/binary/binary_red_template.inl index e1971f5b45..15bdf9201f 100644 --- a/src/cunumeric/binary/binary_red_template.inl +++ b/src/cupynumeric/binary/binary_red_template.inl @@ -17,11 +17,11 @@ #pragma once // Useful for IDEs -#include "cunumeric/binary/binary_red.h" -#include "cunumeric/binary/binary_op_util.h" -#include "cunumeric/pitches.h" +#include "cupynumeric/binary/binary_red.h" +#include "cupynumeric/binary/binary_op_util.h" +#include "cupynumeric/pitches.h" -namespace cunumeric { +namespace cupynumeric { using namespace legate; @@ -98,4 +98,4 @@ static void binary_red_template(TaskContext& context) reduce_op_dispatch(args.op_code, BinaryRedDispatch{}, args); } -} // namespace cunumeric +} // namespace cupynumeric diff --git a/src/cunumeric/bits/bits_util.h b/src/cupynumeric/bits/bits_util.h similarity index 79% rename from src/cunumeric/bits/bits_util.h rename to src/cupynumeric/bits/bits_util.h index bd3294f19f..3e8cd6d077 100644 --- a/src/cunumeric/bits/bits_util.h +++ b/src/cupynumeric/bits/bits_util.h @@ -16,13 +16,13 @@ #pragma once -#include "cunumeric/cunumeric_c.h" +#include "cupynumeric/cupynumeric_c.h" -namespace cunumeric { +namespace cupynumeric { enum class Bitorder { - BIG = CUNUMERIC_BITORDER_BIG, - LITTLE = CUNUMERIC_BITORDER_LITTLE, + BIG = CUPYNUMERIC_BITORDER_BIG, + LITTLE = CUPYNUMERIC_BITORDER_LITTLE, }; -} // namespace cunumeric +} // namespace cupynumeric diff --git a/src/cunumeric/bits/packbits.cc b/src/cupynumeric/bits/packbits.cc similarity index 93% rename from src/cunumeric/bits/packbits.cc rename to src/cupynumeric/bits/packbits.cc index 41b056c1d8..2f48903ce5 100644 --- a/src/cunumeric/bits/packbits.cc +++ b/src/cupynumeric/bits/packbits.cc @@ -14,10 +14,10 @@ * */ -#include "cunumeric/bits/packbits.h" -#include "cunumeric/bits/packbits_template.inl" +#include "cupynumeric/bits/packbits.h" +#include "cupynumeric/bits/packbits_template.inl" -namespace cunumeric { +namespace cupynumeric { using namespace legate; @@ -60,4 +60,4 @@ namespace // unnamed static void __attribute__((constructor)) register_tasks(void) { PackbitsTask::register_variants(); } } // namespace -} // namespace cunumeric +} // namespace cupynumeric diff --git a/src/cunumeric/bits/packbits.cu b/src/cupynumeric/bits/packbits.cu similarity index 93% rename from src/cunumeric/bits/packbits.cu rename to src/cupynumeric/bits/packbits.cu index 541bed6e8a..2252275cfd 100644 --- a/src/cunumeric/bits/packbits.cu +++ b/src/cupynumeric/bits/packbits.cu @@ -14,11 +14,11 @@ * */ -#include "cunumeric/bits/packbits.h" -#include "cunumeric/bits/packbits_template.inl" -#include "cunumeric/cuda_help.h" +#include "cupynumeric/bits/packbits.h" +#include "cupynumeric/bits/packbits_template.inl" +#include "cupynumeric/cuda_help.h" -namespace cunumeric { +namespace cupynumeric { using namespace legate; @@ -76,7 +76,7 @@ struct PackbitsImplBody { in_hi_axis, axis); } - CUNUMERIC_CHECK_CUDA_STREAM(stream); + CUPYNUMERIC_CHECK_CUDA_STREAM(stream); } }; @@ -85,4 +85,4 @@ struct PackbitsImplBody { packbits_template(context); } -} // namespace cunumeric +} // namespace cupynumeric diff --git a/src/cunumeric/bits/packbits.h b/src/cupynumeric/bits/packbits.h similarity index 92% rename from src/cunumeric/bits/packbits.h rename to src/cupynumeric/bits/packbits.h index f24497fe73..de286b4233 100644 --- a/src/cunumeric/bits/packbits.h +++ b/src/cupynumeric/bits/packbits.h @@ -16,10 +16,10 @@ #pragma once -#include "cunumeric/cunumeric_task.h" -#include "cunumeric/bits/bits_util.h" +#include "cupynumeric/cupynumeric_task.h" +#include "cupynumeric/bits/bits_util.h" -namespace cunumeric { +namespace cupynumeric { template struct Pack; @@ -101,9 +101,9 @@ struct Pack { } }; -class PackbitsTask : public CuNumericTask { +class PackbitsTask : public CuPyNumericTask { public: - static constexpr auto TASK_ID = legate::LocalTaskID{CUNUMERIC_PACKBITS}; + static constexpr auto TASK_ID = legate::LocalTaskID{CUPYNUMERIC_PACKBITS}; public: static void cpu_variant(legate::TaskContext context); @@ -115,4 +115,4 @@ class PackbitsTask : public CuNumericTask { #endif }; -} // namespace cunumeric +} // namespace cupynumeric diff --git a/src/cunumeric/bits/packbits_omp.cc b/src/cupynumeric/bits/packbits_omp.cc similarity index 93% rename from src/cunumeric/bits/packbits_omp.cc rename to src/cupynumeric/bits/packbits_omp.cc index c4dd57dd8c..18b39c9a55 100644 --- a/src/cunumeric/bits/packbits_omp.cc +++ b/src/cupynumeric/bits/packbits_omp.cc @@ -14,10 +14,10 @@ * */ -#include "cunumeric/bits/packbits.h" -#include "cunumeric/bits/packbits_template.inl" +#include "cupynumeric/bits/packbits.h" +#include "cupynumeric/bits/packbits_template.inl" -namespace cunumeric { +namespace cupynumeric { using namespace legate; @@ -57,4 +57,4 @@ struct PackbitsImplBody { packbits_template(context); } -} // namespace cunumeric +} // namespace cupynumeric diff --git a/src/cunumeric/bits/packbits_template.inl b/src/cupynumeric/bits/packbits_template.inl similarity index 95% rename from src/cunumeric/bits/packbits_template.inl rename to src/cupynumeric/bits/packbits_template.inl index 9046b85410..6b84138f0b 100644 --- a/src/cunumeric/bits/packbits_template.inl +++ b/src/cupynumeric/bits/packbits_template.inl @@ -17,10 +17,10 @@ #pragma once // Useful for IDEs -#include "cunumeric/bits/packbits.h" -#include "cunumeric/pitches.h" +#include "cupynumeric/bits/packbits.h" +#include "cupynumeric/pitches.h" -namespace cunumeric { +namespace cupynumeric { using namespace legate; @@ -50,13 +50,13 @@ struct PackbitsImpl { auto aligned_rect = out_rect; int64_t axis_extent = in_rect.hi[axis] - in_rect.lo[axis] + 1; aligned_rect.hi[axis] = aligned_rect.lo[axis] + axis_extent / 8 - 1; -#ifdef DEBUG_CUNUMERIC +#ifdef DEBUG_CUPYNUMERIC assert(aligned_rect.hi[axis] <= out_rect.hi[axis]); #endif auto unaligned_rect = out_rect; unaligned_rect.lo[axis] = aligned_rect.hi[axis] + 1; -#ifdef DEBUG_CUNUMERIC +#ifdef DEBUG_CUPYNUMERIC assert(unaligned_rect.union_bbox(aligned_rect) == out_rect); #endif @@ -106,4 +106,4 @@ static void packbits_template(TaskContext& context) } } -} // namespace cunumeric +} // namespace cupynumeric diff --git a/src/cunumeric/bits/unpackbits.cc b/src/cupynumeric/bits/unpackbits.cc similarity index 90% rename from src/cunumeric/bits/unpackbits.cc rename to src/cupynumeric/bits/unpackbits.cc index 15217c5e86..4b5728c6eb 100644 --- a/src/cunumeric/bits/unpackbits.cc +++ b/src/cupynumeric/bits/unpackbits.cc @@ -14,10 +14,10 @@ * */ -#include "cunumeric/bits/unpackbits.h" -#include "cunumeric/bits/unpackbits_template.inl" +#include "cupynumeric/bits/unpackbits.h" +#include "cupynumeric/bits/unpackbits_template.inl" -namespace cunumeric { +namespace cupynumeric { using namespace legate; @@ -51,4 +51,4 @@ static void __attribute__((constructor)) register_tasks(void) } } // namespace -} // namespace cunumeric +} // namespace cupynumeric diff --git a/src/cunumeric/bits/unpackbits.cu b/src/cupynumeric/bits/unpackbits.cu similarity index 89% rename from src/cunumeric/bits/unpackbits.cu rename to src/cupynumeric/bits/unpackbits.cu index 71413618a6..f1b5b66890 100644 --- a/src/cunumeric/bits/unpackbits.cu +++ b/src/cupynumeric/bits/unpackbits.cu @@ -14,12 +14,12 @@ * */ -#include "cunumeric/bits/unpackbits.h" -#include "cunumeric/bits/unpackbits_template.inl" +#include "cupynumeric/bits/unpackbits.h" +#include "cupynumeric/bits/unpackbits_template.inl" -#include "cunumeric/cuda_help.h" +#include "cupynumeric/cuda_help.h" -namespace cunumeric { +namespace cupynumeric { using namespace legate; @@ -55,7 +55,7 @@ struct UnpackbitsImplBody { const size_t blocks = (in_volume + THREADS_PER_BLOCK - 1) / THREADS_PER_BLOCK; generic_kernel<<>>( in_volume, unpack, out, in, in_pitches, in_rect.lo, axis); - CUNUMERIC_CHECK_CUDA_STREAM(stream); + CUPYNUMERIC_CHECK_CUDA_STREAM(stream); } }; @@ -64,4 +64,4 @@ struct UnpackbitsImplBody { unpackbits_template(context); } -} // namespace cunumeric +} // namespace cupynumeric diff --git a/src/cunumeric/bits/unpackbits.h b/src/cupynumeric/bits/unpackbits.h similarity index 87% rename from src/cunumeric/bits/unpackbits.h rename to src/cupynumeric/bits/unpackbits.h index 96b5d39e03..f010e9fc8e 100644 --- a/src/cunumeric/bits/unpackbits.h +++ b/src/cupynumeric/bits/unpackbits.h @@ -16,10 +16,10 @@ #pragma once -#include "cunumeric/cunumeric_task.h" -#include "cunumeric/bits/bits_util.h" +#include "cupynumeric/cupynumeric_task.h" +#include "cupynumeric/bits/bits_util.h" -namespace cunumeric { +namespace cupynumeric { template struct Unpack; @@ -58,9 +58,9 @@ struct Unpack { } }; -class UnpackbitsTask : public CuNumericTask { +class UnpackbitsTask : public CuPyNumericTask { public: - static constexpr auto TASK_ID = legate::LocalTaskID{CUNUMERIC_UNPACKBITS}; + static constexpr auto TASK_ID = legate::LocalTaskID{CUPYNUMERIC_UNPACKBITS}; public: static void cpu_variant(legate::TaskContext context); @@ -72,4 +72,4 @@ class UnpackbitsTask : public CuNumericTask { #endif }; -} // namespace cunumeric +} // namespace cupynumeric diff --git a/src/cunumeric/bits/unpackbits_omp.cc b/src/cupynumeric/bits/unpackbits_omp.cc similarity index 90% rename from src/cunumeric/bits/unpackbits_omp.cc rename to src/cupynumeric/bits/unpackbits_omp.cc index 02151be529..3f12a5355d 100644 --- a/src/cunumeric/bits/unpackbits_omp.cc +++ b/src/cupynumeric/bits/unpackbits_omp.cc @@ -14,10 +14,10 @@ * */ -#include "cunumeric/bits/unpackbits.h" -#include "cunumeric/bits/unpackbits_template.inl" +#include "cupynumeric/bits/unpackbits.h" +#include "cupynumeric/bits/unpackbits_template.inl" -namespace cunumeric { +namespace cupynumeric { using namespace legate; @@ -44,4 +44,4 @@ struct UnpackbitsImplBody { unpackbits_template(context); } -} // namespace cunumeric +} // namespace cupynumeric diff --git a/src/cunumeric/bits/unpackbits_template.inl b/src/cupynumeric/bits/unpackbits_template.inl similarity index 94% rename from src/cunumeric/bits/unpackbits_template.inl rename to src/cupynumeric/bits/unpackbits_template.inl index 0763818c47..2a710b8c01 100644 --- a/src/cunumeric/bits/unpackbits_template.inl +++ b/src/cupynumeric/bits/unpackbits_template.inl @@ -17,10 +17,10 @@ #pragma once // Useful for IDEs -#include "cunumeric/bits/unpackbits.h" -#include "cunumeric/pitches.h" +#include "cupynumeric/bits/unpackbits.h" +#include "cupynumeric/pitches.h" -namespace cunumeric { +namespace cupynumeric { using namespace legate; @@ -77,4 +77,4 @@ static void unpackbits_template(TaskContext& context) } } -} // namespace cunumeric +} // namespace cupynumeric diff --git a/src/cunumeric/cephes/chbevl.cc b/src/cupynumeric/cephes/chbevl.cc similarity index 100% rename from src/cunumeric/cephes/chbevl.cc rename to src/cupynumeric/cephes/chbevl.cc diff --git a/src/cunumeric/cephes/i0.cc b/src/cupynumeric/cephes/i0.cc similarity index 100% rename from src/cunumeric/cephes/i0.cc rename to src/cupynumeric/cephes/i0.cc diff --git a/src/cunumeric/convolution/convolve.cc b/src/cupynumeric/convolution/convolve.cc similarity index 97% rename from src/cunumeric/convolution/convolve.cc rename to src/cupynumeric/convolution/convolve.cc index daf21180da..b86364d758 100644 --- a/src/cunumeric/convolution/convolve.cc +++ b/src/cupynumeric/convolution/convolve.cc @@ -14,11 +14,11 @@ * */ -#include "cunumeric/divmod.h" -#include "cunumeric/convolution/convolve.h" -#include "cunumeric/convolution/convolve_template.inl" +#include "cupynumeric/divmod.h" +#include "cupynumeric/convolution/convolve.h" +#include "cupynumeric/convolution/convolve_template.inl" -namespace cunumeric { +namespace cupynumeric { // This is the easy to understand functional specification of the // algorithm, but it is commented out in favor of the faster one @@ -83,7 +83,7 @@ struct ConvolveImplBody { const Rect& root_rect, const Rect& subrect, const Rect& filter_rect, - CuNumericConvolveMethod method) const + CuPyNumericConvolveMethod method) const { const Point one = Point::ONES(); Point extents = filter_rect.hi - filter_rect.lo + one; @@ -276,4 +276,4 @@ namespace // unnamed static void __attribute__((constructor)) register_tasks(void) { ConvolveTask::register_variants(); } } // namespace -} // namespace cunumeric +} // namespace cupynumeric diff --git a/src/cunumeric/convolution/convolve.cu b/src/cupynumeric/convolution/convolve.cu similarity index 94% rename from src/cunumeric/convolution/convolve.cu rename to src/cupynumeric/convolution/convolve.cu index e473f3ac4f..6cdacd3b9f 100644 --- a/src/cunumeric/convolution/convolve.cu +++ b/src/cupynumeric/convolution/convolve.cu @@ -14,12 +14,12 @@ * */ -#include "cunumeric/divmod.h" -#include "cunumeric/cuda_help.h" -#include "cunumeric/convolution/convolve.h" -#include "cunumeric/convolution/convolve_template.inl" +#include "cupynumeric/divmod.h" +#include "cupynumeric/cuda_help.h" +#include "cupynumeric/convolution/convolve.h" +#include "cupynumeric/convolution/convolve_template.inl" -namespace cunumeric { +namespace cupynumeric { using namespace legate; @@ -744,7 +744,7 @@ __host__ static inline void launch_small_tile_kernel(AccessorWO out, out, filter, in, root_rect, subrect, filter_rect, args); } } - CUNUMERIC_CHECK_CUDA_STREAM(stream); + CUPYNUMERIC_CHECK_CUDA_STREAM(stream); } template @@ -766,24 +766,24 @@ __host__ void direct_convolution(AccessorWO out, static unsigned long long mask = 0; if (!(mask & (1 << device))) { if (properties.sharedMemPerBlock < max_smem_size) { - CUNUMERIC_CHECK_CUDA(cudaFuncSetAttribute(convolution_small_tile1, - cudaFuncAttributeMaxDynamicSharedMemorySize, - max_smem_size)); - CUNUMERIC_CHECK_CUDA(cudaFuncSetAttribute(convolution_small_tile2, - cudaFuncAttributeMaxDynamicSharedMemorySize, - max_smem_size)); - CUNUMERIC_CHECK_CUDA(cudaFuncSetAttribute(convolution_large_tile, - cudaFuncAttributeMaxDynamicSharedMemorySize, - max_smem_size)); + CUPYNUMERIC_CHECK_CUDA(cudaFuncSetAttribute(convolution_small_tile1, + cudaFuncAttributeMaxDynamicSharedMemorySize, + max_smem_size)); + CUPYNUMERIC_CHECK_CUDA(cudaFuncSetAttribute(convolution_small_tile2, + cudaFuncAttributeMaxDynamicSharedMemorySize, + max_smem_size)); + CUPYNUMERIC_CHECK_CUDA(cudaFuncSetAttribute(convolution_large_tile, + cudaFuncAttributeMaxDynamicSharedMemorySize, + max_smem_size)); } if (sizeof(VAL) >= 8) { // Only need to set this on the first invocation - CUNUMERIC_CHECK_CUDA(cudaFuncSetSharedMemConfig(convolution_small_tile1, - cudaSharedMemBankSizeEightByte)); - CUNUMERIC_CHECK_CUDA(cudaFuncSetSharedMemConfig(convolution_small_tile2, - cudaSharedMemBankSizeEightByte)); - CUNUMERIC_CHECK_CUDA(cudaFuncSetSharedMemConfig(convolution_large_tile, - cudaSharedMemBankSizeEightByte)); + CUPYNUMERIC_CHECK_CUDA(cudaFuncSetSharedMemConfig(convolution_small_tile1, + cudaSharedMemBankSizeEightByte)); + CUPYNUMERIC_CHECK_CUDA(cudaFuncSetSharedMemConfig(convolution_small_tile2, + cudaSharedMemBankSizeEightByte)); + CUPYNUMERIC_CHECK_CUDA(cudaFuncSetSharedMemConfig( + convolution_large_tile, cudaSharedMemBankSizeEightByte)); } // Make sure we have enough bits for every device assert(device < (8 * sizeof(mask))); @@ -848,7 +848,7 @@ __host__ void direct_convolution(AccessorWO out, } if (out_dense) { size_t bytes = sizeof(VAL) * out_pitch; - CUNUMERIC_CHECK_CUDA(cudaMemsetAsync(out_ptr, 0, bytes)); + CUPYNUMERIC_CHECK_CUDA(cudaMemsetAsync(out_ptr, 0, bytes)); } else { out_pitch = 1; ConvolutionInitArgs args; @@ -1168,7 +1168,7 @@ __host__ void direct_convolution(AccessorWO out, one, args); } - CUNUMERIC_CHECK_CUDA_STREAM(stream); + CUPYNUMERIC_CHECK_CUDA_STREAM(stream); } } @@ -1300,7 +1300,7 @@ __host__ static inline void cufft_convolution(AccessorWO out, const Rect& root_rect, const Rect& subrect, const Rect& filter_rect, - CuNumericConvolveMethod method) + CuPyNumericConvolveMethod method) { int device = get_device_ordinal(); auto& properties = get_device_properties(); @@ -1311,19 +1311,19 @@ __host__ static inline void cufft_convolution(AccessorWO out, static unsigned long long mask = 0; if (!(mask & (1 << device))) { if (properties.sharedMemPerBlock < max_smem_size) { - CUNUMERIC_CHECK_CUDA(cudaFuncSetAttribute(convolution_small_tile1, - cudaFuncAttributeMaxDynamicSharedMemorySize, - max_smem_size)); - CUNUMERIC_CHECK_CUDA(cudaFuncSetAttribute(convolution_small_tile2, - cudaFuncAttributeMaxDynamicSharedMemorySize, - max_smem_size)); + CUPYNUMERIC_CHECK_CUDA(cudaFuncSetAttribute(convolution_small_tile1, + cudaFuncAttributeMaxDynamicSharedMemorySize, + max_smem_size)); + CUPYNUMERIC_CHECK_CUDA(cudaFuncSetAttribute(convolution_small_tile2, + cudaFuncAttributeMaxDynamicSharedMemorySize, + max_smem_size)); } if (sizeof(VAL) >= 8) { // Only need to set this on the first invocation - CUNUMERIC_CHECK_CUDA(cudaFuncSetSharedMemConfig(convolution_small_tile1, - cudaSharedMemBankSizeEightByte)); - CUNUMERIC_CHECK_CUDA(cudaFuncSetSharedMemConfig(convolution_small_tile2, - cudaSharedMemBankSizeEightByte)); + CUPYNUMERIC_CHECK_CUDA(cudaFuncSetSharedMemConfig(convolution_small_tile1, + cudaSharedMemBankSizeEightByte)); + CUPYNUMERIC_CHECK_CUDA(cudaFuncSetSharedMemConfig(convolution_small_tile2, + cudaSharedMemBankSizeEightByte)); } // Make sure we have enough bits for every device assert(device < (8 * sizeof(mask))); @@ -1355,7 +1355,7 @@ __host__ static inline void cufft_convolution(AccessorWO out, for (int d = 0; d < DIM; d++) { smem_size *= (tile[d] + 2 * centers[d]); } - if (method != CUNUMERIC_CONVOLVE_FFT && smem_size <= max_smem_size) { + if (method != CUPYNUMERIC_CONVOLVE_FFT && smem_size <= max_smem_size) { launch_small_tile_kernel(out, filter, in, @@ -1406,7 +1406,7 @@ __host__ static inline void cufft_convolution(AccessorWO out, // Zero pad and copy in the input data auto signal_buffer = create_buffer(buffersize, Memory::GPU_FB_MEM, 128 /*alignment*/); VAL* signal_ptr = signal_buffer.ptr(zero); - CUNUMERIC_CHECK_CUDA(cudaMemsetAsync(signal_ptr, 0, buffervolume * sizeof(VAL), stream)); + CUPYNUMERIC_CHECK_CUDA(cudaMemsetAsync(signal_ptr, 0, buffervolume * sizeof(VAL), stream)); // Check to see if the input pointer is dense and we can do this with a CUDA memcpy size_t strides[DIM]; const VAL* input_ptr = in.ptr(input_bounds, strides); @@ -1422,7 +1422,7 @@ __host__ static inline void cufft_convolution(AccessorWO out, // Zero pad and copy in the filter data auto filter_buffer = create_buffer(buffersize, Memory::GPU_FB_MEM, 128 /*alignment*/); VAL* filter_ptr = filter_buffer.ptr(zero); - CUNUMERIC_CHECK_CUDA(cudaMemsetAsync(filter_ptr, 0, buffervolume * sizeof(VAL), stream)); + CUPYNUMERIC_CHECK_CUDA(cudaMemsetAsync(filter_ptr, 0, buffervolume * sizeof(VAL), stream)); const VAL* filt_ptr = filter.ptr(filter_rect, strides); pitch = 1; for (int d = DIM - 1; d >= 0; d--) { @@ -1433,7 +1433,7 @@ __host__ static inline void cufft_convolution(AccessorWO out, copy_into_buffer<<>>( filter, filter_buffer, filter_rect.lo, copy_pitches, pitch); - CUNUMERIC_CHECK_CUDA_STREAM(stream); + CUPYNUMERIC_CHECK_CUDA_STREAM(stream); auto forward_plan = get_cufft_plan(ForwardPlanType::value, cufftPlanParams(fftsize)); auto backward_plan = get_cufft_plan(BackwardPlanType::value, cufftPlanParams(fftsize)); @@ -1456,7 +1456,7 @@ __host__ static inline void cufft_convolution(AccessorWO out, // FFT the filter data cufft_execute_forward(forward_plan.handle(), filter_ptr, filter_ptr); - CUNUMERIC_CHECK_CUDA_STREAM(stream); + CUPYNUMERIC_CHECK_CUDA_STREAM(stream); // Perform the pointwise multiplcation { @@ -1493,13 +1493,13 @@ __host__ static inline void cufft_convolution(AccessorWO out, copy_from_buffer<<>>( filter_ptr, out, buffer_offset, subrect.lo, copy_pitches, fft_pitches, pitch, scaling_factor); - CUNUMERIC_CHECK_CUDA_STREAM(stream); + CUPYNUMERIC_CHECK_CUDA_STREAM(stream); #if 0 // This is useful debugging code for finding the output VAL *buffer = (VAL*)malloc(buffervolume*sizeof(VAL)); - CUNUMERIC_CHECK_CUDA( cudaMemcpyAsync(buffer, filter_ptr, buffervolume*sizeof(VAL), cudaMemcpyDeviceToHost, stream) ); - CUNUMERIC_CHECK_CUDA( cudaStreamSynchronize(stream) ); + CUPYNUMERIC_CHECK_CUDA( cudaMemcpyAsync(buffer, filter_ptr, buffervolume*sizeof(VAL), cudaMemcpyDeviceToHost, stream) ); + CUPYNUMERIC_CHECK_CUDA( cudaStreamSynchronize(stream) ); for (unsigned idx = 0; idx < buffervolume; idx++) { if ((idx % fftsize[DIM-1]) == 0) printf("\n"); @@ -1533,9 +1533,9 @@ struct ConvolveImplBody { const Rect<_DIM>& root_rect, const Rect<_DIM>& subrect, const Rect<_DIM>& filter_rect, - CuNumericConvolveMethod method) const + CuPyNumericConvolveMethod method) const { - if (method == CUNUMERIC_CONVOLVE_DIRECT) { + if (method == CUPYNUMERIC_CONVOLVE_DIRECT) { direct_convolution<_VAL, _DIM>(out, filter, in, root_rect, subrect, filter_rect); } else { cufft_convolution<_VAL, _DIM>(out, filter, in, root_rect, subrect, filter_rect, method); @@ -1551,7 +1551,7 @@ struct ConvolveImplBody { const Rect<_DIM>& root_rect, const Rect<_DIM>& subrect, const Rect<_DIM>& filter_rect, - CuNumericConvolveMethod method) const + CuPyNumericConvolveMethod method) const { direct_convolution<_VAL, _DIM>(out, filter, in, root_rect, subrect, filter_rect); } @@ -1562,7 +1562,7 @@ struct ConvolveImplBody { const Rect& root_rect, const Rect& subrect, const Rect& filter_rect, - CuNumericConvolveMethod method) const + CuPyNumericConvolveMethod method) const { dispatch(out, filter, in, root_rect, subrect, filter_rect, method); } @@ -1573,4 +1573,4 @@ struct ConvolveImplBody { convolve_template(context); } -} // namespace cunumeric +} // namespace cupynumeric diff --git a/src/cunumeric/convolution/convolve.h b/src/cupynumeric/convolution/convolve.h similarity index 84% rename from src/cunumeric/convolution/convolve.h rename to src/cupynumeric/convolution/convolve.h index f03d8d0373..cc6f6aa404 100644 --- a/src/cunumeric/convolution/convolve.h +++ b/src/cupynumeric/convolution/convolve.h @@ -16,7 +16,7 @@ #pragma once -#include "cunumeric/cunumeric_task.h" +#include "cupynumeric/cupynumeric_task.h" // We'll make some assumptions here about cache size // that should hold up against most CPUs out there today @@ -27,19 +27,19 @@ // Most caches have 64B lines #define CACHE_LINE_SIZE 64 -namespace cunumeric { +namespace cupynumeric { struct ConvolveArgs { legate::PhysicalStore out{nullptr}; legate::PhysicalStore filter{nullptr}; std::vector inputs; legate::Domain root_domain; - CuNumericConvolveMethod method; + CuPyNumericConvolveMethod method; }; -class ConvolveTask : public CuNumericTask { +class ConvolveTask : public CuPyNumericTask { public: - static constexpr auto TASK_ID = legate::LocalTaskID{CUNUMERIC_CONVOLVE}; + static constexpr auto TASK_ID = legate::LocalTaskID{CUPYNUMERIC_CONVOLVE}; public: static void cpu_variant(legate::TaskContext context); @@ -51,4 +51,4 @@ class ConvolveTask : public CuNumericTask { #endif }; -} // namespace cunumeric +} // namespace cupynumeric diff --git a/src/cunumeric/convolution/convolve_omp.cc b/src/cupynumeric/convolution/convolve_omp.cc similarity index 97% rename from src/cunumeric/convolution/convolve_omp.cc rename to src/cupynumeric/convolution/convolve_omp.cc index 6bcbc5e4ca..f927edfb24 100644 --- a/src/cunumeric/convolution/convolve_omp.cc +++ b/src/cupynumeric/convolution/convolve_omp.cc @@ -14,13 +14,13 @@ * */ -#include "cunumeric/divmod.h" -#include "cunumeric/convolution/convolve.h" -#include "cunumeric/convolution/convolve_template.inl" +#include "cupynumeric/divmod.h" +#include "cupynumeric/convolution/convolve.h" +#include "cupynumeric/convolution/convolve_template.inl" #include -namespace cunumeric { +namespace cupynumeric { using namespace legate; @@ -34,7 +34,7 @@ struct ConvolveImplBody { const Rect& root_rect, const Rect& subrect, const Rect& filter_rect, - CuNumericConvolveMethod method) const + CuPyNumericConvolveMethod method) const { const Point one = Point::ONES(); Point extents = filter_rect.hi - filter_rect.lo + one; @@ -239,4 +239,4 @@ struct ConvolveImplBody { convolve_template(context); } -} // namespace cunumeric +} // namespace cupynumeric diff --git a/src/cunumeric/convolution/convolve_template.inl b/src/cupynumeric/convolution/convolve_template.inl similarity index 98% rename from src/cunumeric/convolution/convolve_template.inl rename to src/cupynumeric/convolution/convolve_template.inl index 2135260e42..ce7adf1041 100644 --- a/src/cunumeric/convolution/convolve_template.inl +++ b/src/cupynumeric/convolution/convolve_template.inl @@ -17,12 +17,12 @@ #pragma once // Useful for IDEs -#include "cunumeric/convolution/convolve.h" -#include "cunumeric/pitches.h" +#include "cupynumeric/convolution/convolve.h" +#include "cupynumeric/pitches.h" #include -namespace cunumeric { +namespace cupynumeric { using namespace legate; @@ -86,7 +86,7 @@ static void convolve_template(TaskContext& context) args.root_domain.rect_data[dim + shape.dim] = shape[dim] - 1; } - args.method = static_cast(context.scalar(1).value()); + args.method = static_cast(context.scalar(1).value()); double_dispatch(args.out.dim(), args.out.code(), ConvolveImpl{}, args); } @@ -395,4 +395,4 @@ static unsigned roundup_tile(Point& tile, } } -} // namespace cunumeric +} // namespace cupynumeric diff --git a/src/cunumeric/cuda_help.h b/src/cupynumeric/cuda_help.h similarity index 82% rename from src/cunumeric/cuda_help.h rename to src/cupynumeric/cuda_help.h index 1be68ffdb4..3e487f2baf 100644 --- a/src/cunumeric/cuda_help.h +++ b/src/cupynumeric/cuda_help.h @@ -23,10 +23,10 @@ #endif #include "legate/cuda/stream_pool.h" -#include "cunumeric/arg.h" +#include "cupynumeric/arg.h" #include #include -#if LEGATE_DEFINED(CUNUMERIC_USE_CUSOLVERMP) +#if LEGATE_DEFINED(CUPYNUMERIC_USE_CUSOLVERMP) #include #include #endif @@ -42,7 +42,7 @@ #define COOPERATIVE_THREADS 256 #define COOPERATIVE_CTAS_PER_SM 4 -namespace cunumeric { +namespace cupynumeric { __host__ inline void check_cuda(cudaError_t error, const char* file, int line) { @@ -53,7 +53,7 @@ __host__ inline void check_cuda(cudaError_t error, const char* file, int line) cudaGetErrorName(error), file, line); -#ifdef DEBUG_CUNUMERIC +#ifdef DEBUG_CUPYNUMERIC assert(false); #else exit(error); @@ -69,7 +69,7 @@ __host__ inline void check_cublas(cublasStatus_t status, const char* file, int l status, file, line); -#ifdef DEBUG_CUNUMERIC +#ifdef DEBUG_CUPYNUMERIC assert(false); #else exit(status); @@ -85,7 +85,7 @@ __host__ inline void check_cufft(cufftResult result, const char* file, int line) result, file, line); -#ifdef DEBUG_CUNUMERIC +#ifdef DEBUG_CUPYNUMERIC assert(false); #else exit(result); @@ -101,7 +101,7 @@ __host__ inline void check_cusolver(cusolverStatus_t status, const char* file, i status, file, line); -#ifdef DEBUG_CUNUMERIC +#ifdef DEBUG_CUPYNUMERIC assert(false); #else exit(status); @@ -109,7 +109,7 @@ __host__ inline void check_cusolver(cusolverStatus_t status, const char* file, i } } -#if LEGATE_DEFINED(CUNUMERIC_USE_CUSOLVERMP) +#if LEGATE_DEFINED(CUPYNUMERIC_USE_CUSOLVERMP) __host__ inline void check_cal(calError_t status, const char* file, int line) { if (status != CAL_OK) { @@ -118,7 +118,7 @@ __host__ inline void check_cal(calError_t status, const char* file, int line) status, file, line); -#ifdef DEBUG_CUNUMERIC +#ifdef DEBUG_CUPYNUMERIC assert(false); #else exit(status); @@ -136,7 +136,7 @@ __host__ inline void check_cutensor(cutensorStatus_t result, const char* file, i result, file, line); -#ifdef DEBUG_CUNUMERIC +#ifdef DEBUG_CUPYNUMERIC assert(false); #else exit(result); @@ -152,7 +152,7 @@ __host__ inline void check_nccl(ncclResult_t error, const char* file, int line) ncclGetErrorString(error), file, line); -#ifdef DEBUG_CUNUMERIC +#ifdef DEBUG_CUPYNUMERIC assert(false); #else exit(error); @@ -160,60 +160,60 @@ __host__ inline void check_nccl(ncclResult_t error, const char* file, int line) } } -} // namespace cunumeric +} // namespace cupynumeric -#define CHECK_CUBLAS(expr) \ - do { \ - cublasStatus_t __result__ = (expr); \ - cunumeric::check_cublas(__result__, __FILE__, __LINE__); \ +#define CHECK_CUBLAS(expr) \ + do { \ + cublasStatus_t __result__ = (expr); \ + cupynumeric::check_cublas(__result__, __FILE__, __LINE__); \ } while (false) -#define CHECK_CUFFT(expr) \ - do { \ - cufftResult __result__ = (expr); \ - cunumeric::check_cufft(__result__, __FILE__, __LINE__); \ +#define CHECK_CUFFT(expr) \ + do { \ + cufftResult __result__ = (expr); \ + cupynumeric::check_cufft(__result__, __FILE__, __LINE__); \ } while (false) -#define CHECK_CUSOLVER(expr) \ - do { \ - cusolverStatus_t __result__ = (expr); \ - cunumeric::check_cusolver(__result__, __FILE__, __LINE__); \ +#define CHECK_CUSOLVER(expr) \ + do { \ + cusolverStatus_t __result__ = (expr); \ + cupynumeric::check_cusolver(__result__, __FILE__, __LINE__); \ } while (false) -#define CHECK_CAL(expr) \ - do { \ - calError_t __result__ = (expr); \ - cunumeric::check_cal(__result__, __FILE__, __LINE__); \ +#define CHECK_CAL(expr) \ + do { \ + calError_t __result__ = (expr); \ + cupynumeric::check_cal(__result__, __FILE__, __LINE__); \ } while (false) -#define CHECK_CUTENSOR(expr) \ - do { \ - cutensorStatus_t __result__ = (expr); \ - cunumeric::check_cutensor(__result__, __FILE__, __LINE__); \ +#define CHECK_CUTENSOR(expr) \ + do { \ + cutensorStatus_t __result__ = (expr); \ + cupynumeric::check_cutensor(__result__, __FILE__, __LINE__); \ } while (false) -#define CHECK_NCCL(...) \ - do { \ - ncclResult_t __result__ = (__VA_ARGS__); \ - cunumeric::check_nccl(__result__, __FILE__, __LINE__); \ +#define CHECK_NCCL(...) \ + do { \ + ncclResult_t __result__ = (__VA_ARGS__); \ + cupynumeric::check_nccl(__result__, __FILE__, __LINE__); \ } while (false) -#define CUNUMERIC_CHECK_CUDA(...) \ - do { \ - cudaError_t __result__ = (__VA_ARGS__); \ - cunumeric::check_cuda(__result__, __FILE__, __LINE__); \ +#define CUPYNUMERIC_CHECK_CUDA(...) \ + do { \ + cudaError_t __result__ = (__VA_ARGS__); \ + cupynumeric::check_cuda(__result__, __FILE__, __LINE__); \ } while (false) -#ifdef DEBUG_CUNUMERIC -#define CUNUMERIC_CHECK_CUDA_STREAM(stream) \ - do { \ - CUNUMERIC_CHECK_CUDA(cudaStreamSynchronize(stream)); \ - CUNUMERIC_CHECK_CUDA(cudaPeekAtLastError()); \ +#ifdef DEBUG_CUPYNUMERIC +#define CUPYNUMERIC_CHECK_CUDA_STREAM(stream) \ + do { \ + CUPYNUMERIC_CHECK_CUDA(cudaStreamSynchronize(stream)); \ + CUPYNUMERIC_CHECK_CUDA(cudaPeekAtLastError()); \ } while (false) #else -#define CUNUMERIC_CHECK_CUDA_STREAM(stream) \ - do { \ - CUNUMERIC_CHECK_CUDA(cudaPeekAtLastError()); \ +#define CUPYNUMERIC_CHECK_CUDA_STREAM(stream) \ + do { \ + CUPYNUMERIC_CHECK_CUDA(cudaPeekAtLastError()); \ } while (false) #endif @@ -224,10 +224,10 @@ __host__ inline void check_nccl(ncclResult_t error, const char* file, int line) #define MIN(x, y) (((x) < (y)) ? (x) : (y)) #endif -// Must go here since it depends on CUNUMERIC_CHECK_CUDA(), which is defined in this header... -#include "cunumeric/device_scalar_reduction_buffer.h" +// Must go here since it depends on CUPYNUMERIC_CHECK_CUDA(), which is defined in this header... +#include "cupynumeric/device_scalar_reduction_buffer.h" -namespace cunumeric { +namespace cupynumeric { template struct cudaTypeToDataType; @@ -319,7 +319,7 @@ int get_device_ordinal(); const cudaDeviceProp& get_device_properties(); cublasHandle_t get_cublas(); cusolverDnHandle_t get_cusolver(); -#if LEGATE_DEFINED(CUNUMERIC_USE_CUSOLVERMP) +#if LEGATE_DEFINED(CUPYNUMERIC_USE_CUSOLVERMP) cusolverMpHandle_t get_cusolvermp(); #endif [[nodiscard]] const cutensorHandle_t& get_cutensor(); @@ -521,4 +521,4 @@ __device__ __forceinline__ void store_streaming(double* ptr, double valu asm volatile("st.global.cs.f64 [%0], %1;" : : "l"(ptr), "d"(value) : "memory"); } -} // namespace cunumeric +} // namespace cupynumeric diff --git a/src/cunumeric/cudalibs.cu b/src/cupynumeric/cudalibs.cu similarity index 92% rename from src/cunumeric/cudalibs.cu rename to src/cupynumeric/cudalibs.cu index 3b09f495aa..a4e89638aa 100644 --- a/src/cunumeric/cudalibs.cu +++ b/src/cupynumeric/cudalibs.cu @@ -14,8 +14,8 @@ * */ -#include "cunumeric/cunumeric_task.h" -#include "cunumeric/random/bitgenerator.h" +#include "cupynumeric/cupynumeric_task.h" +#include "cupynumeric/random/bitgenerator.h" #include "cudalibs.h" @@ -23,9 +23,9 @@ using namespace legate; -namespace cunumeric { +namespace cupynumeric { -static Logger log_cudalibs("cunumeric.cudalibs"); +static Logger log_cudalibs("cupynumeric.cudalibs"); cufftContext::cufftContext(cufftPlan* plan) : plan_(plan) {} @@ -272,7 +272,7 @@ CUDALibraries::CUDALibraries() : finalized_(false), cublas_(nullptr), cusolver_(nullptr), -#if LEGATE_DEFINED(CUNUMERIC_USE_CUSOLVERMP) +#if LEGATE_DEFINED(CUPYNUMERIC_USE_CUSOLVERMP) cusolvermp_(nullptr), #endif plan_caches_() @@ -292,7 +292,7 @@ void CUDALibraries::finalize() if (cusolver_ != nullptr) { finalize_cusolver(); } -#if LEGATE_DEFINED(CUNUMERIC_USE_CUSOLVERMP) +#if LEGATE_DEFINED(CUPYNUMERIC_USE_CUSOLVERMP) if (cusolvermp_ != nullptr) { finalize_cusolvermp(); } @@ -318,7 +318,7 @@ void CUDALibraries::finalize_cusolver() cusolver_ = nullptr; } -#if LEGATE_DEFINED(CUNUMERIC_USE_CUSOLVERMP) +#if LEGATE_DEFINED(CUPYNUMERIC_USE_CUSOLVERMP) void CUDALibraries::finalize_cusolvermp() { CHECK_CUSOLVER(cusolverMpDestroy(cusolvermp_)); @@ -340,7 +340,7 @@ int CUDALibraries::get_device_ordinal() return *ordinal_; } int ordinal{-1}; - CUNUMERIC_CHECK_CUDA(cudaGetDevice(&ordinal)); + CUPYNUMERIC_CHECK_CUDA(cudaGetDevice(&ordinal)); ordinal_ = ordinal; return ordinal; } @@ -351,7 +351,7 @@ const cudaDeviceProp& CUDALibraries::get_device_properties() return *device_prop_; } device_prop_ = std::make_unique(); - CUNUMERIC_CHECK_CUDA(cudaGetDeviceProperties(device_prop_.get(), get_device_ordinal())); + CUPYNUMERIC_CHECK_CUDA(cudaGetDeviceProperties(device_prop_.get(), get_device_ordinal())); return *device_prop_; } @@ -359,7 +359,7 @@ cublasHandle_t CUDALibraries::get_cublas() { if (nullptr == cublas_) { CHECK_CUBLAS(cublasCreate(&cublas_)); - const char* fast_math = getenv("CUNUMERIC_FAST_MATH"); + const char* fast_math = getenv("CUPYNUMERIC_FAST_MATH"); if (fast_math != nullptr && atoi(fast_math) > 0) { // Enable acceleration of single precision routines using TF32 tensor cores. cublasStatus_t status = cublasSetMathMode(cublas_, CUBLAS_TF32_TENSOR_OP_MATH); @@ -379,12 +379,12 @@ cusolverDnHandle_t CUDALibraries::get_cusolver() return cusolver_; } -#if LEGATE_DEFINED(CUNUMERIC_USE_CUSOLVERMP) +#if LEGATE_DEFINED(CUPYNUMERIC_USE_CUSOLVERMP) cusolverMpHandle_t CUDALibraries::get_cusolvermp() { if (nullptr == cusolvermp_) { int device = -1; - CUNUMERIC_CHECK_CUDA(cudaGetDevice(&device)); + CUPYNUMERIC_CHECK_CUDA(cudaGetDevice(&device)); CHECK_CUSOLVER(cusolverMpCreate(&cusolvermp_, device, get_cached_stream())); } return cusolvermp_; @@ -443,7 +443,7 @@ cusolverDnContext* get_cusolver() return lib.get_cusolver(); } -#if LEGATE_DEFINED(CUNUMERIC_USE_CUSOLVERMP) +#if LEGATE_DEFINED(CUPYNUMERIC_USE_CUSOLVERMP) cusolverMpHandle* get_cusolvermp() { const auto proc = legate::Processor::get_executing_processor(); @@ -480,9 +480,9 @@ int get_device_ordinal() return lib.get_device_ordinal(); } -class LoadCUDALibsTask : public CuNumericTask { +class LoadCUDALibsTask : public CuPyNumericTask { public: - static constexpr auto TASK_ID = legate::LocalTaskID{CUNUMERIC_LOAD_CUDALIBS}; + static constexpr auto TASK_ID = legate::LocalTaskID{CUPYNUMERIC_LOAD_CUDALIBS}; public: static void gpu_variant(legate::TaskContext context) @@ -491,16 +491,16 @@ class LoadCUDALibsTask : public CuNumericTask { auto& lib = get_cuda_libraries(proc); lib.get_cublas(); lib.get_cusolver(); -#if LEGATE_DEFINED(CUNUMERIC_USE_CUSOLVERMP) +#if LEGATE_DEFINED(CUPYNUMERIC_USE_CUSOLVERMP) lib.get_cusolvermp(); #endif static_cast(lib.get_cutensor()); } }; -class UnloadCUDALibsTask : public CuNumericTask { +class UnloadCUDALibsTask : public CuPyNumericTask { public: - static constexpr auto TASK_ID = legate::LocalTaskID{CUNUMERIC_UNLOAD_CUDALIBS}; + static constexpr auto TASK_ID = legate::LocalTaskID{CUPYNUMERIC_UNLOAD_CUDALIBS}; public: static void gpu_variant(legate::TaskContext context) @@ -518,4 +518,4 @@ static void __attribute__((constructor)) register_tasks(void) UnloadCUDALibsTask::register_variants(); } -} // namespace cunumeric +} // namespace cupynumeric diff --git a/src/cunumeric/cudalibs.h b/src/cupynumeric/cudalibs.h similarity index 89% rename from src/cunumeric/cudalibs.h rename to src/cupynumeric/cudalibs.h index 6996815110..8615ba3225 100644 --- a/src/cunumeric/cudalibs.h +++ b/src/cupynumeric/cudalibs.h @@ -18,7 +18,7 @@ #include "cuda_help.h" -namespace cunumeric { +namespace cupynumeric { struct cufftPlanCache; @@ -38,7 +38,7 @@ struct CUDALibraries { const cudaDeviceProp& get_device_properties(); cublasHandle_t get_cublas(); cusolverDnHandle_t get_cusolver(); -#if LEGATE_DEFINED(CUNUMERIC_USE_CUSOLVERMP) +#if LEGATE_DEFINED(CUPYNUMERIC_USE_CUSOLVERMP) cusolverMpHandle_t get_cusolvermp(); #endif [[nodiscard]] const cutensorHandle_t& get_cutensor(); @@ -47,7 +47,7 @@ struct CUDALibraries { private: void finalize_cublas(); void finalize_cusolver(); -#if LEGATE_DEFINED(CUNUMERIC_USE_CUSOLVERMP) +#if LEGATE_DEFINED(CUPYNUMERIC_USE_CUSOLVERMP) void finalize_cusolvermp(); #endif void finalize_cutensor(); @@ -58,11 +58,11 @@ struct CUDALibraries { std::unique_ptr device_prop_{}; cublasContext* cublas_; cusolverDnContext* cusolver_; -#if LEGATE_DEFINED(CUNUMERIC_USE_CUSOLVERMP) +#if LEGATE_DEFINED(CUPYNUMERIC_USE_CUSOLVERMP) cusolverMpHandle* cusolvermp_; #endif std::optional cutensor_{}; std::map plan_caches_; }; -} // namespace cunumeric +} // namespace cupynumeric diff --git a/src/cunumeric/cunumeric.cc b/src/cupynumeric/cupynumeric.cc similarity index 55% rename from src/cunumeric/cunumeric.cc rename to src/cupynumeric/cupynumeric.cc index 2f62991118..5c2365871a 100644 --- a/src/cunumeric/cunumeric.cc +++ b/src/cupynumeric/cupynumeric.cc @@ -14,19 +14,19 @@ * */ -#include "cunumeric/cunumeric_c.h" -#include "cunumeric/cunumeric_task.h" -#include "cunumeric/mapper.h" -#include "cunumeric/runtime.h" -#include "cunumeric/unary/unary_red_util.h" +#include "cupynumeric/cupynumeric_c.h" +#include "cupynumeric/cupynumeric_task.h" +#include "cupynumeric/mapper.h" +#include "cupynumeric/runtime.h" +#include "cupynumeric/unary/unary_red_util.h" using namespace legate; -namespace cunumeric { +namespace cupynumeric { -static const char* const cunumeric_library_name = "cunumeric"; +static const char* const cupynumeric_library_name = "cupynumeric"; -/*static*/ TaskRegistrar& CuNumericRegistrar::get_registrar() +/*static*/ TaskRegistrar& CuPyNumericRegistrar::get_registrar() { static TaskRegistrar registrar; return registrar; @@ -42,41 +42,41 @@ void unload_cudalibs() noexcept } auto runtime = legate::Runtime::get_runtime(); - auto library = runtime->find_library(cunumeric_library_name); + auto library = runtime->find_library(cupynumeric_library_name); // Issue an execution fence so all outstanding tasks are done before we start destroying handles runtime->issue_execution_fence(); runtime->submit( runtime->create_task(library, - legate::LocalTaskID{CuNumericOpCode::CUNUMERIC_UNLOAD_CUDALIBS}, + legate::LocalTaskID{CuPyNumericOpCode::CUPYNUMERIC_UNLOAD_CUDALIBS}, legate::tuple{num_gpus})); } void registration_callback() { ResourceConfig config; - config.max_tasks = CUNUMERIC_MAX_TASKS; - config.max_reduction_ops = CUNUMERIC_MAX_REDOPS; + config.max_tasks = CUPYNUMERIC_MAX_TASKS; + config.max_reduction_ops = CUPYNUMERIC_MAX_REDOPS; auto runtime = legate::Runtime::get_runtime(); - auto library = - runtime->create_library(cunumeric_library_name, config, std::make_unique()); + auto library = runtime->create_library( + cupynumeric_library_name, config, std::make_unique()); - CuNumericRegistrar::get_registrar().register_all_tasks(library); - CuNumericRuntime::initialize(runtime, library); + CuPyNumericRegistrar::get_registrar().register_all_tasks(library); + CuPyNumericRuntime::initialize(runtime, library); legate::register_shutdown_callback(unload_cudalibs); } -} // namespace cunumeric +} // namespace cupynumeric extern "C" { -void cunumeric_perform_registration(void) { cunumeric::registration_callback(); } +void cupynumeric_perform_registration(void) { cupynumeric::registration_callback(); } -bool cunumeric_has_cusolvermp() +bool cupynumeric_has_cusolvermp() { - return LEGATE_DEFINED(LEGATE_USE_CUDA) && LEGATE_DEFINED(CUNUMERIC_USE_CUSOLVERMP); + return LEGATE_DEFINED(LEGATE_USE_CUDA) && LEGATE_DEFINED(CUPYNUMERIC_USE_CUSOLVERMP); } } diff --git a/src/cunumeric/cunumeric.cu b/src/cupynumeric/cupynumeric.cu similarity index 95% rename from src/cunumeric/cunumeric.cu rename to src/cupynumeric/cupynumeric.cu index 1c44e57b59..59209290da 100644 --- a/src/cunumeric/cunumeric.cu +++ b/src/cupynumeric/cupynumeric.cu @@ -14,11 +14,11 @@ * */ -#include "cunumeric_task.h" +#include "cupynumeric_task.h" #include "arg.h" #include "arg.inl" -namespace cunumeric { +namespace cupynumeric { #define REGISTER_REDOPS(OP) \ { \ @@ -42,4 +42,4 @@ void register_reduction_operators(legate::LibraryContext& context) REGISTER_REDOPS(ArgminReduction); } -} // namespace cunumeric +} // namespace cupynumeric diff --git a/src/cupynumeric/cupynumeric_c.h b/src/cupynumeric/cupynumeric_c.h new file mode 100644 index 0000000000..fae0123a2f --- /dev/null +++ b/src/cupynumeric/cupynumeric_c.h @@ -0,0 +1,355 @@ +/* Copyright 2024 NVIDIA Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +#ifndef __CUPYNUMERIC_C_H__ +#define __CUPYNUMERIC_C_H__ + +// Match these to CuPyNumericOpCode in config.py +// Also, sort these alphabetically except the first one for easy lookup later +enum CuPyNumericOpCode { + _CUPYNUMERIC_OP_CODE_BASE = 0, + CUPYNUMERIC_ADVANCED_INDEXING, + CUPYNUMERIC_ARANGE, + CUPYNUMERIC_ARGWHERE, + CUPYNUMERIC_BATCHED_CHOLESKY, + CUPYNUMERIC_BINARY_OP, + CUPYNUMERIC_BINARY_RED, + CUPYNUMERIC_BINCOUNT, + CUPYNUMERIC_BITGENERATOR, + CUPYNUMERIC_CHOOSE, + CUPYNUMERIC_CONTRACT, + CUPYNUMERIC_CONVERT, + CUPYNUMERIC_CONVOLVE, + CUPYNUMERIC_SCAN_GLOBAL, + CUPYNUMERIC_SCAN_LOCAL, + CUPYNUMERIC_DIAG, + CUPYNUMERIC_DOT, + CUPYNUMERIC_EYE, + CUPYNUMERIC_FFT, + CUPYNUMERIC_FILL, + CUPYNUMERIC_FLIP, + CUPYNUMERIC_GEMM, + CUPYNUMERIC_HISTOGRAM, + CUPYNUMERIC_LOAD_CUDALIBS, + CUPYNUMERIC_MATMUL, + CUPYNUMERIC_MATVECMUL, + CUPYNUMERIC_MP_POTRF, + CUPYNUMERIC_MP_SOLVE, + CUPYNUMERIC_NONZERO, + CUPYNUMERIC_PACKBITS, + CUPYNUMERIC_POTRF, + CUPYNUMERIC_PUTMASK, + CUPYNUMERIC_QR, + CUPYNUMERIC_RAND, + CUPYNUMERIC_READ, + CUPYNUMERIC_REPEAT, + CUPYNUMERIC_SCALAR_UNARY_RED, + CUPYNUMERIC_SEARCHSORTED, + CUPYNUMERIC_SELECT, + CUPYNUMERIC_SOLVE, + CUPYNUMERIC_SORT, + CUPYNUMERIC_SVD, + CUPYNUMERIC_SYRK, + CUPYNUMERIC_TILE, + CUPYNUMERIC_TRANSPOSE_COPY_2D, + CUPYNUMERIC_TRILU, + CUPYNUMERIC_TRSM, + CUPYNUMERIC_UNARY_OP, + CUPYNUMERIC_UNARY_RED, + CUPYNUMERIC_UNIQUE, + CUPYNUMERIC_UNIQUE_REDUCE, + CUPYNUMERIC_UNLOAD_CUDALIBS, + CUPYNUMERIC_UNPACKBITS, + CUPYNUMERIC_WHERE, + CUPYNUMERIC_WINDOW, + CUPYNUMERIC_WRAP, + CUPYNUMERIC_WRITE, + CUPYNUMERIC_ZIP, +}; + +// Match these to UnaryOpCode in config.py +// Also, sort these alphabetically for easy lookup later +enum CuPyNumericUnaryOpCode { + CUPYNUMERIC_UOP_ABSOLUTE = 1, + CUPYNUMERIC_UOP_ANGLE, + CUPYNUMERIC_UOP_ARCCOS, + CUPYNUMERIC_UOP_ARCCOSH, + CUPYNUMERIC_UOP_ARCSIN, + CUPYNUMERIC_UOP_ARCSINH, + CUPYNUMERIC_UOP_ARCTAN, + CUPYNUMERIC_UOP_ARCTANH, + CUPYNUMERIC_UOP_CBRT, + CUPYNUMERIC_UOP_CEIL, + CUPYNUMERIC_UOP_CLIP, + CUPYNUMERIC_UOP_CONJ, + CUPYNUMERIC_UOP_COPY, + CUPYNUMERIC_UOP_COS, + CUPYNUMERIC_UOP_COSH, + CUPYNUMERIC_UOP_DEG2RAD, + CUPYNUMERIC_UOP_EXP, + CUPYNUMERIC_UOP_EXP2, + CUPYNUMERIC_UOP_EXPM1, + CUPYNUMERIC_UOP_FLOOR, + CUPYNUMERIC_UOP_FREXP, + CUPYNUMERIC_UOP_GETARG, + CUPYNUMERIC_UOP_IMAG, + CUPYNUMERIC_UOP_INVERT, + CUPYNUMERIC_UOP_ISFINITE, + CUPYNUMERIC_UOP_ISINF, + CUPYNUMERIC_UOP_ISNAN, + CUPYNUMERIC_UOP_LOG, + CUPYNUMERIC_UOP_LOG10, + CUPYNUMERIC_UOP_LOG1P, + CUPYNUMERIC_UOP_LOG2, + CUPYNUMERIC_UOP_LOGICAL_NOT, + CUPYNUMERIC_UOP_MODF, + CUPYNUMERIC_UOP_NEGATIVE, + CUPYNUMERIC_UOP_POSITIVE, + CUPYNUMERIC_UOP_RAD2DEG, + CUPYNUMERIC_UOP_REAL, + CUPYNUMERIC_UOP_RECIPROCAL, + CUPYNUMERIC_UOP_RINT, + CUPYNUMERIC_UOP_ROUND, + CUPYNUMERIC_UOP_SIGN, + CUPYNUMERIC_UOP_SIGNBIT, + CUPYNUMERIC_UOP_SIN, + CUPYNUMERIC_UOP_SINH, + CUPYNUMERIC_UOP_SQRT, + CUPYNUMERIC_UOP_SQUARE, + CUPYNUMERIC_UOP_TAN, + CUPYNUMERIC_UOP_TANH, + CUPYNUMERIC_UOP_TRUNC, +}; + +// Match these to UnaryRedCode in config.py +// Also, sort these alphabetically for easy lookup later +enum CuPyNumericUnaryRedCode { + CUPYNUMERIC_RED_ALL = 1, + CUPYNUMERIC_RED_ANY, + CUPYNUMERIC_RED_ARGMAX, + CUPYNUMERIC_RED_ARGMIN, + CUPYNUMERIC_RED_CONTAINS, + CUPYNUMERIC_RED_COUNT_NONZERO, + CUPYNUMERIC_RED_MAX, + CUPYNUMERIC_RED_MIN, + CUPYNUMERIC_RED_NANARGMAX, + CUPYNUMERIC_RED_NANARGMIN, + CUPYNUMERIC_RED_NANMAX, + CUPYNUMERIC_RED_NANMIN, + CUPYNUMERIC_RED_NANPROD, + CUPYNUMERIC_RED_NANSUM, + CUPYNUMERIC_RED_PROD, + CUPYNUMERIC_RED_SUM, + CUPYNUMERIC_RED_SUM_SQUARES, + CUPYNUMERIC_RED_VARIANCE +}; + +// Match these to BinaryOpCode in config.py +// Also, sort these alphabetically for easy lookup later +enum CuPyNumericBinaryOpCode { + CUPYNUMERIC_BINOP_ADD = 1, + CUPYNUMERIC_BINOP_ARCTAN2, + CUPYNUMERIC_BINOP_BITWISE_AND, + CUPYNUMERIC_BINOP_BITWISE_OR, + CUPYNUMERIC_BINOP_BITWISE_XOR, + CUPYNUMERIC_BINOP_COPYSIGN, + CUPYNUMERIC_BINOP_DIVIDE, + CUPYNUMERIC_BINOP_EQUAL, + CUPYNUMERIC_BINOP_FLOAT_POWER, + CUPYNUMERIC_BINOP_FLOOR_DIVIDE, + CUPYNUMERIC_BINOP_FMOD, + CUPYNUMERIC_BINOP_GCD, + CUPYNUMERIC_BINOP_GREATER, + CUPYNUMERIC_BINOP_GREATER_EQUAL, + CUPYNUMERIC_BINOP_HYPOT, + CUPYNUMERIC_BINOP_ISCLOSE, + CUPYNUMERIC_BINOP_LCM, + CUPYNUMERIC_BINOP_LDEXP, + CUPYNUMERIC_BINOP_LEFT_SHIFT, + CUPYNUMERIC_BINOP_LESS, + CUPYNUMERIC_BINOP_LESS_EQUAL, + CUPYNUMERIC_BINOP_LOGADDEXP, + CUPYNUMERIC_BINOP_LOGADDEXP2, + CUPYNUMERIC_BINOP_LOGICAL_AND, + CUPYNUMERIC_BINOP_LOGICAL_OR, + CUPYNUMERIC_BINOP_LOGICAL_XOR, + CUPYNUMERIC_BINOP_MAXIMUM, + CUPYNUMERIC_BINOP_MINIMUM, + CUPYNUMERIC_BINOP_MOD, + CUPYNUMERIC_BINOP_MULTIPLY, + CUPYNUMERIC_BINOP_NEXTAFTER, + CUPYNUMERIC_BINOP_NOT_EQUAL, + CUPYNUMERIC_BINOP_POWER, + CUPYNUMERIC_BINOP_RIGHT_SHIFT, + CUPYNUMERIC_BINOP_SUBTRACT, +}; + +// Match these to WindowOpCode in config.py +// Also, sort these alphabetically for easy lookup later +enum CuPyNumericWindowOpCode { + CUPYNUMERIC_WINDOW_BARLETT = 1, + CUPYNUMERIC_WINDOW_BLACKMAN, + CUPYNUMERIC_WINDOW_HAMMING, + CUPYNUMERIC_WINDOW_HANNING, + CUPYNUMERIC_WINDOW_KAISER, +}; + +// Match these to CuPyNumericRedopCode in config.py +enum CuPyNumericRedopID { + CUPYNUMERIC_ARGMAX_REDOP = 1, + CUPYNUMERIC_ARGMIN_REDOP = 2, +}; + +enum CuPyNumericBounds { + CUPYNUMERIC_MAX_REDOPS = 1024, + CUPYNUMERIC_MAX_TASKS = 1048576, +}; + +// Match these to ScanCode in config.py +// Also, sort these alphabetically for easy lookup later +enum CuPyNumericScanCode { + CUPYNUMERIC_SCAN_PROD = 1, + CUPYNUMERIC_SCAN_SUM, +}; + +// Match these to ConvertCode in config.py +// Also, sort these alphabetically for easy lookup later +enum CuPyNumericConvertCode { + CUPYNUMERIC_CONVERT_NAN_NOOP = 1, + CUPYNUMERIC_CONVERT_NAN_PROD, + CUPYNUMERIC_CONVERT_NAN_SUM, +}; + +// Match these to BitGeneratorOperation in config.py +enum CuPyNumericBitGeneratorOperation { + CUPYNUMERIC_BITGENOP_CREATE = 1, + CUPYNUMERIC_BITGENOP_DESTROY = 2, + CUPYNUMERIC_BITGENOP_RAND_RAW = 3, + CUPYNUMERIC_BITGENOP_DISTRIBUTION = 4, +}; + +// Match these to BitGeneratorType in config.py +enum CuPyNumericBitGeneratorType { + CUPYNUMERIC_BITGENTYPE_DEFAULT = 0, + CUPYNUMERIC_BITGENTYPE_XORWOW = 1, + CUPYNUMERIC_BITGENTYPE_MRG32K3A = 2, + CUPYNUMERIC_BITGENTYPE_MTGP32 = 3, + CUPYNUMERIC_BITGENTYPE_MT19937 = 4, + CUPYNUMERIC_BITGENTYPE_PHILOX4_32_10 = 5, +}; + +// Match these to BitGeneratorDistribution in config.py +enum CuPyNumericBitGeneratorDistribution { + CUPYNUMERIC_BITGENDIST_INTEGERS_16 = 1, + CUPYNUMERIC_BITGENDIST_INTEGERS_32, + CUPYNUMERIC_BITGENDIST_INTEGERS_64, + CUPYNUMERIC_BITGENDIST_UNIFORM_32, + CUPYNUMERIC_BITGENDIST_UNIFORM_64, + CUPYNUMERIC_BITGENDIST_LOGNORMAL_32, + CUPYNUMERIC_BITGENDIST_LOGNORMAL_64, + CUPYNUMERIC_BITGENDIST_NORMAL_32, + CUPYNUMERIC_BITGENDIST_NORMAL_64, + CUPYNUMERIC_BITGENDIST_POISSON, + CUPYNUMERIC_BITGENDIST_EXPONENTIAL_32, + CUPYNUMERIC_BITGENDIST_EXPONENTIAL_64, + CUPYNUMERIC_BITGENDIST_GUMBEL_32, + CUPYNUMERIC_BITGENDIST_GUMBEL_64, + CUPYNUMERIC_BITGENDIST_LAPLACE_32, + CUPYNUMERIC_BITGENDIST_LAPLACE_64, + CUPYNUMERIC_BITGENDIST_LOGISTIC_32, + CUPYNUMERIC_BITGENDIST_LOGISTIC_64, + CUPYNUMERIC_BITGENDIST_PARETO_32, + CUPYNUMERIC_BITGENDIST_PARETO_64, + CUPYNUMERIC_BITGENDIST_POWER_32, + CUPYNUMERIC_BITGENDIST_POWER_64, + CUPYNUMERIC_BITGENDIST_RAYLEIGH_32, + CUPYNUMERIC_BITGENDIST_RAYLEIGH_64, + CUPYNUMERIC_BITGENDIST_CAUCHY_32, + CUPYNUMERIC_BITGENDIST_CAUCHY_64, + CUPYNUMERIC_BITGENDIST_TRIANGULAR_32, + CUPYNUMERIC_BITGENDIST_TRIANGULAR_64, + CUPYNUMERIC_BITGENDIST_WEIBULL_32, + CUPYNUMERIC_BITGENDIST_WEIBULL_64, + CUPYNUMERIC_BITGENDIST_BYTES, + CUPYNUMERIC_BITGENDIST_BETA_32, + CUPYNUMERIC_BITGENDIST_BETA_64, + CUPYNUMERIC_BITGENDIST_F_32, + CUPYNUMERIC_BITGENDIST_F_64, + CUPYNUMERIC_BITGENDIST_LOGSERIES, + CUPYNUMERIC_BITGENDIST_NONCENTRAL_F_32, + CUPYNUMERIC_BITGENDIST_NONCENTRAL_F_64, + CUPYNUMERIC_BITGENDIST_CHISQUARE_32, + CUPYNUMERIC_BITGENDIST_CHISQUARE_64, + CUPYNUMERIC_BITGENDIST_GAMMA_32, + CUPYNUMERIC_BITGENDIST_GAMMA_64, + CUPYNUMERIC_BITGENDIST_STANDARD_T_32, + CUPYNUMERIC_BITGENDIST_STANDARD_T_64, + CUPYNUMERIC_BITGENDIST_HYPERGEOMETRIC, + CUPYNUMERIC_BITGENDIST_VONMISES_32, + CUPYNUMERIC_BITGENDIST_VONMISES_64, + CUPYNUMERIC_BITGENDIST_ZIPF, + CUPYNUMERIC_BITGENDIST_GEOMETRIC, + CUPYNUMERIC_BITGENDIST_WALD_32, + CUPYNUMERIC_BITGENDIST_WALD_64, + CUPYNUMERIC_BITGENDIST_BINOMIAL, + CUPYNUMERIC_BITGENDIST_NEGATIVE_BINOMIAL, +}; + +// These fft types match CuPyNumericFFTType in config.py and cufftType +enum CuPyNumericFFTType { + CUPYNUMERIC_FFT_R2C = 0x2a, // Real to complex (interleaved) + CUPYNUMERIC_FFT_C2R = 0x2c, // Complex (interleaved) to real + CUPYNUMERIC_FFT_C2C = 0x29, // Complex to complex (interleaved) + CUPYNUMERIC_FFT_D2Z = 0x6a, // Double to double-complex (interleaved) + CUPYNUMERIC_FFT_Z2D = 0x6c, // Double-complex (interleaved) to double + CUPYNUMERIC_FFT_Z2Z = 0x69 // Double-complex to double-complex (interleaved) +}; + +enum CuPyNumericConvolveMethod { + CUPYNUMERIC_CONVOLVE_AUTO, + CUPYNUMERIC_CONVOLVE_DIRECT, + CUPYNUMERIC_CONVOLVE_FFT, +}; + +// These fft types match CuPyNumericFFTDirection in config.py and cufftDirection +enum CuPyNumericFFTDirection { CUPYNUMERIC_FFT_FORWARD = -1, CUPYNUMERIC_FFT_INVERSE = 1 }; + +// Match these to Bitorder in config.py +enum CuPyNumericBitorder { CUPYNUMERIC_BITORDER_BIG = 0, CUPYNUMERIC_BITORDER_LITTLE = 1 }; + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct ReductionOpIds { + int argmax_redop_id; + int argmin_redop_id; +} ReductionOpIds; + +void cupynumeric_perform_registration(); +bool cupynumeric_has_cusolvermp(); + +unsigned cupynumeric_max_eager_volume(); + +unsigned cupynumeric_matmul_cache_size(); + +struct ReductionOpIds cupynumeric_register_reduction_ops(int code); + +#ifdef __cplusplus +} +#endif + +#endif // __CUPYNUMERIC_C_H__ diff --git a/src/cunumeric/cunumeric_task.h b/src/cupynumeric/cupynumeric_task.h similarity index 75% rename from src/cunumeric/cunumeric_task.h rename to src/cupynumeric/cupynumeric_task.h index fe710c93ef..729fbb7686 100644 --- a/src/cunumeric/cunumeric_task.h +++ b/src/cupynumeric/cupynumeric_task.h @@ -17,10 +17,10 @@ #pragma once #include "legate.h" -#include "cunumeric/typedefs.h" -#include "cunumeric/cunumeric_c.h" +#include "cupynumeric/typedefs.h" +#include "cupynumeric/cupynumeric_c.h" -namespace cunumeric { +namespace cupynumeric { enum class VariantKind : int { CPU = 0, @@ -28,13 +28,13 @@ enum class VariantKind : int { GPU = 2, }; -struct CuNumericRegistrar { +struct CuPyNumericRegistrar { static legate::TaskRegistrar& get_registrar(); }; template -struct CuNumericTask : public legate::LegateTask { - using Registrar = CuNumericRegistrar; +struct CuPyNumericTask : public legate::LegateTask { + using Registrar = CuPyNumericRegistrar; }; -} // namespace cunumeric +} // namespace cupynumeric diff --git a/src/cunumeric/device_scalar_reduction_buffer.h b/src/cupynumeric/device_scalar_reduction_buffer.h similarity index 88% rename from src/cunumeric/device_scalar_reduction_buffer.h rename to src/cupynumeric/device_scalar_reduction_buffer.h index dd5f5ec673..edd94a6e61 100644 --- a/src/cunumeric/device_scalar_reduction_buffer.h +++ b/src/cupynumeric/device_scalar_reduction_buffer.h @@ -16,10 +16,10 @@ #pragma once -#include "cunumeric/cuda_help.h" +#include "cupynumeric/cuda_help.h" #include "legate/data/buffer.h" -namespace cunumeric { +namespace cupynumeric { template class DeviceScalarReductionBuffer { @@ -32,7 +32,7 @@ class DeviceScalarReductionBuffer { { VAL identity{REDOP::identity}; ptr_ = buffer_.ptr(0); - CUNUMERIC_CHECK_CUDA( + CUPYNUMERIC_CHECK_CUDA( cudaMemcpyAsync(ptr_, &identity, sizeof(VAL), cudaMemcpyHostToDevice, stream)); } @@ -45,9 +45,9 @@ class DeviceScalarReductionBuffer { __host__ VAL read(cudaStream_t stream) const { VAL result{REDOP::identity}; - CUNUMERIC_CHECK_CUDA( + CUPYNUMERIC_CHECK_CUDA( cudaMemcpyAsync(&result, ptr_, sizeof(VAL), cudaMemcpyDeviceToHost, stream)); - CUNUMERIC_CHECK_CUDA(cudaStreamSynchronize(stream)); + CUPYNUMERIC_CHECK_CUDA(cudaStreamSynchronize(stream)); return result; } @@ -58,4 +58,4 @@ class DeviceScalarReductionBuffer { VAL* ptr_; }; -} // namespace cunumeric +} // namespace cupynumeric diff --git a/src/cunumeric/divmod.h b/src/cupynumeric/divmod.h similarity index 99% rename from src/cunumeric/divmod.h rename to src/cupynumeric/divmod.h index 0794e31750..25f377629f 100644 --- a/src/cunumeric/divmod.h +++ b/src/cupynumeric/divmod.h @@ -32,7 +32,7 @@ #include #include -namespace cunumeric { +namespace cupynumeric { // uint128_t for host and device from CUTLASS @@ -465,4 +465,4 @@ struct FastDivmodU64 { } }; -} // namespace cunumeric +} // namespace cupynumeric diff --git a/src/cunumeric/execution_policy/indexing/parallel_loop.cuh b/src/cupynumeric/execution_policy/indexing/parallel_loop.cuh similarity index 85% rename from src/cunumeric/execution_policy/indexing/parallel_loop.cuh rename to src/cupynumeric/execution_policy/indexing/parallel_loop.cuh index fe35ea4c9f..449e3b2e19 100644 --- a/src/cunumeric/execution_policy/indexing/parallel_loop.cuh +++ b/src/cupynumeric/execution_policy/indexing/parallel_loop.cuh @@ -16,11 +16,11 @@ #pragma once -#include "cunumeric/cunumeric_task.h" -#include "cunumeric/execution_policy/indexing/parallel_loop.h" -#include "cunumeric/cuda_help.h" +#include "cupynumeric/cupynumeric_task.h" +#include "cupynumeric/execution_policy/indexing/parallel_loop.h" +#include "cupynumeric/cuda_help.h" -namespace cunumeric { +namespace cupynumeric { template static __global__ void __launch_bounds__(THREADS_PER_BLOCK, MIN_CTAS_PER_SM) @@ -48,8 +48,8 @@ struct ParallelLoopPolicy { parallel_loop_kernel<<>>( volume, std::forward(kernel), Tag{}); - CUNUMERIC_CHECK_CUDA_STREAM(stream); + CUPYNUMERIC_CHECK_CUDA_STREAM(stream); } }; -} // namespace cunumeric +} // namespace cupynumeric diff --git a/src/cunumeric/execution_policy/indexing/parallel_loop.h b/src/cupynumeric/execution_policy/indexing/parallel_loop.h similarity index 91% rename from src/cunumeric/execution_policy/indexing/parallel_loop.h rename to src/cupynumeric/execution_policy/indexing/parallel_loop.h index 8a88f572b6..17e97a1f73 100644 --- a/src/cunumeric/execution_policy/indexing/parallel_loop.h +++ b/src/cupynumeric/execution_policy/indexing/parallel_loop.h @@ -16,9 +16,9 @@ #pragma once -#include "cunumeric/cunumeric_task.h" +#include "cupynumeric/cupynumeric_task.h" -namespace cunumeric { +namespace cupynumeric { template struct ParallelLoopPolicy {}; @@ -35,4 +35,4 @@ struct ParallelLoopPolicy { } }; -} // namespace cunumeric +} // namespace cupynumeric diff --git a/src/cunumeric/execution_policy/indexing/parallel_loop_omp.h b/src/cupynumeric/execution_policy/indexing/parallel_loop_omp.h similarity index 83% rename from src/cunumeric/execution_policy/indexing/parallel_loop_omp.h rename to src/cupynumeric/execution_policy/indexing/parallel_loop_omp.h index f474930d41..e9e0b6d5e4 100644 --- a/src/cunumeric/execution_policy/indexing/parallel_loop_omp.h +++ b/src/cupynumeric/execution_policy/indexing/parallel_loop_omp.h @@ -16,13 +16,13 @@ #pragma once -#include "cunumeric/cunumeric_task.h" -#include "cunumeric/execution_policy/indexing/parallel_loop.h" -#include "cunumeric/omp_help.h" +#include "cupynumeric/cupynumeric_task.h" +#include "cupynumeric/execution_policy/indexing/parallel_loop.h" +#include "cupynumeric/omp_help.h" #include -namespace cunumeric { +namespace cupynumeric { template struct ParallelLoopPolicy { @@ -37,4 +37,4 @@ struct ParallelLoopPolicy { } }; -} // namespace cunumeric +} // namespace cupynumeric diff --git a/src/cunumeric/execution_policy/reduction/scalar_reduction.cuh b/src/cupynumeric/execution_policy/reduction/scalar_reduction.cuh similarity index 93% rename from src/cunumeric/execution_policy/reduction/scalar_reduction.cuh rename to src/cupynumeric/execution_policy/reduction/scalar_reduction.cuh index ac3b1aef29..004c058a76 100644 --- a/src/cunumeric/execution_policy/reduction/scalar_reduction.cuh +++ b/src/cupynumeric/execution_policy/reduction/scalar_reduction.cuh @@ -16,10 +16,10 @@ #pragma once -#include "cunumeric/execution_policy/reduction/scalar_reduction.h" -#include "cunumeric/cuda_help.h" +#include "cupynumeric/execution_policy/reduction/scalar_reduction.h" +#include "cupynumeric/cuda_help.h" -namespace cunumeric { +namespace cupynumeric { namespace scalar_reduction_impl { template @@ -75,8 +75,8 @@ struct ScalarReductionPolicy { volume, 1, result, std::forward(kernel), identity, Tag{}); } scalar_reduction_impl::copy_kernel<<<1, 1, 0, stream>>>(result, out); - CUNUMERIC_CHECK_CUDA_STREAM(stream); + CUPYNUMERIC_CHECK_CUDA_STREAM(stream); } }; -} // namespace cunumeric +} // namespace cupynumeric diff --git a/src/cunumeric/execution_policy/reduction/scalar_reduction.h b/src/cupynumeric/execution_policy/reduction/scalar_reduction.h similarity index 94% rename from src/cunumeric/execution_policy/reduction/scalar_reduction.h rename to src/cupynumeric/execution_policy/reduction/scalar_reduction.h index 66c7851404..900f59e1a5 100644 --- a/src/cunumeric/execution_policy/reduction/scalar_reduction.h +++ b/src/cupynumeric/execution_policy/reduction/scalar_reduction.h @@ -16,9 +16,9 @@ #pragma once -#include "cunumeric/cunumeric_task.h" +#include "cupynumeric/cupynumeric_task.h" -namespace cunumeric { +namespace cupynumeric { template struct ScalarReductionPolicy { @@ -49,4 +49,4 @@ struct ScalarReductionPolicy { } }; -} // namespace cunumeric +} // namespace cupynumeric diff --git a/src/cunumeric/execution_policy/reduction/scalar_reduction_omp.h b/src/cupynumeric/execution_policy/reduction/scalar_reduction_omp.h similarity index 89% rename from src/cunumeric/execution_policy/reduction/scalar_reduction_omp.h rename to src/cupynumeric/execution_policy/reduction/scalar_reduction_omp.h index 5b9334290d..ee0c26ff17 100644 --- a/src/cunumeric/execution_policy/reduction/scalar_reduction_omp.h +++ b/src/cupynumeric/execution_policy/reduction/scalar_reduction_omp.h @@ -16,12 +16,12 @@ #pragma once -#include "cunumeric/execution_policy/reduction/scalar_reduction.h" -#include "cunumeric/omp_help.h" +#include "cupynumeric/execution_policy/reduction/scalar_reduction.h" +#include "cupynumeric/omp_help.h" #include -namespace cunumeric { +namespace cupynumeric { template struct ScalarReductionPolicy { @@ -47,4 +47,4 @@ struct ScalarReductionPolicy { } }; -} // namespace cunumeric +} // namespace cupynumeric diff --git a/src/cunumeric/fft/fft.cu b/src/cupynumeric/fft/fft.cu similarity index 86% rename from src/cunumeric/fft/fft.cu rename to src/cupynumeric/fft/fft.cu index fd0052aa2e..d7bbbfffd1 100644 --- a/src/cunumeric/fft/fft.cu +++ b/src/cupynumeric/fft/fft.cu @@ -15,13 +15,13 @@ */ #include -#include "cunumeric/fft/fft.h" -#include "cunumeric/fft/fft_template.inl" +#include "cupynumeric/fft/fft.h" +#include "cupynumeric/fft/fft_template.inl" -#include "cunumeric/cuda_help.h" -#include "cunumeric/pitches.h" +#include "cupynumeric/cuda_help.h" +#include "cupynumeric/pitches.h" -namespace cunumeric { +namespace cupynumeric { using namespace legate; using dim_t = long long int32_t; @@ -46,7 +46,7 @@ __host__ static inline void copy_into_buffer(TYPE* target, cudaStream_t stream) { if (acc.accessor.is_dense_row_major(rect)) { - CUNUMERIC_CHECK_CUDA(cudaMemcpyAsync( + CUPYNUMERIC_CHECK_CUDA(cudaMemcpyAsync( target, acc.ptr(rect.lo), volume * sizeof(TYPE), cudaMemcpyDeviceToDevice, stream)); } else { Pitches pitches{}; @@ -56,7 +56,7 @@ __host__ static inline void copy_into_buffer(TYPE* target, copy_kernel<<>>( volume, target, acc, pitches, rect.lo); - CUNUMERIC_CHECK_CUDA_STREAM(stream); + CUPYNUMERIC_CHECK_CUDA_STREAM(stream); } } @@ -68,8 +68,8 @@ __host__ static inline void cufft_operation(AccessorWO out, const Rect& out_rect, const Rect& in_rect, std::vector& axes, - CuNumericFFTType type, - CuNumericFFTDirection direction) + CuPyNumericFFTType type, + CuPyNumericFFTDirection direction) { auto stream = get_cached_stream(); @@ -84,8 +84,8 @@ __host__ static inline void cufft_operation(AccessorWO out, Point fft_size_out = out_rect.hi - out_rect.lo + one; num_elements = 1; for (int32_t i = 0; i < DIM; ++i) { - n[i] = - (type == CUNUMERIC_FFT_R2C || type == CUNUMERIC_FFT_D2Z) ? fft_size_in[i] : fft_size_out[i]; + n[i] = (type == CUPYNUMERIC_FFT_R2C || type == CUPYNUMERIC_FFT_D2Z) ? fft_size_in[i] + : fft_size_out[i]; inembed[i] = fft_size_in[i]; onembed[i] = fft_size_out[i]; num_elements *= n[i]; @@ -115,7 +115,7 @@ __host__ static inline void cufft_operation(AccessorWO out, static_cast(out.ptr(out_rect.lo)), static_cast(direction))); // synchronize before cufft_context runs out of scope - CUNUMERIC_CHECK_CUDA(cudaStreamSynchronize(stream)); + CUPYNUMERIC_CHECK_CUDA(cudaStreamSynchronize(stream)); } // Perform the FFT operation as multiple 1D FFTs along the specified axes (Complex-to-complex case). @@ -124,8 +124,8 @@ __host__ static inline void cufft_over_axes_c2c(INOUT_TYPE* out, const INOUT_TYPE* in, const Rect& inout_rect, std::vector& axes, - CuNumericFFTType type, - CuNumericFFTDirection direction) + CuPyNumericFFTType type, + CuPyNumericFFTDirection direction) { auto stream = get_cached_stream(); @@ -144,7 +144,7 @@ __host__ static inline void cufft_over_axes_c2c(INOUT_TYPE* out, // Copy input to output buffer (if needed) // the computation will be done inplace of the target if (in != out) { - CUNUMERIC_CHECK_CUDA(cudaMemcpyAsync( + CUPYNUMERIC_CHECK_CUDA(cudaMemcpyAsync( out, in, num_elements * sizeof(INOUT_TYPE), cudaMemcpyDeviceToDevice, stream)); } @@ -193,7 +193,7 @@ __host__ static inline void cufft_over_axes_c2c(INOUT_TYPE* out, static_cast(direction))); } // synchronize before cufft_context runs out of scope - CUNUMERIC_CHECK_CUDA(cudaStreamSynchronize(stream)); + CUPYNUMERIC_CHECK_CUDA(cudaStreamSynchronize(stream)); } } @@ -204,8 +204,8 @@ __host__ static inline void cufft_r2c_c2r(OUTPUT_TYPE* out, const Rect& out_rect, const Rect& in_rect, const int64_t axis, - CuNumericFFTType type, - CuNumericFFTDirection direction) + CuPyNumericFFTType type, + CuPyNumericFFTDirection direction) { auto stream = get_cached_stream(); @@ -220,7 +220,7 @@ __host__ static inline void cufft_r2c_c2r(OUTPUT_TYPE* out, size_t num_elements_in = 1; size_t num_elements_out = 1; for (int32_t i = 0; i < DIM; ++i) { - n[i] = (direction == CUNUMERIC_FFT_FORWARD) ? fft_size_in[i] : fft_size_out[i]; + n[i] = (direction == CUPYNUMERIC_FFT_FORWARD) ? fft_size_in[i] : fft_size_out[i]; inembed[i] = fft_size_in[i]; onembed[i] = fft_size_out[i]; num_elements_in *= fft_size_in[i]; @@ -237,7 +237,7 @@ __host__ static inline void cufft_r2c_c2r(OUTPUT_TYPE* out, num_slices *= n[i]; } } - dim_t batches = ((direction == CUNUMERIC_FFT_FORWARD) ? num_elements_in : num_elements_out) / + dim_t batches = ((direction == CUPYNUMERIC_FFT_FORWARD) ? num_elements_in : num_elements_out) / (num_slices * size_1d); int64_t offset_in = num_elements_in / num_slices; int64_t offset_out = num_elements_out / num_slices; @@ -269,7 +269,7 @@ __host__ static inline void cufft_r2c_c2r(OUTPUT_TYPE* out, static_cast(direction))); } // synchronize before cufft_context runs out of scope - CUNUMERIC_CHECK_CUDA(cudaStreamSynchronize(stream)); + CUPYNUMERIC_CHECK_CUDA(cudaStreamSynchronize(stream)); } // Perform the FFT operation as multiple 1D FFTs along the specified axes. @@ -282,16 +282,16 @@ __host__ static inline void cufft_over_axes(AccessorWO out, const Rect& out_rect, const Rect& in_rect, std::vector& axes, - CuNumericFFTType type, - CuNumericFFTDirection direction) + CuPyNumericFFTType type, + CuPyNumericFFTDirection direction) { - bool is_c2c = (type == CUNUMERIC_FFT_Z2Z || type == CUNUMERIC_FFT_C2C); - bool is_r2c = !is_c2c && (type == CUNUMERIC_FFT_D2Z || type == CUNUMERIC_FFT_R2C); + bool is_c2c = (type == CUPYNUMERIC_FFT_Z2Z || type == CUPYNUMERIC_FFT_C2C); + bool is_r2c = !is_c2c && (type == CUPYNUMERIC_FFT_D2Z || type == CUPYNUMERIC_FFT_R2C); bool is_c2r = !is_c2c && !is_r2c; bool is_double_precision = - (type == CUNUMERIC_FFT_Z2Z || type == CUNUMERIC_FFT_D2Z || type == CUNUMERIC_FFT_Z2D); - auto c2c_subtype = is_double_precision ? CUNUMERIC_FFT_Z2Z : CUNUMERIC_FFT_C2C; + (type == CUPYNUMERIC_FFT_Z2Z || type == CUPYNUMERIC_FFT_D2Z || type == CUPYNUMERIC_FFT_Z2D); + auto c2c_subtype = is_double_precision ? CUPYNUMERIC_FFT_Z2Z : CUPYNUMERIC_FFT_C2C; // C2C, R2C, C2R all modify input buffer --> create a copy OUTPUT_TYPE* out_ptr = out.ptr(out_rect.lo); @@ -340,7 +340,7 @@ __host__ static inline void cufft_over_axes(AccessorWO out, } } -template +template struct FFTImplBody { using INPUT_TYPE = type_of; using OUTPUT_TYPE = type_of; @@ -350,7 +350,7 @@ struct FFTImplBody { const Rect& out_rect, const Rect& in_rect, std::vector& axes, - CuNumericFFTDirection direction, + CuPyNumericFFTDirection direction, bool operate_over_axes) const { assert(out.accessor.is_dense_row_major(out_rect)); @@ -380,4 +380,4 @@ namespace // unnamed static void __attribute__((constructor)) register_tasks(void) { FFTTask::register_variants(); } } // namespace -} // namespace cunumeric +} // namespace cupynumeric diff --git a/src/cunumeric/fft/fft.h b/src/cupynumeric/fft/fft.h similarity index 73% rename from src/cunumeric/fft/fft.h rename to src/cupynumeric/fft/fft.h index 5e8e358cc1..f4ed02a151 100644 --- a/src/cunumeric/fft/fft.h +++ b/src/cupynumeric/fft/fft.h @@ -16,23 +16,23 @@ #pragma once -#include "cunumeric/cunumeric_task.h" -#include "cunumeric/fft/fft_util.h" +#include "cupynumeric/cupynumeric_task.h" +#include "cupynumeric/fft/fft_util.h" -namespace cunumeric { +namespace cupynumeric { struct FFTArgs { legate::PhysicalStore output{nullptr}; legate::PhysicalStore input{nullptr}; - CuNumericFFTType type; - CuNumericFFTDirection direction; + CuPyNumericFFTType type; + CuPyNumericFFTDirection direction; bool operate_over_axes; std::vector axes; }; -class FFTTask : public CuNumericTask { +class FFTTask : public CuPyNumericTask { public: - static constexpr auto TASK_ID = legate::LocalTaskID{CUNUMERIC_FFT}; + static constexpr auto TASK_ID = legate::LocalTaskID{CUPYNUMERIC_FFT}; public: #if LEGATE_DEFINED(LEGATE_USE_CUDA) @@ -40,4 +40,4 @@ class FFTTask : public CuNumericTask { #endif }; -} // namespace cunumeric +} // namespace cupynumeric diff --git a/src/cunumeric/fft/fft_template.inl b/src/cupynumeric/fft/fft_template.inl similarity index 85% rename from src/cunumeric/fft/fft_template.inl rename to src/cupynumeric/fft/fft_template.inl index 647241e738..3de312b218 100644 --- a/src/cunumeric/fft/fft_template.inl +++ b/src/cupynumeric/fft/fft_template.inl @@ -17,22 +17,22 @@ #pragma once // Useful for IDEs -#include "cunumeric/fft/fft.h" -#include "cunumeric/pitches.h" -#include "cunumeric/fft/fft_util.h" +#include "cupynumeric/fft/fft.h" +#include "cupynumeric/pitches.h" +#include "cupynumeric/fft/fft_util.h" -namespace cunumeric { +namespace cupynumeric { using namespace legate; template struct FFTImplBody; -template +template struct FFTImpl { template struct FFTDispatch { - template + template void operator()(FFTArgs& args) const { // Not expecting changing dimensions, at least for now @@ -85,8 +85,8 @@ static void fft_template(TaskContext& context) args.output = context.output(0); args.input = context.input(0); // Scalar arguments. Pay attention to indexes / ranges when adding or reordering arguments - args.type = context.scalar(0).value(); - args.direction = context.scalar(1).value(); + args.type = context.scalar(0).value(); + args.direction = context.scalar(1).value(); args.operate_over_axes = context.scalar(2).value(); const auto num_scalars = context.num_scalars(); @@ -96,4 +96,4 @@ static void fft_template(TaskContext& context) fft_dispatch(args.type, FFTDispatch{}, args); } -} // namespace cunumeric +} // namespace cupynumeric diff --git a/src/cunumeric/fft/fft_util.h b/src/cupynumeric/fft/fft_util.h similarity index 53% rename from src/cunumeric/fft/fft_util.h rename to src/cupynumeric/fft/fft_util.h index 7f76501928..b5b0afc2ab 100644 --- a/src/cunumeric/fft/fft_util.h +++ b/src/cupynumeric/fft/fft_util.h @@ -16,73 +16,73 @@ #pragma once -#include "cunumeric/cunumeric_task.h" +#include "cupynumeric/cupynumeric_task.h" -namespace cunumeric { +namespace cupynumeric { using namespace legate; template -constexpr decltype(auto) fft_dispatch(CuNumericFFTType type, Functor f, Fnargs&&... args) +constexpr decltype(auto) fft_dispatch(CuPyNumericFFTType type, Functor f, Fnargs&&... args) { switch (type) { - case CUNUMERIC_FFT_R2C: - return f.template operator()(std::forward(args)...); - case CUNUMERIC_FFT_C2R: - return f.template operator()(std::forward(args)...); - case CUNUMERIC_FFT_C2C: - return f.template operator()(std::forward(args)...); - case CUNUMERIC_FFT_D2Z: - return f.template operator()(std::forward(args)...); - case CUNUMERIC_FFT_Z2D: - return f.template operator()(std::forward(args)...); - case CUNUMERIC_FFT_Z2Z: - return f.template operator()(std::forward(args)...); + case CUPYNUMERIC_FFT_R2C: + return f.template operator()(std::forward(args)...); + case CUPYNUMERIC_FFT_C2R: + return f.template operator()(std::forward(args)...); + case CUPYNUMERIC_FFT_C2C: + return f.template operator()(std::forward(args)...); + case CUPYNUMERIC_FFT_D2Z: + return f.template operator()(std::forward(args)...); + case CUPYNUMERIC_FFT_Z2D: + return f.template operator()(std::forward(args)...); + case CUPYNUMERIC_FFT_Z2Z: + return f.template operator()(std::forward(args)...); default: break; } assert(false); - return f.template operator()(std::forward(args)...); + return f.template operator()(std::forward(args)...); } -template +template struct FFT { static constexpr bool valid = false; }; template <> -struct FFT { +struct FFT { static constexpr bool valid = true; static constexpr Type::Code CODE_OUT = Type::Code::COMPLEX64; }; template <> -struct FFT { +struct FFT { static constexpr bool valid = true; static constexpr Type::Code CODE_OUT = Type::Code::FLOAT32; }; template <> -struct FFT { +struct FFT { static constexpr bool valid = true; static constexpr Type::Code CODE_OUT = Type::Code::COMPLEX64; }; template <> -struct FFT { +struct FFT { static constexpr bool valid = true; static constexpr Type::Code CODE_OUT = Type::Code::COMPLEX128; }; template <> -struct FFT { +struct FFT { static constexpr bool valid = true; static constexpr Type::Code CODE_OUT = Type::Code::FLOAT64; }; template <> -struct FFT { +struct FFT { static constexpr bool valid = true; static constexpr Type::Code CODE_OUT = Type::Code::COMPLEX128; }; -} // namespace cunumeric +} // namespace cupynumeric diff --git a/src/cunumeric/index/advanced_indexing.cc b/src/cupynumeric/index/advanced_indexing.cc similarity index 95% rename from src/cunumeric/index/advanced_indexing.cc rename to src/cupynumeric/index/advanced_indexing.cc index 498901a2c4..9fe3ceb0bb 100644 --- a/src/cunumeric/index/advanced_indexing.cc +++ b/src/cupynumeric/index/advanced_indexing.cc @@ -14,10 +14,10 @@ * */ -#include "cunumeric/index/advanced_indexing.h" -#include "cunumeric/index/advanced_indexing_template.inl" +#include "cupynumeric/index/advanced_indexing.h" +#include "cupynumeric/index/advanced_indexing_template.inl" -namespace cunumeric { +namespace cupynumeric { using namespace legate; @@ -117,4 +117,4 @@ static void __attribute__((constructor)) register_tasks(void) } } // namespace -} // namespace cunumeric +} // namespace cupynumeric diff --git a/src/cunumeric/index/advanced_indexing.cu b/src/cupynumeric/index/advanced_indexing.cu similarity index 94% rename from src/cunumeric/index/advanced_indexing.cu rename to src/cupynumeric/index/advanced_indexing.cu index 23c7373b11..925e0081a8 100644 --- a/src/cunumeric/index/advanced_indexing.cu +++ b/src/cupynumeric/index/advanced_indexing.cu @@ -14,14 +14,14 @@ * */ -#include "cunumeric/index/advanced_indexing.h" -#include "cunumeric/index/advanced_indexing_template.inl" -#include "cunumeric/utilities/thrust_util.h" -#include "cunumeric/cuda_help.h" +#include "cupynumeric/index/advanced_indexing.h" +#include "cupynumeric/index/advanced_indexing_template.inl" +#include "cupynumeric/utilities/thrust_util.h" +#include "cupynumeric/cuda_help.h" #include -namespace cunumeric { +namespace cupynumeric { template static __global__ void __launch_bounds__(THREADS_PER_BLOCK, MIN_CTAS_PER_SM) @@ -109,7 +109,7 @@ struct AdvancedIndexingImplBody { volume, size, offsets, in, pitches, rect.lo, 1, skip_size, key_dim); } - CUNUMERIC_CHECK_CUDA_STREAM(stream); + CUPYNUMERIC_CHECK_CUDA_STREAM(stream); auto off_ptr = offsets.ptr(0); thrust::exclusive_scan(DEFAULT_POLICY.on(stream), off_ptr, off_ptr + volume, off_ptr); @@ -158,7 +158,7 @@ struct AdvancedIndexingImplBody { advanced_indexing_kernel<<>>( volume, input, index, out, pitches, rect.lo, offsets, skip_size, key_dim); } - CUNUMERIC_CHECK_CUDA_STREAM(stream); + CUPYNUMERIC_CHECK_CUDA_STREAM(stream); } }; @@ -166,4 +166,4 @@ struct AdvancedIndexingImplBody { { advanced_indexing_template(context); } -} // namespace cunumeric +} // namespace cupynumeric diff --git a/src/cunumeric/index/advanced_indexing.h b/src/cupynumeric/index/advanced_indexing.h similarity index 83% rename from src/cunumeric/index/advanced_indexing.h rename to src/cupynumeric/index/advanced_indexing.h index 6ed94abcc1..c4342cb599 100644 --- a/src/cunumeric/index/advanced_indexing.h +++ b/src/cupynumeric/index/advanced_indexing.h @@ -16,9 +16,9 @@ #pragma once -#include "cunumeric/cunumeric_task.h" +#include "cupynumeric/cupynumeric_task.h" -namespace cunumeric { +namespace cupynumeric { struct AdvancedIndexingArgs { legate::PhysicalStore output; @@ -28,9 +28,9 @@ struct AdvancedIndexingArgs { const int64_t key_dim; }; -class AdvancedIndexingTask : public CuNumericTask { +class AdvancedIndexingTask : public CuPyNumericTask { public: - static constexpr auto TASK_ID = legate::LocalTaskID{CUNUMERIC_ADVANCED_INDEXING}; + static constexpr auto TASK_ID = legate::LocalTaskID{CUPYNUMERIC_ADVANCED_INDEXING}; public: static void cpu_variant(legate::TaskContext context); @@ -54,4 +54,4 @@ constexpr void fill_out(T& out, legate::Point& p, const T& in) out = in; } -} // namespace cunumeric +} // namespace cupynumeric diff --git a/src/cunumeric/index/advanced_indexing_omp.cc b/src/cupynumeric/index/advanced_indexing_omp.cc similarity index 95% rename from src/cunumeric/index/advanced_indexing_omp.cc rename to src/cupynumeric/index/advanced_indexing_omp.cc index a0bf6c80ee..fae843dd99 100644 --- a/src/cunumeric/index/advanced_indexing_omp.cc +++ b/src/cupynumeric/index/advanced_indexing_omp.cc @@ -14,15 +14,15 @@ * */ -#include "cunumeric/index/advanced_indexing.h" -#include "cunumeric/index/advanced_indexing_template.inl" -#include "cunumeric/omp_help.h" +#include "cupynumeric/index/advanced_indexing.h" +#include "cupynumeric/index/advanced_indexing_template.inl" +#include "cupynumeric/omp_help.h" #include #include #include #include -namespace cunumeric { +namespace cupynumeric { using namespace legate; @@ -127,4 +127,4 @@ struct AdvancedIndexingImplBody { advanced_indexing_template(context); } -} // namespace cunumeric +} // namespace cupynumeric diff --git a/src/cunumeric/index/advanced_indexing_template.inl b/src/cupynumeric/index/advanced_indexing_template.inl similarity index 94% rename from src/cunumeric/index/advanced_indexing_template.inl rename to src/cupynumeric/index/advanced_indexing_template.inl index 8370973738..476f536f1a 100644 --- a/src/cunumeric/index/advanced_indexing_template.inl +++ b/src/cupynumeric/index/advanced_indexing_template.inl @@ -17,10 +17,10 @@ #pragma once // Useful for IDEs -#include "cunumeric/index/advanced_indexing.h" -#include "cunumeric/pitches.h" +#include "cupynumeric/index/advanced_indexing.h" +#include "cupynumeric/pitches.h" -namespace cunumeric { +namespace cupynumeric { using namespace legate; @@ -43,7 +43,7 @@ struct AdvancedIndexingImpl { auto index_rect = args.indexing_array.shape(); // this task is executed only for the case when index array is a bool type auto index_arr = args.indexing_array.read_accessor(index_rect); -#ifdef DEBUG_CUNUMERIC +#ifdef DEBUG_CUPYNUMERIC // we make sure that index and input shapes are the same on the python side. // checking this one more time here assert(index_rect == input_rect); @@ -75,4 +75,4 @@ static void advanced_indexing_template(TaskContext& context) args.input_array.dim(), args.input_array.code(), AdvancedIndexingImpl{}, args); } -} // namespace cunumeric +} // namespace cupynumeric diff --git a/src/cunumeric/index/choose.cc b/src/cupynumeric/index/choose.cc similarity index 91% rename from src/cunumeric/index/choose.cc rename to src/cupynumeric/index/choose.cc index 33b0832ec9..02b395f7bc 100644 --- a/src/cunumeric/index/choose.cc +++ b/src/cupynumeric/index/choose.cc @@ -14,10 +14,10 @@ * */ -#include "cunumeric/index/choose.h" -#include "cunumeric/index/choose_template.inl" +#include "cupynumeric/index/choose.h" +#include "cupynumeric/index/choose_template.inl" -namespace cunumeric { +namespace cupynumeric { using namespace legate; @@ -37,7 +37,7 @@ struct ChooseImplBody { auto outptr = out.ptr(rect); auto indexptr = index_arr.ptr(rect); for (size_t idx = 0; idx < volume; ++idx) { -#ifdef DEBUG_CUNUMERIC +#ifdef DEBUG_CUPYNUMERIC assert(indexptr[idx] < static_cast(choices.size())); #endif auto chptr = choices[indexptr[idx]].ptr(rect); @@ -46,7 +46,7 @@ struct ChooseImplBody { } else { for (size_t idx = 0; idx < volume; ++idx) { auto p = pitches.unflatten(idx, rect.lo); -#ifdef DEBUG_CUNUMERIC +#ifdef DEBUG_CUPYNUMERIC assert(index_arr[p] < static_cast(choices.size())); #endif out[p] = choices[index_arr[p]][p]; @@ -65,4 +65,4 @@ namespace // unnamed static void __attribute__((constructor)) register_tasks(void) { ChooseTask::register_variants(); } } // namespace -} // namespace cunumeric +} // namespace cupynumeric diff --git a/src/cunumeric/index/choose.cu b/src/cupynumeric/index/choose.cu similarity index 93% rename from src/cunumeric/index/choose.cu rename to src/cupynumeric/index/choose.cu index e9d8c8f9e6..f204b7e5c2 100644 --- a/src/cunumeric/index/choose.cu +++ b/src/cupynumeric/index/choose.cu @@ -14,11 +14,11 @@ * */ -#include "cunumeric/index/choose.h" -#include "cunumeric/index/choose_template.inl" -#include "cunumeric/cuda_help.h" +#include "cupynumeric/index/choose.h" +#include "cupynumeric/index/choose_template.inl" +#include "cupynumeric/cuda_help.h" -namespace cunumeric { +namespace cupynumeric { template __global__ static void __launch_bounds__(THREADS_PER_BLOCK, MIN_CTAS_PER_SM) @@ -82,7 +82,7 @@ struct ChooseImplBody { choose_kernel <<>>(out, index_arr, ch_arr, rect, pitches, volume); } - CUNUMERIC_CHECK_CUDA_STREAM(stream); + CUPYNUMERIC_CHECK_CUDA_STREAM(stream); } }; @@ -90,4 +90,4 @@ struct ChooseImplBody { { choose_template(context); } -} // namespace cunumeric +} // namespace cupynumeric diff --git a/src/cunumeric/index/choose.h b/src/cupynumeric/index/choose.h similarity index 81% rename from src/cunumeric/index/choose.h rename to src/cupynumeric/index/choose.h index 70b11238d8..4a4959c749 100644 --- a/src/cunumeric/index/choose.h +++ b/src/cupynumeric/index/choose.h @@ -16,18 +16,18 @@ #pragma once -#include "cunumeric/cunumeric_task.h" +#include "cupynumeric/cupynumeric_task.h" -namespace cunumeric { +namespace cupynumeric { struct ChooseArgs { legate::PhysicalStore out; std::vector inputs; }; -class ChooseTask : public CuNumericTask { +class ChooseTask : public CuPyNumericTask { public: - static constexpr auto TASK_ID = legate::LocalTaskID{CUNUMERIC_CHOOSE}; + static constexpr auto TASK_ID = legate::LocalTaskID{CUPYNUMERIC_CHOOSE}; public: static void cpu_variant(legate::TaskContext context); @@ -39,4 +39,4 @@ class ChooseTask : public CuNumericTask { #endif }; -} // namespace cunumeric +} // namespace cupynumeric diff --git a/src/cunumeric/index/choose_omp.cc b/src/cupynumeric/index/choose_omp.cc similarity index 91% rename from src/cunumeric/index/choose_omp.cc rename to src/cupynumeric/index/choose_omp.cc index 8c5441d19a..0969fe305e 100644 --- a/src/cunumeric/index/choose_omp.cc +++ b/src/cupynumeric/index/choose_omp.cc @@ -14,10 +14,10 @@ * */ -#include "cunumeric/index/choose.h" -#include "cunumeric/index/choose_template.inl" +#include "cupynumeric/index/choose.h" +#include "cupynumeric/index/choose_template.inl" -namespace cunumeric { +namespace cupynumeric { using namespace legate; @@ -38,7 +38,7 @@ struct ChooseImplBody { auto indexptr = index_arr.ptr(rect); #pragma omp parallel for schedule(static) for (size_t idx = 0; idx < volume; ++idx) { -#ifdef DEBUG_CUNUMERIC +#ifdef DEBUG_CUPYNUMERIC assert(indexptr[idx] < static_cast(choices.size())); #endif auto chptr = choices[indexptr[idx]].ptr(rect); @@ -59,4 +59,4 @@ struct ChooseImplBody { choose_template(context); } -} // namespace cunumeric +} // namespace cupynumeric diff --git a/src/cunumeric/index/choose_template.inl b/src/cupynumeric/index/choose_template.inl similarity index 94% rename from src/cunumeric/index/choose_template.inl rename to src/cupynumeric/index/choose_template.inl index d93930ccd3..ee40e770c7 100644 --- a/src/cunumeric/index/choose_template.inl +++ b/src/cupynumeric/index/choose_template.inl @@ -17,10 +17,10 @@ #pragma once // Useful for IDEs -#include "cunumeric/index/choose.h" -#include "cunumeric/pitches.h" +#include "cupynumeric/index/choose.h" +#include "cupynumeric/pitches.h" -namespace cunumeric { +namespace cupynumeric { using namespace legate; @@ -74,4 +74,4 @@ static void choose_template(TaskContext& context) double_dispatch(args.inputs[0].dim(), args.inputs[0].code(), ChooseImpl{}, args); } -} // namespace cunumeric +} // namespace cupynumeric diff --git a/src/cunumeric/index/putmask.cc b/src/cupynumeric/index/putmask.cc similarity index 85% rename from src/cunumeric/index/putmask.cc rename to src/cupynumeric/index/putmask.cc index a8d50ef1df..b5a267c09f 100644 --- a/src/cunumeric/index/putmask.cc +++ b/src/cupynumeric/index/putmask.cc @@ -14,10 +14,10 @@ * */ -#include "cunumeric/index/putmask.h" -#include "cunumeric/index/putmask_template.inl" +#include "cupynumeric/index/putmask.h" +#include "cupynumeric/index/putmask_template.inl" -namespace cunumeric { +namespace cupynumeric { /*static*/ void PutmaskTask::cpu_variant(TaskContext context) { @@ -29,4 +29,4 @@ namespace // unnamed static void __attribute__((constructor)) register_tasks(void) { PutmaskTask::register_variants(); } } // namespace -} // namespace cunumeric +} // namespace cupynumeric diff --git a/src/cunumeric/index/putmask.cu b/src/cupynumeric/index/putmask.cu similarity index 77% rename from src/cunumeric/index/putmask.cu rename to src/cupynumeric/index/putmask.cu index 588ed1834b..90308278ad 100644 --- a/src/cunumeric/index/putmask.cu +++ b/src/cupynumeric/index/putmask.cu @@ -14,15 +14,15 @@ * */ -#include "cunumeric/execution_policy/indexing/parallel_loop.cuh" -#include "cunumeric/index/putmask.h" -#include "cunumeric/index/putmask_template.inl" +#include "cupynumeric/execution_policy/indexing/parallel_loop.cuh" +#include "cupynumeric/index/putmask.h" +#include "cupynumeric/index/putmask_template.inl" -namespace cunumeric { +namespace cupynumeric { /*static*/ void PutmaskTask::gpu_variant(TaskContext context) { putmask_template(context); } -} // namespace cunumeric +} // namespace cupynumeric diff --git a/src/cunumeric/index/putmask.h b/src/cupynumeric/index/putmask.h similarity index 81% rename from src/cunumeric/index/putmask.h rename to src/cupynumeric/index/putmask.h index 0b9289487b..0a0fc17e9a 100644 --- a/src/cunumeric/index/putmask.h +++ b/src/cupynumeric/index/putmask.h @@ -16,9 +16,9 @@ #pragma once -#include "cunumeric/cunumeric_task.h" +#include "cupynumeric/cupynumeric_task.h" -namespace cunumeric { +namespace cupynumeric { struct PutmaskArgs { legate::PhysicalStore input; @@ -26,9 +26,9 @@ struct PutmaskArgs { legate::PhysicalStore values; }; -class PutmaskTask : public CuNumericTask { +class PutmaskTask : public CuPyNumericTask { public: - static constexpr auto TASK_ID = legate::LocalTaskID{CUNUMERIC_PUTMASK}; + static constexpr auto TASK_ID = legate::LocalTaskID{CUPYNUMERIC_PUTMASK}; public: static void cpu_variant(legate::TaskContext context); @@ -40,4 +40,4 @@ class PutmaskTask : public CuNumericTask { #endif }; -} // namespace cunumeric +} // namespace cupynumeric diff --git a/src/cunumeric/index/putmask_omp.cc b/src/cupynumeric/index/putmask_omp.cc similarity index 77% rename from src/cunumeric/index/putmask_omp.cc rename to src/cupynumeric/index/putmask_omp.cc index 6ffa91ee9c..30256281ab 100644 --- a/src/cunumeric/index/putmask_omp.cc +++ b/src/cupynumeric/index/putmask_omp.cc @@ -14,15 +14,15 @@ * */ -#include "cunumeric/execution_policy/indexing/parallel_loop_omp.h" -#include "cunumeric/index/putmask.h" -#include "cunumeric/index/putmask_template.inl" +#include "cupynumeric/execution_policy/indexing/parallel_loop_omp.h" +#include "cupynumeric/index/putmask.h" +#include "cupynumeric/index/putmask_template.inl" -namespace cunumeric { +namespace cupynumeric { /*static*/ void PutmaskTask::omp_variant(TaskContext context) { putmask_template(context); } -} // namespace cunumeric +} // namespace cupynumeric diff --git a/src/cunumeric/index/putmask_template.inl b/src/cupynumeric/index/putmask_template.inl similarity index 93% rename from src/cunumeric/index/putmask_template.inl rename to src/cupynumeric/index/putmask_template.inl index 0b438cec1e..ec5c6611a5 100644 --- a/src/cunumeric/index/putmask_template.inl +++ b/src/cupynumeric/index/putmask_template.inl @@ -18,11 +18,11 @@ // Useful for IDEs #include -#include "cunumeric/index/putmask.h" -#include "cunumeric/pitches.h" -#include "cunumeric/execution_policy/indexing/parallel_loop.h" +#include "cupynumeric/index/putmask.h" +#include "cupynumeric/pitches.h" +#include "cupynumeric/execution_policy/indexing/parallel_loop.h" -namespace cunumeric { +namespace cupynumeric { using namespace legate; @@ -117,4 +117,4 @@ static void putmask_template(TaskContext& context) double_dispatch(dim, args.input.code(), PutmaskImpl{}, args); } -} // namespace cunumeric +} // namespace cupynumeric diff --git a/src/cunumeric/index/repeat.cc b/src/cupynumeric/index/repeat.cc similarity index 96% rename from src/cunumeric/index/repeat.cc rename to src/cupynumeric/index/repeat.cc index b23610ff0d..b763919d98 100644 --- a/src/cunumeric/index/repeat.cc +++ b/src/cupynumeric/index/repeat.cc @@ -14,10 +14,10 @@ * */ -#include "cunumeric/index/repeat.h" -#include "cunumeric/index/repeat_template.inl" +#include "cupynumeric/index/repeat.h" +#include "cupynumeric/index/repeat_template.inl" -namespace cunumeric { +namespace cupynumeric { using namespace legate; @@ -122,4 +122,4 @@ namespace // unnamed static void __attribute__((constructor)) register_tasks(void) { RepeatTask::register_variants(); } } // namespace -} // namespace cunumeric +} // namespace cupynumeric diff --git a/src/cunumeric/index/repeat.cu b/src/cupynumeric/index/repeat.cu similarity index 94% rename from src/cunumeric/index/repeat.cu rename to src/cupynumeric/index/repeat.cu index 67184e6a92..f59a3906d7 100644 --- a/src/cunumeric/index/repeat.cu +++ b/src/cupynumeric/index/repeat.cu @@ -14,14 +14,14 @@ * */ -#include "cunumeric/index/repeat.h" -#include "cunumeric/index/repeat_template.inl" -#include "cunumeric/utilities/thrust_util.h" -#include "cunumeric/cuda_help.h" +#include "cupynumeric/index/repeat.h" +#include "cupynumeric/index/repeat_template.inl" +#include "cupynumeric/utilities/thrust_util.h" +#include "cupynumeric/cuda_help.h" #include -namespace cunumeric { +namespace cupynumeric { template static __global__ void __launch_bounds__(THREADS_PER_BLOCK, MIN_CTAS_PER_SM) @@ -116,7 +116,7 @@ struct RepeatImplBody { auto stream = get_cached_stream(); repeat_kernel<<>>( out, in, repeats, axis, out_rect.lo, pitches, out_volume); - CUNUMERIC_CHECK_CUDA_STREAM(stream); + CUPYNUMERIC_CHECK_CUDA_STREAM(stream); } void operator()(legate::PhysicalStore& out_array, @@ -146,7 +146,7 @@ struct RepeatImplBody { count_repeat_kernel<<>>( extent, sum, repeats, in_rect.lo, axis, 1, offsets); } - CUNUMERIC_CHECK_CUDA_STREAM(stream); + CUPYNUMERIC_CHECK_CUDA_STREAM(stream); Point out_extents = in_rect.hi - in_rect.lo + Point::ONES(); out_extents[axis] = static_cast(sum.read(stream)); @@ -159,7 +159,7 @@ struct RepeatImplBody { const size_t blocks = (volume + THREADS_PER_BLOCK - 1) / THREADS_PER_BLOCK; repeat_kernel<<>>( out, in, repeats, offsets, axis, in_rect.lo, pitches, volume); - CUNUMERIC_CHECK_CUDA_STREAM(stream); + CUPYNUMERIC_CHECK_CUDA_STREAM(stream); } }; @@ -167,4 +167,4 @@ struct RepeatImplBody { { repeat_template(context); } -} // namespace cunumeric +} // namespace cupynumeric diff --git a/src/cunumeric/index/repeat.h b/src/cupynumeric/index/repeat.h similarity index 82% rename from src/cunumeric/index/repeat.h rename to src/cupynumeric/index/repeat.h index a041f9ffa3..b9a0f97945 100644 --- a/src/cunumeric/index/repeat.h +++ b/src/cupynumeric/index/repeat.h @@ -16,9 +16,9 @@ #pragma once -#include "cunumeric/cunumeric_task.h" +#include "cupynumeric/cupynumeric_task.h" -namespace cunumeric { +namespace cupynumeric { struct RepeatArgs { legate::PhysicalStore output; @@ -29,9 +29,9 @@ struct RepeatArgs { const bool scalar_repeats; }; -class RepeatTask : public CuNumericTask { +class RepeatTask : public CuPyNumericTask { public: - static constexpr auto TASK_ID = legate::LocalTaskID{CUNUMERIC_REPEAT}; + static constexpr auto TASK_ID = legate::LocalTaskID{CUPYNUMERIC_REPEAT}; public: static void cpu_variant(legate::TaskContext context); @@ -43,4 +43,4 @@ class RepeatTask : public CuNumericTask { #endif }; -} // namespace cunumeric +} // namespace cupynumeric diff --git a/src/cunumeric/index/repeat_omp.cc b/src/cupynumeric/index/repeat_omp.cc similarity index 95% rename from src/cunumeric/index/repeat_omp.cc rename to src/cupynumeric/index/repeat_omp.cc index d74e6a3246..01923c1931 100644 --- a/src/cunumeric/index/repeat_omp.cc +++ b/src/cupynumeric/index/repeat_omp.cc @@ -14,15 +14,15 @@ * */ -#include "cunumeric/index/repeat.h" -#include "cunumeric/index/repeat_template.inl" -#include "cunumeric/omp_help.h" +#include "cupynumeric/index/repeat.h" +#include "cupynumeric/index/repeat_template.inl" +#include "cupynumeric/omp_help.h" #include #include #include -namespace cunumeric { +namespace cupynumeric { using namespace legate; @@ -117,4 +117,4 @@ struct RepeatImplBody { repeat_template(context); } -} // namespace cunumeric +} // namespace cupynumeric diff --git a/src/cunumeric/index/repeat_template.inl b/src/cupynumeric/index/repeat_template.inl similarity index 94% rename from src/cunumeric/index/repeat_template.inl rename to src/cupynumeric/index/repeat_template.inl index c24cace01b..8ce7b5ed40 100644 --- a/src/cunumeric/index/repeat_template.inl +++ b/src/cupynumeric/index/repeat_template.inl @@ -17,10 +17,10 @@ #pragma once // Useful for IDEs -#include "cunumeric/index/repeat.h" -#include "cunumeric/pitches.h" +#include "cupynumeric/index/repeat.h" +#include "cupynumeric/pitches.h" -namespace cunumeric { +namespace cupynumeric { using namespace legate; @@ -74,4 +74,4 @@ static void repeat_template(TaskContext& context) } } -} // namespace cunumeric +} // namespace cupynumeric diff --git a/src/cunumeric/index/select.cc b/src/cupynumeric/index/select.cc similarity index 93% rename from src/cunumeric/index/select.cc rename to src/cupynumeric/index/select.cc index b375c2e631..9b56763310 100644 --- a/src/cunumeric/index/select.cc +++ b/src/cupynumeric/index/select.cc @@ -14,10 +14,10 @@ * */ -#include "cunumeric/index/select.h" -#include "cunumeric/index/select_template.inl" +#include "cupynumeric/index/select.h" +#include "cupynumeric/index/select_template.inl" -namespace cunumeric { +namespace cupynumeric { using namespace legate; @@ -35,7 +35,7 @@ struct SelectImplBody { { const size_t volume = rect.volume(); uint32_t narrays = condlist.size(); -#ifdef DEBUG_CUNUMERIC +#ifdef DEBUG_CUPYNUMERIC assert(narrays == choicelist.size()); #endif @@ -80,4 +80,4 @@ namespace // unnamed static void __attribute__((constructor)) register_tasks(void) { SelectTask::register_variants(); } } // namespace -} // namespace cunumeric +} // namespace cupynumeric diff --git a/src/cunumeric/index/select.cu b/src/cupynumeric/index/select.cu similarity index 94% rename from src/cunumeric/index/select.cu rename to src/cupynumeric/index/select.cu index 2a9320a833..007367b68b 100644 --- a/src/cunumeric/index/select.cu +++ b/src/cupynumeric/index/select.cu @@ -14,11 +14,11 @@ * */ -#include "cunumeric/index/select.h" -#include "cunumeric/index/select_template.inl" -#include "cunumeric/cuda_help.h" +#include "cupynumeric/index/select.h" +#include "cupynumeric/index/select_template.inl" +#include "cupynumeric/cuda_help.h" -namespace cunumeric { +namespace cupynumeric { template __global__ static void __launch_bounds__(THREADS_PER_BLOCK, MIN_CTAS_PER_SM) @@ -80,7 +80,7 @@ struct SelectImplBody { bool dense) const { uint32_t narrays = condlist.size(); -#ifdef DEBUG_CUNUMERIC +#ifdef DEBUG_CUPYNUMERIC assert(narrays == choicelist.size()); #endif const size_t blocks = (rect.volume() + THREADS_PER_BLOCK - 1) / THREADS_PER_BLOCK; @@ -118,7 +118,7 @@ struct SelectImplBody { out, narrays, cond_arr, choice_arr, default_val, rect, pitches, rect.volume()); } - CUNUMERIC_CHECK_CUDA_STREAM(stream); + CUPYNUMERIC_CHECK_CUDA_STREAM(stream); } }; @@ -127,4 +127,4 @@ struct SelectImplBody { select_template(context); } -} // namespace cunumeric +} // namespace cupynumeric diff --git a/src/cunumeric/index/select.h b/src/cupynumeric/index/select.h similarity index 82% rename from src/cunumeric/index/select.h rename to src/cupynumeric/index/select.h index e9857c9f0a..f96c26a963 100644 --- a/src/cunumeric/index/select.h +++ b/src/cupynumeric/index/select.h @@ -16,9 +16,9 @@ #pragma once -#include "cunumeric/cunumeric_task.h" +#include "cupynumeric/cupynumeric_task.h" -namespace cunumeric { +namespace cupynumeric { struct SelectArgs { legate::PhysicalArray out; @@ -26,9 +26,9 @@ struct SelectArgs { const legate::Scalar& default_value; }; -class SelectTask : public CuNumericTask { +class SelectTask : public CuPyNumericTask { public: - static constexpr auto TASK_ID = legate::LocalTaskID{CUNUMERIC_SELECT}; + static constexpr auto TASK_ID = legate::LocalTaskID{CUPYNUMERIC_SELECT}; public: static void cpu_variant(legate::TaskContext context); @@ -40,4 +40,4 @@ class SelectTask : public CuNumericTask { #endif }; -} // namespace cunumeric +} // namespace cupynumeric diff --git a/src/cunumeric/index/select_omp.cc b/src/cupynumeric/index/select_omp.cc similarity index 93% rename from src/cunumeric/index/select_omp.cc rename to src/cupynumeric/index/select_omp.cc index 353d5c1ba5..9fa7a42593 100644 --- a/src/cunumeric/index/select_omp.cc +++ b/src/cupynumeric/index/select_omp.cc @@ -14,10 +14,10 @@ * */ -#include "cunumeric/index/select.h" -#include "cunumeric/index/select_template.inl" +#include "cupynumeric/index/select.h" +#include "cupynumeric/index/select_template.inl" -namespace cunumeric { +namespace cupynumeric { using namespace legate; @@ -35,7 +35,7 @@ struct SelectImplBody { { const size_t volume = rect.volume(); uint32_t narrays = condlist.size(); -#ifdef DEBUG_CUNUMERIC +#ifdef DEBUG_CUPYNUMERIC assert(narrays == choicelist.size()); #endif @@ -79,4 +79,4 @@ struct SelectImplBody { select_template(context); } -} // namespace cunumeric +} // namespace cupynumeric diff --git a/src/cunumeric/index/select_template.inl b/src/cupynumeric/index/select_template.inl similarity index 93% rename from src/cunumeric/index/select_template.inl rename to src/cupynumeric/index/select_template.inl index 425be1ab44..54f530d837 100644 --- a/src/cunumeric/index/select_template.inl +++ b/src/cupynumeric/index/select_template.inl @@ -17,10 +17,10 @@ #pragma once // Useful for IDEs -#include "cunumeric/index/select.h" -#include "cunumeric/pitches.h" +#include "cupynumeric/index/select.h" +#include "cupynumeric/pitches.h" -namespace cunumeric { +namespace cupynumeric { using namespace legate; @@ -55,7 +55,7 @@ struct SelectImpl { condlist.reserve(args.inputs.size() / 2); for (int32_t i = 0; i < args.inputs.size() / 2; i++) { auto rect_c = args.inputs[i].shape(); -#ifdef DEBUG_CUNUMERIC +#ifdef DEBUG_CUPYNUMERIC assert(rect_c == out_rect); #endif condlist.push_back(args.inputs[i].data().read_accessor(rect_c)); @@ -66,7 +66,7 @@ struct SelectImpl { choicelist.reserve(args.inputs.size() / 2); for (int32_t i = args.inputs.size() / 2; i < args.inputs.size(); i++) { auto rect_c = args.inputs[i].shape(); -#ifdef DEBUG_CUNUMERIC +#ifdef DEBUG_CUPYNUMERIC assert(rect_c == out_rect); #endif choicelist.push_back(args.inputs[i].data().read_accessor(rect_c)); @@ -86,4 +86,4 @@ static void select_template(TaskContext& context) double_dispatch(args.out.dim(), args.out.type().code(), SelectImpl{}, args); } -} // namespace cunumeric +} // namespace cupynumeric diff --git a/src/cunumeric/index/wrap.cc b/src/cupynumeric/index/wrap.cc similarity index 94% rename from src/cunumeric/index/wrap.cc rename to src/cupynumeric/index/wrap.cc index bed8a68e02..3794d1e6ff 100644 --- a/src/cunumeric/index/wrap.cc +++ b/src/cupynumeric/index/wrap.cc @@ -14,10 +14,10 @@ * */ -#include "cunumeric/index/wrap.h" -#include "cunumeric/index/wrap_template.inl" +#include "cupynumeric/index/wrap.h" +#include "cupynumeric/index/wrap_template.inl" -namespace cunumeric { +namespace cupynumeric { using namespace legate; @@ -69,4 +69,4 @@ namespace // unnamed static void __attribute__((constructor)) register_tasks(void) { WrapTask::register_variants(); } } // namespace -} // namespace cunumeric +} // namespace cupynumeric diff --git a/src/cunumeric/index/wrap.cu b/src/cupynumeric/index/wrap.cu similarity index 96% rename from src/cunumeric/index/wrap.cu rename to src/cupynumeric/index/wrap.cu index 3ee9ac0bd4..8edf256c7e 100644 --- a/src/cunumeric/index/wrap.cu +++ b/src/cupynumeric/index/wrap.cu @@ -14,11 +14,11 @@ * */ -#include "cunumeric/index/wrap.h" -#include "cunumeric/index/wrap_template.inl" -#include "cunumeric/cuda_help.h" +#include "cupynumeric/index/wrap.h" +#include "cupynumeric/index/wrap_template.inl" +#include "cupynumeric/cuda_help.h" -namespace cunumeric { +namespace cupynumeric { using namespace legate; @@ -113,7 +113,7 @@ void check_out_of_bounds(const AccessorRO& indices, check_kernel<<>>( out_of_bounds, indices, start, volume, volume_base, 1); } - CUNUMERIC_CHECK_CUDA_STREAM(stream); + CUPYNUMERIC_CHECK_CUDA_STREAM(stream); bool res = out_of_bounds.read(stream); if (res) { @@ -158,7 +158,7 @@ struct WrapImplBody { volume_base, indices); } - CUNUMERIC_CHECK_CUDA_STREAM(stream); + CUPYNUMERIC_CHECK_CUDA_STREAM(stream); } }; @@ -167,4 +167,4 @@ struct WrapImplBody { wrap_template(context); } -} // namespace cunumeric +} // namespace cupynumeric diff --git a/src/cunumeric/index/wrap.h b/src/cupynumeric/index/wrap.h similarity index 92% rename from src/cunumeric/index/wrap.h rename to src/cupynumeric/index/wrap.h index 3b4afce930..809de5cf3e 100644 --- a/src/cunumeric/index/wrap.h +++ b/src/cupynumeric/index/wrap.h @@ -16,9 +16,9 @@ #pragma once -#include "cunumeric/cunumeric_task.h" +#include "cupynumeric/cupynumeric_task.h" -namespace cunumeric { +namespace cupynumeric { struct WrapArgs { legate::PhysicalStore out{nullptr}; // Array with Point type that is used to @@ -30,9 +30,9 @@ struct WrapArgs { legate::PhysicalStore in{nullptr}; }; -class WrapTask : public CuNumericTask { +class WrapTask : public CuPyNumericTask { public: - static constexpr auto TASK_ID = legate::LocalTaskID{CUNUMERIC_WRAP}; + static constexpr auto TASK_ID = legate::LocalTaskID{CUPYNUMERIC_WRAP}; public: static void cpu_variant(legate::TaskContext context); @@ -83,4 +83,4 @@ inline bool check_idx_omp(const int64_t i, } inline bool check_idx_omp(const int64_t i, const int64_t volume, const bool&) { return false; } -} // namespace cunumeric +} // namespace cupynumeric diff --git a/src/cunumeric/index/wrap_omp.cc b/src/cupynumeric/index/wrap_omp.cc similarity index 94% rename from src/cunumeric/index/wrap_omp.cc rename to src/cupynumeric/index/wrap_omp.cc index 888b819f00..2d593ad98d 100644 --- a/src/cunumeric/index/wrap_omp.cc +++ b/src/cupynumeric/index/wrap_omp.cc @@ -14,10 +14,10 @@ * */ -#include "cunumeric/index/wrap.h" -#include "cunumeric/index/wrap_template.inl" +#include "cupynumeric/index/wrap.h" +#include "cupynumeric/index/wrap_template.inl" -namespace cunumeric { +namespace cupynumeric { using namespace legate; @@ -75,4 +75,4 @@ struct WrapImplBody { wrap_template(context); } -} // namespace cunumeric +} // namespace cupynumeric diff --git a/src/cunumeric/index/wrap_template.inl b/src/cupynumeric/index/wrap_template.inl similarity index 93% rename from src/cunumeric/index/wrap_template.inl rename to src/cupynumeric/index/wrap_template.inl index 4fd95d574c..e06ad34c50 100644 --- a/src/cunumeric/index/wrap_template.inl +++ b/src/cupynumeric/index/wrap_template.inl @@ -17,10 +17,10 @@ #pragma once // Useful for IDEs -#include "cunumeric/index/wrap.h" -#include "cunumeric/pitches.h" +#include "cupynumeric/index/wrap.h" +#include "cupynumeric/pitches.h" -namespace cunumeric { +namespace cupynumeric { using namespace legate; @@ -56,7 +56,7 @@ struct WrapImpl { Pitches pitches_base{}; size_t volume_base = pitches_base.flatten(rect_base); -#ifdef DEBUG_CUNUMERIC +#ifdef DEBUG_CUPYNUMERIC assert(volume_base != 0); #else static_cast(volume_base); @@ -65,7 +65,7 @@ struct WrapImpl { if (args.has_input) { auto rect_in = args.in.shape<1>(); auto in = args.in.read_accessor(rect_in); // input should be always integer type -#ifdef DEBUG_CUNUMERIC +#ifdef DEBUG_CUPYNUMERIC assert(rect_in == rect_out); #endif WrapImplBody()( @@ -92,4 +92,4 @@ static void wrap_template(TaskContext& context) dim_dispatch(dim, WrapImpl{}, args); } -} // namespace cunumeric +} // namespace cupynumeric diff --git a/src/cunumeric/index/zip.cc b/src/cupynumeric/index/zip.cc similarity index 94% rename from src/cunumeric/index/zip.cc rename to src/cupynumeric/index/zip.cc index 7712ce756f..b9cf0969ce 100644 --- a/src/cunumeric/index/zip.cc +++ b/src/cupynumeric/index/zip.cc @@ -14,10 +14,10 @@ * */ -#include "cunumeric/index/zip.h" -#include "cunumeric/index/zip_template.inl" +#include "cupynumeric/index/zip.h" +#include "cupynumeric/index/zip_template.inl" -namespace cunumeric { +namespace cupynumeric { using namespace legate; @@ -59,7 +59,7 @@ struct ZipImplBody { } } } else { -#ifdef DEBUG_CUNUMERIC +#ifdef DEBUG_CUPYNUMERIC assert(index_arrays.size() < N); #endif const size_t volume = rect.volume(); @@ -92,4 +92,4 @@ namespace // unnamed static void __attribute__((constructor)) register_tasks(void) { ZipTask::register_variants(); } } // namespace -} // namespace cunumeric +} // namespace cupynumeric diff --git a/src/cunumeric/index/zip.cu b/src/cupynumeric/index/zip.cu similarity index 96% rename from src/cunumeric/index/zip.cu rename to src/cupynumeric/index/zip.cu index ecbe10b6b7..53a8ded958 100644 --- a/src/cunumeric/index/zip.cu +++ b/src/cupynumeric/index/zip.cu @@ -14,11 +14,11 @@ * */ -#include "cunumeric/index/zip.h" -#include "cunumeric/index/zip_template.inl" -#include "cunumeric/cuda_help.h" +#include "cupynumeric/index/zip.h" +#include "cupynumeric/index/zip_template.inl" +#include "cupynumeric/cuda_help.h" -namespace cunumeric { +namespace cupynumeric { template __global__ static void __launch_bounds__(THREADS_PER_BLOCK, MIN_CTAS_PER_SM) @@ -146,7 +146,7 @@ struct ZipImplBody { check_kernel<<>>( out_of_bounds, index_arrays, volume, 1, rect, pitches, narrays, start_index, shape); } - CUNUMERIC_CHECK_CUDA_STREAM(stream); + CUPYNUMERIC_CHECK_CUDA_STREAM(stream); bool res = out_of_bounds.read(stream); if (res) { @@ -191,14 +191,14 @@ struct ZipImplBody { out, index_buf, rect, pitches, volume, shape, std::make_index_sequence()); } } else { -#ifdef DEBUG_CUNUMERIC +#ifdef DEBUG_CUPYNUMERIC assert(index_arrays.size() < N); #endif int num_arrays = index_arrays.size(); zip_kernel<<>>( out, index_buf, rect, pitches, num_arrays, volume, key_dim, start_index, shape); } - CUNUMERIC_CHECK_CUDA_STREAM(stream); + CUPYNUMERIC_CHECK_CUDA_STREAM(stream); } }; @@ -206,4 +206,4 @@ struct ZipImplBody { { zip_template(context); } -} // namespace cunumeric +} // namespace cupynumeric diff --git a/src/cunumeric/index/zip.h b/src/cupynumeric/index/zip.h similarity index 89% rename from src/cunumeric/index/zip.h rename to src/cupynumeric/index/zip.h index a1056af0a5..54dc0a0ea4 100644 --- a/src/cunumeric/index/zip.h +++ b/src/cupynumeric/index/zip.h @@ -16,9 +16,9 @@ #pragma once -#include "cunumeric/cunumeric_task.h" +#include "cupynumeric/cupynumeric_task.h" -namespace cunumeric { +namespace cupynumeric { struct ZipArgs { legate::PhysicalStore out; @@ -29,9 +29,9 @@ struct ZipArgs { const legate::DomainPoint shape; }; -class ZipTask : public CuNumericTask { +class ZipTask : public CuPyNumericTask { public: - static constexpr auto TASK_ID = legate::LocalTaskID{CUNUMERIC_ZIP}; + static constexpr auto TASK_ID = legate::LocalTaskID{CUPYNUMERIC_ZIP}; public: static void cpu_variant(legate::TaskContext context); @@ -66,4 +66,4 @@ constexpr legate::coord_t compute_idx_cuda(legate::coord_t index, legate::coord_ return new_index; } -} // namespace cunumeric +} // namespace cupynumeric diff --git a/src/cunumeric/index/zip_omp.cc b/src/cupynumeric/index/zip_omp.cc similarity index 95% rename from src/cunumeric/index/zip_omp.cc rename to src/cupynumeric/index/zip_omp.cc index b48f695c8c..49e39ec449 100644 --- a/src/cunumeric/index/zip_omp.cc +++ b/src/cupynumeric/index/zip_omp.cc @@ -14,10 +14,10 @@ * */ -#include "cunumeric/index/zip.h" -#include "cunumeric/index/zip_template.inl" +#include "cupynumeric/index/zip.h" +#include "cupynumeric/index/zip_template.inl" -namespace cunumeric { +namespace cupynumeric { using namespace legate; @@ -70,7 +70,7 @@ struct ZipImplBody { } } // else } else { -#ifdef DEBUG_CUNUMERIC +#ifdef DEBUG_CUPYNUMERIC assert(index_arrays.size() < N); #endif #pragma omp parallel for schedule(static) @@ -105,4 +105,4 @@ struct ZipImplBody { zip_template(context); } -} // namespace cunumeric +} // namespace cupynumeric diff --git a/src/cunumeric/index/zip_template.inl b/src/cupynumeric/index/zip_template.inl similarity index 96% rename from src/cunumeric/index/zip_template.inl rename to src/cupynumeric/index/zip_template.inl index 642bd33db5..16eff841af 100644 --- a/src/cunumeric/index/zip_template.inl +++ b/src/cupynumeric/index/zip_template.inl @@ -17,10 +17,10 @@ #pragma once // Useful for IDEs -#include "cunumeric/index/zip.h" -#include "cunumeric/pitches.h" +#include "cupynumeric/index/zip.h" +#include "cupynumeric/pitches.h" -namespace cunumeric { +namespace cupynumeric { using namespace legate; @@ -108,4 +108,4 @@ static void zip_template(TaskContext& context) double_dispatch(dim, N, ZipImpl{}, args); } -} // namespace cunumeric +} // namespace cupynumeric diff --git a/src/cunumeric/item/read.cc b/src/cupynumeric/item/read.cc similarity index 88% rename from src/cunumeric/item/read.cc rename to src/cupynumeric/item/read.cc index 477498abda..4ee28d66f1 100644 --- a/src/cunumeric/item/read.cc +++ b/src/cupynumeric/item/read.cc @@ -14,10 +14,10 @@ * */ -#include "cunumeric/item/read.h" -#include "cunumeric/item/read_template.inl" +#include "cupynumeric/item/read.h" +#include "cupynumeric/item/read_template.inl" -namespace cunumeric { +namespace cupynumeric { using namespace legate; @@ -36,4 +36,4 @@ namespace // unnamed static void __attribute__((constructor)) register_tasks(void) { ReadTask::register_variants(); } } // namespace -} // namespace cunumeric +} // namespace cupynumeric diff --git a/src/cunumeric/item/read.cu b/src/cupynumeric/item/read.cu similarity index 84% rename from src/cunumeric/item/read.cu rename to src/cupynumeric/item/read.cu index 20d69e5d7d..a18e5d863a 100644 --- a/src/cunumeric/item/read.cu +++ b/src/cupynumeric/item/read.cu @@ -14,11 +14,11 @@ * */ -#include "cunumeric/item/read.h" -#include "cunumeric/item/read_template.inl" -#include "cunumeric/cuda_help.h" +#include "cupynumeric/item/read.h" +#include "cupynumeric/item/read_template.inl" +#include "cupynumeric/cuda_help.h" -namespace cunumeric { +namespace cupynumeric { template static __global__ void __launch_bounds__(1, 1) @@ -33,7 +33,7 @@ struct ReadImplBody { { auto stream = get_cached_stream(); read_value<<<1, 1, 0, stream>>>(out, in); - CUNUMERIC_CHECK_CUDA_STREAM(stream); + CUPYNUMERIC_CHECK_CUDA_STREAM(stream); } }; @@ -42,4 +42,4 @@ struct ReadImplBody { read_template(context); } -} // namespace cunumeric +} // namespace cupynumeric diff --git a/src/cunumeric/item/read.h b/src/cupynumeric/item/read.h similarity index 80% rename from src/cunumeric/item/read.h rename to src/cupynumeric/item/read.h index c7fbdd909a..8820d3ae14 100644 --- a/src/cunumeric/item/read.h +++ b/src/cupynumeric/item/read.h @@ -16,13 +16,13 @@ #pragma once -#include "cunumeric/cunumeric_task.h" +#include "cupynumeric/cupynumeric_task.h" -namespace cunumeric { +namespace cupynumeric { -class ReadTask : public CuNumericTask { +class ReadTask : public CuPyNumericTask { public: - static constexpr auto TASK_ID = legate::LocalTaskID{CUNUMERIC_READ}; + static constexpr auto TASK_ID = legate::LocalTaskID{CUPYNUMERIC_READ}; public: static void cpu_variant(legate::TaskContext context); @@ -34,4 +34,4 @@ class ReadTask : public CuNumericTask { #endif }; -} // namespace cunumeric +} // namespace cupynumeric diff --git a/src/cunumeric/item/read_template.inl b/src/cupynumeric/item/read_template.inl similarity index 93% rename from src/cunumeric/item/read_template.inl rename to src/cupynumeric/item/read_template.inl index 8af240773c..644856c253 100644 --- a/src/cunumeric/item/read_template.inl +++ b/src/cupynumeric/item/read_template.inl @@ -17,9 +17,9 @@ #pragma once // Useful for IDEs -#include "cunumeric/item/read.h" +#include "cupynumeric/item/read.h" -namespace cunumeric { +namespace cupynumeric { using namespace legate; @@ -46,4 +46,4 @@ static void read_template(TaskContext& context) type_dispatch(in.type().code(), ReadImpl{}, out, in); } -} // namespace cunumeric +} // namespace cupynumeric diff --git a/src/cunumeric/item/write.cc b/src/cupynumeric/item/write.cc similarity index 89% rename from src/cunumeric/item/write.cc rename to src/cupynumeric/item/write.cc index 34cd747c60..fd2049fb4d 100644 --- a/src/cunumeric/item/write.cc +++ b/src/cupynumeric/item/write.cc @@ -14,10 +14,10 @@ * */ -#include "cunumeric/item/write.h" -#include "cunumeric/item/write_template.inl" +#include "cupynumeric/item/write.h" +#include "cupynumeric/item/write_template.inl" -namespace cunumeric { +namespace cupynumeric { using namespace legate; @@ -39,4 +39,4 @@ namespace // unnamed static void __attribute__((constructor)) register_tasks(void) { WriteTask::register_variants(); } } // namespace -} // namespace cunumeric +} // namespace cupynumeric diff --git a/src/cunumeric/item/write.cu b/src/cupynumeric/item/write.cu similarity index 84% rename from src/cunumeric/item/write.cu rename to src/cupynumeric/item/write.cu index aa056c9b80..725402e300 100644 --- a/src/cunumeric/item/write.cu +++ b/src/cupynumeric/item/write.cu @@ -14,11 +14,11 @@ * */ -#include "cunumeric/item/write.h" -#include "cunumeric/item/write_template.inl" -#include "cunumeric/cuda_help.h" +#include "cupynumeric/item/write.h" +#include "cupynumeric/item/write_template.inl" +#include "cupynumeric/cuda_help.h" -namespace cunumeric { +namespace cupynumeric { template static __global__ void __launch_bounds__(1, 1) @@ -33,7 +33,7 @@ struct WriteImplBody { { auto stream = get_cached_stream(); write_value<<<1, 1, 0, stream>>>(out, value); - CUNUMERIC_CHECK_CUDA_STREAM(stream); + CUPYNUMERIC_CHECK_CUDA_STREAM(stream); } }; @@ -42,4 +42,4 @@ struct WriteImplBody { write_template(context); } -} // namespace cunumeric +} // namespace cupynumeric diff --git a/src/cunumeric/item/write.h b/src/cupynumeric/item/write.h similarity index 80% rename from src/cunumeric/item/write.h rename to src/cupynumeric/item/write.h index 65ec95d9ce..81be673ea3 100644 --- a/src/cunumeric/item/write.h +++ b/src/cupynumeric/item/write.h @@ -16,13 +16,13 @@ #pragma once -#include "cunumeric/cunumeric_task.h" +#include "cupynumeric/cupynumeric_task.h" -namespace cunumeric { +namespace cupynumeric { -class WriteTask : public CuNumericTask { +class WriteTask : public CuPyNumericTask { public: - static constexpr auto TASK_ID = legate::LocalTaskID{CUNUMERIC_WRITE}; + static constexpr auto TASK_ID = legate::LocalTaskID{CUPYNUMERIC_WRITE}; public: static void cpu_variant(legate::TaskContext context); @@ -34,4 +34,4 @@ class WriteTask : public CuNumericTask { #endif }; -} // namespace cunumeric +} // namespace cupynumeric diff --git a/src/cunumeric/item/write_template.inl b/src/cupynumeric/item/write_template.inl similarity index 93% rename from src/cunumeric/item/write_template.inl rename to src/cupynumeric/item/write_template.inl index a7f828efa6..b584606f23 100644 --- a/src/cunumeric/item/write_template.inl +++ b/src/cupynumeric/item/write_template.inl @@ -17,9 +17,9 @@ #pragma once // Useful for IDEs -#include "cunumeric/item/write.h" +#include "cupynumeric/item/write.h" -namespace cunumeric { +namespace cupynumeric { using namespace legate; @@ -47,4 +47,4 @@ static void write_template(TaskContext& context) legate::double_dispatch(dim, out.type().code(), WriteImpl(), out, in); } -} // namespace cunumeric +} // namespace cupynumeric diff --git a/src/cunumeric/mapper.cc b/src/cupynumeric/mapper.cc similarity index 86% rename from src/cunumeric/mapper.cc rename to src/cupynumeric/mapper.cc index 711ee0363e..bd6583a854 100644 --- a/src/cunumeric/mapper.cc +++ b/src/cupynumeric/mapper.cc @@ -14,29 +14,29 @@ * */ -#include "cunumeric/mapper.h" +#include "cupynumeric/mapper.h" using namespace legate; using namespace legate::mapping; -namespace cunumeric { +namespace cupynumeric { -TaskTarget CuNumericMapper::task_target(const legate::mapping::Task& task, - const std::vector& options) +TaskTarget CuPyNumericMapper::task_target(const legate::mapping::Task& task, + const std::vector& options) { return *options.begin(); } -Scalar CuNumericMapper::tunable_value(TunableID tunable_id) +Scalar CuPyNumericMapper::tunable_value(TunableID tunable_id) { - LEGATE_ABORT("cuNumeric does not use any tunable values"); + LEGATE_ABORT("cuPyNumeric does not use any tunable values"); } -std::vector CuNumericMapper::store_mappings( +std::vector CuPyNumericMapper::store_mappings( const mapping::Task& task, const std::vector& options) { switch (static_cast(task.task_id())) { - case CUNUMERIC_CONVOLVE: { + case CUPYNUMERIC_CONVOLVE: { std::vector mappings; auto inputs = task.inputs(); mappings.push_back(StoreMapping::default_mapping(inputs[0].data(), options.front())); @@ -47,7 +47,7 @@ std::vector CuNumericMapper::store_mappings( } return mappings; } - case CUNUMERIC_FFT: { + case CUPYNUMERIC_FFT: { std::vector mappings; auto inputs = task.inputs(); auto outputs = task.outputs(); @@ -56,7 +56,7 @@ std::vector CuNumericMapper::store_mappings( StoreMapping::default_mapping(outputs[0].data(), options.front(), true /*exact*/)); return mappings; } - case CUNUMERIC_TRANSPOSE_COPY_2D: { + case CUPYNUMERIC_TRANSPOSE_COPY_2D: { std::vector mappings; auto output = task.output(0); mappings.push_back(StoreMapping::default_mapping(output.data(), options.front())); @@ -64,7 +64,7 @@ std::vector CuNumericMapper::store_mappings( mappings.back().policy().exact = true; return std::move(mappings); } - case CUNUMERIC_MATMUL: { + case CUPYNUMERIC_MATMUL: { std::vector mappings; auto inputA = task.input(1); auto inputB = task.input(2); @@ -82,8 +82,8 @@ std::vector CuNumericMapper::store_mappings( return mappings; } - case CUNUMERIC_MATVECMUL: - case CUNUMERIC_UNIQUE_REDUCE: { + case CUPYNUMERIC_MATVECMUL: + case CUPYNUMERIC_UNIQUE_REDUCE: { // TODO: Our actual requirements are a little less strict than this; we require each array or // vector to have a stride of 1 on at least one dimension. std::vector mappings; @@ -99,15 +99,15 @@ std::vector CuNumericMapper::store_mappings( } return mappings; } - case CUNUMERIC_POTRF: - case CUNUMERIC_QR: - case CUNUMERIC_TRSM: - case CUNUMERIC_SOLVE: - case CUNUMERIC_SVD: - case CUNUMERIC_SYRK: - case CUNUMERIC_GEMM: - case CUNUMERIC_MP_POTRF: - case CUNUMERIC_MP_SOLVE: { + case CUPYNUMERIC_POTRF: + case CUPYNUMERIC_QR: + case CUPYNUMERIC_TRSM: + case CUPYNUMERIC_SOLVE: + case CUPYNUMERIC_SVD: + case CUPYNUMERIC_SYRK: + case CUPYNUMERIC_GEMM: + case CUPYNUMERIC_MP_POTRF: + case CUPYNUMERIC_MP_SOLVE: { std::vector mappings; auto inputs = task.inputs(); auto outputs = task.outputs(); @@ -125,7 +125,7 @@ std::vector CuNumericMapper::store_mappings( } // CHANGE: If this code is changed, make sure all layouts are // consistent with those assumed in batched_cholesky.cu, etc - case CUNUMERIC_BATCHED_CHOLESKY: { + case CUPYNUMERIC_BATCHED_CHOLESKY: { std::vector mappings; auto inputs = task.inputs(); auto outputs = task.outputs(); @@ -142,7 +142,7 @@ std::vector CuNumericMapper::store_mappings( } return std::move(mappings); } - case CUNUMERIC_TRILU: { + case CUPYNUMERIC_TRILU: { if (task.scalars().size() == 2) { return {}; } @@ -155,14 +155,14 @@ std::vector CuNumericMapper::store_mappings( mappings.back().policy().ordering.set_fortran_order(); return mappings; } - case CUNUMERIC_SEARCHSORTED: { + case CUPYNUMERIC_SEARCHSORTED: { std::vector mappings; auto inputs = task.inputs(); mappings.push_back( StoreMapping::default_mapping(inputs[0].data(), options.front(), true /*exact*/)); return mappings; } - case CUNUMERIC_SORT: { + case CUPYNUMERIC_SORT: { std::vector mappings; auto inputs = task.inputs(); auto outputs = task.outputs(); @@ -176,7 +176,7 @@ std::vector CuNumericMapper::store_mappings( } return mappings; } - case CUNUMERIC_SCAN_LOCAL: { + case CUPYNUMERIC_SCAN_LOCAL: { std::vector mappings; auto inputs = task.inputs(); auto outputs = task.outputs(); @@ -190,7 +190,7 @@ std::vector CuNumericMapper::store_mappings( } return mappings; } - case CUNUMERIC_SCAN_GLOBAL: { + case CUPYNUMERIC_SCAN_GLOBAL: { std::vector mappings; auto inputs = task.inputs(); auto outputs = task.outputs(); @@ -204,7 +204,7 @@ std::vector CuNumericMapper::store_mappings( } return mappings; } - case CUNUMERIC_BITGENERATOR: { + case CUPYNUMERIC_BITGENERATOR: { std::vector mappings; auto inputs = task.inputs(); auto outputs = task.outputs(); @@ -226,4 +226,4 @@ std::vector CuNumericMapper::store_mappings( return {}; } -} // namespace cunumeric +} // namespace cupynumeric diff --git a/src/cunumeric/mapper.h b/src/cupynumeric/mapper.h similarity index 87% rename from src/cunumeric/mapper.h rename to src/cupynumeric/mapper.h index 3cd7eb4149..c1128f7102 100644 --- a/src/cunumeric/mapper.h +++ b/src/cupynumeric/mapper.h @@ -16,11 +16,11 @@ #pragma once -#include "cunumeric/cunumeric_task.h" +#include "cupynumeric/cupynumeric_task.h" -namespace cunumeric { +namespace cupynumeric { -class CuNumericMapper final : public legate::mapping::Mapper { +class CuPyNumericMapper final : public legate::mapping::Mapper { // Legate mapping functions public: [[nodiscard]] legate::mapping::TaskTarget task_target( @@ -32,4 +32,4 @@ class CuNumericMapper final : public legate::mapping::Mapper { [[nodiscard]] legate::Scalar tunable_value(legate::TunableID tunable_id) override; }; -} // namespace cunumeric +} // namespace cupynumeric diff --git a/src/cunumeric/matrix/batched_cholesky.cc b/src/cupynumeric/matrix/batched_cholesky.cc similarity index 91% rename from src/cunumeric/matrix/batched_cholesky.cc rename to src/cupynumeric/matrix/batched_cholesky.cc index 67823830ec..a683f32ebf 100644 --- a/src/cunumeric/matrix/batched_cholesky.cc +++ b/src/cupynumeric/matrix/batched_cholesky.cc @@ -14,15 +14,15 @@ * */ -#include "cunumeric/matrix/batched_cholesky.h" -#include "cunumeric/cunumeric_task.h" -#include "cunumeric/matrix/batched_cholesky_template.inl" +#include "cupynumeric/matrix/batched_cholesky.h" +#include "cupynumeric/cupynumeric_task.h" +#include "cupynumeric/matrix/batched_cholesky_template.inl" #include #include #include -namespace cunumeric { +namespace cupynumeric { using namespace legate; @@ -84,4 +84,4 @@ static void __attribute__((constructor)) register_tasks(void) } } // namespace -} // namespace cunumeric +} // namespace cupynumeric diff --git a/src/cunumeric/matrix/batched_cholesky.cu b/src/cupynumeric/matrix/batched_cholesky.cu similarity index 91% rename from src/cunumeric/matrix/batched_cholesky.cu rename to src/cupynumeric/matrix/batched_cholesky.cu index cf3c81c848..0ef285d537 100644 --- a/src/cunumeric/matrix/batched_cholesky.cu +++ b/src/cupynumeric/matrix/batched_cholesky.cu @@ -14,13 +14,13 @@ * */ -#include "cunumeric/matrix/batched_cholesky.h" -#include "cunumeric/matrix/potrf.h" -#include "cunumeric/matrix/batched_cholesky_template.inl" +#include "cupynumeric/matrix/batched_cholesky.h" +#include "cupynumeric/matrix/potrf.h" +#include "cupynumeric/matrix/batched_cholesky_template.inl" -#include "cunumeric/cuda_help.h" +#include "cupynumeric/cuda_help.h" -namespace cunumeric { +namespace cupynumeric { using namespace legate; @@ -101,7 +101,7 @@ struct BatchedTransposeImplBody { // the lower diagonal transpose_2d_lower<<>>(out, n); - CUNUMERIC_CHECK_CUDA_STREAM(stream); + CUPYNUMERIC_CHECK_CUDA_STREAM(stream); } }; @@ -110,4 +110,4 @@ struct BatchedTransposeImplBody { batched_cholesky_task_context_dispatch(context); } -} // namespace cunumeric +} // namespace cupynumeric diff --git a/src/cunumeric/matrix/batched_cholesky.h b/src/cupynumeric/matrix/batched_cholesky.h similarity index 75% rename from src/cunumeric/matrix/batched_cholesky.h rename to src/cupynumeric/matrix/batched_cholesky.h index 94713beffe..e3d9218ca3 100644 --- a/src/cunumeric/matrix/batched_cholesky.h +++ b/src/cupynumeric/matrix/batched_cholesky.h @@ -16,14 +16,14 @@ #pragma once -#include "cunumeric/cunumeric_task.h" -#include "cunumeric/cunumeric_c.h" +#include "cupynumeric/cupynumeric_task.h" +#include "cupynumeric/cupynumeric_c.h" -namespace cunumeric { +namespace cupynumeric { -class BatchedCholeskyTask : public CuNumericTask { +class BatchedCholeskyTask : public CuPyNumericTask { public: - static constexpr auto TASK_ID = legate::LocalTaskID{CUNUMERIC_BATCHED_CHOLESKY}; + static constexpr auto TASK_ID = legate::LocalTaskID{CUPYNUMERIC_BATCHED_CHOLESKY}; public: static void cpu_variant(legate::TaskContext context); @@ -35,4 +35,4 @@ class BatchedCholeskyTask : public CuNumericTask { #endif }; -} // namespace cunumeric +} // namespace cupynumeric diff --git a/src/cunumeric/matrix/batched_cholesky_omp.cc b/src/cupynumeric/matrix/batched_cholesky_omp.cc similarity index 91% rename from src/cunumeric/matrix/batched_cholesky_omp.cc rename to src/cupynumeric/matrix/batched_cholesky_omp.cc index 0f861f2d8a..2d1872b6c9 100644 --- a/src/cunumeric/matrix/batched_cholesky_omp.cc +++ b/src/cupynumeric/matrix/batched_cholesky_omp.cc @@ -14,15 +14,15 @@ * */ -#include "cunumeric/cunumeric_task.h" -#include "cunumeric/matrix/batched_cholesky.h" -#include "cunumeric/matrix/batched_cholesky_template.inl" +#include "cupynumeric/cupynumeric_task.h" +#include "cupynumeric/matrix/batched_cholesky.h" +#include "cupynumeric/matrix/batched_cholesky_template.inl" #include #include #include -namespace cunumeric { +namespace cupynumeric { using namespace legate; @@ -82,4 +82,4 @@ struct BatchedTransposeImplBody { batched_cholesky_task_context_dispatch(context); } -} // namespace cunumeric +} // namespace cupynumeric diff --git a/src/cunumeric/matrix/batched_cholesky_template.inl b/src/cupynumeric/matrix/batched_cholesky_template.inl similarity index 94% rename from src/cunumeric/matrix/batched_cholesky_template.inl rename to src/cupynumeric/matrix/batched_cholesky_template.inl index 4bf807450b..8b330232f9 100644 --- a/src/cunumeric/matrix/batched_cholesky_template.inl +++ b/src/cupynumeric/matrix/batched_cholesky_template.inl @@ -18,13 +18,13 @@ // Useful for IDEs #include -#include "cunumeric/cunumeric_task.h" -#include "cunumeric/matrix/batched_cholesky.h" -#include "cunumeric/matrix/potrf_template.inl" -#include "cunumeric/matrix/transpose_template.inl" -#include "cunumeric/pitches.h" +#include "cupynumeric/cupynumeric_task.h" +#include "cupynumeric/matrix/batched_cholesky.h" +#include "cupynumeric/matrix/potrf_template.inl" +#include "cupynumeric/matrix/transpose_template.inl" +#include "cupynumeric/pitches.h" -namespace cunumeric { +namespace cupynumeric { using namespace legate; @@ -147,4 +147,4 @@ static void batched_cholesky_task_context_dispatch(TaskContext& context) batched_output); } -} // namespace cunumeric +} // namespace cupynumeric diff --git a/src/cunumeric/matrix/contract.cc b/src/cupynumeric/matrix/contract.cc similarity index 98% rename from src/cunumeric/matrix/contract.cc rename to src/cupynumeric/matrix/contract.cc index cf94c94629..bf58951757 100644 --- a/src/cunumeric/matrix/contract.cc +++ b/src/cupynumeric/matrix/contract.cc @@ -14,13 +14,13 @@ * */ -#include "cunumeric/matrix/contract.h" -#include "cunumeric/matrix/contract_template.inl" -#include "cunumeric/matrix/util.h" +#include "cupynumeric/matrix/contract.h" +#include "cupynumeric/matrix/contract_template.inl" +#include "cupynumeric/matrix/util.h" #include -namespace cunumeric { +namespace cupynumeric { using namespace tblis; @@ -250,4 +250,4 @@ namespace // unnamed static void __attribute__((constructor)) register_tasks(void) { ContractTask::register_variants(); } } // namespace -} // namespace cunumeric +} // namespace cupynumeric diff --git a/src/cunumeric/matrix/contract.cu b/src/cupynumeric/matrix/contract.cu similarity index 98% rename from src/cunumeric/matrix/contract.cu rename to src/cupynumeric/matrix/contract.cu index 48130bce9d..b5d636c661 100644 --- a/src/cunumeric/matrix/contract.cu +++ b/src/cupynumeric/matrix/contract.cu @@ -14,12 +14,12 @@ * */ -#include "cunumeric/matrix/contract.h" -#include "cunumeric/matrix/contract_template.inl" +#include "cupynumeric/matrix/contract.h" +#include "cupynumeric/matrix/contract_template.inl" -#include "cunumeric/cuda_help.h" +#include "cupynumeric/cuda_help.h" -namespace cunumeric { +namespace cupynumeric { namespace { // anonymous @@ -143,7 +143,7 @@ __host__ void contract(T* lhs_data, work_size, task_stream)); - CUNUMERIC_CHECK_CUDA_STREAM(task_stream); + CUPYNUMERIC_CHECK_CUDA_STREAM(task_stream); CHECK_CUTENSOR(cutensorDestroyPlan(plan)); CHECK_CUTENSOR(cutensorDestroyPlanPreference(plan_pref)); @@ -348,4 +348,4 @@ struct ContractImplBody { contract_template(context); } -} // namespace cunumeric +} // namespace cupynumeric diff --git a/src/cunumeric/matrix/contract.h b/src/cupynumeric/matrix/contract.h similarity index 83% rename from src/cunumeric/matrix/contract.h rename to src/cupynumeric/matrix/contract.h index 59be08ba40..25821dbd83 100644 --- a/src/cunumeric/matrix/contract.h +++ b/src/cupynumeric/matrix/contract.h @@ -16,9 +16,9 @@ #pragma once -#include "cunumeric/cunumeric_task.h" +#include "cupynumeric/cupynumeric_task.h" -namespace cunumeric { +namespace cupynumeric { struct ContractArgs { legate::PhysicalStore lhs; @@ -29,9 +29,9 @@ struct ContractArgs { legate::Span rhs2_dim_mask; }; -class ContractTask : public CuNumericTask { +class ContractTask : public CuPyNumericTask { public: - static constexpr auto TASK_ID = legate::LocalTaskID{CUNUMERIC_CONTRACT}; + static constexpr auto TASK_ID = legate::LocalTaskID{CUPYNUMERIC_CONTRACT}; public: static void cpu_variant(legate::TaskContext context); @@ -43,4 +43,4 @@ class ContractTask : public CuNumericTask { #endif }; -} // namespace cunumeric +} // namespace cupynumeric diff --git a/src/cunumeric/matrix/contract_omp.cc b/src/cupynumeric/matrix/contract_omp.cc similarity index 98% rename from src/cunumeric/matrix/contract_omp.cc rename to src/cupynumeric/matrix/contract_omp.cc index 06f914d4e7..9aca0acd9f 100644 --- a/src/cunumeric/matrix/contract_omp.cc +++ b/src/cupynumeric/matrix/contract_omp.cc @@ -14,14 +14,14 @@ * */ -#include "cunumeric/matrix/contract.h" -#include "cunumeric/matrix/contract_template.inl" -#include "cunumeric/matrix/util.h" +#include "cupynumeric/matrix/contract.h" +#include "cupynumeric/matrix/contract_template.inl" +#include "cupynumeric/matrix/util.h" #include #include -namespace cunumeric { +namespace cupynumeric { using namespace tblis; @@ -242,4 +242,4 @@ struct ContractImplBody { contract_template(context); } -} // namespace cunumeric +} // namespace cupynumeric diff --git a/src/cunumeric/matrix/contract_template.inl b/src/cupynumeric/matrix/contract_template.inl similarity index 98% rename from src/cunumeric/matrix/contract_template.inl rename to src/cupynumeric/matrix/contract_template.inl index 107a2d6d6f..4351cc1af4 100644 --- a/src/cunumeric/matrix/contract_template.inl +++ b/src/cupynumeric/matrix/contract_template.inl @@ -17,14 +17,14 @@ #pragma once // Useful for IDEs -#include "cunumeric/matrix/contract.h" +#include "cupynumeric/matrix/contract.h" #if 0 // debugging output #include "legate/utilities/debug.h" #include #endif -namespace cunumeric { +namespace cupynumeric { using namespace legate; @@ -221,7 +221,7 @@ static void contract_template(legate::TaskContext& context) auto dim = args.lhs.dim(); auto code = args.lhs.code(); -#ifdef DEBUG_CUNUMERIC +#ifdef DEBUG_CUPYNUMERIC assert(dim == args.rhs1.dim()); assert(dim == args.rhs2.dim()); assert(dim == static_cast(args.lhs_dim_mask.size())); @@ -234,4 +234,4 @@ static void contract_template(legate::TaskContext& context) double_dispatch(dim, code, ContractImpl{}, args); } -} // namespace cunumeric +} // namespace cupynumeric diff --git a/src/cunumeric/matrix/diag.cc b/src/cupynumeric/matrix/diag.cc similarity index 94% rename from src/cunumeric/matrix/diag.cc rename to src/cupynumeric/matrix/diag.cc index 19b0948f09..fefff3710d 100644 --- a/src/cunumeric/matrix/diag.cc +++ b/src/cupynumeric/matrix/diag.cc @@ -14,10 +14,10 @@ * */ -#include "cunumeric/matrix/diag.h" -#include "cunumeric/matrix/diag_template.inl" +#include "cupynumeric/matrix/diag.h" +#include "cupynumeric/matrix/diag_template.inl" -namespace cunumeric { +namespace cupynumeric { using namespace legate; @@ -82,4 +82,4 @@ namespace // unnamed static void __attribute__((constructor)) register_tasks(void) { DiagTask::register_variants(); } } // namespace -} // namespace cunumeric +} // namespace cupynumeric diff --git a/src/cunumeric/matrix/diag.cu b/src/cupynumeric/matrix/diag.cu similarity index 93% rename from src/cunumeric/matrix/diag.cu rename to src/cupynumeric/matrix/diag.cu index 1532e75d99..eea655de8c 100644 --- a/src/cunumeric/matrix/diag.cu +++ b/src/cupynumeric/matrix/diag.cu @@ -14,11 +14,11 @@ * */ -#include "cunumeric/matrix/diag.h" -#include "cunumeric/matrix/diag_template.inl" -#include "cunumeric/cuda_help.h" +#include "cupynumeric/matrix/diag.h" +#include "cupynumeric/matrix/diag_template.inl" +#include "cupynumeric/cuda_help.h" -namespace cunumeric { +namespace cupynumeric { template __global__ static void __launch_bounds__(THREADS_PER_BLOCK, MIN_CTAS_PER_SM) @@ -93,7 +93,7 @@ struct DiagImplBody { auto stream = get_cached_stream(); diag_extract<<>>( out, in, distance, volume, skip_size, start, naxes, m_pitches, m_shape); - CUNUMERIC_CHECK_CUDA_STREAM(stream); + CUPYNUMERIC_CHECK_CUDA_STREAM(stream); } }; @@ -110,7 +110,7 @@ struct DiagImplBody { const size_t blocks = (distance + THREADS_PER_BLOCK - 1) / THREADS_PER_BLOCK; auto stream = get_cached_stream(); diag_populate<<>>(out, in, distance, start); - CUNUMERIC_CHECK_CUDA_STREAM(stream); + CUPYNUMERIC_CHECK_CUDA_STREAM(stream); } }; @@ -119,4 +119,4 @@ struct DiagImplBody { diag_template(context); } -} // namespace cunumeric +} // namespace cupynumeric diff --git a/src/cunumeric/matrix/diag.h b/src/cupynumeric/matrix/diag.h similarity index 82% rename from src/cunumeric/matrix/diag.h rename to src/cupynumeric/matrix/diag.h index 2a7ef9f752..f80e40f7b3 100644 --- a/src/cunumeric/matrix/diag.h +++ b/src/cupynumeric/matrix/diag.h @@ -16,9 +16,9 @@ #pragma once -#include "cunumeric/cunumeric_task.h" +#include "cupynumeric/cupynumeric_task.h" -namespace cunumeric { +namespace cupynumeric { struct DiagArgs { int naxes; @@ -27,9 +27,9 @@ struct DiagArgs { legate::PhysicalStore diag; }; -class DiagTask : public CuNumericTask { +class DiagTask : public CuPyNumericTask { public: - static constexpr auto TASK_ID = legate::LocalTaskID{CUNUMERIC_DIAG}; + static constexpr auto TASK_ID = legate::LocalTaskID{CUPYNUMERIC_DIAG}; public: static void cpu_variant(legate::TaskContext context); @@ -41,4 +41,4 @@ class DiagTask : public CuNumericTask { #endif }; -} // namespace cunumeric +} // namespace cupynumeric diff --git a/src/cunumeric/matrix/diag_omp.cc b/src/cupynumeric/matrix/diag_omp.cc similarity index 94% rename from src/cunumeric/matrix/diag_omp.cc rename to src/cupynumeric/matrix/diag_omp.cc index 700023296e..c1edbc9271 100644 --- a/src/cunumeric/matrix/diag_omp.cc +++ b/src/cupynumeric/matrix/diag_omp.cc @@ -14,10 +14,10 @@ * */ -#include "cunumeric/matrix/diag.h" -#include "cunumeric/matrix/diag_template.inl" +#include "cupynumeric/matrix/diag.h" +#include "cupynumeric/matrix/diag_template.inl" -namespace cunumeric { +namespace cupynumeric { using namespace legate; @@ -82,4 +82,4 @@ struct DiagImplBody { diag_template(context); } -} // namespace cunumeric +} // namespace cupynumeric diff --git a/src/cunumeric/matrix/diag_template.inl b/src/cupynumeric/matrix/diag_template.inl similarity index 95% rename from src/cunumeric/matrix/diag_template.inl rename to src/cupynumeric/matrix/diag_template.inl index fae129c4d8..72718f93aa 100644 --- a/src/cunumeric/matrix/diag_template.inl +++ b/src/cupynumeric/matrix/diag_template.inl @@ -17,10 +17,10 @@ #pragma once // Useful for IDEs -#include "cunumeric/matrix/diag.h" -#include "cunumeric/pitches.h" +#include "cupynumeric/matrix/diag.h" +#include "cupynumeric/pitches.h" -namespace cunumeric { +namespace cupynumeric { using namespace legate; @@ -82,13 +82,13 @@ struct DiagImpl { const Point<2> stop1(shape.hi[0], shape.hi[0]); // y <= shape.hi[1] const Point<2> stop2(shape.hi[1], shape.hi[1]); -#ifdef DEBUG_CUNUMERIC +#ifdef DEBUG_CUPYNUMERIC assert(shape.contains(stop1) || shape.contains(stop2)); #endif const Point<2> stop = shape.contains(stop1) ? stop1 : stop2; // Walk the path from the stop to the start const coord_t distance = (stop[0] - start[0]) + 1; -#ifdef DEBUG_CUNUMERIC +#ifdef DEBUG_CUPYNUMERIC // Should be the same along both dimensions assert(distance == ((stop[1] - start[1]) + 1)); // no extract is supported only for 1d input array (2d output) @@ -115,4 +115,4 @@ static void diag_template(TaskContext& context) double_dispatch(matrix.dim(), matrix.code(), DiagImpl{}, args); } -} // namespace cunumeric +} // namespace cupynumeric diff --git a/src/cunumeric/matrix/dot.cc b/src/cupynumeric/matrix/dot.cc similarity index 92% rename from src/cunumeric/matrix/dot.cc rename to src/cupynumeric/matrix/dot.cc index 514a269275..7d8c73079e 100644 --- a/src/cunumeric/matrix/dot.cc +++ b/src/cupynumeric/matrix/dot.cc @@ -14,10 +14,10 @@ * */ -#include "cunumeric/matrix/dot.h" -#include "cunumeric/matrix/dot_template.inl" +#include "cupynumeric/matrix/dot.h" +#include "cupynumeric/matrix/dot_template.inl" -namespace cunumeric { +namespace cupynumeric { using namespace legate; @@ -60,4 +60,4 @@ namespace // unnamed static void __attribute__((constructor)) register_tasks(void) { DotTask::register_variants(); } } // namespace -} // namespace cunumeric +} // namespace cupynumeric diff --git a/src/cunumeric/matrix/dot.cu b/src/cupynumeric/matrix/dot.cu similarity index 93% rename from src/cunumeric/matrix/dot.cu rename to src/cupynumeric/matrix/dot.cu index ac498f540c..fd4163559e 100644 --- a/src/cunumeric/matrix/dot.cu +++ b/src/cupynumeric/matrix/dot.cu @@ -14,11 +14,11 @@ * */ -#include "cunumeric/matrix/dot.h" -#include "cunumeric/matrix/dot_template.inl" -#include "cunumeric/cuda_help.h" +#include "cupynumeric/matrix/dot.h" +#include "cupynumeric/matrix/dot_template.inl" +#include "cupynumeric/cuda_help.h" -namespace cunumeric { +namespace cupynumeric { template static __global__ void __launch_bounds__(THREADS_PER_BLOCK, MIN_CTAS_PER_SM) reduction_kernel( @@ -72,7 +72,7 @@ struct DotImplBody { } copy_kernel<<<1, 1, 0, stream>>>(result, out); - CUNUMERIC_CHECK_CUDA_STREAM(stream); + CUPYNUMERIC_CHECK_CUDA_STREAM(stream); } }; @@ -81,4 +81,4 @@ struct DotImplBody { dot_template(context); } -} // namespace cunumeric +} // namespace cupynumeric diff --git a/src/cunumeric/matrix/dot.h b/src/cupynumeric/matrix/dot.h similarity index 82% rename from src/cunumeric/matrix/dot.h rename to src/cupynumeric/matrix/dot.h index 07520bbb6c..a680db3928 100644 --- a/src/cunumeric/matrix/dot.h +++ b/src/cupynumeric/matrix/dot.h @@ -16,9 +16,9 @@ #pragma once -#include "cunumeric/cunumeric_task.h" +#include "cupynumeric/cupynumeric_task.h" -namespace cunumeric { +namespace cupynumeric { struct DotArgs { legate::PhysicalStore lhs; @@ -26,9 +26,9 @@ struct DotArgs { legate::PhysicalStore rhs2; }; -class DotTask : public CuNumericTask { +class DotTask : public CuPyNumericTask { public: - static constexpr auto TASK_ID = legate::LocalTaskID{CUNUMERIC_DOT}; + static constexpr auto TASK_ID = legate::LocalTaskID{CUPYNUMERIC_DOT}; public: static void cpu_variant(legate::TaskContext context); @@ -40,4 +40,4 @@ class DotTask : public CuNumericTask { #endif }; -} // namespace cunumeric +} // namespace cupynumeric diff --git a/src/cunumeric/matrix/dot_omp.cc b/src/cupynumeric/matrix/dot_omp.cc similarity index 93% rename from src/cunumeric/matrix/dot_omp.cc rename to src/cupynumeric/matrix/dot_omp.cc index be11b880a4..f7b8dd1c62 100644 --- a/src/cunumeric/matrix/dot_omp.cc +++ b/src/cupynumeric/matrix/dot_omp.cc @@ -14,13 +14,13 @@ * */ -#include "cunumeric/matrix/dot.h" -#include "cunumeric/matrix/dot_template.inl" -#include "cunumeric/omp_help.h" +#include "cupynumeric/matrix/dot.h" +#include "cupynumeric/matrix/dot_template.inl" +#include "cupynumeric/omp_help.h" #include -namespace cunumeric { +namespace cupynumeric { using namespace legate; @@ -78,4 +78,4 @@ struct DotImplBody { dot_template(context); } -} // namespace cunumeric +} // namespace cupynumeric diff --git a/src/cunumeric/matrix/dot_template.inl b/src/cupynumeric/matrix/dot_template.inl similarity index 95% rename from src/cunumeric/matrix/dot_template.inl rename to src/cupynumeric/matrix/dot_template.inl index 7d24e2afa2..fa4a4e838f 100644 --- a/src/cunumeric/matrix/dot_template.inl +++ b/src/cupynumeric/matrix/dot_template.inl @@ -17,9 +17,9 @@ #pragma once // Useful for IDEs -#include "cunumeric/matrix/dot.h" +#include "cupynumeric/matrix/dot.h" -namespace cunumeric { +namespace cupynumeric { using namespace legate; @@ -79,4 +79,4 @@ static void dot_template(TaskContext& context) type_dispatch(args.rhs1.code(), DotImpl{}, args); } -} // namespace cunumeric +} // namespace cupynumeric diff --git a/src/cunumeric/matrix/gemm.cc b/src/cupynumeric/matrix/gemm.cc similarity index 96% rename from src/cunumeric/matrix/gemm.cc rename to src/cupynumeric/matrix/gemm.cc index e420d3dfd1..bff311f441 100644 --- a/src/cunumeric/matrix/gemm.cc +++ b/src/cupynumeric/matrix/gemm.cc @@ -14,12 +14,12 @@ * */ -#include "cunumeric/matrix/gemm.h" -#include "cunumeric/matrix/gemm_template.inl" +#include "cupynumeric/matrix/gemm.h" +#include "cupynumeric/matrix/gemm_template.inl" #include -namespace cunumeric { +namespace cupynumeric { using namespace legate; @@ -110,4 +110,4 @@ namespace // unnamed static void __attribute__((constructor)) register_tasks(void) { GemmTask::register_variants(); } } // namespace -} // namespace cunumeric +} // namespace cupynumeric diff --git a/src/cunumeric/matrix/gemm.cu b/src/cupynumeric/matrix/gemm.cu similarity index 93% rename from src/cunumeric/matrix/gemm.cu rename to src/cupynumeric/matrix/gemm.cu index 03502e6fc3..cce3c342e7 100644 --- a/src/cunumeric/matrix/gemm.cu +++ b/src/cupynumeric/matrix/gemm.cu @@ -14,12 +14,12 @@ * */ -#include "cunumeric/matrix/gemm.h" -#include "cunumeric/matrix/gemm_template.inl" +#include "cupynumeric/matrix/gemm.h" +#include "cupynumeric/matrix/gemm_template.inl" -#include "cunumeric/cuda_help.h" +#include "cupynumeric/cuda_help.h" -namespace cunumeric { +namespace cupynumeric { using namespace legate; @@ -39,7 +39,7 @@ static inline void gemm_template( CHECK_CUBLAS(gemm(context, transa, transb, m, n, k, &alpha, rhs1, m, rhs2, n, &beta, lhs, m)); - CUNUMERIC_CHECK_CUDA_STREAM(stream); + CUPYNUMERIC_CHECK_CUDA_STREAM(stream); } template @@ -58,7 +58,7 @@ static inline void complex_gemm_template( CHECK_CUBLAS(gemm(context, transa, transb, m, n, k, &alpha, rhs1, m, rhs2, n, &beta, lhs, m)); - CUNUMERIC_CHECK_CUDA_STREAM(stream); + CUPYNUMERIC_CHECK_CUDA_STREAM(stream); } template <> @@ -117,4 +117,4 @@ struct GemmImplBody { gemm_template(context); } -} // namespace cunumeric +} // namespace cupynumeric diff --git a/src/cunumeric/matrix/gemm.h b/src/cupynumeric/matrix/gemm.h similarity index 80% rename from src/cunumeric/matrix/gemm.h rename to src/cupynumeric/matrix/gemm.h index c0c7a53b74..d66164bb2c 100644 --- a/src/cunumeric/matrix/gemm.h +++ b/src/cupynumeric/matrix/gemm.h @@ -16,13 +16,13 @@ #pragma once -#include "cunumeric/cunumeric_task.h" +#include "cupynumeric/cupynumeric_task.h" -namespace cunumeric { +namespace cupynumeric { -class GemmTask : public CuNumericTask { +class GemmTask : public CuPyNumericTask { public: - static constexpr auto TASK_ID = legate::LocalTaskID{CUNUMERIC_GEMM}; + static constexpr auto TASK_ID = legate::LocalTaskID{CUPYNUMERIC_GEMM}; public: static void cpu_variant(legate::TaskContext context); @@ -34,4 +34,4 @@ class GemmTask : public CuNumericTask { #endif }; -} // namespace cunumeric +} // namespace cupynumeric diff --git a/src/cunumeric/matrix/gemm_omp.cc b/src/cupynumeric/matrix/gemm_omp.cc similarity index 95% rename from src/cunumeric/matrix/gemm_omp.cc rename to src/cupynumeric/matrix/gemm_omp.cc index 80f21e1639..45cefdfd31 100644 --- a/src/cunumeric/matrix/gemm_omp.cc +++ b/src/cupynumeric/matrix/gemm_omp.cc @@ -14,13 +14,13 @@ * */ -#include "cunumeric/matrix/gemm.h" -#include "cunumeric/matrix/gemm_template.inl" +#include "cupynumeric/matrix/gemm.h" +#include "cupynumeric/matrix/gemm_template.inl" #include #include -namespace cunumeric { +namespace cupynumeric { using namespace legate; @@ -104,4 +104,4 @@ struct GemmImplBody { gemm_template(context); } -} // namespace cunumeric +} // namespace cupynumeric diff --git a/src/cunumeric/matrix/gemm_template.inl b/src/cupynumeric/matrix/gemm_template.inl similarity index 97% rename from src/cunumeric/matrix/gemm_template.inl rename to src/cupynumeric/matrix/gemm_template.inl index c3ffa0967d..e96334eefe 100644 --- a/src/cunumeric/matrix/gemm_template.inl +++ b/src/cupynumeric/matrix/gemm_template.inl @@ -17,9 +17,9 @@ #pragma once // Useful for IDEs -#include "cunumeric/matrix/gemm.h" +#include "cupynumeric/matrix/gemm.h" -namespace cunumeric { +namespace cupynumeric { using namespace legate; @@ -93,4 +93,4 @@ static void gemm_template(TaskContext& context) type_dispatch(lhs.type().code(), GemmImpl{}, lhs, rhs1, rhs2); } -} // namespace cunumeric +} // namespace cupynumeric diff --git a/src/cunumeric/matrix/matmul.cc b/src/cupynumeric/matrix/matmul.cc similarity index 85% rename from src/cunumeric/matrix/matmul.cc rename to src/cupynumeric/matrix/matmul.cc index 9ab62c5727..5d70282d23 100644 --- a/src/cunumeric/matrix/matmul.cc +++ b/src/cupynumeric/matrix/matmul.cc @@ -14,16 +14,16 @@ * */ -#include "cunumeric/matrix/matmul.h" -#include "cunumeric/matrix/matmul_template.inl" -#include "cunumeric/matrix/matmul_cpu.inl" +#include "cupynumeric/matrix/matmul.h" +#include "cupynumeric/matrix/matmul_template.inl" +#include "cupynumeric/matrix/matmul_cpu.inl" #include #if LEGATE_DEFINED(LEGATE_USE_OPENMP) #include #endif -namespace cunumeric { +namespace cupynumeric { using namespace legate; @@ -40,4 +40,4 @@ namespace // unnamed static void __attribute__((constructor)) register_tasks(void) { MatMulTask::register_variants(); } } // namespace -} // namespace cunumeric +} // namespace cupynumeric diff --git a/src/cunumeric/matrix/matmul.cu b/src/cupynumeric/matrix/matmul.cu similarity index 95% rename from src/cunumeric/matrix/matmul.cu rename to src/cupynumeric/matrix/matmul.cu index e11c51909f..00a52d6a12 100644 --- a/src/cunumeric/matrix/matmul.cu +++ b/src/cupynumeric/matrix/matmul.cu @@ -14,12 +14,12 @@ * */ -#include "cunumeric/matrix/matmul.h" -#include "cunumeric/matrix/matmul_template.inl" +#include "cupynumeric/matrix/matmul.h" +#include "cupynumeric/matrix/matmul_template.inl" -#include "cunumeric/cuda_help.h" +#include "cupynumeric/cuda_help.h" -namespace cunumeric { +namespace cupynumeric { // NOTE: // cuBLAS doesn't support row-major, so reverse the matrix order so it thinks things are @@ -68,7 +68,7 @@ struct MatMulImplBody { CUDA_R_32F, lhs_stride)); - CUNUMERIC_CHECK_CUDA_STREAM(task_stream); + CUPYNUMERIC_CHECK_CUDA_STREAM(task_stream); } }; @@ -109,7 +109,7 @@ struct MatMulImplBody { lhs, lhs_stride)); - CUNUMERIC_CHECK_CUDA_STREAM(task_stream); + CUPYNUMERIC_CHECK_CUDA_STREAM(task_stream); } }; @@ -153,7 +153,7 @@ struct MatMulImplBody { CUDA_R_32F, lhs_stride)); - CUNUMERIC_CHECK_CUDA_STREAM(task_stream); + CUPYNUMERIC_CHECK_CUDA_STREAM(task_stream); } }; @@ -201,7 +201,7 @@ struct MatMulImplBody { CUDA_C_32F, lhs_stride)); - CUNUMERIC_CHECK_CUDA_STREAM(task_stream); + CUPYNUMERIC_CHECK_CUDA_STREAM(task_stream); } }; @@ -246,7 +246,7 @@ struct MatMulImplBody { lhs, lhs_stride)); - CUNUMERIC_CHECK_CUDA_STREAM(task_stream); + CUPYNUMERIC_CHECK_CUDA_STREAM(task_stream); } }; @@ -255,4 +255,4 @@ struct MatMulImplBody { matmul_template(context); } -} // namespace cunumeric +} // namespace cupynumeric diff --git a/src/cunumeric/matrix/matmul.h b/src/cupynumeric/matrix/matmul.h similarity index 81% rename from src/cunumeric/matrix/matmul.h rename to src/cupynumeric/matrix/matmul.h index 0420d8045e..c5fc98340e 100644 --- a/src/cunumeric/matrix/matmul.h +++ b/src/cupynumeric/matrix/matmul.h @@ -16,9 +16,9 @@ #pragma once -#include "cunumeric/cunumeric_task.h" +#include "cupynumeric/cupynumeric_task.h" -namespace cunumeric { +namespace cupynumeric { struct MatMulArgs { legate::PhysicalStore lhs; @@ -26,9 +26,9 @@ struct MatMulArgs { legate::PhysicalStore rhs2; }; -class MatMulTask : public CuNumericTask { +class MatMulTask : public CuPyNumericTask { public: - static constexpr auto TASK_ID = legate::LocalTaskID{CUNUMERIC_MATMUL}; + static constexpr auto TASK_ID = legate::LocalTaskID{CUPYNUMERIC_MATMUL}; public: static void cpu_variant(legate::TaskContext context); @@ -40,4 +40,4 @@ class MatMulTask : public CuNumericTask { #endif }; -} // namespace cunumeric +} // namespace cupynumeric diff --git a/src/cunumeric/matrix/matmul_cpu.inl b/src/cupynumeric/matrix/matmul_cpu.inl similarity index 97% rename from src/cunumeric/matrix/matmul_cpu.inl rename to src/cupynumeric/matrix/matmul_cpu.inl index 575e0c6e9f..4bfb039ee6 100644 --- a/src/cunumeric/matrix/matmul_cpu.inl +++ b/src/cupynumeric/matrix/matmul_cpu.inl @@ -16,13 +16,13 @@ #pragma once -#include "cunumeric/matrix/matmul.h" -#include "cunumeric/matrix/matmul_template.inl" -#include "cunumeric/matrix/util.h" +#include "cupynumeric/matrix/matmul.h" +#include "cupynumeric/matrix/matmul_template.inl" +#include "cupynumeric/matrix/util.h" #include -namespace cunumeric { +namespace cupynumeric { using namespace Legion; using namespace legate; @@ -215,4 +215,4 @@ struct MatMulImplBody { } }; -} // namespace cunumeric +} // namespace cupynumeric diff --git a/src/cunumeric/matrix/matmul_omp.cc b/src/cupynumeric/matrix/matmul_omp.cc similarity index 81% rename from src/cunumeric/matrix/matmul_omp.cc rename to src/cupynumeric/matrix/matmul_omp.cc index 3e9bdfbcb7..ceebbb0074 100644 --- a/src/cunumeric/matrix/matmul_omp.cc +++ b/src/cupynumeric/matrix/matmul_omp.cc @@ -14,13 +14,13 @@ * */ -#include "cunumeric/matrix/matmul.h" -#include "cunumeric/matrix/matmul_template.inl" -#include "cunumeric/matrix/matmul_cpu.inl" +#include "cupynumeric/matrix/matmul.h" +#include "cupynumeric/matrix/matmul_template.inl" +#include "cupynumeric/matrix/matmul_cpu.inl" #include -namespace cunumeric { +namespace cupynumeric { using namespace legate; @@ -30,4 +30,4 @@ using namespace legate; matmul_template(context); } -} // namespace cunumeric +} // namespace cupynumeric diff --git a/src/cunumeric/matrix/matmul_template.inl b/src/cupynumeric/matrix/matmul_template.inl similarity index 95% rename from src/cunumeric/matrix/matmul_template.inl rename to src/cupynumeric/matrix/matmul_template.inl index 2b041f923c..adffe16928 100644 --- a/src/cunumeric/matrix/matmul_template.inl +++ b/src/cupynumeric/matrix/matmul_template.inl @@ -17,10 +17,10 @@ #pragma once // Useful for IDEs -#include "cunumeric/matrix/matmul.h" -#include "cunumeric/matrix/util.h" +#include "cupynumeric/matrix/matmul.h" +#include "cupynumeric/matrix/util.h" -namespace cunumeric { +namespace cupynumeric { using namespace legate; @@ -70,7 +70,7 @@ struct MatMulImpl { const auto n = shape_lhs.hi[1] - shape_lhs.lo[1] + 1; const auto k = shape_rhs1.hi[1] - shape_rhs1.lo[1] + 1; -#ifdef DEBUG_CUNUMERIC +#ifdef DEBUG_CUPYNUMERIC assert(m == shape_rhs1.hi[0] - shape_rhs1.lo[0] + 1); assert(k == shape_rhs2.hi[0] - shape_rhs2.lo[0] + 1); assert(n == shape_rhs2.hi[1] - shape_rhs2.lo[1] + 1); @@ -84,7 +84,7 @@ struct MatMulImpl { auto rhs2 = args.rhs2.read_accessor(shape_rhs2).ptr(shape_rhs2, strides_rhs2); auto lhs = args.lhs.read_write_accessor(shape_lhs).ptr(shape_lhs, strides_lhs); -#ifdef DEBUG_CUNUMERIC +#ifdef DEBUG_CUPYNUMERIC assert(strides_rhs1[0] == 1 || strides_rhs1[1] == 1); assert(strides_rhs2[0] == 1 || strides_rhs2[1] == 1); assert(strides_lhs[1] == 1); @@ -128,4 +128,4 @@ static void matmul_template(TaskContext& context) type_dispatch(args.rhs1.code(), MatMulImpl{}, args); } -} // namespace cunumeric +} // namespace cupynumeric diff --git a/src/cunumeric/matrix/matvecmul.cc b/src/cupynumeric/matrix/matvecmul.cc similarity index 84% rename from src/cunumeric/matrix/matvecmul.cc rename to src/cupynumeric/matrix/matvecmul.cc index 214624489b..8e7ede3e7c 100644 --- a/src/cunumeric/matrix/matvecmul.cc +++ b/src/cupynumeric/matrix/matvecmul.cc @@ -14,16 +14,16 @@ * */ -#include "cunumeric/matrix/matvecmul.h" -#include "cunumeric/matrix/matvecmul_template.inl" -#include "cunumeric/matrix/matvecmul_cpu.inl" +#include "cupynumeric/matrix/matvecmul.h" +#include "cupynumeric/matrix/matvecmul_template.inl" +#include "cupynumeric/matrix/matvecmul_cpu.inl" #include #if LEGATE_DEFINED(LEGATE_USE_OPENMP) #include #endif -namespace cunumeric { +namespace cupynumeric { using namespace legate; @@ -43,4 +43,4 @@ static void __attribute__((constructor)) register_tasks(void) } } // namespace -} // namespace cunumeric +} // namespace cupynumeric diff --git a/src/cunumeric/matrix/matvecmul.cu b/src/cupynumeric/matrix/matvecmul.cu similarity index 95% rename from src/cunumeric/matrix/matvecmul.cu rename to src/cupynumeric/matrix/matvecmul.cu index d7ce65a721..f85b350826 100644 --- a/src/cunumeric/matrix/matvecmul.cu +++ b/src/cupynumeric/matrix/matvecmul.cu @@ -14,12 +14,12 @@ * */ -#include "cunumeric/matrix/matvecmul.h" -#include "cunumeric/matrix/matvecmul_template.inl" +#include "cupynumeric/matrix/matvecmul.h" +#include "cupynumeric/matrix/matvecmul_template.inl" -#include "cunumeric/cuda_help.h" +#include "cupynumeric/cuda_help.h" -namespace cunumeric { +namespace cupynumeric { template <> struct MatVecMulImplBody { @@ -71,7 +71,7 @@ struct MatVecMulImplBody { transpose_mat ? n : m)); } - CUNUMERIC_CHECK_CUDA_STREAM(task_stream); + CUPYNUMERIC_CHECK_CUDA_STREAM(task_stream); } }; @@ -119,7 +119,7 @@ struct MatVecMulImplBody { transpose_mat ? n : m)); } - CUNUMERIC_CHECK_CUDA_STREAM(task_stream); + CUPYNUMERIC_CHECK_CUDA_STREAM(task_stream); } }; @@ -161,7 +161,7 @@ struct MatVecMulImplBody { CUDA_R_32F, transpose_mat ? n : m)); - CUNUMERIC_CHECK_CUDA_STREAM(task_stream); + CUPYNUMERIC_CHECK_CUDA_STREAM(task_stream); } }; @@ -216,7 +216,7 @@ struct MatVecMulImplBody { transpose_mat ? n : m)); } - CUNUMERIC_CHECK_CUDA_STREAM(task_stream); + CUPYNUMERIC_CHECK_CUDA_STREAM(task_stream); } }; @@ -268,7 +268,7 @@ struct MatVecMulImplBody { transpose_mat ? n : m)); } - CUNUMERIC_CHECK_CUDA_STREAM(task_stream); + CUPYNUMERIC_CHECK_CUDA_STREAM(task_stream); } }; @@ -277,4 +277,4 @@ struct MatVecMulImplBody { matvecmul_template(context); } -} // namespace cunumeric +} // namespace cupynumeric diff --git a/src/cunumeric/matrix/matvecmul.h b/src/cupynumeric/matrix/matvecmul.h similarity index 81% rename from src/cunumeric/matrix/matvecmul.h rename to src/cupynumeric/matrix/matvecmul.h index c012ab1910..f53073ad59 100644 --- a/src/cunumeric/matrix/matvecmul.h +++ b/src/cupynumeric/matrix/matvecmul.h @@ -16,9 +16,9 @@ #pragma once -#include "cunumeric/cunumeric_task.h" +#include "cupynumeric/cupynumeric_task.h" -namespace cunumeric { +namespace cupynumeric { struct MatVecMulArgs { legate::PhysicalStore lhs; @@ -26,9 +26,9 @@ struct MatVecMulArgs { legate::PhysicalStore rhs2; }; -class MatVecMulTask : public CuNumericTask { +class MatVecMulTask : public CuPyNumericTask { public: - static constexpr auto TASK_ID = legate::LocalTaskID{CUNUMERIC_MATVECMUL}; + static constexpr auto TASK_ID = legate::LocalTaskID{CUPYNUMERIC_MATVECMUL}; public: static void cpu_variant(legate::TaskContext context); @@ -40,4 +40,4 @@ class MatVecMulTask : public CuNumericTask { #endif }; -} // namespace cunumeric +} // namespace cupynumeric diff --git a/src/cunumeric/matrix/matvecmul_cpu.inl b/src/cupynumeric/matrix/matvecmul_cpu.inl similarity index 96% rename from src/cunumeric/matrix/matvecmul_cpu.inl rename to src/cupynumeric/matrix/matvecmul_cpu.inl index 5879d93701..977292433b 100644 --- a/src/cunumeric/matrix/matvecmul_cpu.inl +++ b/src/cupynumeric/matrix/matvecmul_cpu.inl @@ -16,13 +16,13 @@ #pragma once -#include "cunumeric/matrix/matvecmul.h" -#include "cunumeric/matrix/matvecmul_template.inl" -#include "cunumeric/matrix/util.h" +#include "cupynumeric/matrix/matvecmul.h" +#include "cupynumeric/matrix/matvecmul_template.inl" +#include "cupynumeric/matrix/util.h" #include -namespace cunumeric { +namespace cupynumeric { using namespace Legion; using namespace legate; @@ -130,4 +130,4 @@ struct MatVecMulImplBody { } }; -} // namespace cunumeric +} // namespace cupynumeric diff --git a/src/cunumeric/matrix/matvecmul_omp.cc b/src/cupynumeric/matrix/matvecmul_omp.cc similarity index 81% rename from src/cunumeric/matrix/matvecmul_omp.cc rename to src/cupynumeric/matrix/matvecmul_omp.cc index ff332d95ca..7470ea6d1f 100644 --- a/src/cunumeric/matrix/matvecmul_omp.cc +++ b/src/cupynumeric/matrix/matvecmul_omp.cc @@ -14,14 +14,14 @@ * */ -#include "cunumeric/matrix/matvecmul.h" -#include "cunumeric/matrix/matvecmul_template.inl" -#include "cunumeric/matrix/matvecmul_cpu.inl" +#include "cupynumeric/matrix/matvecmul.h" +#include "cupynumeric/matrix/matvecmul_template.inl" +#include "cupynumeric/matrix/matvecmul_cpu.inl" #include #include -namespace cunumeric { +namespace cupynumeric { using namespace legate; @@ -31,4 +31,4 @@ using namespace legate; matvecmul_template(context); } -} // namespace cunumeric +} // namespace cupynumeric diff --git a/src/cunumeric/matrix/matvecmul_template.inl b/src/cupynumeric/matrix/matvecmul_template.inl similarity index 95% rename from src/cunumeric/matrix/matvecmul_template.inl rename to src/cupynumeric/matrix/matvecmul_template.inl index 819b3da809..f8583a87f1 100644 --- a/src/cunumeric/matrix/matvecmul_template.inl +++ b/src/cupynumeric/matrix/matvecmul_template.inl @@ -17,10 +17,10 @@ #pragma once // Useful for IDEs -#include "cunumeric/matrix/matvecmul.h" -#include "cunumeric/matrix/util.h" +#include "cupynumeric/matrix/matvecmul.h" +#include "cupynumeric/matrix/util.h" -namespace cunumeric { +namespace cupynumeric { using namespace legate; @@ -81,7 +81,7 @@ struct MatVecMulImpl { size_t lhs_strides[2]; auto lhs = args.lhs.reduce_accessor, true, 2>().ptr(shape, lhs_strides); -#ifdef DEBUG_CUNUMERIC +#ifdef DEBUG_CUPYNUMERIC assert(vec_strides[0] == 0 && vec_strides[1] == 1); assert(lhs_strides[0] == 1 && lhs_strides[1] == 0); #endif @@ -109,4 +109,4 @@ static void matvecmul_template(TaskContext& context) type_dispatch(args.rhs1.code(), MatVecMulImpl{}, args); } -} // namespace cunumeric +} // namespace cupynumeric diff --git a/src/cunumeric/matrix/mp_potrf.cu b/src/cupynumeric/matrix/mp_potrf.cu similarity index 95% rename from src/cunumeric/matrix/mp_potrf.cu rename to src/cupynumeric/matrix/mp_potrf.cu index aa55691033..365c2d73ec 100644 --- a/src/cunumeric/matrix/mp_potrf.cu +++ b/src/cupynumeric/matrix/mp_potrf.cu @@ -14,12 +14,12 @@ * */ -#include "cunumeric/matrix/mp_potrf.h" -#include "cunumeric/matrix/mp_potrf_template.inl" +#include "cupynumeric/matrix/mp_potrf.h" +#include "cupynumeric/matrix/mp_potrf_template.inl" -#include "cunumeric/cuda_help.h" +#include "cupynumeric/cuda_help.h" -namespace cunumeric { +namespace cupynumeric { using namespace Legion; using namespace legate; @@ -77,7 +77,7 @@ static inline void mp_potrf_template( // TODO: We need a deferred exception to avoid this synchronization CHECK_CAL(cal_stream_sync(comm, stream)); - CUNUMERIC_CHECK_CUDA_STREAM(stream); + CUPYNUMERIC_CHECK_CUDA_STREAM(stream); CHECK_CUSOLVER(cusolverMpDestroyMatrixDesc(desc)); CHECK_CUSOLVER(cusolverMpDestroyGrid(grid)); @@ -143,4 +143,4 @@ namespace // unnamed static void __attribute__((constructor)) register_tasks(void) { MpPotrfTask::register_variants(); } } // namespace -} // namespace cunumeric \ No newline at end of file +} // namespace cupynumeric \ No newline at end of file diff --git a/src/cunumeric/matrix/mp_potrf.h b/src/cupynumeric/matrix/mp_potrf.h similarity index 76% rename from src/cunumeric/matrix/mp_potrf.h rename to src/cupynumeric/matrix/mp_potrf.h index 0c03021ecc..dce7e38767 100644 --- a/src/cunumeric/matrix/mp_potrf.h +++ b/src/cupynumeric/matrix/mp_potrf.h @@ -16,13 +16,13 @@ #pragma once -#include "cunumeric/cunumeric_task.h" +#include "cupynumeric/cupynumeric_task.h" -namespace cunumeric { +namespace cupynumeric { -class MpPotrfTask : public CuNumericTask { +class MpPotrfTask : public CuPyNumericTask { public: - static constexpr auto TASK_ID = legate::LocalTaskID{CUNUMERIC_MP_POTRF}; + static constexpr auto TASK_ID = legate::LocalTaskID{CUPYNUMERIC_MP_POTRF}; public: #if LEGATE_DEFINED(LEGATE_USE_CUDA) @@ -30,4 +30,4 @@ class MpPotrfTask : public CuNumericTask { #endif }; -} // namespace cunumeric \ No newline at end of file +} // namespace cupynumeric \ No newline at end of file diff --git a/src/cunumeric/matrix/mp_potrf_template.inl b/src/cupynumeric/matrix/mp_potrf_template.inl similarity index 96% rename from src/cunumeric/matrix/mp_potrf_template.inl rename to src/cupynumeric/matrix/mp_potrf_template.inl index 521dfa2c56..1c2133d761 100644 --- a/src/cunumeric/matrix/mp_potrf_template.inl +++ b/src/cupynumeric/matrix/mp_potrf_template.inl @@ -21,13 +21,13 @@ #include "legate/comm/coll.h" // Useful for IDEs -#include "cunumeric/matrix/mp_potrf.h" -#include "cunumeric/cuda_help.h" -#include "cunumeric/utilities/repartition.h" +#include "cupynumeric/matrix/mp_potrf.h" +#include "cupynumeric/cuda_help.h" +#include "cupynumeric/utilities/repartition.h" #include -namespace cunumeric { +namespace cupynumeric { using namespace Legion; using namespace legate; @@ -164,4 +164,4 @@ static void mp_potrf_template(TaskContext& context) context.get_launch_domain()); } -} // namespace cunumeric +} // namespace cupynumeric diff --git a/src/cunumeric/matrix/mp_solve.cu b/src/cupynumeric/matrix/mp_solve.cu similarity index 97% rename from src/cunumeric/matrix/mp_solve.cu rename to src/cupynumeric/matrix/mp_solve.cu index 0e2670c897..3ca290a20c 100644 --- a/src/cunumeric/matrix/mp_solve.cu +++ b/src/cupynumeric/matrix/mp_solve.cu @@ -14,12 +14,12 @@ * */ -#include "cunumeric/matrix/mp_solve.h" -#include "cunumeric/matrix/mp_solve_template.inl" +#include "cupynumeric/matrix/mp_solve.h" +#include "cupynumeric/matrix/mp_solve_template.inl" -#include "cunumeric/cuda_help.h" +#include "cupynumeric/cuda_help.h" -namespace cunumeric { +namespace cupynumeric { using namespace Legion; using namespace legate; @@ -136,7 +136,7 @@ static inline void mp_solve_template(cal_comm_t comm, // TODO: We need a deferred exception to avoid this synchronization CHECK_CAL(cal_stream_sync(comm, stream)); - CUNUMERIC_CHECK_CUDA_STREAM(stream); + CUPYNUMERIC_CHECK_CUDA_STREAM(stream); CHECK_CUSOLVER(cusolverMpDestroyMatrixDesc(a_desc)); CHECK_CUSOLVER(cusolverMpDestroyMatrixDesc(b_desc)); @@ -244,4 +244,4 @@ namespace // unnamed static void __attribute__((constructor)) register_tasks(void) { MpSolveTask::register_variants(); } } // namespace -} // namespace cunumeric \ No newline at end of file +} // namespace cupynumeric \ No newline at end of file diff --git a/src/cunumeric/matrix/mp_solve.h b/src/cupynumeric/matrix/mp_solve.h similarity index 76% rename from src/cunumeric/matrix/mp_solve.h rename to src/cupynumeric/matrix/mp_solve.h index e329f95e87..436c4c50be 100644 --- a/src/cunumeric/matrix/mp_solve.h +++ b/src/cupynumeric/matrix/mp_solve.h @@ -16,13 +16,13 @@ #pragma once -#include "cunumeric/cunumeric_task.h" +#include "cupynumeric/cupynumeric_task.h" -namespace cunumeric { +namespace cupynumeric { -class MpSolveTask : public CuNumericTask { +class MpSolveTask : public CuPyNumericTask { public: - static constexpr auto TASK_ID = legate::LocalTaskID{CUNUMERIC_MP_SOLVE}; + static constexpr auto TASK_ID = legate::LocalTaskID{CUPYNUMERIC_MP_SOLVE}; public: #if LEGATE_DEFINED(LEGATE_USE_CUDA) @@ -30,4 +30,4 @@ class MpSolveTask : public CuNumericTask { #endif }; -} // namespace cunumeric \ No newline at end of file +} // namespace cupynumeric \ No newline at end of file diff --git a/src/cunumeric/matrix/mp_solve_template.inl b/src/cupynumeric/matrix/mp_solve_template.inl similarity index 97% rename from src/cunumeric/matrix/mp_solve_template.inl rename to src/cupynumeric/matrix/mp_solve_template.inl index b2e0a6739c..573e90db32 100644 --- a/src/cunumeric/matrix/mp_solve_template.inl +++ b/src/cupynumeric/matrix/mp_solve_template.inl @@ -21,13 +21,13 @@ #include "legate/comm/coll.h" // Useful for IDEs -#include "cunumeric/matrix/mp_solve.h" -#include "cunumeric/cuda_help.h" -#include "cunumeric/utilities/repartition.h" +#include "cupynumeric/matrix/mp_solve.h" +#include "cupynumeric/cuda_help.h" +#include "cupynumeric/utilities/repartition.h" #include -namespace cunumeric { +namespace cupynumeric { using namespace Legion; using namespace legate; @@ -192,4 +192,4 @@ static void mp_solve_template(TaskContext& context) context.get_launch_domain()); } -} // namespace cunumeric +} // namespace cupynumeric diff --git a/src/cunumeric/matrix/potrf.cc b/src/cupynumeric/matrix/potrf.cc similarity index 95% rename from src/cunumeric/matrix/potrf.cc rename to src/cupynumeric/matrix/potrf.cc index bd2f7af5ee..ee9d7b9971 100644 --- a/src/cunumeric/matrix/potrf.cc +++ b/src/cupynumeric/matrix/potrf.cc @@ -14,13 +14,13 @@ * */ -#include "cunumeric/matrix/potrf.h" -#include "cunumeric/matrix/potrf_template.inl" +#include "cupynumeric/matrix/potrf.h" +#include "cupynumeric/matrix/potrf_template.inl" #include #include -namespace cunumeric { +namespace cupynumeric { using namespace legate; @@ -89,4 +89,4 @@ namespace // unnamed static void __attribute__((constructor)) register_tasks(void) { PotrfTask::register_variants(); } } // namespace -} // namespace cunumeric +} // namespace cupynumeric diff --git a/src/cunumeric/matrix/potrf.cu b/src/cupynumeric/matrix/potrf.cu similarity index 91% rename from src/cunumeric/matrix/potrf.cu rename to src/cupynumeric/matrix/potrf.cu index bdae5cd1c2..d8a6016c84 100644 --- a/src/cunumeric/matrix/potrf.cu +++ b/src/cupynumeric/matrix/potrf.cu @@ -14,12 +14,12 @@ * */ -#include "cunumeric/matrix/potrf.h" -#include "cunumeric/matrix/potrf_template.inl" +#include "cupynumeric/matrix/potrf.h" +#include "cupynumeric/matrix/potrf_template.inl" -#include "cunumeric/cuda_help.h" +#include "cupynumeric/cuda_help.h" -namespace cunumeric { +namespace cupynumeric { using namespace legate; @@ -42,8 +42,8 @@ static inline void potrf_template( CHECK_CUSOLVER(potrf(context, uplo, n, array, m, buffer.ptr(0), bufferSize, info.ptr(0))); // TODO: We need a deferred exception to avoid this synchronization - CUNUMERIC_CHECK_CUDA(cudaStreamSynchronize(stream)); - CUNUMERIC_CHECK_CUDA_STREAM(stream); + CUPYNUMERIC_CHECK_CUDA(cudaStreamSynchronize(stream)); + CUPYNUMERIC_CHECK_CUDA_STREAM(stream); if (info[0] != 0) { throw legate::TaskException("Matrix is not positive definite"); @@ -89,4 +89,4 @@ void PotrfImplBody::operator()(complex potrf_template(context); } -} // namespace cunumeric +} // namespace cupynumeric diff --git a/src/cunumeric/matrix/potrf.h b/src/cupynumeric/matrix/potrf.h similarity index 80% rename from src/cunumeric/matrix/potrf.h rename to src/cupynumeric/matrix/potrf.h index d2928df9fc..4d3f4b4211 100644 --- a/src/cunumeric/matrix/potrf.h +++ b/src/cupynumeric/matrix/potrf.h @@ -16,13 +16,13 @@ #pragma once -#include "cunumeric/cunumeric_task.h" +#include "cupynumeric/cupynumeric_task.h" -namespace cunumeric { +namespace cupynumeric { -class PotrfTask : public CuNumericTask { +class PotrfTask : public CuPyNumericTask { public: - static constexpr auto TASK_ID = legate::LocalTaskID{CUNUMERIC_POTRF}; + static constexpr auto TASK_ID = legate::LocalTaskID{CUPYNUMERIC_POTRF}; public: static void cpu_variant(legate::TaskContext context); @@ -34,4 +34,4 @@ class PotrfTask : public CuNumericTask { #endif }; -} // namespace cunumeric +} // namespace cupynumeric diff --git a/src/cunumeric/matrix/potrf_omp.cc b/src/cupynumeric/matrix/potrf_omp.cc similarity index 95% rename from src/cunumeric/matrix/potrf_omp.cc rename to src/cupynumeric/matrix/potrf_omp.cc index e91d55429a..6031e50f04 100644 --- a/src/cunumeric/matrix/potrf_omp.cc +++ b/src/cupynumeric/matrix/potrf_omp.cc @@ -14,14 +14,14 @@ * */ -#include "cunumeric/matrix/potrf.h" -#include "cunumeric/matrix/potrf_template.inl" +#include "cupynumeric/matrix/potrf.h" +#include "cupynumeric/matrix/potrf_template.inl" #include #include #include -namespace cunumeric { +namespace cupynumeric { using namespace legate; @@ -83,4 +83,4 @@ void PotrfImplBody::operator()(complex potrf_template(context); } -} // namespace cunumeric +} // namespace cupynumeric diff --git a/src/cunumeric/matrix/potrf_template.inl b/src/cupynumeric/matrix/potrf_template.inl similarity index 96% rename from src/cunumeric/matrix/potrf_template.inl rename to src/cupynumeric/matrix/potrf_template.inl index b8fc45d703..4728e47861 100644 --- a/src/cunumeric/matrix/potrf_template.inl +++ b/src/cupynumeric/matrix/potrf_template.inl @@ -17,9 +17,9 @@ #pragma once // Useful for IDEs -#include "cunumeric/matrix/potrf.h" +#include "cupynumeric/matrix/potrf.h" -namespace cunumeric { +namespace cupynumeric { using namespace legate; @@ -94,4 +94,4 @@ static void potrf_template(TaskContext& context) type_dispatch(array.type().code(), PotrfImpl{}, array); } -} // namespace cunumeric +} // namespace cupynumeric diff --git a/src/cunumeric/matrix/qr.cc b/src/cupynumeric/matrix/qr.cc similarity index 85% rename from src/cunumeric/matrix/qr.cc rename to src/cupynumeric/matrix/qr.cc index 9d5881ab68..111b932391 100644 --- a/src/cunumeric/matrix/qr.cc +++ b/src/cupynumeric/matrix/qr.cc @@ -14,11 +14,11 @@ * */ -#include "cunumeric/matrix/qr.h" -#include "cunumeric/matrix/qr_template.inl" -#include "cunumeric/matrix/qr_cpu.inl" +#include "cupynumeric/matrix/qr.h" +#include "cupynumeric/matrix/qr_template.inl" +#include "cupynumeric/matrix/qr_cpu.inl" -namespace cunumeric { +namespace cupynumeric { using namespace legate; @@ -37,4 +37,4 @@ namespace // unnamed static void __attribute__((constructor)) register_tasks(void) { QrTask::register_variants(); } } // namespace -} // namespace cunumeric +} // namespace cupynumeric diff --git a/src/cunumeric/matrix/qr.cu b/src/cupynumeric/matrix/qr.cu similarity index 90% rename from src/cunumeric/matrix/qr.cu rename to src/cupynumeric/matrix/qr.cu index 7505565351..2b0f9c7d7e 100644 --- a/src/cunumeric/matrix/qr.cu +++ b/src/cupynumeric/matrix/qr.cu @@ -14,12 +14,12 @@ * */ -#include "cunumeric/matrix/qr.h" -#include "cunumeric/matrix/qr_template.inl" +#include "cupynumeric/matrix/qr.h" +#include "cupynumeric/matrix/qr_template.inl" -#include "cunumeric/cuda_help.h" +#include "cupynumeric/cuda_help.h" #include -namespace cunumeric { +namespace cupynumeric { using namespace legate; @@ -52,9 +52,9 @@ static inline void qr_template(GeqrfBufferSize geqrf_buffer_size, q_tmp = q_copy.ptr(0); } - CUNUMERIC_CHECK_CUDA( + CUPYNUMERIC_CHECK_CUDA( cudaMemcpyAsync(q_tmp, a, sizeof(VAL) * m * n, cudaMemcpyDeviceToDevice, stream)); - CUNUMERIC_CHECK_CUDA(cudaStreamSynchronize(stream)); + CUPYNUMERIC_CHECK_CUDA(cudaStreamSynchronize(stream)); CHECK_CUSOLVER(cusolverDnSetStream(handle, stream)); @@ -71,27 +71,27 @@ static inline void qr_template(GeqrfBufferSize geqrf_buffer_size, CHECK_CUSOLVER( geqrf(handle, m, n, q_tmp, m, tau.ptr(0), buffer.ptr(0), lwork_total, info.ptr(0))); - CUNUMERIC_CHECK_CUDA(cudaStreamSynchronize(stream)); + CUPYNUMERIC_CHECK_CUDA(cudaStreamSynchronize(stream)); if (info[0] != 0) { throw legate::TaskException(QrTask::ERROR_MESSAGE); } // extract R from upper triangular of geqrf result - CUNUMERIC_CHECK_CUDA(cudaMemsetAsync(r, 0, k * n * sizeof(VAL), stream)); + CUPYNUMERIC_CHECK_CUDA(cudaMemsetAsync(r, 0, k * n * sizeof(VAL), stream)); for (int i = 0; i < k; ++i) { int elements = i + 1; if (i == k - 1 && n > k) { elements = k * (n - k + 1); } - CUNUMERIC_CHECK_CUDA(cudaMemcpyAsync( + CUPYNUMERIC_CHECK_CUDA(cudaMemcpyAsync( r + i * k, q_tmp + i * m, sizeof(VAL) * elements, cudaMemcpyDeviceToDevice, stream)); } // assemble Q CHECK_CUSOLVER( orgqr(handle, m, k, k, q_tmp, m, tau.ptr(0), buffer.ptr(0), lwork_total, info.ptr(0))); - CUNUMERIC_CHECK_CUDA(cudaStreamSynchronize(stream)); + CUPYNUMERIC_CHECK_CUDA(cudaStreamSynchronize(stream)); if (info[0] != 0) { throw legate::TaskException(QrTask::ERROR_MESSAGE); @@ -100,13 +100,13 @@ static inline void qr_template(GeqrfBufferSize geqrf_buffer_size, // if we used a tmp storage we still need to copy back Q if (q_tmp != q) { assert(n > m); - CUNUMERIC_CHECK_CUDA( + CUPYNUMERIC_CHECK_CUDA( cudaMemcpyAsync(q, q_tmp, sizeof(VAL) * m * m, cudaMemcpyDeviceToDevice, stream)); } - CUNUMERIC_CHECK_CUDA_STREAM(stream); + CUPYNUMERIC_CHECK_CUDA_STREAM(stream); -#ifdef DEBUG_CUNUMERIC +#ifdef DEBUG_CUPYNUMERIC assert(info[0] == 0); #endif } @@ -187,4 +187,4 @@ struct QrImplBody { /*static*/ void QrTask::gpu_variant(TaskContext context) { qr_template(context); } -} // namespace cunumeric +} // namespace cupynumeric diff --git a/src/cunumeric/matrix/qr.h b/src/cupynumeric/matrix/qr.h similarity index 81% rename from src/cunumeric/matrix/qr.h rename to src/cupynumeric/matrix/qr.h index 9a29199ddb..4085c1d5ed 100644 --- a/src/cunumeric/matrix/qr.h +++ b/src/cupynumeric/matrix/qr.h @@ -16,13 +16,13 @@ #pragma once -#include "cunumeric/cunumeric_task.h" +#include "cupynumeric/cupynumeric_task.h" -namespace cunumeric { +namespace cupynumeric { -class QrTask : public CuNumericTask { +class QrTask : public CuPyNumericTask { public: - static constexpr auto TASK_ID = legate::LocalTaskID{CUNUMERIC_QR}; + static constexpr auto TASK_ID = legate::LocalTaskID{CUPYNUMERIC_QR}; static const char* ERROR_MESSAGE; public: @@ -35,4 +35,4 @@ class QrTask : public CuNumericTask { #endif }; -} // namespace cunumeric +} // namespace cupynumeric diff --git a/src/cunumeric/matrix/qr_cpu.inl b/src/cupynumeric/matrix/qr_cpu.inl similarity index 98% rename from src/cunumeric/matrix/qr_cpu.inl rename to src/cupynumeric/matrix/qr_cpu.inl index ef143a57a0..56c2979ed6 100644 --- a/src/cunumeric/matrix/qr_cpu.inl +++ b/src/cupynumeric/matrix/qr_cpu.inl @@ -20,7 +20,7 @@ #include #include -namespace cunumeric { +namespace cupynumeric { using namespace legate; @@ -128,4 +128,4 @@ struct QrImplBody { } }; -} // namespace cunumeric +} // namespace cupynumeric diff --git a/src/cunumeric/matrix/qr_omp.cc b/src/cupynumeric/matrix/qr_omp.cc similarity index 81% rename from src/cunumeric/matrix/qr_omp.cc rename to src/cupynumeric/matrix/qr_omp.cc index c28c4c496b..a954689b2d 100644 --- a/src/cunumeric/matrix/qr_omp.cc +++ b/src/cupynumeric/matrix/qr_omp.cc @@ -14,13 +14,13 @@ * */ -#include "cunumeric/matrix/qr.h" -#include "cunumeric/matrix/qr_template.inl" -#include "cunumeric/matrix/qr_cpu.inl" +#include "cupynumeric/matrix/qr.h" +#include "cupynumeric/matrix/qr_template.inl" +#include "cupynumeric/matrix/qr_cpu.inl" #include -namespace cunumeric { +namespace cupynumeric { /*static*/ void QrTask::omp_variant(TaskContext context) { @@ -28,4 +28,4 @@ namespace cunumeric { qr_template(context); } -} // namespace cunumeric +} // namespace cupynumeric diff --git a/src/cunumeric/matrix/qr_template.inl b/src/cupynumeric/matrix/qr_template.inl similarity index 94% rename from src/cunumeric/matrix/qr_template.inl rename to src/cupynumeric/matrix/qr_template.inl index 2040b555b6..0fa17e40d6 100644 --- a/src/cunumeric/matrix/qr_template.inl +++ b/src/cupynumeric/matrix/qr_template.inl @@ -19,9 +19,9 @@ #include // Useful for IDEs -#include "cunumeric/matrix/qr.h" +#include "cupynumeric/matrix/qr.h" -namespace cunumeric { +namespace cupynumeric { using namespace legate; @@ -48,7 +48,7 @@ struct QrImpl { { using VAL = type_of; -#ifdef DEBUG_CUNUMERIC +#ifdef DEBUG_CUPYNUMERIC assert(a_array.dim() == 2); assert(q_array.dim() == 2); assert(r_array.dim() == 2); @@ -61,7 +61,7 @@ struct QrImpl { const int64_t n = a_shape.hi[1] - a_shape.lo[1] + 1; const int64_t k = std::min(m, n); -#ifdef DEBUG_CUNUMERIC +#ifdef DEBUG_CUPYNUMERIC assert(q_shape.hi[0] - q_shape.lo[0] + 1 == m); assert(q_shape.hi[1] - q_shape.lo[1] + 1 == k); assert(r_shape.hi[0] - r_shape.lo[0] + 1 == k); @@ -71,7 +71,7 @@ struct QrImpl { auto a_acc = a_array.read_accessor(a_shape); auto q_acc = q_array.write_accessor(q_shape); auto r_acc = r_array.write_accessor(r_shape); -#ifdef DEBUG_CUNUMERIC +#ifdef DEBUG_CUPYNUMERIC assert(a_acc.accessor.is_dense_col_major(a_shape)); assert(q_acc.accessor.is_dense_col_major(q_shape)); assert(r_acc.accessor.is_dense_col_major(r_shape)); @@ -99,4 +99,4 @@ static void qr_template(TaskContext& context) type_dispatch(a_array.type().code(), QrImpl{}, a_array, q_array, r_array); } -} // namespace cunumeric +} // namespace cupynumeric diff --git a/src/cunumeric/matrix/solve.cc b/src/cupynumeric/matrix/solve.cc similarity index 85% rename from src/cunumeric/matrix/solve.cc rename to src/cupynumeric/matrix/solve.cc index c86de37283..2bb8778fb1 100644 --- a/src/cunumeric/matrix/solve.cc +++ b/src/cupynumeric/matrix/solve.cc @@ -14,11 +14,11 @@ * */ -#include "cunumeric/matrix/solve.h" -#include "cunumeric/matrix/solve_template.inl" -#include "cunumeric/matrix/solve_cpu.inl" +#include "cupynumeric/matrix/solve.h" +#include "cupynumeric/matrix/solve_template.inl" +#include "cupynumeric/matrix/solve_cpu.inl" -namespace cunumeric { +namespace cupynumeric { using namespace legate; @@ -37,4 +37,4 @@ namespace // unnamed static void __attribute__((constructor)) register_tasks(void) { SolveTask::register_variants(); } } // namespace -} // namespace cunumeric +} // namespace cupynumeric diff --git a/src/cunumeric/matrix/solve.cu b/src/cupynumeric/matrix/solve.cu similarity index 92% rename from src/cunumeric/matrix/solve.cu rename to src/cupynumeric/matrix/solve.cu index 58e24baee3..147395e13a 100644 --- a/src/cunumeric/matrix/solve.cu +++ b/src/cupynumeric/matrix/solve.cu @@ -14,12 +14,12 @@ * */ -#include "cunumeric/matrix/solve.h" -#include "cunumeric/matrix/solve_template.inl" +#include "cupynumeric/matrix/solve.h" +#include "cupynumeric/matrix/solve_template.inl" -#include "cunumeric/cuda_help.h" +#include "cupynumeric/cuda_help.h" -namespace cunumeric { +namespace cupynumeric { using namespace legate; @@ -47,7 +47,7 @@ static inline void solve_template(GetrfBufferSize getrf_buffer_size, auto info = create_buffer(1, Memory::Kind::Z_COPY_MEM); CHECK_CUSOLVER(getrf(handle, m, n, a, m, buffer.ptr(0), ipiv.ptr(0), info.ptr(0))); - CUNUMERIC_CHECK_CUDA(cudaStreamSynchronize(stream)); + CUPYNUMERIC_CHECK_CUDA(cudaStreamSynchronize(stream)); if (info[0] != 0) { throw legate::TaskException(SolveTask::ERROR_MESSAGE); @@ -55,9 +55,9 @@ static inline void solve_template(GetrfBufferSize getrf_buffer_size, CHECK_CUSOLVER(getrs(handle, trans, n, nrhs, a, m, ipiv.ptr(0), b, n, info.ptr(0))); - CUNUMERIC_CHECK_CUDA_STREAM(stream); + CUPYNUMERIC_CHECK_CUDA_STREAM(stream); -#ifdef DEBUG_CUNUMERIC +#ifdef DEBUG_CUPYNUMERIC assert(info[0] == 0); #endif } @@ -115,4 +115,4 @@ struct SolveImplBody { solve_template(context); } -} // namespace cunumeric +} // namespace cupynumeric diff --git a/src/cunumeric/matrix/solve.h b/src/cupynumeric/matrix/solve.h similarity index 80% rename from src/cunumeric/matrix/solve.h rename to src/cupynumeric/matrix/solve.h index 512d0878e2..ebe88dab4c 100644 --- a/src/cunumeric/matrix/solve.h +++ b/src/cupynumeric/matrix/solve.h @@ -16,13 +16,13 @@ #pragma once -#include "cunumeric/cunumeric_task.h" +#include "cupynumeric/cupynumeric_task.h" -namespace cunumeric { +namespace cupynumeric { -class SolveTask : public CuNumericTask { +class SolveTask : public CuPyNumericTask { public: - static constexpr auto TASK_ID = legate::LocalTaskID{CUNUMERIC_SOLVE}; + static constexpr auto TASK_ID = legate::LocalTaskID{CUPYNUMERIC_SOLVE}; static const char* ERROR_MESSAGE; public: @@ -35,4 +35,4 @@ class SolveTask : public CuNumericTask { #endif }; -} // namespace cunumeric +} // namespace cupynumeric diff --git a/src/cunumeric/matrix/solve_cpu.inl b/src/cupynumeric/matrix/solve_cpu.inl similarity index 97% rename from src/cunumeric/matrix/solve_cpu.inl rename to src/cupynumeric/matrix/solve_cpu.inl index 367bd346bc..4d38569ada 100644 --- a/src/cunumeric/matrix/solve_cpu.inl +++ b/src/cupynumeric/matrix/solve_cpu.inl @@ -19,7 +19,7 @@ #include #include -namespace cunumeric { +namespace cupynumeric { using namespace legate; @@ -89,4 +89,4 @@ struct SolveImplBody { } }; -} // namespace cunumeric +} // namespace cupynumeric diff --git a/src/cunumeric/matrix/solve_omp.cc b/src/cupynumeric/matrix/solve_omp.cc similarity index 81% rename from src/cunumeric/matrix/solve_omp.cc rename to src/cupynumeric/matrix/solve_omp.cc index 223715011b..a709da5a9d 100644 --- a/src/cunumeric/matrix/solve_omp.cc +++ b/src/cupynumeric/matrix/solve_omp.cc @@ -14,13 +14,13 @@ * */ -#include "cunumeric/matrix/solve.h" -#include "cunumeric/matrix/solve_template.inl" -#include "cunumeric/matrix/solve_cpu.inl" +#include "cupynumeric/matrix/solve.h" +#include "cupynumeric/matrix/solve_template.inl" +#include "cupynumeric/matrix/solve_cpu.inl" #include -namespace cunumeric { +namespace cupynumeric { /*static*/ void SolveTask::omp_variant(TaskContext context) { @@ -28,4 +28,4 @@ namespace cunumeric { solve_template(context); } -} // namespace cunumeric +} // namespace cupynumeric diff --git a/src/cunumeric/matrix/solve_template.inl b/src/cupynumeric/matrix/solve_template.inl similarity index 92% rename from src/cunumeric/matrix/solve_template.inl rename to src/cupynumeric/matrix/solve_template.inl index 8ccd85e578..c04ceb4da7 100644 --- a/src/cunumeric/matrix/solve_template.inl +++ b/src/cupynumeric/matrix/solve_template.inl @@ -19,9 +19,9 @@ #include // Useful for IDEs -#include "cunumeric/matrix/solve.h" +#include "cupynumeric/matrix/solve.h" -namespace cunumeric { +namespace cupynumeric { using namespace legate; @@ -46,7 +46,7 @@ struct SolveImpl { { using VAL = type_of; -#ifdef DEBUG_CUNUMERIC +#ifdef DEBUG_CUPYNUMERIC assert(a_array.dim() == 2); assert(b_array.dim() == 1 || b_array.dim() == 2); #endif @@ -55,14 +55,14 @@ struct SolveImpl { const int64_t m = a_shape.hi[0] - a_shape.lo[0] + 1; const int64_t n = a_shape.hi[1] - a_shape.lo[1] + 1; -#ifdef DEBUG_CUNUMERIC +#ifdef DEBUG_CUPYNUMERIC // The Python code guarantees this property assert(m == n); #endif size_t a_strides[2]; VAL* a = a_array.read_write_accessor(a_shape).ptr(a_shape, a_strides); -#ifdef DEBUG_CUNUMERIC +#ifdef DEBUG_CUPYNUMERIC assert(a_array.is_future() || (a_strides[0] == 1 && static_cast(a_strides[1]) == m)); #endif VAL* b = nullptr; @@ -70,25 +70,25 @@ struct SolveImpl { int64_t nrhs = 1; if (b_array.dim() == 1) { const auto b_shape = b_array.shape<1>(); -#ifdef DEBUG_CUNUMERIC +#ifdef DEBUG_CUPYNUMERIC assert(m == b_shape.hi[0] - b_shape.lo[0] + 1); #endif size_t b_strides; b = b_array.read_write_accessor(b_shape).ptr(b_shape, &b_strides); } else { const auto b_shape = b_array.shape<2>(); -#ifdef DEBUG_CUNUMERIC +#ifdef DEBUG_CUPYNUMERIC assert(m == b_shape.hi[0] - b_shape.lo[0] + 1); #endif nrhs = b_shape.hi[1] - b_shape.lo[1] + 1; size_t b_strides[2]; b = b_array.read_write_accessor(b_shape).ptr(b_shape, b_strides); -#ifdef DEBUG_CUNUMERIC +#ifdef DEBUG_CUPYNUMERIC assert(b_array.is_future() || (b_strides[0] == 1 && static_cast(b_strides[1]) == m)); #endif } -#ifdef DEBUG_CUNUMERIC +#ifdef DEBUG_CUPYNUMERIC assert(m > 0 && n > 0 && nrhs > 0); #endif @@ -110,4 +110,4 @@ static void solve_template(TaskContext& context) type_dispatch(a_array.type().code(), SolveImpl{}, a_array, b_array); } -} // namespace cunumeric +} // namespace cupynumeric diff --git a/src/cunumeric/matrix/svd.cc b/src/cupynumeric/matrix/svd.cc similarity index 85% rename from src/cunumeric/matrix/svd.cc rename to src/cupynumeric/matrix/svd.cc index 97d49d694f..c735783fed 100644 --- a/src/cunumeric/matrix/svd.cc +++ b/src/cupynumeric/matrix/svd.cc @@ -14,11 +14,11 @@ * */ -#include "cunumeric/matrix/svd.h" -#include "cunumeric/matrix/svd_template.inl" -#include "cunumeric/matrix/svd_cpu.inl" +#include "cupynumeric/matrix/svd.h" +#include "cupynumeric/matrix/svd_template.inl" +#include "cupynumeric/matrix/svd_cpu.inl" -namespace cunumeric { +namespace cupynumeric { using namespace legate; @@ -37,4 +37,4 @@ namespace // unnamed static void __attribute__((constructor)) register_tasks(void) { SvdTask::register_variants(); } } // namespace -} // namespace cunumeric \ No newline at end of file +} // namespace cupynumeric \ No newline at end of file diff --git a/src/cunumeric/matrix/svd.cu b/src/cupynumeric/matrix/svd.cu similarity index 95% rename from src/cunumeric/matrix/svd.cu rename to src/cupynumeric/matrix/svd.cu index 8df7cdac05..932c499070 100644 --- a/src/cunumeric/matrix/svd.cu +++ b/src/cupynumeric/matrix/svd.cu @@ -14,12 +14,12 @@ * */ -#include "cunumeric/matrix/svd.h" -#include "cunumeric/matrix/svd_template.inl" +#include "cupynumeric/matrix/svd.h" +#include "cupynumeric/matrix/svd_template.inl" -#include "cunumeric/cuda_help.h" +#include "cupynumeric/cuda_help.h" #include -namespace cunumeric { +namespace cupynumeric { using namespace legate; @@ -39,7 +39,7 @@ static inline void svd_template(DataType valTypeC, auto stream = get_cached_stream(); auto a_copy = create_buffer(m * n, Memory::Kind::GPU_FB_MEM); - CUNUMERIC_CHECK_CUDA( + CUPYNUMERIC_CHECK_CUDA( cudaMemcpyAsync(a_copy.ptr(0), a, m * n * sizeof(VAL), cudaMemcpyDeviceToDevice, stream)); // a[m][n], u[m][m] s[k] vh[n][n] @@ -95,15 +95,15 @@ static inline void svd_template(DataType valTypeC, lwork_host, info.ptr(0))); - CUNUMERIC_CHECK_CUDA(cudaStreamSynchronize(stream)); + CUPYNUMERIC_CHECK_CUDA(cudaStreamSynchronize(stream)); if (info[0] != 0) { throw legate::TaskException(SvdTask::ERROR_MESSAGE); } - CUNUMERIC_CHECK_CUDA_STREAM(stream); + CUPYNUMERIC_CHECK_CUDA_STREAM(stream); -#ifdef DEBUG_CUNUMERIC +#ifdef DEBUG_CUPYNUMERIC assert(info[0] == 0); #endif } @@ -191,4 +191,4 @@ struct SvdImplBody { svd_template(context); } -} // namespace cunumeric \ No newline at end of file +} // namespace cupynumeric \ No newline at end of file diff --git a/src/cunumeric/matrix/svd.h b/src/cupynumeric/matrix/svd.h similarity index 81% rename from src/cunumeric/matrix/svd.h rename to src/cupynumeric/matrix/svd.h index bd71ba2e49..d6f8cb722a 100644 --- a/src/cunumeric/matrix/svd.h +++ b/src/cupynumeric/matrix/svd.h @@ -16,13 +16,13 @@ #pragma once -#include "cunumeric/cunumeric_task.h" +#include "cupynumeric/cupynumeric_task.h" -namespace cunumeric { +namespace cupynumeric { -class SvdTask : public CuNumericTask { +class SvdTask : public CuPyNumericTask { public: - static constexpr auto TASK_ID = legate::LocalTaskID{CUNUMERIC_SVD}; + static constexpr auto TASK_ID = legate::LocalTaskID{CUPYNUMERIC_SVD}; static const char* ERROR_MESSAGE; public: @@ -35,4 +35,4 @@ class SvdTask : public CuNumericTask { #endif }; -} // namespace cunumeric \ No newline at end of file +} // namespace cupynumeric \ No newline at end of file diff --git a/src/cunumeric/matrix/svd_cpu.inl b/src/cupynumeric/matrix/svd_cpu.inl similarity index 99% rename from src/cunumeric/matrix/svd_cpu.inl rename to src/cupynumeric/matrix/svd_cpu.inl index bb49aa2f65..8f43b79ce7 100644 --- a/src/cunumeric/matrix/svd_cpu.inl +++ b/src/cupynumeric/matrix/svd_cpu.inl @@ -20,7 +20,7 @@ #include #include -namespace cunumeric { +namespace cupynumeric { using namespace legate; @@ -254,4 +254,4 @@ struct SvdImplBody { } }; -} // namespace cunumeric \ No newline at end of file +} // namespace cupynumeric \ No newline at end of file diff --git a/src/cunumeric/matrix/svd_omp.cc b/src/cupynumeric/matrix/svd_omp.cc similarity index 81% rename from src/cunumeric/matrix/svd_omp.cc rename to src/cupynumeric/matrix/svd_omp.cc index fac2cf1b1b..36fc1ad209 100644 --- a/src/cunumeric/matrix/svd_omp.cc +++ b/src/cupynumeric/matrix/svd_omp.cc @@ -14,13 +14,13 @@ * */ -#include "cunumeric/matrix/svd.h" -#include "cunumeric/matrix/svd_template.inl" -#include "cunumeric/matrix/svd_cpu.inl" +#include "cupynumeric/matrix/svd.h" +#include "cupynumeric/matrix/svd_template.inl" +#include "cupynumeric/matrix/svd_cpu.inl" #include -namespace cunumeric { +namespace cupynumeric { /*static*/ void SvdTask::omp_variant(TaskContext context) { @@ -28,4 +28,4 @@ namespace cunumeric { svd_template(context); } -} // namespace cunumeric \ No newline at end of file +} // namespace cupynumeric \ No newline at end of file diff --git a/src/cunumeric/matrix/svd_template.inl b/src/cupynumeric/matrix/svd_template.inl similarity index 96% rename from src/cunumeric/matrix/svd_template.inl rename to src/cupynumeric/matrix/svd_template.inl index c529ed40e3..a43f0ef3c6 100644 --- a/src/cunumeric/matrix/svd_template.inl +++ b/src/cupynumeric/matrix/svd_template.inl @@ -19,9 +19,9 @@ #include // Useful for IDEs -#include "cunumeric/matrix/svd.h" +#include "cupynumeric/matrix/svd.h" -namespace cunumeric { +namespace cupynumeric { using namespace legate; @@ -63,7 +63,7 @@ struct SvdImpl { using VAL = type_of; using VAL_REAL = typename real_type::TYPE; -#ifdef DEBUG_CUNUMERIC +#ifdef DEBUG_CUPYNUMERIC assert(a_array.dim() == 2); assert(u_array.dim() == 2); assert(s_array.dim() == 1); @@ -81,7 +81,7 @@ struct SvdImpl { assert(m >= n); bool full_matrices = (u_shape.hi[1] - u_shape.lo[1] + 1 == m); -#ifdef DEBUG_CUNUMERIC +#ifdef DEBUG_CUPYNUMERIC assert(u_shape.hi[0] - u_shape.lo[0] + 1 == m); if (full_matrices) { assert(u_shape.hi[1] - u_shape.lo[1] + 1 == m); @@ -97,7 +97,7 @@ struct SvdImpl { auto u_acc = u_array.write_accessor(u_shape); auto s_acc = s_array.write_accessor(s_shape); auto vh_acc = vh_array.write_accessor(vh_shape); -#ifdef DEBUG_CUNUMERIC +#ifdef DEBUG_CUPYNUMERIC assert(a_acc.accessor.is_dense_col_major(a_shape)); assert(u_acc.accessor.is_dense_col_major(u_shape)); assert(vh_acc.accessor.is_dense_col_major(vh_shape)); @@ -134,4 +134,4 @@ static void svd_template(TaskContext& context) type_dispatch(a_array.type().code(), SvdImpl{}, a_array, u_array, s_array, vh_array); } -} // namespace cunumeric +} // namespace cupynumeric diff --git a/src/cunumeric/matrix/syrk.cc b/src/cupynumeric/matrix/syrk.cc similarity index 95% rename from src/cunumeric/matrix/syrk.cc rename to src/cupynumeric/matrix/syrk.cc index 2154cba597..3812538855 100644 --- a/src/cunumeric/matrix/syrk.cc +++ b/src/cupynumeric/matrix/syrk.cc @@ -14,12 +14,12 @@ * */ -#include "cunumeric/matrix/syrk.h" -#include "cunumeric/matrix/syrk_template.inl" +#include "cupynumeric/matrix/syrk.h" +#include "cupynumeric/matrix/syrk_template.inl" #include -namespace cunumeric { +namespace cupynumeric { using namespace legate; @@ -88,4 +88,4 @@ namespace // unnamed static void __attribute__((constructor)) register_tasks(void) { SyrkTask::register_variants(); } } // namespace -} // namespace cunumeric +} // namespace cupynumeric diff --git a/src/cunumeric/matrix/syrk.cu b/src/cupynumeric/matrix/syrk.cu similarity index 92% rename from src/cunumeric/matrix/syrk.cu rename to src/cupynumeric/matrix/syrk.cu index 032d86c31a..4be72374e8 100644 --- a/src/cunumeric/matrix/syrk.cu +++ b/src/cupynumeric/matrix/syrk.cu @@ -14,12 +14,12 @@ * */ -#include "cunumeric/matrix/syrk.h" -#include "cunumeric/matrix/syrk_template.inl" +#include "cupynumeric/matrix/syrk.h" +#include "cupynumeric/matrix/syrk_template.inl" -#include "cunumeric/cuda_help.h" +#include "cupynumeric/cuda_help.h" -namespace cunumeric { +namespace cupynumeric { using namespace legate; @@ -38,7 +38,7 @@ static inline void syrk_template( CHECK_CUBLAS(syrk(context, uplo, trans, m, n, &alpha, rhs, m, &beta, lhs, m)); - CUNUMERIC_CHECK_CUDA_STREAM(stream); + CUPYNUMERIC_CHECK_CUDA_STREAM(stream); } template <> @@ -86,4 +86,4 @@ struct SyrkImplBody { syrk_template(context); } -} // namespace cunumeric +} // namespace cupynumeric diff --git a/src/cunumeric/matrix/syrk.h b/src/cupynumeric/matrix/syrk.h similarity index 80% rename from src/cunumeric/matrix/syrk.h rename to src/cupynumeric/matrix/syrk.h index 0ef23b0a58..9b383fa3f5 100644 --- a/src/cunumeric/matrix/syrk.h +++ b/src/cupynumeric/matrix/syrk.h @@ -16,13 +16,13 @@ #pragma once -#include "cunumeric/cunumeric_task.h" +#include "cupynumeric/cupynumeric_task.h" -namespace cunumeric { +namespace cupynumeric { -class SyrkTask : public CuNumericTask { +class SyrkTask : public CuPyNumericTask { public: - static constexpr auto TASK_ID = legate::LocalTaskID{CUNUMERIC_SYRK}; + static constexpr auto TASK_ID = legate::LocalTaskID{CUPYNUMERIC_SYRK}; public: static void cpu_variant(legate::TaskContext context); @@ -34,4 +34,4 @@ class SyrkTask : public CuNumericTask { #endif }; -} // namespace cunumeric +} // namespace cupynumeric diff --git a/src/cunumeric/matrix/syrk_omp.cc b/src/cupynumeric/matrix/syrk_omp.cc similarity index 94% rename from src/cunumeric/matrix/syrk_omp.cc rename to src/cupynumeric/matrix/syrk_omp.cc index 5146390236..5b25d7707c 100644 --- a/src/cunumeric/matrix/syrk_omp.cc +++ b/src/cupynumeric/matrix/syrk_omp.cc @@ -14,13 +14,13 @@ * */ -#include "cunumeric/matrix/syrk.h" -#include "cunumeric/matrix/syrk_template.inl" +#include "cupynumeric/matrix/syrk.h" +#include "cupynumeric/matrix/syrk_template.inl" #include #include -namespace cunumeric { +namespace cupynumeric { using namespace legate; @@ -82,4 +82,4 @@ struct SyrkImplBody { syrk_template(context); } -} // namespace cunumeric +} // namespace cupynumeric diff --git a/src/cunumeric/matrix/syrk_template.inl b/src/cupynumeric/matrix/syrk_template.inl similarity index 96% rename from src/cunumeric/matrix/syrk_template.inl rename to src/cupynumeric/matrix/syrk_template.inl index 58307e05e3..72aaa949c8 100644 --- a/src/cunumeric/matrix/syrk_template.inl +++ b/src/cupynumeric/matrix/syrk_template.inl @@ -17,9 +17,9 @@ #pragma once // Useful for IDEs -#include "cunumeric/matrix/syrk.h" +#include "cupynumeric/matrix/syrk.h" -namespace cunumeric { +namespace cupynumeric { using namespace legate; @@ -80,4 +80,4 @@ static void syrk_template(TaskContext& context) type_dispatch(lhs.type().code(), SyrkImpl{}, lhs, rhs); } -} // namespace cunumeric +} // namespace cupynumeric diff --git a/src/cunumeric/matrix/tile.cc b/src/cupynumeric/matrix/tile.cc similarity index 91% rename from src/cunumeric/matrix/tile.cc rename to src/cupynumeric/matrix/tile.cc index 123bd9387f..82fa7d542c 100644 --- a/src/cunumeric/matrix/tile.cc +++ b/src/cupynumeric/matrix/tile.cc @@ -14,10 +14,10 @@ * */ -#include "cunumeric/matrix/tile.h" -#include "cunumeric/matrix/tile_template.inl" +#include "cupynumeric/matrix/tile.h" +#include "cupynumeric/matrix/tile_template.inl" -namespace cunumeric { +namespace cupynumeric { using namespace legate; @@ -48,4 +48,4 @@ namespace // unnamed static void __attribute__((constructor)) register_tasks(void) { TileTask::register_variants(); } } // namespace -} // namespace cunumeric +} // namespace cupynumeric diff --git a/src/cunumeric/matrix/tile.cu b/src/cupynumeric/matrix/tile.cu similarity index 90% rename from src/cunumeric/matrix/tile.cu rename to src/cupynumeric/matrix/tile.cu index 5750f54359..24f46b419d 100644 --- a/src/cunumeric/matrix/tile.cu +++ b/src/cupynumeric/matrix/tile.cu @@ -14,12 +14,12 @@ * */ -#include "cunumeric/matrix/tile.h" -#include "cunumeric/matrix/tile_template.inl" +#include "cupynumeric/matrix/tile.h" +#include "cupynumeric/matrix/tile_template.inl" -#include "cunumeric/cuda_help.h" +#include "cupynumeric/cuda_help.h" -namespace cunumeric { +namespace cupynumeric { template __global__ static void __launch_bounds__(THREADS_PER_BLOCK, MIN_CTAS_PER_SM) @@ -53,7 +53,7 @@ struct TileImplBody { auto stream = get_cached_stream(); tile_kernel<<>>( out_rect, out_pitches, out_volume, in_strides, out, in); - CUNUMERIC_CHECK_CUDA_STREAM(stream); + CUPYNUMERIC_CHECK_CUDA_STREAM(stream); } }; @@ -62,4 +62,4 @@ struct TileImplBody { tile_template(context); } -} // namespace cunumeric +} // namespace cupynumeric diff --git a/src/cunumeric/matrix/tile.h b/src/cupynumeric/matrix/tile.h similarity index 81% rename from src/cunumeric/matrix/tile.h rename to src/cupynumeric/matrix/tile.h index eeca4d858d..db0ade32b8 100644 --- a/src/cunumeric/matrix/tile.h +++ b/src/cupynumeric/matrix/tile.h @@ -16,18 +16,18 @@ #pragma once -#include "cunumeric/cunumeric_task.h" +#include "cupynumeric/cupynumeric_task.h" -namespace cunumeric { +namespace cupynumeric { struct TileArgs { legate::PhysicalStore in; legate::PhysicalStore out; }; -class TileTask : public CuNumericTask { +class TileTask : public CuPyNumericTask { public: - static constexpr auto TASK_ID = legate::LocalTaskID{CUNUMERIC_TILE}; + static constexpr auto TASK_ID = legate::LocalTaskID{CUPYNUMERIC_TILE}; public: static void cpu_variant(legate::TaskContext context); @@ -39,4 +39,4 @@ class TileTask : public CuNumericTask { #endif }; -} // namespace cunumeric +} // namespace cupynumeric diff --git a/src/cunumeric/matrix/tile_omp.cc b/src/cupynumeric/matrix/tile_omp.cc similarity index 91% rename from src/cunumeric/matrix/tile_omp.cc rename to src/cupynumeric/matrix/tile_omp.cc index 72f5610eeb..3d1553dc75 100644 --- a/src/cunumeric/matrix/tile_omp.cc +++ b/src/cupynumeric/matrix/tile_omp.cc @@ -14,10 +14,10 @@ * */ -#include "cunumeric/matrix/tile.h" -#include "cunumeric/matrix/tile_template.inl" +#include "cupynumeric/matrix/tile.h" +#include "cupynumeric/matrix/tile_template.inl" -namespace cunumeric { +namespace cupynumeric { using namespace legate; @@ -44,4 +44,4 @@ struct TileImplBody { tile_template(context); } -} // namespace cunumeric +} // namespace cupynumeric diff --git a/src/cunumeric/matrix/tile_template.inl b/src/cupynumeric/matrix/tile_template.inl similarity index 95% rename from src/cunumeric/matrix/tile_template.inl rename to src/cupynumeric/matrix/tile_template.inl index 423ece1cf7..4c5c305617 100644 --- a/src/cunumeric/matrix/tile_template.inl +++ b/src/cupynumeric/matrix/tile_template.inl @@ -17,10 +17,10 @@ #pragma once // Useful for IDEs -#include "cunumeric/matrix/tile.h" -#include "cunumeric/pitches.h" +#include "cupynumeric/matrix/tile.h" +#include "cupynumeric/pitches.h" -namespace cunumeric { +namespace cupynumeric { using namespace legate; @@ -85,4 +85,4 @@ static void tile_template(TaskContext& context) type_dispatch(args.in.code(), TileDispatch{}, args); } -} // namespace cunumeric +} // namespace cupynumeric diff --git a/src/cunumeric/matrix/transpose.cc b/src/cupynumeric/matrix/transpose.cc similarity index 92% rename from src/cunumeric/matrix/transpose.cc rename to src/cupynumeric/matrix/transpose.cc index 22bdf46d2f..8ebcc2f729 100644 --- a/src/cunumeric/matrix/transpose.cc +++ b/src/cupynumeric/matrix/transpose.cc @@ -14,15 +14,15 @@ * */ -#include "cunumeric/matrix/transpose.h" -#include "cunumeric/matrix/transpose_template.inl" +#include "cupynumeric/matrix/transpose.h" +#include "cupynumeric/matrix/transpose_template.inl" #if LEGATE_DEFINED(LEGATE_USE_OPENMP) #include "omp.h" #endif #include "cblas.h" -namespace cunumeric { +namespace cupynumeric { using namespace legate; @@ -65,4 +65,4 @@ static void __attribute__((constructor)) register_tasks(void) } } // namespace -} // namespace cunumeric +} // namespace cupynumeric diff --git a/src/cunumeric/matrix/transpose.cu b/src/cupynumeric/matrix/transpose.cu similarity index 93% rename from src/cunumeric/matrix/transpose.cu rename to src/cupynumeric/matrix/transpose.cu index 9528792d83..67212a05d2 100644 --- a/src/cunumeric/matrix/transpose.cu +++ b/src/cupynumeric/matrix/transpose.cu @@ -14,12 +14,12 @@ * */ -#include "cunumeric/matrix/transpose.h" -#include "cunumeric/matrix/transpose_template.inl" +#include "cupynumeric/matrix/transpose.h" +#include "cupynumeric/matrix/transpose_template.inl" -#include "cunumeric/cuda_help.h" +#include "cupynumeric/cuda_help.h" -namespace cunumeric { +namespace cupynumeric { #define TILE_DIM 32 #define BLOCK_ROWS 8 @@ -99,7 +99,7 @@ struct TransposeImplBody { auto stream = get_cached_stream(); transpose_2d_physical<<>>(out, in, rect.lo, rect.hi); - CUNUMERIC_CHECK_CUDA_STREAM(stream); + CUPYNUMERIC_CHECK_CUDA_STREAM(stream); } }; @@ -108,4 +108,4 @@ struct TransposeImplBody { transpose_template(context); } -} // namespace cunumeric +} // namespace cupynumeric diff --git a/src/cunumeric/matrix/transpose.h b/src/cupynumeric/matrix/transpose.h similarity index 80% rename from src/cunumeric/matrix/transpose.h rename to src/cupynumeric/matrix/transpose.h index d87339e542..26290c1259 100644 --- a/src/cunumeric/matrix/transpose.h +++ b/src/cupynumeric/matrix/transpose.h @@ -16,18 +16,18 @@ #pragma once -#include "cunumeric/cunumeric_task.h" +#include "cupynumeric/cupynumeric_task.h" -namespace cunumeric { +namespace cupynumeric { struct TransposeArgs { legate::PhysicalStore out; legate::PhysicalStore in; }; -class TransposeTask : public CuNumericTask { +class TransposeTask : public CuPyNumericTask { public: - static constexpr auto TASK_ID = legate::LocalTaskID{CUNUMERIC_TRANSPOSE_COPY_2D}; + static constexpr auto TASK_ID = legate::LocalTaskID{CUPYNUMERIC_TRANSPOSE_COPY_2D}; public: static void cpu_variant(legate::TaskContext context); @@ -39,4 +39,4 @@ class TransposeTask : public CuNumericTask { #endif }; -} // namespace cunumeric +} // namespace cupynumeric diff --git a/src/cunumeric/matrix/transpose_omp.cc b/src/cupynumeric/matrix/transpose_omp.cc similarity index 91% rename from src/cunumeric/matrix/transpose_omp.cc rename to src/cupynumeric/matrix/transpose_omp.cc index ea4db6abe4..1b439ae9ae 100644 --- a/src/cunumeric/matrix/transpose_omp.cc +++ b/src/cupynumeric/matrix/transpose_omp.cc @@ -14,13 +14,13 @@ * */ -#include "cunumeric/matrix/transpose.h" -#include "cunumeric/matrix/transpose_template.inl" +#include "cupynumeric/matrix/transpose.h" +#include "cupynumeric/matrix/transpose_template.inl" #include "omp.h" #include "cblas.h" -namespace cunumeric { +namespace cupynumeric { using namespace legate; @@ -54,4 +54,4 @@ struct TransposeImplBody { transpose_template(context); } -} // namespace cunumeric +} // namespace cupynumeric diff --git a/src/cunumeric/matrix/transpose_template.inl b/src/cupynumeric/matrix/transpose_template.inl similarity index 93% rename from src/cunumeric/matrix/transpose_template.inl rename to src/cupynumeric/matrix/transpose_template.inl index 9e1935471c..3a32efe913 100644 --- a/src/cunumeric/matrix/transpose_template.inl +++ b/src/cupynumeric/matrix/transpose_template.inl @@ -17,9 +17,9 @@ #pragma once // Useful for IDEs -#include "cunumeric/matrix/transpose.h" +#include "cupynumeric/matrix/transpose.h" -namespace cunumeric { +namespace cupynumeric { using namespace legate; @@ -55,4 +55,4 @@ static void transpose_template(TaskContext& context) type_dispatch(input.type().code(), TransposeImpl{}, args); } -} // namespace cunumeric +} // namespace cupynumeric diff --git a/src/cunumeric/matrix/trilu.cc b/src/cupynumeric/matrix/trilu.cc similarity index 92% rename from src/cunumeric/matrix/trilu.cc rename to src/cupynumeric/matrix/trilu.cc index ee694e2695..8a177040f1 100644 --- a/src/cunumeric/matrix/trilu.cc +++ b/src/cupynumeric/matrix/trilu.cc @@ -14,10 +14,10 @@ * */ -#include "cunumeric/matrix/trilu.h" -#include "cunumeric/matrix/trilu_template.inl" +#include "cupynumeric/matrix/trilu.h" +#include "cupynumeric/matrix/trilu_template.inl" -namespace cunumeric { +namespace cupynumeric { using namespace legate; @@ -65,4 +65,4 @@ namespace // unnamed static void __attribute__((constructor)) register_tasks(void) { TriluTask::register_variants(); } } // namespace -} // namespace cunumeric +} // namespace cupynumeric diff --git a/src/cunumeric/matrix/trilu.cu b/src/cupynumeric/matrix/trilu.cu similarity index 90% rename from src/cunumeric/matrix/trilu.cu rename to src/cupynumeric/matrix/trilu.cu index 0c4a24f97c..616581a6ea 100644 --- a/src/cunumeric/matrix/trilu.cu +++ b/src/cupynumeric/matrix/trilu.cu @@ -14,12 +14,12 @@ * */ -#include "cunumeric/matrix/trilu.h" -#include "cunumeric/matrix/trilu_template.inl" +#include "cupynumeric/matrix/trilu.h" +#include "cupynumeric/matrix/trilu_template.inl" -#include "cunumeric/cuda_help.h" +#include "cupynumeric/cuda_help.h" -namespace cunumeric { +namespace cupynumeric { using namespace legate; @@ -70,7 +70,7 @@ struct TriluImplBody { auto stream = get_cached_stream(); trilu_kernel <<>>(out, in, pitches, lo, volume, k); - CUNUMERIC_CHECK_CUDA_STREAM(stream); + CUPYNUMERIC_CHECK_CUDA_STREAM(stream); } }; @@ -79,4 +79,4 @@ struct TriluImplBody { trilu_template(context); } -} // namespace cunumeric +} // namespace cupynumeric diff --git a/src/cunumeric/matrix/trilu.h b/src/cupynumeric/matrix/trilu.h similarity index 81% rename from src/cunumeric/matrix/trilu.h rename to src/cupynumeric/matrix/trilu.h index f53a0864fe..27ad3e443e 100644 --- a/src/cunumeric/matrix/trilu.h +++ b/src/cupynumeric/matrix/trilu.h @@ -16,9 +16,9 @@ #pragma once -#include "cunumeric/cunumeric_task.h" +#include "cupynumeric/cupynumeric_task.h" -namespace cunumeric { +namespace cupynumeric { struct TriluArgs { bool lower; @@ -27,9 +27,9 @@ struct TriluArgs { legate::PhysicalStore input; }; -class TriluTask : public CuNumericTask { +class TriluTask : public CuPyNumericTask { public: - static constexpr auto TASK_ID = legate::LocalTaskID{CUNUMERIC_TRILU}; + static constexpr auto TASK_ID = legate::LocalTaskID{CUPYNUMERIC_TRILU}; public: static void cpu_variant(legate::TaskContext context); @@ -41,4 +41,4 @@ class TriluTask : public CuNumericTask { #endif }; -} // namespace cunumeric +} // namespace cupynumeric diff --git a/src/cunumeric/matrix/trilu_omp.cc b/src/cupynumeric/matrix/trilu_omp.cc similarity index 92% rename from src/cunumeric/matrix/trilu_omp.cc rename to src/cupynumeric/matrix/trilu_omp.cc index 3a5e1cc1e4..8b61824687 100644 --- a/src/cunumeric/matrix/trilu_omp.cc +++ b/src/cupynumeric/matrix/trilu_omp.cc @@ -14,10 +14,10 @@ * */ -#include "cunumeric/matrix/trilu.h" -#include "cunumeric/matrix/trilu_template.inl" +#include "cupynumeric/matrix/trilu.h" +#include "cupynumeric/matrix/trilu_template.inl" -namespace cunumeric { +namespace cupynumeric { using namespace legate; @@ -61,4 +61,4 @@ struct TriluImplBody { trilu_template(context); } -} // namespace cunumeric +} // namespace cupynumeric diff --git a/src/cunumeric/matrix/trilu_template.inl b/src/cupynumeric/matrix/trilu_template.inl similarity index 95% rename from src/cunumeric/matrix/trilu_template.inl rename to src/cupynumeric/matrix/trilu_template.inl index 5be34c2e7d..f1c51255c8 100644 --- a/src/cunumeric/matrix/trilu_template.inl +++ b/src/cupynumeric/matrix/trilu_template.inl @@ -17,10 +17,10 @@ #pragma once // Useful for IDEs -#include "cunumeric/matrix/trilu.h" -#include "cunumeric/pitches.h" +#include "cupynumeric/matrix/trilu.h" +#include "cupynumeric/pitches.h" -namespace cunumeric { +namespace cupynumeric { using namespace legate; @@ -81,4 +81,4 @@ static void trilu_template(TaskContext& context) double_dispatch(args.output.dim(), args.output.type().code(), TriluImpl{}, args); } -} // namespace cunumeric +} // namespace cupynumeric diff --git a/src/cunumeric/matrix/trsm.cc b/src/cupynumeric/matrix/trsm.cc similarity index 95% rename from src/cunumeric/matrix/trsm.cc rename to src/cupynumeric/matrix/trsm.cc index 75d50d3138..703b0c63ae 100644 --- a/src/cunumeric/matrix/trsm.cc +++ b/src/cupynumeric/matrix/trsm.cc @@ -14,13 +14,13 @@ * */ -#include "cunumeric/matrix/trsm.h" -#include "cunumeric/matrix/trsm_template.inl" +#include "cupynumeric/matrix/trsm.h" +#include "cupynumeric/matrix/trsm_template.inl" #include #include -namespace cunumeric { +namespace cupynumeric { using namespace legate; @@ -99,4 +99,4 @@ namespace // unnamed static void __attribute__((constructor)) register_tasks(void) { TrsmTask::register_variants(); } } // namespace -} // namespace cunumeric +} // namespace cupynumeric diff --git a/src/cunumeric/matrix/trsm.cu b/src/cupynumeric/matrix/trsm.cu similarity index 92% rename from src/cunumeric/matrix/trsm.cu rename to src/cupynumeric/matrix/trsm.cu index 7f77ad3711..adddfc5b33 100644 --- a/src/cunumeric/matrix/trsm.cu +++ b/src/cupynumeric/matrix/trsm.cu @@ -14,12 +14,12 @@ * */ -#include "cunumeric/matrix/trsm.h" -#include "cunumeric/matrix/trsm_template.inl" +#include "cupynumeric/matrix/trsm.h" +#include "cupynumeric/matrix/trsm_template.inl" -#include "cunumeric/cuda_help.h" +#include "cupynumeric/cuda_help.h" -namespace cunumeric { +namespace cupynumeric { using namespace legate; @@ -39,7 +39,7 @@ static inline void trsm_template( CHECK_CUBLAS(trsm(context, side, uplo, transa, diag, m, n, &alpha, rhs, n, lhs, m)); - CUNUMERIC_CHECK_CUDA_STREAM(stream); + CUPYNUMERIC_CHECK_CUDA_STREAM(stream); } template <> @@ -85,4 +85,4 @@ struct TrsmImplBody { trsm_template(context); } -} // namespace cunumeric +} // namespace cupynumeric diff --git a/src/cunumeric/matrix/trsm.h b/src/cupynumeric/matrix/trsm.h similarity index 80% rename from src/cunumeric/matrix/trsm.h rename to src/cupynumeric/matrix/trsm.h index 658a29494a..2b299dd5e3 100644 --- a/src/cunumeric/matrix/trsm.h +++ b/src/cupynumeric/matrix/trsm.h @@ -16,13 +16,13 @@ #pragma once -#include "cunumeric/cunumeric_task.h" +#include "cupynumeric/cupynumeric_task.h" -namespace cunumeric { +namespace cupynumeric { -class TrsmTask : public CuNumericTask { +class TrsmTask : public CuPyNumericTask { public: - static constexpr auto TASK_ID = legate::LocalTaskID{CUNUMERIC_TRSM}; + static constexpr auto TASK_ID = legate::LocalTaskID{CUPYNUMERIC_TRSM}; public: static void cpu_variant(legate::TaskContext context); @@ -34,4 +34,4 @@ class TrsmTask : public CuNumericTask { #endif }; -} // namespace cunumeric +} // namespace cupynumeric diff --git a/src/cunumeric/matrix/trsm_omp.cc b/src/cupynumeric/matrix/trsm_omp.cc similarity index 95% rename from src/cunumeric/matrix/trsm_omp.cc rename to src/cupynumeric/matrix/trsm_omp.cc index 039e1a59d4..f743a6e6bc 100644 --- a/src/cunumeric/matrix/trsm_omp.cc +++ b/src/cupynumeric/matrix/trsm_omp.cc @@ -14,14 +14,14 @@ * */ -#include "cunumeric/matrix/trsm.h" -#include "cunumeric/matrix/trsm_template.inl" +#include "cupynumeric/matrix/trsm.h" +#include "cupynumeric/matrix/trsm_template.inl" #include #include #include -namespace cunumeric { +namespace cupynumeric { using namespace legate; @@ -93,4 +93,4 @@ struct TrsmImplBody { trsm_template(context); } -} // namespace cunumeric +} // namespace cupynumeric diff --git a/src/cunumeric/matrix/trsm_template.inl b/src/cupynumeric/matrix/trsm_template.inl similarity index 96% rename from src/cunumeric/matrix/trsm_template.inl rename to src/cupynumeric/matrix/trsm_template.inl index 83ec265936..32006bf486 100644 --- a/src/cunumeric/matrix/trsm_template.inl +++ b/src/cupynumeric/matrix/trsm_template.inl @@ -17,9 +17,9 @@ #pragma once // Useful for IDEs -#include "cunumeric/matrix/trsm.h" +#include "cupynumeric/matrix/trsm.h" -namespace cunumeric { +namespace cupynumeric { using namespace legate; @@ -80,4 +80,4 @@ static void trsm_template(TaskContext& context) type_dispatch(lhs.type().code(), TrsmImpl{}, lhs, rhs); } -} // namespace cunumeric +} // namespace cupynumeric diff --git a/src/cunumeric/matrix/util.cc b/src/cupynumeric/matrix/util.cc similarity index 96% rename from src/cunumeric/matrix/util.cc rename to src/cupynumeric/matrix/util.cc index e1678c040a..066a493833 100644 --- a/src/cunumeric/matrix/util.cc +++ b/src/cupynumeric/matrix/util.cc @@ -15,7 +15,7 @@ */ #include "legate/data/buffer.h" -#include "cunumeric/matrix/util.h" +#include "cupynumeric/matrix/util.h" #include "legate/utilities/macros.h" #include "legate_defines.h" @@ -23,7 +23,7 @@ #include #endif -namespace cunumeric { +namespace cupynumeric { size_t stride_for_blas(size_t m, size_t n, size_t x_stride, size_t y_stride, bool& transpose) { @@ -31,14 +31,14 @@ size_t stride_for_blas(size_t m, size_t n, size_t x_stride, size_t y_stride, boo if (n == 1) { // Column matrix: Every row has exactly 1 element, therefore it is trivially contiguous. Any // stride between rows is acceptable. -#ifdef DEBUG_CUNUMERIC +#ifdef DEBUG_CUPYNUMERIC assert(x_stride >= 1); #endif blas_stride = x_stride; transpose = false; } else if (m == 1) { // Row matrix -#ifdef DEBUG_CUNUMERIC +#ifdef DEBUG_CUPYNUMERIC assert(y_stride >= 1); #endif if (y_stride == 1) { @@ -56,7 +56,7 @@ size_t stride_for_blas(size_t m, size_t n, size_t x_stride, size_t y_stride, boo // General case: One dimension needs to be contiguous. If that's not the last dimension, then // the matrix represents the transpose of a row-major nxm matrix. We then tell the BLAS library // that we are passing a row-major nxm matrix, and ask for the matrix to be transposed. -#ifdef DEBUG_CUNUMERIC +#ifdef DEBUG_CUPYNUMERIC assert((x_stride == 1 && y_stride > 1) || (y_stride == 1 && x_stride > 1)); #endif blas_stride = std::max(x_stride, y_stride); @@ -159,4 +159,4 @@ void float_tensor_to_half( } } -} // namespace cunumeric +} // namespace cupynumeric diff --git a/src/cunumeric/matrix/util.h b/src/cupynumeric/matrix/util.h similarity index 97% rename from src/cunumeric/matrix/util.h rename to src/cupynumeric/matrix/util.h index 91b9797510..a9f4a13b35 100644 --- a/src/cunumeric/matrix/util.h +++ b/src/cupynumeric/matrix/util.h @@ -18,7 +18,7 @@ #include "mathtypes/half.h" -namespace cunumeric { +namespace cupynumeric { size_t stride_for_blas(size_t m, size_t n, size_t x_stride, size_t y_stride, bool& transpose); @@ -55,4 +55,4 @@ void half_tensor_to_float( void float_tensor_to_half( __half* out, const float* in, size_t ndim, const int64_t* shape, const int64_t* out_strides); -} // namespace cunumeric +} // namespace cupynumeric diff --git a/src/cunumeric/ndarray.cc b/src/cupynumeric/ndarray.cc similarity index 90% rename from src/cunumeric/ndarray.cc rename to src/cupynumeric/ndarray.cc index 34631c5cda..c3d6ebcbd3 100644 --- a/src/cunumeric/ndarray.cc +++ b/src/cupynumeric/ndarray.cc @@ -14,19 +14,19 @@ * */ -#include "cunumeric/ndarray.h" +#include "cupynumeric/ndarray.h" #include #include -#include "cunumeric/binary/binary_op_util.h" -#include "cunumeric/operators.h" -#include "cunumeric/random/rand_util.h" -#include "cunumeric/runtime.h" -#include "cunumeric/unary/convert_util.h" -#include "cunumeric/unary/unary_op_util.h" -#include "cunumeric/unary/unary_red_util.h" +#include "cupynumeric/binary/binary_op_util.h" +#include "cupynumeric/operators.h" +#include "cupynumeric/random/rand_util.h" +#include "cupynumeric/runtime.h" +#include "cupynumeric/unary/convert_util.h" +#include "cupynumeric/unary/unary_op_util.h" +#include "cupynumeric/unary/unary_red_util.h" -namespace cunumeric { +namespace cupynumeric { // ========================================================================================== @@ -45,7 +45,7 @@ struct generate_zero_fn { struct check_nonzero_scalar_fn { template - bool operator()(cunumeric::NDArray array) + bool operator()(cupynumeric::NDArray array) { assert(array.dim() == 0); using VAL = legate::type_of; @@ -83,7 +83,7 @@ struct generate_identity_fn { Scalar operator()(const legate::Type& type) { auto value = UnaryRedOp::OP::identity; - auto argred_type = CuNumericRuntime::get_runtime()->get_argred_type(type); + auto argred_type = CuPyNumericRuntime::get_runtime()->get_argred_type(type); return Scalar(value, argred_type); } @@ -165,7 +165,7 @@ NDArray NDArray::operator+(const NDArray& other) const { return add(*this, other NDArray NDArray::operator+(const legate::Scalar& other) const { - auto runtime = CuNumericRuntime::get_runtime(); + auto runtime = CuPyNumericRuntime::get_runtime(); auto scalar = runtime->create_scalar_store(other); return operator+(NDArray(std::move(scalar))); } @@ -180,7 +180,7 @@ NDArray NDArray::operator*(const NDArray& other) const { return multiply(*this, NDArray NDArray::operator*(const legate::Scalar& other) const { - auto runtime = CuNumericRuntime::get_runtime(); + auto runtime = CuPyNumericRuntime::get_runtime(); auto scalar = runtime->create_scalar_store(other); return operator*(NDArray(std::move(scalar))); } @@ -218,7 +218,7 @@ void NDArray::assign(const NDArray& other) void NDArray::assign(const legate::Scalar& other) { - auto runtime = CuNumericRuntime::get_runtime(); + auto runtime = CuPyNumericRuntime::get_runtime(); auto scalar = runtime->create_scalar_store(other); assign(NDArray(std::move(scalar))); } @@ -229,9 +229,9 @@ void NDArray::random(int32_t gen_code) return; } - auto runtime = CuNumericRuntime::get_runtime(); + auto runtime = CuPyNumericRuntime::get_runtime(); - auto task = runtime->create_task(CuNumericOpCode::CUNUMERIC_RAND); + auto task = runtime->create_task(CuPyNumericOpCode::CUPYNUMERIC_RAND); task.add_output(store_); task.add_scalar_arg(legate::Scalar(static_cast(RandGenCode::UNIFORM))); @@ -248,7 +248,7 @@ void NDArray::fill(const Scalar& value) return; } - auto runtime = CuNumericRuntime::get_runtime(); + auto runtime = CuPyNumericRuntime::get_runtime(); if (!store_.transformed()) { legate::Runtime::get_runtime()->issue_fill(store_, value); @@ -257,7 +257,7 @@ void NDArray::fill(const Scalar& value) auto fill_value = runtime->create_scalar_store(value); - auto task = runtime->create_task(CuNumericOpCode::CUNUMERIC_FILL); + auto task = runtime->create_task(CuPyNumericOpCode::CUPYNUMERIC_FILL); task.add_output(store_); task.add_input(fill_value); @@ -277,8 +277,8 @@ void NDArray::_fill(legate::LogicalStore const& value) return; } - auto runtime = CuNumericRuntime::get_runtime(); - auto task = runtime->create_task(CuNumericOpCode::CUNUMERIC_FILL); + auto runtime = CuPyNumericRuntime::get_runtime(); + auto task = runtime->create_task(CuPyNumericOpCode::CUPYNUMERIC_FILL); task.add_output(store_); task.add_input(value); task.add_scalar_arg(Scalar(false)); @@ -296,9 +296,9 @@ void NDArray::eye(int32_t k) auto zero = legate::type_dispatch(type().code(), generate_zero_fn{}); fill(zero); - auto runtime = CuNumericRuntime::get_runtime(); + auto runtime = CuPyNumericRuntime::get_runtime(); - auto task = runtime->create_task(CuNumericOpCode::CUNUMERIC_EYE); + auto task = runtime->create_task(CuPyNumericOpCode::CUPYNUMERIC_EYE); task.add_input(store_); task.add_output(store_); @@ -315,7 +315,7 @@ void NDArray::bincount(NDArray rhs, std::optional weights /*=std::nullo assert(dim() == 1); - auto runtime = CuNumericRuntime::get_runtime(); + auto runtime = CuPyNumericRuntime::get_runtime(); if (weights.has_value()) { assert(rhs.shape() == weights.value().shape()); @@ -324,7 +324,7 @@ void NDArray::bincount(NDArray rhs, std::optional weights /*=std::nullo auto zero = legate::type_dispatch(type().code(), generate_zero_fn{}); fill(zero); - auto task = runtime->create_task(CuNumericOpCode::CUNUMERIC_BINCOUNT); + auto task = runtime->create_task(CuPyNumericOpCode::CUPYNUMERIC_BINCOUNT); legate::ReductionOpKind redop = legate::ReductionOpKind::ADD; auto p_lhs = task.add_reduction(store_, redop); @@ -340,8 +340,8 @@ void NDArray::bincount(NDArray rhs, std::optional weights /*=std::nullo void NDArray::sort_task(NDArray rhs, bool argsort, bool stable) { - auto runtime = CuNumericRuntime::get_runtime(); - auto task = runtime->create_task(CuNumericOpCode::CUNUMERIC_SORT); + auto runtime = CuPyNumericRuntime::get_runtime(); + auto task = runtime->create_task(CuPyNumericOpCode::CUPYNUMERIC_SORT); auto p_rhs = task.add_input(rhs.store_); auto machine = legate::Runtime::get_runtime()->get_machine(); @@ -375,7 +375,7 @@ void NDArray::sort_swapped(NDArray rhs, bool argsort, int32_t sort_axis, bool st sort_axis = normalize_axis_index(sort_axis, rhs.dim()); auto swapped = rhs.swapaxes(sort_axis, rhs.dim() - 1); - auto runtime = CuNumericRuntime::get_runtime(); + auto runtime = CuPyNumericRuntime::get_runtime(); auto swapped_copy = runtime->create_array(swapped.shape(), swapped.type()); swapped_copy.assign(swapped); @@ -430,9 +430,9 @@ void NDArray::trilu(NDArray rhs, int32_t k, bool lower) return; } - auto runtime = CuNumericRuntime::get_runtime(); + auto runtime = CuPyNumericRuntime::get_runtime(); - auto task = runtime->create_task(CuNumericOpCode::CUNUMERIC_TRILU); + auto task = runtime->create_task(CuPyNumericOpCode::CUPYNUMERIC_TRILU); auto& out_shape = shape(); rhs = rhs.broadcast(out_shape, rhs.store_); @@ -458,9 +458,9 @@ void NDArray::binary_op(int32_t op_code, NDArray rhs1, NDArray rhs2) return; } - auto runtime = CuNumericRuntime::get_runtime(); + auto runtime = CuPyNumericRuntime::get_runtime(); - auto task = runtime->create_task(CuNumericOpCode::CUNUMERIC_BINARY_OP); + auto task = runtime->create_task(CuPyNumericOpCode::CUPYNUMERIC_BINARY_OP); auto& out_shape = shape(); auto rhs1_store = broadcast(out_shape, rhs1.store_); @@ -483,7 +483,7 @@ void NDArray::binary_reduction(int32_t op_code, NDArray rhs1, NDArray rhs2) return; } - auto runtime = CuNumericRuntime::get_runtime(); + auto runtime = CuPyNumericRuntime::get_runtime(); auto rhs1_store = broadcast(rhs1, rhs2); auto rhs2_store = broadcast(rhs2, rhs1); @@ -496,7 +496,7 @@ void NDArray::binary_reduction(int32_t op_code, NDArray rhs1, NDArray rhs2) redop = get_reduction_op(UnaryRedCode::PROD); fill(legate::Scalar(true)); } - auto task = runtime->create_task(CuNumericOpCode::CUNUMERIC_BINARY_RED); + auto task = runtime->create_task(CuPyNumericOpCode::CUPYNUMERIC_BINARY_RED); task.add_reduction(store_, redop); auto p_rhs1 = task.add_input(rhs1_store); @@ -516,9 +516,9 @@ void NDArray::unary_op(int32_t op_code, return; } - auto runtime = CuNumericRuntime::get_runtime(); + auto runtime = CuPyNumericRuntime::get_runtime(); - auto task = runtime->create_task(CuNumericOpCode::CUNUMERIC_UNARY_OP); + auto task = runtime->create_task(CuPyNumericOpCode::CUPYNUMERIC_UNARY_OP); auto rhs = broadcast(shape(), input.store_); @@ -541,13 +541,13 @@ void NDArray::unary_reduction(int32_t op_code_, NDArray input) return; } - auto runtime = CuNumericRuntime::get_runtime(); + auto runtime = CuPyNumericRuntime::get_runtime(); auto op_code = static_cast(op_code_); fill(get_reduction_identity(op_code, type())); - auto task = runtime->create_task(CuNumericOpCode::CUNUMERIC_SCALAR_UNARY_RED); + auto task = runtime->create_task(CuPyNumericOpCode::CUPYNUMERIC_SCALAR_UNARY_RED); task.add_reduction(store_, get_reduction_op(op_code)); task.add_input(input.store_); @@ -566,7 +566,7 @@ void NDArray::dot(NDArray rhs1, NDArray rhs2) return; } - auto runtime = CuNumericRuntime::get_runtime(); + auto runtime = CuPyNumericRuntime::get_runtime(); fill(get_reduction_identity(UnaryRedCode::SUM, type())); @@ -587,7 +587,7 @@ void NDArray::dot(NDArray rhs1, NDArray rhs2) auto get_batchsize = [&](const std::vector& tilesize, std::uint64_t k) { uint64_t typesize = legate::type_dispatch(type().code(), get_typesize_fn{}); // default corresponds to 128MB (to store A and B tile) - uint64_t max_elements_per_tile = cunumeric_matmul_cache_size() / typesize; + uint64_t max_elements_per_tile = cupynumeric_matmul_cache_size() / typesize; uint64_t total_elements_rhs = (tilesize[0] + tilesize[1]) * k; uint64_t num_batches = ceildiv(total_elements_rhs, max_elements_per_tile); uint64_t batch_size = ceildiv(k, num_batches); @@ -604,7 +604,7 @@ void NDArray::dot(NDArray rhs1, NDArray rhs2) auto p_rhs2 = rhs2.store_.partition_by_tiling(tile_shape_rhs2); for (std::uint64_t i = 0; i < color_k; ++i) { - auto task = runtime->create_task(CuNumericOpCode::CUNUMERIC_MATMUL, color_shape); + auto task = runtime->create_task(CuPyNumericOpCode::CUPYNUMERIC_MATMUL, color_shape); task.add_output(p_lhs); task.add_input(p_lhs); task.add_input(p_rhs1, legate::SymbolicPoint{legate::dimension(0), legate::constant(i)}); @@ -619,7 +619,7 @@ void NDArray::arange(Scalar start, Scalar stop, Scalar step) return; } - auto runtime = CuNumericRuntime::get_runtime(); + auto runtime = CuPyNumericRuntime::get_runtime(); if (start.type() != type() || stop.type() != type() || step.type() != type()) { throw std::invalid_argument("start/stop/step should have the same type as the array"); @@ -629,7 +629,7 @@ void NDArray::arange(Scalar start, Scalar stop, Scalar step) // TODO: Optimization when value is a scalar - auto task = runtime->create_task(CuNumericOpCode::CUNUMERIC_ARANGE); + auto task = runtime->create_task(CuPyNumericOpCode::CUPYNUMERIC_ARANGE); task.add_output(store_); @@ -641,7 +641,7 @@ void NDArray::arange(Scalar start, Scalar stop, Scalar step) std::vector NDArray::nonzero() { - auto runtime = CuNumericRuntime::get_runtime(); + auto runtime = CuPyNumericRuntime::get_runtime(); std::vector outputs; auto ndim = dim(); @@ -649,7 +649,7 @@ std::vector NDArray::nonzero() outputs.emplace_back(runtime->create_array(legate::int64())); } - auto task = runtime->create_task(CuNumericOpCode::CUNUMERIC_NONZERO); + auto task = runtime->create_task(CuPyNumericOpCode::CUPYNUMERIC_NONZERO); for (auto& output : outputs) { task.add_output(output.store_); @@ -670,10 +670,10 @@ NDArray NDArray::unique() auto machine = legate::Runtime::get_runtime()->get_machine(); bool has_gpus = machine.count(legate::mapping::TaskTarget::GPU) > 0; - auto runtime = CuNumericRuntime::get_runtime(); + auto runtime = CuPyNumericRuntime::get_runtime(); auto result = runtime->create_array(type()); - auto task = runtime->create_task(CuNumericOpCode::CUNUMERIC_UNIQUE); + auto task = runtime->create_task(CuPyNumericOpCode::CUPYNUMERIC_UNIQUE); auto part_out = task.declare_partition(); auto part_in = task.declare_partition(); task.add_output(result.store_, part_out); @@ -708,13 +708,13 @@ NDArray NDArray::swapaxes(int32_t axis1, int32_t axis2) std::swap(dims[axis1], dims[axis2]); auto transposed = store_.transpose(std::move(dims)); - auto runtime = CuNumericRuntime::get_runtime(); + auto runtime = CuPyNumericRuntime::get_runtime(); return runtime->create_array(std::move(transposed)); } NDArray NDArray::as_type(const legate::Type& type) const { - auto runtime = CuNumericRuntime::get_runtime(); + auto runtime = CuPyNumericRuntime::get_runtime(); // TODO: Check if conversion is valid @@ -734,9 +734,9 @@ void NDArray::create_window(int32_t op_code, int64_t M, std::vector args return; } - auto runtime = CuNumericRuntime::get_runtime(); + auto runtime = CuPyNumericRuntime::get_runtime(); - auto task = runtime->create_task(CuNumericOpCode::CUNUMERIC_WINDOW); + auto task = runtime->create_task(CuPyNumericOpCode::CUPYNUMERIC_WINDOW); task.add_output(store_); task.add_scalar_arg(legate::Scalar(op_code)); @@ -751,9 +751,9 @@ void NDArray::create_window(int32_t op_code, int64_t M, std::vector args void NDArray::convolve(NDArray input, NDArray filter) { - auto runtime = CuNumericRuntime::get_runtime(); + auto runtime = CuPyNumericRuntime::get_runtime(); - auto task = runtime->create_task(CuNumericOpCode::CUNUMERIC_CONVOLVE); + auto task = runtime->create_task(CuPyNumericOpCode::CUPYNUMERIC_CONVOLVE); auto p_filter = task.add_input(filter.store_); auto p_input = task.add_input(input.store_); @@ -796,7 +796,7 @@ NDArray NDArray::transpose(std::vector axes) NDArray NDArray::argwhere() { - auto runtime = CuNumericRuntime::get_runtime(); + auto runtime = CuPyNumericRuntime::get_runtime(); if (dim() == 0) { auto not_zero = legate::type_dispatch(type().code(), check_nonzero_scalar_fn{}, *this); if (not_zero) { @@ -810,7 +810,7 @@ NDArray NDArray::argwhere() auto result = runtime->create_array(legate::int64(), 2); - auto task = runtime->create_task(CuNumericOpCode::CUNUMERIC_ARGWHERE); + auto task = runtime->create_task(CuPyNumericOpCode::CUPYNUMERIC_ARGWHERE); auto part_out = task.declare_partition(); auto part_in = task.declare_partition(); task.add_output(result.store_, part_out); @@ -824,7 +824,7 @@ NDArray NDArray::argwhere() NDArray NDArray::flip(std::optional> axis) { - auto runtime = CuNumericRuntime::get_runtime(); + auto runtime = CuPyNumericRuntime::get_runtime(); auto result = runtime->create_array(shape(), type()); result.flip(*this, axis); @@ -846,8 +846,8 @@ void NDArray::flip(NDArray rhs, std::optional> axis) axes = normalize_axis_vector(axis.value(), dim()); } - auto runtime = CuNumericRuntime::get_runtime(); - auto task = runtime->create_task(CuNumericOpCode::CUNUMERIC_FLIP); + auto runtime = CuPyNumericRuntime::get_runtime(); + auto task = runtime->create_task(CuPyNumericOpCode::CUPYNUMERIC_FLIP); auto p_out = task.add_output(output); auto p_in = task.add_input(input); task.add_scalar_arg(legate::Scalar(axes)); @@ -950,7 +950,7 @@ NDArray NDArray::_perform_unary_reduction(int32_t op, } } - auto runtime = CuNumericRuntime::get_runtime(); + auto runtime = CuPyNumericRuntime::get_runtime(); if (!out.has_value()) { out = runtime->create_array(out_shape, res_dtype.value()); } else if (out.value().shape() != out_shape) { @@ -1009,7 +1009,7 @@ void NDArray::unary_reduction(int32_t op, auto rhs_array = src; assert(lhs_array.dim() <= rhs_array.dim()); - auto runtime = CuNumericRuntime::get_runtime(); + auto runtime = CuPyNumericRuntime::get_runtime(); auto op_code = static_cast(op); if (initial.has_value()) { @@ -1028,7 +1028,7 @@ void NDArray::unary_reduction(int32_t op, p_lhs = p_lhs.project(0, 0); } - auto task = runtime->create_task(CuNumericOpCode::CUNUMERIC_SCALAR_UNARY_RED); + auto task = runtime->create_task(CuPyNumericOpCode::CUPYNUMERIC_SCALAR_UNARY_RED); task.add_reduction(p_lhs, get_reduction_op(op_code)); auto p_rhs = task.add_input(rhs_array.store_); @@ -1065,7 +1065,7 @@ void NDArray::unary_reduction(int32_t op, throw std::runtime_error("Need support for reducing multiple dimensions"); } - auto task = runtime->create_task(CuNumericOpCode::CUNUMERIC_UNARY_RED); + auto task = runtime->create_task(CuPyNumericOpCode::CUPYNUMERIC_UNARY_RED); auto p_lhs = task.add_reduction(result, get_reduction_op(op_code)); auto p_rhs = task.add_input(rhs_array.store_); @@ -1094,7 +1094,7 @@ NDArray NDArray::broadcast_where(NDArray where, NDArray source) auto where_shape = broadcast_shapes({where, source}); auto where_store = broadcast(where_shape, where.store_); - auto runtime = CuNumericRuntime::get_runtime(); + auto runtime = CuPyNumericRuntime::get_runtime(); return runtime->create_array(std::move(where_store)); } @@ -1107,8 +1107,8 @@ void NDArray::convert(NDArray rhs, int32_t nan_op) auto lhs_s = lhs_array.store_; auto rhs_s = rhs_array.store_; - auto runtime = CuNumericRuntime::get_runtime(); - auto task = runtime->create_task(CuNumericOpCode::CUNUMERIC_CONVERT); + auto runtime = CuPyNumericRuntime::get_runtime(); + auto task = runtime->create_task(CuPyNumericOpCode::CUPYNUMERIC_CONVERT); auto p_lhs = task.add_output(lhs_s); auto p_rhs = task.add_input(rhs_s); task.add_scalar_arg(legate::Scalar(nan_op)); @@ -1123,7 +1123,7 @@ NDArray NDArray::diag_helper(int32_t offset, const std::optional& type, std::optional out) { - auto runtime = CuNumericRuntime::get_runtime(); + auto runtime = CuPyNumericRuntime::get_runtime(); if (dim() <= 1) { throw std::invalid_argument("diag_helper is implemented for dim > 1"); @@ -1239,7 +1239,7 @@ NDArray NDArray::diag_helper(int32_t offset, void NDArray::diag_task(NDArray rhs, int32_t offset, int32_t naxes, bool extract, bool trace) { - auto runtime = CuNumericRuntime::get_runtime(); + auto runtime = CuPyNumericRuntime::get_runtime(); legate::LogicalStore diag = get_store(); legate::LogicalStore matrix = get_store(); @@ -1294,7 +1294,7 @@ void NDArray::diag_task(NDArray rhs, int32_t offset, int32_t naxes, bool extract } } - auto task = runtime->create_task(CuNumericOpCode::CUNUMERIC_DIAG); + auto task = runtime->create_task(CuPyNumericOpCode::CUPYNUMERIC_DIAG); if (extract) { auto p_diag = task.add_reduction(diag, get_reduction_op(UnaryRedCode::SUM)); auto p_matrix = task.add_input(matrix); @@ -1361,9 +1361,9 @@ void NDArray::put(NDArray indices, NDArray values, std::string mode) self_tmp = self_tmp._convert_future_to_regionfield(change_shape); } - auto runtime = CuNumericRuntime::get_runtime(); + auto runtime = CuPyNumericRuntime::get_runtime(); bool check_bounds = (mode == "raise"); - auto task = runtime->create_task(CuNumericOpCode::CUNUMERIC_WRAP); + auto task = runtime->create_task(CuPyNumericOpCode::CUPYNUMERIC_WRAP); auto indirect = runtime->create_array(indices.shape(), legate::point_type(self_tmp.dim()), false); auto p_indirect = task.add_output(indirect.store_); auto p_indices = task.add_input(indices.store_); @@ -1387,7 +1387,7 @@ void NDArray::put(NDArray indices, NDArray values, std::string mode) NDArray NDArray::copy() { - auto runtime = CuNumericRuntime::get_runtime(); + auto runtime = CuPyNumericRuntime::get_runtime(); auto legate_runtime = legate::Runtime::get_runtime(); auto out = runtime->create_array(shape(), type()); if (store_.has_scalar_storage() && out.store_.has_scalar_storage()) { @@ -1404,7 +1404,7 @@ NDArray NDArray::repeat(int64_t repeats, std::optional axis) throw std::invalid_argument("negative dimensions are not allowed"); } - auto runtime = CuNumericRuntime::get_runtime(); + auto runtime = CuPyNumericRuntime::get_runtime(); // when array is a scalar if (dim() == 0) { @@ -1437,7 +1437,7 @@ NDArray NDArray::repeat(int64_t repeats, std::optional axis) return runtime->create_array(empty_shape, src.type()); } - auto task = runtime->create_task(CuNumericOpCode::CUNUMERIC_REPEAT); + auto task = runtime->create_task(CuPyNumericOpCode::CUPYNUMERIC_REPEAT); auto out_shape = src.shape(); out_shape[axis_int] *= repeats; @@ -1501,10 +1501,10 @@ NDArray NDArray::repeat(NDArray repeats, std::optional axis) repeats = repeats._warn_and_convert(legate::int64()); } - auto runtime = CuNumericRuntime::get_runtime(); + auto runtime = CuPyNumericRuntime::get_runtime(); auto legate_runtime = legate::Runtime::get_runtime(); auto out_store = legate_runtime->create_store(src.type(), src.dim()); - auto task = runtime->create_task(CuNumericOpCode::CUNUMERIC_REPEAT); + auto task = runtime->create_task(CuPyNumericOpCode::CUPYNUMERIC_REPEAT); auto p_src = task.add_input(src.store_); task.add_output(out_store); task.add_scalar_arg(Scalar(axis_int)); @@ -1526,7 +1526,7 @@ NDArray NDArray::repeat(NDArray repeats, std::optional axis) NDArray NDArray::_convert_future_to_regionfield(bool change_shape) { - auto runtime = CuNumericRuntime::get_runtime(); + auto runtime = CuPyNumericRuntime::get_runtime(); if (change_shape && dim() == 0) { auto out = runtime->create_array({1}, type(), false); out.assign(*this); @@ -1539,7 +1539,7 @@ NDArray NDArray::_convert_future_to_regionfield(bool change_shape) NDArray NDArray::_wrap(size_t new_len) { - auto runtime = CuNumericRuntime::get_runtime(); + auto runtime = CuPyNumericRuntime::get_runtime(); if (0 == new_len) { return runtime->create_array({0}, type()); @@ -1564,7 +1564,7 @@ NDArray NDArray::_wrap(size_t new_len) src = src._convert_future_to_regionfield(change_shape); } - auto task = runtime->create_task(CuNumericOpCode::CUNUMERIC_WRAP); + auto task = runtime->create_task(CuPyNumericOpCode::CUPYNUMERIC_WRAP); auto indirect = runtime->create_array({new_len}, legate::point_type(src.dim()), false); task.add_output(indirect.store_); task.add_scalar_arg(legate::Scalar(src.shape())); @@ -1584,7 +1584,7 @@ NDArray NDArray::_warn_and_convert(legate::Type const& type) if (this->type() != type) { std::stringstream ss; ss << "converting array to " << type.to_string() << " type"; - cunumeric_log().warning() << ss.str(); + cupynumeric_log().warning() << ss.str(); return as_type(type); } return *this; @@ -1592,18 +1592,18 @@ NDArray NDArray::_warn_and_convert(legate::Type const& type) NDArray NDArray::wrap_indices(Scalar const& n) { - auto runtime = CuNumericRuntime::get_runtime(); + auto runtime = CuPyNumericRuntime::get_runtime(); auto out = runtime->create_array(shape(), type()); - auto divisor = cunumeric::full({}, n); - out.binary_op(static_cast(cunumeric::BinaryOpCode::MOD), *this, divisor); + auto divisor = cupynumeric::full({}, n); + out.binary_op(static_cast(cupynumeric::BinaryOpCode::MOD), *this, divisor); return out; } NDArray NDArray::clip_indices(Scalar const& min, Scalar const& max) { - auto runtime = CuNumericRuntime::get_runtime(); + auto runtime = CuPyNumericRuntime::get_runtime(); auto out = runtime->create_array(shape(), type()); - auto task = runtime->create_task(CuNumericOpCode::CUNUMERIC_UNARY_OP); + auto task = runtime->create_task(CuPyNumericOpCode::CUPYNUMERIC_UNARY_OP); auto p_out = task.add_output(out.store_); auto p_in = task.add_input(store_); task.add_scalar_arg(legate::Scalar(static_cast(UnaryOpCode::CLIP))); @@ -1626,7 +1626,7 @@ NDArray NDArray::diagonal(int32_t offset, if (axis1 || axis2) { throw std::invalid_argument("Axes shouldn't be specified when getting diagonal for 1D array"); } - auto runtime = CuNumericRuntime::get_runtime(); + auto runtime = CuPyNumericRuntime::get_runtime(); auto m = shape()[0] + std::abs(offset); auto res = runtime->create_array({m, m}, store_.type()); res.diag_task(*this, offset, 0, false, false); @@ -1666,7 +1666,7 @@ NDArray NDArray::reshape(std::vector newshape, std::string order) } if (order == "F") { throw std::invalid_argument( - "cuNumeric has not implemented reshape using Fortran-like index order."); + "cuPyNumeric has not implemented reshape using Fortran-like index order."); } if (order != "C") { throw std::invalid_argument("order must be one of 'C', 'F', 'A'"); @@ -1676,7 +1676,7 @@ NDArray NDArray::reshape(std::vector newshape, std::string order) NDArray NDArray::reshape(std::vector newshape) { - auto runtime = cunumeric::CuNumericRuntime::get_runtime(); + auto runtime = cupynumeric::CuPyNumericRuntime::get_runtime(); int num_unknowns = std::count_if(newshape.begin(), newshape.end(), [](auto x) { return x < 0; }); if (num_unknowns > 1) { throw std::invalid_argument("can only specify one unknown dimension"); @@ -1810,7 +1810,7 @@ NDArray NDArray::squeeze( if (result.extents().data() == store_.extents().data()) { return *this; } else { - auto runtime = CuNumericRuntime::get_runtime(); + auto runtime = CuPyNumericRuntime::get_runtime(); return runtime->create_array(std::move(result)); } } @@ -1824,8 +1824,8 @@ void NDArray::where(NDArray rhs1, NDArray rhs2, NDArray rhs3) assert(store_.type() == rhs2.store_.type()); assert(store_.type() == rhs3.store_.type()); - auto runtime = CuNumericRuntime::get_runtime(); - auto task = runtime->create_task(CuNumericOpCode::CUNUMERIC_WHERE); + auto runtime = CuPyNumericRuntime::get_runtime(); + auto task = runtime->create_task(CuPyNumericOpCode::CUPYNUMERIC_WHERE); auto p_lhs = task.declare_partition(); auto p_rhs1 = task.declare_partition(); @@ -1860,7 +1860,7 @@ legate::LogicalStore NDArray::broadcast(const std::vector& shape, { int32_t diff = static_cast(shape.size()) - store.dim(); -#ifdef DEBUG_CUNUMERIC +#ifdef DEBUG_CUPYNUMERIC assert(diff >= 0); #endif @@ -1872,14 +1872,14 @@ legate::LogicalStore NDArray::broadcast(const std::vector& shape, std::vector orig_shape = result.extents().data(); for (uint32_t dim = 0; dim < shape.size(); ++dim) { if (orig_shape[dim] != shape[dim]) { -#ifdef DEBUG_CUNUMERIC +#ifdef DEBUG_CUPYNUMERIC assert(orig_shape[dim] == 1); #endif result = result.project(dim, 0).promote(dim, shape[dim]); } } -#ifdef DEBUG_CUNUMERIC +#ifdef DEBUG_CUPYNUMERIC assert(static_cast(result.dim()) == shape.size()); #endif @@ -1897,7 +1897,7 @@ legate::LogicalStore NDArray::broadcast(NDArray rhs1, NDArray rhs2) /*static*/ legate::Library NDArray::get_library() { - return CuNumericRuntime::get_runtime()->get_library(); + return CuPyNumericRuntime::get_runtime()->get_library(); } -} // namespace cunumeric +} // namespace cupynumeric diff --git a/src/cunumeric/ndarray.h b/src/cupynumeric/ndarray.h similarity index 97% rename from src/cunumeric/ndarray.h rename to src/cupynumeric/ndarray.h index 2f5a4a0d82..87b2c5292f 100644 --- a/src/cunumeric/ndarray.h +++ b/src/cupynumeric/ndarray.h @@ -20,13 +20,13 @@ #include #include "legate.h" -#include "cunumeric/slice.h" -#include "cunumeric/typedefs.h" +#include "cupynumeric/slice.h" +#include "cupynumeric/typedefs.h" -namespace cunumeric { +namespace cupynumeric { class NDArray { - friend class CuNumericRuntime; + friend class CuPyNumericRuntime; private: NDArray(legate::LogicalStore&& store); @@ -170,6 +170,6 @@ class NDArray { legate::LogicalStore store_; }; -} // namespace cunumeric +} // namespace cupynumeric -#include "cunumeric/ndarray.inl" +#include "cupynumeric/ndarray.inl" diff --git a/src/cunumeric/ndarray.inl b/src/cupynumeric/ndarray.inl similarity index 94% rename from src/cunumeric/ndarray.inl rename to src/cupynumeric/ndarray.inl index 341e957ab6..31cbe83a3a 100644 --- a/src/cunumeric/ndarray.inl +++ b/src/cupynumeric/ndarray.inl @@ -14,7 +14,7 @@ * */ -namespace cunumeric { +namespace cupynumeric { template legate::AccessorRO NDArray::get_read_accessor() @@ -30,4 +30,4 @@ legate::AccessorWO NDArray::get_write_accessor() return mapped.write_accessor(); } -} // namespace cunumeric +} // namespace cupynumeric diff --git a/src/cunumeric/nullary/arange.cc b/src/cupynumeric/nullary/arange.cc similarity index 89% rename from src/cunumeric/nullary/arange.cc rename to src/cupynumeric/nullary/arange.cc index fc3b42608d..9acc578499 100644 --- a/src/cunumeric/nullary/arange.cc +++ b/src/cupynumeric/nullary/arange.cc @@ -14,10 +14,10 @@ * */ -#include "cunumeric/nullary/arange.h" -#include "cunumeric/nullary/arange_template.inl" +#include "cupynumeric/nullary/arange.h" +#include "cupynumeric/nullary/arange_template.inl" -namespace cunumeric { +namespace cupynumeric { using namespace legate; @@ -44,4 +44,4 @@ namespace // unnamed static void __attribute__((constructor)) register_tasks(void) { ArangeTask::register_variants(); } } // namespace -} // namespace cunumeric +} // namespace cupynumeric diff --git a/src/cunumeric/nullary/arange.cu b/src/cupynumeric/nullary/arange.cu similarity index 88% rename from src/cunumeric/nullary/arange.cu rename to src/cupynumeric/nullary/arange.cu index 08968221cd..94ba2e7472 100644 --- a/src/cunumeric/nullary/arange.cu +++ b/src/cupynumeric/nullary/arange.cu @@ -14,12 +14,12 @@ * */ -#include "cunumeric/nullary/arange.h" -#include "cunumeric/nullary/arange_template.inl" +#include "cupynumeric/nullary/arange.h" +#include "cupynumeric/nullary/arange_template.inl" -#include "cunumeric/cuda_help.h" +#include "cupynumeric/cuda_help.h" -namespace cunumeric { +namespace cupynumeric { template __global__ static void __launch_bounds__(THREADS_PER_BLOCK, MIN_CTAS_PER_SM) arange_kernel( @@ -45,7 +45,7 @@ struct ArangeImplBody { auto stream = get_cached_stream(); arange_kernel <<>>(out, rect.lo[0], start, step, distance); - CUNUMERIC_CHECK_CUDA_STREAM(stream); + CUPYNUMERIC_CHECK_CUDA_STREAM(stream); } }; @@ -54,4 +54,4 @@ struct ArangeImplBody { arange_template(context); } -} // namespace cunumeric +} // namespace cupynumeric diff --git a/src/cunumeric/nullary/arange.h b/src/cupynumeric/nullary/arange.h similarity index 81% rename from src/cunumeric/nullary/arange.h rename to src/cupynumeric/nullary/arange.h index 46ac863c0a..52f157c313 100644 --- a/src/cunumeric/nullary/arange.h +++ b/src/cupynumeric/nullary/arange.h @@ -16,9 +16,9 @@ #pragma once -#include "cunumeric/cunumeric_task.h" +#include "cupynumeric/cupynumeric_task.h" -namespace cunumeric { +namespace cupynumeric { struct ArangeArgs { legate::PhysicalStore out; @@ -26,9 +26,9 @@ struct ArangeArgs { legate::Scalar step; }; -class ArangeTask : public CuNumericTask { +class ArangeTask : public CuPyNumericTask { public: - static constexpr auto TASK_ID = legate::LocalTaskID{CUNUMERIC_ARANGE}; + static constexpr auto TASK_ID = legate::LocalTaskID{CUPYNUMERIC_ARANGE}; public: static void cpu_variant(legate::TaskContext context); @@ -40,4 +40,4 @@ class ArangeTask : public CuNumericTask { #endif }; -} // namespace cunumeric +} // namespace cupynumeric diff --git a/src/cunumeric/nullary/arange_omp.cc b/src/cupynumeric/nullary/arange_omp.cc similarity index 88% rename from src/cunumeric/nullary/arange_omp.cc rename to src/cupynumeric/nullary/arange_omp.cc index 9fccfee3a0..6ebc80bbeb 100644 --- a/src/cunumeric/nullary/arange_omp.cc +++ b/src/cupynumeric/nullary/arange_omp.cc @@ -14,10 +14,10 @@ * */ -#include "cunumeric/nullary/arange.h" -#include "cunumeric/nullary/arange_template.inl" +#include "cupynumeric/nullary/arange.h" +#include "cupynumeric/nullary/arange_template.inl" -namespace cunumeric { +namespace cupynumeric { using namespace legate; @@ -40,4 +40,4 @@ struct ArangeImplBody { arange_template(context); } -} // namespace cunumeric +} // namespace cupynumeric diff --git a/src/cunumeric/nullary/arange_template.inl b/src/cupynumeric/nullary/arange_template.inl similarity index 88% rename from src/cunumeric/nullary/arange_template.inl rename to src/cupynumeric/nullary/arange_template.inl index 82bf335b91..29ba759c2f 100644 --- a/src/cunumeric/nullary/arange_template.inl +++ b/src/cupynumeric/nullary/arange_template.inl @@ -17,12 +17,12 @@ #pragma once // Useful for IDEs -#include "cunumeric/nullary/arange.h" -#include "cunumeric/arg.h" -#include "cunumeric/arg.inl" -#include "cunumeric/pitches.h" +#include "cupynumeric/nullary/arange.h" +#include "cupynumeric/arg.h" +#include "cupynumeric/arg.inl" +#include "cupynumeric/pitches.h" -namespace cunumeric { +namespace cupynumeric { using namespace legate; @@ -58,4 +58,4 @@ static void arange_template(TaskContext& context) type_dispatch(args.out.code(), ArangeImpl{}, args); } -} // namespace cunumeric +} // namespace cupynumeric diff --git a/src/cunumeric/nullary/eye.cc b/src/cupynumeric/nullary/eye.cc similarity index 89% rename from src/cunumeric/nullary/eye.cc rename to src/cupynumeric/nullary/eye.cc index c1750cafab..42af36c94e 100644 --- a/src/cunumeric/nullary/eye.cc +++ b/src/cupynumeric/nullary/eye.cc @@ -14,10 +14,10 @@ * */ -#include "cunumeric/nullary/eye.h" -#include "cunumeric/nullary/eye_template.inl" +#include "cupynumeric/nullary/eye.h" +#include "cupynumeric/nullary/eye_template.inl" -namespace cunumeric { +namespace cupynumeric { using namespace legate; @@ -43,4 +43,4 @@ namespace // unnamed static void __attribute__((constructor)) register_tasks(void) { EyeTask::register_variants(); } } // namespace -} // namespace cunumeric +} // namespace cupynumeric diff --git a/src/cunumeric/nullary/eye.cu b/src/cupynumeric/nullary/eye.cu similarity index 87% rename from src/cunumeric/nullary/eye.cu rename to src/cupynumeric/nullary/eye.cu index 99b0fa5f16..e2c79c2609 100644 --- a/src/cunumeric/nullary/eye.cu +++ b/src/cupynumeric/nullary/eye.cu @@ -14,11 +14,11 @@ * */ -#include "cunumeric/nullary/eye.h" -#include "cunumeric/nullary/eye_template.inl" -#include "cunumeric/cuda_help.h" +#include "cupynumeric/nullary/eye.h" +#include "cupynumeric/nullary/eye_template.inl" +#include "cupynumeric/cuda_help.h" -namespace cunumeric { +namespace cupynumeric { template __global__ static void __launch_bounds__(THREADS_PER_BLOCK, MIN_CTAS_PER_SM) @@ -40,7 +40,7 @@ struct EyeImplBody { const size_t blocks = (distance + THREADS_PER_BLOCK - 1) / THREADS_PER_BLOCK; auto stream = get_cached_stream(); eye_kernel<<>>(out, start, distance); - CUNUMERIC_CHECK_CUDA_STREAM(stream); + CUPYNUMERIC_CHECK_CUDA_STREAM(stream); } }; @@ -49,4 +49,4 @@ struct EyeImplBody { eye_template(context); } -} // namespace cunumeric +} // namespace cupynumeric diff --git a/src/cunumeric/nullary/eye.h b/src/cupynumeric/nullary/eye.h similarity index 81% rename from src/cunumeric/nullary/eye.h rename to src/cupynumeric/nullary/eye.h index 09d29bf6b7..a50c051a97 100644 --- a/src/cunumeric/nullary/eye.h +++ b/src/cupynumeric/nullary/eye.h @@ -16,18 +16,18 @@ #pragma once -#include "cunumeric/cunumeric_task.h" +#include "cupynumeric/cupynumeric_task.h" -namespace cunumeric { +namespace cupynumeric { struct EyeArgs { legate::PhysicalStore out; int32_t k; }; -class EyeTask : public CuNumericTask { +class EyeTask : public CuPyNumericTask { public: - static constexpr auto TASK_ID = legate::LocalTaskID{CUNUMERIC_EYE}; + static constexpr auto TASK_ID = legate::LocalTaskID{CUPYNUMERIC_EYE}; public: static void cpu_variant(legate::TaskContext context); @@ -39,4 +39,4 @@ class EyeTask : public CuNumericTask { #endif }; -} // namespace cunumeric +} // namespace cupynumeric diff --git a/src/cunumeric/nullary/eye_omp.cc b/src/cupynumeric/nullary/eye_omp.cc similarity index 88% rename from src/cunumeric/nullary/eye_omp.cc rename to src/cupynumeric/nullary/eye_omp.cc index b28c9b6528..294477f7cd 100644 --- a/src/cunumeric/nullary/eye_omp.cc +++ b/src/cupynumeric/nullary/eye_omp.cc @@ -14,10 +14,10 @@ * */ -#include "cunumeric/nullary/eye.h" -#include "cunumeric/nullary/eye_template.inl" +#include "cupynumeric/nullary/eye.h" +#include "cupynumeric/nullary/eye_template.inl" -namespace cunumeric { +namespace cupynumeric { using namespace legate; @@ -39,4 +39,4 @@ struct EyeImplBody { eye_template(context); } -} // namespace cunumeric +} // namespace cupynumeric diff --git a/src/cunumeric/nullary/eye_template.inl b/src/cupynumeric/nullary/eye_template.inl similarity index 92% rename from src/cunumeric/nullary/eye_template.inl rename to src/cupynumeric/nullary/eye_template.inl index 3786cbd9cd..0a2bc20180 100644 --- a/src/cunumeric/nullary/eye_template.inl +++ b/src/cupynumeric/nullary/eye_template.inl @@ -17,12 +17,12 @@ #pragma once // Useful for IDEs -#include "cunumeric/nullary/eye.h" -#include "cunumeric/arg.h" -#include "cunumeric/arg.inl" -#include "cunumeric/pitches.h" +#include "cupynumeric/nullary/eye.h" +#include "cupynumeric/arg.h" +#include "cupynumeric/arg.inl" +#include "cupynumeric/pitches.h" -namespace cunumeric { +namespace cupynumeric { using namespace legate; @@ -75,4 +75,4 @@ static void eye_template(TaskContext& context) type_dispatch(args.out.code(), EyeImpl{}, args); } -} // namespace cunumeric +} // namespace cupynumeric diff --git a/src/cunumeric/nullary/fill.cc b/src/cupynumeric/nullary/fill.cc similarity index 91% rename from src/cunumeric/nullary/fill.cc rename to src/cupynumeric/nullary/fill.cc index f375b897fc..d252e0504a 100644 --- a/src/cunumeric/nullary/fill.cc +++ b/src/cupynumeric/nullary/fill.cc @@ -14,10 +14,10 @@ * */ -#include "cunumeric/nullary/fill.h" -#include "cunumeric/nullary/fill_template.inl" +#include "cupynumeric/nullary/fill.h" +#include "cupynumeric/nullary/fill_template.inl" -namespace cunumeric { +namespace cupynumeric { using namespace legate; @@ -55,4 +55,4 @@ namespace // unnamed static void __attribute__((constructor)) register_tasks(void) { FillTask::register_variants(); } } // namespace -} // namespace cunumeric +} // namespace cupynumeric diff --git a/src/cunumeric/nullary/fill.cu b/src/cupynumeric/nullary/fill.cu similarity index 90% rename from src/cunumeric/nullary/fill.cu rename to src/cupynumeric/nullary/fill.cu index 5b2a42a798..249f0203d5 100644 --- a/src/cunumeric/nullary/fill.cu +++ b/src/cupynumeric/nullary/fill.cu @@ -14,12 +14,12 @@ * */ -#include "cunumeric/nullary/fill.h" -#include "cunumeric/nullary/fill_template.inl" +#include "cupynumeric/nullary/fill.h" +#include "cupynumeric/nullary/fill_template.inl" -#include "cunumeric/cuda_help.h" +#include "cupynumeric/cuda_help.h" -namespace cunumeric { +namespace cupynumeric { template static __global__ void __launch_bounds__(THREADS_PER_BLOCK, MIN_CTAS_PER_SM) @@ -61,7 +61,7 @@ struct FillImplBody { } else { generic_kernel<<>>(volume, out, in, pitches, rect); } - CUNUMERIC_CHECK_CUDA_STREAM(stream); + CUPYNUMERIC_CHECK_CUDA_STREAM(stream); } }; @@ -70,4 +70,4 @@ struct FillImplBody { fill_template(context); } -} // namespace cunumeric +} // namespace cupynumeric diff --git a/src/cunumeric/nullary/fill.h b/src/cupynumeric/nullary/fill.h similarity index 81% rename from src/cunumeric/nullary/fill.h rename to src/cupynumeric/nullary/fill.h index e6023e850e..229cd63f27 100644 --- a/src/cunumeric/nullary/fill.h +++ b/src/cupynumeric/nullary/fill.h @@ -16,18 +16,18 @@ #pragma once -#include "cunumeric/cunumeric_task.h" +#include "cupynumeric/cupynumeric_task.h" -namespace cunumeric { +namespace cupynumeric { struct FillArgs { legate::PhysicalStore out; legate::PhysicalStore fill_value; }; -class FillTask : public CuNumericTask { +class FillTask : public CuPyNumericTask { public: - static constexpr auto TASK_ID = legate::LocalTaskID{CUNUMERIC_FILL}; + static constexpr auto TASK_ID = legate::LocalTaskID{CUPYNUMERIC_FILL}; public: static void cpu_variant(legate::TaskContext context); @@ -39,4 +39,4 @@ class FillTask : public CuNumericTask { #endif }; -} // namespace cunumeric +} // namespace cupynumeric diff --git a/src/cunumeric/nullary/fill_omp.cc b/src/cupynumeric/nullary/fill_omp.cc similarity index 92% rename from src/cunumeric/nullary/fill_omp.cc rename to src/cupynumeric/nullary/fill_omp.cc index afb7ec78d3..35c23b38f9 100644 --- a/src/cunumeric/nullary/fill_omp.cc +++ b/src/cupynumeric/nullary/fill_omp.cc @@ -14,10 +14,10 @@ * */ -#include "cunumeric/nullary/fill.h" -#include "cunumeric/nullary/fill_template.inl" +#include "cupynumeric/nullary/fill.h" +#include "cupynumeric/nullary/fill_template.inl" -namespace cunumeric { +namespace cupynumeric { using namespace legate; @@ -57,4 +57,4 @@ struct FillImplBody { fill_template(context); } -} // namespace cunumeric +} // namespace cupynumeric diff --git a/src/cunumeric/nullary/fill_template.inl b/src/cupynumeric/nullary/fill_template.inl similarity index 90% rename from src/cunumeric/nullary/fill_template.inl rename to src/cupynumeric/nullary/fill_template.inl index da92359312..79c015afd7 100644 --- a/src/cunumeric/nullary/fill_template.inl +++ b/src/cupynumeric/nullary/fill_template.inl @@ -17,12 +17,12 @@ #pragma once // Useful for IDEs -#include "cunumeric/nullary/fill.h" -#include "cunumeric/arg.h" -#include "cunumeric/arg.inl" -#include "cunumeric/pitches.h" +#include "cupynumeric/nullary/fill.h" +#include "cupynumeric/arg.h" +#include "cupynumeric/arg.inl" +#include "cupynumeric/pitches.h" -namespace cunumeric { +namespace cupynumeric { using namespace legate; @@ -71,4 +71,4 @@ static void fill_template(TaskContext& context) double_dispatch(std::max(args.out.dim(), 1), args.out.code(), FillImpl{}, args); } -} // namespace cunumeric +} // namespace cupynumeric diff --git a/src/cunumeric/nullary/window.cc b/src/cupynumeric/nullary/window.cc similarity index 90% rename from src/cunumeric/nullary/window.cc rename to src/cupynumeric/nullary/window.cc index 273ce7cabb..a5490359fb 100644 --- a/src/cunumeric/nullary/window.cc +++ b/src/cupynumeric/nullary/window.cc @@ -14,10 +14,10 @@ * */ -#include "cunumeric/nullary/window.h" -#include "cunumeric/nullary/window_template.inl" +#include "cupynumeric/nullary/window.h" +#include "cupynumeric/nullary/window_template.inl" -namespace cunumeric { +namespace cupynumeric { using namespace legate; @@ -51,4 +51,4 @@ namespace // unnamed static void __attribute__((constructor)) register_tasks(void) { WindowTask::register_variants(); } } // namespace -} // namespace cunumeric +} // namespace cupynumeric diff --git a/src/cunumeric/nullary/window.cu b/src/cupynumeric/nullary/window.cu similarity index 90% rename from src/cunumeric/nullary/window.cu rename to src/cupynumeric/nullary/window.cu index 0dfbce2044..2abcf01e95 100644 --- a/src/cunumeric/nullary/window.cu +++ b/src/cupynumeric/nullary/window.cu @@ -14,12 +14,12 @@ * */ -#include "cunumeric/nullary/window.h" -#include "cunumeric/nullary/window_template.inl" +#include "cupynumeric/nullary/window.h" +#include "cupynumeric/nullary/window_template.inl" -#include "cunumeric/cuda_help.h" +#include "cupynumeric/cuda_help.h" -namespace cunumeric { +namespace cupynumeric { template static __global__ void __launch_bounds__(THREADS_PER_BLOCK, MIN_CTAS_PER_SM) @@ -64,7 +64,7 @@ struct WindowImplBody { <<>>(gen, volume, out, rect.lo[0]); } - CUNUMERIC_CHECK_CUDA_STREAM(stream); + CUPYNUMERIC_CHECK_CUDA_STREAM(stream); } }; @@ -73,4 +73,4 @@ struct WindowImplBody { window_template(context); } -} // namespace cunumeric +} // namespace cupynumeric diff --git a/src/cunumeric/nullary/window.h b/src/cupynumeric/nullary/window.h similarity index 79% rename from src/cunumeric/nullary/window.h rename to src/cupynumeric/nullary/window.h index 0b688a3951..2a59549d22 100644 --- a/src/cunumeric/nullary/window.h +++ b/src/cupynumeric/nullary/window.h @@ -16,13 +16,13 @@ #pragma once -#include "cunumeric/cunumeric_task.h" +#include "cupynumeric/cupynumeric_task.h" -namespace cunumeric { +namespace cupynumeric { -class WindowTask : public CuNumericTask { +class WindowTask : public CuPyNumericTask { public: - static constexpr auto TASK_ID = legate::LocalTaskID{CUNUMERIC_WINDOW}; + static constexpr auto TASK_ID = legate::LocalTaskID{CUPYNUMERIC_WINDOW}; public: static void cpu_variant(legate::TaskContext context); @@ -34,4 +34,4 @@ class WindowTask : public CuNumericTask { #endif }; -} // namespace cunumeric +} // namespace cupynumeric diff --git a/src/cunumeric/nullary/window_omp.cc b/src/cupynumeric/nullary/window_omp.cc similarity index 90% rename from src/cunumeric/nullary/window_omp.cc rename to src/cupynumeric/nullary/window_omp.cc index e43d3a23b8..c0adf0e298 100644 --- a/src/cunumeric/nullary/window_omp.cc +++ b/src/cupynumeric/nullary/window_omp.cc @@ -14,10 +14,10 @@ * */ -#include "cunumeric/nullary/window.h" -#include "cunumeric/nullary/window_template.inl" +#include "cupynumeric/nullary/window.h" +#include "cupynumeric/nullary/window_template.inl" -namespace cunumeric { +namespace cupynumeric { using namespace legate; @@ -47,4 +47,4 @@ struct WindowImplBody { window_template(context); } -} // namespace cunumeric +} // namespace cupynumeric diff --git a/src/cunumeric/nullary/window_template.inl b/src/cupynumeric/nullary/window_template.inl similarity index 92% rename from src/cunumeric/nullary/window_template.inl rename to src/cupynumeric/nullary/window_template.inl index f744e2ad21..216d0f03aa 100644 --- a/src/cunumeric/nullary/window_template.inl +++ b/src/cupynumeric/nullary/window_template.inl @@ -17,10 +17,10 @@ #pragma once // Useful for IDEs -#include "cunumeric/nullary/window.h" -#include "cunumeric/nullary/window_util.h" +#include "cupynumeric/nullary/window.h" +#include "cupynumeric/nullary/window_util.h" -namespace cunumeric { +namespace cupynumeric { using namespace legate; @@ -63,4 +63,4 @@ static void window_template(TaskContext& context) op_dispatch(op_code, WindowImpl{}, output, M, beta); } -} // namespace cunumeric +} // namespace cupynumeric diff --git a/src/cunumeric/nullary/window_util.h b/src/cupynumeric/nullary/window_util.h similarity index 91% rename from src/cunumeric/nullary/window_util.h rename to src/cupynumeric/nullary/window_util.h index 4d6e400316..84993548cb 100644 --- a/src/cunumeric/nullary/window_util.h +++ b/src/cupynumeric/nullary/window_util.h @@ -18,19 +18,19 @@ #define _USE_MATH_DEFINES -#include "cunumeric/cunumeric_task.h" +#include "cupynumeric/cupynumeric_task.h" #include extern double i0(double); -namespace cunumeric { +namespace cupynumeric { enum class WindowOpCode : int { - BARLETT = CUNUMERIC_WINDOW_BARLETT, - BLACKMAN = CUNUMERIC_WINDOW_BLACKMAN, - HAMMING = CUNUMERIC_WINDOW_HAMMING, - HANNING = CUNUMERIC_WINDOW_HANNING, - KAISER = CUNUMERIC_WINDOW_KAISER, + BARLETT = CUPYNUMERIC_WINDOW_BARLETT, + BLACKMAN = CUPYNUMERIC_WINDOW_BLACKMAN, + HAMMING = CUPYNUMERIC_WINDOW_HAMMING, + HANNING = CUPYNUMERIC_WINDOW_HANNING, + KAISER = CUPYNUMERIC_WINDOW_KAISER, }; template @@ -121,4 +121,4 @@ struct WindowOp { double beta_; }; -} // namespace cunumeric +} // namespace cupynumeric diff --git a/src/cunumeric/omp_help.h b/src/cupynumeric/omp_help.h similarity index 96% rename from src/cunumeric/omp_help.h rename to src/cupynumeric/omp_help.h index fe132280fa..9c3cdf738d 100644 --- a/src/cunumeric/omp_help.h +++ b/src/cupynumeric/omp_help.h @@ -18,7 +18,7 @@ #include -namespace cunumeric { +namespace cupynumeric { // Simple STL vector-based thread local storage for OpenMP threads to avoid false sharing template @@ -47,4 +47,4 @@ struct ThreadLocalStorage { size_t num_threads_; }; -} // namespace cunumeric +} // namespace cupynumeric diff --git a/src/cunumeric/operators.cc b/src/cupynumeric/operators.cc similarity index 91% rename from src/cunumeric/operators.cc rename to src/cupynumeric/operators.cc index c03c0ae0db..e20ee156c6 100644 --- a/src/cunumeric/operators.cc +++ b/src/cupynumeric/operators.cc @@ -14,31 +14,31 @@ * */ -#include "cunumeric/operators.h" +#include "cupynumeric/operators.h" -#include "cunumeric/runtime.h" -#include "cunumeric/binary/binary_op_util.h" -#include "cunumeric/unary/unary_op_util.h" -#include "cunumeric/unary/unary_red_util.h" -#include "cunumeric/random/rand_util.h" -#include "cunumeric/nullary/window_util.h" +#include "cupynumeric/runtime.h" +#include "cupynumeric/binary/binary_op_util.h" +#include "cupynumeric/unary/unary_op_util.h" +#include "cupynumeric/unary/unary_red_util.h" +#include "cupynumeric/random/rand_util.h" +#include "cupynumeric/nullary/window_util.h" -namespace cunumeric { +namespace cupynumeric { -static legate::Logger log_cunumeric("cunumeric"); +static legate::Logger log_cupynumeric("cupynumeric"); -legate::Logger& cunumeric_log() { return log_cunumeric; } +legate::Logger& cupynumeric_log() { return log_cupynumeric; } NDArray array(std::vector shape, const legate::Type& type) { - return CuNumericRuntime::get_runtime()->create_array(std::move(shape), type); + return CuPyNumericRuntime::get_runtime()->create_array(std::move(shape), type); } NDArray unary_op(UnaryOpCode op_code, NDArray input, const std::vector& extra_args = {}) { - auto runtime = CuNumericRuntime::get_runtime(); + auto runtime = CuPyNumericRuntime::get_runtime(); auto out = runtime->create_array(input.shape(), input.type()); out.unary_op(static_cast(op_code), std::move(input), extra_args); return out; @@ -46,7 +46,7 @@ NDArray unary_op(UnaryOpCode op_code, NDArray unary_reduction(UnaryRedCode op_code, NDArray input) { - auto runtime = CuNumericRuntime::get_runtime(); + auto runtime = CuPyNumericRuntime::get_runtime(); auto out = runtime->create_array({1}, input.type()); out.unary_reduction(static_cast(op_code), std::move(input)); return out; @@ -54,7 +54,7 @@ NDArray unary_reduction(UnaryRedCode op_code, NDArray input) NDArray binary_op(BinaryOpCode op_code, NDArray rhs1, NDArray rhs2, std::optional out) { - auto runtime = CuNumericRuntime::get_runtime(); + auto runtime = CuPyNumericRuntime::get_runtime(); if (!out.has_value()) { auto out_shape = broadcast_shapes({rhs1, rhs2}); out = runtime->create_array(out_shape, rhs1.type()); @@ -86,7 +86,7 @@ NDArray negative(NDArray input) { return unary_op(UnaryOpCode::NEGATIVE, std::mo NDArray random(std::vector shape) { - auto runtime = CuNumericRuntime::get_runtime(); + auto runtime = CuPyNumericRuntime::get_runtime(); auto out = runtime->create_array(std::move(shape), legate::float64()); out.random(static_cast(RandGenCode::UNIFORM)); return out; @@ -161,7 +161,7 @@ NDArray zeros(std::vector shape, std::optional type) NDArray full(std::vector shape, const Scalar& value) { - auto runtime = CuNumericRuntime::get_runtime(); + auto runtime = CuPyNumericRuntime::get_runtime(); auto out = runtime->create_array(std::move(shape), value.type()); out.fill(value); return out; @@ -177,7 +177,7 @@ NDArray eye(int32_t n, std::optional m, int32_t k, const legate::Type& throw std::invalid_argument("Type must be a primitive type"); } - auto runtime = CuNumericRuntime::get_runtime(); + auto runtime = CuPyNumericRuntime::get_runtime(); auto out = runtime->create_array({static_cast(n), static_cast(m.value_or(n))}, type); out.eye(k); @@ -214,7 +214,7 @@ NDArray bincount(NDArray x, min_length = max_val + 1; } - auto runtime = CuNumericRuntime::get_runtime(); + auto runtime = CuPyNumericRuntime::get_runtime(); if (!weights.has_value()) { auto out = runtime->create_array({min_length}, legate::int64()); out.bincount(x); @@ -250,7 +250,7 @@ NDArray trilu(NDArray rhs, int32_t k, bool lower) out_shape.emplace_back(shape[0]); } - auto runtime = CuNumericRuntime::get_runtime(); + auto runtime = CuPyNumericRuntime::get_runtime(); auto out = runtime->create_array(std::move(out_shape), rhs.type()); out.trilu(std::move(rhs), k, lower); return out; @@ -263,7 +263,7 @@ NDArray triu(NDArray rhs, int32_t k) { return trilu(rhs, k, false); } NDArray dot(NDArray rhs1, NDArray rhs2) { if (rhs1.dim() != 2 || rhs2.dim() != 2) { - LEGATE_ABORT("cunumeric::dot only supports matrices now"); + LEGATE_ABORT("cupynumeric::dot only supports matrices now"); } auto& rhs1_shape = rhs1.shape(); @@ -281,7 +281,7 @@ NDArray dot(NDArray rhs1, NDArray rhs2) ")"); } - auto runtime = CuNumericRuntime::get_runtime(); + auto runtime = CuPyNumericRuntime::get_runtime(); std::vector shape; shape.push_back(rhs1_shape[0]); shape.push_back(rhs2_shape[1]); @@ -358,19 +358,19 @@ NDArray arange(Scalar start, Scalar stop, Scalar step) throw std::invalid_argument("start/stop/step should be of the same type"); } - auto out = CuNumericRuntime::get_runtime()->create_array({N}, start.type()); + auto out = CuPyNumericRuntime::get_runtime()->create_array({N}, start.type()); out.arange(start, stop, step); return out; } NDArray as_array(legate::LogicalStore store) { - return CuNumericRuntime::get_runtime()->create_array(std::move(store)); + return CuPyNumericRuntime::get_runtime()->create_array(std::move(store)); } NDArray array_equal(NDArray input0, NDArray input1) { - auto dst = CuNumericRuntime::get_runtime()->create_array({1}, legate::bool_()); + auto dst = CuPyNumericRuntime::get_runtime()->create_array({1}, legate::bool_()); if (input0.shape() != input1.shape()) { dst.fill(legate::Scalar(false)); @@ -386,7 +386,7 @@ std::vector nonzero(NDArray input) { return input.nonzero(); } NDArray create_window(int64_t M, WindowOpCode op_code, std::vector args) { auto type = legate::float64(); - auto runtime = CuNumericRuntime::get_runtime(); + auto runtime = CuPyNumericRuntime::get_runtime(); if (M <= 0) { return runtime->create_array({0}, std::move(type)); } else if (M == 1) { @@ -418,7 +418,7 @@ NDArray convolve(NDArray a, NDArray v) if (a.dim() > 3) { throw std::runtime_error(std::to_string(a.dim()) + "-D arrays are not yet supported"); } - auto out = CuNumericRuntime::get_runtime()->create_array(a.shape(), a.type()); + auto out = CuPyNumericRuntime::get_runtime()->create_array(a.shape(), a.type()); if (a.type() != v.type()) { v = v.as_type(a.type()); } @@ -428,7 +428,7 @@ NDArray convolve(NDArray a, NDArray v) NDArray sort(NDArray input, std::optional axis /*=-1*/, std::string kind /*="quicksort"*/) { - auto runtime = CuNumericRuntime::get_runtime(); + auto runtime = CuPyNumericRuntime::get_runtime(); auto result = runtime->create_array(input.shape(), input.type()); result.sort(input, false, axis, kind); return result; @@ -438,7 +438,7 @@ NDArray argsort(NDArray input, std::optional axis /*=-1*/, std::string kind /*="quicksort"*/) { - auto runtime = CuNumericRuntime::get_runtime(); + auto runtime = CuPyNumericRuntime::get_runtime(); auto result = runtime->create_array(input.shape(), legate::int64()); result.sort(input, true, axis, kind); return result; @@ -591,7 +591,7 @@ NDArray where(NDArray a, NDArray x, NDArray y) auto rhs3 = y._maybe_convert(common_type); auto out_shape = broadcast_shapes({rhs1, rhs2, rhs3}); - auto runtime = CuNumericRuntime::get_runtime(); + auto runtime = CuPyNumericRuntime::get_runtime(); auto out = runtime->create_array(std::move(out_shape), common_type); out.where(std::move(rhs1), std::move(rhs2), std::move(rhs3)); return out; @@ -611,4 +611,4 @@ legate::Type find_common_type(const std::vector& arrays) return max_type; } -} // namespace cunumeric +} // namespace cupynumeric diff --git a/src/cunumeric/operators.h b/src/cupynumeric/operators.h similarity index 96% rename from src/cunumeric/operators.h rename to src/cupynumeric/operators.h index 8048211950..0e562c0675 100644 --- a/src/cunumeric/operators.h +++ b/src/cupynumeric/operators.h @@ -20,12 +20,12 @@ #include #include "legate.h" -#include "cunumeric/ndarray.h" -#include "cunumeric/typedefs.h" +#include "cupynumeric/ndarray.h" +#include "cupynumeric/typedefs.h" -namespace cunumeric { +namespace cupynumeric { -legate::Logger& cunumeric_log(); +legate::Logger& cupynumeric_log(); void initialize(int32_t argc, char** argv); @@ -196,5 +196,5 @@ std::vector vec_convert(const std::vector& input) return output; } -} // namespace cunumeric -#include "cunumeric/operators.inl" +} // namespace cupynumeric +#include "cupynumeric/operators.inl" diff --git a/src/cunumeric/operators.inl b/src/cupynumeric/operators.inl similarity index 85% rename from src/cunumeric/operators.inl rename to src/cupynumeric/operators.inl index 0249892881..0b7a24955e 100644 --- a/src/cunumeric/operators.inl +++ b/src/cupynumeric/operators.inl @@ -13,8 +13,8 @@ * limitations under the License. * */ -#include "cunumeric/runtime.h" -namespace cunumeric { +#include "cupynumeric/runtime.h" +namespace cupynumeric { template NDArray arange(T start, std::optional stop, T step) @@ -30,9 +30,9 @@ NDArray arange(T start, std::optional stop, T step) auto s_start = Scalar(start); auto s_stop = Scalar(stop.value()); auto s_step = Scalar(step); - auto out = CuNumericRuntime::get_runtime()->create_array({N}, s_start.type()); + auto out = CuPyNumericRuntime::get_runtime()->create_array({N}, s_start.type()); out.arange(s_start, s_stop, s_step); return out; } -} // namespace cunumeric +} // namespace cupynumeric diff --git a/src/cunumeric/pitches.h b/src/cupynumeric/pitches.h similarity index 98% rename from src/cunumeric/pitches.h rename to src/cupynumeric/pitches.h index 445d1ea47a..05808dc82b 100644 --- a/src/cunumeric/pitches.h +++ b/src/cupynumeric/pitches.h @@ -18,7 +18,7 @@ #include "legate/utilities/typedefs.h" -namespace cunumeric { +namespace cupynumeric { // This is a small helper class that will also work if we have zero-sized arrays // We also need to have this instead of std::array so that it works on devices @@ -120,4 +120,4 @@ class Pitches<0, C_ORDER> { } }; -} // namespace cunumeric +} // namespace cupynumeric diff --git a/src/cunumeric/random/bitgenerator.cc b/src/cupynumeric/random/bitgenerator.cc similarity index 80% rename from src/cunumeric/random/bitgenerator.cc rename to src/cupynumeric/random/bitgenerator.cc index 6c3728b85c..b4c25282d3 100644 --- a/src/cunumeric/random/bitgenerator.cc +++ b/src/cupynumeric/random/bitgenerator.cc @@ -18,33 +18,33 @@ // CPU Builds: // -#if !LEGATE_DEFINED(LEGATE_USE_CUDA) && !LEGATE_DEFINED(CUNUMERIC_CURAND_FOR_CPU_BUILD) -#define CUNUMERIC_USE_STL_RANDOM_ENGINE +#if !LEGATE_DEFINED(LEGATE_USE_CUDA) && !LEGATE_DEFINED(CUPYNUMERIC_CURAND_FOR_CPU_BUILD) +#define CUPYNUMERIC_USE_STL_RANDOM_ENGINE #endif -#include "cunumeric/random/bitgenerator.h" -#include "cunumeric/random/bitgenerator_template.inl" -#include "cunumeric/random/bitgenerator_util.h" +#include "cupynumeric/random/bitgenerator.h" +#include "cupynumeric/random/bitgenerator_template.inl" +#include "cupynumeric/random/bitgenerator_util.h" -#include "cunumeric/random/rnd_types.h" +#include "cupynumeric/random/rnd_types.h" -#if !LEGATE_DEFINED(CUNUMERIC_USE_STL_RANDOM_ENGINE) -#include "cunumeric/random/curand_help.h" +#if !LEGATE_DEFINED(CUPYNUMERIC_USE_STL_RANDOM_ENGINE) +#include "cupynumeric/random/curand_help.h" #endif -#include "cunumeric/random/randutil/randutil.h" +#include "cupynumeric/random/randutil/randutil.h" -#include "cunumeric/random/bitgenerator_curand.inl" +#include "cupynumeric/random/bitgenerator_curand.inl" -namespace cunumeric { +namespace cupynumeric { using namespace legate; -static Logger log_curand("cunumeric.random"); +static Logger log_curand("cupynumeric.random"); Logger& randutil_log() { return log_curand; } -#ifdef CUNUMERIC_USE_STL_RANDOM_ENGINE +#ifdef CUPYNUMERIC_USE_STL_RANDOM_ENGINE void randutil_check_status(rnd_status_t error, std::string_view file, int line) { if (error) { @@ -115,4 +115,4 @@ static void __attribute__((constructor)) register_tasks(void) } // namespace -} // namespace cunumeric +} // namespace cupynumeric diff --git a/src/cunumeric/random/bitgenerator.cu b/src/cupynumeric/random/bitgenerator.cu similarity index 83% rename from src/cunumeric/random/bitgenerator.cu rename to src/cupynumeric/random/bitgenerator.cu index 4471964d45..b4dbbee4a9 100644 --- a/src/cunumeric/random/bitgenerator.cu +++ b/src/cupynumeric/random/bitgenerator.cu @@ -14,19 +14,19 @@ * */ -#include "cunumeric/random/bitgenerator.h" -#include "cunumeric/random/bitgenerator_template.inl" -#include "cunumeric/random/bitgenerator_util.h" +#include "cupynumeric/random/bitgenerator.h" +#include "cupynumeric/random/bitgenerator_template.inl" +#include "cupynumeric/random/bitgenerator_util.h" -#include "cunumeric/cuda_help.h" -#include "cunumeric/random/curand_help.h" +#include "cupynumeric/cuda_help.h" +#include "cupynumeric/random/curand_help.h" -#include "cunumeric/random/bitgenerator_curand.inl" +#include "cupynumeric/random/bitgenerator_curand.inl" #include #include -namespace cunumeric { +namespace cupynumeric { using namespace legate; @@ -46,13 +46,13 @@ struct GPUGenerator : public CURANDGenerator { GPUGenerator(BitGeneratorType gentype, uint64_t seed, uint64_t generatorId, uint32_t flags) : CURANDGenerator(gentype, seed, generatorId) { - CUNUMERIC_CHECK_CUDA(::cudaStreamCreate(&stream_)); + CUPYNUMERIC_CHECK_CUDA(::cudaStreamCreate(&stream_)); CHECK_CURAND(::randutilCreateGenerator(&gen_, type_, seed, generatorId, flags, stream_)); } virtual ~GPUGenerator() { - CUNUMERIC_CHECK_CUDA(::cudaStreamSynchronize(stream_)); + CUPYNUMERIC_CHECK_CUDA(::cudaStreamSynchronize(stream_)); CHECK_CURAND(::randutilDestroyGenerator(gen_)); } }; @@ -87,4 +87,4 @@ void destroy_bitgenerator(const legate::Processor& proc) BitGeneratorImplBody::destroy_bitgenerator(proc); } -} // namespace cunumeric +} // namespace cupynumeric diff --git a/src/cunumeric/random/bitgenerator.h b/src/cupynumeric/random/bitgenerator.h similarity index 90% rename from src/cunumeric/random/bitgenerator.h rename to src/cupynumeric/random/bitgenerator.h index fff2d3a617..71c92076fb 100644 --- a/src/cunumeric/random/bitgenerator.h +++ b/src/cupynumeric/random/bitgenerator.h @@ -16,10 +16,10 @@ #pragma once -#include "cunumeric/cunumeric_task.h" -#include "cunumeric/random/bitgenerator_util.h" +#include "cupynumeric/cupynumeric_task.h" +#include "cupynumeric/random/bitgenerator_util.h" -namespace cunumeric { +namespace cupynumeric { struct BitGeneratorArgs { BitGeneratorOperation bitgen_op; @@ -78,9 +78,9 @@ struct BitGeneratorArgs { } }; -class BitGeneratorTask : public CuNumericTask { +class BitGeneratorTask : public CuPyNumericTask { public: - static constexpr auto TASK_ID = legate::LocalTaskID{CUNUMERIC_BITGENERATOR}; + static constexpr auto TASK_ID = legate::LocalTaskID{CUPYNUMERIC_BITGENERATOR}; public: static void cpu_variant(legate::TaskContext context); @@ -96,4 +96,4 @@ class BitGeneratorTask : public CuNumericTask { void destroy_bitgenerator(const legate::Processor& proc); -} // namespace cunumeric +} // namespace cupynumeric diff --git a/src/cunumeric/random/bitgenerator_curand.inl b/src/cupynumeric/random/bitgenerator_curand.inl similarity index 99% rename from src/cunumeric/random/bitgenerator_curand.inl rename to src/cupynumeric/random/bitgenerator_curand.inl index ddf19b0244..94058161ea 100644 --- a/src/cunumeric/random/bitgenerator_curand.inl +++ b/src/cupynumeric/random/bitgenerator_curand.inl @@ -21,14 +21,14 @@ #include #include -#include "cunumeric/random/bitgenerator.h" -#include "cunumeric/random/bitgenerator_template.inl" -#include "cunumeric/random/bitgenerator_util.h" +#include "cupynumeric/random/bitgenerator.h" +#include "cupynumeric/random/bitgenerator_template.inl" +#include "cupynumeric/random/bitgenerator_util.h" -#include "cunumeric/random/rnd_types.h" -#include "cunumeric/random/randutil/randutil.h" +#include "cupynumeric/random/rnd_types.h" +#include "cupynumeric/random/randutil/randutil.h" -namespace cunumeric { +namespace cupynumeric { using namespace legate; @@ -1633,4 +1633,4 @@ struct BitGeneratorImplBody { } }; -} // namespace cunumeric +} // namespace cupynumeric diff --git a/src/cunumeric/random/bitgenerator_template.inl b/src/cupynumeric/random/bitgenerator_template.inl similarity index 97% rename from src/cunumeric/random/bitgenerator_template.inl rename to src/cupynumeric/random/bitgenerator_template.inl index 7ed2467a0d..a9375acd25 100644 --- a/src/cunumeric/random/bitgenerator_template.inl +++ b/src/cupynumeric/random/bitgenerator_template.inl @@ -16,12 +16,12 @@ #pragma once -#include "cunumeric/arg.h" -#include "cunumeric/pitches.h" +#include "cupynumeric/arg.h" +#include "cupynumeric/pitches.h" #include "bitgenerator_util.h" -namespace cunumeric { +namespace cupynumeric { using namespace legate; @@ -128,4 +128,4 @@ static void bitgenerator_template(TaskContext& context) BitGeneratorImpl{}(args); } -} // namespace cunumeric +} // namespace cupynumeric diff --git a/src/cupynumeric/random/bitgenerator_util.h b/src/cupynumeric/random/bitgenerator_util.h new file mode 100644 index 0000000000..ba87111289 --- /dev/null +++ b/src/cupynumeric/random/bitgenerator_util.h @@ -0,0 +1,98 @@ +/* Copyright 2024 NVIDIA Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +#pragma once + +#include "cupynumeric/cupynumeric_task.h" + +namespace cupynumeric { + +// Match these to BitGeneratorOperation in config.py +enum class BitGeneratorOperation : int32_t { + CREATE = CUPYNUMERIC_BITGENOP_CREATE, + DESTROY = CUPYNUMERIC_BITGENOP_DESTROY, + RAND_RAW = CUPYNUMERIC_BITGENOP_RAND_RAW, + DISTRIBUTION = CUPYNUMERIC_BITGENOP_DISTRIBUTION, +}; + +// Match these to BitGeneratorType in config.py +enum class BitGeneratorType : uint32_t { + DEFAULT = CUPYNUMERIC_BITGENTYPE_DEFAULT, + XORWOW = CUPYNUMERIC_BITGENTYPE_XORWOW, + MRG32K3A = CUPYNUMERIC_BITGENTYPE_MRG32K3A, + MTGP32 = CUPYNUMERIC_BITGENTYPE_MTGP32, + MT19937 = CUPYNUMERIC_BITGENTYPE_MT19937, + PHILOX4_32_10 = CUPYNUMERIC_BITGENTYPE_PHILOX4_32_10, +}; + +// Match these to BitGeneratorDistribution in config.py +enum class BitGeneratorDistribution : int32_t { + INTEGERS_16 = CUPYNUMERIC_BITGENDIST_INTEGERS_16, + INTEGERS_32 = CUPYNUMERIC_BITGENDIST_INTEGERS_32, + INTEGERS_64 = CUPYNUMERIC_BITGENDIST_INTEGERS_64, + UNIFORM_32 = CUPYNUMERIC_BITGENDIST_UNIFORM_32, + UNIFORM_64 = CUPYNUMERIC_BITGENDIST_UNIFORM_64, + LOGNORMAL_32 = CUPYNUMERIC_BITGENDIST_LOGNORMAL_32, + LOGNORMAL_64 = CUPYNUMERIC_BITGENDIST_LOGNORMAL_64, + NORMAL_32 = CUPYNUMERIC_BITGENDIST_NORMAL_32, + NORMAL_64 = CUPYNUMERIC_BITGENDIST_NORMAL_64, + POISSON = CUPYNUMERIC_BITGENDIST_POISSON, + EXPONENTIAL_32 = CUPYNUMERIC_BITGENDIST_EXPONENTIAL_32, + EXPONENTIAL_64 = CUPYNUMERIC_BITGENDIST_EXPONENTIAL_64, + GUMBEL_32 = CUPYNUMERIC_BITGENDIST_GUMBEL_32, + GUMBEL_64 = CUPYNUMERIC_BITGENDIST_GUMBEL_64, + LAPLACE_32 = CUPYNUMERIC_BITGENDIST_LAPLACE_32, + LAPLACE_64 = CUPYNUMERIC_BITGENDIST_LAPLACE_64, + LOGISTIC_32 = CUPYNUMERIC_BITGENDIST_LOGISTIC_32, + LOGISTIC_64 = CUPYNUMERIC_BITGENDIST_LOGISTIC_64, + PARETO_32 = CUPYNUMERIC_BITGENDIST_PARETO_32, + PARETO_64 = CUPYNUMERIC_BITGENDIST_PARETO_64, + POWER_32 = CUPYNUMERIC_BITGENDIST_POWER_32, + POWER_64 = CUPYNUMERIC_BITGENDIST_POWER_64, + RAYLEIGH_32 = CUPYNUMERIC_BITGENDIST_RAYLEIGH_32, + RAYLEIGH_64 = CUPYNUMERIC_BITGENDIST_RAYLEIGH_64, + CAUCHY_32 = CUPYNUMERIC_BITGENDIST_CAUCHY_32, + CAUCHY_64 = CUPYNUMERIC_BITGENDIST_CAUCHY_64, + TRIANGULAR_32 = CUPYNUMERIC_BITGENDIST_TRIANGULAR_32, + TRIANGULAR_64 = CUPYNUMERIC_BITGENDIST_TRIANGULAR_64, + WEIBULL_32 = CUPYNUMERIC_BITGENDIST_WEIBULL_32, + WEIBULL_64 = CUPYNUMERIC_BITGENDIST_WEIBULL_64, + BYTES = CUPYNUMERIC_BITGENDIST_BYTES, + BETA_32 = CUPYNUMERIC_BITGENDIST_BETA_32, + BETA_64 = CUPYNUMERIC_BITGENDIST_BETA_64, + F_32 = CUPYNUMERIC_BITGENDIST_F_32, + F_64 = CUPYNUMERIC_BITGENDIST_F_64, + LOGSERIES = CUPYNUMERIC_BITGENDIST_LOGSERIES, + NONCENTRAL_F_32 = CUPYNUMERIC_BITGENDIST_NONCENTRAL_F_32, + NONCENTRAL_F_64 = CUPYNUMERIC_BITGENDIST_NONCENTRAL_F_64, + CHISQUARE_32 = CUPYNUMERIC_BITGENDIST_CHISQUARE_32, + CHISQUARE_64 = CUPYNUMERIC_BITGENDIST_CHISQUARE_64, + GAMMA_32 = CUPYNUMERIC_BITGENDIST_GAMMA_32, + GAMMA_64 = CUPYNUMERIC_BITGENDIST_GAMMA_64, + STANDARD_T_32 = CUPYNUMERIC_BITGENDIST_STANDARD_T_32, + STANDARD_T_64 = CUPYNUMERIC_BITGENDIST_STANDARD_T_64, + HYPERGEOMETRIC = CUPYNUMERIC_BITGENDIST_HYPERGEOMETRIC, + VONMISES_32 = CUPYNUMERIC_BITGENDIST_VONMISES_32, + VONMISES_64 = CUPYNUMERIC_BITGENDIST_VONMISES_64, + ZIPF = CUPYNUMERIC_BITGENDIST_ZIPF, + GEOMETRIC = CUPYNUMERIC_BITGENDIST_GEOMETRIC, + WALD_32 = CUPYNUMERIC_BITGENDIST_WALD_32, + WALD_64 = CUPYNUMERIC_BITGENDIST_WALD_64, + BINOMIAL = CUPYNUMERIC_BITGENDIST_BINOMIAL, + NEGATIVE_BINOMIAL = CUPYNUMERIC_BITGENDIST_NEGATIVE_BINOMIAL, +}; + +} // namespace cupynumeric diff --git a/src/cunumeric/random/curand_help.h b/src/cupynumeric/random/curand_help.h similarity index 75% rename from src/cunumeric/random/curand_help.h rename to src/cupynumeric/random/curand_help.h index 39847860aa..b30ac6b117 100644 --- a/src/cunumeric/random/curand_help.h +++ b/src/cupynumeric/random/curand_help.h @@ -38,7 +38,7 @@ randutil_check_curand_device(__result__, __FILE__, __LINE__); \ } while (false) -namespace cunumeric { +namespace cupynumeric { legate::Logger& randutil_log(); void randutil_check_curand(curandStatus_t error, std::string_view file, int line); @@ -46,19 +46,19 @@ void randutil_check_curand(curandStatus_t error, std::string_view file, int line // void randutil_check_curand_device(curandStatus_t error, const char* file, int line); -static inline curandRngType get_curandRngType(cunumeric::BitGeneratorType kind) +static inline curandRngType get_curandRngType(cupynumeric::BitGeneratorType kind) { switch (kind) { - case cunumeric::BitGeneratorType::DEFAULT: return curandRngType::CURAND_RNG_PSEUDO_XORWOW; - case cunumeric::BitGeneratorType::XORWOW: return curandRngType::CURAND_RNG_PSEUDO_XORWOW; - case cunumeric::BitGeneratorType::MRG32K3A: return curandRngType::CURAND_RNG_PSEUDO_MRG32K3A; - case cunumeric::BitGeneratorType::MTGP32: return curandRngType::CURAND_RNG_PSEUDO_MTGP32; - case cunumeric::BitGeneratorType::MT19937: return curandRngType::CURAND_RNG_PSEUDO_MT19937; - case cunumeric::BitGeneratorType::PHILOX4_32_10: + case cupynumeric::BitGeneratorType::DEFAULT: return curandRngType::CURAND_RNG_PSEUDO_XORWOW; + case cupynumeric::BitGeneratorType::XORWOW: return curandRngType::CURAND_RNG_PSEUDO_XORWOW; + case cupynumeric::BitGeneratorType::MRG32K3A: return curandRngType::CURAND_RNG_PSEUDO_MRG32K3A; + case cupynumeric::BitGeneratorType::MTGP32: return curandRngType::CURAND_RNG_PSEUDO_MTGP32; + case cupynumeric::BitGeneratorType::MT19937: return curandRngType::CURAND_RNG_PSEUDO_MT19937; + case cupynumeric::BitGeneratorType::PHILOX4_32_10: return curandRngType::CURAND_RNG_PSEUDO_PHILOX4_32_10; default: LEGATE_ABORT("Unknown generator type"); } return curandRngType::CURAND_RNG_TEST; } -} // namespace cunumeric +} // namespace cupynumeric diff --git a/src/cunumeric/random/philox.h b/src/cupynumeric/random/philox.h similarity index 98% rename from src/cunumeric/random/philox.h rename to src/cupynumeric/random/philox.h index acada656e4..46eb7f6ae3 100644 --- a/src/cunumeric/random/philox.h +++ b/src/cupynumeric/random/philox.h @@ -26,7 +26,7 @@ #endif #endif -namespace cunumeric { +namespace cupynumeric { template class Philox_2x32 { @@ -139,4 +139,4 @@ class Philox_2x32 { } }; -} // namespace cunumeric +} // namespace cupynumeric diff --git a/src/cunumeric/random/rand.cc b/src/cupynumeric/random/rand.cc similarity index 91% rename from src/cunumeric/random/rand.cc rename to src/cupynumeric/random/rand.cc index 690a81fdeb..d2e354d4e4 100644 --- a/src/cunumeric/random/rand.cc +++ b/src/cupynumeric/random/rand.cc @@ -14,10 +14,10 @@ * */ -#include "cunumeric/random/rand.h" -#include "cunumeric/random/rand_template.inl" +#include "cupynumeric/random/rand.h" +#include "cupynumeric/random/rand_template.inl" -namespace cunumeric { +namespace cupynumeric { using namespace legate; @@ -51,4 +51,4 @@ namespace // unnamed static void __attribute__((constructor)) register_tasks(void) { RandTask::register_variants(); } } // namespace -} // namespace cunumeric +} // namespace cupynumeric diff --git a/src/cunumeric/random/rand.cu b/src/cupynumeric/random/rand.cu similarity index 89% rename from src/cunumeric/random/rand.cu rename to src/cupynumeric/random/rand.cu index 85ab708376..7d02293c51 100644 --- a/src/cunumeric/random/rand.cu +++ b/src/cupynumeric/random/rand.cu @@ -14,12 +14,12 @@ * */ -#include "cunumeric/random/rand.h" -#include "cunumeric/random/rand_template.inl" +#include "cupynumeric/random/rand.h" +#include "cupynumeric/random/rand_template.inl" -#include "cunumeric/cuda_help.h" +#include "cupynumeric/cuda_help.h" -namespace cunumeric { +namespace cupynumeric { template static __global__ void __launch_bounds__(THREADS_PER_BLOCK, MIN_CTAS_PER_SM) rand_kernel( @@ -50,7 +50,7 @@ struct RandImplBody { auto stream = get_cached_stream(); rand_kernel<<>>( volume, out, rng, strides, pitches, rect.lo); - CUNUMERIC_CHECK_CUDA_STREAM(stream); + CUPYNUMERIC_CHECK_CUDA_STREAM(stream); } }; @@ -59,4 +59,4 @@ struct RandImplBody { rand_template(context); } -} // namespace cunumeric +} // namespace cupynumeric diff --git a/src/cunumeric/random/rand.h b/src/cupynumeric/random/rand.h similarity index 80% rename from src/cunumeric/random/rand.h rename to src/cupynumeric/random/rand.h index 1269c25df0..ccf2de91ef 100644 --- a/src/cunumeric/random/rand.h +++ b/src/cupynumeric/random/rand.h @@ -16,10 +16,10 @@ #pragma once -#include "cunumeric/cunumeric_task.h" -#include "cunumeric/random/rand_util.h" +#include "cupynumeric/cupynumeric_task.h" +#include "cupynumeric/random/rand_util.h" -namespace cunumeric { +namespace cupynumeric { struct RandArgs { legate::PhysicalStore out; @@ -29,9 +29,9 @@ struct RandArgs { std::vector args; }; -class RandTask : public CuNumericTask { +class RandTask : public CuPyNumericTask { public: - static constexpr auto TASK_ID = legate::LocalTaskID{CUNUMERIC_RAND}; + static constexpr auto TASK_ID = legate::LocalTaskID{CUPYNUMERIC_RAND}; public: static void cpu_variant(legate::TaskContext context); @@ -43,4 +43,4 @@ class RandTask : public CuNumericTask { #endif }; -} // namespace cunumeric +} // namespace cupynumeric diff --git a/src/cunumeric/random/rand_omp.cc b/src/cupynumeric/random/rand_omp.cc similarity index 91% rename from src/cunumeric/random/rand_omp.cc rename to src/cupynumeric/random/rand_omp.cc index 089a8829e6..6eaaccdca8 100644 --- a/src/cunumeric/random/rand_omp.cc +++ b/src/cupynumeric/random/rand_omp.cc @@ -14,10 +14,10 @@ * */ -#include "cunumeric/random/rand.h" -#include "cunumeric/random/rand_template.inl" +#include "cupynumeric/random/rand.h" +#include "cupynumeric/random/rand_template.inl" -namespace cunumeric { +namespace cupynumeric { using namespace legate; @@ -47,4 +47,4 @@ struct RandImplBody { rand_template(context); } -} // namespace cunumeric +} // namespace cupynumeric diff --git a/src/cunumeric/random/rand_template.inl b/src/cupynumeric/random/rand_template.inl similarity index 92% rename from src/cunumeric/random/rand_template.inl rename to src/cupynumeric/random/rand_template.inl index 9aac043c44..4db3fec1c2 100644 --- a/src/cunumeric/random/rand_template.inl +++ b/src/cupynumeric/random/rand_template.inl @@ -17,12 +17,12 @@ #pragma once // Useful for IDEs -#include "cunumeric/random/rand.h" -#include "cunumeric/arg.h" -#include "cunumeric/arg.inl" -#include "cunumeric/pitches.h" +#include "cupynumeric/random/rand.h" +#include "cupynumeric/arg.h" +#include "cupynumeric/arg.inl" +#include "cupynumeric/pitches.h" -namespace cunumeric { +namespace cupynumeric { using namespace legate; @@ -89,4 +89,4 @@ static void rand_template(TaskContext& context) op_dispatch(args.gen_code, RandDispatch{}, args); } -} // namespace cunumeric +} // namespace cupynumeric diff --git a/src/cunumeric/random/rand_util.h b/src/cupynumeric/random/rand_util.h similarity index 98% rename from src/cunumeric/random/rand_util.h rename to src/cupynumeric/random/rand_util.h index 69412cbc30..95968d6b3c 100644 --- a/src/cunumeric/random/rand_util.h +++ b/src/cupynumeric/random/rand_util.h @@ -16,13 +16,13 @@ #pragma once -#include "cunumeric/cunumeric_task.h" -#include "cunumeric/random/philox.h" +#include "cupynumeric/cupynumeric_task.h" +#include "cupynumeric/random/philox.h" #define HI_BITS(x) (static_cast((x) >> 32)) #define LO_BITS(x) (static_cast((x)&0x00000000FFFFFFFF)) -namespace cunumeric { +namespace cupynumeric { // Match these to RandGenCode in config.py enum class RandGenCode : int32_t { @@ -202,4 +202,4 @@ struct RandomGenerator { uint64_t diff; }; -} // namespace cunumeric +} // namespace cupynumeric diff --git a/src/cunumeric/random/randutil/generator.cuh b/src/cupynumeric/random/randutil/generator.cuh similarity index 84% rename from src/cunumeric/random/randutil/generator.cuh rename to src/cupynumeric/random/randutil/generator.cuh index b0046bf683..45e1706cd6 100644 --- a/src/cunumeric/random/randutil/generator.cuh +++ b/src/cupynumeric/random/randutil/generator.cuh @@ -18,7 +18,7 @@ #include "generator.h" -#include "cunumeric/cuda_help.h" +#include "cupynumeric/cuda_help.h" namespace randutilimpl { static constexpr int blocksPerMultiProcessor = 2; // TODO: refine => number of blocks per mp @@ -73,47 +73,47 @@ struct inner_generator : basegenerato : seed(seed), generatorID(generatorID), stream(stream) { int deviceId; - CUNUMERIC_CHECK_CUDA(::cudaGetDevice(&deviceId)); - CUNUMERIC_CHECK_CUDA( + CUPYNUMERIC_CHECK_CUDA(::cudaGetDevice(&deviceId)); + CUPYNUMERIC_CHECK_CUDA( ::cudaDeviceGetAttribute(&multiProcessorCount, cudaDevAttrMultiProcessorCount, deviceId)); // get number of generators ngenerators = blockDimX * multiProcessorCount * blocksPerMultiProcessor; -#ifdef DEBUG_CUNUMERIC +#ifdef DEBUG_CUPYNUMERIC assert(ngenerators > 0); #endif // allocate buffer for generators state int driverVersion, runtimeVersion; - CUNUMERIC_CHECK_CUDA(::cudaDriverGetVersion(&driverVersion)); - CUNUMERIC_CHECK_CUDA(::cudaRuntimeGetVersion(&runtimeVersion)); + CUPYNUMERIC_CHECK_CUDA(::cudaDriverGetVersion(&driverVersion)); + CUPYNUMERIC_CHECK_CUDA(::cudaRuntimeGetVersion(&runtimeVersion)); asyncsupported = ((driverVersion >= 10020) && (runtimeVersion >= 10020)); if (asyncsupported) { #if (__CUDACC_VER_MAJOR__ > 11 || ((__CUDACC_VER_MAJOR__ >= 11) && (__CUDACC_VER_MINOR__ >= 2))) - CUNUMERIC_CHECK_CUDA(::cudaMallocAsync(&generators, ngenerators * sizeof(gen_t), stream)); + CUPYNUMERIC_CHECK_CUDA(::cudaMallocAsync(&generators, ngenerators * sizeof(gen_t), stream)); #else - CUNUMERIC_CHECK_CUDA(::cudaMalloc(&generators, ngenerators * sizeof(gen_t))); + CUPYNUMERIC_CHECK_CUDA(::cudaMalloc(&generators, ngenerators * sizeof(gen_t))); #endif } else { - CUNUMERIC_CHECK_CUDA(::cudaMalloc(&generators, ngenerators * sizeof(gen_t))); + CUPYNUMERIC_CHECK_CUDA(::cudaMalloc(&generators, ngenerators * sizeof(gen_t))); } // initialize generators initgenerators<<>>( generators, seed, generatorID); - CUNUMERIC_CHECK_CUDA(::cudaPeekAtLastError()); + CUPYNUMERIC_CHECK_CUDA(::cudaPeekAtLastError()); } virtual void destroy() override { - CUNUMERIC_CHECK_CUDA(::cudaStreamSynchronize(stream)); + CUPYNUMERIC_CHECK_CUDA(::cudaStreamSynchronize(stream)); if (asyncsupported) { #if (__CUDACC_VER_MAJOR__ > 11 || ((__CUDACC_VER_MAJOR__ >= 11) && (__CUDACC_VER_MINOR__ >= 2))) - CUNUMERIC_CHECK_CUDA(::cudaFreeAsync(generators, stream)); + CUPYNUMERIC_CHECK_CUDA(::cudaFreeAsync(generators, stream)); #else - CUNUMERIC_CHECK_CUDA(::cudaFree(generators)); + CUPYNUMERIC_CHECK_CUDA(::cudaFree(generators)); #endif } else { - CUNUMERIC_CHECK_CUDA(::cudaFree(generators)); + CUPYNUMERIC_CHECK_CUDA(::cudaFree(generators)); } generators = nullptr; diff --git a/src/cunumeric/random/randutil/generator.h b/src/cupynumeric/random/randutil/generator.h similarity index 96% rename from src/cunumeric/random/randutil/generator.h rename to src/cupynumeric/random/randutil/generator.h index 50d3b0489f..41b2fe35fe 100644 --- a/src/cunumeric/random/randutil/generator.h +++ b/src/cupynumeric/random/randutil/generator.h @@ -23,7 +23,7 @@ #include "randutil_curand.h" #include "randutil_impl.h" -#include "cunumeric/random/rnd_aliases.h" +#include "cupynumeric/random/rnd_aliases.h" namespace randutilimpl { @@ -42,7 +42,7 @@ struct generatorid { static constexpr int rng_type = RND_RNG_PSEUDO_XORWOW; }; -#ifndef CUNUMERIC_USE_STL_RANDOM_ENGINE +#ifndef CUPYNUMERIC_USE_STL_RANDOM_ENGINE // Curand *different* specializations, not possible with only one generator // template <> @@ -64,12 +64,12 @@ struct inner_generator : basegenerator inner_generator(uint64_t seed, uint64_t generatorID, stream_t) : seed(seed), generatorID(generatorID) -#ifdef CUNUMERIC_USE_STL_RANDOM_ENGINE +#ifdef CUPYNUMERIC_USE_STL_RANDOM_ENGINE , generator(seed) #endif { -#ifdef CUNUMERIC_USE_STL_RANDOM_ENGINE +#ifdef CUPYNUMERIC_USE_STL_RANDOM_ENGINE std::srand(seed); #else curand_init(seed, generatorID, 0, &generator); diff --git a/src/cunumeric/random/randutil/generator_beta.inl b/src/cupynumeric/random/randutil/generator_beta.inl similarity index 100% rename from src/cunumeric/random/randutil/generator_beta.inl rename to src/cupynumeric/random/randutil/generator_beta.inl diff --git a/src/cunumeric/random/randutil/generator_binomial.inl b/src/cupynumeric/random/randutil/generator_binomial.inl similarity index 100% rename from src/cunumeric/random/randutil/generator_binomial.inl rename to src/cupynumeric/random/randutil/generator_binomial.inl diff --git a/src/cunumeric/random/randutil/generator_cauchy.inl b/src/cupynumeric/random/randutil/generator_cauchy.inl similarity index 100% rename from src/cunumeric/random/randutil/generator_cauchy.inl rename to src/cupynumeric/random/randutil/generator_cauchy.inl diff --git a/src/cunumeric/random/randutil/generator_chisquare.inl b/src/cupynumeric/random/randutil/generator_chisquare.inl similarity index 100% rename from src/cunumeric/random/randutil/generator_chisquare.inl rename to src/cupynumeric/random/randutil/generator_chisquare.inl diff --git a/src/cunumeric/random/randutil/generator_create.inl b/src/cupynumeric/random/randutil/generator_create.inl similarity index 100% rename from src/cunumeric/random/randutil/generator_create.inl rename to src/cupynumeric/random/randutil/generator_create.inl diff --git a/src/cunumeric/random/randutil/generator_device.cu b/src/cupynumeric/random/randutil/generator_device.cu similarity index 100% rename from src/cunumeric/random/randutil/generator_device.cu rename to src/cupynumeric/random/randutil/generator_device.cu diff --git a/src/cunumeric/random/randutil/generator_device_advanced.cu b/src/cupynumeric/random/randutil/generator_device_advanced.cu similarity index 100% rename from src/cunumeric/random/randutil/generator_device_advanced.cu rename to src/cupynumeric/random/randutil/generator_device_advanced.cu diff --git a/src/cunumeric/random/randutil/generator_device_straightforward.cu b/src/cupynumeric/random/randutil/generator_device_straightforward.cu similarity index 100% rename from src/cunumeric/random/randutil/generator_device_straightforward.cu rename to src/cupynumeric/random/randutil/generator_device_straightforward.cu diff --git a/src/cunumeric/random/randutil/generator_exponential.inl b/src/cupynumeric/random/randutil/generator_exponential.inl similarity index 100% rename from src/cunumeric/random/randutil/generator_exponential.inl rename to src/cupynumeric/random/randutil/generator_exponential.inl diff --git a/src/cunumeric/random/randutil/generator_f.inl b/src/cupynumeric/random/randutil/generator_f.inl similarity index 100% rename from src/cunumeric/random/randutil/generator_f.inl rename to src/cupynumeric/random/randutil/generator_f.inl diff --git a/src/cunumeric/random/randutil/generator_gamma.inl b/src/cupynumeric/random/randutil/generator_gamma.inl similarity index 100% rename from src/cunumeric/random/randutil/generator_gamma.inl rename to src/cupynumeric/random/randutil/generator_gamma.inl diff --git a/src/cunumeric/random/randutil/generator_geometric.inl b/src/cupynumeric/random/randutil/generator_geometric.inl similarity index 100% rename from src/cunumeric/random/randutil/generator_geometric.inl rename to src/cupynumeric/random/randutil/generator_geometric.inl diff --git a/src/cunumeric/random/randutil/generator_gumbel.inl b/src/cupynumeric/random/randutil/generator_gumbel.inl similarity index 93% rename from src/cunumeric/random/randutil/generator_gumbel.inl rename to src/cupynumeric/random/randutil/generator_gumbel.inl index e60b8a60bc..dffc255fd0 100644 --- a/src/cunumeric/random/randutil/generator_gumbel.inl +++ b/src/cupynumeric/random/randutil/generator_gumbel.inl @@ -31,7 +31,9 @@ struct gumbel_t { { auto y = randutilimpl::engine_uniform(gen); // y cannot be zero - if (y == 1.0f) return mu; + if (y == 1.0f) { + return mu; + } float lny = ::logf(y); return mu - beta * ::logf(-lny); } @@ -46,7 +48,9 @@ struct gumbel_t { { auto y = randutilimpl::engine_uniform(gen); // y cannot be zero - if (y == 1.0) return mu; + if (y == 1.0) { + return mu; + } double lny = ::log(y); return mu - beta * ::log(-lny); } diff --git a/src/cunumeric/random/randutil/generator_host.cc b/src/cupynumeric/random/randutil/generator_host.cc similarity index 96% rename from src/cunumeric/random/randutil/generator_host.cc rename to src/cupynumeric/random/randutil/generator_host.cc index 0752149222..6a20278c95 100644 --- a/src/cunumeric/random/randutil/generator_host.cc +++ b/src/cupynumeric/random/randutil/generator_host.cc @@ -18,15 +18,15 @@ // CPU Builds: // -#if !LEGATE_DEFINED(LEGATE_USE_CUDA) && !LEGATE_DEFINED(CUNUMERIC_CURAND_FOR_CPU_BUILD) -#define CUNUMERIC_USE_STL_RANDOM_ENGINE +#if !LEGATE_DEFINED(LEGATE_USE_CUDA) && !LEGATE_DEFINED(CUPYNUMERIC_CURAND_FOR_CPU_BUILD) +#define CUPYNUMERIC_USE_STL_RANDOM_ENGINE #endif #include "generator.h" #include "generator_create.inl" -#include "cunumeric/random/rnd_aliases.h" +#include "cupynumeric/random/rnd_aliases.h" -#if LEGATE_DEFINED(CUNUMERIC_CURAND_FOR_CPU_BUILD) +#if LEGATE_DEFINED(CUPYNUMERIC_CURAND_FOR_CPU_BUILD) // the host code of cuRAND try to extern these variables out of nowhere, // so we need to define them somewhere. const dim3 blockDim{}; diff --git a/src/cunumeric/random/randutil/generator_host_advanced.cc b/src/cupynumeric/random/randutil/generator_host_advanced.cc similarity index 98% rename from src/cunumeric/random/randutil/generator_host_advanced.cc rename to src/cupynumeric/random/randutil/generator_host_advanced.cc index fc3817c8b5..97d47570f6 100644 --- a/src/cunumeric/random/randutil/generator_host_advanced.cc +++ b/src/cupynumeric/random/randutil/generator_host_advanced.cc @@ -18,8 +18,8 @@ // CPU Builds: // -#if !LEGATE_DEFINED(LEGATE_USE_CUDA) && !LEGATE_DEFINED(CUNUMERIC_CURAND_FOR_CPU_BUILD) -#define CUNUMERIC_USE_STL_RANDOM_ENGINE +#if !LEGATE_DEFINED(LEGATE_USE_CUDA) && !LEGATE_DEFINED(CUPYNUMERIC_CURAND_FOR_CPU_BUILD) +#define CUPYNUMERIC_USE_STL_RANDOM_ENGINE #endif #include "generator.h" diff --git a/src/cunumeric/random/randutil/generator_host_straightforward.cc b/src/cupynumeric/random/randutil/generator_host_straightforward.cc similarity index 98% rename from src/cunumeric/random/randutil/generator_host_straightforward.cc rename to src/cupynumeric/random/randutil/generator_host_straightforward.cc index 3deffdf216..6984fb8664 100644 --- a/src/cunumeric/random/randutil/generator_host_straightforward.cc +++ b/src/cupynumeric/random/randutil/generator_host_straightforward.cc @@ -18,8 +18,8 @@ // CPU Builds: // -#if !LEGATE_DEFINED(LEGATE_USE_CUDA) && !LEGATE_DEFINED(CUNUMERIC_CURAND_FOR_CPU_BUILD) -#define CUNUMERIC_USE_STL_RANDOM_ENGINE +#if !LEGATE_DEFINED(LEGATE_USE_CUDA) && !LEGATE_DEFINED(CUPYNUMERIC_CURAND_FOR_CPU_BUILD) +#define CUPYNUMERIC_USE_STL_RANDOM_ENGINE #endif #include "generator.h" diff --git a/src/cunumeric/random/randutil/generator_hypergeometric.inl b/src/cupynumeric/random/randutil/generator_hypergeometric.inl similarity index 100% rename from src/cunumeric/random/randutil/generator_hypergeometric.inl rename to src/cupynumeric/random/randutil/generator_hypergeometric.inl diff --git a/src/cunumeric/random/randutil/generator_integers.inl b/src/cupynumeric/random/randutil/generator_integers.inl similarity index 100% rename from src/cunumeric/random/randutil/generator_integers.inl rename to src/cupynumeric/random/randutil/generator_integers.inl diff --git a/src/cunumeric/random/randutil/generator_laplace.inl b/src/cupynumeric/random/randutil/generator_laplace.inl similarity index 100% rename from src/cunumeric/random/randutil/generator_laplace.inl rename to src/cupynumeric/random/randutil/generator_laplace.inl diff --git a/src/cunumeric/random/randutil/generator_logistic.inl b/src/cupynumeric/random/randutil/generator_logistic.inl similarity index 94% rename from src/cunumeric/random/randutil/generator_logistic.inl rename to src/cupynumeric/random/randutil/generator_logistic.inl index e0db0e4dd4..913ab06028 100644 --- a/src/cunumeric/random/randutil/generator_logistic.inl +++ b/src/cupynumeric/random/randutil/generator_logistic.inl @@ -30,7 +30,9 @@ struct logistic_t { { float y = randutilimpl::engine_uniform(gen); // y cannot be 0 float t = 1.0f / y - 1.0f; - if (t == 0) t = 1.0f; + if (t == 0) { + t = 1.0f; + } return mu - beta * ::logf(t); } }; @@ -44,7 +46,9 @@ struct logistic_t { { auto y = randutilimpl::engine_uniform(gen); // y cannot be 0 auto t = 1.0 / y - 1.0; - if (t == 0) t = 1.0; + if (t == 0) { + t = 1.0; + } return mu - beta * ::log(t); } }; diff --git a/src/cunumeric/random/randutil/generator_lognormal.inl b/src/cupynumeric/random/randutil/generator_lognormal.inl similarity index 100% rename from src/cunumeric/random/randutil/generator_lognormal.inl rename to src/cupynumeric/random/randutil/generator_lognormal.inl diff --git a/src/cunumeric/random/randutil/generator_logseries.inl b/src/cupynumeric/random/randutil/generator_logseries.inl similarity index 100% rename from src/cunumeric/random/randutil/generator_logseries.inl rename to src/cupynumeric/random/randutil/generator_logseries.inl diff --git a/src/cunumeric/random/randutil/generator_negative_binomial.inl b/src/cupynumeric/random/randutil/generator_negative_binomial.inl similarity index 100% rename from src/cunumeric/random/randutil/generator_negative_binomial.inl rename to src/cupynumeric/random/randutil/generator_negative_binomial.inl diff --git a/src/cunumeric/random/randutil/generator_normal.inl b/src/cupynumeric/random/randutil/generator_normal.inl similarity index 100% rename from src/cunumeric/random/randutil/generator_normal.inl rename to src/cupynumeric/random/randutil/generator_normal.inl diff --git a/src/cunumeric/random/randutil/generator_pareto.inl b/src/cupynumeric/random/randutil/generator_pareto.inl similarity index 100% rename from src/cunumeric/random/randutil/generator_pareto.inl rename to src/cupynumeric/random/randutil/generator_pareto.inl diff --git a/src/cunumeric/random/randutil/generator_poisson.inl b/src/cupynumeric/random/randutil/generator_poisson.inl similarity index 100% rename from src/cunumeric/random/randutil/generator_poisson.inl rename to src/cupynumeric/random/randutil/generator_poisson.inl diff --git a/src/cunumeric/random/randutil/generator_power.inl b/src/cupynumeric/random/randutil/generator_power.inl similarity index 100% rename from src/cunumeric/random/randutil/generator_power.inl rename to src/cupynumeric/random/randutil/generator_power.inl diff --git a/src/cunumeric/random/randutil/generator_raw.inl b/src/cupynumeric/random/randutil/generator_raw.inl similarity index 100% rename from src/cunumeric/random/randutil/generator_raw.inl rename to src/cupynumeric/random/randutil/generator_raw.inl diff --git a/src/cunumeric/random/randutil/generator_rayleigh.inl b/src/cupynumeric/random/randutil/generator_rayleigh.inl similarity index 100% rename from src/cunumeric/random/randutil/generator_rayleigh.inl rename to src/cupynumeric/random/randutil/generator_rayleigh.inl diff --git a/src/cunumeric/random/randutil/generator_standard_t.inl b/src/cupynumeric/random/randutil/generator_standard_t.inl similarity index 100% rename from src/cunumeric/random/randutil/generator_standard_t.inl rename to src/cupynumeric/random/randutil/generator_standard_t.inl diff --git a/src/cunumeric/random/randutil/generator_triangular.inl b/src/cupynumeric/random/randutil/generator_triangular.inl similarity index 100% rename from src/cunumeric/random/randutil/generator_triangular.inl rename to src/cupynumeric/random/randutil/generator_triangular.inl diff --git a/src/cunumeric/random/randutil/generator_uniform.inl b/src/cupynumeric/random/randutil/generator_uniform.inl similarity index 100% rename from src/cunumeric/random/randutil/generator_uniform.inl rename to src/cupynumeric/random/randutil/generator_uniform.inl diff --git a/src/cunumeric/random/randutil/generator_vonmises.inl b/src/cupynumeric/random/randutil/generator_vonmises.inl similarity index 100% rename from src/cunumeric/random/randutil/generator_vonmises.inl rename to src/cupynumeric/random/randutil/generator_vonmises.inl diff --git a/src/cunumeric/random/randutil/generator_wald.inl b/src/cupynumeric/random/randutil/generator_wald.inl similarity index 100% rename from src/cunumeric/random/randutil/generator_wald.inl rename to src/cupynumeric/random/randutil/generator_wald.inl diff --git a/src/cunumeric/random/randutil/generator_weibull.inl b/src/cupynumeric/random/randutil/generator_weibull.inl similarity index 93% rename from src/cunumeric/random/randutil/generator_weibull.inl rename to src/cupynumeric/random/randutil/generator_weibull.inl index 4f0411e2ac..19d2875979 100644 --- a/src/cunumeric/random/randutil/generator_weibull.inl +++ b/src/cupynumeric/random/randutil/generator_weibull.inl @@ -31,7 +31,9 @@ struct weibull_t { float y = randutilimpl::engine_uniform(gen); // y cannot be 0 // log(y) can be zero ! auto lny = ::logf(y); - if (lny == 0.0f) return 0.0f; + if (lny == 0.0f) { + return 0.0f; + } return lambda * ::expf(::logf(-lny) * invk); } }; @@ -46,7 +48,9 @@ struct weibull_t { double y = randutilimpl::engine_uniform(gen); // y cannot be 0 // log(y) can be zero ! auto lny = ::log(y); - if (lny == 0.0f) return 0.0f; + if (lny == 0.0f) { + return 0.0f; + } return lambda * ::exp(::log(-lny) * invk); } }; diff --git a/src/cunumeric/random/randutil/generator_zipf.inl b/src/cupynumeric/random/randutil/generator_zipf.inl similarity index 100% rename from src/cunumeric/random/randutil/generator_zipf.inl rename to src/cupynumeric/random/randutil/generator_zipf.inl diff --git a/src/cunumeric/random/randutil/random_distributions.h b/src/cupynumeric/random/randutil/random_distributions.h similarity index 90% rename from src/cunumeric/random/randutil/random_distributions.h rename to src/cupynumeric/random/randutil/random_distributions.h index 244c8d83f4..5e0977309d 100644 --- a/src/cunumeric/random/randutil/random_distributions.h +++ b/src/cupynumeric/random/randutil/random_distributions.h @@ -145,11 +145,15 @@ RANDUTIL_QUALIFIERS double rk_standard_gamma(rk_state* state, double shape) V = rk_standard_exponential(state); if (U <= 1.0 - shape) { X = pow(U, 1. / shape); - if (X <= V) { return X; } + if (X <= V) { + return X; + } } else { Y = -log((1 - U) / shape); X = pow(1.0 - shape + shape * Y, 1. / shape); - if (X <= (V + Y)) { return X; } + if (X <= (V + Y)) { + return X; + } } } } else { @@ -162,8 +166,12 @@ RANDUTIL_QUALIFIERS double rk_standard_gamma(rk_state* state, double shape) } while (V <= 0.0); V = V * V * V; U = rk_double(state); - if (U < 1.0 - 0.0331 * (X * X) * (X * X)) return (b * V); - if (log(U) < 0.5 * X * X + b * (1. - V + log(V))) return (b * V); + if (U < 1.0 - 0.0331 * (X * X) * (X * X)) { + return (b * V); + } + if (log(U) < 0.5 * X * X + b * (1. - V + log(V))) { + return (b * V); + } } } } @@ -223,8 +231,12 @@ RANDUTIL_QUALIFIERS long rk_poisson_ptrs(rk_state* state, double lam) V = rk_double(state); us = 0.5 - fabs(U); k = (long)floor((2 * a / us + b) * U + lam + 0.43); - if ((us >= 0.07) && (V <= vr)) { return k; } - if ((k < 0) || ((us < 0.013) && (V > us))) { continue; } + if ((us >= 0.07) && (V <= vr)) { + return k; + } + if ((k < 0) || ((us < 0.013) && (V > us))) { + continue; + } if ((log(V) + log(invalpha) - log(a / (us * us) + b)) <= (-lam + k * loglam - loggam(k + 1))) { return k; } @@ -274,7 +286,9 @@ RANDUTIL_QUALIFIERS double rk_chisquare(rk_state* state, double df) template RANDUTIL_QUALIFIERS double rk_noncentral_chisquare(rk_state* state, double df, double nonc) { - if (nonc == 0) { return rk_chisquare(state, df); } + if (nonc == 0) { + return rk_chisquare(state, df); + } if (1 < df) { const double Chi2 = rk_chisquare(state, df - 1); const double N = rk_gauss(state) + sqrt(nonc); @@ -306,7 +320,9 @@ RANDUTIL_QUALIFIERS long rk_logseries(rk_state* state, double p) r = log(1.0 - p); while (1) { V = rk_double(state); - if (V >= p) { return 1; } + if (V >= p) { + return 1; + } U = rk_double(state); q = 1.0 - exp(r * U); if (V <= q * q) { @@ -317,7 +333,9 @@ RANDUTIL_QUALIFIERS long rk_logseries(rk_state* state, double p) return result; } } - if (V >= q) { return 1; } + if (V >= q) { + return 1; + } return 2; } } @@ -373,9 +391,13 @@ RANDUTIL_QUALIFIERS long rk_zipf(rk_state* state, double a) U = 1.0 - rk_double(state); V = rk_double(state); X = floor(pow(U, -1.0 / am1)); - if (X < 1.0) { continue; } + if (X < 1.0) { + continue; + } T = pow(1.0 + 1.0 / X, am1); - if (V * X * (T - 1.0) / (b - 1.0) <= T / b) { return (long)X; } + if (V * X * (T - 1.0) / (b - 1.0) <= T / b) { + return (long)X; + } } } @@ -407,16 +429,22 @@ RANDUTIL_QUALIFIERS double rk_vonmises(rk_state* state, double mu, double kappa) W = (1 + s * Z) / (s + Z); Y = kappa * (s - W); V = rk_double(state); - if ((Y * (2 - Y) - V >= 0) || (log(Y / V) + 1 - Y >= 0)) { break; } + if ((Y * (2 - Y) - V >= 0) || (log(Y / V) + 1 - Y >= 0)) { + break; + } } U = rk_double(state); result = acos(W); - if (U < 0.5) { result = -result; } + if (U < 0.5) { + result = -result; + } result += mu; neg = (result < 0); mod = fabs(result); mod = (fmod(mod + M_PI, 2 * M_PI) - M_PI); - if (neg) { mod *= -1; } + if (neg) { + mod *= -1; + } return mod; } } @@ -437,10 +465,14 @@ RANDUTIL_QUALIFIERS long rk_hypergeometric_hyp(rk_state* state, long good, long U = rk_double(state); Y -= (long)floor(U + Y / (d1 + K)); K--; - if (K == 0) break; + if (K == 0) { + break; + } } Z = (long)(d2 - Y); - if (good > bad) Z = sample - Z; + if (good > bad) { + Z = sample - Z; + } return Z; } /* D1 = 2*sqrt(2/e) */ @@ -473,20 +505,32 @@ RANDUTIL_QUALIFIERS long rk_hypergeometric_hrua(rk_state* state, long good, long Y = rk_double(state); W = d6 + d8 * (Y - 0.5) / X; /* fast rejection: */ - if ((W < 0.0) || (W >= d11)) continue; + if ((W < 0.0) || (W >= d11)) { + continue; + } Z = (long)floor(W); T = d10 - (loggam(Z + 1) + loggam(mingoodbad - Z + 1) + loggam(m - Z + 1) + loggam(maxgoodbad - m + Z + 1)); /* fast acceptance: */ - if ((X * (4.0 - X) - 3.0) <= T) break; + if ((X * (4.0 - X) - 3.0) <= T) { + break; + } /* fast rejection: */ - if (X * (X - T) >= 1) continue; - if (2.0 * log(X) <= T) break; /* acceptance */ + if (X * (X - T) >= 1) { + continue; + } + if (2.0 * log(X) <= T) { + break; /* acceptance */ + } } /* this is a correction to HRUA* by Ivan Frohne in rv.py */ - if (good > bad) Z = m - Z; + if (good > bad) { + Z = m - Z; + } /* another fix from rv.py to allow sample to exceed popsize/2 */ - if (m < sample) Z = good - Z; + if (m < sample) { + Z = good - Z; + } return Z; } #undef D1 @@ -531,45 +575,69 @@ RANDUTIL_QUALIFIERS unsigned rk_binomial_btpe(rk_state* state, unsigned n, doubl nrq = n * r * q; u = rk_double(state) * p4; v = rk_double(state); - if (u > p1) goto Step20; + if (u > p1) { + goto Step20; + } y = (long)floor(xm - p1 * v + u); goto Step60; Step20: - if (u > p2) goto Step30; + if (u > p2) { + goto Step30; + } x = xl + (u - p1) / c; v = v * c + 1.0 - fabs(m - x + 0.5) / p1; - if (v > 1.0) goto Step10; + if (v > 1.0) { + goto Step10; + } y = (long)floor(x); goto Step50; Step30: - if (u > p3) goto Step40; + if (u > p3) { + goto Step40; + } y = (long)floor(xl + log(v) / laml); - if (y < 0) goto Step10; + if (y < 0) { + goto Step10; + } v = v * (u - p2) * laml; goto Step50; Step40: y = (long)floor(xr - log(v) / lamr); - if (y > n) goto Step10; + if (y > n) { + goto Step10; + } v = v * (u - p3) * lamr; Step50: k = labs(y - m); - if ((k > 20) && (k < ((nrq) / 2.0 - 1))) goto Step52; + if ((k > 20) && (k < ((nrq) / 2.0 - 1))) { + goto Step52; + } s = r / q; a = s * (n + 1); F = 1.0; if (m < y) { - for (i = m + 1; i <= y; i++) { F *= (a / i - s); } + for (i = m + 1; i <= y; i++) { + F *= (a / i - s); + } } else if (m > y) { - for (i = y + 1; i <= m; i++) { F /= (a / i - s); } + for (i = y + 1; i <= m; i++) { + F /= (a / i - s); + } + } + if (v > F) { + goto Step10; } - if (v > F) goto Step10; goto Step60; Step52: rho = (k / (nrq)) * ((k * (k / 3.0 + 0.625) + 0.16666666666666666) / nrq + 0.5); t = -k * k / (2 * nrq); A = log(v); - if (A < (t - rho)) goto Step60; - if (A > (t + rho)) goto Step10; + if (A < (t - rho)) { + goto Step60; + } + if (A > (t + rho)) { + goto Step10; + } x1 = y + 1; f1 = m + 1; z = n + 1 - m; @@ -586,7 +654,9 @@ RANDUTIL_QUALIFIERS unsigned rk_binomial_btpe(rk_state* state, unsigned n, doubl goto Step10; } Step60: - if (p > 0.5) { y = n - y; } + if (p > 0.5) { + y = n - y; + } return (unsigned)y; } diff --git a/src/cunumeric/random/randutil/randomizer.h b/src/cupynumeric/random/randutil/randomizer.h similarity index 94% rename from src/cunumeric/random/randutil/randomizer.h rename to src/cupynumeric/random/randutil/randomizer.h index 4b9fc068db..796f28a47c 100644 --- a/src/cunumeric/random/randutil/randomizer.h +++ b/src/cupynumeric/random/randutil/randomizer.h @@ -32,7 +32,7 @@ namespace randutilimpl { template RANDUTIL_QUALIFIERS decltype(auto) engine_uniform(gen_t& gen) { -#ifdef CUNUMERIC_USE_STL_RANDOM_ENGINE +#ifdef CUPYNUMERIC_USE_STL_RANDOM_ENGINE std::uniform_real_distribution dis(0, 1); auto y = dis(gen); // returns [0, 1); @@ -52,7 +52,7 @@ RANDUTIL_QUALIFIERS decltype(auto) engine_uniform(gen_t& gen) template RANDUTIL_QUALIFIERS decltype(auto) engine_poisson(gen_t& gen, double lambda) { -#ifdef CUNUMERIC_USE_STL_RANDOM_ENGINE +#ifdef CUPYNUMERIC_USE_STL_RANDOM_ENGINE std::poisson_distribution dis(lambda); return dis(gen); #else @@ -63,7 +63,7 @@ RANDUTIL_QUALIFIERS decltype(auto) engine_poisson(gen_t& gen, double lambda) template RANDUTIL_QUALIFIERS decltype(auto) engine_normal(gen_t& gen) { -#ifdef CUNUMERIC_USE_STL_RANDOM_ENGINE +#ifdef CUPYNUMERIC_USE_STL_RANDOM_ENGINE std::normal_distribution dis(0, 1); return dis(gen); #else @@ -80,7 +80,7 @@ RANDUTIL_QUALIFIERS decltype(auto) engine_normal(gen_t& gen) template RANDUTIL_QUALIFIERS decltype(auto) engine_log_normal(gen_t& gen, element_t mean, element_t stddev) { -#ifdef CUNUMERIC_USE_STL_RANDOM_ENGINE +#ifdef CUPYNUMERIC_USE_STL_RANDOM_ENGINE std::lognormal_distribution dis{mean, stddev}; return dis(gen); #else @@ -97,7 +97,7 @@ RANDUTIL_QUALIFIERS decltype(auto) engine_log_normal(gen_t& gen, element_t mean, template RANDUTIL_QUALIFIERS decltype(auto) engine_rand(gen_t& gen) { -#ifdef CUNUMERIC_USE_STL_RANDOM_ENGINE +#ifdef CUPYNUMERIC_USE_STL_RANDOM_ENGINE return std::rand(); #else return curand(&gen); diff --git a/src/cunumeric/random/randutil/randutil.h b/src/cupynumeric/random/randutil/randutil.h similarity index 99% rename from src/cunumeric/random/randutil/randutil.h rename to src/cupynumeric/random/randutil/randutil.h index e52e8a77ca..31130ce464 100644 --- a/src/cunumeric/random/randutil/randutil.h +++ b/src/cupynumeric/random/randutil/randutil.h @@ -18,7 +18,7 @@ #include // #include -#include "cunumeric/random/rnd_aliases.h" +#include "cupynumeric/random/rnd_aliases.h" typedef void* randutilGenerator_t; diff --git a/src/cunumeric/random/randutil/randutil_curand.h b/src/cupynumeric/random/randutil/randutil_curand.h similarity index 100% rename from src/cunumeric/random/randutil/randutil_curand.h rename to src/cupynumeric/random/randutil/randutil_curand.h diff --git a/src/cunumeric/random/randutil/randutil_impl.h b/src/cupynumeric/random/randutil/randutil_impl.h similarity index 100% rename from src/cunumeric/random/randutil/randutil_impl.h rename to src/cupynumeric/random/randutil/randutil_impl.h diff --git a/src/cunumeric/random/rnd_aliases.h b/src/cupynumeric/random/rnd_aliases.h similarity index 98% rename from src/cunumeric/random/rnd_aliases.h rename to src/cupynumeric/random/rnd_aliases.h index 89d3fbebd2..e232e2d61f 100644 --- a/src/cunumeric/random/rnd_aliases.h +++ b/src/cupynumeric/random/rnd_aliases.h @@ -16,7 +16,7 @@ #pragma once -#ifdef CUNUMERIC_USE_STL_RANDOM_ENGINE +#ifdef CUPYNUMERIC_USE_STL_RANDOM_ENGINE // #pragma message("************ STL path *************") diff --git a/src/cunumeric/random/rnd_types.h b/src/cupynumeric/random/rnd_types.h similarity index 66% rename from src/cunumeric/random/rnd_types.h rename to src/cupynumeric/random/rnd_types.h index 0fcab9f489..dfd633792f 100644 --- a/src/cunumeric/random/rnd_types.h +++ b/src/cupynumeric/random/rnd_types.h @@ -16,10 +16,10 @@ #pragma once -#include "cunumeric/random/rnd_aliases.h" +#include "cupynumeric/random/rnd_aliases.h" #include -#ifdef CUNUMERIC_USE_STL_RANDOM_ENGINE +#ifdef CUPYNUMERIC_USE_STL_RANDOM_ENGINE #define CHECK_RND_ENGINE(expr) \ do { \ @@ -29,33 +29,33 @@ // #define randutil_check_curand randutil_check_status -namespace cunumeric { +namespace cupynumeric { legate::Logger& randutil_log(); void randutil_check_status(rnd_status_t error, std::string_view, int line); -static inline randRngType get_rndRngType(cunumeric::BitGeneratorType kind) +static inline randRngType get_rndRngType(cupynumeric::BitGeneratorType kind) { // for now, all generator types rerouted to STL // would use the MT19937 generator; perhaps, // this might become more flexible in the future; // switch (kind) { - case cunumeric::BitGeneratorType::DEFAULT: return randRngType::STL_MT_19937; - case cunumeric::BitGeneratorType::XORWOW: return randRngType::STL_MT_19937; - case cunumeric::BitGeneratorType::MRG32K3A: return randRngType::STL_MT_19937; - case cunumeric::BitGeneratorType::MTGP32: return randRngType::STL_MT_19937; - case cunumeric::BitGeneratorType::MT19937: return randRngType::STL_MT_19937; - case cunumeric::BitGeneratorType::PHILOX4_32_10: return randRngType::STL_MT_19937; + case cupynumeric::BitGeneratorType::DEFAULT: return randRngType::STL_MT_19937; + case cupynumeric::BitGeneratorType::XORWOW: return randRngType::STL_MT_19937; + case cupynumeric::BitGeneratorType::MRG32K3A: return randRngType::STL_MT_19937; + case cupynumeric::BitGeneratorType::MTGP32: return randRngType::STL_MT_19937; + case cupynumeric::BitGeneratorType::MT19937: return randRngType::STL_MT_19937; + case cupynumeric::BitGeneratorType::PHILOX4_32_10: return randRngType::STL_MT_19937; default: LEGATE_ABORT("Unsupported random generator."); } return randRngType::RND_RNG_TEST; } -} // namespace cunumeric +} // namespace cupynumeric #else -#include "cunumeric/random/curand_help.h" +#include "cupynumeric/random/curand_help.h" #define CHECK_RND_ENGINE(...) CHECK_CURAND(__VA_ARGS__) #define get_rndRngType get_curandRngType diff --git a/src/cunumeric/runtime.cc b/src/cupynumeric/runtime.cc similarity index 55% rename from src/cunumeric/runtime.cc rename to src/cupynumeric/runtime.cc index ff6afc92fc..04068a8ed5 100644 --- a/src/cunumeric/runtime.cc +++ b/src/cupynumeric/runtime.cc @@ -15,61 +15,61 @@ */ #include "env_defaults.h" -#include "cunumeric/runtime.h" +#include "cupynumeric/runtime.h" -#include "cunumeric/ndarray.h" -#include "cunumeric/unary/unary_red_util.h" +#include "cupynumeric/ndarray.h" +#include "cupynumeric/unary/unary_red_util.h" #include #include #include -namespace cunumeric { +namespace cupynumeric { -/*static*/ CuNumericRuntime* CuNumericRuntime::runtime_; +/*static*/ CuPyNumericRuntime* CuPyNumericRuntime::runtime_; extern void bootstrapping_callback(Legion::Machine machine, Legion::Runtime* runtime, const std::set& local_procs); -void initialize(int32_t argc, char** argv) { cunumeric_perform_registration(); } +void initialize(int32_t argc, char** argv) { cupynumeric_perform_registration(); } -CuNumericRuntime::CuNumericRuntime(legate::Runtime* legate_runtime, legate::Library library) +CuPyNumericRuntime::CuPyNumericRuntime(legate::Runtime* legate_runtime, legate::Library library) : legate_runtime_(legate_runtime), library_(library) { } -NDArray CuNumericRuntime::create_array(const legate::Type& type) +NDArray CuPyNumericRuntime::create_array(const legate::Type& type) { auto store = legate_runtime_->create_store(type); return NDArray(std::move(store)); } -NDArray CuNumericRuntime::create_array(std::vector shape, - const legate::Type& type, - bool optimize_scalar) +NDArray CuPyNumericRuntime::create_array(std::vector shape, + const legate::Type& type, + bool optimize_scalar) { auto store = legate_runtime_->create_store(legate::Shape{shape}, type, optimize_scalar); return NDArray(std::move(store)); } -NDArray CuNumericRuntime::create_array(legate::LogicalStore&& store) +NDArray CuPyNumericRuntime::create_array(legate::LogicalStore&& store) { return NDArray(std::move(store)); } -NDArray CuNumericRuntime::create_array(const legate::Type& type, int32_t dim) +NDArray CuPyNumericRuntime::create_array(const legate::Type& type, int32_t dim) { auto store = legate_runtime_->create_store(type, dim); return NDArray(std::move(store)); } -legate::LogicalStore CuNumericRuntime::create_scalar_store(const Scalar& value) +legate::LogicalStore CuPyNumericRuntime::create_scalar_store(const Scalar& value) { return legate_runtime_->create_store(value); } -legate::Type CuNumericRuntime::get_argred_type(const legate::Type& value_type) +legate::Type CuPyNumericRuntime::get_argred_type(const legate::Type& value_type) { auto finder = argred_types_.find(value_type.code()); if (finder != argred_types_.end()) { @@ -81,32 +81,35 @@ legate::Type CuNumericRuntime::get_argred_type(const legate::Type& value_type) return argred_type; } -legate::AutoTask CuNumericRuntime::create_task(CuNumericOpCode op_code) +legate::AutoTask CuPyNumericRuntime::create_task(CuPyNumericOpCode op_code) { return legate_runtime_->create_task(library_, legate::LocalTaskID{op_code}); } -legate::ManualTask CuNumericRuntime::create_task(CuNumericOpCode op_code, - const legate::tuple& launch_shape) +legate::ManualTask CuPyNumericRuntime::create_task(CuPyNumericOpCode op_code, + const legate::tuple& launch_shape) { return legate_runtime_->create_task(library_, legate::LocalTaskID{op_code}, launch_shape); } -void CuNumericRuntime::submit(legate::AutoTask&& task) { legate_runtime_->submit(std::move(task)); } +void CuPyNumericRuntime::submit(legate::AutoTask&& task) +{ + legate_runtime_->submit(std::move(task)); +} -void CuNumericRuntime::submit(legate::ManualTask&& task) +void CuPyNumericRuntime::submit(legate::ManualTask&& task) { legate_runtime_->submit(std::move(task)); } -uint32_t CuNumericRuntime::get_next_random_epoch() { return next_epoch_++; } +uint32_t CuPyNumericRuntime::get_next_random_epoch() { return next_epoch_++; } -/*static*/ CuNumericRuntime* CuNumericRuntime::get_runtime() { return runtime_; } +/*static*/ CuPyNumericRuntime* CuPyNumericRuntime::get_runtime() { return runtime_; } -/*static*/ void CuNumericRuntime::initialize(legate::Runtime* legate_runtime, - legate::Library library) +/*static*/ void CuPyNumericRuntime::initialize(legate::Runtime* legate_runtime, + legate::Library library) { - runtime_ = new CuNumericRuntime(legate_runtime, library); + runtime_ = new CuPyNumericRuntime(legate_runtime, library); } namespace { @@ -141,18 +144,18 @@ std::uint32_t extract_env(const char* env_name, } // namespace -} // namespace cunumeric +} // namespace cupynumeric extern "C" { -unsigned cunumeric_max_eager_volume() +unsigned cupynumeric_max_eager_volume() { - static const auto min_gpu_chunk = - cunumeric::extract_env("CUNUMERIC_MIN_GPU_CHUNK", MIN_GPU_CHUNK_DEFAULT, MIN_GPU_CHUNK_TEST); - static const auto min_cpu_chunk = - cunumeric::extract_env("CUNUMERIC_MIN_CPU_CHUNK", MIN_CPU_CHUNK_DEFAULT, MIN_CPU_CHUNK_TEST); - static const auto min_omp_chunk = - cunumeric::extract_env("CUNUMERIC_MIN_OMP_CHUNK", MIN_OMP_CHUNK_DEFAULT, MIN_OMP_CHUNK_TEST); + static const auto min_gpu_chunk = cupynumeric::extract_env( + "CUPYNUMERIC_MIN_GPU_CHUNK", MIN_GPU_CHUNK_DEFAULT, MIN_GPU_CHUNK_TEST); + static const auto min_cpu_chunk = cupynumeric::extract_env( + "CUPYNUMERIC_MIN_CPU_CHUNK", MIN_CPU_CHUNK_DEFAULT, MIN_CPU_CHUNK_TEST); + static const auto min_omp_chunk = cupynumeric::extract_env( + "CUPYNUMERIC_MIN_OMP_CHUNK", MIN_OMP_CHUNK_DEFAULT, MIN_OMP_CHUNK_TEST); auto machine = legate::get_machine(); @@ -165,10 +168,10 @@ unsigned cunumeric_max_eager_volume() return min_cpu_chunk; } -unsigned cunumeric_matmul_cache_size() +unsigned cupynumeric_matmul_cache_size() { - static const auto max_cache_size = cunumeric::extract_env( - "CUNUMERIC_MATMUL_CACHE_SIZE", MATMUL_CACHE_SIZE_DEFAULT, MATMUL_CACHE_SIZE_TEST); + static const auto max_cache_size = cupynumeric::extract_env( + "CUPYNUMERIC_MATMUL_CACHE_SIZE", MATMUL_CACHE_SIZE_DEFAULT, MATMUL_CACHE_SIZE_TEST); return max_cache_size; } diff --git a/src/cunumeric/runtime.h b/src/cupynumeric/runtime.h similarity index 79% rename from src/cunumeric/runtime.h rename to src/cupynumeric/runtime.h index ce8fa49247..f640a81e60 100644 --- a/src/cunumeric/runtime.h +++ b/src/cupynumeric/runtime.h @@ -20,16 +20,16 @@ #include "legate.h" -#include "cunumeric/cunumeric_c.h" -#include "cunumeric/typedefs.h" +#include "cupynumeric/cupynumeric_c.h" +#include "cupynumeric/typedefs.h" -namespace cunumeric { +namespace cupynumeric { class NDArray; -class CuNumericRuntime { +class CuPyNumericRuntime { private: - CuNumericRuntime(legate::Runtime* legate_runtime, legate::Library library); + CuPyNumericRuntime(legate::Runtime* legate_runtime, legate::Library library); public: NDArray create_array(const legate::Type& type); @@ -44,8 +44,8 @@ class CuNumericRuntime { legate::Type get_argred_type(const legate::Type& value_type); public: - legate::AutoTask create_task(CuNumericOpCode op_code); - legate::ManualTask create_task(CuNumericOpCode op_code, + legate::AutoTask create_task(CuPyNumericOpCode op_code); + legate::ManualTask create_task(CuPyNumericOpCode op_code, const legate::tuple& launch_shape); void submit(legate::AutoTask&& task); void submit(legate::ManualTask&& task); @@ -57,11 +57,11 @@ class CuNumericRuntime { legate::Library get_library() const { return library_; } public: - static CuNumericRuntime* get_runtime(); + static CuPyNumericRuntime* get_runtime(); static void initialize(legate::Runtime* legate_runtime, legate::Library library); private: - static CuNumericRuntime* runtime_; + static CuPyNumericRuntime* runtime_; private: legate::Runtime* legate_runtime_; @@ -70,4 +70,4 @@ class CuNumericRuntime { std::unordered_map argred_types_; }; -} // namespace cunumeric +} // namespace cupynumeric diff --git a/src/cunumeric/scan/scan_global.cc b/src/cupynumeric/scan/scan_global.cc similarity index 94% rename from src/cunumeric/scan/scan_global.cc rename to src/cupynumeric/scan/scan_global.cc index 965ce98ec6..767bba1764 100644 --- a/src/cunumeric/scan/scan_global.cc +++ b/src/cupynumeric/scan/scan_global.cc @@ -14,13 +14,13 @@ * */ -#include "cunumeric/scan/scan_global.h" -#include "cunumeric/scan/scan_global_template.inl" +#include "cupynumeric/scan/scan_global.h" +#include "cupynumeric/scan/scan_global_template.inl" #include #include -namespace cunumeric { +namespace cupynumeric { using namespace legate; @@ -80,4 +80,4 @@ static void __attribute__((constructor)) register_tasks(void) } } // namespace -} // namespace cunumeric +} // namespace cupynumeric diff --git a/src/cunumeric/scan/scan_global.cu b/src/cupynumeric/scan/scan_global.cu similarity index 91% rename from src/cunumeric/scan/scan_global.cu rename to src/cupynumeric/scan/scan_global.cu index fd2fb603ee..6e05d6457a 100644 --- a/src/cunumeric/scan/scan_global.cu +++ b/src/cupynumeric/scan/scan_global.cu @@ -14,15 +14,15 @@ * */ -#include "cunumeric/scan/scan_global.h" -#include "cunumeric/scan/scan_global_template.inl" -#include "cunumeric/utilities/thrust_util.h" +#include "cupynumeric/scan/scan_global.h" +#include "cupynumeric/scan/scan_global_template.inl" +#include "cupynumeric/utilities/thrust_util.h" #include -#include "cunumeric/cuda_help.h" +#include "cupynumeric/cuda_help.h" -namespace cunumeric { +namespace cupynumeric { using namespace legate; @@ -79,7 +79,7 @@ struct ScanGlobalImplBody { scalar_kernel<<>>( stride, func, &outptr[index], global_prefix); } - CUNUMERIC_CHECK_CUDA_STREAM(stream); + CUPYNUMERIC_CHECK_CUDA_STREAM(stream); } }; @@ -88,4 +88,4 @@ struct ScanGlobalImplBody { scan_global_template(context); } -} // namespace cunumeric +} // namespace cupynumeric diff --git a/src/cunumeric/scan/scan_global.h b/src/cupynumeric/scan/scan_global.h similarity index 79% rename from src/cunumeric/scan/scan_global.h rename to src/cupynumeric/scan/scan_global.h index 908520ccc3..dcd10c2555 100644 --- a/src/cunumeric/scan/scan_global.h +++ b/src/cupynumeric/scan/scan_global.h @@ -16,10 +16,10 @@ #pragma once -#include "cunumeric/scan/scan_util.h" -#include "cunumeric/cunumeric_task.h" +#include "cupynumeric/scan/scan_util.h" +#include "cupynumeric/cupynumeric_task.h" -namespace cunumeric { +namespace cupynumeric { struct ScanGlobalArgs { legate::PhysicalStore sum_vals; @@ -28,9 +28,9 @@ struct ScanGlobalArgs { const legate::DomainPoint& partition_index; }; -class ScanGlobalTask : public CuNumericTask { +class ScanGlobalTask : public CuPyNumericTask { public: - static constexpr auto TASK_ID = legate::LocalTaskID{CUNUMERIC_SCAN_GLOBAL}; + static constexpr auto TASK_ID = legate::LocalTaskID{CUPYNUMERIC_SCAN_GLOBAL}; public: static void cpu_variant(legate::TaskContext context); @@ -42,4 +42,4 @@ class ScanGlobalTask : public CuNumericTask { #endif }; -} // namespace cunumeric +} // namespace cupynumeric diff --git a/src/cunumeric/scan/scan_global_omp.cc b/src/cupynumeric/scan/scan_global_omp.cc similarity index 94% rename from src/cunumeric/scan/scan_global_omp.cc rename to src/cupynumeric/scan/scan_global_omp.cc index f9f1355fa7..0d6fcdb918 100644 --- a/src/cunumeric/scan/scan_global_omp.cc +++ b/src/cupynumeric/scan/scan_global_omp.cc @@ -14,15 +14,15 @@ * */ -#include "cunumeric/scan/scan_global.h" -#include "cunumeric/scan/scan_global_template.inl" +#include "cupynumeric/scan/scan_global.h" +#include "cupynumeric/scan/scan_global_template.inl" #include #include #include #include -namespace cunumeric { +namespace cupynumeric { using namespace legate; @@ -75,4 +75,4 @@ struct ScanGlobalImplBody { scan_global_template(context); } -} // namespace cunumeric +} // namespace cupynumeric diff --git a/src/cunumeric/scan/scan_global_template.inl b/src/cupynumeric/scan/scan_global_template.inl similarity index 95% rename from src/cunumeric/scan/scan_global_template.inl rename to src/cupynumeric/scan/scan_global_template.inl index 15fda91abf..34c39f6140 100644 --- a/src/cunumeric/scan/scan_global_template.inl +++ b/src/cupynumeric/scan/scan_global_template.inl @@ -14,10 +14,10 @@ * */ -#include "cunumeric/scan/scan_util.h" -#include "cunumeric/pitches.h" +#include "cupynumeric/scan/scan_util.h" +#include "cupynumeric/pitches.h" -namespace cunumeric { +namespace cupynumeric { using namespace legate; @@ -76,4 +76,4 @@ static void scan_global_template(TaskContext& context) op_dispatch(args.op_code, ScanGlobalDispatch{}, args); } -} // namespace cunumeric +} // namespace cupynumeric diff --git a/src/cunumeric/scan/scan_local.cc b/src/cupynumeric/scan/scan_local.cc similarity index 93% rename from src/cunumeric/scan/scan_local.cc rename to src/cupynumeric/scan/scan_local.cc index f5387b05de..827536e20e 100644 --- a/src/cunumeric/scan/scan_local.cc +++ b/src/cupynumeric/scan/scan_local.cc @@ -14,15 +14,15 @@ * */ -#include "cunumeric/scan/scan_local.h" -#include "cunumeric/scan/scan_local_template.inl" -#include "cunumeric/unary/isnan.h" +#include "cupynumeric/scan/scan_local.h" +#include "cupynumeric/scan/scan_local_template.inl" +#include "cupynumeric/unary/isnan.h" #include #include #include -namespace cunumeric { +namespace cupynumeric { using namespace legate; @@ -70,7 +70,7 @@ struct ScanLocalNanImplBody { struct convert_nan_func { VAL operator()(VAL x) const { - return cunumeric::is_nan(x) ? (VAL)ScanOp::nan_identity : x; + return cupynumeric::is_nan(x) ? (VAL)ScanOp::nan_identity : x; } }; @@ -122,4 +122,4 @@ static void __attribute__((constructor)) register_tasks(void) } } // namespace -} // namespace cunumeric +} // namespace cupynumeric diff --git a/src/cunumeric/scan/scan_local.cu b/src/cupynumeric/scan/scan_local.cu similarity index 90% rename from src/cunumeric/scan/scan_local.cu rename to src/cupynumeric/scan/scan_local.cu index 2ded8c7056..fe7938b53c 100644 --- a/src/cunumeric/scan/scan_local.cu +++ b/src/cupynumeric/scan/scan_local.cu @@ -14,17 +14,17 @@ * */ -#include "cunumeric/scan/scan_local.h" -#include "cunumeric/scan/scan_local_template.inl" -#include "cunumeric/unary/isnan.h" -#include "cunumeric/utilities/thrust_util.h" +#include "cupynumeric/scan/scan_local.h" +#include "cupynumeric/scan/scan_local_template.inl" +#include "cupynumeric/unary/isnan.h" +#include "cupynumeric/utilities/thrust_util.h" #include #include -#include "cunumeric/cuda_help.h" +#include "cupynumeric/cuda_help.h" -namespace cunumeric { +namespace cupynumeric { using namespace legate; @@ -75,7 +75,7 @@ struct ScanLocalImplBody { lazy_kernel<<<1, THREADS_PER_BLOCK, 0, stream>>>(&outptr[index + stride - 1], &sum_valsptr[sum_valp]); } - CUNUMERIC_CHECK_CUDA_STREAM(stream); + CUPYNUMERIC_CHECK_CUDA_STREAM(stream); } }; @@ -87,7 +87,7 @@ struct ScanLocalNanImplBody { struct convert_nan_func { __device__ VAL operator()(VAL x) { - return cunumeric::is_nan(x) ? (VAL)ScanOp::nan_identity : x; + return cupynumeric::is_nan(x) ? (VAL)ScanOp::nan_identity : x; } }; @@ -126,7 +126,7 @@ struct ScanLocalNanImplBody { lazy_kernel<<<1, THREADS_PER_BLOCK, 0, stream>>>(&outptr[index + stride - 1], &sum_valsptr[sum_valp]); } - CUNUMERIC_CHECK_CUDA_STREAM(stream); + CUPYNUMERIC_CHECK_CUDA_STREAM(stream); } }; @@ -135,4 +135,4 @@ struct ScanLocalNanImplBody { scan_local_template(context); } -} // namespace cunumeric +} // namespace cupynumeric diff --git a/src/cunumeric/scan/scan_local.h b/src/cupynumeric/scan/scan_local.h similarity index 79% rename from src/cunumeric/scan/scan_local.h rename to src/cupynumeric/scan/scan_local.h index 30b3a2ab87..8eb8858a17 100644 --- a/src/cunumeric/scan/scan_local.h +++ b/src/cupynumeric/scan/scan_local.h @@ -16,10 +16,10 @@ #pragma once -#include "cunumeric/scan/scan_util.h" -#include "cunumeric/cunumeric_task.h" +#include "cupynumeric/scan/scan_util.h" +#include "cupynumeric/cupynumeric_task.h" -namespace cunumeric { +namespace cupynumeric { struct ScanLocalArgs { legate::PhysicalStore out; @@ -29,9 +29,9 @@ struct ScanLocalArgs { bool nan_to_identity; }; -class ScanLocalTask : public CuNumericTask { +class ScanLocalTask : public CuPyNumericTask { public: - static constexpr auto TASK_ID = legate::LocalTaskID{CUNUMERIC_SCAN_LOCAL}; + static constexpr auto TASK_ID = legate::LocalTaskID{CUPYNUMERIC_SCAN_LOCAL}; public: static void cpu_variant(legate::TaskContext context); @@ -43,4 +43,4 @@ class ScanLocalTask : public CuNumericTask { #endif }; -} // namespace cunumeric +} // namespace cupynumeric diff --git a/src/cunumeric/scan/scan_local_omp.cc b/src/cupynumeric/scan/scan_local_omp.cc similarity index 93% rename from src/cunumeric/scan/scan_local_omp.cc rename to src/cupynumeric/scan/scan_local_omp.cc index 9e4e9f0639..d78a7c21ec 100644 --- a/src/cunumeric/scan/scan_local_omp.cc +++ b/src/cupynumeric/scan/scan_local_omp.cc @@ -14,9 +14,9 @@ * */ -#include "cunumeric/scan/scan_local.h" -#include "cunumeric/scan/scan_local_template.inl" -#include "cunumeric/unary/isnan.h" +#include "cupynumeric/scan/scan_local.h" +#include "cupynumeric/scan/scan_local_template.inl" +#include "cupynumeric/unary/isnan.h" #include #include @@ -24,7 +24,7 @@ #include #include -namespace cunumeric { +namespace cupynumeric { using namespace legate; @@ -72,7 +72,7 @@ struct ScanLocalNanImplBody { struct convert_nan_func { VAL operator()(VAL x) const { - return cunumeric::is_nan(x) ? (VAL)ScanOp::nan_identity : x; + return cupynumeric::is_nan(x) ? (VAL)ScanOp::nan_identity : x; } }; @@ -116,4 +116,4 @@ struct ScanLocalNanImplBody { scan_local_template(context); } -} // namespace cunumeric +} // namespace cupynumeric diff --git a/src/cunumeric/scan/scan_local_template.inl b/src/cupynumeric/scan/scan_local_template.inl similarity index 96% rename from src/cunumeric/scan/scan_local_template.inl rename to src/cupynumeric/scan/scan_local_template.inl index 889f9007fb..bfb9095f4e 100644 --- a/src/cunumeric/scan/scan_local_template.inl +++ b/src/cupynumeric/scan/scan_local_template.inl @@ -14,10 +14,10 @@ * */ -#include "cunumeric/scan/scan_util.h" -#include "cunumeric/pitches.h" +#include "cupynumeric/scan/scan_util.h" +#include "cupynumeric/pitches.h" -namespace cunumeric { +namespace cupynumeric { using namespace legate; @@ -104,4 +104,4 @@ static void scan_local_template(TaskContext& context) op_dispatch(args.op_code, args.nan_to_identity, ScanLocalDispatch{}, args); } -} // namespace cunumeric +} // namespace cupynumeric diff --git a/src/cunumeric/scan/scan_local_util.h b/src/cupynumeric/scan/scan_local_util.h similarity index 92% rename from src/cunumeric/scan/scan_local_util.h rename to src/cupynumeric/scan/scan_local_util.h index 38a0420159..660ad13b23 100644 --- a/src/cunumeric/scan/scan_local_util.h +++ b/src/cupynumeric/scan/scan_local_util.h @@ -16,15 +16,15 @@ #pragma once -#include "cunumeric/cunumeric_task.h" +#include "cupynumeric/cupynumeric_task.h" #include -namespace cunumeric { +namespace cupynumeric { enum class ScanCode : int { - PROD = CUNUMERIC_SCAN_PROD, - SUM = CUNUMERIC_SCAN_SUM, + PROD = CUPYNUMERIC_SCAN_PROD, + SUM = CUPYNUMERIC_SCAN_SUM, }; template @@ -67,4 +67,4 @@ struct ScanOp : thrust::multiplies> ScanOp() {} }; -} // namespace cunumeric +} // namespace cupynumeric diff --git a/src/cunumeric/scan/scan_util.h b/src/cupynumeric/scan/scan_util.h similarity index 94% rename from src/cunumeric/scan/scan_util.h rename to src/cupynumeric/scan/scan_util.h index 84e05fced5..6e32de4ac2 100644 --- a/src/cunumeric/scan/scan_util.h +++ b/src/cupynumeric/scan/scan_util.h @@ -16,15 +16,15 @@ #pragma once -#include "cunumeric/cunumeric_task.h" +#include "cupynumeric/cupynumeric_task.h" #include -namespace cunumeric { +namespace cupynumeric { enum class ScanCode : int { - PROD = CUNUMERIC_SCAN_PROD, - SUM = CUNUMERIC_SCAN_SUM, + PROD = CUPYNUMERIC_SCAN_PROD, + SUM = CUPYNUMERIC_SCAN_SUM, }; template @@ -87,4 +87,4 @@ struct ScanOp { constexpr T operator()(const bool& lhs, const bool& rhs) const { return lhs && rhs; } }; -} // namespace cunumeric +} // namespace cupynumeric diff --git a/src/cunumeric/search/argwhere.cc b/src/cupynumeric/search/argwhere.cc similarity index 92% rename from src/cunumeric/search/argwhere.cc rename to src/cupynumeric/search/argwhere.cc index 8367d6f1d2..8a71f04dff 100644 --- a/src/cunumeric/search/argwhere.cc +++ b/src/cupynumeric/search/argwhere.cc @@ -14,10 +14,10 @@ * */ -#include "cunumeric/search/argwhere.h" -#include "cunumeric/search/argwhere_template.inl" +#include "cupynumeric/search/argwhere.h" +#include "cupynumeric/search/argwhere_template.inl" -namespace cunumeric { +namespace cupynumeric { using namespace legate; @@ -65,4 +65,4 @@ namespace // unnamed static void __attribute__((constructor)) register_tasks(void) { ArgWhereTask::register_variants(); } } // namespace -} // namespace cunumeric +} // namespace cupynumeric diff --git a/src/cunumeric/search/argwhere.cu b/src/cupynumeric/search/argwhere.cu similarity index 88% rename from src/cunumeric/search/argwhere.cu rename to src/cupynumeric/search/argwhere.cu index d8c999dbf7..6a38ac531c 100644 --- a/src/cunumeric/search/argwhere.cu +++ b/src/cupynumeric/search/argwhere.cu @@ -14,13 +14,13 @@ * */ -#include "cunumeric/search/argwhere.h" -#include "cunumeric/search/argwhere_template.inl" -#include "cunumeric/search/nonzero.cuh" +#include "cupynumeric/search/argwhere.h" +#include "cupynumeric/search/argwhere_template.inl" +#include "cupynumeric/search/nonzero.cuh" -#include "cunumeric/cuda_help.h" +#include "cupynumeric/cuda_help.h" -namespace cunumeric { +namespace cupynumeric { template static __global__ void __launch_bounds__(THREADS_PER_BLOCK, MIN_CTAS_PER_SM) @@ -59,7 +59,7 @@ struct ArgWhereImplBody { auto offsets = create_buffer(volume, legate::Memory::Kind::GPU_FB_MEM); auto size = compute_offsets(input, pitches, rect, volume, offsets, stream); - CUNUMERIC_CHECK_CUDA_STREAM(stream); + CUPYNUMERIC_CHECK_CUDA_STREAM(stream); auto out = out_array.create_output_buffer(Point<2>(size, DIM), true); @@ -67,7 +67,7 @@ struct ArgWhereImplBody { const size_t blocks = (volume + THREADS_PER_BLOCK - 1) / THREADS_PER_BLOCK; argwhere_kernel<<>>( volume, input, pitches, rect.lo, offsets, out); - CUNUMERIC_CHECK_CUDA_STREAM(stream); + CUPYNUMERIC_CHECK_CUDA_STREAM(stream); } } }; @@ -77,4 +77,4 @@ struct ArgWhereImplBody { argwhere_template(context); } -} // namespace cunumeric +} // namespace cupynumeric diff --git a/src/cunumeric/search/argwhere.h b/src/cupynumeric/search/argwhere.h similarity index 80% rename from src/cunumeric/search/argwhere.h rename to src/cupynumeric/search/argwhere.h index 78d4a8fbf4..aecb7a6f9b 100644 --- a/src/cunumeric/search/argwhere.h +++ b/src/cupynumeric/search/argwhere.h @@ -16,18 +16,18 @@ #pragma once -#include "cunumeric/cunumeric_task.h" +#include "cupynumeric/cupynumeric_task.h" -namespace cunumeric { +namespace cupynumeric { struct ArgWhereArgs { legate::PhysicalStore out; legate::PhysicalStore in; }; -class ArgWhereTask : public CuNumericTask { +class ArgWhereTask : public CuPyNumericTask { public: - static constexpr auto TASK_ID = legate::LocalTaskID{CUNUMERIC_ARGWHERE}; + static constexpr auto TASK_ID = legate::LocalTaskID{CUPYNUMERIC_ARGWHERE}; public: static void cpu_variant(legate::TaskContext context); @@ -39,4 +39,4 @@ class ArgWhereTask : public CuNumericTask { #endif }; -} // namespace cunumeric +} // namespace cupynumeric diff --git a/src/cunumeric/search/argwhere_omp.cc b/src/cupynumeric/search/argwhere_omp.cc similarity index 93% rename from src/cunumeric/search/argwhere_omp.cc rename to src/cupynumeric/search/argwhere_omp.cc index cd8fbf3653..d40cf57edb 100644 --- a/src/cunumeric/search/argwhere_omp.cc +++ b/src/cupynumeric/search/argwhere_omp.cc @@ -14,12 +14,12 @@ * */ -#include "cunumeric/search/argwhere.h" -#include "cunumeric/search/argwhere_template.inl" -#include "cunumeric/omp_help.h" +#include "cupynumeric/search/argwhere.h" +#include "cupynumeric/search/argwhere_template.inl" +#include "cupynumeric/omp_help.h" #include -namespace cunumeric { +namespace cupynumeric { using namespace legate; @@ -87,4 +87,4 @@ struct ArgWhereImplBody { argwhere_template(context); } -} // namespace cunumeric +} // namespace cupynumeric diff --git a/src/cunumeric/search/argwhere_template.inl b/src/cupynumeric/search/argwhere_template.inl similarity index 92% rename from src/cunumeric/search/argwhere_template.inl rename to src/cupynumeric/search/argwhere_template.inl index bcc6f4d014..6a0cd886c2 100644 --- a/src/cunumeric/search/argwhere_template.inl +++ b/src/cupynumeric/search/argwhere_template.inl @@ -17,10 +17,10 @@ #pragma once // Useful for IDEs -#include "cunumeric/search/argwhere.h" -#include "cunumeric/pitches.h" +#include "cupynumeric/search/argwhere.h" +#include "cupynumeric/pitches.h" -namespace cunumeric { +namespace cupynumeric { using namespace legate; @@ -56,4 +56,4 @@ static void argwhere_template(TaskContext& context) double_dispatch(args.in.dim(), args.in.code(), ArgWhereImpl{}, args); } -} // namespace cunumeric +} // namespace cupynumeric diff --git a/src/cunumeric/search/nonzero.cc b/src/cupynumeric/search/nonzero.cc similarity index 93% rename from src/cunumeric/search/nonzero.cc rename to src/cupynumeric/search/nonzero.cc index f5e9c8a1bb..d9fef6d44c 100644 --- a/src/cunumeric/search/nonzero.cc +++ b/src/cupynumeric/search/nonzero.cc @@ -14,10 +14,10 @@ * */ -#include "cunumeric/search/nonzero.h" -#include "cunumeric/search/nonzero_template.inl" +#include "cupynumeric/search/nonzero.h" +#include "cupynumeric/search/nonzero_template.inl" -namespace cunumeric { +namespace cupynumeric { using namespace legate; @@ -68,4 +68,4 @@ namespace // unnamed static void __attribute__((constructor)) register_tasks(void) { NonzeroTask::register_variants(); } } // namespace -} // namespace cunumeric +} // namespace cupynumeric diff --git a/src/cunumeric/search/nonzero.cu b/src/cupynumeric/search/nonzero.cu similarity index 93% rename from src/cunumeric/search/nonzero.cu rename to src/cupynumeric/search/nonzero.cu index 180f124b66..8e9b2f0a9d 100644 --- a/src/cunumeric/search/nonzero.cu +++ b/src/cupynumeric/search/nonzero.cu @@ -14,11 +14,11 @@ * */ -#include "cunumeric/search/nonzero.h" -#include "cunumeric/search/nonzero_template.inl" -#include "cunumeric/search/nonzero.cuh" +#include "cupynumeric/search/nonzero.h" +#include "cupynumeric/search/nonzero_template.inl" +#include "cupynumeric/search/nonzero.cuh" -namespace cunumeric { +namespace cupynumeric { template static __global__ void __launch_bounds__(THREADS_PER_BLOCK, MIN_CTAS_PER_SM) @@ -85,7 +85,7 @@ struct NonzeroImplBody { if (size > 0) { populate_nonzeros(in, pitches, rect, volume, results, offsets, stream); } - CUNUMERIC_CHECK_CUDA_STREAM(stream); + CUPYNUMERIC_CHECK_CUDA_STREAM(stream); } }; @@ -94,4 +94,4 @@ struct NonzeroImplBody { nonzero_template(context); } -} // namespace cunumeric +} // namespace cupynumeric diff --git a/src/cunumeric/search/nonzero.cuh b/src/cupynumeric/search/nonzero.cuh similarity index 94% rename from src/cunumeric/search/nonzero.cuh rename to src/cupynumeric/search/nonzero.cuh index e08d6267e9..5befb3b594 100644 --- a/src/cunumeric/search/nonzero.cuh +++ b/src/cupynumeric/search/nonzero.cuh @@ -16,10 +16,10 @@ #include -#include "cunumeric/cuda_help.h" -#include "cunumeric/utilities/thrust_util.h" +#include "cupynumeric/cuda_help.h" +#include "cupynumeric/utilities/thrust_util.h" -namespace cunumeric { +namespace cupynumeric { using namespace legate; @@ -78,8 +78,8 @@ int64_t compute_offsets(const AccessorRO& in, exclusive_sum(p_offsets, volume, stream); - CUNUMERIC_CHECK_CUDA_STREAM(stream); + CUPYNUMERIC_CHECK_CUDA_STREAM(stream); return size.read(stream); } -} // namespace cunumeric +} // namespace cupynumeric diff --git a/src/cunumeric/search/nonzero.h b/src/cupynumeric/search/nonzero.h similarity index 81% rename from src/cunumeric/search/nonzero.h rename to src/cupynumeric/search/nonzero.h index 78cf34a42e..5366d280b5 100644 --- a/src/cunumeric/search/nonzero.h +++ b/src/cupynumeric/search/nonzero.h @@ -16,18 +16,18 @@ #pragma once -#include "cunumeric/cunumeric_task.h" +#include "cupynumeric/cupynumeric_task.h" -namespace cunumeric { +namespace cupynumeric { struct NonzeroArgs { legate::PhysicalStore input; std::vector results; }; -class NonzeroTask : public CuNumericTask { +class NonzeroTask : public CuPyNumericTask { public: - static constexpr auto TASK_ID = legate::LocalTaskID{CUNUMERIC_NONZERO}; + static constexpr auto TASK_ID = legate::LocalTaskID{CUPYNUMERIC_NONZERO}; public: static void cpu_variant(legate::TaskContext context); @@ -39,4 +39,4 @@ class NonzeroTask : public CuNumericTask { #endif }; -} // namespace cunumeric +} // namespace cupynumeric diff --git a/src/cunumeric/search/nonzero_omp.cc b/src/cupynumeric/search/nonzero_omp.cc similarity index 93% rename from src/cunumeric/search/nonzero_omp.cc rename to src/cupynumeric/search/nonzero_omp.cc index 1f85499d78..fbbeab14bd 100644 --- a/src/cunumeric/search/nonzero_omp.cc +++ b/src/cupynumeric/search/nonzero_omp.cc @@ -14,13 +14,13 @@ * */ -#include "cunumeric/search/nonzero.h" -#include "cunumeric/search/nonzero_template.inl" -#include "cunumeric/omp_help.h" +#include "cupynumeric/search/nonzero.h" +#include "cupynumeric/search/nonzero_template.inl" +#include "cupynumeric/omp_help.h" #include -namespace cunumeric { +namespace cupynumeric { using namespace legate; @@ -93,4 +93,4 @@ struct NonzeroImplBody { nonzero_template(context); } -} // namespace cunumeric +} // namespace cupynumeric diff --git a/src/cunumeric/search/nonzero_template.inl b/src/cupynumeric/search/nonzero_template.inl similarity index 92% rename from src/cunumeric/search/nonzero_template.inl rename to src/cupynumeric/search/nonzero_template.inl index 250bb88e5f..36c6fbc0f8 100644 --- a/src/cunumeric/search/nonzero_template.inl +++ b/src/cupynumeric/search/nonzero_template.inl @@ -17,10 +17,10 @@ #pragma once // Useful for IDEs -#include "cunumeric/search/nonzero.h" -#include "cunumeric/pitches.h" +#include "cupynumeric/search/nonzero.h" +#include "cupynumeric/pitches.h" -namespace cunumeric { +namespace cupynumeric { using namespace legate; @@ -62,4 +62,4 @@ static void nonzero_template(TaskContext& context) double_dispatch(args.input.dim(), args.input.code(), NonzeroImpl{}, args); } -} // namespace cunumeric +} // namespace cupynumeric diff --git a/src/cunumeric/set/unique.cc b/src/cupynumeric/set/unique.cc similarity index 93% rename from src/cunumeric/set/unique.cc rename to src/cupynumeric/set/unique.cc index b4dba93990..1aaa0571a2 100644 --- a/src/cunumeric/set/unique.cc +++ b/src/cupynumeric/set/unique.cc @@ -14,10 +14,10 @@ * */ -#include "cunumeric/set/unique.h" -#include "cunumeric/set/unique_template.inl" +#include "cupynumeric/set/unique.h" +#include "cupynumeric/set/unique_template.inl" -namespace cunumeric { +namespace cupynumeric { using namespace legate; @@ -63,4 +63,4 @@ static void __attribute__((constructor)) register_tasks(void) } } // namespace -} // namespace cunumeric +} // namespace cupynumeric diff --git a/src/cunumeric/set/unique.cu b/src/cupynumeric/set/unique.cu similarity index 89% rename from src/cunumeric/set/unique.cu rename to src/cupynumeric/set/unique.cu index fa2b14155a..9f661172b2 100644 --- a/src/cunumeric/set/unique.cu +++ b/src/cupynumeric/set/unique.cu @@ -14,17 +14,17 @@ * */ -#include "cunumeric/set/unique.h" -#include "cunumeric/set/unique_template.inl" -#include "cunumeric/utilities/thrust_util.h" +#include "cupynumeric/set/unique.h" +#include "cupynumeric/set/unique_template.inl" +#include "cupynumeric/utilities/thrust_util.h" -#include "cunumeric/cuda_help.h" +#include "cupynumeric/cuda_help.h" #include #include #include -namespace cunumeric { +namespace cupynumeric { using namespace legate; @@ -66,7 +66,7 @@ static Piece tree_reduce(legate::PhysicalStore& output, // but I suspect point-to-point can be slower... all_sizes[my_id] = my_piece.second; CHECK_NCCL(ncclAllGather(all_sizes.ptr(my_id), all_sizes.ptr(0), 1, ncclUint64, *comm, stream)); - CUNUMERIC_CHECK_CUDA(cudaStreamSynchronize(stream)); + CUPYNUMERIC_CHECK_CUDA(cudaStreamSynchronize(stream)); Piece other_piece; size_t offset = radix / 2; @@ -121,11 +121,11 @@ static Piece tree_reduce(legate::PhysicalStore& output, assert(my_piece.second <= buf_size); my_piece.first = output.create_output_buffer(buf_size); - CUNUMERIC_CHECK_CUDA(cudaMemcpyAsync(my_piece.first.ptr(0), - p_merged, - sizeof(VAL) * my_piece.second, - cudaMemcpyDeviceToDevice, - stream)); + CUPYNUMERIC_CHECK_CUDA(cudaMemcpyAsync(my_piece.first.ptr(0), + p_merged, + sizeof(VAL) * my_piece.second, + cudaMemcpyDeviceToDevice, + stream)); merged.destroy(); } @@ -163,14 +163,14 @@ struct UniqueImplBody { if (volume > 0) { if (in.accessor.is_dense_arbitrary(rect)) { auto* src = in.ptr(rect.lo); - CUNUMERIC_CHECK_CUDA( + CUPYNUMERIC_CHECK_CUDA( cudaMemcpyAsync(ptr, src, sizeof(VAL) * volume, cudaMemcpyDeviceToDevice, stream)); } else { const size_t num_blocks = (volume + THREADS_PER_BLOCK - 1) / THREADS_PER_BLOCK; copy_into_buffer<<>>( ptr, in, rect.lo, pitches, volume); } - CUNUMERIC_CHECK_CUDA_STREAM(stream); + CUPYNUMERIC_CHECK_CUDA_STREAM(stream); // Find unique values thrust::sort(DEFAULT_POLICY.on(stream), ptr, ptr + volume); @@ -183,7 +183,7 @@ struct UniqueImplBody { assert(end - ptr <= buf_size); result.first = output.create_output_buffer(buf_size); if (result.second > 0) { - CUNUMERIC_CHECK_CUDA(cudaMemcpyAsync( + CUPYNUMERIC_CHECK_CUDA(cudaMemcpyAsync( result.first.ptr(0), ptr, sizeof(VAL) * result.second, cudaMemcpyDeviceToDevice, stream)); } @@ -193,7 +193,7 @@ struct UniqueImplBody { auto comm = comms[0].get(); result = tree_reduce(output, result, point[0], launch_domain.get_volume(), stream, comm); } - CUNUMERIC_CHECK_CUDA_STREAM(stream); + CUPYNUMERIC_CHECK_CUDA_STREAM(stream); // Finally we pack the result output.bind_data(result.first, Point<1>(result.second)); @@ -205,4 +205,4 @@ struct UniqueImplBody { unique_template(context); } -} // namespace cunumeric +} // namespace cupynumeric diff --git a/src/cunumeric/set/unique.h b/src/cupynumeric/set/unique.h similarity index 79% rename from src/cunumeric/set/unique.h rename to src/cupynumeric/set/unique.h index ab6ed17cd7..68ca2913a7 100644 --- a/src/cunumeric/set/unique.h +++ b/src/cupynumeric/set/unique.h @@ -16,13 +16,13 @@ #pragma once -#include "cunumeric/cunumeric_task.h" +#include "cupynumeric/cupynumeric_task.h" -namespace cunumeric { +namespace cupynumeric { -class UniqueTask : public CuNumericTask { +class UniqueTask : public CuPyNumericTask { public: - static constexpr auto TASK_ID = legate::LocalTaskID{CUNUMERIC_UNIQUE}; + static constexpr auto TASK_ID = legate::LocalTaskID{CUPYNUMERIC_UNIQUE}; public: static void cpu_variant(legate::TaskContext context); @@ -34,4 +34,4 @@ class UniqueTask : public CuNumericTask { #endif }; -} // namespace cunumeric +} // namespace cupynumeric diff --git a/src/cunumeric/set/unique_omp.cc b/src/cupynumeric/set/unique_omp.cc similarity index 94% rename from src/cunumeric/set/unique_omp.cc rename to src/cupynumeric/set/unique_omp.cc index 1f3646f5e0..e65783467e 100644 --- a/src/cunumeric/set/unique_omp.cc +++ b/src/cupynumeric/set/unique_omp.cc @@ -14,12 +14,12 @@ * */ -#include "cunumeric/set/unique.h" -#include "cunumeric/set/unique_template.inl" +#include "cupynumeric/set/unique.h" +#include "cupynumeric/set/unique_template.inl" #include -namespace cunumeric { +namespace cupynumeric { using namespace legate; @@ -79,4 +79,4 @@ struct UniqueImplBody { unique_template(context); } -} // namespace cunumeric +} // namespace cupynumeric diff --git a/src/cunumeric/set/unique_reduce.cc b/src/cupynumeric/set/unique_reduce.cc similarity index 85% rename from src/cunumeric/set/unique_reduce.cc rename to src/cupynumeric/set/unique_reduce.cc index 095def2927..e6db524a47 100644 --- a/src/cunumeric/set/unique_reduce.cc +++ b/src/cupynumeric/set/unique_reduce.cc @@ -14,10 +14,10 @@ * */ -#include "cunumeric/set/unique_reduce.h" -#include "cunumeric/set/unique_reduce_template.inl" +#include "cupynumeric/set/unique_reduce.h" +#include "cupynumeric/set/unique_reduce_template.inl" -namespace cunumeric { +namespace cupynumeric { /*static*/ void UniqueReduceTask::cpu_variant(TaskContext context) { @@ -32,4 +32,4 @@ static void __attribute__((constructor)) register_tasks(void) } } // namespace -} // namespace cunumeric +} // namespace cupynumeric diff --git a/src/cunumeric/set/unique_reduce.h b/src/cupynumeric/set/unique_reduce.h similarity index 76% rename from src/cunumeric/set/unique_reduce.h rename to src/cupynumeric/set/unique_reduce.h index 4a754a5f66..34e3fb1dfc 100644 --- a/src/cunumeric/set/unique_reduce.h +++ b/src/cupynumeric/set/unique_reduce.h @@ -16,13 +16,13 @@ #pragma once -#include "cunumeric/cunumeric_task.h" +#include "cupynumeric/cupynumeric_task.h" -namespace cunumeric { +namespace cupynumeric { -class UniqueReduceTask : public CuNumericTask { +class UniqueReduceTask : public CuPyNumericTask { public: - static constexpr auto TASK_ID = legate::LocalTaskID{CUNUMERIC_UNIQUE_REDUCE}; + static constexpr auto TASK_ID = legate::LocalTaskID{CUPYNUMERIC_UNIQUE_REDUCE}; public: static void cpu_variant(legate::TaskContext context); @@ -31,4 +31,4 @@ class UniqueReduceTask : public CuNumericTask { #endif }; -} // namespace cunumeric +} // namespace cupynumeric diff --git a/src/cunumeric/set/unique_reduce_omp.cc b/src/cupynumeric/set/unique_reduce_omp.cc similarity index 83% rename from src/cunumeric/set/unique_reduce_omp.cc rename to src/cupynumeric/set/unique_reduce_omp.cc index efb30b2694..2f683524a6 100644 --- a/src/cunumeric/set/unique_reduce_omp.cc +++ b/src/cupynumeric/set/unique_reduce_omp.cc @@ -14,16 +14,16 @@ * */ -#include "cunumeric/set/unique_reduce.h" -#include "cunumeric/set/unique_reduce_template.inl" +#include "cupynumeric/set/unique_reduce.h" +#include "cupynumeric/set/unique_reduce_template.inl" #include -namespace cunumeric { +namespace cupynumeric { /*static*/ void UniqueReduceTask::omp_variant(TaskContext context) { unique_reduce_template(context, thrust::omp::par); } -} // namespace cunumeric +} // namespace cupynumeric diff --git a/src/cunumeric/set/unique_reduce_template.inl b/src/cupynumeric/set/unique_reduce_template.inl similarity index 94% rename from src/cunumeric/set/unique_reduce_template.inl rename to src/cupynumeric/set/unique_reduce_template.inl index f356ee5109..d8f9aed2b9 100644 --- a/src/cunumeric/set/unique_reduce_template.inl +++ b/src/cupynumeric/set/unique_reduce_template.inl @@ -17,15 +17,15 @@ #pragma once // Useful for IDEs -#include "cunumeric/set/unique_reduce.h" -#include "cunumeric/pitches.h" +#include "cupynumeric/set/unique_reduce.h" +#include "cupynumeric/pitches.h" #include #include #include #include -namespace cunumeric { +namespace cupynumeric { using namespace legate; @@ -72,4 +72,4 @@ static void unique_reduce_template(TaskContext& context, const exe_pol_t& exe_po type_dispatch(output.type().code(), UniqueReduceImpl{}, output, inputs, exe_pol); } -} // namespace cunumeric +} // namespace cupynumeric diff --git a/src/cunumeric/set/unique_template.inl b/src/cupynumeric/set/unique_template.inl similarity index 93% rename from src/cunumeric/set/unique_template.inl rename to src/cupynumeric/set/unique_template.inl index e30980c320..a7e425f345 100644 --- a/src/cunumeric/set/unique_template.inl +++ b/src/cupynumeric/set/unique_template.inl @@ -17,10 +17,10 @@ #pragma once // Useful for IDEs -#include "cunumeric/set/unique.h" -#include "cunumeric/pitches.h" +#include "cupynumeric/set/unique.h" +#include "cupynumeric/pitches.h" -namespace cunumeric { +namespace cupynumeric { using namespace legate; @@ -64,4 +64,4 @@ static void unique_template(TaskContext& context) context.get_launch_domain()); } -} // namespace cunumeric +} // namespace cupynumeric diff --git a/src/cunumeric/slice.h b/src/cupynumeric/slice.h similarity index 93% rename from src/cunumeric/slice.h rename to src/cupynumeric/slice.h index f2cd46da36..38e6819d68 100644 --- a/src/cunumeric/slice.h +++ b/src/cupynumeric/slice.h @@ -18,10 +18,10 @@ #include -namespace cunumeric { +namespace cupynumeric { using slice = legate::Slice; constexpr auto open = legate::Slice::OPEN; -} // namespace cunumeric +} // namespace cupynumeric diff --git a/src/cunumeric/sort/cub_sort.cuh b/src/cupynumeric/sort/cub_sort.cuh similarity index 97% rename from src/cunumeric/sort/cub_sort.cuh rename to src/cupynumeric/sort/cub_sort.cuh index 2ed55e7ef5..578e1384b9 100644 --- a/src/cunumeric/sort/cub_sort.cuh +++ b/src/cupynumeric/sort/cub_sort.cuh @@ -25,9 +25,9 @@ #include #include -#include "cunumeric/cuda_help.h" +#include "cupynumeric/cuda_help.h" -namespace cunumeric { +namespace cupynumeric { namespace detail { using namespace legate; @@ -47,7 +47,7 @@ void cub_local_sort(const VAL* values_in, if (values_in == values_out) { keys_in = create_buffer(volume, Memory::Kind::GPU_FB_MEM); values_in_cub = keys_in.ptr(0); - CUNUMERIC_CHECK_CUDA(cudaMemcpyAsync( + CUPYNUMERIC_CHECK_CUDA(cudaMemcpyAsync( keys_in.ptr(0), values_out, sizeof(VAL) * volume, cudaMemcpyDeviceToDevice, stream)); } @@ -111,7 +111,7 @@ void cub_local_sort(const VAL* values_in, if (indices_in == indices_out) { idx_in = create_buffer(volume, Memory::Kind::GPU_FB_MEM); indices_in_cub = idx_in.ptr(0); - CUNUMERIC_CHECK_CUDA(cudaMemcpyAsync( + CUPYNUMERIC_CHECK_CUDA(cudaMemcpyAsync( idx_in.ptr(0), indices_out, sizeof(int64_t) * volume, cudaMemcpyDeviceToDevice, stream)); } @@ -193,4 +193,4 @@ void cub_local_sort(const VAL* values_in, } } // namespace detail -} // namespace cunumeric +} // namespace cupynumeric diff --git a/src/cunumeric/sort/cub_sort.h b/src/cupynumeric/sort/cub_sort.h similarity index 98% rename from src/cunumeric/sort/cub_sort.h rename to src/cupynumeric/sort/cub_sort.h index cfd03a3853..fcadb5ca91 100644 --- a/src/cunumeric/sort/cub_sort.h +++ b/src/cupynumeric/sort/cub_sort.h @@ -16,7 +16,7 @@ #pragma once -namespace cunumeric { +namespace cupynumeric { void cub_local_sort(const bool* values_in, bool* values_out, @@ -114,4 +114,4 @@ void cub_local_sort(const double* values_in, const size_t sort_dim_size, cudaStream_t stream); -} // namespace cunumeric +} // namespace cupynumeric diff --git a/src/cunumeric/sort/cub_sort_bool.cu b/src/cupynumeric/sort/cub_sort_bool.cu similarity index 91% rename from src/cunumeric/sort/cub_sort_bool.cu rename to src/cupynumeric/sort/cub_sort_bool.cu index 3f951454b4..e3f9117b58 100644 --- a/src/cunumeric/sort/cub_sort_bool.cu +++ b/src/cupynumeric/sort/cub_sort_bool.cu @@ -14,9 +14,9 @@ * */ -#include "cunumeric/sort/cub_sort.cuh" +#include "cupynumeric/sort/cub_sort.cuh" -namespace cunumeric { +namespace cupynumeric { void cub_local_sort(const bool* values_in, bool* values_out, @@ -30,4 +30,4 @@ void cub_local_sort(const bool* values_in, values_in, values_out, indices_in, indices_out, volume, sort_dim_size, stream); } -} // namespace cunumeric +} // namespace cupynumeric diff --git a/src/cunumeric/sort/cub_sort_double.cu b/src/cupynumeric/sort/cub_sort_double.cu similarity index 91% rename from src/cunumeric/sort/cub_sort_double.cu rename to src/cupynumeric/sort/cub_sort_double.cu index 03a02fef27..e644802838 100644 --- a/src/cunumeric/sort/cub_sort_double.cu +++ b/src/cupynumeric/sort/cub_sort_double.cu @@ -14,9 +14,9 @@ * */ -#include "cunumeric/sort/cub_sort.cuh" +#include "cupynumeric/sort/cub_sort.cuh" -namespace cunumeric { +namespace cupynumeric { void cub_local_sort(const double* values_in, double* values_out, @@ -30,4 +30,4 @@ void cub_local_sort(const double* values_in, values_in, values_out, indices_in, indices_out, volume, sort_dim_size, stream); } -} // namespace cunumeric +} // namespace cupynumeric diff --git a/src/cunumeric/sort/cub_sort_float.cu b/src/cupynumeric/sort/cub_sort_float.cu similarity index 91% rename from src/cunumeric/sort/cub_sort_float.cu rename to src/cupynumeric/sort/cub_sort_float.cu index 1502ad795f..2edc8db40c 100644 --- a/src/cunumeric/sort/cub_sort_float.cu +++ b/src/cupynumeric/sort/cub_sort_float.cu @@ -14,9 +14,9 @@ * */ -#include "cunumeric/sort/cub_sort.cuh" +#include "cupynumeric/sort/cub_sort.cuh" -namespace cunumeric { +namespace cupynumeric { void cub_local_sort(const float* values_in, float* values_out, @@ -30,4 +30,4 @@ void cub_local_sort(const float* values_in, values_in, values_out, indices_in, indices_out, volume, sort_dim_size, stream); } -} // namespace cunumeric +} // namespace cupynumeric diff --git a/src/cunumeric/sort/cub_sort_half.cu b/src/cupynumeric/sort/cub_sort_half.cu similarity index 91% rename from src/cunumeric/sort/cub_sort_half.cu rename to src/cupynumeric/sort/cub_sort_half.cu index ef89de7379..d75bc4d189 100644 --- a/src/cunumeric/sort/cub_sort_half.cu +++ b/src/cupynumeric/sort/cub_sort_half.cu @@ -14,9 +14,9 @@ * */ -#include "cunumeric/sort/cub_sort.cuh" +#include "cupynumeric/sort/cub_sort.cuh" -namespace cunumeric { +namespace cupynumeric { void cub_local_sort(const __half* values_in, __half* values_out, @@ -30,4 +30,4 @@ void cub_local_sort(const __half* values_in, values_in, values_out, indices_in, indices_out, volume, sort_dim_size, stream); } -} // namespace cunumeric +} // namespace cupynumeric diff --git a/src/cunumeric/sort/cub_sort_int16.cu b/src/cupynumeric/sort/cub_sort_int16.cu similarity index 91% rename from src/cunumeric/sort/cub_sort_int16.cu rename to src/cupynumeric/sort/cub_sort_int16.cu index b6a0b419e2..843c797556 100644 --- a/src/cunumeric/sort/cub_sort_int16.cu +++ b/src/cupynumeric/sort/cub_sort_int16.cu @@ -14,9 +14,9 @@ * */ -#include "cunumeric/sort/cub_sort.cuh" +#include "cupynumeric/sort/cub_sort.cuh" -namespace cunumeric { +namespace cupynumeric { void cub_local_sort(const int16_t* values_in, int16_t* values_out, @@ -30,4 +30,4 @@ void cub_local_sort(const int16_t* values_in, values_in, values_out, indices_in, indices_out, volume, sort_dim_size, stream); } -} // namespace cunumeric +} // namespace cupynumeric diff --git a/src/cunumeric/sort/cub_sort_int32.cu b/src/cupynumeric/sort/cub_sort_int32.cu similarity index 91% rename from src/cunumeric/sort/cub_sort_int32.cu rename to src/cupynumeric/sort/cub_sort_int32.cu index f3af455476..5f682a5bd5 100644 --- a/src/cunumeric/sort/cub_sort_int32.cu +++ b/src/cupynumeric/sort/cub_sort_int32.cu @@ -14,9 +14,9 @@ * */ -#include "cunumeric/sort/cub_sort.cuh" +#include "cupynumeric/sort/cub_sort.cuh" -namespace cunumeric { +namespace cupynumeric { void cub_local_sort(const int32_t* values_in, int32_t* values_out, @@ -30,4 +30,4 @@ void cub_local_sort(const int32_t* values_in, values_in, values_out, indices_in, indices_out, volume, sort_dim_size, stream); } -} // namespace cunumeric +} // namespace cupynumeric diff --git a/src/cunumeric/sort/cub_sort_int64.cu b/src/cupynumeric/sort/cub_sort_int64.cu similarity index 91% rename from src/cunumeric/sort/cub_sort_int64.cu rename to src/cupynumeric/sort/cub_sort_int64.cu index cd16f992ff..99410c6051 100644 --- a/src/cunumeric/sort/cub_sort_int64.cu +++ b/src/cupynumeric/sort/cub_sort_int64.cu @@ -14,9 +14,9 @@ * */ -#include "cunumeric/sort/cub_sort.cuh" +#include "cupynumeric/sort/cub_sort.cuh" -namespace cunumeric { +namespace cupynumeric { void cub_local_sort(const int64_t* values_in, int64_t* values_out, @@ -30,4 +30,4 @@ void cub_local_sort(const int64_t* values_in, values_in, values_out, indices_in, indices_out, volume, sort_dim_size, stream); } -} // namespace cunumeric +} // namespace cupynumeric diff --git a/src/cunumeric/sort/cub_sort_int8.cu b/src/cupynumeric/sort/cub_sort_int8.cu similarity index 91% rename from src/cunumeric/sort/cub_sort_int8.cu rename to src/cupynumeric/sort/cub_sort_int8.cu index c960dbb345..fc58527e23 100644 --- a/src/cunumeric/sort/cub_sort_int8.cu +++ b/src/cupynumeric/sort/cub_sort_int8.cu @@ -14,9 +14,9 @@ * */ -#include "cunumeric/sort/cub_sort.cuh" +#include "cupynumeric/sort/cub_sort.cuh" -namespace cunumeric { +namespace cupynumeric { void cub_local_sort(const int8_t* values_in, int8_t* values_out, @@ -30,4 +30,4 @@ void cub_local_sort(const int8_t* values_in, values_in, values_out, indices_in, indices_out, volume, sort_dim_size, stream); } -} // namespace cunumeric +} // namespace cupynumeric diff --git a/src/cunumeric/sort/cub_sort_uint16.cu b/src/cupynumeric/sort/cub_sort_uint16.cu similarity index 91% rename from src/cunumeric/sort/cub_sort_uint16.cu rename to src/cupynumeric/sort/cub_sort_uint16.cu index 3058dd8794..231c71ad55 100644 --- a/src/cunumeric/sort/cub_sort_uint16.cu +++ b/src/cupynumeric/sort/cub_sort_uint16.cu @@ -14,9 +14,9 @@ * */ -#include "cunumeric/sort/cub_sort.cuh" +#include "cupynumeric/sort/cub_sort.cuh" -namespace cunumeric { +namespace cupynumeric { void cub_local_sort(const uint16_t* values_in, uint16_t* values_out, @@ -30,4 +30,4 @@ void cub_local_sort(const uint16_t* values_in, values_in, values_out, indices_in, indices_out, volume, sort_dim_size, stream); } -} // namespace cunumeric +} // namespace cupynumeric diff --git a/src/cunumeric/sort/cub_sort_uint32.cu b/src/cupynumeric/sort/cub_sort_uint32.cu similarity index 91% rename from src/cunumeric/sort/cub_sort_uint32.cu rename to src/cupynumeric/sort/cub_sort_uint32.cu index 49cb1556ff..b339c70439 100644 --- a/src/cunumeric/sort/cub_sort_uint32.cu +++ b/src/cupynumeric/sort/cub_sort_uint32.cu @@ -14,9 +14,9 @@ * */ -#include "cunumeric/sort/cub_sort.cuh" +#include "cupynumeric/sort/cub_sort.cuh" -namespace cunumeric { +namespace cupynumeric { void cub_local_sort(const uint32_t* values_in, uint32_t* values_out, @@ -30,4 +30,4 @@ void cub_local_sort(const uint32_t* values_in, values_in, values_out, indices_in, indices_out, volume, sort_dim_size, stream); } -} // namespace cunumeric +} // namespace cupynumeric diff --git a/src/cunumeric/sort/cub_sort_uint64.cu b/src/cupynumeric/sort/cub_sort_uint64.cu similarity index 91% rename from src/cunumeric/sort/cub_sort_uint64.cu rename to src/cupynumeric/sort/cub_sort_uint64.cu index 9ed917f9b8..bfaeb2a0df 100644 --- a/src/cunumeric/sort/cub_sort_uint64.cu +++ b/src/cupynumeric/sort/cub_sort_uint64.cu @@ -14,9 +14,9 @@ * */ -#include "cunumeric/sort/cub_sort.cuh" +#include "cupynumeric/sort/cub_sort.cuh" -namespace cunumeric { +namespace cupynumeric { void cub_local_sort(const uint64_t* values_in, uint64_t* values_out, @@ -30,4 +30,4 @@ void cub_local_sort(const uint64_t* values_in, values_in, values_out, indices_in, indices_out, volume, sort_dim_size, stream); } -} // namespace cunumeric +} // namespace cupynumeric diff --git a/src/cunumeric/sort/cub_sort_uint8.cu b/src/cupynumeric/sort/cub_sort_uint8.cu similarity index 91% rename from src/cunumeric/sort/cub_sort_uint8.cu rename to src/cupynumeric/sort/cub_sort_uint8.cu index ca776af561..073164bce8 100644 --- a/src/cunumeric/sort/cub_sort_uint8.cu +++ b/src/cupynumeric/sort/cub_sort_uint8.cu @@ -14,9 +14,9 @@ * */ -#include "cunumeric/sort/cub_sort.cuh" +#include "cupynumeric/sort/cub_sort.cuh" -namespace cunumeric { +namespace cupynumeric { void cub_local_sort(const uint8_t* values_in, uint8_t* values_out, @@ -30,4 +30,4 @@ void cub_local_sort(const uint8_t* values_in, values_in, values_out, indices_in, indices_out, volume, sort_dim_size, stream); } -} // namespace cunumeric +} // namespace cupynumeric diff --git a/src/cunumeric/sort/searchsorted.cc b/src/cupynumeric/sort/searchsorted.cc similarity index 95% rename from src/cunumeric/sort/searchsorted.cc rename to src/cupynumeric/sort/searchsorted.cc index 6ea3d85c18..89957f397f 100644 --- a/src/cunumeric/sort/searchsorted.cc +++ b/src/cupynumeric/sort/searchsorted.cc @@ -14,10 +14,10 @@ * */ -#include "cunumeric/sort/searchsorted.h" -#include "cunumeric/sort/searchsorted_template.inl" +#include "cupynumeric/sort/searchsorted.h" +#include "cupynumeric/sort/searchsorted_template.inl" -namespace cunumeric { +namespace cupynumeric { using namespace legate; @@ -85,4 +85,4 @@ static void __attribute__((constructor)) register_tasks(void) } } // namespace -} // namespace cunumeric +} // namespace cupynumeric diff --git a/src/cunumeric/sort/searchsorted.cu b/src/cupynumeric/sort/searchsorted.cu similarity index 95% rename from src/cunumeric/sort/searchsorted.cu rename to src/cupynumeric/sort/searchsorted.cu index 2c7488dc6e..d358c89ae0 100644 --- a/src/cunumeric/sort/searchsorted.cu +++ b/src/cupynumeric/sort/searchsorted.cu @@ -14,13 +14,13 @@ * */ -#include "cunumeric/sort/searchsorted.h" -#include "cunumeric/sort/searchsorted_template.inl" +#include "cupynumeric/sort/searchsorted.h" +#include "cupynumeric/sort/searchsorted_template.inl" #include -#include "cunumeric/cuda_help.h" +#include "cupynumeric/cuda_help.h" -namespace cunumeric { +namespace cupynumeric { template static __global__ void __launch_bounds__(THREADS_PER_BLOCK, MIN_CTAS_PER_SM) @@ -106,7 +106,7 @@ struct SearchSortedImplBody { searchsorted_kernel_max<<>>( output_reduction, input, input_v, rect_values.lo, pitches, volume, num_values, offset); } - CUNUMERIC_CHECK_CUDA_STREAM(stream); + CUPYNUMERIC_CHECK_CUDA_STREAM(stream); } }; @@ -115,4 +115,4 @@ struct SearchSortedImplBody { searchsorted_template(context); } -} // namespace cunumeric +} // namespace cupynumeric diff --git a/src/cunumeric/sort/searchsorted.h b/src/cupynumeric/sort/searchsorted.h similarity index 81% rename from src/cunumeric/sort/searchsorted.h rename to src/cupynumeric/sort/searchsorted.h index a55b0ddf63..e983f2deaf 100644 --- a/src/cunumeric/sort/searchsorted.h +++ b/src/cupynumeric/sort/searchsorted.h @@ -16,9 +16,9 @@ #pragma once -#include "cunumeric/cunumeric_task.h" +#include "cupynumeric/cupynumeric_task.h" -namespace cunumeric { +namespace cupynumeric { struct SearchSortedArgs { legate::PhysicalStore input_base; @@ -29,9 +29,9 @@ struct SearchSortedArgs { bool is_index_space; }; -class SearchSortedTask : public CuNumericTask { +class SearchSortedTask : public CuPyNumericTask { public: - static constexpr auto TASK_ID = legate::LocalTaskID{CUNUMERIC_SEARCHSORTED}; + static constexpr auto TASK_ID = legate::LocalTaskID{CUPYNUMERIC_SEARCHSORTED}; public: static void cpu_variant(legate::TaskContext context); @@ -43,4 +43,4 @@ class SearchSortedTask : public CuNumericTask { #endif }; -} // namespace cunumeric +} // namespace cupynumeric diff --git a/src/cunumeric/sort/searchsorted_omp.cc b/src/cupynumeric/sort/searchsorted_omp.cc similarity index 95% rename from src/cunumeric/sort/searchsorted_omp.cc rename to src/cupynumeric/sort/searchsorted_omp.cc index b19b09f77d..7130b7d1e7 100644 --- a/src/cunumeric/sort/searchsorted_omp.cc +++ b/src/cupynumeric/sort/searchsorted_omp.cc @@ -14,12 +14,12 @@ * */ -#include "cunumeric/sort/searchsorted.h" -#include "cunumeric/sort/searchsorted_template.inl" +#include "cupynumeric/sort/searchsorted.h" +#include "cupynumeric/sort/searchsorted_template.inl" #include -namespace cunumeric { +namespace cupynumeric { using namespace legate; @@ -81,4 +81,4 @@ struct SearchSortedImplBody { searchsorted_template(context); } -} // namespace cunumeric +} // namespace cupynumeric diff --git a/src/cunumeric/sort/searchsorted_template.inl b/src/cupynumeric/sort/searchsorted_template.inl similarity index 95% rename from src/cunumeric/sort/searchsorted_template.inl rename to src/cupynumeric/sort/searchsorted_template.inl index 9c910808f8..0bc3906a19 100644 --- a/src/cunumeric/sort/searchsorted_template.inl +++ b/src/cupynumeric/sort/searchsorted_template.inl @@ -17,10 +17,10 @@ #pragma once // Useful for IDEs -#include "cunumeric/sort/searchsorted.h" -#include "cunumeric/pitches.h" +#include "cupynumeric/sort/searchsorted.h" +#include "cupynumeric/pitches.h" -namespace cunumeric { +namespace cupynumeric { using namespace legate; @@ -82,4 +82,4 @@ static void searchsorted_template(TaskContext& context) std::max(1, args.input_values.dim()), args.input_base.code(), SearchSortedImpl{}, args); } -} // namespace cunumeric +} // namespace cupynumeric diff --git a/src/cunumeric/sort/sort.cc b/src/cupynumeric/sort/sort.cc similarity index 93% rename from src/cunumeric/sort/sort.cc rename to src/cupynumeric/sort/sort.cc index 985fdaa1ac..21b24580ef 100644 --- a/src/cunumeric/sort/sort.cc +++ b/src/cupynumeric/sort/sort.cc @@ -17,14 +17,14 @@ #include #include -#include "cunumeric/sort/sort.h" -#include "cunumeric/sort/sort_cpu.inl" -#include "cunumeric/sort/sort_template.inl" +#include "cupynumeric/sort/sort.h" +#include "cupynumeric/sort/sort_cpu.inl" +#include "cupynumeric/sort/sort_template.inl" #include #include -namespace cunumeric { +namespace cupynumeric { using namespace legate; @@ -81,4 +81,4 @@ static void __attribute__((constructor)) register_tasks(void) } } // namespace -} // namespace cunumeric +} // namespace cupynumeric diff --git a/src/cunumeric/sort/sort.cu b/src/cupynumeric/sort/sort.cu similarity index 94% rename from src/cunumeric/sort/sort.cu rename to src/cupynumeric/sort/sort.cu index 0c23890802..7479618069 100644 --- a/src/cunumeric/sort/sort.cu +++ b/src/cupynumeric/sort/sort.cu @@ -14,12 +14,12 @@ * */ -#include "cunumeric/sort/sort.h" -#include "cunumeric/sort/sort_template.inl" -#include "cunumeric/sort/cub_sort.h" -#include "cunumeric/sort/thrust_sort.h" -#include "cunumeric/utilities/thrust_allocator.h" -#include "cunumeric/utilities/thrust_util.h" +#include "cupynumeric/sort/sort.h" +#include "cupynumeric/sort/sort_template.inl" +#include "cupynumeric/sort/cub_sort.h" +#include "cupynumeric/sort/thrust_sort.h" +#include "cupynumeric/utilities/thrust_allocator.h" +#include "cupynumeric/utilities/thrust_util.h" #include #include @@ -33,14 +33,14 @@ #include -#include "cunumeric/cuda_help.h" +#include "cupynumeric/cuda_help.h" // above this threshold segment sort will be performed // by cub::DeviceSegmentedRadixSort instead of thrust::(stable_)sort // with tuple keys (not available for complex) #define SEGMENT_THRESHOLD_RADIX_SORT 400 -namespace cunumeric { +namespace cupynumeric { template struct support_cub : std::true_type {}; @@ -484,7 +484,7 @@ __global__ static void __launch_bounds__(THREADS_PER_BLOCK, MIN_CTAS_PER_SM) } } -#ifdef DEBUG_CUNUMERIC +#ifdef DEBUG_CUPYNUMERIC assert(target_offset == (segment_id + 1) * segment_size_l); #endif } @@ -647,10 +647,10 @@ SegmentMergePiece> merge_all_buffers( combine_buffers_no_sort<<>>( idc_buffers_ptr, target_offsets, result.indices, merged_size, num_sort_ranks); - CUNUMERIC_CHECK_CUDA(cudaStreamSynchronize(stream)); // needed before Z-copy destroy() + CUPYNUMERIC_CHECK_CUDA(cudaStreamSynchronize(stream)); // needed before Z-copy destroy() idc_buffers_ptr.destroy(); } else { - CUNUMERIC_CHECK_CUDA(cudaStreamSynchronize(stream)); // needed before Z-copy destroy() + CUPYNUMERIC_CHECK_CUDA(cudaStreamSynchronize(stream)); // needed before Z-copy destroy() } val_buffers_ptr.destroy(); target_offsets.destroy(); @@ -672,7 +672,7 @@ SegmentMergePiece> merge_all_buffers( local_sort( p_values, p_values, p_indices, p_indices, merged_size, merged_size, true, stream); - CUNUMERIC_CHECK_CUDA_STREAM(stream); + CUPYNUMERIC_CHECK_CUDA_STREAM(stream); return result; } else { // maybe k-way merge is more efficient here... @@ -773,7 +773,7 @@ SegmentMergePiece> merge_all_buffers( } SegmentMergePiece result = merge_buffers[0]; merge_buffers.clear(); - CUNUMERIC_CHECK_CUDA_STREAM(stream); + CUPYNUMERIC_CHECK_CUDA_STREAM(stream); return result; } } @@ -833,7 +833,7 @@ void rebalance_data(SegmentMergePiece& merge_buffer, merge_buffer.values.destroy(); } -#ifdef DEBUG_CUNUMERIC +#ifdef DEBUG_CUPYNUMERIC { size_t reduce = thrust::reduce(exec_policy, segment_diff.ptr(0), segment_diff.ptr(0) + num_segments_l, 0); @@ -875,7 +875,7 @@ void rebalance_data(SegmentMergePiece& merge_buffer, num_sort_ranks); } -#ifdef DEBUG_CUNUMERIC +#ifdef DEBUG_CUPYNUMERIC { for (size_t segment = 0; segment < num_segments_l; ++segment) { assert(0 == thrust::reduce(exec_policy, @@ -913,28 +913,28 @@ void rebalance_data(SegmentMergePiece& merge_buffer, segment_diff_2d_ptr, segment_diff_2d_ptr + num_segments_l * num_sort_ranks, segment_diff_2d_scan_ptr); - CUNUMERIC_CHECK_CUDA(cudaMemcpy2DAsync(send_right.ptr(0), - sizeof(int64_t), - segment_diff_2d_scan.ptr(0) + my_sort_rank, - num_sort_ranks * sizeof(int64_t), - sizeof(int64_t), - num_segments_l, - cudaMemcpyDeviceToDevice, - stream)); + CUPYNUMERIC_CHECK_CUDA(cudaMemcpy2DAsync(send_right.ptr(0), + sizeof(int64_t), + segment_diff_2d_scan.ptr(0) + my_sort_rank, + num_sort_ranks * sizeof(int64_t), + sizeof(int64_t), + num_segments_l, + cudaMemcpyDeviceToDevice, + stream)); thrust::reverse_iterator::iterator> iter_in( segment_diff_2d_ptr + num_segments_l * num_sort_ranks); thrust::reverse_iterator::iterator> iter_out( segment_diff_2d_scan_ptr + num_segments_l * num_sort_ranks); thrust::inclusive_scan( exec_policy, iter_in, iter_in + num_segments_l * num_sort_ranks, iter_out); - CUNUMERIC_CHECK_CUDA(cudaMemcpy2DAsync(send_left.ptr(0), - sizeof(int64_t), - segment_diff_2d_scan.ptr(0) + my_sort_rank, - num_sort_ranks * sizeof(int64_t), - sizeof(int64_t), - num_segments_l, - cudaMemcpyDeviceToDevice, - stream)); + CUPYNUMERIC_CHECK_CUDA(cudaMemcpy2DAsync(send_left.ptr(0), + sizeof(int64_t), + segment_diff_2d_scan.ptr(0) + my_sort_rank, + num_sort_ranks * sizeof(int64_t), + sizeof(int64_t), + num_segments_l, + cudaMemcpyDeviceToDevice, + stream)); segment_diff_2d.destroy(); segment_diff_2d_scan.destroy(); @@ -1212,7 +1212,7 @@ void rebalance_data(SegmentMergePiece& merge_buffer, recv_left_data.values.destroy(); recv_right_data.values.destroy(); } - CUNUMERIC_CHECK_CUDA_STREAM(stream); + CUPYNUMERIC_CHECK_CUDA_STREAM(stream); } } @@ -1260,15 +1260,15 @@ void sample_sort_nccl_nd( { auto worker_count_d = create_buffer(1, legate::Memory::GPU_FB_MEM); size_t worker_count = (segment_size_l > 0 ? 1 : 0); - CUNUMERIC_CHECK_CUDA(cudaMemcpyAsync( + CUPYNUMERIC_CHECK_CUDA(cudaMemcpyAsync( worker_count_d.ptr(0), &worker_count, sizeof(int32_t), cudaMemcpyHostToDevice, stream)); context.concurrent_task_barrier(); CHECK_NCCL(ncclAllReduce( worker_count_d.ptr(0), worker_count_d.ptr(0), 1, ncclInt32, ncclSum, *comm, stream)); context.concurrent_task_barrier(); - CUNUMERIC_CHECK_CUDA(cudaMemcpyAsync( + CUPYNUMERIC_CHECK_CUDA(cudaMemcpyAsync( &worker_count, worker_count_d.ptr(0), sizeof(int32_t), cudaMemcpyDeviceToHost, stream)); - CUNUMERIC_CHECK_CUDA(cudaStreamSynchronize(stream)); + CUPYNUMERIC_CHECK_CUDA(cudaStreamSynchronize(stream)); if (worker_count < num_ranks) { const size_t number_sort_groups = num_ranks / num_sort_ranks; num_sort_ranks = worker_count / number_sort_groups; @@ -1316,7 +1316,7 @@ void sample_sort_nccl_nd( offset, num_sort_ranks, my_sort_rank); - CUNUMERIC_CHECK_CUDA_STREAM(stream); + CUPYNUMERIC_CHECK_CUDA_STREAM(stream); } // AllGather does not work here as not all have the same amount! @@ -1325,11 +1325,11 @@ void sample_sort_nccl_nd( // allocate receive buffer const size_t aligned_count = get_16b_aligned_count(num_samples_l, sizeof(SegmentSample)); auto send_buffer = create_buffer>(aligned_count, legate::Memory::GPU_FB_MEM); - CUNUMERIC_CHECK_CUDA(cudaMemcpyAsync(send_buffer.ptr(0), - samples.ptr(offset), - sizeof(SegmentSample) * num_samples_l, - cudaMemcpyDeviceToDevice, - stream)); + CUPYNUMERIC_CHECK_CUDA(cudaMemcpyAsync(send_buffer.ptr(0), + samples.ptr(offset), + sizeof(SegmentSample) * num_samples_l, + cudaMemcpyDeviceToDevice, + stream)); auto recv_buffer = create_buffer>(aligned_count * num_sort_ranks, legate::Memory::GPU_FB_MEM); @@ -1356,11 +1356,11 @@ void sample_sort_nccl_nd( // copy back for (size_t r = 0; r < num_sort_ranks; r++) { if (r != my_sort_rank) { - CUNUMERIC_CHECK_CUDA(cudaMemcpyAsync(samples.ptr(num_samples_l * r), - recv_buffer.ptr(aligned_count * r), - sizeof(SegmentSample) * num_samples_l, - cudaMemcpyDeviceToDevice, - stream)); + CUPYNUMERIC_CHECK_CUDA(cudaMemcpyAsync(samples.ptr(num_samples_l * r), + recv_buffer.ptr(aligned_count * r), + sizeof(SegmentSample) * num_samples_l, + cudaMemcpyDeviceToDevice, + stream)); } } @@ -1368,7 +1368,7 @@ void sample_sort_nccl_nd( send_buffer.destroy(); recv_buffer.destroy(); - CUNUMERIC_CHECK_CUDA_STREAM(stream); + CUPYNUMERIC_CHECK_CUDA_STREAM(stream); } ///////////////////////////////////////////////////////////////////////////////////////////////// @@ -1434,7 +1434,7 @@ void sample_sort_nccl_nd( compute_scan_per_rank<<>>( segment_blocks.ptr(0), size_send.ptr(0), num_segments_l, num_segments_l_aligned); - CUNUMERIC_CHECK_CUDA_STREAM(stream); + CUPYNUMERIC_CHECK_CUDA_STREAM(stream); } // cleanup intermediate data structures @@ -1471,25 +1471,25 @@ void sample_sort_nccl_nd( Buffer size_recv_total = create_buffer(num_sort_ranks, legate::Memory::Z_COPY_MEM); { - CUNUMERIC_CHECK_CUDA(cudaMemcpy2DAsync(size_send_total.ptr(0), - 1 * sizeof(size_t), - size_send.ptr(num_segments_l), - num_segments_l_aligned * sizeof(size_t), - sizeof(int64_t), - num_sort_ranks, - cudaMemcpyDeviceToHost, - stream)); - CUNUMERIC_CHECK_CUDA(cudaMemcpy2DAsync(size_recv_total.ptr(0), - 1 * sizeof(size_t), - size_recv.ptr(num_segments_l), - num_segments_l_aligned * sizeof(size_t), - sizeof(int64_t), - num_sort_ranks, - cudaMemcpyDeviceToHost, - stream)); + CUPYNUMERIC_CHECK_CUDA(cudaMemcpy2DAsync(size_send_total.ptr(0), + 1 * sizeof(size_t), + size_send.ptr(num_segments_l), + num_segments_l_aligned * sizeof(size_t), + sizeof(int64_t), + num_sort_ranks, + cudaMemcpyDeviceToHost, + stream)); + CUPYNUMERIC_CHECK_CUDA(cudaMemcpy2DAsync(size_recv_total.ptr(0), + 1 * sizeof(size_t), + size_recv.ptr(num_segments_l), + num_segments_l_aligned * sizeof(size_t), + sizeof(int64_t), + num_sort_ranks, + cudaMemcpyDeviceToHost, + stream)); // need to sync as we share values in between host/device - CUNUMERIC_CHECK_CUDA(cudaStreamSynchronize(stream)); + CUPYNUMERIC_CHECK_CUDA(cudaStreamSynchronize(stream)); } // copy values into aligned send buffer @@ -1540,13 +1540,13 @@ void sample_sort_nccl_nd( segment_size_l, my_rank, num_sort_ranks); - CUNUMERIC_CHECK_CUDA(cudaStreamSynchronize(stream)); // needed before Z-copy destroy() + CUPYNUMERIC_CHECK_CUDA(cudaStreamSynchronize(stream)); // needed before Z-copy destroy() idc_send_buffers_ptr.destroy(); } else { - CUNUMERIC_CHECK_CUDA(cudaStreamSynchronize(stream)); // needed before Z-copy destroy() + CUPYNUMERIC_CHECK_CUDA(cudaStreamSynchronize(stream)); // needed before Z-copy destroy() } val_send_buffers_ptr.destroy(); - CUNUMERIC_CHECK_CUDA_STREAM(stream); + CUPYNUMERIC_CHECK_CUDA_STREAM(stream); } local_sorted.values.destroy(); @@ -1576,7 +1576,7 @@ void sample_sort_nccl_nd( size_recv.ptr(r * num_segments_l_aligned), size_recv.ptr(r * num_segments_l_aligned) + num_segments_l + 1, size_recv.ptr(r * num_segments_l_aligned)); - CUNUMERIC_CHECK_CUDA( + CUPYNUMERIC_CHECK_CUDA( cudaMemsetAsync(merge_buffers[r].segments.ptr(0), 0, size * sizeof(size_t), stream)); const size_t num_blocks = (num_segments_l + THREADS_PER_BLOCK - 1) / THREADS_PER_BLOCK; assert(sizeof(unsigned long long int) == @@ -1600,7 +1600,7 @@ void sample_sort_nccl_nd( } } - CUNUMERIC_CHECK_CUDA_STREAM(stream); + CUPYNUMERIC_CHECK_CUDA_STREAM(stream); } // communicate all2all (in sort dimension) @@ -1655,7 +1655,7 @@ void sample_sort_nccl_nd( idc_send_buffers[r].destroy(); } } - CUNUMERIC_CHECK_CUDA_STREAM(stream); + CUPYNUMERIC_CHECK_CUDA_STREAM(stream); ///////////////////////////////////////////////////////////////////////////////////////////////// /////////////// Part 4: merge data @@ -1786,7 +1786,7 @@ struct SortImplBody { values_ptr = output.ptr(rect.lo); } } - CUNUMERIC_CHECK_CUDA_STREAM(stream); + CUPYNUMERIC_CHECK_CUDA_STREAM(stream); if (volume > 0) { // sort data (locally) @@ -1799,7 +1799,7 @@ struct SortImplBody { stable, stream); } - CUNUMERIC_CHECK_CUDA_STREAM(stream); + CUPYNUMERIC_CHECK_CUDA_STREAM(stream); if (need_distributed_sort) { if (is_index_space) { @@ -1854,7 +1854,7 @@ struct SortImplBody { local_sorted.values.destroy(); } - CUNUMERIC_CHECK_CUDA_STREAM(stream); + CUPYNUMERIC_CHECK_CUDA_STREAM(stream); } }; @@ -1863,4 +1863,4 @@ struct SortImplBody { sort_template(context); } -} // namespace cunumeric +} // namespace cupynumeric diff --git a/src/cunumeric/sort/sort.h b/src/cupynumeric/sort/sort.h similarity index 92% rename from src/cunumeric/sort/sort.h rename to src/cupynumeric/sort/sort.h index 722f2e8cc3..f3c0cd2c58 100644 --- a/src/cunumeric/sort/sort.h +++ b/src/cupynumeric/sort/sort.h @@ -16,11 +16,11 @@ #pragma once -#include "cunumeric/cunumeric_task.h" +#include "cupynumeric/cupynumeric_task.h" #include -namespace cunumeric { +namespace cupynumeric { struct SortArgs { legate::PhysicalStore input; @@ -92,9 +92,9 @@ struct modulusWithOffset : public thrust::binary_function { +class SortTask : public CuPyNumericTask { public: - static constexpr auto TASK_ID = legate::LocalTaskID{CUNUMERIC_SORT}; + static constexpr auto TASK_ID = legate::LocalTaskID{CUPYNUMERIC_SORT}; public: static void cpu_variant(legate::TaskContext context); @@ -106,4 +106,4 @@ class SortTask : public CuNumericTask { #endif }; -} // namespace cunumeric +} // namespace cupynumeric diff --git a/src/cunumeric/sort/sort_cpu.inl b/src/cupynumeric/sort/sort_cpu.inl similarity index 99% rename from src/cunumeric/sort/sort_cpu.inl rename to src/cupynumeric/sort/sort_cpu.inl index a8043a3131..e38fcc1b55 100644 --- a/src/cunumeric/sort/sort_cpu.inl +++ b/src/cupynumeric/sort/sort_cpu.inl @@ -17,8 +17,8 @@ #pragma once // Useful for IDEs -#include "cunumeric/sort/sort.h" -#include "cunumeric/pitches.h" +#include "cupynumeric/sort/sort.h" +#include "cupynumeric/pitches.h" #include "legate/comm/coll.h" #include @@ -32,7 +32,7 @@ #include #include -namespace cunumeric { +namespace cupynumeric { using namespace legate; @@ -120,7 +120,7 @@ void rebalance_data(SegmentMergePiece& merge_buffer, merge_buffer.values.destroy(); } -#ifdef DEBUG_CUNUMERIC +#ifdef DEBUG_CUPYNUMERIC { size_t reduce = thrust::reduce(exec, segment_diff.ptr(0), segment_diff.ptr(0) + num_segments_l, 0); @@ -174,7 +174,7 @@ void rebalance_data(SegmentMergePiece& merge_buffer, segment_diff_buffers.destroy(); } -#ifdef DEBUG_CUNUMERIC +#ifdef DEBUG_CUPYNUMERIC { for (size_t segment = 0; segment < num_segments_l; ++segment) { assert(0 == thrust::reduce(exec, @@ -670,7 +670,7 @@ void sample_sort_nd( // cleanup intermediate data structures samples_g.destroy(); -#ifdef DEBUG_CUNUMERIC +#ifdef DEBUG_CUPYNUMERIC { size_t total_send = 0; for (size_t r = 0; r < num_sort_ranks; ++r) { @@ -1050,4 +1050,4 @@ struct SortImplBodyCpu { } }; -} // namespace cunumeric +} // namespace cupynumeric diff --git a/src/cunumeric/sort/sort_omp.cc b/src/cupynumeric/sort/sort_omp.cc similarity index 93% rename from src/cunumeric/sort/sort_omp.cc rename to src/cupynumeric/sort/sort_omp.cc index 373ecfe095..47e687f640 100644 --- a/src/cunumeric/sort/sort_omp.cc +++ b/src/cupynumeric/sort/sort_omp.cc @@ -14,9 +14,9 @@ * */ -#include "cunumeric/sort/sort.h" -#include "cunumeric/sort/sort_cpu.inl" -#include "cunumeric/sort/sort_template.inl" +#include "cupynumeric/sort/sort.h" +#include "cupynumeric/sort/sort_cpu.inl" +#include "cupynumeric/sort/sort_template.inl" #include #include @@ -25,7 +25,7 @@ #include #include -namespace cunumeric { +namespace cupynumeric { using namespace legate; @@ -72,4 +72,4 @@ struct SortImplBody { sort_template(context); } -} // namespace cunumeric +} // namespace cupynumeric diff --git a/src/cunumeric/sort/sort_template.inl b/src/cupynumeric/sort/sort_template.inl similarity index 97% rename from src/cunumeric/sort/sort_template.inl rename to src/cupynumeric/sort/sort_template.inl index 7963310152..a3c8f169fe 100644 --- a/src/cunumeric/sort/sort_template.inl +++ b/src/cupynumeric/sort/sort_template.inl @@ -17,10 +17,10 @@ #pragma once // Useful for IDEs -#include "cunumeric/sort/sort.h" -#include "cunumeric/pitches.h" +#include "cupynumeric/sort/sort.h" +#include "cupynumeric/pitches.h" -namespace cunumeric { +namespace cupynumeric { using namespace legate; @@ -112,4 +112,4 @@ static void sort_template(TaskContext& context) args.input.dim(), args.input.code(), SortImpl{}, args, context, context.communicators()); } -} // namespace cunumeric +} // namespace cupynumeric diff --git a/src/cunumeric/sort/thrust_sort.cuh b/src/cupynumeric/sort/thrust_sort.cuh similarity index 94% rename from src/cunumeric/sort/thrust_sort.cuh rename to src/cupynumeric/sort/thrust_sort.cuh index df643573fa..c14428b2c0 100644 --- a/src/cunumeric/sort/thrust_sort.cuh +++ b/src/cupynumeric/sort/thrust_sort.cuh @@ -17,17 +17,17 @@ #pragma once #include "legate/data/buffer.h" -#include "cunumeric/utilities/thrust_allocator.h" -#include "cunumeric/utilities/thrust_util.h" +#include "cupynumeric/utilities/thrust_allocator.h" +#include "cupynumeric/utilities/thrust_util.h" #include #include #include #include -#include "cunumeric/cuda_help.h" +#include "cupynumeric/cuda_help.h" -namespace cunumeric { +namespace cupynumeric { namespace detail { using namespace legate; @@ -48,12 +48,12 @@ void thrust_local_sort(const VAL* values_in, if (values_in != values_out) { // not in-place --> need a copy - CUNUMERIC_CHECK_CUDA(cudaMemcpyAsync( + CUPYNUMERIC_CHECK_CUDA(cudaMemcpyAsync( values_out, values_in, sizeof(VAL) * volume, cudaMemcpyDeviceToDevice, stream)); } if (indices_in != indices_out) { // not in-place --> need a copy - CUNUMERIC_CHECK_CUDA(cudaMemcpyAsync( + CUPYNUMERIC_CHECK_CUDA(cudaMemcpyAsync( indices_out, values_in, sizeof(int64_t) * volume, cudaMemcpyDeviceToDevice, stream)); } @@ -123,4 +123,4 @@ void thrust_local_sort(const VAL* values_in, } } // namespace detail -} // namespace cunumeric +} // namespace cupynumeric diff --git a/src/cunumeric/sort/thrust_sort.h b/src/cupynumeric/sort/thrust_sort.h similarity index 99% rename from src/cunumeric/sort/thrust_sort.h rename to src/cupynumeric/sort/thrust_sort.h index 0dcd12a90c..4e578c24b4 100644 --- a/src/cunumeric/sort/thrust_sort.h +++ b/src/cupynumeric/sort/thrust_sort.h @@ -16,7 +16,7 @@ #pragma once -namespace cunumeric { +namespace cupynumeric { void thrust_local_sort(const bool* values_in, bool* values_out, @@ -144,4 +144,4 @@ void thrust_local_sort(const complex* values_in, const bool stable, cudaStream_t stream); -} // namespace cunumeric +} // namespace cupynumeric diff --git a/src/cunumeric/sort/thrust_sort_bool.cu b/src/cupynumeric/sort/thrust_sort_bool.cu similarity index 91% rename from src/cunumeric/sort/thrust_sort_bool.cu rename to src/cupynumeric/sort/thrust_sort_bool.cu index 3406171eb8..e5b1358e95 100644 --- a/src/cunumeric/sort/thrust_sort_bool.cu +++ b/src/cupynumeric/sort/thrust_sort_bool.cu @@ -14,9 +14,9 @@ * */ -#include "cunumeric/sort/thrust_sort.cuh" +#include "cupynumeric/sort/thrust_sort.cuh" -namespace cunumeric { +namespace cupynumeric { void thrust_local_sort(const bool* values_in, bool* values_out, @@ -31,4 +31,4 @@ void thrust_local_sort(const bool* values_in, values_in, values_out, indices_in, indices_out, volume, sort_dim_size, stable, stream); } -} // namespace cunumeric +} // namespace cupynumeric diff --git a/src/cunumeric/sort/thrust_sort_complex128.cu b/src/cupynumeric/sort/thrust_sort_complex128.cu similarity index 92% rename from src/cunumeric/sort/thrust_sort_complex128.cu rename to src/cupynumeric/sort/thrust_sort_complex128.cu index 978afa6918..97c1a5d41a 100644 --- a/src/cunumeric/sort/thrust_sort_complex128.cu +++ b/src/cupynumeric/sort/thrust_sort_complex128.cu @@ -14,9 +14,9 @@ * */ -#include "cunumeric/sort/thrust_sort.cuh" +#include "cupynumeric/sort/thrust_sort.cuh" -namespace cunumeric { +namespace cupynumeric { void thrust_local_sort(const complex* values_in, complex* values_out, @@ -31,4 +31,4 @@ void thrust_local_sort(const complex* values_in, values_in, values_out, indices_in, indices_out, volume, sort_dim_size, stable, stream); } -} // namespace cunumeric +} // namespace cupynumeric diff --git a/src/cunumeric/sort/thrust_sort_complex64.cu b/src/cupynumeric/sort/thrust_sort_complex64.cu similarity index 92% rename from src/cunumeric/sort/thrust_sort_complex64.cu rename to src/cupynumeric/sort/thrust_sort_complex64.cu index 15a6072255..9f5f959e85 100644 --- a/src/cunumeric/sort/thrust_sort_complex64.cu +++ b/src/cupynumeric/sort/thrust_sort_complex64.cu @@ -14,9 +14,9 @@ * */ -#include "cunumeric/sort/thrust_sort.cuh" +#include "cupynumeric/sort/thrust_sort.cuh" -namespace cunumeric { +namespace cupynumeric { void thrust_local_sort(const complex* values_in, complex* values_out, @@ -31,4 +31,4 @@ void thrust_local_sort(const complex* values_in, values_in, values_out, indices_in, indices_out, volume, sort_dim_size, stable, stream); } -} // namespace cunumeric +} // namespace cupynumeric diff --git a/src/cunumeric/sort/thrust_sort_double.cu b/src/cupynumeric/sort/thrust_sort_double.cu similarity index 91% rename from src/cunumeric/sort/thrust_sort_double.cu rename to src/cupynumeric/sort/thrust_sort_double.cu index 0b3d54db1d..ea783fa2a6 100644 --- a/src/cunumeric/sort/thrust_sort_double.cu +++ b/src/cupynumeric/sort/thrust_sort_double.cu @@ -14,9 +14,9 @@ * */ -#include "cunumeric/sort/thrust_sort.cuh" +#include "cupynumeric/sort/thrust_sort.cuh" -namespace cunumeric { +namespace cupynumeric { void thrust_local_sort(const double* values_in, double* values_out, @@ -31,4 +31,4 @@ void thrust_local_sort(const double* values_in, values_in, values_out, indices_in, indices_out, volume, sort_dim_size, stable, stream); } -} // namespace cunumeric +} // namespace cupynumeric diff --git a/src/cunumeric/sort/thrust_sort_float.cu b/src/cupynumeric/sort/thrust_sort_float.cu similarity index 91% rename from src/cunumeric/sort/thrust_sort_float.cu rename to src/cupynumeric/sort/thrust_sort_float.cu index a32af26012..182ecdb3ef 100644 --- a/src/cunumeric/sort/thrust_sort_float.cu +++ b/src/cupynumeric/sort/thrust_sort_float.cu @@ -14,9 +14,9 @@ * */ -#include "cunumeric/sort/thrust_sort.cuh" +#include "cupynumeric/sort/thrust_sort.cuh" -namespace cunumeric { +namespace cupynumeric { void thrust_local_sort(const float* values_in, float* values_out, @@ -31,4 +31,4 @@ void thrust_local_sort(const float* values_in, values_in, values_out, indices_in, indices_out, volume, sort_dim_size, stable, stream); } -} // namespace cunumeric +} // namespace cupynumeric diff --git a/src/cunumeric/sort/thrust_sort_half.cu b/src/cupynumeric/sort/thrust_sort_half.cu similarity index 91% rename from src/cunumeric/sort/thrust_sort_half.cu rename to src/cupynumeric/sort/thrust_sort_half.cu index 86467247e2..a582d919b6 100644 --- a/src/cunumeric/sort/thrust_sort_half.cu +++ b/src/cupynumeric/sort/thrust_sort_half.cu @@ -14,9 +14,9 @@ * */ -#include "cunumeric/sort/thrust_sort.cuh" +#include "cupynumeric/sort/thrust_sort.cuh" -namespace cunumeric { +namespace cupynumeric { void thrust_local_sort(const __half* values_in, __half* values_out, @@ -31,4 +31,4 @@ void thrust_local_sort(const __half* values_in, values_in, values_out, indices_in, indices_out, volume, sort_dim_size, stable, stream); } -} // namespace cunumeric +} // namespace cupynumeric diff --git a/src/cunumeric/sort/thrust_sort_int16.cu b/src/cupynumeric/sort/thrust_sort_int16.cu similarity index 91% rename from src/cunumeric/sort/thrust_sort_int16.cu rename to src/cupynumeric/sort/thrust_sort_int16.cu index d0f80ac6d5..d4f14ccffe 100644 --- a/src/cunumeric/sort/thrust_sort_int16.cu +++ b/src/cupynumeric/sort/thrust_sort_int16.cu @@ -14,9 +14,9 @@ * */ -#include "cunumeric/sort/thrust_sort.cuh" +#include "cupynumeric/sort/thrust_sort.cuh" -namespace cunumeric { +namespace cupynumeric { void thrust_local_sort(const int16_t* values_in, int16_t* values_out, @@ -31,4 +31,4 @@ void thrust_local_sort(const int16_t* values_in, values_in, values_out, indices_in, indices_out, volume, sort_dim_size, stable, stream); } -} // namespace cunumeric +} // namespace cupynumeric diff --git a/src/cunumeric/sort/thrust_sort_int32.cu b/src/cupynumeric/sort/thrust_sort_int32.cu similarity index 91% rename from src/cunumeric/sort/thrust_sort_int32.cu rename to src/cupynumeric/sort/thrust_sort_int32.cu index 8217b19c18..7398e16213 100644 --- a/src/cunumeric/sort/thrust_sort_int32.cu +++ b/src/cupynumeric/sort/thrust_sort_int32.cu @@ -14,9 +14,9 @@ * */ -#include "cunumeric/sort/thrust_sort.cuh" +#include "cupynumeric/sort/thrust_sort.cuh" -namespace cunumeric { +namespace cupynumeric { void thrust_local_sort(const int32_t* values_in, int32_t* values_out, @@ -31,4 +31,4 @@ void thrust_local_sort(const int32_t* values_in, values_in, values_out, indices_in, indices_out, volume, sort_dim_size, stable, stream); } -} // namespace cunumeric +} // namespace cupynumeric diff --git a/src/cunumeric/sort/thrust_sort_int64.cu b/src/cupynumeric/sort/thrust_sort_int64.cu similarity index 91% rename from src/cunumeric/sort/thrust_sort_int64.cu rename to src/cupynumeric/sort/thrust_sort_int64.cu index 238bc828d0..a4a2faf9ef 100644 --- a/src/cunumeric/sort/thrust_sort_int64.cu +++ b/src/cupynumeric/sort/thrust_sort_int64.cu @@ -14,9 +14,9 @@ * */ -#include "cunumeric/sort/thrust_sort.cuh" +#include "cupynumeric/sort/thrust_sort.cuh" -namespace cunumeric { +namespace cupynumeric { void thrust_local_sort(const int64_t* values_in, int64_t* values_out, @@ -31,4 +31,4 @@ void thrust_local_sort(const int64_t* values_in, values_in, values_out, indices_in, indices_out, volume, sort_dim_size, stable, stream); } -} // namespace cunumeric +} // namespace cupynumeric diff --git a/src/cunumeric/sort/thrust_sort_int8.cu b/src/cupynumeric/sort/thrust_sort_int8.cu similarity index 91% rename from src/cunumeric/sort/thrust_sort_int8.cu rename to src/cupynumeric/sort/thrust_sort_int8.cu index 8ce4fbcff4..b9d7aa06b1 100644 --- a/src/cunumeric/sort/thrust_sort_int8.cu +++ b/src/cupynumeric/sort/thrust_sort_int8.cu @@ -14,9 +14,9 @@ * */ -#include "cunumeric/sort/thrust_sort.cuh" +#include "cupynumeric/sort/thrust_sort.cuh" -namespace cunumeric { +namespace cupynumeric { void thrust_local_sort(const int8_t* values_in, int8_t* values_out, @@ -31,4 +31,4 @@ void thrust_local_sort(const int8_t* values_in, values_in, values_out, indices_in, indices_out, volume, sort_dim_size, stable, stream); } -} // namespace cunumeric +} // namespace cupynumeric diff --git a/src/cunumeric/sort/thrust_sort_uint16.cu b/src/cupynumeric/sort/thrust_sort_uint16.cu similarity index 91% rename from src/cunumeric/sort/thrust_sort_uint16.cu rename to src/cupynumeric/sort/thrust_sort_uint16.cu index 31d0db9b19..569f9be78d 100644 --- a/src/cunumeric/sort/thrust_sort_uint16.cu +++ b/src/cupynumeric/sort/thrust_sort_uint16.cu @@ -14,9 +14,9 @@ * */ -#include "cunumeric/sort/thrust_sort.cuh" +#include "cupynumeric/sort/thrust_sort.cuh" -namespace cunumeric { +namespace cupynumeric { void thrust_local_sort(const uint16_t* values_in, uint16_t* values_out, @@ -31,4 +31,4 @@ void thrust_local_sort(const uint16_t* values_in, values_in, values_out, indices_in, indices_out, volume, sort_dim_size, stable, stream); } -} // namespace cunumeric +} // namespace cupynumeric diff --git a/src/cunumeric/sort/thrust_sort_uint32.cu b/src/cupynumeric/sort/thrust_sort_uint32.cu similarity index 91% rename from src/cunumeric/sort/thrust_sort_uint32.cu rename to src/cupynumeric/sort/thrust_sort_uint32.cu index 318a1e991b..571c5b5006 100644 --- a/src/cunumeric/sort/thrust_sort_uint32.cu +++ b/src/cupynumeric/sort/thrust_sort_uint32.cu @@ -14,9 +14,9 @@ * */ -#include "cunumeric/sort/thrust_sort.cuh" +#include "cupynumeric/sort/thrust_sort.cuh" -namespace cunumeric { +namespace cupynumeric { void thrust_local_sort(const uint32_t* values_in, uint32_t* values_out, @@ -31,4 +31,4 @@ void thrust_local_sort(const uint32_t* values_in, values_in, values_out, indices_in, indices_out, volume, sort_dim_size, stable, stream); } -} // namespace cunumeric +} // namespace cupynumeric diff --git a/src/cunumeric/sort/thrust_sort_uint64.cu b/src/cupynumeric/sort/thrust_sort_uint64.cu similarity index 91% rename from src/cunumeric/sort/thrust_sort_uint64.cu rename to src/cupynumeric/sort/thrust_sort_uint64.cu index e457cfb9b3..d63a8acaf9 100644 --- a/src/cunumeric/sort/thrust_sort_uint64.cu +++ b/src/cupynumeric/sort/thrust_sort_uint64.cu @@ -14,9 +14,9 @@ * */ -#include "cunumeric/sort/thrust_sort.cuh" +#include "cupynumeric/sort/thrust_sort.cuh" -namespace cunumeric { +namespace cupynumeric { void thrust_local_sort(const uint64_t* values_in, uint64_t* values_out, @@ -31,4 +31,4 @@ void thrust_local_sort(const uint64_t* values_in, values_in, values_out, indices_in, indices_out, volume, sort_dim_size, stable, stream); } -} // namespace cunumeric +} // namespace cupynumeric diff --git a/src/cunumeric/sort/thrust_sort_uint8.cu b/src/cupynumeric/sort/thrust_sort_uint8.cu similarity index 91% rename from src/cunumeric/sort/thrust_sort_uint8.cu rename to src/cupynumeric/sort/thrust_sort_uint8.cu index 873d51796a..9d99fc2eff 100644 --- a/src/cunumeric/sort/thrust_sort_uint8.cu +++ b/src/cupynumeric/sort/thrust_sort_uint8.cu @@ -14,9 +14,9 @@ * */ -#include "cunumeric/sort/thrust_sort.cuh" +#include "cupynumeric/sort/thrust_sort.cuh" -namespace cunumeric { +namespace cupynumeric { void thrust_local_sort(const uint8_t* values_in, uint8_t* values_out, @@ -31,4 +31,4 @@ void thrust_local_sort(const uint8_t* values_in, values_in, values_out, indices_in, indices_out, volume, sort_dim_size, stable, stream); } -} // namespace cunumeric +} // namespace cupynumeric diff --git a/src/cunumeric/stat/bincount.cc b/src/cupynumeric/stat/bincount.cc similarity index 92% rename from src/cunumeric/stat/bincount.cc rename to src/cupynumeric/stat/bincount.cc index b18c95d8db..8a57525af7 100644 --- a/src/cunumeric/stat/bincount.cc +++ b/src/cupynumeric/stat/bincount.cc @@ -14,10 +14,10 @@ * */ -#include "cunumeric/stat/bincount.h" -#include "cunumeric/stat/bincount_template.inl" +#include "cupynumeric/stat/bincount.h" +#include "cupynumeric/stat/bincount_template.inl" -namespace cunumeric { +namespace cupynumeric { using namespace legate; @@ -61,4 +61,4 @@ namespace // unnamed static void __attribute__((constructor)) register_tasks(void) { BincountTask::register_variants(); } } // namespace -} // namespace cunumeric +} // namespace cupynumeric diff --git a/src/cunumeric/stat/bincount.cu b/src/cupynumeric/stat/bincount.cu similarity index 96% rename from src/cunumeric/stat/bincount.cu rename to src/cupynumeric/stat/bincount.cu index 314b0f00ec..fa2c79eaf1 100644 --- a/src/cunumeric/stat/bincount.cu +++ b/src/cupynumeric/stat/bincount.cu @@ -14,12 +14,12 @@ * */ -#include "cunumeric/stat/bincount.h" -#include "cunumeric/stat/bincount_template.inl" +#include "cupynumeric/stat/bincount.h" +#include "cupynumeric/stat/bincount_template.inl" -#include "cunumeric/cuda_help.h" +#include "cupynumeric/cuda_help.h" -namespace cunumeric { +namespace cupynumeric { template static __device__ inline void _bincount(int32_t* bins, @@ -183,7 +183,7 @@ struct BincountImplBody { bincount_kernel_rd_global <<>>(lhs, rhs, volume, rect.lo); } - CUNUMERIC_CHECK_CUDA_STREAM(stream); + CUPYNUMERIC_CHECK_CUDA_STREAM(stream); } void operator()(AccessorRD, false, 1> lhs, @@ -212,7 +212,7 @@ struct BincountImplBody { weighted_bincount_kernel_rd_global <<>>(lhs, rhs, weights, volume, rect.lo); } - CUNUMERIC_CHECK_CUDA_STREAM(stream); + CUPYNUMERIC_CHECK_CUDA_STREAM(stream); } }; @@ -221,4 +221,4 @@ struct BincountImplBody { bincount_template(context); } -} // namespace cunumeric +} // namespace cupynumeric diff --git a/src/cunumeric/stat/bincount.h b/src/cupynumeric/stat/bincount.h similarity index 82% rename from src/cunumeric/stat/bincount.h rename to src/cupynumeric/stat/bincount.h index 2571737150..188c5b11dd 100644 --- a/src/cunumeric/stat/bincount.h +++ b/src/cupynumeric/stat/bincount.h @@ -16,9 +16,9 @@ #pragma once -#include "cunumeric/cunumeric_task.h" +#include "cupynumeric/cupynumeric_task.h" -namespace cunumeric { +namespace cupynumeric { struct BincountArgs { legate::PhysicalStore lhs{nullptr}; @@ -27,9 +27,9 @@ struct BincountArgs { bool has_weights; }; -class BincountTask : public CuNumericTask { +class BincountTask : public CuPyNumericTask { public: - static constexpr auto TASK_ID = legate::LocalTaskID{CUNUMERIC_BINCOUNT}; + static constexpr auto TASK_ID = legate::LocalTaskID{CUPYNUMERIC_BINCOUNT}; public: static void cpu_variant(legate::TaskContext context); @@ -41,4 +41,4 @@ class BincountTask : public CuNumericTask { #endif }; -} // namespace cunumeric +} // namespace cupynumeric diff --git a/src/cunumeric/stat/bincount_omp.cc b/src/cupynumeric/stat/bincount_omp.cc similarity index 96% rename from src/cunumeric/stat/bincount_omp.cc rename to src/cupynumeric/stat/bincount_omp.cc index a0fe21817b..c3cb67ee4a 100644 --- a/src/cunumeric/stat/bincount_omp.cc +++ b/src/cupynumeric/stat/bincount_omp.cc @@ -14,12 +14,12 @@ * */ -#include "cunumeric/stat/bincount.h" -#include "cunumeric/stat/bincount_template.inl" +#include "cupynumeric/stat/bincount.h" +#include "cupynumeric/stat/bincount_template.inl" #include -namespace cunumeric { +namespace cupynumeric { using namespace legate; @@ -111,4 +111,4 @@ struct BincountImplBody { bincount_template(context); } -} // namespace cunumeric +} // namespace cupynumeric diff --git a/src/cunumeric/stat/bincount_template.inl b/src/cupynumeric/stat/bincount_template.inl similarity index 96% rename from src/cunumeric/stat/bincount_template.inl rename to src/cupynumeric/stat/bincount_template.inl index 02706fa8f4..8606446fe5 100644 --- a/src/cunumeric/stat/bincount_template.inl +++ b/src/cupynumeric/stat/bincount_template.inl @@ -17,9 +17,9 @@ #pragma once // Useful for IDEs -#include "cunumeric/stat/bincount.h" +#include "cupynumeric/stat/bincount.h" -namespace cunumeric { +namespace cupynumeric { using namespace legate; @@ -78,4 +78,4 @@ static void bincount_template(TaskContext& context) type_dispatch(args.rhs.code(), BincountImpl{}, args); } -} // namespace cunumeric +} // namespace cupynumeric diff --git a/src/cunumeric/stat/histogram.cc b/src/cupynumeric/stat/histogram.cc similarity index 89% rename from src/cunumeric/stat/histogram.cc rename to src/cupynumeric/stat/histogram.cc index c8c19fd117..f8191b3d8a 100644 --- a/src/cunumeric/stat/histogram.cc +++ b/src/cupynumeric/stat/histogram.cc @@ -14,11 +14,11 @@ * */ -#include "cunumeric/stat/histogram.h" -#include "cunumeric/stat/histogram_template.inl" +#include "cupynumeric/stat/histogram.h" +#include "cupynumeric/stat/histogram_template.inl" -#include "cunumeric/stat/histogram_cpu.h" -#include "cunumeric/stat/histogram_impl.h" +#include "cupynumeric/stat/histogram_cpu.h" +#include "cupynumeric/stat/histogram_impl.h" #include #include @@ -30,7 +30,7 @@ #include #endif -namespace cunumeric { +namespace cupynumeric { using namespace legate; template @@ -75,4 +75,4 @@ static void __attribute__((constructor)) register_tasks(void) } } // namespace -} // namespace cunumeric +} // namespace cupynumeric diff --git a/src/cunumeric/stat/histogram.cu b/src/cupynumeric/stat/histogram.cu similarity index 86% rename from src/cunumeric/stat/histogram.cu rename to src/cupynumeric/stat/histogram.cu index ece9cca26a..ff8912daad 100644 --- a/src/cunumeric/stat/histogram.cu +++ b/src/cupynumeric/stat/histogram.cu @@ -14,15 +14,15 @@ * */ -#include "cunumeric/stat/histogram.h" -#include "cunumeric/stat/histogram_template.inl" +#include "cupynumeric/stat/histogram.h" +#include "cupynumeric/stat/histogram_template.inl" -#include "cunumeric/cuda_help.h" +#include "cupynumeric/cuda_help.h" -#include "cunumeric/stat/histogram.cuh" -#include "cunumeric/stat/histogram_impl.h" +#include "cupynumeric/stat/histogram.cuh" +#include "cupynumeric/stat/histogram_impl.h" -#include "cunumeric/utilities/thrust_util.h" +#include "cupynumeric/utilities/thrust_util.h" #include @@ -36,7 +36,7 @@ #include #endif -namespace cunumeric { +namespace cupynumeric { template struct HistogramImplBody { @@ -74,4 +74,4 @@ struct HistogramImplBody { histogram_template(context); } -} // namespace cunumeric +} // namespace cupynumeric diff --git a/src/cunumeric/stat/histogram.cuh b/src/cupynumeric/stat/histogram.cuh similarity index 93% rename from src/cunumeric/stat/histogram.cuh rename to src/cupynumeric/stat/histogram.cuh index 1d5ec1b38b..94c63587c7 100644 --- a/src/cunumeric/stat/histogram.cuh +++ b/src/cupynumeric/stat/histogram.cuh @@ -38,11 +38,11 @@ #include #endif -#include "cunumeric/utilities/thrust_util.h" +#include "cupynumeric/utilities/thrust_util.h" -#include "cunumeric/stat/histogram_gen.h" +#include "cupynumeric/stat/histogram_gen.h" -namespace cunumeric { +namespace cupynumeric { namespace detail { // device specialization: @@ -108,8 +108,8 @@ template struct sync_policy_t>> { sync_policy_t() {} - void operator()(cudaStream_t stream) { CUNUMERIC_CHECK_CUDA_STREAM(stream); } + void operator()(cudaStream_t stream) { CUPYNUMERIC_CHECK_CUDA_STREAM(stream); } }; } // namespace detail -} // namespace cunumeric +} // namespace cupynumeric diff --git a/src/cunumeric/stat/histogram.h b/src/cupynumeric/stat/histogram.h similarity index 81% rename from src/cunumeric/stat/histogram.h rename to src/cupynumeric/stat/histogram.h index f2ee746566..5d5bb27ae9 100644 --- a/src/cunumeric/stat/histogram.h +++ b/src/cupynumeric/stat/histogram.h @@ -16,9 +16,9 @@ #pragma once -#include "cunumeric/cunumeric_task.h" +#include "cupynumeric/cupynumeric_task.h" -namespace cunumeric { +namespace cupynumeric { struct HistogramArgs { legate::PhysicalStore result; @@ -27,9 +27,9 @@ struct HistogramArgs { legate::PhysicalStore weights; }; -class HistogramTask : public CuNumericTask { +class HistogramTask : public CuPyNumericTask { public: - static constexpr auto TASK_ID = legate::LocalTaskID{CUNUMERIC_HISTOGRAM}; + static constexpr auto TASK_ID = legate::LocalTaskID{CUPYNUMERIC_HISTOGRAM}; public: static void cpu_variant(legate::TaskContext context); @@ -41,4 +41,4 @@ class HistogramTask : public CuNumericTask { #endif }; -} // namespace cunumeric +} // namespace cupynumeric diff --git a/src/cunumeric/stat/histogram_cpu.h b/src/cupynumeric/stat/histogram_cpu.h similarity index 95% rename from src/cunumeric/stat/histogram_cpu.h rename to src/cupynumeric/stat/histogram_cpu.h index 28677e52a1..c162737d69 100644 --- a/src/cunumeric/stat/histogram_cpu.h +++ b/src/cupynumeric/stat/histogram_cpu.h @@ -30,13 +30,13 @@ #include #if LEGATE_DEFINED(LEGATE_USE_CUDA) and LEGATE_DEFINED(LEGATE_NVCC) -#include "cunumeric/cuda_help.h" +#include "cupynumeric/cuda_help.h" #else #define cudaStream_t std::int32_t #endif -#include "cunumeric/stat/histogram_gen.h" +#include "cupynumeric/stat/histogram_gen.h" -namespace cunumeric { +namespace cupynumeric { namespace detail { // host specialization: @@ -93,4 +93,4 @@ struct sync_policy_t @@ -177,4 +177,4 @@ void histogram_wrapper(exe_policy_t exe_pol, } } // namespace detail -} // namespace cunumeric +} // namespace cupynumeric diff --git a/src/cunumeric/stat/histogram_omp.cc b/src/cupynumeric/stat/histogram_omp.cc similarity index 94% rename from src/cunumeric/stat/histogram_omp.cc rename to src/cupynumeric/stat/histogram_omp.cc index edae0fa769..26078ebbb2 100644 --- a/src/cunumeric/stat/histogram_omp.cc +++ b/src/cupynumeric/stat/histogram_omp.cc @@ -14,14 +14,14 @@ * */ -#include "cunumeric/stat/histogram.h" -#include "cunumeric/stat/histogram_template.inl" +#include "cupynumeric/stat/histogram.h" +#include "cupynumeric/stat/histogram_template.inl" #define _USE_THRUST_ #ifdef _USE_THRUST_ -#include "cunumeric/stat/histogram_cpu.h" -#include "cunumeric/stat/histogram_impl.h" +#include "cupynumeric/stat/histogram_cpu.h" +#include "cupynumeric/stat/histogram_impl.h" #endif #include @@ -30,7 +30,7 @@ #include #include -namespace cunumeric { +namespace cupynumeric { using namespace legate; template @@ -118,4 +118,4 @@ struct HistogramImplBody { histogram_template(context); } -} // namespace cunumeric +} // namespace cupynumeric diff --git a/src/cunumeric/stat/histogram_template.inl b/src/cupynumeric/stat/histogram_template.inl similarity index 96% rename from src/cunumeric/stat/histogram_template.inl rename to src/cupynumeric/stat/histogram_template.inl index 4fc1a69e03..a132acd929 100644 --- a/src/cunumeric/stat/histogram_template.inl +++ b/src/cupynumeric/stat/histogram_template.inl @@ -17,9 +17,9 @@ #pragma once // Useful for IDEs -#include "cunumeric/stat/histogram.h" +#include "cupynumeric/stat/histogram.h" -namespace cunumeric { +namespace cupynumeric { using namespace legate; @@ -75,4 +75,4 @@ static void histogram_template(TaskContext& context) type_dispatch(args.src.code(), HistogramImpl{}, args); } -} // namespace cunumeric +} // namespace cupynumeric diff --git a/src/cunumeric/ternary/where.cc b/src/cupynumeric/ternary/where.cc similarity index 92% rename from src/cunumeric/ternary/where.cc rename to src/cupynumeric/ternary/where.cc index 3ee46c2c35..f8c5cc1fc6 100644 --- a/src/cunumeric/ternary/where.cc +++ b/src/cupynumeric/ternary/where.cc @@ -14,10 +14,10 @@ * */ -#include "cunumeric/ternary/where.h" -#include "cunumeric/ternary/where_template.inl" +#include "cupynumeric/ternary/where.h" +#include "cupynumeric/ternary/where_template.inl" -namespace cunumeric { +namespace cupynumeric { using namespace legate; @@ -62,4 +62,4 @@ namespace // unnamed static void __attribute__((constructor)) register_tasks(void) { WhereTask::register_variants(); } } // namespace -} // namespace cunumeric +} // namespace cupynumeric diff --git a/src/cunumeric/ternary/where.cu b/src/cupynumeric/ternary/where.cu similarity index 92% rename from src/cunumeric/ternary/where.cu rename to src/cupynumeric/ternary/where.cu index c665d40045..b810124b2c 100644 --- a/src/cunumeric/ternary/where.cu +++ b/src/cupynumeric/ternary/where.cu @@ -14,12 +14,12 @@ * */ -#include "cunumeric/ternary/where.h" -#include "cunumeric/ternary/where_template.inl" +#include "cupynumeric/ternary/where.h" +#include "cupynumeric/ternary/where_template.inl" -#include "cunumeric/cuda_help.h" +#include "cupynumeric/cuda_help.h" -namespace cunumeric { +namespace cupynumeric { template static __global__ void __launch_bounds__(THREADS_PER_BLOCK, MIN_CTAS_PER_SM) @@ -71,7 +71,7 @@ struct WhereImplBody { generic_kernel<<>>( volume, out, mask, in1, in2, pitches, rect); } - CUNUMERIC_CHECK_CUDA_STREAM(stream); + CUPYNUMERIC_CHECK_CUDA_STREAM(stream); } }; @@ -80,4 +80,4 @@ struct WhereImplBody { where_template(context); } -} // namespace cunumeric +} // namespace cupynumeric diff --git a/src/cunumeric/ternary/where.h b/src/cupynumeric/ternary/where.h similarity index 82% rename from src/cunumeric/ternary/where.h rename to src/cupynumeric/ternary/where.h index 702c90adea..91bf04ead5 100644 --- a/src/cunumeric/ternary/where.h +++ b/src/cupynumeric/ternary/where.h @@ -16,9 +16,9 @@ #pragma once -#include "cunumeric/cunumeric_task.h" +#include "cupynumeric/cupynumeric_task.h" -namespace cunumeric { +namespace cupynumeric { struct WhereArgs { legate::PhysicalStore out; @@ -27,9 +27,9 @@ struct WhereArgs { legate::PhysicalStore in2; }; -class WhereTask : public CuNumericTask { +class WhereTask : public CuPyNumericTask { public: - static constexpr auto TASK_ID = legate::LocalTaskID{CUNUMERIC_WHERE}; + static constexpr auto TASK_ID = legate::LocalTaskID{CUPYNUMERIC_WHERE}; public: static void cpu_variant(legate::TaskContext context); @@ -41,4 +41,4 @@ class WhereTask : public CuNumericTask { #endif }; -} // namespace cunumeric +} // namespace cupynumeric diff --git a/src/cunumeric/ternary/where_omp.cc b/src/cupynumeric/ternary/where_omp.cc similarity index 92% rename from src/cunumeric/ternary/where_omp.cc rename to src/cupynumeric/ternary/where_omp.cc index 6a8928ee42..e7ff77bc2a 100644 --- a/src/cunumeric/ternary/where_omp.cc +++ b/src/cupynumeric/ternary/where_omp.cc @@ -14,10 +14,10 @@ * */ -#include "cunumeric/ternary/where.h" -#include "cunumeric/ternary/where_template.inl" +#include "cupynumeric/ternary/where.h" +#include "cupynumeric/ternary/where_template.inl" -namespace cunumeric { +namespace cupynumeric { using namespace legate; @@ -59,4 +59,4 @@ struct WhereImplBody { where_template(context); } -} // namespace cunumeric +} // namespace cupynumeric diff --git a/src/cunumeric/ternary/where_template.inl b/src/cupynumeric/ternary/where_template.inl similarity index 94% rename from src/cunumeric/ternary/where_template.inl rename to src/cupynumeric/ternary/where_template.inl index 1e3d2001a0..61d1fda87f 100644 --- a/src/cunumeric/ternary/where_template.inl +++ b/src/cupynumeric/ternary/where_template.inl @@ -17,10 +17,10 @@ #pragma once // Useful for IDEs -#include "cunumeric/ternary/where.h" -#include "cunumeric/pitches.h" +#include "cupynumeric/ternary/where.h" +#include "cupynumeric/pitches.h" -namespace cunumeric { +namespace cupynumeric { using namespace legate; @@ -70,4 +70,4 @@ static void where_template(TaskContext& context) double_dispatch(dim, args.out.code(), WhereImpl{}, args); } -} // namespace cunumeric +} // namespace cupynumeric diff --git a/src/cunumeric/transform/flip.cc b/src/cupynumeric/transform/flip.cc similarity index 91% rename from src/cunumeric/transform/flip.cc rename to src/cupynumeric/transform/flip.cc index 17dd7b0896..e1bc799ee4 100644 --- a/src/cunumeric/transform/flip.cc +++ b/src/cupynumeric/transform/flip.cc @@ -14,10 +14,10 @@ * */ -#include "cunumeric/transform/flip.h" -#include "cunumeric/transform/flip_template.inl" +#include "cupynumeric/transform/flip.h" +#include "cupynumeric/transform/flip_template.inl" -namespace cunumeric { +namespace cupynumeric { using namespace legate; @@ -52,4 +52,4 @@ namespace // unnamed static void __attribute__((constructor)) register_tasks(void) { FlipTask::register_variants(); } } // namespace -} // namespace cunumeric +} // namespace cupynumeric diff --git a/src/cunumeric/transform/flip.cu b/src/cupynumeric/transform/flip.cu similarity index 91% rename from src/cunumeric/transform/flip.cu rename to src/cupynumeric/transform/flip.cu index ab7bb0111e..a8eee9b87a 100644 --- a/src/cunumeric/transform/flip.cu +++ b/src/cupynumeric/transform/flip.cu @@ -14,12 +14,12 @@ * */ -#include "cunumeric/transform/flip.h" -#include "cunumeric/transform/flip_template.inl" +#include "cupynumeric/transform/flip.h" +#include "cupynumeric/transform/flip_template.inl" -#include "cunumeric/cuda_help.h" +#include "cupynumeric/cuda_help.h" -namespace cunumeric { +namespace cupynumeric { using namespace legate; @@ -66,7 +66,7 @@ struct FlipImplBody { auto stream = get_cached_stream(); flip_kernel<<>>( volume, out, in, pitches, rect, gpu_axes, num_axes); - CUNUMERIC_CHECK_CUDA_STREAM(stream); + CUPYNUMERIC_CHECK_CUDA_STREAM(stream); } }; @@ -75,4 +75,4 @@ struct FlipImplBody { flip_template(context); } -} // namespace cunumeric +} // namespace cupynumeric diff --git a/src/cunumeric/transform/flip.h b/src/cupynumeric/transform/flip.h similarity index 82% rename from src/cunumeric/transform/flip.h rename to src/cupynumeric/transform/flip.h index 18b6ec1128..56a637099c 100644 --- a/src/cunumeric/transform/flip.h +++ b/src/cupynumeric/transform/flip.h @@ -16,9 +16,9 @@ #pragma once -#include "cunumeric/cunumeric_task.h" +#include "cupynumeric/cupynumeric_task.h" -namespace cunumeric { +namespace cupynumeric { struct FlipArgs { legate::PhysicalStore in; @@ -26,9 +26,9 @@ struct FlipArgs { legate::Span axes; }; -class FlipTask : public CuNumericTask { +class FlipTask : public CuPyNumericTask { public: - static constexpr auto TASK_ID = legate::LocalTaskID{CUNUMERIC_FLIP}; + static constexpr auto TASK_ID = legate::LocalTaskID{CUPYNUMERIC_FLIP}; public: static void cpu_variant(legate::TaskContext context); @@ -40,4 +40,4 @@ class FlipTask : public CuNumericTask { #endif }; -} // namespace cunumeric +} // namespace cupynumeric diff --git a/src/cunumeric/transform/flip_omp.cc b/src/cupynumeric/transform/flip_omp.cc similarity index 91% rename from src/cunumeric/transform/flip_omp.cc rename to src/cupynumeric/transform/flip_omp.cc index eb7e640136..fce3b1d7a6 100644 --- a/src/cunumeric/transform/flip_omp.cc +++ b/src/cupynumeric/transform/flip_omp.cc @@ -14,10 +14,10 @@ * */ -#include "cunumeric/transform/flip.h" -#include "cunumeric/transform/flip_template.inl" +#include "cupynumeric/transform/flip.h" +#include "cupynumeric/transform/flip_template.inl" -namespace cunumeric { +namespace cupynumeric { using namespace legate; @@ -50,4 +50,4 @@ struct FlipImplBody { flip_template(context); } -} // namespace cunumeric +} // namespace cupynumeric diff --git a/src/cunumeric/transform/flip_template.inl b/src/cupynumeric/transform/flip_template.inl similarity index 92% rename from src/cunumeric/transform/flip_template.inl rename to src/cupynumeric/transform/flip_template.inl index b4956622b4..d74ec8a95a 100644 --- a/src/cunumeric/transform/flip_template.inl +++ b/src/cupynumeric/transform/flip_template.inl @@ -17,10 +17,10 @@ #pragma once // Useful for IDEs -#include "cunumeric/transform/flip.h" -#include "cunumeric/pitches.h" +#include "cupynumeric/transform/flip.h" +#include "cupynumeric/pitches.h" -namespace cunumeric { +namespace cupynumeric { using namespace legate; @@ -57,4 +57,4 @@ static void flip_template(TaskContext& context) double_dispatch(args.in.dim(), args.in.code(), FlipImpl{}, args); } -} // namespace cunumeric +} // namespace cupynumeric diff --git a/src/cunumeric/typedefs.h b/src/cupynumeric/typedefs.h similarity index 93% rename from src/cunumeric/typedefs.h rename to src/cupynumeric/typedefs.h index 840cf875aa..ad9ea1229f 100644 --- a/src/cunumeric/typedefs.h +++ b/src/cupynumeric/typedefs.h @@ -20,9 +20,9 @@ #include "legate.h" -namespace cunumeric { +namespace cupynumeric { using Array = legate::PhysicalStore; using Scalar = legate::Scalar; -} // namespace cunumeric +} // namespace cupynumeric diff --git a/src/cunumeric/unary/convert.cc b/src/cupynumeric/unary/convert.cc similarity index 92% rename from src/cunumeric/unary/convert.cc rename to src/cupynumeric/unary/convert.cc index 78ece02ebb..7594d2f871 100644 --- a/src/cunumeric/unary/convert.cc +++ b/src/cupynumeric/unary/convert.cc @@ -14,10 +14,10 @@ * */ -#include "cunumeric/unary/convert.h" -#include "cunumeric/unary/convert_template.inl" +#include "cupynumeric/unary/convert.h" +#include "cupynumeric/unary/convert_template.inl" -namespace cunumeric { +namespace cupynumeric { using namespace legate; @@ -60,4 +60,4 @@ namespace // unnamed static void __attribute__((constructor)) register_tasks(void) { ConvertTask::register_variants(); } } // namespace -} // namespace cunumeric +} // namespace cupynumeric diff --git a/src/cunumeric/unary/convert.cu b/src/cupynumeric/unary/convert.cu similarity index 91% rename from src/cunumeric/unary/convert.cu rename to src/cupynumeric/unary/convert.cu index 564473a4e0..903ffc929b 100644 --- a/src/cunumeric/unary/convert.cu +++ b/src/cupynumeric/unary/convert.cu @@ -14,12 +14,12 @@ * */ -#include "cunumeric/unary/convert.h" -#include "cunumeric/unary/convert_template.inl" +#include "cupynumeric/unary/convert.h" +#include "cupynumeric/unary/convert_template.inl" -#include "cunumeric/cuda_help.h" +#include "cupynumeric/cuda_help.h" -namespace cunumeric { +namespace cupynumeric { template static __global__ void __launch_bounds__(THREADS_PER_BLOCK, MIN_CTAS_PER_SM) @@ -68,7 +68,7 @@ struct ConvertImplBody { generic_kernel<<>>( volume, func, out, in, pitches, rect); } - CUNUMERIC_CHECK_CUDA_STREAM(stream); + CUPYNUMERIC_CHECK_CUDA_STREAM(stream); } }; @@ -77,4 +77,4 @@ struct ConvertImplBody { convert_template(context); } -} // namespace cunumeric +} // namespace cupynumeric diff --git a/src/cunumeric/unary/convert.h b/src/cupynumeric/unary/convert.h similarity index 78% rename from src/cunumeric/unary/convert.h rename to src/cupynumeric/unary/convert.h index 3d29d0bf7c..9d771dde9e 100644 --- a/src/cunumeric/unary/convert.h +++ b/src/cupynumeric/unary/convert.h @@ -16,10 +16,10 @@ #pragma once -#include "cunumeric/cunumeric_task.h" -#include "cunumeric/unary/convert_util.h" +#include "cupynumeric/cupynumeric_task.h" +#include "cupynumeric/unary/convert_util.h" -namespace cunumeric { +namespace cupynumeric { struct ConvertArgs { legate::PhysicalStore out; @@ -27,9 +27,9 @@ struct ConvertArgs { ConvertCode nan_op; }; -class ConvertTask : public CuNumericTask { +class ConvertTask : public CuPyNumericTask { public: - static constexpr auto TASK_ID = legate::LocalTaskID{CUNUMERIC_CONVERT}; + static constexpr auto TASK_ID = legate::LocalTaskID{CUPYNUMERIC_CONVERT}; public: static void cpu_variant(legate::TaskContext context); @@ -41,4 +41,4 @@ class ConvertTask : public CuNumericTask { #endif }; -} // namespace cunumeric +} // namespace cupynumeric diff --git a/src/cunumeric/unary/convert_omp.cc b/src/cupynumeric/unary/convert_omp.cc similarity index 92% rename from src/cunumeric/unary/convert_omp.cc rename to src/cupynumeric/unary/convert_omp.cc index d0823daf34..6273e99f28 100644 --- a/src/cunumeric/unary/convert_omp.cc +++ b/src/cupynumeric/unary/convert_omp.cc @@ -14,10 +14,10 @@ * */ -#include "cunumeric/unary/convert.h" -#include "cunumeric/unary/convert_template.inl" +#include "cupynumeric/unary/convert.h" +#include "cupynumeric/unary/convert_template.inl" -namespace cunumeric { +namespace cupynumeric { using namespace legate; @@ -57,4 +57,4 @@ struct ConvertImplBody { convert_template(context); } -} // namespace cunumeric +} // namespace cupynumeric diff --git a/src/cunumeric/unary/convert_template.inl b/src/cupynumeric/unary/convert_template.inl similarity index 95% rename from src/cunumeric/unary/convert_template.inl rename to src/cupynumeric/unary/convert_template.inl index d78f13b4db..c252112050 100644 --- a/src/cunumeric/unary/convert_template.inl +++ b/src/cupynumeric/unary/convert_template.inl @@ -17,11 +17,11 @@ #pragma once // Useful for IDEs -#include "cunumeric/unary/convert.h" -#include "cunumeric/pitches.h" -#include "cunumeric/unary/convert_util.h" +#include "cupynumeric/unary/convert.h" +#include "cupynumeric/pitches.h" +#include "cupynumeric/unary/convert_util.h" -namespace cunumeric { +namespace cupynumeric { using namespace legate; @@ -106,4 +106,4 @@ static void convert_template(TaskContext& context) type_dispatch(args.in.code(), SourceTypeDispatch{}, args); } -} // namespace cunumeric +} // namespace cupynumeric diff --git a/src/cunumeric/unary/convert_util.h b/src/cupynumeric/unary/convert_util.h similarity index 81% rename from src/cunumeric/unary/convert_util.h rename to src/cupynumeric/unary/convert_util.h index 08951f6b12..ef2a1ce534 100644 --- a/src/cunumeric/unary/convert_util.h +++ b/src/cupynumeric/unary/convert_util.h @@ -16,15 +16,15 @@ #pragma once -#include "cunumeric/cunumeric_task.h" -#include "cunumeric/unary/isnan.h" +#include "cupynumeric/cupynumeric_task.h" +#include "cupynumeric/unary/isnan.h" -namespace cunumeric { +namespace cupynumeric { enum class ConvertCode : int { - NOOP = CUNUMERIC_CONVERT_NAN_NOOP, - PROD = CUNUMERIC_CONVERT_NAN_PROD, - SUM = CUNUMERIC_CONVERT_NAN_SUM, + NOOP = CUPYNUMERIC_CONVERT_NAN_NOOP, + PROD = CUPYNUMERIC_CONVERT_NAN_PROD, + SUM = CUPYNUMERIC_CONVERT_NAN_SUM, }; template @@ -112,7 +112,7 @@ struct ConvertOp { legate::is_complex_type::value>* = nullptr> constexpr DST operator()(const _SRC& src) const { - return cunumeric::is_nan(src) ? static_cast(1) : static_cast(src); + return cupynumeric::is_nan(src) ? static_cast(1) : static_cast(src); } template { !legate::is_complex_type::value>* = nullptr> constexpr DST operator()(const _SRC& src) const { - return cunumeric::is_nan(src) ? static_cast(1) : static_cast(src.real()); + return cupynumeric::is_nan(src) ? static_cast(1) : static_cast(src.real()); } }; @@ -131,15 +131,15 @@ struct ConvertOp { template ::value>* = nullptr> __CUDA_HD__ __half operator()(const _SRC& src) const { - return cunumeric::is_nan(src) ? static_cast<__half>(1) - : static_cast<__half>(static_cast(src)); + return cupynumeric::is_nan(src) ? static_cast<__half>(1) + : static_cast<__half>(static_cast(src)); } template ::value>* = nullptr> __CUDA_HD__ __half operator()(const _SRC& src) const { - return cunumeric::is_nan(src) ? static_cast<__half>(1) - : static_cast<__half>(static_cast(src.real())); + return cupynumeric::is_nan(src) ? static_cast<__half>(1) + : static_cast<__half>(static_cast(src.real())); } }; @@ -149,8 +149,8 @@ struct ConvertOp { constexpr DST operator()(const __half& src) const { - return cunumeric::is_nan(src) ? static_cast(1) - : static_cast(static_cast(src)); + return cupynumeric::is_nan(src) ? static_cast(1) + : static_cast(static_cast(src)); } }; @@ -164,7 +164,7 @@ struct ConvertOp { legate::is_complex_type::value>* = nullptr> constexpr DST operator()(const _SRC& src) const { - return cunumeric::is_nan(src) ? static_cast(0) : static_cast(src); + return cupynumeric::is_nan(src) ? static_cast(0) : static_cast(src); } template { !legate::is_complex_type::value>* = nullptr> constexpr DST operator()(const _SRC& src) const { - return cunumeric::is_nan(src) ? static_cast(0) : static_cast(src.real()); + return cupynumeric::is_nan(src) ? static_cast(0) : static_cast(src.real()); } }; @@ -183,15 +183,15 @@ struct ConvertOp { template ::value>* = nullptr> __CUDA_HD__ __half operator()(const _SRC& src) const { - return cunumeric::is_nan(src) ? static_cast<__half>(0) - : static_cast<__half>(static_cast(src)); + return cupynumeric::is_nan(src) ? static_cast<__half>(0) + : static_cast<__half>(static_cast(src)); } template ::value>* = nullptr> __CUDA_HD__ __half operator()(const _SRC& src) const { - return cunumeric::is_nan(src) ? static_cast<__half>(0) - : static_cast<__half>(static_cast(src.real())); + return cupynumeric::is_nan(src) ? static_cast<__half>(0) + : static_cast<__half>(static_cast(src.real())); } }; @@ -201,9 +201,9 @@ struct ConvertOp { constexpr DST operator()(const __half& src) const { - return cunumeric::is_nan(src) ? static_cast(0) - : static_cast(static_cast(src)); + return cupynumeric::is_nan(src) ? static_cast(0) + : static_cast(static_cast(src)); } }; -} // namespace cunumeric +} // namespace cupynumeric diff --git a/src/cunumeric/unary/isnan.h b/src/cupynumeric/unary/isnan.h similarity index 95% rename from src/cunumeric/unary/isnan.h rename to src/cupynumeric/unary/isnan.h index 1f3dacd253..9808b55153 100644 --- a/src/cunumeric/unary/isnan.h +++ b/src/cupynumeric/unary/isnan.h @@ -18,7 +18,7 @@ #include "legate/utilities/typedefs.h" -namespace cunumeric { +namespace cupynumeric { template ::value>* = nullptr> constexpr bool is_nan(const T& x) @@ -44,4 +44,4 @@ __CUDA_HD__ inline bool is_nan(const __half& x) return isnan(x); } -} // namespace cunumeric +} // namespace cupynumeric diff --git a/src/cunumeric/unary/scalar_unary_red.cc b/src/cupynumeric/unary/scalar_unary_red.cc similarity index 84% rename from src/cunumeric/unary/scalar_unary_red.cc rename to src/cupynumeric/unary/scalar_unary_red.cc index 94640035af..a0ae42a081 100644 --- a/src/cunumeric/unary/scalar_unary_red.cc +++ b/src/cupynumeric/unary/scalar_unary_red.cc @@ -14,10 +14,10 @@ * */ -#include "cunumeric/unary/scalar_unary_red.h" -#include "cunumeric/unary/scalar_unary_red_template.inl" +#include "cupynumeric/unary/scalar_unary_red.h" +#include "cupynumeric/unary/scalar_unary_red_template.inl" -namespace cunumeric { +namespace cupynumeric { /*static*/ void ScalarUnaryRedTask::cpu_variant(TaskContext context) { @@ -32,4 +32,4 @@ static void __attribute__((constructor)) register_tasks(void) } } // namespace -} // namespace cunumeric +} // namespace cupynumeric diff --git a/src/cunumeric/unary/scalar_unary_red.cu b/src/cupynumeric/unary/scalar_unary_red.cu similarity index 73% rename from src/cunumeric/unary/scalar_unary_red.cu rename to src/cupynumeric/unary/scalar_unary_red.cu index 71521be73c..0dfc12d3ab 100644 --- a/src/cunumeric/unary/scalar_unary_red.cu +++ b/src/cupynumeric/unary/scalar_unary_red.cu @@ -14,12 +14,12 @@ * */ -#include "cunumeric/cunumeric_task.h" -#include "cunumeric/unary/scalar_unary_red.h" -#include "cunumeric/unary/scalar_unary_red_template.inl" -#include "cunumeric/execution_policy/reduction/scalar_reduction.cuh" +#include "cupynumeric/cupynumeric_task.h" +#include "cupynumeric/unary/scalar_unary_red.h" +#include "cupynumeric/unary/scalar_unary_red_template.inl" +#include "cupynumeric/execution_policy/reduction/scalar_reduction.cuh" -namespace cunumeric { +namespace cupynumeric { using namespace legate; @@ -28,4 +28,4 @@ using namespace legate; scalar_unary_red_template(context); } -} // namespace cunumeric +} // namespace cupynumeric diff --git a/src/cunumeric/unary/scalar_unary_red.h b/src/cupynumeric/unary/scalar_unary_red.h similarity index 79% rename from src/cunumeric/unary/scalar_unary_red.h rename to src/cupynumeric/unary/scalar_unary_red.h index 0941896ab5..37f1148f7a 100644 --- a/src/cunumeric/unary/scalar_unary_red.h +++ b/src/cupynumeric/unary/scalar_unary_red.h @@ -16,10 +16,10 @@ #pragma once -#include "cunumeric/cunumeric_task.h" -#include "cunumeric/unary/unary_red_util.h" +#include "cupynumeric/cupynumeric_task.h" +#include "cupynumeric/unary/unary_red_util.h" -namespace cunumeric { +namespace cupynumeric { struct ScalarUnaryRedArgs { legate::PhysicalStore out; @@ -31,9 +31,9 @@ struct ScalarUnaryRedArgs { }; // Unary reduction task that produces scalar results -class ScalarUnaryRedTask : public CuNumericTask { +class ScalarUnaryRedTask : public CuPyNumericTask { public: - static constexpr auto TASK_ID = legate::LocalTaskID{CUNUMERIC_SCALAR_UNARY_RED}; + static constexpr auto TASK_ID = legate::LocalTaskID{CUPYNUMERIC_SCALAR_UNARY_RED}; public: static void cpu_variant(legate::TaskContext context); @@ -45,4 +45,4 @@ class ScalarUnaryRedTask : public CuNumericTask { #endif }; -} // namespace cunumeric +} // namespace cupynumeric diff --git a/src/cunumeric/unary/scalar_unary_red_omp.cc b/src/cupynumeric/unary/scalar_unary_red_omp.cc similarity index 76% rename from src/cunumeric/unary/scalar_unary_red_omp.cc rename to src/cupynumeric/unary/scalar_unary_red_omp.cc index 1bba055b3e..f711580ab2 100644 --- a/src/cunumeric/unary/scalar_unary_red_omp.cc +++ b/src/cupynumeric/unary/scalar_unary_red_omp.cc @@ -14,15 +14,15 @@ * */ -#include "cunumeric/unary/scalar_unary_red.h" -#include "cunumeric/unary/scalar_unary_red_template.inl" -#include "cunumeric/execution_policy/reduction/scalar_reduction_omp.h" +#include "cupynumeric/unary/scalar_unary_red.h" +#include "cupynumeric/unary/scalar_unary_red_template.inl" +#include "cupynumeric/execution_policy/reduction/scalar_reduction_omp.h" -namespace cunumeric { +namespace cupynumeric { /*static*/ void ScalarUnaryRedTask::omp_variant(TaskContext context) { scalar_unary_red_template(context); } -} // namespace cunumeric +} // namespace cupynumeric diff --git a/src/cunumeric/unary/scalar_unary_red_template.inl b/src/cupynumeric/unary/scalar_unary_red_template.inl similarity index 95% rename from src/cunumeric/unary/scalar_unary_red_template.inl rename to src/cupynumeric/unary/scalar_unary_red_template.inl index 762b10ddfe..20d20bb50d 100644 --- a/src/cunumeric/unary/scalar_unary_red_template.inl +++ b/src/cupynumeric/unary/scalar_unary_red_template.inl @@ -18,13 +18,13 @@ // Useful for IDEs #include -#include "cunumeric/cunumeric_task.h" -#include "cunumeric/unary/scalar_unary_red.h" -#include "cunumeric/unary/unary_red_util.h" -#include "cunumeric/pitches.h" -#include "cunumeric/execution_policy/reduction/scalar_reduction.h" +#include "cupynumeric/cupynumeric_task.h" +#include "cupynumeric/unary/scalar_unary_red.h" +#include "cupynumeric/unary/unary_red_util.h" +#include "cupynumeric/pitches.h" +#include "cupynumeric/execution_policy/reduction/scalar_reduction.h" -namespace cunumeric { +namespace cupynumeric { using namespace legate; @@ -219,4 +219,4 @@ static void scalar_unary_red_template(TaskContext& context) op_dispatch(args.op_code, ScalarUnaryRedDispatch{}, args, has_where); } -} // namespace cunumeric +} // namespace cupynumeric diff --git a/src/cunumeric/unary/unary_op.cc b/src/cupynumeric/unary/unary_op.cc similarity index 96% rename from src/cunumeric/unary/unary_op.cc rename to src/cupynumeric/unary/unary_op.cc index 547e75ec08..f307801b63 100644 --- a/src/cunumeric/unary/unary_op.cc +++ b/src/cupynumeric/unary/unary_op.cc @@ -14,10 +14,10 @@ * */ -#include "cunumeric/unary/unary_op.h" -#include "cunumeric/unary/unary_op_template.inl" +#include "cupynumeric/unary/unary_op.h" +#include "cupynumeric/unary/unary_op_template.inl" -namespace cunumeric { +namespace cupynumeric { using namespace legate; @@ -116,4 +116,4 @@ namespace // unnamed static void __attribute__((constructor)) register_tasks(void) { UnaryOpTask::register_variants(); } } // namespace -} // namespace cunumeric +} // namespace cupynumeric diff --git a/src/cunumeric/unary/unary_op.cu b/src/cupynumeric/unary/unary_op.cu similarity index 95% rename from src/cunumeric/unary/unary_op.cu rename to src/cupynumeric/unary/unary_op.cu index 88838f958b..31b2eb3b5b 100644 --- a/src/cunumeric/unary/unary_op.cu +++ b/src/cupynumeric/unary/unary_op.cu @@ -14,12 +14,12 @@ * */ -#include "cunumeric/unary/unary_op.h" -#include "cunumeric/unary/unary_op_template.inl" +#include "cupynumeric/unary/unary_op.h" +#include "cupynumeric/unary/unary_op_template.inl" -#include "cunumeric/cuda_help.h" +#include "cupynumeric/cuda_help.h" -namespace cunumeric { +namespace cupynumeric { template static __global__ void __launch_bounds__(THREADS_PER_BLOCK, MIN_CTAS_PER_SM) @@ -95,7 +95,7 @@ struct UnaryOpImplBody { generic_kernel<<>>( volume, func, out, in, pitches, rect); } - CUNUMERIC_CHECK_CUDA_STREAM(stream); + CUPYNUMERIC_CHECK_CUDA_STREAM(stream); } }; @@ -117,7 +117,7 @@ struct PointCopyImplBody { } else { generic_copy_kernel<<>>(volume, out, in, pitches, rect); } - CUNUMERIC_CHECK_CUDA_STREAM(stream); + CUPYNUMERIC_CHECK_CUDA_STREAM(stream); } }; @@ -183,7 +183,7 @@ struct MultiOutUnaryOpImplBody { generic_kernel_multiout<<>>( volume, func, lhs, rhs1, rhs2, pitches, rect); } - CUNUMERIC_CHECK_CUDA_STREAM(stream); + CUPYNUMERIC_CHECK_CUDA_STREAM(stream); } }; @@ -192,4 +192,4 @@ struct MultiOutUnaryOpImplBody { unary_op_template(context); } -} // namespace cunumeric +} // namespace cupynumeric diff --git a/src/cunumeric/unary/unary_op.h b/src/cupynumeric/unary/unary_op.h similarity index 72% rename from src/cunumeric/unary/unary_op.h rename to src/cupynumeric/unary/unary_op.h index 973ed916bb..357062293f 100644 --- a/src/cunumeric/unary/unary_op.h +++ b/src/cupynumeric/unary/unary_op.h @@ -16,10 +16,10 @@ #pragma once -#include "cunumeric/cunumeric_task.h" -#include "cunumeric/unary/unary_op_util.h" +#include "cupynumeric/cupynumeric_task.h" +#include "cupynumeric/unary/unary_op_util.h" -namespace cunumeric { +namespace cupynumeric { struct UnaryOpArgs { legate::PhysicalStore in; @@ -35,9 +35,9 @@ struct MultiOutUnaryOpArgs { UnaryOpCode op_code; }; -class UnaryOpTask : public CuNumericTask { +class UnaryOpTask : public CuPyNumericTask { public: - static constexpr auto TASK_ID = legate::LocalTaskID{CUNUMERIC_UNARY_OP}; + static constexpr auto TASK_ID = legate::LocalTaskID{CUPYNUMERIC_UNARY_OP}; public: static void cpu_variant(legate::TaskContext context); @@ -112,52 +112,52 @@ constexpr decltype(auto) double_dispatch(int dim, int point_dim, Functor f, Fnar switch (dim) { #if LEGATE_MAX_DIM >= 1 case 1: { - return cunumeric::inner_type_dispatch_fn<1>{}(point_dim, f, std::forward(args)...); + return cupynumeric::inner_type_dispatch_fn<1>{}(point_dim, f, std::forward(args)...); } #endif #if LEGATE_MAX_DIM >= 2 case 2: { - return cunumeric::inner_type_dispatch_fn<2>{}(point_dim, f, std::forward(args)...); + return cupynumeric::inner_type_dispatch_fn<2>{}(point_dim, f, std::forward(args)...); } #endif #if LEGATE_MAX_DIM >= 3 case 3: { - return cunumeric::inner_type_dispatch_fn<3>{}(point_dim, f, std::forward(args)...); + return cupynumeric::inner_type_dispatch_fn<3>{}(point_dim, f, std::forward(args)...); } #endif #if LEGATE_MAX_DIM >= 4 case 4: { - return cunumeric::inner_type_dispatch_fn<4>{}(point_dim, f, std::forward(args)...); + return cupynumeric::inner_type_dispatch_fn<4>{}(point_dim, f, std::forward(args)...); } #endif #if LEGATE_MAX_DIM >= 5 case 5: { - return cunumeric::inner_type_dispatch_fn<5>{}(point_dim, f, std::forward(args)...); + return cupynumeric::inner_type_dispatch_fn<5>{}(point_dim, f, std::forward(args)...); } #endif #if LEGATE_MAX_DIM >= 6 case 6: { - return cunumeric::inner_type_dispatch_fn<6>{}(point_dim, f, std::forward(args)...); + return cupynumeric::inner_type_dispatch_fn<6>{}(point_dim, f, std::forward(args)...); } #endif #if LEGATE_MAX_DIM >= 7 case 7: { - return cunumeric::inner_type_dispatch_fn<7>{}(point_dim, f, std::forward(args)...); + return cupynumeric::inner_type_dispatch_fn<7>{}(point_dim, f, std::forward(args)...); } #endif #if LEGATE_MAX_DIM >= 8 case 8: { - return cunumeric::inner_type_dispatch_fn<8>{}(point_dim, f, std::forward(args)...); + return cupynumeric::inner_type_dispatch_fn<8>{}(point_dim, f, std::forward(args)...); } #endif #if LEGATE_MAX_DIM >= 9 case 9: { - return cunumeric::inner_type_dispatch_fn<9>{}(point_dim, f, std::forward(args)...); + return cupynumeric::inner_type_dispatch_fn<9>{}(point_dim, f, std::forward(args)...); } #endif } assert(false); - return cunumeric::inner_type_dispatch_fn<1>{}(point_dim, f, std::forward(args)...); + return cupynumeric::inner_type_dispatch_fn<1>{}(point_dim, f, std::forward(args)...); } -} // namespace cunumeric +} // namespace cupynumeric diff --git a/src/cunumeric/unary/unary_op_omp.cc b/src/cupynumeric/unary/unary_op_omp.cc similarity index 96% rename from src/cunumeric/unary/unary_op_omp.cc rename to src/cupynumeric/unary/unary_op_omp.cc index fad475fce9..4c42344cd7 100644 --- a/src/cunumeric/unary/unary_op_omp.cc +++ b/src/cupynumeric/unary/unary_op_omp.cc @@ -14,10 +14,10 @@ * */ -#include "cunumeric/unary/unary_op.h" -#include "cunumeric/unary/unary_op_template.inl" +#include "cupynumeric/unary/unary_op.h" +#include "cupynumeric/unary/unary_op_template.inl" -namespace cunumeric { +namespace cupynumeric { using namespace legate; @@ -117,4 +117,4 @@ struct MultiOutUnaryOpImplBody { unary_op_template(context); } -} // namespace cunumeric +} // namespace cupynumeric diff --git a/src/cunumeric/unary/unary_op_template.inl b/src/cupynumeric/unary/unary_op_template.inl similarity index 96% rename from src/cunumeric/unary/unary_op_template.inl rename to src/cupynumeric/unary/unary_op_template.inl index 3642f0b8b0..0df142530c 100644 --- a/src/cunumeric/unary/unary_op_template.inl +++ b/src/cupynumeric/unary/unary_op_template.inl @@ -17,10 +17,10 @@ #pragma once // Useful for IDEs -#include "cunumeric/unary/unary_op.h" -#include "cunumeric/pitches.h" +#include "cupynumeric/unary/unary_op.h" +#include "cupynumeric/pitches.h" -namespace cunumeric { +namespace cupynumeric { using namespace legate; @@ -172,7 +172,7 @@ struct UnaryOpDispatch { auto dim = std::max(args.in.dim(), 1); if ((OP_CODE == UnaryOpCode::COPY) && (args.in.code() == Type::Code::FIXED_ARRAY)) { auto type = args.in.type().as_fixed_array_type(); - cunumeric::double_dispatch(dim, type.num_elements(), UnaryCopyImpl{}, args); + cupynumeric::double_dispatch(dim, type.num_elements(), UnaryCopyImpl{}, args); } else { auto code = OP_CODE == UnaryOpCode::GETARG ? args.out.code() : args.in.code(); legate::double_dispatch(dim, code, UnaryOpImpl{}, args); @@ -215,4 +215,4 @@ static void unary_op_template(TaskContext& context) } } -} // namespace cunumeric +} // namespace cupynumeric diff --git a/src/cunumeric/unary/unary_op_util.h b/src/cupynumeric/unary/unary_op_util.h similarity index 94% rename from src/cunumeric/unary/unary_op_util.h rename to src/cupynumeric/unary/unary_op_util.h index 4bcd6ec8f6..bb592af857 100644 --- a/src/cunumeric/unary/unary_op_util.h +++ b/src/cupynumeric/unary/unary_op_util.h @@ -16,9 +16,9 @@ #pragma once -#include "cunumeric/cunumeric_task.h" -#include "cunumeric/arg.h" -#include "cunumeric/arg.inl" +#include "cupynumeric/cupynumeric_task.h" +#include "cupynumeric/arg.h" +#include "cupynumeric/arg.inl" #ifdef __NVCC__ #include "thrust/complex.h" @@ -29,58 +29,58 @@ #include #include -namespace cunumeric { +namespace cupynumeric { enum class UnaryOpCode : int { - ABSOLUTE = CUNUMERIC_UOP_ABSOLUTE, - ANGLE = CUNUMERIC_UOP_ANGLE, - ARCCOS = CUNUMERIC_UOP_ARCCOS, - ARCCOSH = CUNUMERIC_UOP_ARCCOSH, - ARCSIN = CUNUMERIC_UOP_ARCSIN, - ARCSINH = CUNUMERIC_UOP_ARCSINH, - ARCTAN = CUNUMERIC_UOP_ARCTAN, - ARCTANH = CUNUMERIC_UOP_ARCTANH, - CBRT = CUNUMERIC_UOP_CBRT, - CEIL = CUNUMERIC_UOP_CEIL, - CLIP = CUNUMERIC_UOP_CLIP, - CONJ = CUNUMERIC_UOP_CONJ, - COPY = CUNUMERIC_UOP_COPY, - COS = CUNUMERIC_UOP_COS, - COSH = CUNUMERIC_UOP_COSH, - DEG2RAD = CUNUMERIC_UOP_DEG2RAD, - EXP = CUNUMERIC_UOP_EXP, - EXP2 = CUNUMERIC_UOP_EXP2, - EXPM1 = CUNUMERIC_UOP_EXPM1, - FLOOR = CUNUMERIC_UOP_FLOOR, - FREXP = CUNUMERIC_UOP_FREXP, - GETARG = CUNUMERIC_UOP_GETARG, - IMAG = CUNUMERIC_UOP_IMAG, - INVERT = CUNUMERIC_UOP_INVERT, - ISFINITE = CUNUMERIC_UOP_ISFINITE, - ISINF = CUNUMERIC_UOP_ISINF, - ISNAN = CUNUMERIC_UOP_ISNAN, - LOG = CUNUMERIC_UOP_LOG, - LOG10 = CUNUMERIC_UOP_LOG10, - LOG1P = CUNUMERIC_UOP_LOG1P, - LOG2 = CUNUMERIC_UOP_LOG2, - LOGICAL_NOT = CUNUMERIC_UOP_LOGICAL_NOT, - MODF = CUNUMERIC_UOP_MODF, - NEGATIVE = CUNUMERIC_UOP_NEGATIVE, - POSITIVE = CUNUMERIC_UOP_POSITIVE, - RAD2DEG = CUNUMERIC_UOP_RAD2DEG, - REAL = CUNUMERIC_UOP_REAL, - RECIPROCAL = CUNUMERIC_UOP_RECIPROCAL, - RINT = CUNUMERIC_UOP_RINT, - ROUND = CUNUMERIC_UOP_ROUND, - SIGN = CUNUMERIC_UOP_SIGN, - SIGNBIT = CUNUMERIC_UOP_SIGNBIT, - SIN = CUNUMERIC_UOP_SIN, - SINH = CUNUMERIC_UOP_SINH, - SQRT = CUNUMERIC_UOP_SQRT, - SQUARE = CUNUMERIC_UOP_SQUARE, - TAN = CUNUMERIC_UOP_TAN, - TANH = CUNUMERIC_UOP_TANH, - TRUNC = CUNUMERIC_UOP_TRUNC, + ABSOLUTE = CUPYNUMERIC_UOP_ABSOLUTE, + ANGLE = CUPYNUMERIC_UOP_ANGLE, + ARCCOS = CUPYNUMERIC_UOP_ARCCOS, + ARCCOSH = CUPYNUMERIC_UOP_ARCCOSH, + ARCSIN = CUPYNUMERIC_UOP_ARCSIN, + ARCSINH = CUPYNUMERIC_UOP_ARCSINH, + ARCTAN = CUPYNUMERIC_UOP_ARCTAN, + ARCTANH = CUPYNUMERIC_UOP_ARCTANH, + CBRT = CUPYNUMERIC_UOP_CBRT, + CEIL = CUPYNUMERIC_UOP_CEIL, + CLIP = CUPYNUMERIC_UOP_CLIP, + CONJ = CUPYNUMERIC_UOP_CONJ, + COPY = CUPYNUMERIC_UOP_COPY, + COS = CUPYNUMERIC_UOP_COS, + COSH = CUPYNUMERIC_UOP_COSH, + DEG2RAD = CUPYNUMERIC_UOP_DEG2RAD, + EXP = CUPYNUMERIC_UOP_EXP, + EXP2 = CUPYNUMERIC_UOP_EXP2, + EXPM1 = CUPYNUMERIC_UOP_EXPM1, + FLOOR = CUPYNUMERIC_UOP_FLOOR, + FREXP = CUPYNUMERIC_UOP_FREXP, + GETARG = CUPYNUMERIC_UOP_GETARG, + IMAG = CUPYNUMERIC_UOP_IMAG, + INVERT = CUPYNUMERIC_UOP_INVERT, + ISFINITE = CUPYNUMERIC_UOP_ISFINITE, + ISINF = CUPYNUMERIC_UOP_ISINF, + ISNAN = CUPYNUMERIC_UOP_ISNAN, + LOG = CUPYNUMERIC_UOP_LOG, + LOG10 = CUPYNUMERIC_UOP_LOG10, + LOG1P = CUPYNUMERIC_UOP_LOG1P, + LOG2 = CUPYNUMERIC_UOP_LOG2, + LOGICAL_NOT = CUPYNUMERIC_UOP_LOGICAL_NOT, + MODF = CUPYNUMERIC_UOP_MODF, + NEGATIVE = CUPYNUMERIC_UOP_NEGATIVE, + POSITIVE = CUPYNUMERIC_UOP_POSITIVE, + RAD2DEG = CUPYNUMERIC_UOP_RAD2DEG, + REAL = CUPYNUMERIC_UOP_REAL, + RECIPROCAL = CUPYNUMERIC_UOP_RECIPROCAL, + RINT = CUPYNUMERIC_UOP_RINT, + ROUND = CUPYNUMERIC_UOP_ROUND, + SIGN = CUPYNUMERIC_UOP_SIGN, + SIGNBIT = CUPYNUMERIC_UOP_SIGNBIT, + SIN = CUPYNUMERIC_UOP_SIN, + SINH = CUPYNUMERIC_UOP_SINH, + SQRT = CUPYNUMERIC_UOP_SQRT, + SQUARE = CUPYNUMERIC_UOP_SQUARE, + TAN = CUPYNUMERIC_UOP_TAN, + TANH = CUPYNUMERIC_UOP_TANH, + TRUNC = CUPYNUMERIC_UOP_TRUNC, }; template @@ -1354,4 +1354,4 @@ struct MultiOutUnaryOp { } }; -} // namespace cunumeric +} // namespace cupynumeric diff --git a/src/cunumeric/unary/unary_red.cc b/src/cupynumeric/unary/unary_red.cc similarity index 92% rename from src/cunumeric/unary/unary_red.cc rename to src/cupynumeric/unary/unary_red.cc index 827f27a9c9..062c645f76 100644 --- a/src/cunumeric/unary/unary_red.cc +++ b/src/cupynumeric/unary/unary_red.cc @@ -14,10 +14,10 @@ * */ -#include "cunumeric/unary/unary_red.h" -#include "cunumeric/unary/unary_red_template.inl" +#include "cupynumeric/unary/unary_red.h" +#include "cupynumeric/unary/unary_red_template.inl" -namespace cunumeric { +namespace cupynumeric { using namespace legate; @@ -59,4 +59,4 @@ namespace // unnamed static void __attribute__((constructor)) register_tasks(void) { UnaryRedTask::register_variants(); } } // namespace -} // namespace cunumeric +} // namespace cupynumeric diff --git a/src/cunumeric/unary/unary_red.cu b/src/cupynumeric/unary/unary_red.cu similarity index 98% rename from src/cunumeric/unary/unary_red.cu rename to src/cupynumeric/unary/unary_red.cu index f245305e88..55f3616cbf 100644 --- a/src/cunumeric/unary/unary_red.cu +++ b/src/cupynumeric/unary/unary_red.cu @@ -14,12 +14,12 @@ * */ -#include "cunumeric/unary/unary_red.h" -#include "cunumeric/unary/unary_red_template.inl" +#include "cupynumeric/unary/unary_red.h" +#include "cupynumeric/unary/unary_red_template.inl" -#include "cunumeric/cuda_help.h" +#include "cupynumeric/cuda_help.h" -namespace cunumeric { +namespace cupynumeric { template static constexpr T div_and_ceil(T value, T divider) @@ -353,7 +353,7 @@ struct UnaryRedImplBody { blocks.compute_maximum_concurrency(reinterpret_cast(Kernel)); Kernel<<>>( lhs, rhs, where, LG_OP::identity, blocks, rect, collapsed_dim); - CUNUMERIC_CHECK_CUDA_STREAM(stream); + CUPYNUMERIC_CHECK_CUDA_STREAM(stream); } }; @@ -362,4 +362,4 @@ struct UnaryRedImplBody { unary_red_template(context); } -} // namespace cunumeric +} // namespace cupynumeric diff --git a/src/cunumeric/unary/unary_red.h b/src/cupynumeric/unary/unary_red.h similarity index 79% rename from src/cunumeric/unary/unary_red.h rename to src/cupynumeric/unary/unary_red.h index b86868d71b..9667253249 100644 --- a/src/cunumeric/unary/unary_red.h +++ b/src/cupynumeric/unary/unary_red.h @@ -16,10 +16,10 @@ #pragma once -#include "cunumeric/cunumeric_task.h" -#include "cunumeric/unary/unary_red_util.h" +#include "cupynumeric/cupynumeric_task.h" +#include "cupynumeric/unary/unary_red_util.h" -namespace cunumeric { +namespace cupynumeric { struct UnaryRedArgs { legate::PhysicalStore lhs; @@ -29,9 +29,9 @@ struct UnaryRedArgs { UnaryRedCode op_code; }; -class UnaryRedTask : public CuNumericTask { +class UnaryRedTask : public CuPyNumericTask { public: - static constexpr auto TASK_ID = legate::LocalTaskID{CUNUMERIC_UNARY_RED}; + static constexpr auto TASK_ID = legate::LocalTaskID{CUPYNUMERIC_UNARY_RED}; public: static void cpu_variant(legate::TaskContext context); @@ -43,4 +43,4 @@ class UnaryRedTask : public CuNumericTask { #endif }; -} // namespace cunumeric +} // namespace cupynumeric diff --git a/src/cunumeric/unary/unary_red_omp.cc b/src/cupynumeric/unary/unary_red_omp.cc similarity index 95% rename from src/cunumeric/unary/unary_red_omp.cc rename to src/cupynumeric/unary/unary_red_omp.cc index 8ed659aee2..d5d885766f 100644 --- a/src/cunumeric/unary/unary_red_omp.cc +++ b/src/cupynumeric/unary/unary_red_omp.cc @@ -14,10 +14,10 @@ * */ -#include "cunumeric/unary/unary_red.h" -#include "cunumeric/unary/unary_red_template.inl" +#include "cupynumeric/unary/unary_red.h" +#include "cupynumeric/unary/unary_red_template.inl" -namespace cunumeric { +namespace cupynumeric { using namespace legate; @@ -112,4 +112,4 @@ struct UnaryRedImplBody { unary_red_template(context); } -} // namespace cunumeric +} // namespace cupynumeric diff --git a/src/cunumeric/unary/unary_red_template.inl b/src/cupynumeric/unary/unary_red_template.inl similarity index 92% rename from src/cunumeric/unary/unary_red_template.inl rename to src/cupynumeric/unary/unary_red_template.inl index 35bc589746..1d268ca6c1 100644 --- a/src/cunumeric/unary/unary_red_template.inl +++ b/src/cupynumeric/unary/unary_red_template.inl @@ -17,13 +17,13 @@ #pragma once // Useful for IDEs -#include "cunumeric/unary/unary_red.h" -#include "cunumeric/unary/unary_red_util.h" -#include "cunumeric/arg.h" -#include "cunumeric/arg.inl" -#include "cunumeric/pitches.h" +#include "cupynumeric/unary/unary_red.h" +#include "cupynumeric/unary/unary_red_util.h" +#include "cupynumeric/arg.h" +#include "cupynumeric/arg.inl" +#include "cupynumeric/pitches.h" -namespace cunumeric { +namespace cupynumeric { using namespace legate; @@ -95,4 +95,4 @@ static void unary_red_template(TaskContext& context) } } -} // namespace cunumeric +} // namespace cupynumeric diff --git a/src/cunumeric/unary/unary_red_util.h b/src/cupynumeric/unary/unary_red_util.h similarity index 94% rename from src/cunumeric/unary/unary_red_util.h rename to src/cupynumeric/unary/unary_red_util.h index 3dafba0bf6..09162507d2 100644 --- a/src/cunumeric/unary/unary_red_util.h +++ b/src/cupynumeric/unary/unary_red_util.h @@ -16,32 +16,32 @@ #pragma once -#include "cunumeric/cunumeric_task.h" -#include "cunumeric/arg.h" -#include "cunumeric/arg.inl" -#include "cunumeric/unary/isnan.h" +#include "cupynumeric/cupynumeric_task.h" +#include "cupynumeric/arg.h" +#include "cupynumeric/arg.inl" +#include "cupynumeric/unary/isnan.h" -namespace cunumeric { +namespace cupynumeric { enum class UnaryRedCode : int { - ALL = CUNUMERIC_RED_ALL, - ANY = CUNUMERIC_RED_ANY, - ARGMAX = CUNUMERIC_RED_ARGMAX, - ARGMIN = CUNUMERIC_RED_ARGMIN, - CONTAINS = CUNUMERIC_RED_CONTAINS, - COUNT_NONZERO = CUNUMERIC_RED_COUNT_NONZERO, - MAX = CUNUMERIC_RED_MAX, - MIN = CUNUMERIC_RED_MIN, - NANARGMAX = CUNUMERIC_RED_NANARGMAX, - NANARGMIN = CUNUMERIC_RED_NANARGMIN, - NANMAX = CUNUMERIC_RED_NANMAX, - NANMIN = CUNUMERIC_RED_NANMIN, - NANPROD = CUNUMERIC_RED_NANPROD, - NANSUM = CUNUMERIC_RED_NANSUM, - PROD = CUNUMERIC_RED_PROD, - SUM = CUNUMERIC_RED_SUM, - SUM_SQUARES = CUNUMERIC_RED_SUM_SQUARES, - VARIANCE = CUNUMERIC_RED_VARIANCE + ALL = CUPYNUMERIC_RED_ALL, + ANY = CUPYNUMERIC_RED_ANY, + ARGMAX = CUPYNUMERIC_RED_ARGMAX, + ARGMIN = CUPYNUMERIC_RED_ARGMIN, + CONTAINS = CUPYNUMERIC_RED_CONTAINS, + COUNT_NONZERO = CUPYNUMERIC_RED_COUNT_NONZERO, + MAX = CUPYNUMERIC_RED_MAX, + MIN = CUPYNUMERIC_RED_MIN, + NANARGMAX = CUPYNUMERIC_RED_NANARGMAX, + NANARGMIN = CUPYNUMERIC_RED_NANARGMIN, + NANMAX = CUPYNUMERIC_RED_NANMAX, + NANMIN = CUPYNUMERIC_RED_NANMIN, + NANPROD = CUPYNUMERIC_RED_NANPROD, + NANSUM = CUPYNUMERIC_RED_NANSUM, + PROD = CUPYNUMERIC_RED_PROD, + SUM = CUPYNUMERIC_RED_SUM, + SUM_SQUARES = CUPYNUMERIC_RED_SUM_SQUARES, + VARIANCE = CUPYNUMERIC_RED_VARIANCE }; template @@ -608,4 +608,4 @@ struct UnaryRedOp { using OP = _RED_OP::OP; }; -} // namespace cunumeric +} // namespace cupynumeric diff --git a/src/cunumeric/utilities/repartition.cc b/src/cupynumeric/utilities/repartition.cc similarity index 98% rename from src/cunumeric/utilities/repartition.cc rename to src/cupynumeric/utilities/repartition.cc index a27e28955e..9b009939ad 100644 --- a/src/cunumeric/utilities/repartition.cc +++ b/src/cupynumeric/utilities/repartition.cc @@ -16,7 +16,7 @@ #include "repartition.h" -namespace cunumeric { +namespace cupynumeric { std::tuple elements_for_rank_in_dimension( size_t dim_length, size_t offset_id, size_t proc_id, size_t num_dim_procs, size_t tilesize) @@ -68,4 +68,4 @@ std::tuple elements_for_rank_in_dimension( return {num_elements, offset_elements}; } -} // namespace cunumeric \ No newline at end of file +} // namespace cupynumeric \ No newline at end of file diff --git a/src/cunumeric/utilities/repartition.cu b/src/cupynumeric/utilities/repartition.cu similarity index 98% rename from src/cunumeric/utilities/repartition.cu rename to src/cupynumeric/utilities/repartition.cu index 917c84be98..ec5d2ea1d3 100644 --- a/src/cunumeric/utilities/repartition.cu +++ b/src/cupynumeric/utilities/repartition.cu @@ -16,9 +16,9 @@ #include "repartition.h" -#include "cunumeric/cuda_help.h" +#include "cupynumeric/cuda_help.h" -namespace cunumeric { +namespace cupynumeric { using namespace Legion; using namespace legate; @@ -426,7 +426,7 @@ std::tuple, size_t, size_t> repartition_matrix_2dbc(const VAL* input assert(total_send_elements == volume); // TODO / OPTIMIZE - // in case we have the global partition information of the cuNumeric block partition + // in case we have the global partition information of the cuPyNumeric block partition // we can compute receive buffers instead and skip this all2all // same applies for inverse operation @@ -439,7 +439,7 @@ std::tuple, size_t, size_t> repartition_matrix_2dbc(const VAL* input recv_info.ptr(r * stored_size_per_rank), stored_size_per_rank, ncclUint64, r, *comm, stream)); } CHECK_NCCL(ncclGroupEnd()); - CUNUMERIC_CHECK_CUDA(cudaStreamSynchronize(stream)); // need Z-copy synchronized to Host + CUPYNUMERIC_CHECK_CUDA(cudaStreamSynchronize(stream)); // need Z-copy synchronized to Host // allocate send/recv buffer std::vector> send_buffers; @@ -499,11 +499,11 @@ std::tuple, size_t, size_t> repartition_matrix_2dbc(const VAL* input p_c, tile_r, tile_c); - CUNUMERIC_CHECK_CUDA(cudaStreamSynchronize(stream)); + CUPYNUMERIC_CHECK_CUDA(cudaStreamSynchronize(stream)); send_buffers_ptr.destroy(); } - CUNUMERIC_CHECK_CUDA_STREAM(stream); + CUPYNUMERIC_CHECK_CUDA_STREAM(stream); // all2all data CHECK_NCCL(ncclGroupStart()); @@ -550,11 +550,11 @@ std::tuple, size_t, size_t> repartition_matrix_2dbc(const VAL* input tile_c, (size_t)nccl_rank, num_ranks); - CUNUMERIC_CHECK_CUDA(cudaStreamSynchronize(stream)); + CUPYNUMERIC_CHECK_CUDA(cudaStreamSynchronize(stream)); recv_buffers_ptr.destroy(); } - CUNUMERIC_CHECK_CUDA_STREAM(stream); + CUPYNUMERIC_CHECK_CUDA_STREAM(stream); recv_info.destroy(); for (auto&& buf : recv_buffers) { @@ -610,7 +610,7 @@ void repartition_matrix_block( offsets[2 * local_rank + 1] = num_target_cols > 0 ? target_offset_c + num_target_cols : 0; CHECK_NCCL( ncclAllGather(offsets.ptr(2 * local_rank), offsets.ptr(0), 2, ncclUint64, *comm, stream)); - CUNUMERIC_CHECK_CUDA(cudaStreamSynchronize(stream)); + CUPYNUMERIC_CHECK_CUDA(cudaStreamSynchronize(stream)); // re-arrange so that all row offsets come first for (size_t i = 1; i < num_ranks; i += 2) { @@ -697,7 +697,7 @@ void repartition_matrix_block( // Assumptions: // a. local_rank == nccl_rank == 2dbc-id (col-major) - // b. local_rank interpreted row-major (cuNumeric) should match offsets in offset mappings + // b. local_rank interpreted row-major (cuPyNumeric) should match offsets in offset mappings // c. offsets for ranks outside valid bounds are not considered size_t rank_r_rm = local_rank / target_p_c; size_t rank_c_rm = local_rank % target_p_c; @@ -805,7 +805,7 @@ void repartition_matrix_block( tile_r, tile_c); - CUNUMERIC_CHECK_CUDA(cudaStreamSynchronize(stream)); + CUPYNUMERIC_CHECK_CUDA(cudaStreamSynchronize(stream)); send_buffers_ptr.destroy(); } // we can destroy the input once we distributed data into the buffers @@ -904,11 +904,11 @@ void repartition_matrix_block( p_c, tile_r, tile_c); - CUNUMERIC_CHECK_CUDA(cudaStreamSynchronize(stream)); + CUPYNUMERIC_CHECK_CUDA(cudaStreamSynchronize(stream)); recv_buffers_ptr.destroy(); } - CUNUMERIC_CHECK_CUDA_STREAM(stream); + CUPYNUMERIC_CHECK_CUDA_STREAM(stream); // cleanup offsets_r.destroy(); @@ -1354,4 +1354,4 @@ template void repartition_matrix_block>( size_t, comm::Communicator); -} // namespace cunumeric \ No newline at end of file +} // namespace cupynumeric \ No newline at end of file diff --git a/src/cunumeric/utilities/repartition.h b/src/cupynumeric/utilities/repartition.h similarity index 94% rename from src/cunumeric/utilities/repartition.h rename to src/cupynumeric/utilities/repartition.h index d513cb7727..febbb81c9f 100644 --- a/src/cunumeric/utilities/repartition.h +++ b/src/cupynumeric/utilities/repartition.h @@ -17,9 +17,9 @@ #pragma once #include "legate.h" -#include "cunumeric/cunumeric_task.h" +#include "cupynumeric/cupynumeric_task.h" -namespace cunumeric { +namespace cupynumeric { enum BlockInfo { TOTAL_SIZE, // # values send @@ -79,7 +79,7 @@ void repartition_matrix_block( VAL* target, size_t target_volume, size_t target_lld, - // cuNumeric process grid layout (needs to match communicator size) + // cuPyNumeric process grid layout (needs to match communicator size) size_t num_target_rows, size_t num_target_cols, bool target_row_major, @@ -92,4 +92,4 @@ void repartition_matrix_block( [[nodiscard]] std::tuple elements_for_rank_in_dimension( size_t dim_length, size_t offset_id, size_t proc_id, size_t num_dim_procs, size_t tilesize); -} // namespace cunumeric \ No newline at end of file +} // namespace cupynumeric \ No newline at end of file diff --git a/src/cunumeric/utilities/thrust_allocator.h b/src/cupynumeric/utilities/thrust_allocator.h similarity index 95% rename from src/cunumeric/utilities/thrust_allocator.h rename to src/cupynumeric/utilities/thrust_allocator.h index 465b74974f..934df71f8a 100644 --- a/src/cunumeric/utilities/thrust_allocator.h +++ b/src/cupynumeric/utilities/thrust_allocator.h @@ -18,7 +18,7 @@ #include "legate.h" -namespace cunumeric { +namespace cupynumeric { class ThrustAllocator : public legate::ScopedAllocator { public: @@ -34,4 +34,4 @@ class ThrustAllocator : public legate::ScopedAllocator { void deallocate(char* ptr, size_t n) { ScopedAllocator::deallocate(ptr); } }; -} // namespace cunumeric +} // namespace cupynumeric diff --git a/src/cunumeric/utilities/thrust_util.h b/src/cupynumeric/utilities/thrust_util.h similarity index 100% rename from src/cunumeric/utilities/thrust_util.h rename to src/cupynumeric/utilities/thrust_util.h diff --git a/src/env_defaults.h b/src/env_defaults.h index ec63f96027..0307aaa9ac 100644 --- a/src/env_defaults.h +++ b/src/env_defaults.h @@ -14,7 +14,7 @@ * */ -// These values are copied manually in cunumeric.settings and there is a Python +// These values are copied manually in cupynumeric.settings and there is a Python // unit test that will maintain that these values and the Python settings // values agree. If these values are modified, the corresponding Python values // must also be updated. diff --git a/tests/cpp/CMakeLists.txt b/tests/cpp/CMakeLists.txt index c19bc0514a..89d6d8c907 100644 --- a/tests/cpp/CMakeLists.txt +++ b/tests/cpp/CMakeLists.txt @@ -20,7 +20,7 @@ project(cpp_tests VERSION 0.1 LANGUAGES C CXX) if(PROJECT_IS_TOP_LEVEL) # To catch people trying to build the tests from within tests/cpp instead of top-level - message(FATAL_ERROR "Error: Tests can only be built as part of the main library build. Please re-run cmake from top-level directory (\${CMAKE_SOURCE_DIR}) with -Dcunumeric_BUILD_TESTS=ON" + message(FATAL_ERROR "Error: Tests can only be built as part of the main library build. Please re-run cmake from top-level directory (\${CMAKE_SOURCE_DIR}) with -Dcupynumeric_BUILD_TESTS=ON" ) endif() @@ -37,8 +37,8 @@ include(${rapids-cmake-dir}/cpm/gtest.cmake) # BUILD_EXPORT_SET and INSTALL_EXPORT_SET are crucial, otherwise gtest does not get # installed -rapids_cpm_gtest(BUILD_EXPORT_SET cunumeric-exports - INSTALL_EXPORT_SET cunumeric-exports) +rapids_cpm_gtest(BUILD_EXPORT_SET cupynumeric-exports + INSTALL_EXPORT_SET cupynumeric-exports) file(GLOB main_SRC ${PROJECT_SOURCE_DIR}/main.cc) file(GLOB integration_SRC ${PROJECT_SOURCE_DIR}/integration/*.cc) @@ -50,7 +50,7 @@ endif() add_executable(cpp_tests ${main_SRC} ${tasks_SRC} ${integration_SRC} ${unit_SRC}) -target_link_libraries(cpp_tests PRIVATE legate::legate cunumeric::cunumeric GTest::gtest) +target_link_libraries(cpp_tests PRIVATE legate::legate cupynumeric::cupynumeric GTest::gtest) if(Legion_USE_CUDA) target_link_libraries(cpp_tests PRIVATE NCCL::NCCL) endif() diff --git a/tests/cpp/integration/common_utils.cc b/tests/cpp/integration/common_utils.cc index 107309f399..94b8cb132b 100644 --- a/tests/cpp/integration/common_utils.cc +++ b/tests/cpp/integration/common_utils.cc @@ -18,7 +18,7 @@ #include #include -namespace cunumeric { +namespace cupynumeric { template void show_array(NDArray& a) @@ -67,9 +67,9 @@ void debug_array(NDArray a, bool show_data) } } -} // namespace cunumeric +} // namespace cupynumeric -using namespace cunumeric; +using namespace cupynumeric; // unit test for common_utils namespace { @@ -153,7 +153,7 @@ TEST(Utils, test_ndarray_warn_and_convert) auto y = x._warn_and_convert(legate::int32()); debug_array(x); debug_array(y); - cunumeric_log().warning() << "Just a test!"; + cupynumeric_log().warning() << "Just a test!"; } TEST(Utils, test_wrap_indices_and_clip_indices) diff --git a/tests/cpp/integration/common_utils.h b/tests/cpp/integration/common_utils.h index d2d89544b9..47e84b1c11 100644 --- a/tests/cpp/integration/common_utils.h +++ b/tests/cpp/integration/common_utils.h @@ -27,11 +27,11 @@ #include #include "legate.h" -#include "cunumeric.h" -#include "cunumeric/runtime.h" +#include "cupynumeric.h" +#include "cupynumeric/runtime.h" #include "util.inl" -namespace cunumeric { +namespace cupynumeric { void debug_array(NDArray a, bool show_data = true); @@ -66,7 +66,7 @@ NDArray mk_array(std::vector const& values, std::vector shape = {}) } else { auto a1 = zeros({out.size()}, out.type()); assign_values(a1, values); - auto runtime = CuNumericRuntime::get_runtime(); + auto runtime = CuPyNumericRuntime::get_runtime(); auto a2 = runtime->create_array(std::move(a1.get_store().delinearize(0, shape))); out.assign(a2); } @@ -134,7 +134,7 @@ void check_array_near(NDArray a, template struct PrintArray { template - void operator()(cunumeric::NDArray array) + void operator()(cupynumeric::NDArray array) { auto acc = array.get_read_accessor(); auto& shape = array.shape(); @@ -193,4 +193,4 @@ std::vector as_type_vector(std::vector const& in) return out; } -} // namespace cunumeric +} // namespace cupynumeric diff --git a/tests/cpp/integration/test_amax.cc b/tests/cpp/integration/test_amax.cc index 43308eba2e..ce483ccec2 100644 --- a/tests/cpp/integration/test_amax.cc +++ b/tests/cpp/integration/test_amax.cc @@ -20,21 +20,21 @@ void test_amax(const std::vector& in_array, const std::vector& shape, const std::vector& expect_result, const std::vector& expect_shape, - std::vector axis = {}, - std::optional dtype = std::nullopt, - std::optional out = std::nullopt, - bool keepdims = false, - std::optional initial = std::nullopt, - std::optional where = std::nullopt) + std::vector axis = {}, + std::optional dtype = std::nullopt, + std::optional out = std::nullopt, + bool keepdims = false, + std::optional initial = std::nullopt, + std::optional where = std::nullopt) { - auto array = cunumeric::mk_array(in_array, shape); + auto array = cupynumeric::mk_array(in_array, shape); if (!out.has_value()) { - auto result = cunumeric::amax(array, axis, dtype, std::nullopt, keepdims, initial, where); - cunumeric::check_array(result, expect_result, expect_shape); + auto result = cupynumeric::amax(array, axis, dtype, std::nullopt, keepdims, initial, where); + cupynumeric::check_array(result, expect_result, expect_shape); } else { - cunumeric::amax(array, axis, dtype, out, keepdims, initial, where); - cunumeric::check_array(out.value(), expect_result, expect_shape); + cupynumeric::amax(array, axis, dtype, out, keepdims, initial, where); + cupynumeric::check_array(out.value(), expect_result, expect_shape); } } @@ -163,8 +163,8 @@ void test_amax_out_input() std::vector shape1 = {6}; std::vector exp_shape1 = {}; auto df = std::nullopt; - auto out1 = cunumeric::zeros(exp_shape1, legate::int32()); - auto out1_1 = cunumeric::zeros(exp_shape1, legate::float64()); + auto out1 = cupynumeric::zeros(exp_shape1, legate::int32()); + auto out1_1 = cupynumeric::zeros(exp_shape1, legate::float64()); std::vector exp1 = {5}; std::vector exp1_1 = {5.0}; test_amax(arr, shape1, exp1, exp_shape1, {}, df, out1); @@ -174,8 +174,8 @@ void test_amax_out_input() std::vector shape2 = {2, 3}; std::vector exp_shape2 = {2}; std::vector exp_shape2_k = {2, 1}; - auto out2 = cunumeric::zeros(exp_shape2, legate::int32()); - auto out2_k = cunumeric::zeros(exp_shape2_k, legate::int32()); + auto out2 = cupynumeric::zeros(exp_shape2, legate::int32()); + auto out2_k = cupynumeric::zeros(exp_shape2_k, legate::int32()); std::vector axis = {-1}; auto ini = legate::Scalar(2); std::vector exp2 = {5, 3}; @@ -247,7 +247,7 @@ void test_amax_scalar_array() std::vector arr = {10}; std::vector shape = {}; std::vector exp = {10}; - auto out = cunumeric::zeros(shape, legate::int32()); + auto out = cupynumeric::zeros(shape, legate::int32()); auto df = std::nullopt; test_amax(arr, shape, exp, shape); test_amax(arr, shape, exp, shape, {}, df, out); @@ -263,65 +263,65 @@ void test_amax_invalid_array() // Test zero size array std::vector arr1 = {}; std::vector shape1 = {0}; - auto arr_emp = cunumeric::mk_array(arr1, shape1); - EXPECT_THROW(cunumeric::amax(arr_emp), std::invalid_argument); + auto arr_emp = cupynumeric::mk_array(arr1, shape1); + EXPECT_THROW(cupynumeric::amax(arr_emp), std::invalid_argument); // Test complex array (not supported now) std::vector> arr2 = {complex(0, 1), complex(1, 1)}; std::vector shape2 = {2}; - auto arr_comp = cunumeric::mk_array>(arr2, shape2); - EXPECT_THROW(cunumeric::amax(arr_comp), std::runtime_error); + auto arr_comp = cupynumeric::mk_array>(arr2, shape2); + EXPECT_THROW(cupynumeric::amax(arr_comp), std::runtime_error); } void test_amax_invalid_axis() { std::vector arr = {1, 2, 3, 4, 5, 6}; std::vector shape = {1, 3, 2}; - auto array = cunumeric::mk_array(arr, shape); + auto array = cupynumeric::mk_array(arr, shape); // Test out-of-bound std::vector axis1 = {-4, 3}; std::vector axis2 = {0, 3}; - EXPECT_THROW(cunumeric::amax(array, axis1), std::invalid_argument); - EXPECT_THROW(cunumeric::amax(array, axis2), std::invalid_argument); + EXPECT_THROW(cupynumeric::amax(array, axis1), std::invalid_argument); + EXPECT_THROW(cupynumeric::amax(array, axis2), std::invalid_argument); // Test repeated axes std::vector axis3 = {1, 1}; std::vector axis4 = {-1, 2}; - EXPECT_THROW(cunumeric::amax(array, axis3), std::invalid_argument); - EXPECT_THROW(cunumeric::amax(array, axis4), std::invalid_argument); + EXPECT_THROW(cupynumeric::amax(array, axis3), std::invalid_argument); + EXPECT_THROW(cupynumeric::amax(array, axis4), std::invalid_argument); // Not reduce to one value (valid but not supported now) std::vector axis5 = {0, 1}; - EXPECT_THROW(cunumeric::amax(array, axis5), std::runtime_error); + EXPECT_THROW(cupynumeric::amax(array, axis5), std::runtime_error); } void test_amax_invalid_shape() { std::vector arr = {1, 2, 3, 4, 5, 6}; std::vector shape = {1, 3, 2}; - auto array = cunumeric::mk_array(arr, shape); + auto array = cupynumeric::mk_array(arr, shape); auto df = std::nullopt; std::vector out_shape1 = {1}; - auto out1 = cunumeric::zeros(out_shape1, legate::int32()); - EXPECT_THROW(cunumeric::amax(array, {}, df, out1), std::invalid_argument); + auto out1 = cupynumeric::zeros(out_shape1, legate::int32()); + EXPECT_THROW(cupynumeric::amax(array, {}, df, out1), std::invalid_argument); std::vector out_shape2 = {2}; std::vector axis2 = {1}; - auto out2 = cunumeric::zeros(out_shape2, legate::int32()); - EXPECT_THROW(cunumeric::amax(array, axis2, df, out2), std::invalid_argument); + auto out2 = cupynumeric::zeros(out_shape2, legate::int32()); + EXPECT_THROW(cupynumeric::amax(array, axis2, df, out2), std::invalid_argument); } void test_amax_invalid_dtype() { std::vector arr = {1, 2, 3, 4, 5, 6}; std::vector shape = {1, 3, 2}; - auto array = cunumeric::mk_array(arr, shape); + auto array = cupynumeric::mk_array(arr, shape); // Test invalid dtype auto dtype = legate::point_type(2); - EXPECT_THROW(cunumeric::amax(array, {}, dtype), std::invalid_argument); + EXPECT_THROW(cupynumeric::amax(array, {}, dtype), std::invalid_argument); } // void cpp_test() diff --git a/tests/cpp/integration/test_amin.cc b/tests/cpp/integration/test_amin.cc index e82ec50de4..eed3528dc5 100644 --- a/tests/cpp/integration/test_amin.cc +++ b/tests/cpp/integration/test_amin.cc @@ -20,21 +20,21 @@ void test_amin(const std::vector& in_array, const std::vector& shape, const std::vector& expect_result, const std::vector& expect_shape, - std::vector axis = {}, - std::optional dtype = std::nullopt, - std::optional out = std::nullopt, - bool keepdims = false, - std::optional initial = std::nullopt, - std::optional where = std::nullopt) + std::vector axis = {}, + std::optional dtype = std::nullopt, + std::optional out = std::nullopt, + bool keepdims = false, + std::optional initial = std::nullopt, + std::optional where = std::nullopt) { - auto array = cunumeric::mk_array(in_array, shape); + auto array = cupynumeric::mk_array(in_array, shape); if (!out.has_value()) { - auto result = cunumeric::amin(array, axis, dtype, std::nullopt, keepdims, initial, where); - cunumeric::check_array(result, expect_result, expect_shape); + auto result = cupynumeric::amin(array, axis, dtype, std::nullopt, keepdims, initial, where); + cupynumeric::check_array(result, expect_result, expect_shape); } else { - cunumeric::amin(array, axis, dtype, out, keepdims, initial, where); - cunumeric::check_array(out.value(), expect_result, expect_shape); + cupynumeric::amin(array, axis, dtype, out, keepdims, initial, where); + cupynumeric::check_array(out.value(), expect_result, expect_shape); } } @@ -163,8 +163,8 @@ void test_amin_out_input() std::vector shape1 = {6}; std::vector exp_shape1 = {}; auto df = std::nullopt; - auto out1 = cunumeric::zeros(exp_shape1, legate::int32()); - auto out1_1 = cunumeric::zeros(exp_shape1, legate::float64()); + auto out1 = cupynumeric::zeros(exp_shape1, legate::int32()); + auto out1_1 = cupynumeric::zeros(exp_shape1, legate::float64()); std::vector exp1 = {-1}; std::vector exp1_1 = {-1.0}; test_amin(arr, shape1, exp1, exp_shape1, {}, df, out1); @@ -174,8 +174,8 @@ void test_amin_out_input() std::vector shape2 = {2, 3}; std::vector exp_shape2 = {2}; std::vector exp_shape2_k = {2, 1}; - auto out2 = cunumeric::zeros(exp_shape2, legate::int32()); - auto out2_k = cunumeric::zeros(exp_shape2_k, legate::int32()); + auto out2 = cupynumeric::zeros(exp_shape2, legate::int32()); + auto out2_k = cupynumeric::zeros(exp_shape2_k, legate::int32()); std::vector axis = {-1}; auto ini = legate::Scalar(2); std::vector exp2 = {-1, 0}; @@ -247,7 +247,7 @@ void test_amin_scalar_array() std::vector arr = {10}; std::vector shape = {}; std::vector exp = {10}; - auto out = cunumeric::zeros(shape, legate::int32()); + auto out = cupynumeric::zeros(shape, legate::int32()); auto df = std::nullopt; test_amin(arr, shape, exp, shape); test_amin(arr, shape, exp, shape, {}, df, out); @@ -263,65 +263,65 @@ void test_amin_invalid_array() // Test zero size array std::vector arr1 = {}; std::vector shape1 = {0}; - auto arr_emp = cunumeric::mk_array(arr1, shape1); - EXPECT_THROW(cunumeric::amin(arr_emp), std::invalid_argument); + auto arr_emp = cupynumeric::mk_array(arr1, shape1); + EXPECT_THROW(cupynumeric::amin(arr_emp), std::invalid_argument); // Test complex array (not supported now) std::vector> arr2 = {complex(0, 1), complex(1, 1)}; std::vector shape2 = {2}; - auto arr_comp = cunumeric::mk_array>(arr2, shape2); - EXPECT_THROW(cunumeric::amin(arr_comp), std::runtime_error); + auto arr_comp = cupynumeric::mk_array>(arr2, shape2); + EXPECT_THROW(cupynumeric::amin(arr_comp), std::runtime_error); } void test_amin_invalid_axis() { std::vector arr = {1, 2, 3, 4, 5, 6}; std::vector shape = {1, 3, 2}; - auto array = cunumeric::mk_array(arr, shape); + auto array = cupynumeric::mk_array(arr, shape); // Test out-of-bound std::vector axis1 = {-4, 3}; std::vector axis2 = {0, 3}; - EXPECT_THROW(cunumeric::amin(array, axis1), std::invalid_argument); - EXPECT_THROW(cunumeric::amin(array, axis2), std::invalid_argument); + EXPECT_THROW(cupynumeric::amin(array, axis1), std::invalid_argument); + EXPECT_THROW(cupynumeric::amin(array, axis2), std::invalid_argument); // Test repeated axes std::vector axis3 = {1, 1}; std::vector axis4 = {-1, 2}; - EXPECT_THROW(cunumeric::amin(array, axis3), std::invalid_argument); - EXPECT_THROW(cunumeric::amin(array, axis4), std::invalid_argument); + EXPECT_THROW(cupynumeric::amin(array, axis3), std::invalid_argument); + EXPECT_THROW(cupynumeric::amin(array, axis4), std::invalid_argument); // Not reduce to one value (valid but not supported now) std::vector axis5 = {0, 1}; - EXPECT_THROW(cunumeric::amin(array, axis5), std::runtime_error); + EXPECT_THROW(cupynumeric::amin(array, axis5), std::runtime_error); } void test_amin_invalid_shape() { std::vector arr = {1, 2, 3, 4, 5, 6}; std::vector shape = {1, 3, 2}; - auto array = cunumeric::mk_array(arr, shape); + auto array = cupynumeric::mk_array(arr, shape); auto df = std::nullopt; std::vector out_shape1 = {1}; - auto out1 = cunumeric::zeros(out_shape1, legate::int32()); - EXPECT_THROW(cunumeric::amin(array, {}, df, out1), std::invalid_argument); + auto out1 = cupynumeric::zeros(out_shape1, legate::int32()); + EXPECT_THROW(cupynumeric::amin(array, {}, df, out1), std::invalid_argument); std::vector out_shape2 = {2}; std::vector axis2 = {1}; - auto out2 = cunumeric::zeros(out_shape2, legate::int32()); - EXPECT_THROW(cunumeric::amin(array, axis2, df, out2), std::invalid_argument); + auto out2 = cupynumeric::zeros(out_shape2, legate::int32()); + EXPECT_THROW(cupynumeric::amin(array, axis2, df, out2), std::invalid_argument); } void test_amin_invalid_dtype() { std::vector arr = {1, 2, 3, 4, 5, 6}; std::vector shape = {1, 3, 2}; - auto array = cunumeric::mk_array(arr, shape); + auto array = cupynumeric::mk_array(arr, shape); // Test invalid dtype auto dtype = legate::point_type(2); - EXPECT_THROW(cunumeric::amin(array, {}, dtype), std::invalid_argument); + EXPECT_THROW(cupynumeric::amin(array, {}, dtype), std::invalid_argument); } // void cpp_test() diff --git a/tests/cpp/integration/test_arange.cc b/tests/cpp/integration/test_arange.cc index d6a5fb7300..3152d58497 100644 --- a/tests/cpp/integration/test_arange.cc +++ b/tests/cpp/integration/test_arange.cc @@ -18,7 +18,7 @@ #include #include #include "legate.h" -#include "cunumeric.h" +#include "cupynumeric.h" #include "util.inl" TEST(ArangeType, ImplicitInt64) @@ -34,7 +34,7 @@ TEST(ArangeType, ImplicitInt64) 1567891032462, 1567891032463, 1567891032464}; - auto arr = cunumeric::arange(start, stop); + auto arr = cupynumeric::arange(start, stop); check_array_eq(arr, exp.data(), exp.size()); } @@ -42,7 +42,7 @@ TEST(ArangeType, ImplicitInt32) { int32_t stop = 10; std::array exp = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9}; - auto arr = cunumeric::arange(stop); + auto arr = cupynumeric::arange(stop); check_array_eq(arr, exp.data(), exp.size()); } @@ -51,7 +51,7 @@ TEST(ArangeType, ImplicitFloat64) double start = 1.5; double stop = 10.5; std::array exp = {1.5, 2.5, 3.5, 4.5, 5.5, 6.5, 7.5, 8.5, 9.5}; - auto arr = cunumeric::arange(start, (std::optional)stop); + auto arr = cupynumeric::arange(start, (std::optional)stop); check_array_eq(arr, exp.data(), exp.size()); } @@ -60,7 +60,7 @@ TEST(ArangeType, ImplicitFloat32) float start = 1.5; float stop = 10.5; std::array exp = {1.5, 2.5, 3.5, 4.5, 5.5, 6.5, 7.5, 8.5, 9.5}; - auto arr = cunumeric::arange(start, (std::optional)stop); + auto arr = cupynumeric::arange(start, (std::optional)stop); check_array_eq(arr, exp.data(), exp.size()); } @@ -69,7 +69,7 @@ TEST(ArangeType, ExplicitInt32) float start = 1.5; float stop = 10.5; std::array exp = {1, 2, 3, 4, 5, 6, 7, 8, 9}; - auto arr = cunumeric::arange(start, stop); + auto arr = cupynumeric::arange(start, stop); check_array_eq(arr, exp.data(), exp.size()); } @@ -78,7 +78,7 @@ TEST(ArangeScalar, Float32) float start = 1.5; float stop = 10.5; std::array exp = {1.5, 2.5, 3.5, 4.5, 5.5, 6.5, 7.5, 8.5, 9.5}; - auto arr = cunumeric::arange(legate::Scalar(start), legate::Scalar(stop)); + auto arr = cupynumeric::arange(legate::Scalar(start), legate::Scalar(stop)); check_array_eq(arr, exp.data(), exp.size()); } @@ -86,6 +86,6 @@ TEST(ArangeErrors, ScalarTypeMismatch) { float start = 1.5; int32_t stop = 10; - EXPECT_THROW(cunumeric::arange(legate::Scalar(start), legate::Scalar(stop)), + EXPECT_THROW(cupynumeric::arange(legate::Scalar(start), legate::Scalar(stop)), std::invalid_argument); } diff --git a/tests/cpp/integration/test_argsort.cc b/tests/cpp/integration/test_argsort.cc index 62b3ececaa..ffe9cda33b 100644 --- a/tests/cpp/integration/test_argsort.cc +++ b/tests/cpp/integration/test_argsort.cc @@ -20,7 +20,7 @@ #include #include "legate.h" -#include "cunumeric.h" +#include "cupynumeric.h" #include "util.inl" auto get_argsort_expect_result() @@ -211,7 +211,7 @@ void test_argsort(std::array& in_array, std::optional axis, bool test_only_stable = false) { - auto A1 = cunumeric::zeros(shape, leg_type); + auto A1 = cupynumeric::zeros(shape, leg_type); if (in_array.size() != 0) { if (in_array.size() == 1) { A1.fill(legate::Scalar(in_array[0])); @@ -224,7 +224,7 @@ void test_argsort(std::array& in_array, algos = {"mergesort", "stable"}; } for (auto algo = algos.begin(); algo < algos.end(); ++algo) { - auto B1 = cunumeric::argsort(A1, axis, *algo); + auto B1 = cupynumeric::argsort(A1, axis, *algo); if (in_array.size() != 0) { check_array_eq(B1, expect.data(), expect.size()); } @@ -457,14 +457,14 @@ void argsort_single_item_array() void argsort_negative_test() { - auto in_ar1 = cunumeric::zeros({2, 3}, legate::int32()); + auto in_ar1 = cupynumeric::zeros({2, 3}, legate::int32()); // Test invalid input sort axis - EXPECT_THROW(cunumeric::argsort(in_ar1, 2, "quicksort"), std::invalid_argument); - EXPECT_THROW(cunumeric::argsort(in_ar1, -3, "quicksort"), std::invalid_argument); + EXPECT_THROW(cupynumeric::argsort(in_ar1, 2, "quicksort"), std::invalid_argument); + EXPECT_THROW(cupynumeric::argsort(in_ar1, -3, "quicksort"), std::invalid_argument); // Test invalid input algorithm - EXPECT_THROW(cunumeric::argsort(in_ar1, 0, "negative"), std::invalid_argument); + EXPECT_THROW(cupynumeric::argsort(in_ar1, 0, "negative"), std::invalid_argument); } // void cpp_test() diff --git a/tests/cpp/integration/test_argwhere.cc b/tests/cpp/integration/test_argwhere.cc index 9627e6eb78..1ebc9c985a 100644 --- a/tests/cpp/integration/test_argwhere.cc +++ b/tests/cpp/integration/test_argwhere.cc @@ -20,10 +20,10 @@ #include #include "legate.h" -#include "cunumeric.h" +#include "cupynumeric.h" #include "common_utils.h" -using namespace cunumeric; +using namespace cupynumeric; namespace { std::vector> get_in_shapes_basic() @@ -268,7 +268,7 @@ TEST(Argwhere, EmptyArray) {0, 0}, {0, 0}, {0, 0}, - {0, 0} // This is shape of cunumeric output array + {0, 0} // This is shape of cupynumeric output array }; assert(in_shapes.size() == exp_shapes.size()); @@ -289,7 +289,7 @@ TEST(Argwhere, Scalar) std::vector exp_shape2 = {1, 0}; auto A2 = zeros({}, legate::float64()); A2.fill(legate::Scalar(static_cast(1))); - auto B2 = cunumeric::argwhere(A2); + auto B2 = cupynumeric::argwhere(A2); EXPECT_EQ(B2.size(), 0); EXPECT_EQ(B2.type(), legate::int64()); EXPECT_EQ(B2.shape(), exp_shape2); diff --git a/tests/cpp/integration/test_bincount.cc b/tests/cpp/integration/test_bincount.cc index d4f4dc7f23..8cbd0953ae 100644 --- a/tests/cpp/integration/test_bincount.cc +++ b/tests/cpp/integration/test_bincount.cc @@ -20,7 +20,7 @@ #include #include "legate.h" -#include "cunumeric.h" +#include "cupynumeric.h" #include "util.inl" void bincount_test() @@ -28,76 +28,76 @@ void bincount_test() // case: x, no w, min_length=0. out NDArray type is int64_t if no weights std::array exp1 = {0, 1, 1, 2, 0, 1, 1}; std::array in_x1 = {1, 2, 3, 3, 5, 6}; - auto A1 = cunumeric::zeros({6}, legate::int32()); + auto A1 = cupynumeric::zeros({6}, legate::int32()); assign_values_to_array(A1, in_x1.data(), in_x1.size()); - auto B1 = cunumeric::bincount(A1); + auto B1 = cupynumeric::bincount(A1); check_array_eq(B1, exp1.data(), exp1.size()); // case: x, w, min_length=0. std::array exp2 = {0, 1, 1.2, 2, 0, 1, 0.1}; std::array in_w2 = {1, 1.2, 1, 1, 1, 0.1}; - auto w2 = cunumeric::zeros({6}, legate::float64()); + auto w2 = cupynumeric::zeros({6}, legate::float64()); assign_values_to_array(w2, in_w2.data(), in_w2.size()); - auto B2 = cunumeric::bincount(A1, w2); + auto B2 = cupynumeric::bincount(A1, w2); check_array_eq(B2, exp2.data(), exp2.size()); // case: x, no w, min_length=8. out NDArray type is int64_t if no weights std::array exp3 = {0, 1, 1, 2, 0, 1, 1, 0}; - auto B3 = cunumeric::bincount(A1, std::nullopt, 8); + auto B3 = cupynumeric::bincount(A1, std::nullopt, 8); check_array_eq(B3, exp3.data(), exp3.size()); // case: x of length 1, no w, min_length=0 std::array exp4 = {0, 0, 0, 0, 0, 1}; - auto A4 = cunumeric::full({1}, cunumeric::Scalar(5)); + auto A4 = cupynumeric::full({1}, cupynumeric::Scalar(5)); // If we use another way to initialize A4 of length 1 as below, it would rasie error. Seems a lock // issue. In this way, if A4 is not of length 1, it pass. int64_t in_x4[1] = {5}; auto A4 = - // cunumeric::zeros({1}, legate::int64()); assign_values_to_array(A4, (void *)in_x4, + // cupynumeric::zeros({1}, legate::int64()); assign_values_to_array(A4, (void *)in_x4, // sizeof(in_x4)/sizeof(int64_t)); cpp_tests: legion/runtime/realm/runtime_impl.cc:2755: // Realm::RegionInstanceImpl* Realm::RuntimeImpl::get_instance_impl(Realm::ID): Assertion `0 && // "invalid instance handle"' failed. - auto B4 = cunumeric::bincount(A4); + auto B4 = cupynumeric::bincount(A4); check_array_eq(B4, exp4.data(), exp4.size()); // case: x of length 1, w of length 1, min_length=0 std::array exp5 = {0, 0, 0, 0, 0, 1.3}; - auto w5 = cunumeric::full({1}, cunumeric::Scalar(1.3)); - auto B5 = cunumeric::bincount(A4, w5); + auto w5 = cupynumeric::full({1}, cupynumeric::Scalar(1.3)); + auto B5 = cupynumeric::bincount(A4, w5); check_array_eq(B5, exp5.data(), exp5.size()); // case: x of length 1, w of length 1, min_length=8 std::array exp6 = {0, 0, 0, 0, 0, 1.3, 0, 0}; - auto B6 = cunumeric::bincount(A4, w5, 8); + auto B6 = cupynumeric::bincount(A4, w5, 8); check_array_eq(B6, exp6.data(), exp6.size()); } void bincount_negative_test() { // case: x.size() == 0 - auto A1 = cunumeric::full({0}, cunumeric::Scalar(5)); - EXPECT_THROW(cunumeric::bincount(A1), std::invalid_argument); + auto A1 = cupynumeric::full({0}, cupynumeric::Scalar(5)); + EXPECT_THROW(cupynumeric::bincount(A1), std::invalid_argument); // case: x.dim() != 1 - auto A2 = cunumeric::full({1, 1}, cunumeric::Scalar(5)); - EXPECT_THROW(cunumeric::bincount(A2), std::invalid_argument); + auto A2 = cupynumeric::full({1, 1}, cupynumeric::Scalar(5)); + EXPECT_THROW(cupynumeric::bincount(A2), std::invalid_argument); // case: x.type() is not int - auto A3 = cunumeric::full({3}, cunumeric::Scalar(1.3)); - EXPECT_THROW(cunumeric::bincount(A3), std::invalid_argument); + auto A3 = cupynumeric::full({3}, cupynumeric::Scalar(1.3)); + EXPECT_THROW(cupynumeric::bincount(A3), std::invalid_argument); // case: x.shape() != w.shape() - auto A4 = cunumeric::zeros({6}, legate::int32()); - auto w4 = cunumeric::zeros({4}, legate::int32()); - EXPECT_THROW(cunumeric::bincount(A4, w4), std::invalid_argument); + auto A4 = cupynumeric::zeros({6}, legate::int32()); + auto w4 = cupynumeric::zeros({4}, legate::int32()); + EXPECT_THROW(cupynumeric::bincount(A4, w4), std::invalid_argument); // case: w.type() is not convertible to float64 - auto w5 = cunumeric::zeros({6}, legate::complex64()); - EXPECT_THROW(cunumeric::bincount(A4, w5), std::invalid_argument); + auto w5 = cupynumeric::zeros({6}, legate::complex64()); + EXPECT_THROW(cupynumeric::bincount(A4, w5), std::invalid_argument); // case: x is negative std::array in_x = {1, 2, -3, 4, 5, 6}; - auto A7 = cunumeric::zeros({6}, legate::int32()); + auto A7 = cupynumeric::zeros({6}, legate::int32()); assign_values_to_array(A7, in_x.data(), in_x.size()); - EXPECT_THROW(cunumeric::bincount(A7), std::invalid_argument); + EXPECT_THROW(cupynumeric::bincount(A7), std::invalid_argument); } // void cpp_test() diff --git a/tests/cpp/integration/test_convolve.cc b/tests/cpp/integration/test_convolve.cc index fa93172b9d..f67cc1c357 100644 --- a/tests/cpp/integration/test_convolve.cc +++ b/tests/cpp/integration/test_convolve.cc @@ -17,7 +17,7 @@ #include "common_utils.h" #include -using namespace cunumeric; +using namespace cupynumeric; namespace { diff --git a/tests/cpp/integration/test_diagonal.cc b/tests/cpp/integration/test_diagonal.cc index cd7e1986ac..300cf8e852 100644 --- a/tests/cpp/integration/test_diagonal.cc +++ b/tests/cpp/integration/test_diagonal.cc @@ -15,7 +15,7 @@ */ #include -#include "cunumeric.h" +#include "cupynumeric.h" #include "util.inl" template @@ -27,9 +27,9 @@ void diagonal_test(std::array input, int32_t axis2 = 1, bool extract = true) { - auto a_input = cunumeric::zeros(in_shape); + auto a_input = cupynumeric::zeros(in_shape); assign_values_to_array(a_input, input.data(), input.size()); - auto a_output = cunumeric::diagonal(a_input, offset, axis1, axis2, extract); + auto a_output = cupynumeric::diagonal(a_input, offset, axis1, axis2, extract); check_array_eq(a_output, exp.data(), exp.size()); } @@ -45,9 +45,9 @@ TEST(Diagonal, Singleton) 0., 0., 0., 0., 5., 0., 0., 0., 0., 0., 0., 6.}; std::vector in_shape = {6}; - auto a_input = cunumeric::zeros(in_shape); + auto a_input = cupynumeric::zeros(in_shape); assign_values_to_array(a_input, input.data(), input.size()); - auto a_output = cunumeric::diagonal(a_input, 0, std::nullopt, std::nullopt, false); + auto a_output = cupynumeric::diagonal(a_input, 0, std::nullopt, std::nullopt, false); check_array_eq(a_output, exp.data(), exp.size()); } @@ -55,8 +55,8 @@ TEST(Diagonal, SingletonExtract) { std::vector in_shape = {6}; - auto a_input = cunumeric::zeros(in_shape); - EXPECT_THROW(cunumeric::diagonal(a_input, 0, std::nullopt, std::nullopt, true), + auto a_input = cupynumeric::zeros(in_shape); + EXPECT_THROW(cupynumeric::diagonal(a_input, 0, std::nullopt, std::nullopt, true), std::invalid_argument); } @@ -64,8 +64,8 @@ TEST(Diagonal, SingletonAxes) { std::vector in_shape = {6}; - auto a_input = cunumeric::zeros(in_shape); - EXPECT_THROW(cunumeric::diagonal(a_input, 0, 0, 1, false), std::invalid_argument); + auto a_input = cupynumeric::zeros(in_shape); + EXPECT_THROW(cupynumeric::diagonal(a_input, 0, 0, 1, false), std::invalid_argument); } TEST(Diagonal, Defaults) @@ -78,9 +78,9 @@ TEST(Diagonal, Defaults) std::array exp = {9, 2, 6}; std::vector in_shape = {3, 3}; - auto a_input = cunumeric::zeros(in_shape); + auto a_input = cupynumeric::zeros(in_shape); assign_values_to_array(a_input, input.data(), input.size()); - auto a_output = cunumeric::diagonal(a_input); + auto a_output = cupynumeric::diagonal(a_input); check_array_eq(a_output, exp.data(), exp.size()); } @@ -90,8 +90,8 @@ TEST(Diagonal, EmptyArray) const size_t exp_dim = 2; std::array exp = {}; - auto a_input = cunumeric::array({0}, legate::int32()); - auto a_output = cunumeric::diagonal(a_input, 0, std::nullopt, std::nullopt, false); + auto a_input = cupynumeric::array({0}, legate::int32()); + auto a_output = cupynumeric::diagonal(a_input, 0, std::nullopt, std::nullopt, false); check_array_eq(a_output, exp.data(), exp.size()); } @@ -141,10 +141,10 @@ TEST(Diagonal, InvalidAxes) std::array input = {1.3, 2, 3.6, 4, 5, 6}; std::vector in_shape = {2, 3}; - auto a_input = cunumeric::zeros(in_shape); + auto a_input = cupynumeric::zeros(in_shape); assign_values_to_array(a_input, input.data(), input.size()); - EXPECT_THROW(cunumeric::diagonal(a_input, 0, 2, 6, true), std::invalid_argument); - EXPECT_THROW(cunumeric::diagonal(a_input, 0, 1, 1, true), std::invalid_argument); + EXPECT_THROW(cupynumeric::diagonal(a_input, 0, 2, 6, true), std::invalid_argument); + EXPECT_THROW(cupynumeric::diagonal(a_input, 0, 1, 1, true), std::invalid_argument); } TEST(Diagonal, InvalidOffset) @@ -154,9 +154,9 @@ TEST(Diagonal, InvalidOffset) std::array input = {1.3, 2, 3.6, 4, 5, 6}; std::vector in_shape = {2, 3}; - auto a_input = cunumeric::zeros(in_shape); + auto a_input = cupynumeric::zeros(in_shape); assign_values_to_array(a_input, input.data(), input.size()); - EXPECT_THROW(cunumeric::diagonal(a_input, 3), std::invalid_argument); + EXPECT_THROW(cupynumeric::diagonal(a_input, 3), std::invalid_argument); } TEST(Diagonal, IntArray) @@ -169,9 +169,9 @@ TEST(Diagonal, IntArray) std::array exp = {1, 5}; std::vector in_shape = {2, 3}; - auto a_input = cunumeric::zeros(in_shape, legate::int32()); + auto a_input = cupynumeric::zeros(in_shape, legate::int32()); assign_values_to_array(a_input, input.data(), input.size()); - auto a_output = cunumeric::diagonal(a_input); + auto a_output = cupynumeric::diagonal(a_input); check_array_eq(a_output, exp.data(), exp.size()); } @@ -201,15 +201,15 @@ template void trace_test(std::array input, std::array exp, std::vector in_shape, - int32_t offset = 0, - int32_t axis1 = 0, - int32_t axis2 = 1, - std::optional type = std::nullopt, - std::optional out = std::nullopt) + int32_t offset = 0, + int32_t axis1 = 0, + int32_t axis2 = 1, + std::optional type = std::nullopt, + std::optional out = std::nullopt) { - auto a_input = cunumeric::zeros(in_shape); + auto a_input = cupynumeric::zeros(in_shape); assign_values_to_array(a_input, input.data(), input.size()); - auto a_output = cunumeric::trace(a_input, offset, axis1, axis2, type, out); + auto a_output = cupynumeric::trace(a_input, offset, axis1, axis2, type, out); check_array_eq(a_output, exp.data(), exp.size()); } @@ -258,9 +258,9 @@ TEST(Trace, IntArray) std::array input = {9, 7, 5, 3, 2, 6, 4, 1}; std::array exp = {15}; std::vector in_shape = {2, 4, 1}; - auto a_input = cunumeric::zeros(in_shape, legate::int32()); + auto a_input = cupynumeric::zeros(in_shape, legate::int32()); assign_values_to_array(a_input, input.data(), input.size()); - auto a_output = cunumeric::trace(a_input, 0, 0, 1); + auto a_output = cupynumeric::trace(a_input, 0, 0, 1); check_array_eq(a_output, exp.data(), exp.size()); } @@ -274,9 +274,9 @@ TEST(Trace, TypeInt) std::array exp = {12}; std::vector in_shape = {2, 4, 1}; - auto a_input = cunumeric::zeros(in_shape); + auto a_input = cupynumeric::zeros(in_shape); assign_values_to_array(a_input, input.data(), input.size()); - auto a_output = cunumeric::trace(a_input, 0, 0, 1, legate::int32()); + auto a_output = cupynumeric::trace(a_input, 0, 0, 1, legate::int32()); check_array_eq(a_output, exp.data(), exp.size()); } @@ -291,10 +291,10 @@ TEST(Trace, OutType) std::vector in_shape = {2, 4, 1}; std::vector out_shape = {1}; - auto a_input = cunumeric::zeros(in_shape); - auto a_output = cunumeric::zeros(out_shape, legate::int32()); + auto a_input = cupynumeric::zeros(in_shape); + auto a_output = cupynumeric::zeros(out_shape, legate::int32()); assign_values_to_array(a_input, input.data(), input.size()); - cunumeric::trace(a_input, 0, 0, 1, std::nullopt, a_output); + cupynumeric::trace(a_input, 0, 0, 1, std::nullopt, a_output); check_array_eq(a_output, exp.data(), exp.size()); } @@ -305,7 +305,7 @@ TEST(Trace, InvalidArray) std::array input = {9, 7, 0.5, 1.3, 2, 3.6, 4, 5}; std::vector in_shape = {8}; - auto a_input = cunumeric::zeros(in_shape); + auto a_input = cupynumeric::zeros(in_shape); assign_values_to_array(a_input, input.data(), input.size()); - EXPECT_THROW(cunumeric::trace(a_input), std::invalid_argument); + EXPECT_THROW(cupynumeric::trace(a_input), std::invalid_argument); } diff --git a/tests/cpp/integration/test_dot.cc b/tests/cpp/integration/test_dot.cc index f38f646b7d..b13b029f2c 100644 --- a/tests/cpp/integration/test_dot.cc +++ b/tests/cpp/integration/test_dot.cc @@ -21,10 +21,10 @@ #include #include "legate.h" -#include "cunumeric.h" +#include "cupynumeric.h" #include "common_utils.h" -using namespace cunumeric; +using namespace cupynumeric; namespace { @@ -36,8 +36,8 @@ auto test_standard(uint64_t m, uint64_t n, uint64_t k, legate::Type leg_type) std::iota(data_a.begin(), data_a.end(), 0); std::iota(data_b.begin(), data_b.end(), 0.0); - auto A = cunumeric::zeros({m, k}, leg_type); - auto B = cunumeric::zeros({k, n}, leg_type); + auto A = cupynumeric::zeros({m, k}, leg_type); + auto B = cupynumeric::zeros({k, n}, leg_type); assign_values_to_array(A, data_a.data(), m * k); assign_values_to_array(B, data_b.data(), n * k); diff --git a/tests/cpp/integration/test_eye.cc b/tests/cpp/integration/test_eye.cc index a60c6693ff..fef0925c5e 100644 --- a/tests/cpp/integration/test_eye.cc +++ b/tests/cpp/integration/test_eye.cc @@ -20,7 +20,7 @@ #include #include "legate.h" -#include "cunumeric.h" +#include "cupynumeric.h" #include "util.inl" template @@ -77,13 +77,13 @@ auto test_eye_3_2(std::vector& k_vals, std::optional type std::vector expect_shape = {3, 2}; for (auto k : k_vals) { if (type.has_value()) { - auto result = cunumeric::eye(3, 2, k, type.value()); + auto result = cupynumeric::eye(3, 2, k, type.value()); EXPECT_EQ(result.type(), type.value()); EXPECT_EQ(result.shape(), expect_shape); auto expect = expect_result[k]; check_array_eq(result, expect.data(), expect.size()); } else { - auto result = cunumeric::eye(3, 2, k); + auto result = cupynumeric::eye(3, 2, k); EXPECT_EQ(result.type(), legate::float64()); EXPECT_EQ(result.shape(), expect_shape); auto expect = expect_result[k]; @@ -99,13 +99,13 @@ auto test_eye_3_3(std::vector& k_vals, std::optional type std::vector expect_shape = {3, 3}; for (auto k : k_vals) { if (type.has_value()) { - auto result = cunumeric::eye(3, 3, k, type.value()); + auto result = cupynumeric::eye(3, 3, k, type.value()); EXPECT_EQ(result.type(), type.value()); EXPECT_EQ(result.shape(), expect_shape); auto expect = expect_result[k]; check_array_eq(result, expect.data(), expect.size()); } else { - auto result = cunumeric::eye(3, 3, k); + auto result = cupynumeric::eye(3, 3, k); EXPECT_EQ(result.type(), legate::float64()); EXPECT_EQ(result.shape(), expect_shape); auto expect = expect_result[k]; @@ -121,13 +121,13 @@ auto test_eye_3_4(std::vector& k_vals, std::optional type std::vector expect_shape = {3, 4}; for (auto k : k_vals) { if (type.has_value()) { - auto result = cunumeric::eye(3, 4, k, type.value()); + auto result = cupynumeric::eye(3, 4, k, type.value()); EXPECT_EQ(result.type(), type.value()); EXPECT_EQ(result.shape(), expect_shape); auto expect = expect_result[k]; check_array_eq(result, expect.data(), expect.size()); } else { - auto result = cunumeric::eye(3, 4, k); + auto result = cupynumeric::eye(3, 4, k); EXPECT_EQ(result.type(), legate::float64()); EXPECT_EQ(result.shape(), expect_shape); auto expect = expect_result[k]; @@ -145,13 +145,13 @@ auto test_eye_square_3(std::optional> k_vals = std::nullopt if (k_vals.has_value()) { for (auto k : k_vals.value()) { if (type.has_value()) { - auto result = cunumeric::eye(3, std::nullopt, k, type.value()); + auto result = cupynumeric::eye(3, std::nullopt, k, type.value()); EXPECT_EQ(result.type(), type.value()); EXPECT_EQ(result.shape(), expect_shape); auto expect = expect_result[k]; check_array_eq(result, expect.data(), expect.size()); } else { - auto result = cunumeric::eye(3, std::nullopt, k); + auto result = cupynumeric::eye(3, std::nullopt, k); EXPECT_EQ(result.type(), legate::float64()); auto expect = expect_result[k]; check_array_eq(result, expect.data(), expect.size()); @@ -159,13 +159,13 @@ auto test_eye_square_3(std::optional> k_vals = std::nullopt } } else { if (type.has_value()) { - auto result = cunumeric::eye(3, std::nullopt, 0, type.value()); + auto result = cupynumeric::eye(3, std::nullopt, 0, type.value()); EXPECT_EQ(result.type(), type.value()); EXPECT_EQ(result.shape(), expect_shape); auto expect = expect_result[0]; check_array_eq(result, expect.data(), expect.size()); } else { - auto result = cunumeric::eye(3); + auto result = cupynumeric::eye(3); EXPECT_EQ(result.type(), legate::float64()); EXPECT_EQ(result.shape(), expect_shape); auto expect = expect_result[0]; @@ -214,14 +214,14 @@ void eye_square() void eye_input_zero() { // Test n=0 - auto result1 = cunumeric::eye(0); + auto result1 = cupynumeric::eye(0); std::vector expect_shape1 = {0, 0}; EXPECT_EQ(result1.type(), legate::float64()); EXPECT_EQ(result1.size(), 0); EXPECT_EQ(result1.shape(), expect_shape1); // Test m=0 - auto result2 = cunumeric::eye(3, 0); + auto result2 = cupynumeric::eye(3, 0); std::vector expect_shape2 = {3, 0}; EXPECT_EQ(result2.type(), legate::float64()); EXPECT_EQ(result2.size(), 0); @@ -233,7 +233,7 @@ void eye_large_array() const size_t n_or_m = 1000; // Test 1000 * 1000 array - auto result1 = cunumeric::eye(n_or_m); + auto result1 = cupynumeric::eye(n_or_m); std::vector expect_shape1 = {n_or_m, n_or_m}; std::array expect_result1; expect_result1.fill(0); @@ -246,7 +246,7 @@ void eye_large_array() // Test 3 * 1000 array const size_t n = 3; - auto result2 = cunumeric::eye(n, n_or_m, 0, legate::int32()); + auto result2 = cupynumeric::eye(n, n_or_m, 0, legate::int32()); std::vector expect_shape2 = {n, n_or_m}; std::array expect_result2; expect_result2.fill(0); @@ -259,7 +259,7 @@ void eye_large_array() // Test 1000 * 3 array const size_t m = 3; - auto result3 = cunumeric::eye(n_or_m, m, 0, legate::complex64()); + auto result3 = cupynumeric::eye(n_or_m, m, 0, legate::complex64()); std::vector expect_shape3 = {n_or_m, m}; std::array, n_or_m * m> expect_result3; expect_result3.fill(0); @@ -276,16 +276,16 @@ void eye_large_array() void eye_negative() { // Test bad n - EXPECT_THROW(cunumeric::eye(-1), std::invalid_argument); - EXPECT_THROW(cunumeric::eye(-1, 3), std::invalid_argument); + EXPECT_THROW(cupynumeric::eye(-1), std::invalid_argument); + EXPECT_THROW(cupynumeric::eye(-1, 3), std::invalid_argument); // Test bad m - EXPECT_THROW(cunumeric::eye(3, -1), std::invalid_argument); - EXPECT_THROW(cunumeric::eye(-1, -1), std::invalid_argument); + EXPECT_THROW(cupynumeric::eye(3, -1), std::invalid_argument); + EXPECT_THROW(cupynumeric::eye(-1, -1), std::invalid_argument); // Test bad dtype - EXPECT_THROW(cunumeric::eye(3, std::nullopt, 0, legate::binary_type(2)), std::invalid_argument); - EXPECT_THROW(cunumeric::eye(3, std::nullopt, 0, legate::point_type(2)), std::invalid_argument); + EXPECT_THROW(cupynumeric::eye(3, std::nullopt, 0, legate::binary_type(2)), std::invalid_argument); + EXPECT_THROW(cupynumeric::eye(3, std::nullopt, 0, legate::point_type(2)), std::invalid_argument); } // void cpp_test() diff --git a/tests/cpp/integration/test_fill.cc b/tests/cpp/integration/test_fill.cc index 27800d488b..76daaab284 100644 --- a/tests/cpp/integration/test_fill.cc +++ b/tests/cpp/integration/test_fill.cc @@ -18,7 +18,7 @@ #include #include -using namespace cunumeric; +using namespace cupynumeric; namespace { diff --git a/tests/cpp/integration/test_flip.cc b/tests/cpp/integration/test_flip.cc index 80226665e1..f20afe7a17 100644 --- a/tests/cpp/integration/test_flip.cc +++ b/tests/cpp/integration/test_flip.cc @@ -20,7 +20,7 @@ #include #include "legate.h" -#include "cunumeric.h" +#include "cupynumeric.h" #include "util.inl" auto get_flip_expect_result_int() @@ -330,7 +330,7 @@ void test_flip(std::array& in_array, std::vector shape, std::optional> axis = std::nullopt) { - auto A1 = cunumeric::zeros(shape, leg_type); + auto A1 = cupynumeric::zeros(shape, leg_type); if (in_array.size() != 0) { if (in_array.size() == 1) { A1.fill(legate::Scalar(in_array[0])); @@ -339,7 +339,7 @@ void test_flip(std::array& in_array, } } - auto B1 = cunumeric::flip(A1, axis); + auto B1 = cupynumeric::flip(A1, axis); check_array_eq(B1, expect.data(), expect.size()); } @@ -636,23 +636,23 @@ void flip_single_item_array() void flip_negative_test() { - auto in_array = cunumeric::zeros({2, 3}, legate::int32()); + auto in_array = cupynumeric::zeros({2, 3}, legate::int32()); // Test axis out-of-bound auto axes1 = {12}; - EXPECT_THROW(cunumeric::flip(in_array, axes1), std::invalid_argument); + EXPECT_THROW(cupynumeric::flip(in_array, axes1), std::invalid_argument); // Test axis out-of-bound negative auto axes2 = {-12}; - EXPECT_THROW(cunumeric::flip(in_array, axes2), std::invalid_argument); + EXPECT_THROW(cupynumeric::flip(in_array, axes2), std::invalid_argument); // Test axis repeated axis auto axes3 = {1, 1}; - EXPECT_THROW(cunumeric::flip(in_array, axes3), std::invalid_argument); + EXPECT_THROW(cupynumeric::flip(in_array, axes3), std::invalid_argument); // Test axis out-of-bound multiple auto axes4 = {1, 2}; - EXPECT_THROW(cunumeric::flip(in_array, axes4), std::invalid_argument); + EXPECT_THROW(cupynumeric::flip(in_array, axes4), std::invalid_argument); } // void cpp_test() diff --git a/tests/cpp/integration/test_logical.cc b/tests/cpp/integration/test_logical.cc index 8e6ff6bac6..f1c1908abc 100644 --- a/tests/cpp/integration/test_logical.cc +++ b/tests/cpp/integration/test_logical.cc @@ -19,7 +19,7 @@ #include #include "legate.h" -#include "cunumeric.h" +#include "cupynumeric.h" #include "util.inl" template & in_array, std::array& expect_result, legate::Type leg_type, std::vector shape, - std::vector axis = {}, - std::optional out = std::nullopt, - bool keepdims = false, - std::optional where = std::nullopt) + std::vector axis = {}, + std::optional out = std::nullopt, + bool keepdims = false, + std::optional where = std::nullopt) { - auto A1 = cunumeric::zeros(shape, leg_type); + auto A1 = cupynumeric::zeros(shape, leg_type); if (in_array.size() != 0) { if (in_array.size() == 1) { A1.fill(legate::Scalar(in_array[0])); @@ -47,10 +47,10 @@ void test_all(std::array& in_array, } if (!out.has_value()) { - auto B1 = cunumeric::all(A1, axis, std::nullopt, keepdims, where); + auto B1 = cupynumeric::all(A1, axis, std::nullopt, keepdims, where); check_array_eq(B1, expect_result.data(), expect_result.size()); } else { - cunumeric::all(A1, axis, out, keepdims, where); + cupynumeric::all(A1, axis, out, keepdims, where); check_array_eq(out.value(), expect_result.data(), expect_result.size()); } } @@ -222,7 +222,7 @@ void test_all_where_input() // Test where with multiple bool values std::array where_in1 = {true, false}; - auto where_array1 = cunumeric::zeros({2}, legate::bool_()); + auto where_array1 = cupynumeric::zeros({2}, legate::bool_()); assign_values_to_array(where_array1, where_in1.data(), where_in1.size()); std::array expect_val1 = {true}; @@ -231,7 +231,7 @@ void test_all_where_input() // Test where with single bool value std::array where_in2 = {true}; - auto where_array2 = cunumeric::zeros({1}, legate::bool_()); + auto where_array2 = cupynumeric::zeros({1}, legate::bool_()); assign_values_to_array(where_array2, where_in2.data(), where_in2.size()); std::array expect_val2 = {false}; @@ -239,7 +239,7 @@ void test_all_where_input() in_array, expect_val2, legate::bool_(), shape, {}, std::nullopt, false, where_array2); std::array where_in3 = {false}; - auto where_array3 = cunumeric::zeros({1}, legate::bool_()); + auto where_array3 = cupynumeric::zeros({1}, legate::bool_()); assign_values_to_array(where_array3, where_in3.data(), where_in3.size()); std::array expect_val3 = {true}; @@ -254,21 +254,21 @@ void test_all_out_input() std::vector out_shape = {2, 2}; std::vector axis = {0}; - auto out1 = cunumeric::zeros(out_shape, legate::int32()); + auto out1 = cupynumeric::zeros(out_shape, legate::int32()); std::array expect_val1 = {0, 1, 1, 1}; test_all(in_array, expect_val1, legate::int32(), shape, axis, out1); - auto out2 = cunumeric::zeros(out_shape, legate::float64()); + auto out2 = cupynumeric::zeros(out_shape, legate::float64()); std::array expect_val2 = {0.0, 1.0, 1.0, 1.0}; test_all(in_array, expect_val2, legate::int32(), shape, axis, out2); - auto out3 = cunumeric::zeros(out_shape, legate::complex64()); + auto out3 = cupynumeric::zeros(out_shape, legate::complex64()); std::array, 4> expect_val3 = { complex(0, 0), complex(1, 0), complex(1, 0), complex(1, 0)}; test_all, 8, 4, 3, 2>( in_array, expect_val3, legate::int32(), shape, axis, out3); - auto out4 = cunumeric::zeros(out_shape, legate::bool_()); + auto out4 = cupynumeric::zeros(out_shape, legate::bool_()); std::array expect_val4 = {false, true, true, true}; test_all(in_array, expect_val4, legate::int32(), shape, axis, out4); } @@ -375,65 +375,65 @@ void test_all_invalid_axis() { std::array in_array = {5, 10, 0, 100}; std::vector shape = {1, 2, 2}; - auto array = cunumeric::zeros(shape, legate::int32()); + auto array = cupynumeric::zeros(shape, legate::int32()); assign_values_to_array(array, in_array.data(), in_array.size()); // Test out-of-bound std::vector axis1 = {-4, 3}; - EXPECT_THROW(cunumeric::all(array, axis1), std::invalid_argument); + EXPECT_THROW(cupynumeric::all(array, axis1), std::invalid_argument); std::vector axis2 = {0, 3}; - EXPECT_THROW(cunumeric::all(array, axis2), std::invalid_argument); + EXPECT_THROW(cupynumeric::all(array, axis2), std::invalid_argument); // Test repeated axes std::vector axis3 = {1, 1}; - EXPECT_THROW(cunumeric::all(array, axis3), std::invalid_argument); + EXPECT_THROW(cupynumeric::all(array, axis3), std::invalid_argument); std::vector axis4 = {-1, 2}; - EXPECT_THROW(cunumeric::all(array, axis4), std::invalid_argument); + EXPECT_THROW(cupynumeric::all(array, axis4), std::invalid_argument); } void test_all_invalid_shape() { std::array in_array = {5, 10, 0, 100}; std::vector shape = {1, 2, 2}; - auto array = cunumeric::zeros(shape, legate::int32()); + auto array = cupynumeric::zeros(shape, legate::int32()); assign_values_to_array(array, in_array.data(), in_array.size()); std::vector out_shape1 = {1}; - auto out1 = cunumeric::zeros(out_shape1, legate::int32()); - EXPECT_THROW(cunumeric::all(array, {}, out1), std::invalid_argument); + auto out1 = cupynumeric::zeros(out_shape1, legate::int32()); + EXPECT_THROW(cupynumeric::all(array, {}, out1), std::invalid_argument); std::vector out_shape2 = {2}; std::vector axis2 = {1}; - auto out2 = cunumeric::zeros(out_shape2, legate::int32()); - EXPECT_THROW(cunumeric::all(array, axis2, out2), std::invalid_argument); + auto out2 = cupynumeric::zeros(out_shape2, legate::int32()); + EXPECT_THROW(cupynumeric::all(array, axis2, out2), std::invalid_argument); std::vector out_shape3 = {2, 2}; std::vector axis3 = {1}; - auto out3 = cunumeric::zeros(out_shape3, legate::int32()); - EXPECT_THROW(cunumeric::all(array, axis3, out3), std::invalid_argument); + auto out3 = cupynumeric::zeros(out_shape3, legate::int32()); + EXPECT_THROW(cupynumeric::all(array, axis3, out3), std::invalid_argument); } void test_all_invalid_where() { std::array in_array = {5, 10, 0, 100}; std::vector shape = {1, 2, 2}; - auto array = cunumeric::zeros(shape, legate::int32()); + auto array = cupynumeric::zeros(shape, legate::int32()); assign_values_to_array(array, in_array.data(), in_array.size()); // Test where with invalid type std::array in_where1 = {0, 1, 0, 1}; - auto where1 = cunumeric::zeros(shape, legate::int32()); + auto where1 = cupynumeric::zeros(shape, legate::int32()); assign_values_to_array(where1, in_where1.data(), in_where1.size()); - EXPECT_THROW(cunumeric::all(array, {}, std::nullopt, false, where1), std::invalid_argument); + EXPECT_THROW(cupynumeric::all(array, {}, std::nullopt, false, where1), std::invalid_argument); // Test where with invalid shape std::vector where_shape = {2, 2, 1}; std::array in_where2 = {false, true, false, true}; - auto where2 = cunumeric::zeros(where_shape, legate::bool_()); + auto where2 = cupynumeric::zeros(where_shape, legate::bool_()); assign_values_to_array(where2, in_where2.data(), in_where2.size()); - EXPECT_THROW(cunumeric::all(array, {}, std::nullopt, false, where2), std::exception); + EXPECT_THROW(cupynumeric::all(array, {}, std::nullopt, false, where2), std::exception); } // void cpp_test() diff --git a/tests/cpp/integration/test_moveaxis.cc b/tests/cpp/integration/test_moveaxis.cc index ad0db4dfa9..4fde5db796 100644 --- a/tests/cpp/integration/test_moveaxis.cc +++ b/tests/cpp/integration/test_moveaxis.cc @@ -20,7 +20,7 @@ #include #include "legate.h" -#include "cunumeric.h" +#include "cupynumeric.h" #include "util.inl" template @@ -31,9 +31,9 @@ static void moveaxis_int32_test(std::vector input, std::vector source, std::vector destination) { - auto a_input = cunumeric::zeros(in_shape, legate::int32()); + auto a_input = cupynumeric::zeros(in_shape, legate::int32()); assign_values_to_array(a_input, input.data(), input.size()); - auto a_output = cunumeric::moveaxis(a_input, source, destination); + auto a_output = cupynumeric::moveaxis(a_input, source, destination); check_array_eq(a_output, exp.data(), exp.size()); EXPECT_EQ(a_output.shape(), out_shape); } @@ -43,8 +43,8 @@ static void moveaxis_int32_test_2(std::vector in_shape, std::vector source, std::vector destination) { - auto a_input = cunumeric::zeros(in_shape, legate::int32()); - auto a_output = cunumeric::moveaxis(a_input, source, destination); + auto a_input = cupynumeric::zeros(in_shape, legate::int32()); + auto a_output = cupynumeric::moveaxis(a_input, source, destination); EXPECT_EQ(a_output.shape(), out_shape); } @@ -66,25 +66,25 @@ TEST(MoveAxis, SpecialArrays) // test single element array { std::vector input{99}; - auto a = cunumeric::zeros({1}, legate::int32()); + auto a = cupynumeric::zeros({1}, legate::int32()); a.fill(legate::Scalar(input[0])); - auto a_out = cunumeric::moveaxis(a, {0}, {-1}); + auto a_out = cupynumeric::moveaxis(a, {0}, {-1}); check_array_eq(a_out, input.data(), input.size()); EXPECT_EQ(a_out.shape(), a.shape()); } { std::vector input{-100}; - auto a = cunumeric::zeros({1, 1}, legate::int32()); + auto a = cupynumeric::zeros({1, 1}, legate::int32()); a.fill(legate::Scalar(input[0])); - auto a_out = cunumeric::moveaxis(a, {0, 1}, {-1, -2}); + auto a_out = cupynumeric::moveaxis(a, {0, 1}, {-1, -2}); check_array_eq(a_out, input.data(), input.size()); EXPECT_EQ(a_out.shape(), a.shape()); } // test empty array { - auto a = cunumeric::zeros({0}, legate::int32()); - auto a_out = cunumeric::moveaxis(a, {0}, {-1}); + auto a = cupynumeric::zeros({0}, legate::int32()); + auto a_out = cupynumeric::moveaxis(a, {0}, {-1}); EXPECT_EQ(a_out.shape(), a.shape()); } } @@ -129,22 +129,22 @@ TEST(MoveAxis, With_empty_array) TEST(MoveAxisErrors, Repeated_axis) { - auto x = cunumeric::zeros({3, 4, 5}, legate::int32()); - EXPECT_THROW(cunumeric::moveaxis(x, {0, 0}, {1, 0}), std::invalid_argument); - EXPECT_THROW(cunumeric::moveaxis(x, {0, 1}, {0, -3}), std::invalid_argument); + auto x = cupynumeric::zeros({3, 4, 5}, legate::int32()); + EXPECT_THROW(cupynumeric::moveaxis(x, {0, 0}, {1, 0}), std::invalid_argument); + EXPECT_THROW(cupynumeric::moveaxis(x, {0, 1}, {0, -3}), std::invalid_argument); } TEST(MoveAxisErrors, Axis_out_of_bound) { - auto x = cunumeric::zeros({3, 4, 5}, legate::int32()); - EXPECT_THROW(cunumeric::moveaxis(x, {0, 3}, {0, 1}), std::invalid_argument); - EXPECT_THROW(cunumeric::moveaxis(x, {0, 1}, {0, -4}), std::invalid_argument); - EXPECT_THROW(cunumeric::moveaxis(x, {4}, {0}), std::invalid_argument); - EXPECT_THROW(cunumeric::moveaxis(x, {0}, {-4}), std::invalid_argument); + auto x = cupynumeric::zeros({3, 4, 5}, legate::int32()); + EXPECT_THROW(cupynumeric::moveaxis(x, {0, 3}, {0, 1}), std::invalid_argument); + EXPECT_THROW(cupynumeric::moveaxis(x, {0, 1}, {0, -4}), std::invalid_argument); + EXPECT_THROW(cupynumeric::moveaxis(x, {4}, {0}), std::invalid_argument); + EXPECT_THROW(cupynumeric::moveaxis(x, {0}, {-4}), std::invalid_argument); } TEST(MoveAxisErrors, Axis_with_different_length) { - auto x = cunumeric::zeros({3, 4, 5}, legate::int32()); - EXPECT_THROW(cunumeric::moveaxis(x, {0}, {1, 0}), std::invalid_argument); + auto x = cupynumeric::zeros({3, 4, 5}, legate::int32()); + EXPECT_THROW(cupynumeric::moveaxis(x, {0}, {1, 0}), std::invalid_argument); } diff --git a/tests/cpp/integration/test_msort.cc b/tests/cpp/integration/test_msort.cc index 6516532450..34d8138d83 100644 --- a/tests/cpp/integration/test_msort.cc +++ b/tests/cpp/integration/test_msort.cc @@ -20,7 +20,7 @@ #include #include "legate.h" -#include "cunumeric.h" +#include "cupynumeric.h" #include "util.inl" auto get_msort_expect_result_int() @@ -193,7 +193,7 @@ void test_msort(std::array& in_array, legate::Type leg_type, std::vector shape) { - auto A1 = cunumeric::zeros(shape, leg_type); + auto A1 = cupynumeric::zeros(shape, leg_type); if (in_array.size() != 0) { if (in_array.size() == 1) { A1.fill(legate::Scalar(in_array[0])); @@ -203,7 +203,7 @@ void test_msort(std::array& in_array, print_array(A1); } - auto B1 = cunumeric::msort(A1); + auto B1 = cupynumeric::msort(A1); check_array_eq(B1, expect.data(), expect.size()); } diff --git a/tests/cpp/integration/test_nonzero.cc b/tests/cpp/integration/test_nonzero.cc index 34516d840f..b1d1fb9f52 100644 --- a/tests/cpp/integration/test_nonzero.cc +++ b/tests/cpp/integration/test_nonzero.cc @@ -120,8 +120,8 @@ void test_nonzero(const std::vector& in_array, const std::vector>& expect, const std::vector& shape) { - auto array = cunumeric::mk_array(in_array, shape); - auto result_vec = cunumeric::nonzero(array); + auto array = cupynumeric::mk_array(in_array, shape); + auto result_vec = cupynumeric::nonzero(array); size_t result_size = result_vec.size(); ASSERT_EQ(result_size, expect.size()); std::vector expect_shape = {}; @@ -133,7 +133,7 @@ void test_nonzero(const std::vector& in_array, } } for (size_t i = 0; i < result_size; ++i) { - cunumeric::check_array(result_vec[i], expect[i], expect_shape); + cupynumeric::check_array(result_vec[i], expect[i], expect_shape); } } diff --git a/tests/cpp/integration/test_put.cc b/tests/cpp/integration/test_put.cc index ec2ec4fb02..d0fd3db97d 100644 --- a/tests/cpp/integration/test_put.cc +++ b/tests/cpp/integration/test_put.cc @@ -17,7 +17,7 @@ #include "common_utils.h" #include -using namespace cunumeric; +using namespace cupynumeric; namespace { template diff --git a/tests/cpp/integration/test_repartition.cc b/tests/cpp/integration/test_repartition.cc index 2d094576a6..4acd665641 100644 --- a/tests/cpp/integration/test_repartition.cc +++ b/tests/cpp/integration/test_repartition.cc @@ -18,9 +18,9 @@ #include #include #include "legate.h" -#include "cunumeric.h" +#include "cupynumeric.h" #include "util.inl" -#include "cunumeric/utilities/repartition.h" +#include "cupynumeric/utilities/repartition.h" namespace repartition_test { @@ -120,17 +120,17 @@ void repartition_2dbc_test(legate::AccessorRO input, size_t input_lld = in_rect.empty() ? 1 : (in_rect.hi[in_row_major ? 1 : 0] - in_rect.lo[in_row_major ? 1 : 0] + 1); - auto [buffer_2dbc, volume_2dbc, lld_2dbc] = cunumeric::repartition_matrix_2dbc(input_ptr, - input_volume, - in_row_major, - input_offset_r, - input_offset_c, - input_lld, - proc_r, - proc_c, - tile_r, - tile_c, - comm); + auto [buffer_2dbc, volume_2dbc, lld_2dbc] = cupynumeric::repartition_matrix_2dbc(input_ptr, + input_volume, + in_row_major, + input_offset_r, + input_offset_c, + input_lld, + proc_r, + proc_c, + tile_r, + tile_c, + comm); int32_t* output_ptr = output.ptr(out_rect.lo); size_t output_volume = out_rect.volume(); @@ -151,23 +151,23 @@ void repartition_2dbc_test(legate::AccessorRO input, std::cerr << stringStream.str(); } - cunumeric::repartition_matrix_block(buffer_2dbc, - volume_2dbc, - lld_2dbc, - local_rank, - proc_r, - proc_c, - tile_r, - tile_c, - output_ptr, - output_volume, - output_lld, - num_rows, - num_cols, - out_row_major, - output_offset_r, - output_offset_c, - comm); + cupynumeric::repartition_matrix_block(buffer_2dbc, + volume_2dbc, + lld_2dbc, + local_rank, + proc_r, + proc_c, + tile_r, + tile_c, + output_ptr, + output_volume, + output_lld, + num_rows, + num_cols, + out_row_major, + output_offset_r, + output_offset_c, + comm); } #endif @@ -272,8 +272,8 @@ void run_test_aligned_default_launch(std::vector& data_shape, // generate data size_t volume = data_shape[0] * data_shape[1]; - auto data_input = cunumeric::zeros(data_shape, legate::int32()); - auto data_output = cunumeric::zeros(data_shape, legate::int32()); + auto data_input = cupynumeric::zeros(data_shape, legate::int32()); + auto data_output = cupynumeric::zeros(data_shape, legate::int32()); if (volume != 0) { if (volume == 1) { data_input.fill(legate::Scalar(0)); diff --git a/tests/cpp/integration/test_repeat.cc b/tests/cpp/integration/test_repeat.cc index 8ab064a973..dc89a1448b 100644 --- a/tests/cpp/integration/test_repeat.cc +++ b/tests/cpp/integration/test_repeat.cc @@ -19,7 +19,7 @@ #include #include -using namespace cunumeric; +using namespace cupynumeric; namespace { @@ -115,7 +115,7 @@ TEST(Repeat, test_array_empty_repeats_valid) } } -// numpy fail, cunumeric pass +// numpy fail, cupynumeric pass TEST(Repeat, test_array_empty_repeats_invalid_negative) { std::vector> repeats_list{{3, 4}, {1, 2, 3}}; diff --git a/tests/cpp/integration/test_reshape.cc b/tests/cpp/integration/test_reshape.cc index 6a254bbe00..c1ed9534a1 100644 --- a/tests/cpp/integration/test_reshape.cc +++ b/tests/cpp/integration/test_reshape.cc @@ -16,7 +16,7 @@ #include "common_utils.h" -using namespace cunumeric; +using namespace cupynumeric; namespace { diff --git a/tests/cpp/integration/test_sort.cc b/tests/cpp/integration/test_sort.cc index 8c4c5c3e86..9a5a76a47a 100644 --- a/tests/cpp/integration/test_sort.cc +++ b/tests/cpp/integration/test_sort.cc @@ -20,7 +20,7 @@ #include #include "legate.h" -#include "cunumeric.h" +#include "cupynumeric.h" #include "util.inl" auto get_expect_result_int() @@ -498,7 +498,7 @@ void test_sort(std::array& in_array, std::vector shape, std::optional axis) { - auto A1 = cunumeric::zeros(shape, leg_type); + auto A1 = cupynumeric::zeros(shape, leg_type); if (in_array.size() != 0) { if (in_array.size() == 1) { A1.fill(legate::Scalar(in_array[0])); @@ -508,7 +508,7 @@ void test_sort(std::array& in_array, } std::vector algos = {"quicksort", "mergesort", "heapsort", "stable"}; for (auto algo = algos.begin(); algo < algos.end(); ++algo) { - auto B1 = cunumeric::sort(A1, axis, *algo); + auto B1 = cupynumeric::sort(A1, axis, *algo); if (in_array.size() != 0) { check_array_eq(B1, expect.data(), expect.size()); } @@ -615,14 +615,14 @@ void sort_single_item_array() void sort_negative_test() { - auto in_ar1 = cunumeric::zeros({2, 3}, legate::int32()); + auto in_ar1 = cupynumeric::zeros({2, 3}, legate::int32()); // Test invalid input sort axis - EXPECT_THROW(cunumeric::sort(in_ar1, 2, "quicksort"), std::invalid_argument); - EXPECT_THROW(cunumeric::sort(in_ar1, -3, "quicksort"), std::invalid_argument); + EXPECT_THROW(cupynumeric::sort(in_ar1, 2, "quicksort"), std::invalid_argument); + EXPECT_THROW(cupynumeric::sort(in_ar1, -3, "quicksort"), std::invalid_argument); // Test invalid input algorithm - EXPECT_THROW(cunumeric::sort(in_ar1, 0, "negative"), std::invalid_argument); + EXPECT_THROW(cupynumeric::sort(in_ar1, 0, "negative"), std::invalid_argument); } // void cpp_test() diff --git a/tests/cpp/integration/test_sort_complex.cc b/tests/cpp/integration/test_sort_complex.cc index e2f237da5a..53c36c3758 100644 --- a/tests/cpp/integration/test_sort_complex.cc +++ b/tests/cpp/integration/test_sort_complex.cc @@ -20,7 +20,7 @@ #include #include "legate.h" -#include "cunumeric.h" +#include "cupynumeric.h" #include "util.inl" template @@ -197,7 +197,7 @@ void test_sort_complex(std::array& in_array, legate::Type leg_type, std::vector shape) { - auto A1 = cunumeric::zeros(shape, leg_type); + auto A1 = cupynumeric::zeros(shape, leg_type); if (in_array.size() != 0) { if (in_array.size() == 1) { A1.fill(legate::Scalar(in_array[0])); @@ -205,7 +205,7 @@ void test_sort_complex(std::array& in_array, assign_values_to_array(A1, in_array.data(), in_array.size()); } } - auto B1 = cunumeric::sort_complex(A1); + auto B1 = cupynumeric::sort_complex(A1); if (in_array.size() != 0) { check_array_eq(B1, expect.data(), expect.size()); } diff --git a/tests/cpp/integration/test_squeeze.cc b/tests/cpp/integration/test_squeeze.cc index 116cabec07..f2147b3bf7 100644 --- a/tests/cpp/integration/test_squeeze.cc +++ b/tests/cpp/integration/test_squeeze.cc @@ -17,7 +17,7 @@ #include "common_utils.h" #include -using namespace cunumeric; +using namespace cupynumeric; namespace { typedef std::vector, std::vector>> VEC_SHAPE_AXES; diff --git a/tests/cpp/integration/test_swapaxes.cc b/tests/cpp/integration/test_swapaxes.cc index fbadbc96c1..15edf5787d 100644 --- a/tests/cpp/integration/test_swapaxes.cc +++ b/tests/cpp/integration/test_swapaxes.cc @@ -20,67 +20,67 @@ #include #include "legate.h" -#include "cunumeric.h" +#include "cupynumeric.h" #include "util.inl" void swapaxes_test() { // Test small { - auto A = cunumeric::zeros({3, 3}, legate::int32()); + auto A = cupynumeric::zeros({3, 3}, legate::int32()); EXPECT_EQ(A.shape(), (std::vector{3, 3})); - auto B = cunumeric::swapaxes(A, 0, 1); + auto B = cupynumeric::swapaxes(A, 0, 1); EXPECT_EQ(B.shape(), (std::vector{3, 3})); } // Test tall { - auto A_tall = cunumeric::zeros({300, 3}, legate::int32()); + auto A_tall = cupynumeric::zeros({300, 3}, legate::int32()); EXPECT_EQ(A_tall.shape(), (std::vector{300, 3})); - auto B_tall = cunumeric::swapaxes(A_tall, 0, 1); + auto B_tall = cupynumeric::swapaxes(A_tall, 0, 1); EXPECT_EQ(B_tall.shape(), (std::vector{3, 300})); } // Test wide { - auto A_wide = cunumeric::zeros({3, 300}, legate::int32()); + auto A_wide = cupynumeric::zeros({3, 300}, legate::int32()); EXPECT_EQ(A_wide.shape(), (std::vector{3, 300})); - auto B_wide = cunumeric::swapaxes(A_wide, 0, 1); + auto B_wide = cupynumeric::swapaxes(A_wide, 0, 1); EXPECT_EQ(B_wide.shape(), (std::vector{300, 3})); } // Test big { - auto A_big = cunumeric::zeros({300, 300}, legate::int32()); + auto A_big = cupynumeric::zeros({300, 300}, legate::int32()); EXPECT_EQ(A_big.shape(), (std::vector{300, 300})); - auto B_big = cunumeric::swapaxes(A_big, 0, 1); + auto B_big = cupynumeric::swapaxes(A_big, 0, 1); EXPECT_EQ(B_big.shape(), (std::vector{300, 300})); } // Test 3-dim array with different swap axes { - auto A = cunumeric::zeros({3, 4, 5}, legate::int32()); + auto A = cupynumeric::zeros({3, 4, 5}, legate::int32()); EXPECT_EQ(A.shape(), (std::vector{3, 4, 5})); - auto B1 = cunumeric::swapaxes(A, 0, 0); + auto B1 = cupynumeric::swapaxes(A, 0, 0); EXPECT_EQ(B1.shape(), (std::vector{3, 4, 5})); - auto B2 = cunumeric::swapaxes(A, -3, 1); + auto B2 = cupynumeric::swapaxes(A, -3, 1); EXPECT_EQ(B2.shape(), (std::vector{4, 3, 5})); - auto B3 = cunumeric::swapaxes(A, 0, 2); + auto B3 = cupynumeric::swapaxes(A, 0, 2); EXPECT_EQ(B3.shape(), (std::vector{5, 4, 3})); - auto B4 = cunumeric::swapaxes(A, -3, -2); + auto B4 = cupynumeric::swapaxes(A, -3, -2); EXPECT_EQ(B4.shape(), (std::vector{4, 3, 5})); } // Test empty array { - auto A = cunumeric::zeros({0}, legate::int32()); + auto A = cupynumeric::zeros({0}, legate::int32()); EXPECT_EQ(A.shape(), (std::vector{0})); - auto B = cunumeric::swapaxes(A, 0, 0); + auto B = cupynumeric::swapaxes(A, 0, 0); EXPECT_EQ(B.shape(), (std::vector{0})); } } @@ -88,13 +88,13 @@ void swapaxes_test() void swapaxes_negative_test() { // Test out-of-bound1 - auto A = cunumeric::zeros({3, 3}, legate::int32()); - EXPECT_THROW(cunumeric::swapaxes(A, 3, 0), std::invalid_argument); - EXPECT_THROW(cunumeric::swapaxes(A, 0, 3), std::invalid_argument); + auto A = cupynumeric::zeros({3, 3}, legate::int32()); + EXPECT_THROW(cupynumeric::swapaxes(A, 3, 0), std::invalid_argument); + EXPECT_THROW(cupynumeric::swapaxes(A, 0, 3), std::invalid_argument); // Test out-of-bound2 - EXPECT_THROW(cunumeric::swapaxes(A, -4, 0), std::invalid_argument); - EXPECT_THROW(cunumeric::swapaxes(A, 0, -4), std::invalid_argument); + EXPECT_THROW(cupynumeric::swapaxes(A, -4, 0), std::invalid_argument); + EXPECT_THROW(cupynumeric::swapaxes(A, 0, -4), std::invalid_argument); } // void cpp_test() diff --git a/tests/cpp/integration/test_transpose.cc b/tests/cpp/integration/test_transpose.cc index 0124588c60..6047fe094a 100644 --- a/tests/cpp/integration/test_transpose.cc +++ b/tests/cpp/integration/test_transpose.cc @@ -16,7 +16,7 @@ #include #include "legate.h" -#include "cunumeric.h" +#include "cupynumeric.h" #include "util.inl" template @@ -26,15 +26,15 @@ void transpose_int32_test(std::array input, std::vector out_shape, std::optional> axes = std::nullopt) { - auto a_input = cunumeric::zeros(in_shape, legate::int32()); + auto a_input = cupynumeric::zeros(in_shape, legate::int32()); assign_values_to_array(a_input, input.data(), input.size()); - auto a_output = cunumeric::array(out_shape, legate::int32()); + auto a_output = cupynumeric::array(out_shape, legate::int32()); if (axes) { - a_output = cunumeric::transpose(a_input, axes.value()); + a_output = cupynumeric::transpose(a_input, axes.value()); } else { - a_output = cunumeric::transpose(a_input); + a_output = cupynumeric::transpose(a_input); } check_array_eq(a_output, exp.data(), exp.size()); EXPECT_EQ(a_output.shape(), out_shape); @@ -114,9 +114,9 @@ TEST(Transpose, DefaultType) std::vector in_shape = {2, 3}; std::vector out_shape = {3, 2}; - auto a_input = cunumeric::zeros(in_shape); + auto a_input = cupynumeric::zeros(in_shape); assign_values_to_array(a_input, input.data(), input.size()); - auto a_output = cunumeric::transpose(a_input); + auto a_output = cupynumeric::transpose(a_input); check_array_eq(a_output, exp.data(), exp.size()); EXPECT_EQ(a_output.shape(), out_shape); } @@ -129,10 +129,11 @@ TEST(TransposeErrors, InvalidAxes) std::vector in_shape = {2, 3}; std::vector out_shape = {3, 2}; - auto a_input = cunumeric::zeros(in_shape); + auto a_input = cupynumeric::zeros(in_shape); assign_values_to_array(a_input, input.data(), input.size()); - EXPECT_THROW(cunumeric::transpose(a_input, (std::vector){0, 1, 2}), + EXPECT_THROW(cupynumeric::transpose(a_input, (std::vector){0, 1, 2}), + std::invalid_argument); + EXPECT_THROW(cupynumeric::transpose(a_input, (std::vector){1}), std::invalid_argument); + EXPECT_THROW(cupynumeric::transpose(a_input, (std::vector){3, 4}), std::invalid_argument); - EXPECT_THROW(cunumeric::transpose(a_input, (std::vector){1}), std::invalid_argument); - EXPECT_THROW(cunumeric::transpose(a_input, (std::vector){3, 4}), std::invalid_argument); } diff --git a/tests/cpp/integration/test_trilu.cc b/tests/cpp/integration/test_trilu.cc index c83f51bc6c..29e7915152 100644 --- a/tests/cpp/integration/test_trilu.cc +++ b/tests/cpp/integration/test_trilu.cc @@ -17,7 +17,7 @@ #include "common_utils.h" #include -using namespace cunumeric; +using namespace cupynumeric; namespace { diff --git a/tests/cpp/integration/test_unique.cc b/tests/cpp/integration/test_unique.cc index b7aa1bacab..36ee0797a0 100644 --- a/tests/cpp/integration/test_unique.cc +++ b/tests/cpp/integration/test_unique.cc @@ -18,7 +18,7 @@ #include #include -using namespace cunumeric; +using namespace cupynumeric; namespace { diff --git a/tests/cpp/integration/test_where.cc b/tests/cpp/integration/test_where.cc index 5abec00cfc..52eea72c9a 100644 --- a/tests/cpp/integration/test_where.cc +++ b/tests/cpp/integration/test_where.cc @@ -20,10 +20,10 @@ #include #include "legate.h" -#include "cunumeric.h" +#include "cupynumeric.h" #include "common_utils.h" -using namespace cunumeric; +using namespace cupynumeric; template void test_where_basic(std::vector in_a, diff --git a/tests/cpp/integration/test_window.cc b/tests/cpp/integration/test_window.cc index f25cdf81bf..3ca0631702 100644 --- a/tests/cpp/integration/test_window.cc +++ b/tests/cpp/integration/test_window.cc @@ -17,7 +17,7 @@ #include #include "common_utils.h" -using namespace cunumeric; +using namespace cupynumeric; namespace { @@ -157,7 +157,7 @@ TEST_P(BartlettTest, Basic) { auto& [input, expected_values, expected_shape] = GetParam(); - auto result = cunumeric::bartlett(input); + auto result = cupynumeric::bartlett(input); check_array_near(result, expected_values, expected_shape); } @@ -165,7 +165,7 @@ TEST_P(BlackmanTest, Basic) { auto& [input, expected_values, expected_shape] = GetParam(); - auto result = cunumeric::blackman(input); + auto result = cupynumeric::blackman(input); check_array_near(result, expected_values, expected_shape); } @@ -173,7 +173,7 @@ TEST_P(HammingTest, Basic) { auto& [input, expected_values, expected_shape] = GetParam(); - auto result = cunumeric::hamming(input); + auto result = cupynumeric::hamming(input); check_array_near(result, expected_values, expected_shape); } @@ -181,7 +181,7 @@ TEST_P(HanningTest, Basic) { auto& [input, expected_values, expected_shape] = GetParam(); - auto result = cunumeric::hanning(input); + auto result = cupynumeric::hanning(input); check_array_near(result, expected_values, expected_shape); } @@ -189,7 +189,7 @@ TEST_P(KaiserTest, Basic) { auto& [input, beta_input, expected_values, expected_shape] = GetParam(); - auto result = cunumeric::kaiser(input, beta_input); + auto result = cupynumeric::kaiser(input, beta_input); check_array_near(result, expected_values, expected_shape); } diff --git a/tests/cpp/integration/test_zeros.cc b/tests/cpp/integration/test_zeros.cc index 861bf0f6bc..e0cc1b838d 100644 --- a/tests/cpp/integration/test_zeros.cc +++ b/tests/cpp/integration/test_zeros.cc @@ -16,7 +16,7 @@ #include "common_utils.h" -using namespace cunumeric; +using namespace cupynumeric; using Code = legate::Type::Code; namespace { diff --git a/tests/cpp/integration/util.inl b/tests/cpp/integration/util.inl index 4a18896284..c532c2b24e 100644 --- a/tests/cpp/integration/util.inl +++ b/tests/cpp/integration/util.inl @@ -175,7 +175,7 @@ struct copy_array_fn { }; template -void print_array(cunumeric::NDArray array) +void print_array(cupynumeric::NDArray array) { auto acc = array.get_read_accessor(); auto& shape = array.shape(); @@ -186,7 +186,7 @@ void print_array(cunumeric::NDArray array) } template -void check_array_eq(cunumeric::NDArray array, T* values_ptr, size_t length) +void check_array_eq(cupynumeric::NDArray array, T* values_ptr, size_t length) { assert(array.size() == length); if (length == 0) { @@ -202,7 +202,7 @@ void check_array_eq(cunumeric::NDArray array, T* values_ptr, size_t length) } template -void assign_values_to_array(cunumeric::NDArray array, T* values_ptr, size_t length) +void assign_values_to_array(cupynumeric::NDArray array, T* values_ptr, size_t length) { assert(array.size() == length); if (length == 0) { @@ -217,7 +217,7 @@ void assign_values_to_array(cunumeric::NDArray array, T* values_ptr, size_t leng } template -std::vector assign_array_to_values(cunumeric::NDArray array) +std::vector assign_array_to_values(cupynumeric::NDArray array) { std::vector result(array.size()); if (array.size() > 0) { @@ -233,7 +233,7 @@ std::vector assign_array_to_values(cunumeric::NDArray array) } template -void check_array_eq(cunumeric::NDArray array1, cunumeric::NDArray array2) +void check_array_eq(cupynumeric::NDArray array1, cupynumeric::NDArray array2) { assert(array1.size() == array2.size()); if (array1.size() == 0) { diff --git a/tests/cpp/main.cc b/tests/cpp/main.cc index 97211a77c8..1dd56f6c34 100644 --- a/tests/cpp/main.cc +++ b/tests/cpp/main.cc @@ -16,7 +16,7 @@ #include #include "legate.h" -#include "cunumeric.h" +#include "cupynumeric.h" class Environment : public ::testing::Environment { public: @@ -25,7 +25,7 @@ class Environment : public ::testing::Environment { void SetUp() override { EXPECT_EQ(legate::start(argc_, argv_), 0); - cunumeric::initialize(argc_, argv_); + cupynumeric::initialize(argc_, argv_); } void TearDown() override { EXPECT_EQ(legate::finish(), 0); } diff --git a/tests/cpp/run.py b/tests/cpp/run.py index e3c775e0a3..f83ce1c469 100755 --- a/tests/cpp/run.py +++ b/tests/cpp/run.py @@ -29,7 +29,7 @@ "PYTHON", "UCX_", "NCCL_", - "CUNUMERIC_", + "CUPYNUMERIC_", "NVIDIA_", ) @@ -95,14 +95,14 @@ def is_launcher_var(name: str) -> bool: def main(): - CUNUMERIC_DIR = Path(__file__).resolve().parent.parent.parent + CUPYNUMERIC_DIR = Path(__file__).resolve().parent.parent.parent parser = argparse.ArgumentParser(description="Run Legate cpp tests.") parser.add_argument( "--binary-path", dest="binary_path", required=False, default=str( - CUNUMERIC_DIR / "build" / "tests" / "cpp" / "bin" / "cpp_tests" + CUPYNUMERIC_DIR / "build" / "tests" / "cpp" / "bin" / "cpp_tests" ), help="Path to binary under test.", ) @@ -110,7 +110,7 @@ def main(): "--log-path", dest="log_path", required=False, - default=str(CUNUMERIC_DIR / "build" / "results.log"), + default=str(CUPYNUMERIC_DIR / "build" / "results.log"), help="Path to output log file.", ) parser.add_argument( diff --git a/tests/integration/test_0d_store.py b/tests/integration/test_0d_store.py index a2d22fab83..7e0ef278e7 100644 --- a/tests/integration/test_0d_store.py +++ b/tests/integration/test_0d_store.py @@ -17,7 +17,7 @@ import pytest -import cunumeric as num +import cupynumeric as num SIZE = 3 diff --git a/tests/integration/test_advanced_indexing.py b/tests/integration/test_advanced_indexing.py index 90751500de..c55d781da1 100644 --- a/tests/integration/test_advanced_indexing.py +++ b/tests/integration/test_advanced_indexing.py @@ -18,7 +18,7 @@ from legate.core import LEGATE_MAX_DIM from utils.generators import mk_seq_array -import cunumeric as num +import cupynumeric as num @pytest.fixture diff --git a/tests/integration/test_allclose.py b/tests/integration/test_allclose.py index 9270c77d3a..130a12b3ca 100755 --- a/tests/integration/test_allclose.py +++ b/tests/integration/test_allclose.py @@ -16,7 +16,7 @@ import numpy as np import pytest -import cunumeric as num +import cupynumeric as num SCALARS_TRUE_DEFAULT = ( (0, -1e-8), @@ -150,7 +150,7 @@ def test_array_false(shape): def test_broadcast_true1(shape_b): # for all cases, # In Numpy, it pass - # In cuNumeric, it raises AttributeError: + # In cuPyNumeric, it raises AttributeError: # 'Store' object has no attribute '_broadcast' len_scalars = len(SCALARS_TRUE_DEFAULT) @@ -186,7 +186,7 @@ def test_broadcast_true1(shape_b): def test_broadcast_true2(shape_b): # for all cases, # In Numpy, it pass - # In cuNumeric, it raises AttributeError: + # In cuPyNumeric, it raises AttributeError: # 'Store' object has no attribute '_broadcast' shape_a = (3,) size_a = np.prod(shape_a) @@ -219,7 +219,7 @@ def test_broadcast_true2(shape_b): def test_equal_nan_basic(arr, equal_nan): # If equal_nan is True, # In Numpy, it pass - # In cuNumeric, it raises NotImplementedError + # In cuPyNumeric, it raises NotImplementedError res_np = np.allclose(arr, arr, equal_nan=equal_nan) res_num = num.allclose(arr, arr, equal_nan=equal_nan) assert res_np == res_num @@ -257,7 +257,7 @@ def test_empty_array(a, b): def test_scalar_broadcasting(a, b): # for all cases, # In Numpy, it pass - # In cuNumeric, it raises AttributeError: + # In cuPyNumeric, it raises AttributeError: # 'Store' object has no attribute '_broadcast' res_np = np.allclose(a, b) res_num = num.allclose(a, b) diff --git a/tests/integration/test_amax_amin.py b/tests/integration/test_amax_amin.py index ee85b2e2b0..fac1f959bb 100755 --- a/tests/integration/test_amax_amin.py +++ b/tests/integration/test_amax_amin.py @@ -17,7 +17,7 @@ import pytest from legate.core import LEGATE_MAX_DIM -import cunumeric as num +import cupynumeric as num FUNCS = ("amax", "amin") @@ -54,7 +54,7 @@ def test_basic(func_name, ndim, keepdims, initial): def test_src_dt(func_name, keepdims, src_dt): # For src_dt=np.complex128, # In Numpy, it pass - # In cuNumeric, it raises NotImplementedError + # In cuPyNumeric, it raises NotImplementedError ndim = 3 shape = (5,) * ndim in_np = np.random.randint(-5, 5, size=shape).astype(src_dt) @@ -97,7 +97,7 @@ def test_axis(func_name, ndim, keepdims, initial): @pytest.mark.parametrize("func_name", FUNCS) def test_axis_tuple(func_name, keepdims, axes): # In Numpy, it pass - # In cuNumeric, it raises NotImplementedError + # In cuPyNumeric, it raises NotImplementedError shape = (3, 4, 5) in_np = np.random.randint(-5, 5, size=shape) in_num = num.array(in_np) @@ -189,7 +189,7 @@ def test_out(func_name, ndim, keepdims, initial): def test_out_with_dtype(func_name, keepdims, out_dt): # For out_dt=np.complex128 # In Numpy, it pass - # In cuNumeric, it raises KeyError + # In cuPyNumeric, it raises KeyError ndim = 3 shape = (5,) * ndim in_np = np.random.randint(-5, 5, size=shape) @@ -216,7 +216,7 @@ def test_out_with_dtype(func_name, keepdims, out_dt): @pytest.mark.parametrize("func_name", FUNCS) def test_where(func_name): # In Numpy, it pass - # In cuNumeric, it raises NotImplementedError + # In cuPyNumeric, it raises NotImplementedError shape = (3, 4, 5) in_np = np.random.randint(-5, 5, size=shape) in_num = num.array(in_np) diff --git a/tests/integration/test_angle.py b/tests/integration/test_angle.py index 16a81bc5ce..02570f13f9 100644 --- a/tests/integration/test_angle.py +++ b/tests/integration/test_angle.py @@ -18,7 +18,7 @@ from legate.core import LEGATE_MAX_DIM from utils.generators import mk_seq_array -import cunumeric as num +import cupynumeric as num class TestAngleErrors: diff --git a/tests/integration/test_append.py b/tests/integration/test_append.py index bece5e7f85..5693072165 100644 --- a/tests/integration/test_append.py +++ b/tests/integration/test_append.py @@ -17,7 +17,7 @@ import pytest from utils.utils import check_module_function -import cunumeric as num +import cupynumeric as num DIM = 10 diff --git a/tests/integration/test_arg_reduce.py b/tests/integration/test_arg_reduce.py index 17c491fbec..c4ef2d2e39 100644 --- a/tests/integration/test_arg_reduce.py +++ b/tests/integration/test_arg_reduce.py @@ -18,7 +18,7 @@ from legate.core import LEGATE_MAX_DIM from utils.utils import AxisError -import cunumeric as num +import cupynumeric as num ARG_FUNCS = ("argmax", "argmin") diff --git a/tests/integration/test_argsort.py b/tests/integration/test_argsort.py index 07d165eefb..89fcc2d7a6 100644 --- a/tests/integration/test_argsort.py +++ b/tests/integration/test_argsort.py @@ -16,9 +16,9 @@ import numpy as np import pytest -import cunumeric as num +import cupynumeric as num -# cunumeric.argsort(a: ndarray, axis: int = -1, kind: SortType = 'quicksort', +# cupynumeric.argsort(a: ndarray, axis: int = -1, kind: SortType = 'quicksort', # order: Optional = None) → ndarray # ndarray.argsort(axis=-1, kind=None, order=None) @@ -93,8 +93,8 @@ def test_structured_array_order(self): res_np = np.argsort(a_np, order="height") res_num = num.argsort(a_num, order="height") - # cuNumeric raises AssertionError in - # function cunumeric/cunumeric/eager.py:to_deferred_array + # cuPyNumeric raises AssertionError in + # function cupynumeric/cupynumeric/eager.py:to_deferred_array # if self.deferred is None: # if self.parent is None: # @@ -124,7 +124,7 @@ def test_sort_type_invalid(self): res_num = num.argsort(arr_num, kind="negative") # Numpy raises "ValueError: sort kind must be one of 'quick', # 'heap', or 'stable' (got 'negative')" - # cuNumeric passed. The code basically supports ‘stable’ + # cuPyNumeric passed. The code basically supports ‘stable’ # or not ‘stable’. assert np.array_equal(res_num, res_np) @@ -151,7 +151,7 @@ def test_basic_axis_sort_type(self, size, sort_type): @pytest.mark.parametrize("sort_type", UNSTABLE_SORT_TYPES) def test_basic_axis_sort_type_unstable(self, size, sort_type): # have to guarantee unique values in input - # see https://github.com/nv-legate/cunumeric/issues/782 + # see https://github.com/nv-legate/cupynumeric/issues/782 arr_np = np.arange(np.prod(size)) np.random.shuffle(arr_np) arr_np = arr_np.reshape(size) @@ -188,7 +188,7 @@ def test_arr_basic_axis_sort(self, size, sort_type): @pytest.mark.parametrize("sort_type", UNSTABLE_SORT_TYPES) def test_arr_basic_axis_sort_unstable(self, size, sort_type): # have to guarantee unique values in input - # see https://github.com/nv-legate/cunumeric/issues/782 + # see https://github.com/nv-legate/cupynumeric/issues/782 arr_np = np.arange(np.prod(size)) np.random.shuffle(arr_np) arr_np = arr_np.reshape(size) diff --git a/tests/integration/test_array.py b/tests/integration/test_array.py index 43854a42df..6029f8a1c9 100755 --- a/tests/integration/test_array.py +++ b/tests/integration/test_array.py @@ -17,7 +17,7 @@ import pytest from legate.core import LEGATE_MAX_DIM -import cunumeric as num +import cupynumeric as num SCALARS = ( 0, @@ -61,7 +61,7 @@ def test_array_basic(obj): @pytest.mark.parametrize("obj", UNSUPPORTED_OBJECTS) def test_array_unsupported(obj): - with pytest.raises(TypeError, match="cuNumeric does not support dtype"): + with pytest.raises(TypeError, match="cuPyNumeric does not support dtype"): num.array(obj) @@ -153,7 +153,7 @@ def test_asarray_basic(obj): @pytest.mark.parametrize("obj", UNSUPPORTED_OBJECTS) def test_asarray_unsupported(obj): - with pytest.raises(TypeError, match="cuNumeric does not support dtype"): + with pytest.raises(TypeError, match="cuPyNumeric does not support dtype"): num.array(obj) diff --git a/tests/integration/test_array_creation.py b/tests/integration/test_array_creation.py index d9bc1e76a5..a015522dad 100644 --- a/tests/integration/test_array_creation.py +++ b/tests/integration/test_array_creation.py @@ -18,7 +18,7 @@ import numpy as np import pytest -import cunumeric as num +import cupynumeric as num def test_array(): diff --git a/tests/integration/test_array_dunders.py b/tests/integration/test_array_dunders.py index 83e4c2a5ec..c7a3a53a93 100644 --- a/tests/integration/test_array_dunders.py +++ b/tests/integration/test_array_dunders.py @@ -17,7 +17,7 @@ import pytest from numpy.lib import NumpyVersion -import cunumeric as num +import cupynumeric as num arr_np = np.eye(4) vec_np = np.arange(4).astype(np.float64) diff --git a/tests/integration/test_array_equal.py b/tests/integration/test_array_equal.py index bb298eec57..71d2c88f3c 100755 --- a/tests/integration/test_array_equal.py +++ b/tests/integration/test_array_equal.py @@ -16,7 +16,7 @@ import numpy as np import pytest -import cunumeric as num +import cupynumeric as num @pytest.mark.parametrize( @@ -86,7 +86,7 @@ def test_equal_values_with_different_dtype(dtype1, dtype2): def test_equal_nan_basic(arr, equal_nan): # If equal_nan is True, # In Numpy, it pass - # In cuNumeric, it raises NotImplementedError + # In cuPyNumeric, it raises NotImplementedError res_np = np.array_equal(arr, arr, equal_nan=equal_nan) res_num = num.array_equal(arr, arr, equal_nan=equal_nan) assert res_np == res_num @@ -98,7 +98,7 @@ def test_equal_nan_basic(arr, equal_nan): def test_equal_nan_complex_values(equal_nan): # If equal_nan is True, # In Numpy, it pass - # In cuNumeric, it raises NotImplementedError + # In cuPyNumeric, it raises NotImplementedError a = np.array([1, 1 + 1j]) b = a.copy() a.real = np.nan diff --git a/tests/integration/test_array_fallback.py b/tests/integration/test_array_fallback.py index 46e74a8faf..7ab602ebc9 100644 --- a/tests/integration/test_array_fallback.py +++ b/tests/integration/test_array_fallback.py @@ -15,10 +15,10 @@ import pytest -import cunumeric as num +import cupynumeric as num -# ref: https://github.com/nv-legate/cunumeric/pull/430 +# ref: https://github.com/nv-legate/cupynumeric/pull/430 def test_unimplemented_method_self_fallback(): ones = num.ones((10,)) ones.mean() @@ -27,7 +27,7 @@ def test_unimplemented_method_self_fallback(): # to verify a behaviour of unimplemented ndarray method wrappers. If std # becomes implemeneted in the future, this assertion will start to fail, # and a new (unimplemented) ndarray method should be found to replace it - assert not ones.std._cunumeric.implemented + assert not ones.std._cupynumeric.implemented ones.std() diff --git a/tests/integration/test_array_split.py b/tests/integration/test_array_split.py index 6b73c16813..29c52d1d41 100644 --- a/tests/integration/test_array_split.py +++ b/tests/integration/test_array_split.py @@ -19,7 +19,7 @@ import pytest from utils.utils import check_module_function -import cunumeric as num +import cupynumeric as num DIM = 20 diff --git a/tests/integration/test_astype.py b/tests/integration/test_astype.py index fe5aa3068f..da0ed1c7a1 100644 --- a/tests/integration/test_astype.py +++ b/tests/integration/test_astype.py @@ -17,7 +17,7 @@ import numpy as np import pytest -import cunumeric as num +import cupynumeric as num TEST_VECTOR = [0, 0, 1, 2, 3, 0, 1, 2, 3] ALL_BUT_COMPLEX = ["?", "b", "h", "i", "l", "B", "H", "I", "L", "e", "f", "d"] @@ -120,14 +120,14 @@ def test_complex_negative(src_dtype): out_np = in_np.astype(to_dtype("?")) out_num = in_num.astype(to_dtype("?")) - # Numpy and cuNumeric have different performance. - # For complex data 0.+1.j, Numpy set as True, cuNumeric set as False. + # Numpy and cuPyNumeric have different performance. + # For complex data 0.+1.j, Numpy set as True, cuPyNumeric set as False. assert np.array_equal(out_num, out_np) def test_default_copy_value(): # it was decided to explicitly diverge from the numpy default value in - # https://github.com/nv-legate/cunumeric.internal/issues/421 + # https://github.com/nv-legate/cupynumeric.internal/issues/421 a = num.array([]) assert inspect.signature(a.astype).parameters["copy"].default is False diff --git a/tests/integration/test_atleast_nd.py b/tests/integration/test_atleast_nd.py index cac98ad722..5f679c4809 100644 --- a/tests/integration/test_atleast_nd.py +++ b/tests/integration/test_atleast_nd.py @@ -18,7 +18,7 @@ from legate.core import LEGATE_MAX_DIM from utils.utils import check_module_function -import cunumeric as num +import cupynumeric as num DIM = 10 @@ -34,7 +34,7 @@ @pytest.mark.parametrize("size", SIZE_CASES, ids=str) def test_atleast_1d(size): a = [np.arange(np.prod(size)).reshape(size)] - print_msg = f"np & cunumeric.atleast_1d(size={size})" + print_msg = f"np & cupynumeric.atleast_1d(size={size})" check_module_function("atleast_1d", a, {}, print_msg) @@ -46,7 +46,7 @@ def test_atleast_1d_scalar(): @pytest.mark.parametrize("size", SIZE_CASES, ids=str) def test_atleast_2d(size): a = [np.arange(np.prod(size)).reshape(size)] - print_msg = f"np & cunumeric.atleast_2d(size={size})" + print_msg = f"np & cupynumeric.atleast_2d(size={size})" check_module_function("atleast_2d", a, {}, print_msg) @@ -58,7 +58,7 @@ def test_atleast_2d_scalar(): @pytest.mark.parametrize("size", SIZE_CASES, ids=str) def test_atleast_3d(size): a = [np.arange(np.prod(size)).reshape(size)] - print_msg = f"np & cunumeric.atleast_3d(size={size})" + print_msg = f"np & cupynumeric.atleast_3d(size={size})" check_module_function("atleast_3d", a, {}, print_msg) @@ -73,7 +73,7 @@ def test_atleast_nd(dim): a = list(np.arange(np.prod(size)).reshape(size) for size in SIZE_CASES) scalar = 10.0 a.append(scalar) - print_msg = f"np & cunumeric.atleast_{dim}d(size={SIZE_CASES})" + print_msg = f"np & cupynumeric.atleast_{dim}d(size={SIZE_CASES})" check_module_function(f"atleast_{dim}d", a, {}, print_msg) diff --git a/tests/integration/test_average.py b/tests/integration/test_average.py index e8ff4934da..6d7f8df943 100644 --- a/tests/integration/test_average.py +++ b/tests/integration/test_average.py @@ -17,7 +17,7 @@ import pytest from utils.comparisons import allclose -import cunumeric as num +import cupynumeric as num axes = [None, 0, 1, 2, (0, 1, 2)] diff --git a/tests/integration/test_binary_op_broadcast.py b/tests/integration/test_binary_op_broadcast.py index d779d5c343..303fe7856e 100644 --- a/tests/integration/test_binary_op_broadcast.py +++ b/tests/integration/test_binary_op_broadcast.py @@ -16,7 +16,7 @@ import numpy as np import pytest -import cunumeric as num +import cupynumeric as num N = 20 diff --git a/tests/integration/test_binary_op_complex.py b/tests/integration/test_binary_op_complex.py index b9263bd170..48d12e7d78 100644 --- a/tests/integration/test_binary_op_complex.py +++ b/tests/integration/test_binary_op_complex.py @@ -16,7 +16,7 @@ import numpy as np import pytest -import cunumeric as num +import cupynumeric as num xn = np.array([1 + 4j, 2 + 5j, 3 + 6j], np.complex64) yn = np.array([4 + 7j, 5 + 8j, 6 + 9j], np.complex64) diff --git a/tests/integration/test_binary_op_typing.py b/tests/integration/test_binary_op_typing.py index e78d432634..d4612b5671 100644 --- a/tests/integration/test_binary_op_typing.py +++ b/tests/integration/test_binary_op_typing.py @@ -18,7 +18,7 @@ import numpy as np import pytest -import cunumeric as num +import cupynumeric as num def value_type(obj): @@ -87,7 +87,7 @@ def generate_array_array_cases(): # the code somewhat compatible with NumPy for cases where Python scalars # are passed. # -# If anyone can do a better job than me and finally make cuNumeric +# If anyone can do a better job than me and finally make cuPyNumeric # implement the same typing rules, please put these tests back. def generate_array_scalar_cases(): for idx, lhs_type in enumerate(TYPES): @@ -129,7 +129,7 @@ def test_array_array(lhs_np, rhs_np, lhs_num, rhs_num): print(f"LHS {lhs_np}") print(f"RHS {rhs_np}") - print(f"NumPy type: {out_np.dtype}, cuNumeric type: {out_num.dtype}") + print(f"NumPy type: {out_np.dtype}, cuPyNumeric type: {out_num.dtype}") assert out_np.dtype == out_num.dtype @@ -145,7 +145,7 @@ def test_array_scalar(lhs_np, rhs_np, lhs_num, rhs_num): print(f"LHS {lhs_np}") print(f"RHS {rhs_np}") - print(f"NumPy type: {out_np.dtype}, cuNumeric type: {out_num.dtype}") + print(f"NumPy type: {out_np.dtype}, cuPyNumeric type: {out_num.dtype}") assert out_np.dtype == out_num.dtype diff --git a/tests/integration/test_binary_ufunc.py b/tests/integration/test_binary_ufunc.py index c27f20d7df..9ae99edd6f 100644 --- a/tests/integration/test_binary_ufunc.py +++ b/tests/integration/test_binary_ufunc.py @@ -20,7 +20,7 @@ import pytest from utils.comparisons import allclose -import cunumeric as num +import cupynumeric as num def check_result(op, in_np, out_np, out_num): @@ -33,7 +33,7 @@ def check_result(op, in_np, out_np, out_num): getattr(out_np, "dtype", None) == getattr(out_num, "dtype", None) ) if not result: - print(f"cunumeric.{op} failed the test") + print(f"cupynumeric.{op} failed the test") print("Inputs:") for arr in in_np: print(arr) @@ -42,7 +42,7 @@ def check_result(op, in_np, out_np, out_num): print("NumPy output:") print(out_np) print(f"dtype: {out_np.dtype}") - print("cuNumeric output:") + print("cuPyNumeric output:") print(out_num) print(f"dtype: {out_num.dtype}") assert False @@ -70,7 +70,7 @@ def check_op(op, in_np, out_dtype="D"): check_result(op, in_np, out_np, out_num) - # Ask cuNumeric to produce outputs to NumPy ndarrays + # Ask cuPyNumeric to produce outputs to NumPy ndarrays out_num = np.empty(out_np.shape, dtype=out_dtype) op_num(*in_num, out=out_num) @@ -297,7 +297,7 @@ def test_bit_ops_arr_scalar(op) -> None: check_op(op, (arrs[0], scalars[0])) check_op(op, (arrs[0], scalars[1])) check_op(op, (arrs[0], scalars[2])) - # Cunumeric << and >> have problems with python integers: + # cuPyNumeric << and >> have problems with python integers: # check_op(op, (scalars[0], arrs[0])) check_op(op, (scalars[1], arrs[0])) check_op(op, (scalars[2], arrs[0])) diff --git a/tests/integration/test_bincount.py b/tests/integration/test_bincount.py index d382d1a702..9f61f43ad6 100644 --- a/tests/integration/test_bincount.py +++ b/tests/integration/test_bincount.py @@ -17,7 +17,7 @@ import pytest from utils.comparisons import allclose -import cunumeric as num +import cupynumeric as num N = 8000 MAX_VAL = 9 diff --git a/tests/integration/test_bits.py b/tests/integration/test_bits.py index 40882706ee..612c584d48 100644 --- a/tests/integration/test_bits.py +++ b/tests/integration/test_bits.py @@ -18,14 +18,14 @@ import pytest from legate.core import LEGATE_MAX_DIM -import cunumeric as num +import cupynumeric as num class TestPackbits(object): def test_none_arr(self): # Numpy raises "TypeError: # Expected an input array of integer or boolean data type" - # For cuNumeric raises: + # For cuPyNumeric raises: # > if a.dtype.kind not in ("u", "i", "b"): # E AttributeError: 'NoneType' object has no attribute 'dtype' with pytest.raises(AttributeError): @@ -50,7 +50,7 @@ def test_bitorder_negative(self, bitorder): in_num = num.random.randint(low=0, high=2, size=shape, dtype="i") # when bitorder is 1 or True, Numpy raises # "TypeError: pack() argument 3 must be str". - # while cuNumeric raises valueError. + # while cuPyNumeric raises valueError. with pytest.raises(ValueError): num.packbits(in_num, bitorder=bitorder) @@ -94,7 +94,7 @@ class TestUnpackbits(object): def test_none_arr(self): # Numpy raises "TypeError: # TypeError: Expected an input array of unsigned byte data type - # For cuNumeric raises: + # For cuPyNumeric raises: # > if a.dtype != "B": # E AttributeError: 'NoneType' object has no attribute 'dtype' with pytest.raises(AttributeError): @@ -121,7 +121,7 @@ def test_bitorder_negative(self, bitorder): in_num = num.array(in_np) # when bitorder is 1 or True, Numpy raises # "TypeError: unpack() argument 4 must be str". - # while cuNumeric raises valueError. + # while cuPyNumeric raises valueError. with pytest.raises(ValueError): num.unpackbits(in_num, bitorder=bitorder) diff --git a/tests/integration/test_block.py b/tests/integration/test_block.py index 326b18e518..7692af1f3f 100644 --- a/tests/integration/test_block.py +++ b/tests/integration/test_block.py @@ -17,7 +17,7 @@ import pytest from utils.utils import check_module_function -import cunumeric as num +import cupynumeric as num def _deepen(depth, x): @@ -58,7 +58,7 @@ def test_block_simple_row_wise(self): arg = [a_2d, b_2d] print_msg = ( - f"np & cunumeric.block([array({a_2d.shape}), " + f"np & cupynumeric.block([array({a_2d.shape}), " f"array({b_2d.shape})])" ) check_module_function("block", [arg], {}, print_msg) @@ -69,7 +69,7 @@ def test_block_simple_column_wise(self): arg = [[a_2d], [b_2d]] print_msg = ( - f"np & cunumeric.block([[array({a_2d.shape})], " + f"np & cupynumeric.block([[array({a_2d.shape})], " f"[array({b_2d.shape})]])" ) check_module_function("block", [arg], {}, print_msg) @@ -80,7 +80,7 @@ def test_block_with_1d_arrays_multiple_rows(self): arg = [[a, b], [a, b]] print_msg = ( - f"np & cunumeric.block([[array({a.shape}), array({b.shape})], " + f"np & cupynumeric.block([[array({a.shape}), array({b.shape})], " f"[array({a.shape}), array({b.shape})]])" ) check_module_function("block", [arg], {}, print_msg, check_type=False) @@ -91,7 +91,7 @@ def test_block_mixed_1d_and_2d(self): arg = [[a_2d], [b_1d]] print_msg = ( - f"np & cunumeric.block([[array({a_2d.shape})], " + f"np & cupynumeric.block([[array({a_2d.shape})], " f"[array({b_1d.shape})]])" ) check_module_function("block", [arg], {}, print_msg) @@ -112,7 +112,7 @@ def test_block_complicated(self): [zero_2d], ] - print_msg = "np & cunumeric.block()" + print_msg = "np & cupynumeric.block()" check_module_function("block", [arg], {}, print_msg) def test_nested(self): @@ -164,7 +164,7 @@ def test_3d(self): ], ] - print_msg = "np & cunumeric.block()" + print_msg = "np & cupynumeric.block()" check_module_function("block", [arg], {}, print_msg, check_type=False) @@ -197,11 +197,11 @@ def test_mismatched_shape_3(self): def test_no_lists(self): # numpy: pass, output is np.array(1) - # cunumeric: raises TypeError, cunumeric doesn't support 0-D array + # cupynumeric: raises TypeError, cupynumeric doesn't support 0-D array # assert np.array_equal(num.block(1), np.array(1)) # numpy: pass, output is np.eye(3) - # cunumeric: pass, output is 1-D array: [1, 0, 0, 0, 1, 0, 0, 0, 1] + # cupynumeric: pass, output is 1-D array: [1, 0, 0, 0, 1, 0, 0, 0, 1] # assert np.array_equal(num.block(np.eye(3)), np.eye(3)) np.array_equal(num.block(num.eye(3)), [1, 0, 0, 0, 1, 0, 0, 0, 1]) @@ -235,7 +235,7 @@ def test_tuple(self): # TypeError: arrays is a tuple. Only lists can be used # to arrange blocks,and np.block does not allow implicit # conversion from tuple to ndarray. - # cunumeric: pass + # cupynumeric: pass np.array_equal(num.block(([1, 2], [3, 4])), [1, 2, 3, 4]) np.array_equal(num.block([(1, 2), (3, 4)]), [1, 2, 3, 4]) @@ -246,7 +246,7 @@ def test_different_ndims(self): c = 3 * np.ones((1, 1, 3)) # numpy: pass, output is np.array([[[1., 2., 2., 3., 3., 3.]]]) - # cunumeric: raises ValueError + # cupynumeric: raises ValueError with pytest.raises(ValueError, match=msg): num.block([a, b, c]) @@ -259,7 +259,7 @@ def test_different_ndims_depths(self): # numpy: pass,output is np.array([[[1., 2., 2.], # [3., 3., 3.], # [3., 3., 3.]]]) - # cunumeric: raises ValueError + # cupynumeric: raises ValueError with pytest.raises(ValueError, match=msg): num.block([[a, b], [c]]) diff --git a/tests/integration/test_broadcast.py b/tests/integration/test_broadcast.py index a051054aaf..d95e3975c5 100644 --- a/tests/integration/test_broadcast.py +++ b/tests/integration/test_broadcast.py @@ -17,7 +17,7 @@ import pytest from legate.core import LEGATE_MAX_DIM -import cunumeric as num +import cupynumeric as num DIM_CASES = [5, 40] @@ -29,11 +29,11 @@ def _check_result(print_msg, err_arrs): print_output += ( f"Attr, {err_arr[0]}\n" f"numpy result: {err_arr[1]}\n" - f"cunumeric_result: {err_arr[2]}\n" + f"cupynumeric_result: {err_arr[2]}\n" ) assert False, ( f"{print_output}" - f"cunumeric and numpy shows" + f"cupynumeric and numpy shows" f" different result\n" ) else: diff --git a/tests/integration/test_cholesky.py b/tests/integration/test_cholesky.py index e0f9d260e5..7f2dd5d102 100644 --- a/tests/integration/test_cholesky.py +++ b/tests/integration/test_cholesky.py @@ -17,7 +17,7 @@ import pytest from utils.comparisons import allclose -import cunumeric as num +import cupynumeric as num SIZES = [8, 9, 255, 512, 1024] diff --git a/tests/integration/test_clip.py b/tests/integration/test_clip.py index f583398dbb..85141c9e45 100644 --- a/tests/integration/test_clip.py +++ b/tests/integration/test_clip.py @@ -18,7 +18,7 @@ from legate.core import LEGATE_MAX_DIM from utils.generators import mk_seq_array -import cunumeric as num +import cupynumeric as num class TestClipErrors: @@ -28,7 +28,7 @@ def test_none_array(self): with pytest.raises(expected_exc): np.clip(None, a_min=0, a_max=0) with pytest.raises(expected_exc): - # cunumeric raises + # cupynumeric raises # AttributeError: 'NoneType' object has no attribute 'clip' num.clip(None, a_min=0, a_max=0) @@ -41,7 +41,7 @@ def test_value_none(self): # ValueError: One of max or min must be given np.clip(array, a_min=None, a_max=None) with pytest.raises(expected_exc): - # cunumeric raises: + # cupynumeric raises: # TypeError: int() argument must be a string, # a bytes-like object or a real number, not 'NoneType' num.clip(array, a_min=None, a_max=None) @@ -95,7 +95,7 @@ def test_amin_value(amin): # res_np is not match res_num # in Numpy, when one of a_min of a_max is float, # all data are marked as float, - # while in cunumeric, all datas are int. + # while in cupynumeric, all datas are int. # for example, amin = 5 # array = array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9]) # res_np = array([5., 5., 5., 5., 5., 5., 6., 7., 8., 8.5]) @@ -111,7 +111,7 @@ def test_amin_complex(): # res_np = array([5. +5.j, 5. +5.j, 5. +5.j, 5. +5.j, 5. +5.j, # 5. +5.j, 6. +0.j, 7. +0.j, 8. +0.j, 8.5+0.j]) res_num = num.clip(array, a_min=amin, a_max=8.5) - # cunumeric raises: + # cupynumeric raises: # TypeError: int() argument must be a string, a bytes-like object # or a real number, not 'complex' assert np.array_equal(res_np, res_num) diff --git a/tests/integration/test_complex_ops.py b/tests/integration/test_complex_ops.py index e00de22e2c..20ffe77fd8 100644 --- a/tests/integration/test_complex_ops.py +++ b/tests/integration/test_complex_ops.py @@ -16,7 +16,7 @@ import numpy as np import pytest -import cunumeric as num +import cupynumeric as num ARRAYS = ( [1, 2, 3], @@ -69,7 +69,7 @@ def test_non_complex_array(dtype): def test_scalar(val): # e.g., np.array_equal(1.1, array(1.1)) # In numpy, it returns val as a scalar - # In cunumeric, it returns a 0-dim array(val) + # In cupynumeric, it returns a 0-dim array(val) assert np.array_equal(np.real(val), num.real(val)) assert np.array_equal(np.imag(val), num.imag(val)) @@ -79,7 +79,7 @@ def test_scalar(val): @pytest.mark.parametrize("real_val", ([7, 8, 9], 9)) def test_assignment(real_val, imag_val): # In numpy, x_np.real = real_val pass - # In cunumeric, it rasies AttributeError: can't set attribute + # In cupynumeric, it rasies AttributeError: can't set attribute arr = [1 + 4j, 2 + 5j, 3 + 6j] x_np = np.array(arr) x_num = num.array(x_np) diff --git a/tests/integration/test_compress.py b/tests/integration/test_compress.py index af466f7bf1..2e6aa4e334 100644 --- a/tests/integration/test_compress.py +++ b/tests/integration/test_compress.py @@ -18,13 +18,13 @@ from legate.core import LEGATE_MAX_DIM from utils.generators import mk_seq_array -import cunumeric as num +import cupynumeric as num @pytest.mark.xfail def test_none_array(): res_np = np.compress([0], None) # numpy return [] - # cuNumeric raises: + # cuPyNumeric raises: # AttributeError: 'NoneType' object has no attribute 'compress' res_num = num.compress([0], None) assert np.array_equal(res_np, res_num) @@ -33,7 +33,7 @@ def test_none_array(): @pytest.mark.xfail def test_empty_array(): res_np = np.compress([0], []) # numpy return [] - # cuNumeric raises: ValueError: + # cuPyNumeric raises: ValueError: # Shape mismatch: condition contains entries that are out of bounds res_num = num.compress([0], []) assert np.array_equal(res_np, res_num) @@ -79,14 +79,14 @@ def test_dtype_out1(): # for Numpy, it will raise TypeError: # "Cannot cast array data from dtype('float64') to dtype('int64') # according to the rule 'safe'". - # cuNumeric passed. + # cuPyNumeric passed. np.compress([True, True, True, True], a, out=out_np) num.compress([True, True, True, True], b, out=out_num) assert np.array_equal(out_np, out_num) def test_dtype_out2(): - # both Numpy and cuNumeric turn float into int + # both Numpy and cuPyNumeric turn float into int a = np.random.random((4,)) * 10 b = num.array(a) out_np = np.random.randint(1, 10, (4,)) @@ -104,7 +104,7 @@ def test_out_parameter(): out_num = np.random.randint(1, 5, (4,)) np.compress([True, True, True, True], a, 0, out_np) num.compress([True, True, True, True], b, 0, out_num) - # for cuNumeric, the last parameter 'out', + # for cuPyNumeric, the last parameter 'out', # it should be written as 'out=out_num' # otherwise it raises error assert np.array_equal(out_num, out_np) diff --git a/tests/integration/test_concatenate_stack.py b/tests/integration/test_concatenate_stack.py index d59fd47ce3..f476f19792 100644 --- a/tests/integration/test_concatenate_stack.py +++ b/tests/integration/test_concatenate_stack.py @@ -18,7 +18,7 @@ import numpy as np import pytest -import cunumeric as num +import cupynumeric as num def run_test(arr, routine, input_size): @@ -56,8 +56,8 @@ def run_test(arr, routine, input_size): assert is_equal, ( f"Failed, {print_msg}\n" f"numpy result: {err_arr[0]}, {b.shape}\n" - f"cunumeric_result: {err_arr[1]}, {c.shape}\n" - f"cunumeric and numpy shows" + f"cupynumeric_result: {err_arr[1]}, {c.shape}\n" + f"cupynumeric and numpy shows" f" different result\n" f"array({arr})," f"routine: {routine}," @@ -65,7 +65,7 @@ def run_test(arr, routine, input_size): ) print( f"Passed, {print_msg}, np: ({b.shape}, {b.dtype})" - f", cunumeric: ({c.shape}, {c.dtype}" + f", cupynumeric: ({c.shape}, {c.dtype}" ) diff --git a/tests/integration/test_contains.py b/tests/integration/test_contains.py index 08ab23dc8c..0e392e51ba 100644 --- a/tests/integration/test_contains.py +++ b/tests/integration/test_contains.py @@ -18,7 +18,7 @@ import pytest from utils.generators import mk_seq_array -import cunumeric as num +import cupynumeric as num DIM = 128 NO_EMPTY_SIZES = [ diff --git a/tests/integration/test_convolve.py b/tests/integration/test_convolve.py index f2b8535270..be1dbaf83b 100644 --- a/tests/integration/test_convolve.py +++ b/tests/integration/test_convolve.py @@ -20,7 +20,7 @@ import scipy.signal as sig from utils.comparisons import allclose -import cunumeric as num +import cupynumeric as num CUDA_TEST = os.environ.get("LEGATE_NEED_CUDA") == "1" @@ -80,7 +80,7 @@ def test_none(): expected_exc = TypeError with pytest.raises(expected_exc): num.convolve(None, None, mode="same") - # cuNumeric raises AttributeError + # cuPyNumeric raises AttributeError with pytest.raises(expected_exc): np.convolve(None, None, mode="same") @@ -166,7 +166,7 @@ def test_modes(mode): arr1 = num.random.random(shape) arr2 = num.random.random(shape) out_num = num.convolve(arr1, arr2, mode=mode) - # when mode!="same", cunumeric raises + # when mode!="same", cupynumeric raises # NotImplementedError: Need to implement other convolution modes out_np = np.convolve(arr1, arr2, mode=mode) assert allclose(out_num, out_np) @@ -188,7 +188,7 @@ def test_ndim(ndim): arr1 = num.random.random(shape) arr2 = num.random.random(shape) out_num = num.convolve(arr1, arr2, mode="same") - # cunumeric raises, NotImplementedError: 4-D arrays are not yet supported + # cupynumeric raises NotImplementedError: 4-D arrays are not yet supported out_np = np.convolve(arr1, arr2, mode="same") assert allclose(out_num, out_np) diff --git a/tests/integration/test_copy.py b/tests/integration/test_copy.py index 76efb4f834..a0ff1b8ae5 100644 --- a/tests/integration/test_copy.py +++ b/tests/integration/test_copy.py @@ -16,7 +16,7 @@ import numpy as np import pytest -import cunumeric as num +import cupynumeric as num def test_basic(): diff --git a/tests/integration/test_corner_quantiles.py b/tests/integration/test_corner_quantiles.py index 152bc5d84b..8538c06e02 100644 --- a/tests/integration/test_corner_quantiles.py +++ b/tests/integration/test_corner_quantiles.py @@ -19,7 +19,7 @@ from legate.core import LEGATE_MAX_DIM from utils.comparisons import allclose -import cunumeric as num +import cupynumeric as num ALL_METHODS = ( "inverted_cdf", @@ -116,8 +116,8 @@ def test_quantiles_w_output(str_method, axes, qs_arr, keepdims): q_out = num.zeros((*qs_arr.shape, *remaining_shape), dtype=float) # np_q_out = np.zeros((*qs_arr.shape, *remaining_shape), dtype=float) - # cunumeric: - # print("cunumeric axis = %d:"%(axis)) + # cupynumeric: + # print("cupynumeric axis = %d:"%(axis)) num.quantile( arr, qs_arr, axis=axes, out=q_out, method=str_method, keepdims=keepdims ) @@ -189,8 +189,8 @@ def test_quantiles_axis_none(str_method, qin_arr, keepdims): else: qs_arr = np.array(qin_arr) - # cunumeric: - # print("cunumeric axis = %d:"%(axis)) + # cupynumeric: + # print("cupynumeric axis = %d:"%(axis)) q_out = num.quantile( arr, qs_arr, diff --git a/tests/integration/test_data_interface.py b/tests/integration/test_data_interface.py index 7214aa0f1d..77ffaff713 100644 --- a/tests/integration/test_data_interface.py +++ b/tests/integration/test_data_interface.py @@ -15,8 +15,8 @@ import pytest -import cunumeric as num -from cunumeric._utils.array import SUPPORTED_DTYPES +import cupynumeric as num +from cupynumeric._utils.array import SUPPORTED_DTYPES DTYPES = SUPPORTED_DTYPES.keys() diff --git a/tests/integration/test_diag_indices.py b/tests/integration/test_diag_indices.py index 03659d44bb..386ca43734 100644 --- a/tests/integration/test_diag_indices.py +++ b/tests/integration/test_diag_indices.py @@ -17,7 +17,7 @@ import pytest from legate.core import LEGATE_MAX_DIM -import cunumeric as num +import cupynumeric as num @pytest.mark.parametrize("n", [10, -10.5, -1]) diff --git a/tests/integration/test_diff.py b/tests/integration/test_diff.py index 0644f5c9cc..759badb2fc 100644 --- a/tests/integration/test_diff.py +++ b/tests/integration/test_diff.py @@ -17,7 +17,7 @@ import pytest from utils.comparisons import allclose -import cunumeric as num +import cupynumeric as num @pytest.mark.parametrize( diff --git a/tests/integration/test_digitize.py b/tests/integration/test_digitize.py index f7d524f2cf..194814e8d7 100644 --- a/tests/integration/test_digitize.py +++ b/tests/integration/test_digitize.py @@ -18,7 +18,7 @@ import numpy as np import pytest -import cunumeric as num +import cupynumeric as num DTYPES = ( np.uint32, @@ -49,7 +49,7 @@ def test_bad_array(self): bins = [0, 5, 3] expected_exc = ValueError with pytest.raises(expected_exc): - # cunumeric raises TypeError + # cupynumeric raises TypeError num.digitize(None, bins) with pytest.raises(expected_exc): np.digitize(None, bins) @@ -59,7 +59,7 @@ def test_bad_bins(self): a = [2, 3, 10, 9] expected_exc = ValueError with pytest.raises(expected_exc): - # cunumeric raises TypeError + # cupynumeric raises TypeError num.digitize(a, None) with pytest.raises(expected_exc): np.digitize(a, None) diff --git a/tests/integration/test_dot.py b/tests/integration/test_dot.py index e3b775145e..c45e1f0ae8 100644 --- a/tests/integration/test_dot.py +++ b/tests/integration/test_dot.py @@ -18,8 +18,8 @@ from utils.contractions import check_default from utils.generators import mk_0to1_array -import cunumeric as num -from cunumeric._utils.linalg import dot_modes +import cupynumeric as num +from cupynumeric._utils.linalg import dot_modes @pytest.mark.parametrize("b_ndim", range(LEGATE_MAX_DIM + 1)) @@ -64,7 +64,7 @@ def test_out_invalid_shape(self, shape): ) def test_out_invalid_dtype(self, dtype): # In Numpy, for np.float32 and np.int64, it raises ValueError - # In cuNumeric, + # In cuPyNumeric, # for np.float32, it pass # for np.int64, it raises TypeError: Unsupported type: int64 out = num.zeros((5, 2), dtype=dtype) diff --git a/tests/integration/test_einsum.py b/tests/integration/test_einsum.py index 1268c4fee5..3debf76749 100644 --- a/tests/integration/test_einsum.py +++ b/tests/integration/test_einsum.py @@ -22,7 +22,7 @@ from utils.comparisons import allclose from utils.generators import mk_0to1_array, permutes_to -import cunumeric as num +import cupynumeric as num # Limits for exhaustive expression generation routines MAX_MODES = 3 @@ -294,7 +294,7 @@ def test_expr_opposite(): # sum subscripts string, subscripts must be letters with pytest.raises(expected_exc): num.einsum("ik,kj=>ij", a, b) - # cuNumeric raises ValueError: Subscripts can only contain one '->' + # cuPyNumeric raises ValueError: Subscripts can only contain one '->' @pytest.mark.xfail diff --git a/tests/integration/test_einsum_path.py b/tests/integration/test_einsum_path.py index 675ae44500..c5b59cb5da 100644 --- a/tests/integration/test_einsum_path.py +++ b/tests/integration/test_einsum_path.py @@ -16,7 +16,7 @@ import numpy as np import pytest -import cunumeric as num +import cupynumeric as num expr = "ij,jk,kl->il" np_a = np.empty((2, 2)) @@ -61,7 +61,7 @@ def test_einsum_path_optimize_opposite(optimize): path_num, _ = num.einsum_path( expr, num_a, num_b, num_c, optimize=optimize ) - # cuNumeric raises ValueError: einsum_path: unexpected value + # cuPyNumeric raises ValueError: einsum_path: unexpected value # for optimize: 2 @@ -71,7 +71,7 @@ def test_einsum_path_optimize_none(): path_np, _ = np.einsum_path(expr, np_a, np_b, np_c, optimize=optimize) # Numpy returns results path_num, _ = num.einsum_path(expr, num_a, num_b, num_c, optimize=optimize) - # cunumeric raises ValueError: einsum_path: unexpected value + # cupynumeric raises ValueError: einsum_path: unexpected value # for optimize: None assert path_np == path_num diff --git a/tests/integration/test_exp.py b/tests/integration/test_exp.py index 1b0fe195a0..af14b1f64a 100644 --- a/tests/integration/test_exp.py +++ b/tests/integration/test_exp.py @@ -16,9 +16,9 @@ import numpy as np import pytest -import cunumeric as num +import cupynumeric as num -# cunumeric.exp(*args: Any, out: Union[ndarray, None] = None, +# cupynumeric.exp(*args: Any, out: Union[ndarray, None] = None, # where: bool = True, casting: CastingKind = 'same_kind', # order: str = 'K', # dtype: Union[np.dtype[Any], None] = None, **kwargs: Any) → ndarray @@ -69,7 +69,7 @@ def test_casting_negative(casting): arr_np = np.array(arr_num) res_num = num.exp(arr_num, casting=casting) res_np = np.exp(arr_np, casting=casting) - # cuNumeric run successfully. + # cuPyNumeric run successfully. # Numpy raises " numpy.core._exceptions._UFuncInputCastingError: # Cannot cast ufunc 'exp' input from dtype('int64') to dtype('float64') # with casting rule 'no' @@ -98,7 +98,7 @@ def test_where_false(): arr_np = np.array(arr_num) np_out = np.ones(shape=shape) # Numpy get the results. - # cuNumeric raises "NotImplementedError: + # cuPyNumeric raises "NotImplementedError: # the 'where' keyword is not yet supported" num.exp(arr_num, where=False, out=num_out) np.exp(arr_np, where=False, out=np_out) diff --git a/tests/integration/test_expand_dims.py b/tests/integration/test_expand_dims.py index 043d209e78..ab69bea0ec 100644 --- a/tests/integration/test_expand_dims.py +++ b/tests/integration/test_expand_dims.py @@ -17,7 +17,7 @@ import pytest from utils.utils import AxisError -import cunumeric as num +import cupynumeric as num DIM = 5 SIZES = [ @@ -37,7 +37,7 @@ def test_none_array_compare(): res_num = num.expand_dims( None, 0 - ) # TypeError: cuNumeric does not support dtype=object + ) # TypeError: cuPyNumeric does not support dtype=object res_np = np.expand_dims(None, 0) # return array([None], dtype=object) assert np.array_equal(res_num, res_np, equal_nan=True) diff --git a/tests/integration/test_extract.py b/tests/integration/test_extract.py index 5268a51f8c..a87f473169 100644 --- a/tests/integration/test_extract.py +++ b/tests/integration/test_extract.py @@ -17,7 +17,7 @@ import pytest from utils.generators import mk_seq_array -import cunumeric as num +import cupynumeric as num DIM = 5 SIZES = [ @@ -91,7 +91,7 @@ def test_negative_condition(con): @pytest.mark.xfail def test_complex_condition(): # when condition is complex type a+bj, - # if a==0, cuNumeric take it as 0, while Numpy take it as 1 + # if a==0, cuPyNumeric take it as 0, while Numpy take it as 1 a = np.array([1, 2, 3, 4]) b = num.array([1, 2, 3, 4]) condition = [1 + 2j, 2, 2, 5j] @@ -179,7 +179,7 @@ def test_place_basic(shape, vals): assert np.array_equal(arr_np, arr_num) -@pytest.mark.xfail(reason="cunumeric raises exception when vals is ndim") +@pytest.mark.xfail(reason="cupynumeric raises exception when vals is ndim") @pytest.mark.parametrize("vals", VALUES, ids=str) @pytest.mark.parametrize("ndim", range(2, DIM), ids=str) def test_place_vals_ndim(vals, ndim): @@ -194,7 +194,7 @@ def test_place_vals_ndim(vals, ndim): # NumPy pass, array([[[2, 2, 2], [2, 2, 2]]]) np.place(arr_np, mask_np, vals_np) - # cuNumeric raises ValueError: vals array has to be 1-dimensional + # cuPyNumeric raises ValueError: vals array has to be 1-dimensional num.place(arr_num, mask_num, vals_num) assert np.array_equal(arr_np, arr_num) diff --git a/tests/integration/test_eye.py b/tests/integration/test_eye.py index 7d7bf43598..a1abca2791 100644 --- a/tests/integration/test_eye.py +++ b/tests/integration/test_eye.py @@ -17,7 +17,7 @@ import pytest from utils.utils import check_module_function -import cunumeric as num +import cupynumeric as num N = 5 KS = [0, -1, 1, -2, 2] @@ -26,27 +26,27 @@ @pytest.mark.parametrize("k", KS + [-N, N, -10 * N, 10 * N]) @pytest.mark.parametrize("M", [N, N + 1, N - 1, N * 10, 0]) def test_eye(M, k): - print_msg = f"np & cunumeric.eye({N},{M}, k={k})" + print_msg = f"np & cupynumeric.eye({N},{M}, k={k})" check_module_function("eye", [N, M], {"k": k}, print_msg) @pytest.mark.parametrize("dtype", [np.int32, np.float64, None], ids=str) @pytest.mark.parametrize("k", KS, ids=str) def test_square(k, dtype): - print_msg = f"np & cunumeric.eye({N},k={k},dtype={dtype})" + print_msg = f"np & cupynumeric.eye({N},k={k},dtype={dtype})" check_module_function("eye", [N], {"k": k, "dtype": dtype}, print_msg) def test_N_zero(): N = 0 - print_msg = f"np & cunumeric eye({N})" + print_msg = f"np & cupynumeric eye({N})" check_module_function("eye", [N], {}, print_msg) def test_M_zero(): N = 5 M = 0 - print_msg = f"np & cunumeric eye({N},{M})" + print_msg = f"np & cupynumeric eye({N},{M})" check_module_function("eye", [N, M], {}, print_msg) @@ -74,7 +74,7 @@ def testBadM(self): @pytest.mark.xfail def testBadK(self): # numpy: raises TypeError - # cunumeric: the error is found by legate, raises struct.error + # cupynumeric: the error is found by legate, raises struct.error with pytest.raises(TypeError): num.eye(5, k=0.0) diff --git a/tests/integration/test_fallback.py b/tests/integration/test_fallback.py index 885762993a..2971b78358 100644 --- a/tests/integration/test_fallback.py +++ b/tests/integration/test_fallback.py @@ -16,7 +16,7 @@ import numpy as np import pytest -import cunumeric as num +import cupynumeric as num def test_array_equal(): @@ -33,7 +33,7 @@ def test_ufunc(): # methods. If logical_and.accumulate becomes implemented in the future, # this assertion will start to fail, and a new (unimplemented) ufunc method # should be found to replace it - assert not num.logical_and.accumulate._cunumeric.implemented + assert not num.logical_and.accumulate._cupynumeric.implemented out_num = num.logical_and.accumulate(in_num) out_np = np.logical_and.accumulate(in_np) diff --git a/tests/integration/test_fft_c2c.py b/tests/integration/test_fft_c2c.py index 16f4598946..c16a4bbe1f 100644 --- a/tests/integration/test_fft_c2c.py +++ b/tests/integration/test_fft_c2c.py @@ -18,7 +18,7 @@ from utils.comparisons import allclose as _allclose from utils.generators import mk_0to1_array -import cunumeric as num +import cupynumeric as num def allclose(A, B): @@ -247,7 +247,7 @@ def test_4d(): pytest.param(np.uint64, marks=pytest.mark.xfail), pytest.param(np.float16, marks=pytest.mark.xfail), # NumPy accepts the dtypes - # cuNumeric raises + # cuPyNumeric raises # TypeError: FFT input not supported (missing a conversion?) ), ids=str, diff --git a/tests/integration/test_fft_c2r.py b/tests/integration/test_fft_c2r.py index 861977b59d..7ee6a824c9 100644 --- a/tests/integration/test_fft_c2r.py +++ b/tests/integration/test_fft_c2r.py @@ -17,7 +17,7 @@ import pytest from utils.comparisons import allclose as _allclose -import cunumeric as num +import cupynumeric as num def allclose(A, B): diff --git a/tests/integration/test_fft_hermitian.py b/tests/integration/test_fft_hermitian.py index 623e6ec31f..8431e6b82f 100644 --- a/tests/integration/test_fft_hermitian.py +++ b/tests/integration/test_fft_hermitian.py @@ -17,7 +17,7 @@ import pytest from utils.comparisons import allclose as _allclose -import cunumeric as num +import cupynumeric as num def allclose(A, B): diff --git a/tests/integration/test_fft_r2c.py b/tests/integration/test_fft_r2c.py index 408b0de2fe..c21a8c6494 100644 --- a/tests/integration/test_fft_r2c.py +++ b/tests/integration/test_fft_r2c.py @@ -17,7 +17,7 @@ import pytest from utils.comparisons import allclose as _allclose -import cunumeric as num +import cupynumeric as num def allclose(A, B): diff --git a/tests/integration/test_fftshift.py b/tests/integration/test_fftshift.py index ec30914a0f..1d3fb280e5 100644 --- a/tests/integration/test_fftshift.py +++ b/tests/integration/test_fftshift.py @@ -16,7 +16,7 @@ import numpy as np import pytest -import cunumeric as num +import cupynumeric as num def test_fftshift_1d(): diff --git a/tests/integration/test_file.py b/tests/integration/test_file.py index 0f815a45cd..9df5281484 100644 --- a/tests/integration/test_file.py +++ b/tests/integration/test_file.py @@ -18,7 +18,7 @@ import numpy as np import pytest -import cunumeric as num +import cupynumeric as num def test_load(): diff --git a/tests/integration/test_fill.py b/tests/integration/test_fill.py index 89cfde7a5a..6b29230e48 100644 --- a/tests/integration/test_fill.py +++ b/tests/integration/test_fill.py @@ -18,7 +18,7 @@ import numpy as np import pytest -import cunumeric as num +import cupynumeric as num INF_VALUES = [-np.inf, np.inf] FLOAT_FILL_VALUES = (-2.4e120, -1.3, 8.9e-130, 0.0, 5.7e-150, 0.6, 3.7e160) @@ -53,11 +53,11 @@ def test_fill_int_with_none(): a_np = np.full((2, 3), 1) a_num = num.array(a_np) # numpy fill with -9223372036854775808, - # while cunumeric raises TypeError + # while cupynumeric raises TypeError # # Update (wonchan): Numpy 1.23.3 no longer fills # the array with -9223372036854775808 on 'array.fill(None)' - # but raises the same exception as cuNumeric + # but raises the same exception as cuPyNumeric try: int(None) except TypeError as e: @@ -70,7 +70,7 @@ def test_fill_int_with_nan(): a_np = np.full((2, 3), 1) a_num = num.array(a_np) # numpy fill with -9223372036854775808, - # while cunumeric raises ValueError + # while cupynumeric raises ValueError msg = r"cannot convert float NaN to integer" with pytest.raises(ValueError, match=msg): a_num.fill(np.nan) @@ -81,7 +81,7 @@ def test_fill_inf_to_int(value: float) -> None: a_np = np.full((2, 3), 1) a_num = num.array(a_np) # numpy fill with -9223372036854775808, - # while cunumeric raises OverflowError + # while cupynumeric raises OverflowError msg = r"cannot convert float infinity to integer" with pytest.raises(OverflowError, match=msg): a_num.fill(value) @@ -110,7 +110,7 @@ def test_fill_float_to_int(value: float) -> None: a_np = np.full((2, 3), 1) a_num = num.array(a_np) # numpy fill with -9223372036854775808, - # while cunumeric raises OverflowError + # while cupynumeric raises OverflowError msg = r"Python int too large to convert to C long" with pytest.raises(OverflowError, match=msg): a_num.fill(value) diff --git a/tests/integration/test_fill_diagonal.py b/tests/integration/test_fill_diagonal.py index 7482fc4128..0ce8eb3dd5 100644 --- a/tests/integration/test_fill_diagonal.py +++ b/tests/integration/test_fill_diagonal.py @@ -18,9 +18,8 @@ from legate.core import LEGATE_MAX_DIM from utils.generators import mk_seq_array -import cunumeric as num +import cupynumeric as num -# cunumeric.fill_diagonal(a: ndarray, val: ndarray, wrap: bool = False) → None WRAP = [True, False] @@ -121,7 +120,7 @@ def test_val_none(self): # a bytes-like object or a real number, not 'NoneType' with pytest.raises(expected_exc): num.fill_diagonal(num_array, val) - # cuNumeric raises AttributeError: + # cuPyNumeric raises AttributeError: # 'NoneType' object has no attribute 'size' diff --git a/tests/integration/test_flags.py b/tests/integration/test_flags.py index ae63f117ea..f041e918d4 100644 --- a/tests/integration/test_flags.py +++ b/tests/integration/test_flags.py @@ -16,7 +16,7 @@ import numpy as np import pytest -import cunumeric as num +import cupynumeric as num DIM_CASE = (5, 5) diff --git a/tests/integration/test_flatten.py b/tests/integration/test_flatten.py index f143597e93..d571bee64b 100644 --- a/tests/integration/test_flatten.py +++ b/tests/integration/test_flatten.py @@ -17,7 +17,7 @@ import pytest from utils.utils import check_array_method -import cunumeric as num +import cupynumeric as num DIM = 10 @@ -39,7 +39,7 @@ @pytest.mark.parametrize("size", SIZES, ids=str) def test_basic(order, size): a = np.random.randint(low=0, high=100, size=size) - print_msg = f"np & cunumeric.ndarray.flatten({order})" + print_msg = f"np & cupynumeric.ndarray.flatten({order})" check_array_method(a, "flatten", [order], {}, print_msg) diff --git a/tests/integration/test_flip.py b/tests/integration/test_flip.py index 97e57ef26b..6df8b7962e 100644 --- a/tests/integration/test_flip.py +++ b/tests/integration/test_flip.py @@ -19,7 +19,7 @@ from legate.core import LEGATE_MAX_DIM from utils.utils import AxisError -import cunumeric as num +import cupynumeric as num a = num.random.random((10, 10, 10)) AXES_1d = [-2, 0, 1, 2] diff --git a/tests/integration/test_floating.py b/tests/integration/test_floating.py index b68731ce27..4fd9856238 100644 --- a/tests/integration/test_floating.py +++ b/tests/integration/test_floating.py @@ -17,7 +17,7 @@ import pytest from utils.comparisons import allclose -import cunumeric as num +import cupynumeric as num SHAPES = [ (10, 20), diff --git a/tests/integration/test_get_item.py b/tests/integration/test_get_item.py index 6293737064..9cc49de438 100644 --- a/tests/integration/test_get_item.py +++ b/tests/integration/test_get_item.py @@ -15,7 +15,7 @@ import numpy as np import pytest -import cunumeric as num +import cupynumeric as num def test_basic(): diff --git a/tests/integration/test_gradient.py b/tests/integration/test_gradient.py index d5b4804fb9..786d579f63 100644 --- a/tests/integration/test_gradient.py +++ b/tests/integration/test_gradient.py @@ -18,7 +18,7 @@ import pytest from legate.core import LEGATE_MAX_DIM -import cunumeric as cn +import cupynumeric as cn def test_gradient_with_scalar_dx(): diff --git a/tests/integration/test_histogram.py b/tests/integration/test_histogram.py index 3764f7f51b..39eba1acbe 100644 --- a/tests/integration/test_histogram.py +++ b/tests/integration/test_histogram.py @@ -17,7 +17,7 @@ import pytest from utils.comparisons import allclose -import cunumeric as num +import cupynumeric as num @pytest.mark.parametrize( diff --git a/tests/integration/test_identity.py b/tests/integration/test_identity.py index f84e000d17..ec669cec8f 100644 --- a/tests/integration/test_identity.py +++ b/tests/integration/test_identity.py @@ -16,7 +16,7 @@ import numpy as np import pytest -import cunumeric as num +import cupynumeric as num DTYPE_ALL = [ np.int8, diff --git a/tests/integration/test_index_routines.py b/tests/integration/test_index_routines.py index 405a6a2d5a..526bab1b89 100644 --- a/tests/integration/test_index_routines.py +++ b/tests/integration/test_index_routines.py @@ -21,8 +21,8 @@ from utils.generators import mk_seq_array from utils.utils import AxisError -import cunumeric as num -from cunumeric._thunk.eager import diagonal_reference +import cupynumeric as num +from cupynumeric._thunk.eager import diagonal_reference class TestChoose1d: @@ -176,7 +176,7 @@ def test_choose_out(): num_a = mk_seq_array(num, shape_a) % shape_choices[0] num_a = num_a.astype( np.int32 - ) # cuNumeric would convert np.int32 to default type np.int64 + ) # cuPyNumeric would convert np.int32 to default type np.int64 np_choices = mk_seq_array(np, shape_choices) num_choices = mk_seq_array(num, shape_choices) np_aout = mk_seq_array(np, shape_a_out) - 10 @@ -191,7 +191,7 @@ def test_choose_out(): @pytest.mark.xfail def test_choose_mode_none(): # In Numpy, pass and returns array equals default mode - # In cuNumeric, raises ValueError: mode=None not understood. + # In cuPyNumeric, raises ValueError: mode=None not understood. # Must be 'raise', 'wrap', or 'clip' shape_choices = (3, 2, 4) shape_a = (2, 4) @@ -242,7 +242,7 @@ def test_a_invalid_shape(self, shape_a): @pytest.mark.xfail def test_a_none(self): # In Numpy, it raises TypeError - # In cuNumeric, it raises AttributeError: + # In cuPyNumeric, it raises AttributeError: # 'NoneType' object has no attribute 'choose' with pytest.raises(TypeError): num.choose(None, self.choices) @@ -255,7 +255,7 @@ def test_empty_choices(self): @pytest.mark.xfail def test_choices_none(self): # In Numpy, it raises TypeError - # In cuNumeric, it raises IndexError: tuple index out of range + # In cuPyNumeric, it raises IndexError: tuple index out of range with pytest.raises(TypeError): num.choose(self.a, None) @@ -444,7 +444,7 @@ def test_diagonal(): def test_diagonal_offset(shape, k): # for shape=(5, 1) and k=1, 2, # for shape=(1, 5) and k=-1, -2, - # In cuNumeric, raise ValueError: 'offset' + # In cuPyNumeric, raise ValueError: 'offset' # for diag or diagonal must be in range # In Numpy, pass and returns empty array a = mk_seq_array(num, shape) @@ -462,7 +462,7 @@ def test_diagonal_offset(shape, k): ) def test_diagonal_empty_array(shape): # for shape=(3, 0) and k=0, - # In cuNumeric, raise ValueError: 'offset' + # In cuPyNumeric, raise ValueError: 'offset' # for diag or diagonal must be in range # In Numpy, pass and returns empty array a = mk_seq_array(num, shape) @@ -473,13 +473,13 @@ def test_diagonal_empty_array(shape): assert np.array_equal(b, bn) -@pytest.mark.xfail(reason="cuNumeric does not take single axis") +@pytest.mark.xfail(reason="cuPyNumeric does not take single axis") def test_diagonal_axis1(): shape = (3, 1, 2) a = mk_seq_array(num, shape) an = mk_seq_array(np, shape) - # cuNumeric hits AssertionError in _diag_helper: assert axes is not None + # cuPyNumeric hits AssertionError in _diag_helper: assert axes is not None b = num.diagonal(a, axis1=2) # NumPy passes bn = np.diagonal(an, axis1=2) @@ -504,7 +504,7 @@ def test_1d_array(self): @pytest.mark.xfail def test_array_none(self): - # In cuNumeric, it raises AttributeError: + # In cuPyNumeric, it raises AttributeError: # 'NoneType' object has no attribute 'diagonal' # In Numpy, it raises ValueError: # diag requires an array of at least two dimensions. @@ -518,7 +518,7 @@ def test_array_none(self): ) def test_axes_same(self, axes): # For axes = (0, -3), - # In cuNumeric, it raises ValueError: + # In cuPyNumeric, it raises ValueError: # axes must be the same size as ndim for transpose # In Numpy, it raises ValueError: axis1 and axis2 cannot be the same axis1, axis2 = axes @@ -532,7 +532,7 @@ def test_axes_same(self, axes): ) def test_axes_out_of_bound(self, axes): # In Numpy, it raises AxisError: is out of bounds - # In cuNumeric, it raises ValueError: + # In cuPyNumeric, it raises ValueError: # axes must be the same size as ndim for transpose axis1, axis2 = axes with pytest.raises(AxisError): @@ -541,14 +541,14 @@ def test_axes_out_of_bound(self, axes): @pytest.mark.xfail def test_axes_float(self): # In Numpy, it raise TypeError - # In cuNumeric, it raises AssertionError + # In cuPyNumeric, it raises AssertionError with pytest.raises(TypeError): num.diagonal(self.a, 0, 0.0, 1) @pytest.mark.xfail def test_axes_none(self): # In Numpy, it raise TypeError - # In cuNumeric, it raises AssertionError + # In cuPyNumeric, it raises AssertionError with pytest.raises(TypeError): num.diagonal(self.a, 0, None, 0) @@ -572,7 +572,7 @@ def test_n_axes_offset(self): ) def test_k_float(self, k): # for k=0.0, - # In cuNumeric, pass + # In cuPyNumeric, pass # In Numpy, raises TypeError: integer argument expected, got float with pytest.raises(TypeError): num.diagonal(self.a, k) @@ -596,7 +596,7 @@ def test_k_none(self): def test_diag(shape, k): # for shape=(5, 1) and k=1, 2, # for shape=(1, 5) and k=-1, -2, - # In cuNumeric, raise ValueError: + # In cuPyNumeric, raise ValueError: # 'offset' for diag or diagonal must be in range # In Numpy, pass and returns empty array a = mk_seq_array(num, shape) @@ -614,7 +614,7 @@ def test_diag(shape, k): ) def test_diag_empty_array(shape): # for shape=(3, 0) and k=0, - # In cuNumeric, raise ValueError: + # In cuPyNumeric, raise ValueError: # 'offset' for diag or diagonal must be in range # In Numpy, pass and returns empty array a = mk_seq_array(num, shape) @@ -640,7 +640,7 @@ def test_3d_array(self): @pytest.mark.xfail def test_array_none(self): - # In cuNumeric, it raises AttributeError, + # In cuPyNumeric, it raises AttributeError, # 'NoneType' object has no attribute 'ndim' # In Numpy, it raises ValueError, Input must be 1- or 2-d. with pytest.raises(ValueError): @@ -653,7 +653,7 @@ def test_array_none(self): ) def test_k_float(self, k): # for k=0.0, - # In cuNumeric, pass + # In cuPyNumeric, pass # In Numpy, raises TypeError: integer argument expected, got float shape = (3, 3) a = mk_seq_array(num, shape) diff --git a/tests/integration/test_indices.py b/tests/integration/test_indices.py index 5a8346bb1a..5ca02e3ba8 100644 --- a/tests/integration/test_indices.py +++ b/tests/integration/test_indices.py @@ -17,7 +17,7 @@ import pytest from legate.core import LEGATE_MAX_DIM -import cunumeric as num +import cupynumeric as num class TestIndicesErrors: @@ -47,7 +47,7 @@ def test_float_dimensions(self): def test_negative_tuple_dimensions(self): dimensions = (1, -1) # numpy raises: "ValueError: negative dimensions are not allowed" - # In cunumeric Eager Executions test, + # In cupynumeric Eager Executions test, # it raises "ValueError: negative dimensions are not allowed" # in other conditions, it raises # "ValueError: Invalid shape: Shape((2, 1, -1))" diff --git a/tests/integration/test_inlinemap-keeps-region-alive.py b/tests/integration/test_inlinemap-keeps-region-alive.py index 6f6bbf92c8..d7d7380abf 100644 --- a/tests/integration/test_inlinemap-keeps-region-alive.py +++ b/tests/integration/test_inlinemap-keeps-region-alive.py @@ -17,7 +17,7 @@ import pytest -import cunumeric as num +import cupynumeric as num def test_all(): diff --git a/tests/integration/test_inner.py b/tests/integration/test_inner.py index d1f27a12f9..01543d506a 100644 --- a/tests/integration/test_inner.py +++ b/tests/integration/test_inner.py @@ -18,8 +18,8 @@ from utils.contractions import check_default from utils.generators import mk_0to1_array -import cunumeric as num -from cunumeric._utils.linalg import inner_modes +import cupynumeric as num +from cupynumeric._utils.linalg import inner_modes @pytest.mark.parametrize("b_ndim", range(LEGATE_MAX_DIM + 1)) diff --git a/tests/integration/test_input_output.py b/tests/integration/test_input_output.py index 2e2b2f5947..409b9cc19d 100644 --- a/tests/integration/test_input_output.py +++ b/tests/integration/test_input_output.py @@ -20,7 +20,7 @@ import pytest from utils.generators import mk_0to1_array, mk_seq_array -import cunumeric as num +import cupynumeric as num def test_ndarray_dumps(): diff --git a/tests/integration/test_intra_array_copy.py b/tests/integration/test_intra_array_copy.py index 03b1976358..241a4c06ea 100644 --- a/tests/integration/test_intra_array_copy.py +++ b/tests/integration/test_intra_array_copy.py @@ -18,7 +18,7 @@ from legate.core import LEGATE_MAX_DIM from utils.generators import mk_0to1_array -import cunumeric as num +import cupynumeric as num def random_array(lib, ndim): diff --git a/tests/integration/test_item.py b/tests/integration/test_item.py index a80bc070b9..35cb43da18 100644 --- a/tests/integration/test_item.py +++ b/tests/integration/test_item.py @@ -17,7 +17,7 @@ from legate.core import LEGATE_MAX_DIM from utils.generators import generate_item -import cunumeric as num +import cupynumeric as num @pytest.mark.xfail @@ -32,7 +32,7 @@ def test_no_item(): # Numpy raises KeyError: 'invalid key' with pytest.raises(expected_exc): arr_num.item() - # cuNumeric raises ValueError: can only convert an array + # cuPyNumeric raises ValueError: can only convert an array # of size 1 to a Python scalar @@ -47,7 +47,7 @@ def test_out_of_bound(): # Numpy raises IndexError: index 10 is out of bounds for size 9 with pytest.raises(expected_exc): arr_num.item(10) - # cuNumeric returns some value + # cuPyNumeric returns some value @pytest.mark.xfail @@ -62,7 +62,7 @@ def test_out_of_index(): # for axis 1 with size 3 with pytest.raises(expected_exc): arr_num.item(2, 4) - # cuNumeric raises ValueError: Out-of-bounds projection on dimension 0 + # cuPyNumeric: ValueError: Out-of-bounds projection on dimension 0 # with index 4 for a store of shape Shape((3,)) diff --git a/tests/integration/test_itemset.py b/tests/integration/test_itemset.py index 283d976a8d..108198511d 100644 --- a/tests/integration/test_itemset.py +++ b/tests/integration/test_itemset.py @@ -17,8 +17,8 @@ from legate.core import LEGATE_MAX_DIM from utils.generators import generate_item -import cunumeric as num -from cunumeric._utils import is_np2 +import cupynumeric as num +from cupynumeric._utils import is_np2 # itemset was removed in numpy 2.0, skip the entire module if is_np2: @@ -38,7 +38,7 @@ def test_no_itemset(): # at least one argument with pytest.raises(expected_exc): arr_np.itemset() - # cuNumeric raises KeyError: 'itemset() requires + # cuPyNumeric raises KeyError: 'itemset() requires # at least one argument' @@ -55,7 +55,7 @@ def test_invalid_itemset(): # to a Python scalar with pytest.raises(expected_exc): arr_num.itemset(8) - # cuNumeric raises KeyError: 'invalid key' + # cuPyNumeric raises KeyError: 'invalid key' @pytest.mark.xfail @@ -69,7 +69,7 @@ def test_out_of_index(): # Numpy raises IndexError: index 10 is out of bounds for size 9 with pytest.raises(expected_exc): arr_num.itemset(10, 4) - # cuNumeric set the value of index 1 as 4 + # cuPyNumeric set the value of index 1 as 4 # Original array: # [[193 212 238] # [ 97 103 225] @@ -93,7 +93,7 @@ def test_tuple_out_of_index(): # for axis 1 with size 3 with pytest.raises(expected_exc): arr_num.itemset((2, 2), 4) - # cuNumeric raises ValueError: Out-of-bounds projection on + # cuPyNumeric raises ValueError: Out-of-bounds projection on # dimension 0 with index 3 for a store of shape Shape((3,)) diff --git a/tests/integration/test_jacobi.py b/tests/integration/test_jacobi.py index 82b4ff0b63..74fd679e45 100644 --- a/tests/integration/test_jacobi.py +++ b/tests/integration/test_jacobi.py @@ -17,7 +17,7 @@ import pytest from utils.comparisons import allclose -import cunumeric as num +import cupynumeric as num def test_basic(): diff --git a/tests/integration/test_length.py b/tests/integration/test_length.py index c00157eeb4..683ae3f963 100644 --- a/tests/integration/test_length.py +++ b/tests/integration/test_length.py @@ -15,7 +15,7 @@ import pytest -import cunumeric as num +import cupynumeric as num LIST_X = [1, 2, 3] diff --git a/tests/integration/test_linspace.py b/tests/integration/test_linspace.py index 0937ac10f7..c1680123fb 100644 --- a/tests/integration/test_linspace.py +++ b/tests/integration/test_linspace.py @@ -19,7 +19,7 @@ import pytest from utils.generators import broadcasts_to, mk_seq_array -import cunumeric as num +import cupynumeric as num def equivalent_shapes_gen(shape): @@ -206,10 +206,10 @@ def test_empty_array_retstep(shape, endpoint): "axis", range(-3, 3), ids=lambda axis: f"(axis={axis})" ) def test_arrays_axis(axis, number): - # In cuNumeric, if axis < -1, raise ValueError + # In cuPyNumeric, if axis < -1, raise ValueError # 'Point cannot exceed 4 dimensions set from LEGATE_MAX_DIM' # In Numpy, if axis is -2 or -3, also pass - # In cuNumeric, for axis >= -1, if num=0, raise IndexError: + # In cuPyNumeric, for axis >= -1, if num=0, raise IndexError: # tuple index out of range # In Numpy, if num=0, pass and returns empty array x = np.array([[0, 1], [2, 3]]) @@ -253,7 +253,7 @@ def setup_method(self): @pytest.mark.xfail def test_num_float(self): # In Numpy, raise TypeError - # In cuNumeric, pass + # In cuPyNumeric, pass msg = "cannot be interpreted as an integer" with pytest.raises(TypeError, match=msg): num.linspace(0, 10, num=4.5) @@ -273,7 +273,7 @@ def test_num_none(self): "axis", (-4, 3), ids=lambda axis: f"(axis={axis})" ) def test_axis_out_of_bound_array(self, axis): - # In cuNumeric, if axis < -1, raise ValueError + # In cuPyNumeric, if axis < -1, raise ValueError # 'Point cannot exceed 4 dimensions set from LEGATE_MAX_DIM' msg = "out of bounds" # In Numpy, it raises AxisError @@ -285,7 +285,7 @@ def test_axis_out_of_bound_array(self, axis): "axis", (-2, 1), ids=lambda axis: f"(axis={axis})" ) def test_axis_out_of_bound_scalar(self, axis): - # In cuNumeric, it pass and the result equals when axis=0 + # In cuPyNumeric, it pass and the result equals when axis=0 # In Numpy, it raises AxisError msg = "out of bounds" with pytest.raises(ValueError, match=msg): @@ -299,7 +299,7 @@ def test_axis_float(self): @pytest.mark.xfail def test_axis_none(self): - # In cuNumeric, pass and treat it as axis=0 + # In cuPyNumeric, pass and treat it as axis=0 # In Numpy, raises TypeError axis = None msg = "'NoneType' object is not iterable" diff --git a/tests/integration/test_logic.py b/tests/integration/test_logic.py index c4b8a33b47..9bd15566a3 100644 --- a/tests/integration/test_logic.py +++ b/tests/integration/test_logic.py @@ -18,7 +18,7 @@ import numpy as np import pytest -import cunumeric as num +import cupynumeric as num SCALARS_INF = (np.inf, -np.inf, np.nan, 0) ARRAYS_INF = ([np.inf, -np.inf, np.nan, 0],) @@ -126,7 +126,7 @@ def test_isscalar_array(): # NumPy's scalar reduction returns a Python scalar assert num.isscalar(np.sum(in_np)) is True - # but cuNumeric's scalar reduction returns a 0-D array that behaves like + # but cuPyNumeric's scalar reduction returns a 0-D array that behaves like # a deferred scalar assert num.isscalar(num.sum(in_np)) is False @@ -238,7 +238,7 @@ def test_isclose_arrays_rtol_atol(rtol, atol): def test_isclose_euqal_nan(equal_nan): # If equal_nan is True, # In Numpy, it pass - # In cuNumeric, it raises NotImplementedError + # In cuPyNumeric, it raises NotImplementedError values = [np.inf, -np.inf, np.nan, 0.0, -0.0] pairs = tuple(combinations_with_replacement(values, 2)) in1_np = np.array([x for x, _ in pairs]) diff --git a/tests/integration/test_logical.py b/tests/integration/test_logical.py index dac2de22ae..bcaa9a6ea0 100644 --- a/tests/integration/test_logical.py +++ b/tests/integration/test_logical.py @@ -17,7 +17,7 @@ import pytest from legate.core import LEGATE_MAX_DIM -import cunumeric as num +import cupynumeric as num INPUTS = ( [-1, 4, 5], @@ -39,7 +39,7 @@ @pytest.mark.parametrize("func", FUNCTIONS) def test_basic(func, input, keepdims): in_np = np.array(input) - # cuNumeric doesn't support reductions for complex128 + # cuPyNumeric doesn't support reductions for complex128 if in_np.dtype.kind == "c": in_np = in_np.astype("F") in_num = num.array(in_np) @@ -64,7 +64,7 @@ def test_basic(func, input, keepdims): def test_axis_tuple(func, axes): # For axes=(-1, 0), # in Numpy, it pass - # in cuNumeric, raises ValueError: + # in cuPyNumeric, raises ValueError: # Invalid promotion on dimension 2 for a 1-D store input = [[[5, 10], [0, 100]]] in_np = np.array(input) diff --git a/tests/integration/test_logical_reduction.py b/tests/integration/test_logical_reduction.py index 21b6216dd0..452381656c 100644 --- a/tests/integration/test_logical_reduction.py +++ b/tests/integration/test_logical_reduction.py @@ -18,7 +18,7 @@ from legate.core import LEGATE_MAX_DIM from utils.generators import mk_seq_array -import cunumeric as num +import cupynumeric as num @pytest.mark.parametrize("axis", [None, 0, 1, 2, (0, 1, 2)]) @@ -45,7 +45,7 @@ def test_logical_reductions(axis): -1, ], ) -def test_logical_reductions_over_cunumeric_arrays(ndim, axis): +def test_logical_reductions_over_cupynumeric_arrays(ndim, axis): shape = (5,) * ndim np_arr = mk_seq_array(np, shape) in_np = tuple(np_arr % 2 for dim in range(ndim)) diff --git a/tests/integration/test_lstm_backward_test.py b/tests/integration/test_lstm_backward_test.py index aa8c5bfb20..394438ffc1 100644 --- a/tests/integration/test_lstm_backward_test.py +++ b/tests/integration/test_lstm_backward_test.py @@ -17,7 +17,7 @@ import pytest from utils.comparisons import allclose -import cunumeric as num +import cupynumeric as num def test_basic(): diff --git a/tests/integration/test_lstm_simple_forward.py b/tests/integration/test_lstm_simple_forward.py index 629d1ef761..10b4502c8d 100644 --- a/tests/integration/test_lstm_simple_forward.py +++ b/tests/integration/test_lstm_simple_forward.py @@ -15,7 +15,7 @@ import pytest -import cunumeric as num +import cupynumeric as num def test_basic(): diff --git a/tests/integration/test_map_reduce.py b/tests/integration/test_map_reduce.py index 15ac5c7d05..e5eab87139 100644 --- a/tests/integration/test_map_reduce.py +++ b/tests/integration/test_map_reduce.py @@ -16,7 +16,7 @@ import numpy as np import pytest -import cunumeric as num +import cupynumeric as num def test_basic(): diff --git a/tests/integration/test_mask.py b/tests/integration/test_mask.py index 648d54e5e2..ebaa5a0058 100644 --- a/tests/integration/test_mask.py +++ b/tests/integration/test_mask.py @@ -16,7 +16,7 @@ import numpy as np import pytest -import cunumeric as num +import cupynumeric as num def test_lhs(): diff --git a/tests/integration/test_mask_indices.py b/tests/integration/test_mask_indices.py index bd45879164..44bb90cd0e 100644 --- a/tests/integration/test_mask_indices.py +++ b/tests/integration/test_mask_indices.py @@ -16,7 +16,7 @@ import numpy as np import pytest -import cunumeric as num +import cupynumeric as num KS = (0, -1, 1, -2, 2) FUNCTIONS = ("tril", "triu") @@ -61,7 +61,7 @@ def test_mask_indices(k, mask_func): ) @pytest.mark.parametrize("mask_func", FUNCTIONS) def test_mask_indices_float_k(k, mask_func): - # cuNumeric: struct.error: required argument is not an integer + # cuPyNumeric: struct.error: required argument is not an integer # Numpy: pass _test(mask_func, N, k) @@ -79,7 +79,7 @@ def test_float_n(self, n): @pytest.mark.xfail def test_k_complex(self): - # In cuNumeric, it raises struct.error, + # In cuPyNumeric, it raises struct.error, # msg is required argument is not an integer # In Numpy, it raises TypeError, # msg is '<=' not supported between instances of 'complex' and 'int' @@ -88,7 +88,7 @@ def test_k_complex(self): @pytest.mark.xfail def test_k_none(self): - # In cuNumeric, it raises struct.error, + # In cuPyNumeric, it raises struct.error, # msg is required argument is not an integer # In Numpy, it raises TypeError, # msg is unsupported operand type(s) for -: 'NoneType' and 'int' diff --git a/tests/integration/test_matmul.py b/tests/integration/test_matmul.py index 0952e4d605..be7e4caf33 100644 --- a/tests/integration/test_matmul.py +++ b/tests/integration/test_matmul.py @@ -24,8 +24,8 @@ check_types, ) -import cunumeric as num -from cunumeric._utils.linalg import matmul_modes +import cupynumeric as num +from cupynumeric._utils.linalg import matmul_modes @pytest.mark.parametrize("a_ndim", range(1, LEGATE_MAX_DIM + 1)) @@ -163,7 +163,7 @@ def test_invalid_shape_dim_greater_than_one(self, shapesAB): def test_invalid_shape_with_vector(self, shapesAB): # For ((4, 1), (3,)), ((3,), (1, 4)), ((3,), (1,)), # In Numpy, raise ValueError - # In cuNumeric, broadcast 1 to 3 and pass + # In cuPyNumeric, broadcast 1 to 3 and pass expected_exc = ValueError shapeA, shapeB = shapesAB A_np = np.ones(shapeA) @@ -221,7 +221,7 @@ def test_out_invalid_shape(self, shape): @pytest.mark.xfail def test_out_invalid_shape_DIVERGENCE(self): # In Numpy, PASS - # In cuNumeric, raise ValueError + # In cuPyNumeric, raise ValueError A = num.ones((3, 2, 4)) B = num.ones((3, 4, 3)) shape = (3, 3, 2, 3) @@ -279,7 +279,7 @@ def test_invalid_casting(self, dtype): # In Numpy, raise ValueError with pytest.raises(expected_exc): np.matmul(A_np, B_np, casting=casting) - # cuNumeric does not check casting when A and B are of the same dtype + # cuPyNumeric does not check casting when A and B are of the same dtype with pytest.raises(expected_exc): num.matmul(A_num, B_num, casting=casting) diff --git a/tests/integration/test_matrix_power.py b/tests/integration/test_matrix_power.py index 58508897c7..dca930ff69 100644 --- a/tests/integration/test_matrix_power.py +++ b/tests/integration/test_matrix_power.py @@ -19,7 +19,7 @@ from utils.comparisons import allclose from utils.generators import mk_0to1_array -import cunumeric as num +import cupynumeric as num # TODO: add negative exponents here, once they become supported EXPONENTS = (0, 1, 2, 3, 5) @@ -38,7 +38,7 @@ def test_matrix_power(ndim, exp, dtype): # If dtype=np.int32 and exp greater than 1, # In Numpy, pass - # In cuNumeric, raises TypeError: Unsupported type: int32 + # In cuPyNumeric, raises TypeError: Unsupported type: int32 shape = (3,) * ndim + (2, 2) a_np = mk_0to1_array(np, shape, dtype=dtype) a_num = mk_0to1_array(num, shape, dtype=dtype) @@ -59,7 +59,7 @@ def test_matrix_power(ndim, exp, dtype): def test_matrix_power_empty_matrix(exp): # If exp =2 or 3, # In Numpy, pass and returns empty array - # In cuNumeric, raise AssertionError in _contract + # In cuPyNumeric, raise AssertionError in _contract shape = (0, 0) a_np = mk_0to1_array(np, shape) a_num = mk_0to1_array(num, shape) diff --git a/tests/integration/test_mean.py b/tests/integration/test_mean.py index 2065b6f758..d0055945a7 100755 --- a/tests/integration/test_mean.py +++ b/tests/integration/test_mean.py @@ -16,7 +16,7 @@ import numpy as np import pytest -import cunumeric as num +import cupynumeric as num DIM = 7 @@ -103,7 +103,7 @@ def test_where_broadcast(size): @pytest.mark.parametrize("axis", ((-3, -1), (-1, 0), (-2, 2), (0, 2))) def test_axis_tuple(axis): # In Numpy, it pass - # In cuNumeric, it raises NotImplementedError + # In cuPyNumeric, it raises NotImplementedError size = (3, 4, 7) arr_np = np.random.randint(-5, 5, size=size) arr_num = num.array(arr_np) diff --git a/tests/integration/test_median.py b/tests/integration/test_median.py index 37dec719c9..c0c4315896 100644 --- a/tests/integration/test_median.py +++ b/tests/integration/test_median.py @@ -18,7 +18,7 @@ from legate.core import LEGATE_MAX_DIM from utils.generators import mk_seq_array -import cunumeric as num +import cupynumeric as num class TestMedianErrors: diff --git a/tests/integration/test_meshgrid.py b/tests/integration/test_meshgrid.py index 909ca56d57..d3ae666a48 100644 --- a/tests/integration/test_meshgrid.py +++ b/tests/integration/test_meshgrid.py @@ -17,7 +17,7 @@ import pytest from utils.generators import mk_0to1_array, mk_seq_array -import cunumeric as num +import cupynumeric as num @pytest.mark.parametrize( diff --git a/tests/integration/test_min_on_gpu.py b/tests/integration/test_min_on_gpu.py index ccb09aa82d..d4803281aa 100644 --- a/tests/integration/test_min_on_gpu.py +++ b/tests/integration/test_min_on_gpu.py @@ -15,7 +15,7 @@ import pytest -import cunumeric as num +import cupynumeric as num def test_min(): diff --git a/tests/integration/test_moveaxis.py b/tests/integration/test_moveaxis.py index f8a58f7991..296f902d1a 100644 --- a/tests/integration/test_moveaxis.py +++ b/tests/integration/test_moveaxis.py @@ -19,7 +19,7 @@ from utils.generators import mk_0to1_array from utils.utils import AxisError -import cunumeric as num +import cupynumeric as num AXES = ( (0, 0), diff --git a/tests/integration/test_msort.py b/tests/integration/test_msort.py index cc99a3fbe6..15e4a3f17d 100644 --- a/tests/integration/test_msort.py +++ b/tests/integration/test_msort.py @@ -16,10 +16,10 @@ import numpy as np import pytest -import cunumeric as num -from cunumeric._utils import is_np2 +import cupynumeric as num +from cupynumeric._utils import is_np2 -# cunumeric.msort(a: ndarray) → ndarray +# cupynumeric.msort(a: ndarray) → ndarray DIM = 5 SIZES = [ diff --git a/tests/integration/test_multi_dot.py b/tests/integration/test_multi_dot.py index 399f70e239..b6702d3951 100644 --- a/tests/integration/test_multi_dot.py +++ b/tests/integration/test_multi_dot.py @@ -18,7 +18,7 @@ from utils.comparisons import allclose from utils.generators import mk_0to1_array -import cunumeric as num +import cupynumeric as num SHAPES = [ # 2 arrays @@ -124,7 +124,7 @@ def test_out_invalid_dim(self): @pytest.mark.xfail def test_out_invalid_shape(self): - # In cuNumeric, it raises AssertionError + # In cuPyNumeric, it raises AssertionError out = num.zeros((2, 1)) with pytest.raises(ValueError): num.linalg.multi_dot(self.arrays, out=out) @@ -135,7 +135,7 @@ def test_out_invalid_shape(self): ) def test_out_invalid_dtype(self, dtype): # In Numpy, for np.float32 and np.int64, it raises ValueError - # In cuNumeric, + # In cuPyNumeric, # for np.float32, it pass # for np.int64, it raises TypeError: Unsupported type: int64 out = num.zeros((2, 2), dtype=dtype) diff --git a/tests/integration/test_nan_reduction.py b/tests/integration/test_nan_reduction.py index f0a4509974..a9b393c7ab 100644 --- a/tests/integration/test_nan_reduction.py +++ b/tests/integration/test_nan_reduction.py @@ -21,12 +21,12 @@ from legate.core import LEGATE_MAX_DIM from utils.comparisons import allclose -import cunumeric as num -from cunumeric.settings import settings +import cupynumeric as num +from cupynumeric.settings import settings NAN_FUNCS = ("nanmax", "nanmin", "nanprod", "nansum") -EAGER_TEST = os.environ.get("CUNUMERIC_FORCE_THUNK", None) == "eager" +EAGER_TEST = os.environ.get("CUPYNUMERIC_FORCE_THUNK", None) == "eager" NDIMS = range(LEGATE_MAX_DIM + 1) @@ -47,7 +47,7 @@ class TestNanReductions: @pytest.mark.parametrize("keepdims", [True, False]) def test_basic_nan_sum_prod(self, func_name, ndim, keepdims): """This test sets an element to NaN and checks if the output - from cuNumeric and numpy match.""" + from cuPyNumeric and numpy match.""" shape = (5,) * ndim size = prod(shape) in_np = np.random.random(shape) @@ -72,7 +72,7 @@ def test_basic_nan_sum_prod(self, func_name, ndim, keepdims): @pytest.mark.parametrize("keepdims", [True, False]) def test_basic_nan_min_max(self, func_name, ndim, keepdims): """This test sets an element to NaN and checks if the output - from cuNumeric and numpy match.""" + from cuPyNumeric and numpy match.""" shape = (5,) * ndim size = prod(shape) in_np = np.random.random(shape) diff --git a/tests/integration/test_nanarg_reduction.py b/tests/integration/test_nanarg_reduction.py index c5f2d66d35..c9bf247626 100644 --- a/tests/integration/test_nanarg_reduction.py +++ b/tests/integration/test_nanarg_reduction.py @@ -20,12 +20,12 @@ import pytest from legate.core import LEGATE_MAX_DIM -import cunumeric as num -from cunumeric.settings import settings +import cupynumeric as num +from cupynumeric.settings import settings NAN_ARG_FUNCS = ("nanargmax", "nanargmin") -EAGER_TEST = os.environ.get("CUNUMERIC_FORCE_THUNK", None) == "eager" +EAGER_TEST = os.environ.get("CUPYNUMERIC_FORCE_THUNK", None) == "eager" DISALLOWED_DTYPES = ( np.complex64, @@ -33,7 +33,7 @@ ) # Note that when an element is repeated mulitple times in an array, -# the output from cuNumeric and numpy will vary. This is expected and +# the output from cuPyNumeric and numpy will vary. This is expected and # is not a bug. So, whenever we compare with numpy, we try to make # sure the elements in the array are unique. Another way to circumvent # this problem would be to make sure that argument corresponding @@ -50,7 +50,7 @@ class TestNanArgReductions: @pytest.mark.parametrize("keepdims", [True, False]) def test_basic(self, func_name, ndim, keepdims): """This test inserts a NaN in the array and checks if the - output from cuNumeric and numpy match + output from cuPyNumeric and numpy match """ shape = (5,) * ndim size = prod(shape) @@ -66,7 +66,7 @@ def test_basic(self, func_name, ndim, keepdims): func_np = getattr(np, func_name) func_num = getattr(num, func_name) - # make sure numpy and cunumeric give the same out array and max val + # make sure numpy and cupynumeric give the same out array and max val out_np = np.unravel_index(func_np(in_np, keepdims=keepdims), shape) out_num = np.unravel_index(func_num(in_num, keepdims=keepdims), shape) @@ -181,7 +181,7 @@ def test_all_nan_no_numpy_compat(self, func_name, ndim): @pytest.mark.parametrize("func_name", NAN_ARG_FUNCS) def test_slice_nan_numpy_compat(self, func_name): """This test checks if we comply with the numpy when - a slice contains only NaNs and CUNUMERIC_NUMPY_COMPATABILITY + a slice contains only NaNs and CUPYNUMERIC_NUMPY_COMPATABILITY is set to 1. """ settings.numpy_compat = True @@ -211,7 +211,7 @@ def test_slice_nan_numpy_compat(self, func_name): ) def test_slice_nan_no_numpy_compat(self, identity, func_name): """This test checks if we return identity for a slice that - contains NaNs when CUNUMERIC_NUMPY_COMPATABILITY is set to 0. + contains NaNs when CUPYNUMERIC_NUMPY_COMPATABILITY is set to 0. """ settings.numpy_compat = False diff --git a/tests/integration/test_nanmean.py b/tests/integration/test_nanmean.py index f02487d116..5d13e5f8a8 100755 --- a/tests/integration/test_nanmean.py +++ b/tests/integration/test_nanmean.py @@ -16,7 +16,7 @@ import numpy as np import pytest -import cunumeric as num +import cupynumeric as num DIM = 7 @@ -94,7 +94,7 @@ def test_basic_where(size): @pytest.mark.parametrize("axis", ((-3, -1), (-1, 0), (-2, 2), (0, 2))) def test_axis_tuple(axis): # In Numpy, it pass - # In cuNumeric, it raises NotImplementedError + # In cuPyNumeric, it raises NotImplementedError size = (3, 4, 7) arr_np = np.random.randint(-5, 5, size=size).astype(float) arr_np[arr_np % 2 == 1] = np.nan diff --git a/tests/integration/test_nanpercentiles.py b/tests/integration/test_nanpercentiles.py index 620577da73..c490d72768 100644 --- a/tests/integration/test_nanpercentiles.py +++ b/tests/integration/test_nanpercentiles.py @@ -19,7 +19,7 @@ import pytest from utils.comparisons import allclose -import cunumeric as num +import cupynumeric as num ALL_METHODS = ( "inverted_cdf", @@ -62,8 +62,8 @@ def test_multi_axes(str_method, axes, qin_arr, keepdims, overwrite_input): else: qs_arr = np.array(qin_arr) - # cunumeric: - # print("cunumeric axis = %d:"%(axis)) + # cupynumeric: + # print("cupynumeric axis = %d:"%(axis)) q_out = num.nanpercentile( arr, qs_arr, diff --git a/tests/integration/test_nanquantiles.py b/tests/integration/test_nanquantiles.py index 56f4ae7f2f..33c7f92e28 100644 --- a/tests/integration/test_nanquantiles.py +++ b/tests/integration/test_nanquantiles.py @@ -19,7 +19,7 @@ import pytest from utils.comparisons import allclose -import cunumeric as num +import cupynumeric as num ALL_METHODS = ( "inverted_cdf", @@ -62,8 +62,8 @@ def test_multi_axes(str_method, axes, qin_arr, keepdims, overwrite_input): else: qs_arr = np.array(qin_arr) - # cunumeric: - # print("cunumeric axis = %d:"%(axis)) + # cupynumeric: + # print("cupynumeric axis = %d:"%(axis)) q_out = num.nanquantile( arr, qs_arr, diff --git a/tests/integration/test_nd_convolve.py b/tests/integration/test_nd_convolve.py index 4090707945..6685c45394 100644 --- a/tests/integration/test_nd_convolve.py +++ b/tests/integration/test_nd_convolve.py @@ -15,54 +15,66 @@ import os -import numpy as np import pytest -import scipy.signal as sig from utils.comparisons import allclose -import cunumeric as num +import cupynumeric as num CUDA_TEST = os.environ.get("LEGATE_NEED_CUDA") == "1" def test_interpolation_x(): - import scipy.signal as signal + import scipy.signal as signal - nz = 100 - nx = 200 - hs = 2 - nvariables = 4 - shape = (nvariables, nz + 2 * hs, nx + 2 * hs) + nz = 100 + nx = 200 + hs = 2 + nvariables = 4 + shape = (nvariables, nz + 2 * hs, nx + 2 * hs) nelements = num.prod(shape) - kernel = num.array([-1.0 / 12, 7.0 / 12, 7.0 / 12, -1.0 / 12], dtype=num.float64).reshape(1, 1, 4) + kernel = num.array( + [-1.0 / 12, 7.0 / 12, 7.0 / 12, -1.0 / 12], dtype=num.float64 + ).reshape(1, 1, 4) state = num.arange(nelements).astype(num.float64).reshape(shape) - out_legate = num.convolve(state[:, 2 : nz + 2, :], kernel, mode="same",) - out_scipy = signal.convolve(state[:, 2 : nz + 2, :], kernel, mode="same",) - - #print(f"min/max, legate: {out_legate.min()}, {out_legate.max()}", flush=True) - #print(f"min/max, scipy : {out_scipy.min()}, {out_scipy.max()}", flush=True) + out_legate = num.convolve( + state[:, 2 : nz + 2, :], + kernel, + mode="same", + ) + out_scipy = signal.convolve( + state[:, 2 : nz + 2, :], + kernel, + mode="same", + ) assert allclose(out_scipy, out_legate) def test_interpolation_z(): - import scipy.signal as signal + import scipy.signal as signal - nz = 100 - nx = 200 - hs = 2 - nvariables = 4 - shape = (nvariables, nz + 2 * hs, nx + 2 * hs) + nz = 100 + nx = 200 + hs = 2 + nvariables = 4 + shape = (nvariables, nz + 2 * hs, nx + 2 * hs) nelements = num.prod(shape) - kernel = num.array([-1.0 / 12, 7.0 / 12, 7.0 / 12, -1.0 / 12], dtype=num.float64).reshape(1, 4, 1) + kernel = num.array( + [-1.0 / 12, 7.0 / 12, 7.0 / 12, -1.0 / 12], dtype=num.float64 + ).reshape(1, 4, 1) state = num.arange(nelements).astype(num.float64).reshape(shape) - out_legate = num.convolve(state[:, :, 2 : nx + 2], kernel, mode="same",) - out_scipy = signal.convolve(state[:, :, 2 : nx + 2], kernel, mode="same",) - - #print(f"min/max, legate: {out_legate.min()}, {out_legate.max()}", flush=True) - #print(f"min/max, scipy : {out_scipy.min()}, {out_scipy.max()}", flush=True) + out_legate = num.convolve( + state[:, :, 2 : nx + 2], + kernel, + mode="same", + ) + out_scipy = signal.convolve( + state[:, :, 2 : nx + 2], + kernel, + mode="same", + ) assert allclose(out_scipy, out_legate) diff --git a/tests/integration/test_ndim.py b/tests/integration/test_ndim.py index d520928d3f..114ff691bd 100644 --- a/tests/integration/test_ndim.py +++ b/tests/integration/test_ndim.py @@ -17,7 +17,7 @@ import pytest from legate.core import LEGATE_MAX_DIM -import cunumeric as num +import cupynumeric as num @pytest.mark.parametrize("ndim", range(LEGATE_MAX_DIM + 1)) diff --git a/tests/integration/test_negaxes_quantiles.py b/tests/integration/test_negaxes_quantiles.py index 40cec5a7af..7648cf234d 100644 --- a/tests/integration/test_negaxes_quantiles.py +++ b/tests/integration/test_negaxes_quantiles.py @@ -16,10 +16,9 @@ import numpy as np import pytest -from legate.core import LEGATE_MAX_DIM from utils.comparisons import allclose -import cunumeric as num +import cupynumeric as num ALL_METHODS = ( "inverted_cdf", @@ -78,8 +77,8 @@ def test_quantiles_negative_axes(str_method, axes): dtype=float, ) - # cunumeric: - # print("cunumeric axis = %d:"%(axis)) + # cupynumeric: + # print("cupynumeric axis = %d:"%(axis)) num_q_out = num.quantile( arr, qs_arr, axis=axes, method=str_method, keepdims=keepdims ) diff --git a/tests/integration/test_nonzero.py b/tests/integration/test_nonzero.py index 990e35fa43..83b53f1158 100644 --- a/tests/integration/test_nonzero.py +++ b/tests/integration/test_nonzero.py @@ -17,13 +17,13 @@ import pytest from utils.utils import AxisError -import cunumeric as num -from cunumeric._utils import is_np2 +import cupynumeric as num +from cupynumeric._utils import is_np2 -# cunumeric.count_nonzero(a: ndarray, +# cupynumeric.count_nonzero(a: ndarray, # axis: Optional[Union[int, tuple[int, ...]]] = None) → Union[int, ndarray] -# cunumeric.nonzero(a: ndarray) → tuple[cunumeric.array.ndarray, ...] -# cunumeric.flatnonzero(a: ndarray) → ndarray +# cupynumeric.nonzero(a: ndarray) → tuple[cupynumeric.array.ndarray, ...] +# cupynumeric.flatnonzero(a: ndarray) → ndarray DIM = 5 EMPTY_SIZES = [ @@ -104,9 +104,9 @@ def test_axis_tuple(axis): out_np = np.count_nonzero(arr_np, axis=axis) # Numpy passed all axis values out_num = num.count_nonzero(arr_num, axis=axis) - # For (-1, 1), cuNumeric raises 'ValueError: + # For (-1, 1), cuPyNumeric raises 'ValueError: # Invalid promotion on dimension 2 for a 1-D store' - # For the others, cuNumeric raises 'NotImplementedError: + # For the others, cuPyNumeric raises 'NotImplementedError: # Need support for reducing multiple dimensions' assert np.array_equal(out_np, out_num) @@ -131,7 +131,7 @@ def test_empty_axis(size): for axis in range(-ndim + 1, ndim, 1): out_np = np.count_nonzero(arr_np, axis=axis) out_num = num.count_nonzero(arr_num, axis=axis) - # Numpy and cuNumeric have diffrent out. + # Numpy and cuPyNumeric have diffrent out. # out_np = array([[0]]) # out_num = 0 assert np.array_equal(out_np, out_num) @@ -148,8 +148,8 @@ def test_axis_keepdims(size, keepdims): out_np = np.count_nonzero(arr_np, axis=axis, keepdims=keepdims) out_num = num.count_nonzero(arr_num, axis=axis, keepdims=keepdims) # Numpy has the parameter 'keepdims', - # cuNumeric do not have this parameter. - # cuNumeric raises "TypeError: count_nonzero() got an unexpected + # cuPyNumeric do not have this parameter. + # cuPyNumeric raises "TypeError: count_nonzero() got an unexpected # keyword argument 'keepdims'" assert np.array_equal(out_np, out_num) diff --git a/tests/integration/test_norm.py b/tests/integration/test_norm.py index 51f9b4a5ad..94938653a6 100644 --- a/tests/integration/test_norm.py +++ b/tests/integration/test_norm.py @@ -19,7 +19,7 @@ from utils.comparisons import allclose from utils.generators import mk_0to1_array -import cunumeric as num +import cupynumeric as num VECTOR_ORDS = [None, np.inf, -np.inf, 0, 1, -1, 2, -2] @@ -44,7 +44,7 @@ def test_noaxis_1d(ord, keepdims, dtype): # for ord=0, dtype is np.complex64 # Numpy output array is float32 - # cuNumeric output array is complex64 + # cuPyNumeric output array is complex64 np_res = np.linalg.norm( np_arrays[1].astype(dtype), ord=ord, keepdims=keepdims ) @@ -101,7 +101,7 @@ def test_axis_1d(ndim, ord, keepdims): ) def test_axis_2d(ndim, ord, keepdims, axis): # For all cases when axis is (1, 0) and ord is None or fro, - # output values of cuNumeric and Numpy are different and not close enough + # output values of cuPyNumeric and Numpy are different and not close enough np_res = np.linalg.norm( np_arrays[ndim], ord=ord, axis=axis, keepdims=keepdims ) @@ -113,7 +113,7 @@ def test_axis_2d(ndim, ord, keepdims, axis): class TestNormErrors: def test_axis_invalid_type(self): - # In cuNumeric, raises error in normalize_axis_tuple + # In cuPyNumeric, raises error in normalize_axis_tuple expected_exc = TypeError x_np = np.array([1, 2, 3]) x_num = num.array([1, 2, 3]) @@ -131,7 +131,7 @@ def test_axis_invalid_type(self): ids=lambda axis: f"(axis={axis})", ) def test_axis_invalid_value(self, axis): - # for (1, 1), In cuNumeric, raises error in normalize_axis_tuple + # for (1, 1), In cuPyNumeric, raises error in normalize_axis_tuple expected_exc = ValueError ndim = 2 diff --git a/tests/integration/test_ones.py b/tests/integration/test_ones.py index 6e5f047e3f..b648d6a08a 100644 --- a/tests/integration/test_ones.py +++ b/tests/integration/test_ones.py @@ -16,9 +16,9 @@ import numpy as np import pytest -import cunumeric as num +import cupynumeric as num -# cunumeric.ones(shape: NdShapeLike, +# cupynumeric.ones(shape: NdShapeLike, # dtype: npt.DTypeLike = ) → ndarray DIM = 5 @@ -54,9 +54,9 @@ class TestOnes(object): def test_size_none(self): res_np = np.ones(None) # output is 1.0 res_num = num.ones(None) - # cunumeric raises AssertionError + # cupynumeric raises AssertionError # 'assert shape is not None' - # in cunumeric/array.py:ndarray:__init__ + # in cupynumeric/array.py:ndarray:__init__ assert np.equal(res_np, res_num) @pytest.mark.parametrize("size", (200 + 20j, "hello")) diff --git a/tests/integration/test_outer.py b/tests/integration/test_outer.py index 850cb0e837..5e2ea1caa6 100644 --- a/tests/integration/test_outer.py +++ b/tests/integration/test_outer.py @@ -17,7 +17,7 @@ import pytest from utils.generators import mk_0to1_array -import cunumeric as num +import cupynumeric as num SHAPES = ((), (0,), (1,), (10,), (4, 5), (1, 4, 5)) diff --git a/tests/integration/test_overlap.py b/tests/integration/test_overlap.py index e695297486..580331d0af 100644 --- a/tests/integration/test_overlap.py +++ b/tests/integration/test_overlap.py @@ -19,7 +19,7 @@ from utils.comparisons import allclose from utils.generators import mk_seq_array -import cunumeric as num +import cupynumeric as num def setitem(lib, a, slice_lhs, slice_rhs): diff --git a/tests/integration/test_overwrite_slice.py b/tests/integration/test_overwrite_slice.py index 005daff0ef..892e9a5817 100644 --- a/tests/integration/test_overwrite_slice.py +++ b/tests/integration/test_overwrite_slice.py @@ -16,7 +16,7 @@ import numpy as np import pytest -import cunumeric as num +import cupynumeric as num def test_basic(): diff --git a/tests/integration/test_partition.py b/tests/integration/test_partition.py index 2f75aeb1a3..7fb282981c 100644 --- a/tests/integration/test_partition.py +++ b/tests/integration/test_partition.py @@ -17,7 +17,7 @@ import pytest from utils.comparisons import allclose -import cunumeric as num +import cupynumeric as num def assert_partition(a_num, kth, axis): @@ -184,7 +184,7 @@ def test_axis_out_of_bound(self, axis): def test_kth_out_of_bound(self, kth): # For all cases, # In numpy, it raises ValueError - # In cunumeric, it pass + # In cupynumeric, it pass expected_exc = ValueError axis = 0 with pytest.raises(expected_exc): @@ -214,7 +214,7 @@ def test_axis_out_of_bound(self, axis): def test_kth_out_of_bound(self, kth): # For all cases, # In numpy, it raises ValueError - # In cunumeric, it pass + # In cupynumeric, it pass expected_exc = ValueError axis = 0 with pytest.raises(expected_exc): diff --git a/tests/integration/test_percentiles.py b/tests/integration/test_percentiles.py index 9699d462fe..9dda04a009 100644 --- a/tests/integration/test_percentiles.py +++ b/tests/integration/test_percentiles.py @@ -18,7 +18,7 @@ import pytest from utils.comparisons import allclose -import cunumeric as num +import cupynumeric as num ALL_METHODS = ( "inverted_cdf", @@ -84,8 +84,8 @@ def test_multi_axes(str_method, axes, qin_arr, keepdims, overwrite_input): else: qs_arr = np.array(qin_arr) - # cunumeric: - # print("cunumeric axis = %d:"%(axis)) + # cupynumeric: + # print("cupynumeric axis = %d:"%(axis)) q_out = num.percentile( arr, qs_arr, diff --git a/tests/integration/test_prod.py b/tests/integration/test_prod.py index 9fecf6c4f4..f7d6f9db32 100644 --- a/tests/integration/test_prod.py +++ b/tests/integration/test_prod.py @@ -17,8 +17,8 @@ from utils.comparisons import allclose from utils.utils import AxisError -import cunumeric as num -from cunumeric._utils import is_np2 +import cupynumeric as num +from cupynumeric._utils import is_np2 # numpy.prod(a, axis=None, dtype=None, out=None, keepdims=, # initial=, where=) @@ -182,7 +182,7 @@ def test_dtype(self, dtype): out_num = num.prod(arr_num) assert allclose(out_np, out_num) - @pytest.mark.xfail(reason="numpy and cunumeric return different dtypes") + @pytest.mark.xfail(reason="numpy and cupynumeric return different dtypes") @pytest.mark.parametrize("dtype", INTEGER_DTYPE, ids=to_dtype) def test_dtype_integer_precision(self, dtype): arr_np = np.arange(0, 5).astype(dtype) @@ -192,7 +192,7 @@ def test_dtype_integer_precision(self, dtype): assert allclose(out_num, arr_num.prod()) # When input precision is less than default platform integer # NumPy returns the product with dtype of platform integer - # cuNumeric returns the product with dtype of the input array + # cuPyNumeric returns the product with dtype of the input array assert allclose(out_np, out_num) @pytest.mark.parametrize( @@ -211,11 +211,11 @@ def test_dtype_complex(self, dtype): arr_np = np.array(arr, dtype=dtype) arr_num = num.array(arr, dtype=dtype) out_np = np.prod(arr_np) - # cunumeric always returns [1+0.j] when LEGATE_TEST=1 + # cupynumeric always returns [1+0.j] when LEGATE_TEST=1 out_num = num.prod(arr_num) - # When running tests with CUNUMERIC_TEST=1 and dtype is complex256, + # When running tests with CUPYNUMERIC_TEST=1 and dtype is complex256, # allclose hits assertion error: - # File "/legate/cunumeric/cunumeric/eager.py", line 293, + # File "/legate/cupynumeric/cupynumeric/eager.py", line 293, # in to_deferred_array # assert self.runtime.is_supported_dtype(self.array.dtype) # AssertionError @@ -230,7 +230,9 @@ def test_axis_basic(self, axis): out_np = np.prod(arr_np, axis=axis) assert allclose(out_np, out_num) - @pytest.mark.xfail(reason="cunumeric raises exceptions when LEGATE_TEST=1") + @pytest.mark.xfail( + reason="cupynumeric raises exceptions when LEGATE_TEST=1" + ) @pytest.mark.parametrize( "axis", ((-1, 1), (0, 1), (1, 2), (0, 2)), ids=str ) @@ -239,7 +241,7 @@ def test_axis_tuple(self, axis): arr_np = np.random.random(size) * 10 arr_num = num.array(arr_np) out_np = np.prod(arr_np, axis=axis) - # when LEGATE_TEST = 1 cuNumeric raises two types of exceptions + # when LEGATE_TEST = 1 cuPyNumeric raises two types of exceptions # (-1, 1): ValueError: Invalid promotion on dimension 2 for a 1-D store # others: # NotImplementedError: Need support for reducing multiple dimensions @@ -317,7 +319,7 @@ def test_axis_keepdims_true(self, size): for axis in range(-ndim + 1, ndim, 1): out_np = np.prod(arr_np, axis=axis, keepdims=True) out_num = num.prod(arr_num, axis=axis, keepdims=True) - # in cunumeric/deferred/unary_reduction: + # in cupynumeric/deferred/unary_reduction: # if lhs_array.size == 1: # > assert axes is None or len(axes) == rhs_array.ndim - ( # 0 if keepdims else lhs_array.ndim diff --git a/tests/integration/test_put.py b/tests/integration/test_put.py index ef8c593b9a..7284febf3d 100644 --- a/tests/integration/test_put.py +++ b/tests/integration/test_put.py @@ -18,7 +18,7 @@ from legate.core import LEGATE_MAX_DIM from utils.generators import mk_seq_array -import cunumeric as num +import cupynumeric as num INDICES_VALUES = ( (0, 10), diff --git a/tests/integration/test_put_along_axis.py b/tests/integration/test_put_along_axis.py index 89cb3725a5..03894824dc 100644 --- a/tests/integration/test_put_along_axis.py +++ b/tests/integration/test_put_along_axis.py @@ -22,7 +22,7 @@ mk_seq_array, ) -import cunumeric as num +import cupynumeric as num def equivalent_shapes_gen(shape): @@ -127,7 +127,7 @@ def test_empty_indice(): pytest.param( np.array((0,)), marks=pytest.mark.xfail( - reason="NumPy: IndexError, cuNumeric: return None" + reason="NumPy: IndexError, cuPyNumeric: return None" ), ), ], diff --git a/tests/integration/test_putmask.py b/tests/integration/test_putmask.py index fa40f7fb09..8d62b6e0b4 100644 --- a/tests/integration/test_putmask.py +++ b/tests/integration/test_putmask.py @@ -18,7 +18,7 @@ from legate.core import LEGATE_MAX_DIM from utils.generators import mk_0to1_array, mk_seq_array -import cunumeric as num +import cupynumeric as num def test_scalar(): @@ -178,7 +178,7 @@ def test_ndim(ndim): def test_a_values_different_shapes(shape_val): # for (2, 3, 4), # In Numpy, pass - # In cuNumeric, it raises ValueError + # In cuPyNumeric, it raises ValueError shape_arr = (3, 4) np_arr = mk_seq_array(np, shape_arr) num_arr = mk_seq_array(num, shape_arr) @@ -226,7 +226,7 @@ def test_invalid_mask_shape(self): def test_a_values_different_dtype(self, dtype_val): # for both cases, # In Numpy, it raises TypeError - # In cuNumeric, it pass + # In cuPyNumeric, it pass expected_exc = TypeError shape = (3, 4) dtype_arr = int diff --git a/tests/integration/test_qr.py b/tests/integration/test_qr.py index 80bcea47e1..670d763c58 100644 --- a/tests/integration/test_qr.py +++ b/tests/integration/test_qr.py @@ -17,7 +17,7 @@ import pytest from utils.comparisons import allclose -import cunumeric as num +import cupynumeric as num SIZES = (8, 9, 255) diff --git a/tests/integration/test_quantiles.py b/tests/integration/test_quantiles.py index b16da8e39d..ae96f509c8 100644 --- a/tests/integration/test_quantiles.py +++ b/tests/integration/test_quantiles.py @@ -18,7 +18,7 @@ import pytest from utils.comparisons import allclose -import cunumeric as num +import cupynumeric as num ALL_METHODS = ( "inverted_cdf", @@ -84,8 +84,8 @@ def test_multi_axes(str_method, axes, qin_arr, keepdims, overwrite_input): else: qs_arr = np.array(qin_arr) - # cunumeric: - # print("cunumeric axis = %d:"%(axis)) + # cupynumeric: + # print("cupynumeric axis = %d:"%(axis)) q_out = num.quantile( arr, qs_arr, @@ -143,8 +143,8 @@ def test_nd_quantile(str_method, ls_in, axes, keepdims): buffer=np.array([0.001, 0.37, 0.42, 0.5, 0.67, 0.83, 0.99, 0.39]).data, ) - # cunumeric: - # print("cunumeric axis = %d:"%(axis)) + # cupynumeric: + # print("cupynumeric axis = %d:"%(axis)) q_out = num.quantile( arr, qs_arr, axis=axes, method=str_method, keepdims=keepdims ) diff --git a/tests/integration/test_randint.py b/tests/integration/test_randint.py index de83116870..1da1425fd3 100644 --- a/tests/integration/test_randint.py +++ b/tests/integration/test_randint.py @@ -15,7 +15,7 @@ import pytest -import cunumeric as num +import cupynumeric as num def test_1d(): diff --git a/tests/integration/test_random.py b/tests/integration/test_random.py index 6ec69e7b59..b317ef8e2a 100644 --- a/tests/integration/test_random.py +++ b/tests/integration/test_random.py @@ -14,11 +14,11 @@ import numpy as np import pytest -import cunumeric as num +import cupynumeric as num @pytest.mark.xfail( - reason="https://github.com/nv-legate/cunumeric.internal/issues/199" + reason="https://github.com/nv-legate/cupynumeric.internal/issues/199" ) def test_basic_num() -> None: num.random.seed(10) @@ -45,7 +45,7 @@ def test_basic_np() -> None: @pytest.mark.xfail( - reason="https://github.com/nv-legate/cunumeric.internal/issues/199" + reason="https://github.com/nv-legate/cupynumeric.internal/issues/199" ) def test_none_num() -> None: num.random.seed() @@ -100,7 +100,7 @@ def test_float() -> None: np.random.seed(10.5) # TypeError: 'float' object cannot be interpreted as an integer num.random.seed(10.5) - # cuNumeric passed with float + # cuPyNumeric passed with float if __name__ == "__main__": diff --git a/tests/integration/test_random_advanced.py b/tests/integration/test_random_advanced.py index c470643231..ec4898a72a 100644 --- a/tests/integration/test_random_advanced.py +++ b/tests/integration/test_random_advanced.py @@ -18,7 +18,7 @@ import pytest from utils.random import ModuleGenerator, assert_distribution -import cunumeric as num +import cupynumeric as num LEGATE_TEST = os.environ.get("LEGATE_TEST", None) == "1" @@ -121,7 +121,7 @@ def test_geometric(t): (1.2, 3.1415), marks=pytest.mark.xfail, # NumPy returns 1-dim array - # cuNumeric raises TypeError: float() argument must be a string + # cuPyNumeric raises TypeError: float() argument must be a string # or a real number, not 'tuple' ), ), @@ -219,13 +219,13 @@ def test_random_size_none(func, args): gen_num = num.random.Generator(num.random.XORWOW(seed=seed)) a_np = getattr(gen_np, func)(*args, size=None) a_num = getattr(gen_num, func)(*args, size=None) - # cuNumeric returns singleton array + # cuPyNumeric returns singleton array # NumPy returns scalar assert np.ndim(a_np) == np.ndim(a_num) class TestRandomErrors: - # cuNumeric zipf hangs on the invalid args when LEGATE_TEST=1 + # cuPyNumeric zipf hangs on the invalid args when LEGATE_TEST=1 @pytest.mark.skipif(LEGATE_TEST, reason="Test hang when LEGATE_TEST=1") @pytest.mark.parametrize( "dist, expected_exc", diff --git a/tests/integration/test_random_beta.py b/tests/integration/test_random_beta.py index 1670b742a1..bcaf419a38 100644 --- a/tests/integration/test_random_beta.py +++ b/tests/integration/test_random_beta.py @@ -17,7 +17,7 @@ import pytest from utils.random import ModuleGenerator, assert_distribution -import cunumeric as num +import cupynumeric as num BITGENERATOR_ARGS = [ ModuleGenerator, @@ -155,7 +155,7 @@ def test_beta_size_none(func, args): gen_num = num.random.Generator(num.random.XORWOW(seed=seed)) a_np = getattr(gen_np, func)(*args, size=None) a_num = getattr(gen_num, func)(*args, size=None) - # cuNumeric returns singleton array + # cuPyNumeric returns singleton array # NumPy returns scalar assert np.ndim(a_np) == np.ndim(a_num) diff --git a/tests/integration/test_random_bitgenerator.py b/tests/integration/test_random_bitgenerator.py index c0b8b70a2f..0d6784cef5 100644 --- a/tests/integration/test_random_bitgenerator.py +++ b/tests/integration/test_random_bitgenerator.py @@ -17,7 +17,7 @@ import pytest from utils.random import ModuleGenerator, assert_distribution -import cunumeric as num +import cupynumeric as num BITGENERATOR_ARGS = [ ModuleGenerator, @@ -60,7 +60,7 @@ def test_bitgenerator_size_none(): gen_num = num.random.XORWOW(seed=seed) a_np = gen_np.random_raw(size=None) a_num = gen_num.random_raw(shape=None) - # cuNumeric returns singleton array + # cuPyNumeric returns singleton array # NumPy returns scalar assert np.ndim(a_np) == np.ndim(a_num) @@ -254,7 +254,7 @@ def test_random_size_none(func): gen_num = num.random.Generator(num.random.XORWOW(seed=seed)) a_np = getattr(gen_np, func)(size=None) a_num = getattr(gen_num, func)(size=None) - # cuNumeric returns singleton array + # cuPyNumeric returns singleton array # NumPy returns scalar assert np.ndim(a_np) == np.ndim(a_num) diff --git a/tests/integration/test_random_creation.py b/tests/integration/test_random_creation.py index 82fd8f5483..9cfecb35fa 100644 --- a/tests/integration/test_random_creation.py +++ b/tests/integration/test_random_creation.py @@ -20,10 +20,10 @@ import pytest from utils.random import assert_distribution -import cunumeric as num +import cupynumeric as num LEGATE_TEST = os.environ.get("LEGATE_TEST", None) == "1" -EAGER_TEST = os.environ.get("CUNUMERIC_FORCE_THUNK", None) == "eager" +EAGER_TEST = os.environ.get("CUPYNUMERIC_FORCE_THUNK", None) == "eager" def test_randn(): @@ -42,14 +42,14 @@ def test_randn(): def reseed_and_gen_random( func: str, seed: Any, *args: Any, **kwargs: Any ) -> tuple[Any, Any]: - """Reseeed singleton rng and generate random in NumPy and cuNumeric.""" + """Reseeed singleton rng and generate random in NumPy and cuPyNumeric.""" return gen_random_from_both(func, *args, **kwargs) def gen_random_from_both( func: str, *args: Any, **kwargs: Any ) -> tuple[Any, Any]: - """Call the same random function from both NumPy and cuNumeric.""" + """Call the same random function from both NumPy and cuPyNumeric.""" return ( getattr(np.random, func)(*args, **kwargs), getattr(num.random, func)(*args, **kwargs), @@ -63,21 +63,21 @@ def gen_random_from_both( 12345, marks=pytest.mark.xfail( not EAGER_TEST, - reason="cuNumeric does not respect the singleton generator", + reason="cuPyNumeric does not respect the singleton generator", ), - # https://github.com/nv-legate/cunumeric/issues/601 + # https://github.com/nv-legate/cupynumeric/issues/601 # NumPy: generates the same array after initializing with the seed. - # cuNumeric: keeps generating different arrays. + # cuPyNumeric: keeps generating different arrays. # seed is respected in Eager mode. ), pytest.param(None), pytest.param( (4, 6, 8), marks=pytest.mark.xfail( - reason="cuNumeric does not take tuple as seed" + reason="cuPyNumeric does not take tuple as seed" ), # NumPy: pass - # cuNumeric: from runtime.set_next_random_epoch(int(init)): + # cuPyNumeric: from runtime.set_next_random_epoch(int(init)): # TypeError: int() argument must be a string, a bytes-like object # or a real number, not 'tuple' ), @@ -95,7 +95,7 @@ def test_singleton_seed(seed): @pytest.mark.xfail( EAGER_TEST, - reason="cuNumeric does not respect seed in Eager mode", + reason="cuPyNumeric does not respect seed in Eager mode", ) @pytest.mark.parametrize( "seed", @@ -104,9 +104,9 @@ def test_singleton_seed(seed): pytest.param( (0, 4, 5), marks=pytest.mark.xfail( - reason="cuNumeric fails to generate random" + reason="cuPyNumeric fails to generate random" # NumPy: pass - # cuNumeric: struct.error: required argument is not an integer + # cuPyNumeric: struct.error:required argument is not an integer ), ), ], @@ -121,7 +121,7 @@ def test_default_rng_seed(seed): @pytest.mark.xfail( EAGER_TEST, - reason="cuNumeric does not respect seed in Eager mode", + reason="cuPyNumeric does not respect seed in Eager mode", ) def test_default_rng_bitgenerator(): seed = 12345 @@ -135,9 +135,9 @@ def test_default_rng_bitgenerator(): @pytest.mark.xfail( EAGER_TEST, - reason="cuNumeric does not respect seed in Eager mode", + reason="cuPyNumeric does not respect seed in Eager mode", ) -@pytest.mark.xfail(reason="cunumeric.internal#135") +@pytest.mark.xfail(reason="cupynumeric.internal#135") def test_default_rng_generator(): steps = 3 seed = 12345 @@ -215,13 +215,13 @@ def test_random_integers_high_limit(): assert np.max(arr_num) <= limit -@pytest.mark.xfail(reason="cuNumeric raises NotImplementedError") +@pytest.mark.xfail(reason="cuPyNumeric raises NotImplementedError") @pytest.mark.parametrize( "low, high", [(3000.45, 15000), (123, 456.7), (12.3, 45.6)], ids=str ) def test_randint_float_range(low, high): # NumPy returns integer scalar - # cuNumeric raises one of the following + # cuPyNumeric raises one of the following # NotImplementedError: 'low' must be an integer # NotImplementedError: 'high' must be an integer or None arr_np, arr_num = gen_random_from_both( @@ -234,13 +234,13 @@ def test_randint_float_range(low, high): @pytest.mark.xfail( not EAGER_TEST, - reason="cuNumeric raises NotImplementedError", + reason="cuPyNumeric raises NotImplementedError", ) @pytest.mark.parametrize("size", ALL_RNG_SIZES, ids=str) @pytest.mark.parametrize("low, high", [(1000, 65535), (0, 1024)], ids=str) @pytest.mark.parametrize("dtype", UINT_DTYPES, ids=str) def test_randint_uint(low, high, dtype, size): - # NotImplementedError: cunumeric.random.randint must be given an integer + # NotImplementedError: cupynumeric.random.randint must be given an integer # dtype # NotImplementedError: type for random.integers has to be int64 or int32 # or int16 @@ -278,7 +278,7 @@ def test_randint_distribution(low, high, size, dtype): @pytest.mark.xfail( not EAGER_TEST, - reason="cuNumeric raises NotImplementedError", + reason="cuPyNumeric raises NotImplementedError", ) @pytest.mark.parametrize("size", (1024, 1025)) def test_randint_bool(size): @@ -308,7 +308,7 @@ def test_random_sample_basic_stats(size): @pytest.mark.xfail( - reason="NumPy returns scalar, cuNumeric returns 1-dim array" + reason="NumPy returns scalar, cuPyNumeric returns 1-dim array" ) def test_random_sample_size_none(): arr_np, arr_num = gen_random_from_both("random_sample", size=None) @@ -355,7 +355,7 @@ def assert_exc_from_both(self, func, exc, *args, **kwargs): ], ids=lambda x: f" {str(getattr(x, 'expected_exception', x))} ", ) - @pytest.mark.xfail(reason="NumPy raises exceptions, cuNumeric pass") + @pytest.mark.xfail(reason="NumPy raises exceptions, cuPyNumeric pass") def test_invalid_seed(self, seed, expected_exc): self.assert_exc_from_both("seed", expected_exc, seed) # -100: NumPy raises ValueError: Seed must be between 0 and 2**32 - 1 @@ -363,7 +363,7 @@ def test_invalid_seed(self, seed, expected_exc): # dtype('float64') to dtype('int64') according to the rule 'safe' # "abc": TypeError: Cannot cast scalar from dtype(' 1024 or LEGATE_TEST=1: + # cuPyNumeric size > 1024 or LEGATE_TEST=1: # NotImplementedError: type for random.integers has to be int64 or # int32 or int16 - # cuNumeric size <= 1024 and LEGATE_TEST=0: returns array of booleans + # cuPyNumeric size <= 1024 and LEGATE_TEST=0: returns array of booleans @pytest.mark.parametrize( "size, expected_exc", diff --git a/tests/integration/test_random_gamma.py b/tests/integration/test_random_gamma.py index 56ec28159b..2af341b974 100644 --- a/tests/integration/test_random_gamma.py +++ b/tests/integration/test_random_gamma.py @@ -17,7 +17,7 @@ import pytest from utils.random import ModuleGenerator, assert_distribution -import cunumeric as num +import cupynumeric as num BITGENERATOR_ARGS = [ ModuleGenerator, @@ -140,7 +140,7 @@ def test_gamma_size_none(func): gen_num = num.random.Generator(num.random.XORWOW(seed=seed)) a_np = getattr(gen_np, func)(3.1415, 1.414, size=None) a_num = getattr(gen_num, func)(3.1415, 1.414, size=None) - # cuNumeric returns singleton array + # cuPyNumeric returns singleton array # NumPy returns scalar assert np.ndim(a_np) == np.ndim(a_num) diff --git a/tests/integration/test_random_straightforward.py b/tests/integration/test_random_straightforward.py index f3e022c35f..19dcb0ddd5 100644 --- a/tests/integration/test_random_straightforward.py +++ b/tests/integration/test_random_straightforward.py @@ -19,7 +19,7 @@ import pytest from utils.random import ModuleGenerator, assert_distribution -import cunumeric as num +import cupynumeric as num BITGENERATOR_ARGS = [ ModuleGenerator, @@ -366,7 +366,7 @@ def test_beta_sizes(t, func, args, size): @pytest.mark.xfail( - reason="cuNumeric returns singleton array; NumPy returns scalar" + reason="cuPyNumeric returns singleton array; NumPy returns scalar" ) @pytest.mark.parametrize("t", BITGENERATOR_ARGS, ids=str) @pytest.mark.parametrize("func, args", FUNC_ARGS, ids=str) @@ -376,7 +376,7 @@ def test_beta_size_none(t, func, args): gen_num = num.random.Generator(t(seed=seed)) a_np = getattr(gen_np, func)(*args, size=None) a_num = getattr(gen_num, func)(*args, size=None) - # cuNumeric returns singleton array + # cuPyNumeric returns singleton array # NumPy returns scalar # print("a_np: %s, a_num=%s\n"%(str(a_np), str(a_num))) assert (1 + np.ndim(a_np)) == np.ndim(a_num) diff --git a/tests/integration/test_reduction.py b/tests/integration/test_reduction.py index 698d145723..32c237f08a 100644 --- a/tests/integration/test_reduction.py +++ b/tests/integration/test_reduction.py @@ -17,7 +17,7 @@ from utils.comparisons import allclose from utils.utils import AxisError -import cunumeric as num +import cupynumeric as num # numpy.sum(a, axis=None, dtype=None, out=None, keepdims=, # initial=, where=) @@ -88,7 +88,7 @@ def test_dtype_negative(self, dtype): out_np = np.sum(arr_np) # Numpy return sum of all datas out_num = num.sum( arr_num - ) # cuNumeric return an array with different data + ) # cuPyNumeric return an array with different data assert allclose(out_np, out_num) def test_axis_out_bound(self): @@ -104,7 +104,7 @@ def test_axis_tuple(self, axis): arr_np = np.random.random(size) * 10 arr_num = num.array(arr_np) out_np = np.sum(arr_np, axis=axis) - # cuNumeric raises NotImplementedError: + # cuPyNumeric raises NotImplementedError: # 'Need support for reducing multiple dimensions' # Numpy get results out_num = num.sum(arr_num, axis=axis) @@ -296,7 +296,7 @@ def test_axis_keepdims(self, size, keepdims): for axis in range(-ndim + 1, ndim, 1): out_np = np.sum(arr_np, axis=axis, keepdims=keepdims) out_num = num.sum(arr_num, axis=axis, keepdims=keepdims) - # in cunumeric/deferred/unary_reduction: + # in cupynumeric/deferred/unary_reduction: # if lhs_array.size == 1: # > assert axes is None or len(axes) == rhs_array.ndim - ( # 0 if keepdims else lhs_array.ndim diff --git a/tests/integration/test_repeat.py b/tests/integration/test_repeat.py index 0df92ff172..51addb46fc 100644 --- a/tests/integration/test_repeat.py +++ b/tests/integration/test_repeat.py @@ -18,7 +18,7 @@ from utils.generators import mk_seq_array from utils.utils import AxisError -import cunumeric as num +import cupynumeric as num @pytest.mark.parametrize( @@ -49,7 +49,7 @@ def test_array_empty_repeats_invalid_negative(repeats): # together with shape (0,) (2,) with pytest.raises(expected_exc): num.repeat([], repeats) - # while cunumeric is pass with the result [] + # while cupynumeric is pass with the result [] @pytest.mark.xfail @@ -158,7 +158,7 @@ def test_array_1d_repeats_fatal_error(arr, repeats): # numpy raises "ValueError: negative dimensions are not allowed" with pytest.raises(expected_exc): num.repeat(anum, repeats) - # cuNumeric got "Fatal Python error: Aborted" + # cuPyNumeric got "Fatal Python error: Aborted" @pytest.mark.parametrize("arr", (None, [], 3, [1, 2, 3], [[1, 3], [2, 4]])) diff --git a/tests/integration/test_reshape.py b/tests/integration/test_reshape.py index 696ca0f410..e2a72f335c 100644 --- a/tests/integration/test_reshape.py +++ b/tests/integration/test_reshape.py @@ -16,7 +16,7 @@ import numpy as np import pytest -import cunumeric as num +import cupynumeric as num SQUARE_CASES = [ (10, 5, 2), @@ -133,7 +133,7 @@ def test_shape(self, shape, order): def test_0d(self, shape): # for shape=None, # In Numpy, pass, returns the flattened 1-D array - # In cuNumeric, raises TypeError: 'NoneType' object is not iterable + # In cuPyNumeric, raises TypeError: 'NoneType' object is not iterable a = num.array(self.anp) assert np.array_equal( num.reshape(a, shape), @@ -154,7 +154,7 @@ def test_1d(self): ) def test_ravel(self, order): # In Numpy, pass with 'K' - # In cuNumeric, when order is 'K', raise ValueError: + # In cuPyNumeric, when order is 'K', raise ValueError: # order 'K' is not permitted for reshaping a = num.array(self.anp) assert np.array_equal( @@ -165,7 +165,7 @@ def test_ravel(self, order): @pytest.mark.xfail def test_ravel_a_none(self): # In Numpy, pass and returns [None] - # In cuNumeric, raises AttributeError: + # In cuPyNumeric, raises AttributeError: # 'NoneType' object has no attribute 'ravel' assert np.array_equal( num.ravel(None), @@ -197,7 +197,7 @@ def setup_method(self): @pytest.mark.xfail def test_a_none(self): # In Numpy, it raises ValueError: cannot reshape array - # In cuNumeric, it raises AttributeError: + # In cuPyNumeric, it raises AttributeError: # 'NoneType' object has no attribute with pytest.raises(ValueError): num.reshape(None, self.shape) diff --git a/tests/integration/test_roll.py b/tests/integration/test_roll.py index 571b67975d..bed4fa402b 100644 --- a/tests/integration/test_roll.py +++ b/tests/integration/test_roll.py @@ -16,7 +16,7 @@ import numpy as np import pytest -import cunumeric as num +import cupynumeric as num # roll tests adapted directly from numpy/_core/tests/test_numeric.py diff --git a/tests/integration/test_rot90.py b/tests/integration/test_rot90.py index bcc9d00f81..2e0211e4d9 100644 --- a/tests/integration/test_rot90.py +++ b/tests/integration/test_rot90.py @@ -15,7 +15,7 @@ import numpy as np import pytest -import cunumeric as num +import cupynumeric as num class TestRot90: diff --git a/tests/integration/test_round.py b/tests/integration/test_round.py index 3ea87bab43..c47766b1c9 100644 --- a/tests/integration/test_round.py +++ b/tests/integration/test_round.py @@ -18,7 +18,7 @@ from legate.core import LEGATE_MAX_DIM from utils.generators import mk_0to1_array -import cunumeric as num +import cupynumeric as num FLOAT = ( np.float32, diff --git a/tests/integration/test_scan.py b/tests/integration/test_scan.py index 4b102f39cd..95bd5ca13e 100644 --- a/tests/integration/test_scan.py +++ b/tests/integration/test_scan.py @@ -18,7 +18,7 @@ import pytest from utils.generators import mk_0to1_array -import cunumeric as num +import cupynumeric as num def _gen_array(n0, shape, dt, axis, outtype): @@ -86,7 +86,7 @@ def _run_tests(op, n0, shape, dt, axis, out0, outtype): else: print("FAIL!") print(f"INPUT : {A}") - print(f"CUNUMERIC: {B}") + print(f"CUPYNUMERIC: {B}") print(f"NUMPY : {C}") assert False @@ -136,7 +136,7 @@ def _run_tests(op, n0, shape, dt, axis, out0, outtype): "dtype, outtype", [ pytest.param(np.int16, np.float64, marks=pytest.mark.xfail), - # NumPy and cuNumeric produce different values + # NumPy and cuPyNumeric produce different values # out_np: array([0., 0., 0., 0., 0., 0.]) # out_num: array([0.16666667, 0.05555556, 0.02777778, 0.01851852, # 0.0154321, 0.0154321 ])) @@ -149,7 +149,7 @@ def _run_tests(op, n0, shape, dt, axis, out0, outtype): "op", [ pytest.param("cumsum", marks=pytest.mark.xfail), - # cunumeric.cumsum returns different value to numpy.cumsum + # cupynumeric.cumsum returns different value to numpy.cumsum # out_np: array([0., 0., 0., 0., 0., 0.]) # out_num: # array([6.8983227e-310, 6.8983227e-310, 6.8983227e-310, @@ -256,7 +256,7 @@ def test_axis_out_of_bound(self, op, axis): def test_out_invalid_shape(self, op, out_shape): # for all ops and all out_shape, # in Numpy, it raises ValueError - # in cuNumeric, it raises NotImplementedError + # in cuPyNumeric, it raises NotImplementedError expected_exc = ValueError A = [1, 2, 3, 4] out_np = np.zeros(out_shape) diff --git a/tests/integration/test_searchsorted.py b/tests/integration/test_searchsorted.py index 8ef77f944b..bbed6e1d89 100644 --- a/tests/integration/test_searchsorted.py +++ b/tests/integration/test_searchsorted.py @@ -17,9 +17,9 @@ import pytest from legate.core import LEGATE_MAX_DIM -import cunumeric as num +import cupynumeric as num -# cunumeric.searchsorted(a: ndarray, v: Union[int, float, ndarray], +# cupynumeric.searchsorted(a: ndarray, v: Union[int, float, ndarray], # side: Literal['left', 'right'] = 'left', # sorter: Optional[ndarray] = None) → Union[int, ndarray] @@ -80,13 +80,13 @@ def test_val_none(self): # instances of 'NoneType' and 'NoneType' with pytest.raises(expected_exc): num.searchsorted(arr, None) - # cuNumeric raises AssertionError + # cuPyNumeric raises AssertionError # if self.deferred is None: # if self.parent is None: # > assert self.runtime.is_supported_dtype # (self.array.dtype) # E AssertionError - # cunumeric/cunumeric/eager.py:to_deferred_array() + # cupynumeric/cupynumeric/eager.py:to_deferred_array() @pytest.mark.xfail def test_side_invalid(self): @@ -98,7 +98,7 @@ def test_side_invalid(self): # (got 'hi') with pytest.raises(expected_exc): num.searchsorted(arr, 10, "hi") - # cuNumeric passed, and the result is the same as that of 'right'. + # cuPyNumeric passed, and result is the same as that of 'right'. def test_ndim_mismatch(self): a = np.random.random((5, 5, 5)) diff --git a/tests/integration/test_set_item.py b/tests/integration/test_set_item.py index bfbda631dc..67e701ebb1 100644 --- a/tests/integration/test_set_item.py +++ b/tests/integration/test_set_item.py @@ -15,7 +15,7 @@ import numpy as np import pytest -import cunumeric as num +import cupynumeric as num def test_basic(): diff --git a/tests/integration/test_setflags.py b/tests/integration/test_setflags.py index a3bc81699b..c8d3fc896f 100644 --- a/tests/integration/test_setflags.py +++ b/tests/integration/test_setflags.py @@ -16,7 +16,7 @@ import pytest from legate.core import LEGATE_MAX_DIM -import cunumeric as num +import cupynumeric as num @pytest.mark.parametrize("write", (None, False, True, 1, -1, 100, "11")) @@ -73,7 +73,7 @@ def test_writeable(): array_num = num.array([0, 0, 0, 0, 0]) array_np.setflags(1) array_num.setflags(1) - # cuNumeric raises ValueError: cannot set WRITEABLE flag to + # cuPyNumeric raises ValueError: cannot set WRITEABLE flag to # True of this array array_np[2] = 1 array_num[2] = 1 @@ -95,7 +95,7 @@ def test_logic(): expected_exc = ValueError with pytest.raises(expected_exc): array_num.setflags(uic=True) - # cuNumeric raises ValueError: cannot set WRITEBACKIFCOPY flag to True + # cuPyNumeric: ValueError: cannot set WRITEBACKIFCOPY flag to True @pytest.mark.parametrize("ndim", range(1, LEGATE_MAX_DIM + 1)) diff --git a/tests/integration/test_shape.py b/tests/integration/test_shape.py index e134f64ae3..05e4848c05 100644 --- a/tests/integration/test_shape.py +++ b/tests/integration/test_shape.py @@ -16,7 +16,7 @@ import numpy as np import pytest -import cunumeric as num +import cupynumeric as num def test_basic(): diff --git a/tests/integration/test_singleton_access.py b/tests/integration/test_singleton_access.py index 8d146a35b1..6167c2a748 100644 --- a/tests/integration/test_singleton_access.py +++ b/tests/integration/test_singleton_access.py @@ -18,7 +18,7 @@ from legate.core import LEGATE_MAX_DIM from utils.generators import mk_0to1_array, scalar_gen -import cunumeric as num +import cupynumeric as num def nonscalar_gen(lib): @@ -55,7 +55,7 @@ def array_gen(lib): # get "multiple" items from scalar array for arr in scalar_gen(lib, 42): yield arr[arr.ndim * (slice(None),)] # arr[:,:] - # TODO: fix cunumeric#34 + # TODO: fix cupynumeric#34 # yield arr[arr.ndim * (slice(1, None),)] # arr[1:,1:] # set single item on non-scalar array for arr in nonscalar_gen(lib): @@ -87,7 +87,7 @@ def array_gen(lib): for arr in scalar_gen(lib, 42): arr[arr.ndim * (slice(None),)] = -1 # arr[:,:] = -1 yield arr - # TODO: fix cunumeric#34 + # TODO: fix cupynumeric#34 # for arr in scalar_gen(lib, 42): # arr[arr.ndim * (slice(1, None),)] = -1 # arr[1:,1:] = -1 # yield arr diff --git a/tests/integration/test_slicing.py b/tests/integration/test_slicing.py index 38e4a5f518..1d186f129d 100644 --- a/tests/integration/test_slicing.py +++ b/tests/integration/test_slicing.py @@ -16,7 +16,7 @@ import numpy as np import pytest -import cunumeric as num +import cupynumeric as num def test_basic(): diff --git a/tests/integration/test_solve.py b/tests/integration/test_solve.py index 5747693001..3ecf30f123 100644 --- a/tests/integration/test_solve.py +++ b/tests/integration/test_solve.py @@ -17,7 +17,7 @@ import pytest from utils.comparisons import allclose -import cunumeric as num +import cupynumeric as num SIZES = (8, 9, 255) diff --git a/tests/integration/test_sort.py b/tests/integration/test_sort.py index 2618d0491f..4b5ef09046 100644 --- a/tests/integration/test_sort.py +++ b/tests/integration/test_sort.py @@ -16,7 +16,7 @@ import numpy as np import pytest -import cunumeric as num +import cupynumeric as num DIM = 5 SIZES = [ @@ -42,7 +42,7 @@ SORT_TYPES = ["quicksort", "mergesort", "heapsort", "stable"] -# cunumeric.sort(a: ndarray, axis: int = -1, +# cupynumeric.sort(a: ndarray, axis: int = -1, # kind: SortType = 'quicksort', order: Optional = None) → ndarray # ndarray.sort(axis=-1, kind=None, order=None) @@ -78,7 +78,7 @@ def test_sorttype_invalid(self): res_num = num.sort(arr_num, kind="negative") # Numpy raises "ValueError: sort kind must be one of 'quick', 'heap', # or 'stable' (got 'negative')" - # cuNumeric passed. The code basically supports ‘stable’ + # cuPyNumeric passed. The code basically supports ‘stable’ # or not ‘stable’. assert np.array_equal(res_num, res_np) @@ -104,7 +104,7 @@ def test_basic_axis_sort_type(self, size, sort_type): @pytest.mark.skip @pytest.mark.parametrize("size", SIZES) def test_arr_basic_axis(self, size): - # Set skip due to https://github.com/nv-legate/cunumeric/issues/781 + # Set skip due to https://github.com/nv-legate/cupynumeric/issues/781 arr_np = np.random.randint(-100, 100, size) arr_num = num.array(arr_np) for axis in range(-arr_num.ndim + 1, arr_num.ndim): @@ -118,7 +118,7 @@ def test_arr_basic_axis(self, size): @pytest.mark.parametrize("size", SIZES) @pytest.mark.parametrize("sort_type", SORT_TYPES) def test_arr_basic_axis_sort(self, size, sort_type): - # Set skip due to https://github.com/nv-legate/cunumeric/issues/781 + # Set skip due to https://github.com/nv-legate/cupynumeric/issues/781 arr_np = np.random.randint(-100, 100, size) arr_num = num.array(arr_np) for axis in range(-arr_num.ndim + 1, arr_num.ndim): @@ -131,7 +131,7 @@ def test_arr_basic_axis_sort(self, size, sort_type): @pytest.mark.skip @pytest.mark.parametrize("size", SIZES) def test_compare_arr_axis(self, size): - # Set skip due to https://github.com/nv-legate/cunumeric/issues/781 + # Set skip due to https://github.com/nv-legate/cupynumeric/issues/781 arr_num = num.random.randint(-100, 100, size) for axis in range(-arr_num.ndim + 1, arr_num.ndim): arr_num_copy = arr_num @@ -143,7 +143,7 @@ def test_compare_arr_axis(self, size): @pytest.mark.parametrize("size", SIZES) @pytest.mark.parametrize("sort_type", SORT_TYPES) def test_compare_arr_axis_sort(self, size, sort_type): - # Set skip due to https://github.com/nv-legate/cunumeric/issues/781 + # Set skip due to https://github.com/nv-legate/cupynumeric/issues/781 arr_num = num.random.randint(-100, 100, size) for axis in range(-arr_num.ndim + 1, arr_num.ndim): arr_num_copy = arr_num @@ -181,7 +181,7 @@ def test_basic_complex_axis_sort(self, size, sort_type): @pytest.mark.parametrize("size", SIZES) @pytest.mark.parametrize("sort_type", SORT_TYPES) def test_compare_complex_arr_axis_sort(self, size, sort_type): - # Set skip due to https://github.com/nv-legate/cunumeric/issues/781 + # Set skip due to https://github.com/nv-legate/cupynumeric/issues/781 arr_num = ( num.random.randint(-100, 100, size) + num.random.randint(-100, 100, size) * 1.0j diff --git a/tests/integration/test_sort_complex.py b/tests/integration/test_sort_complex.py index eeb0bc85d6..9216604453 100644 --- a/tests/integration/test_sort_complex.py +++ b/tests/integration/test_sort_complex.py @@ -16,9 +16,9 @@ import numpy as np import pytest -import cunumeric as num +import cupynumeric as num -# cunumeric.sort_complex(a: ndarray) → ndarray +# cupynumeric.sort_complex(a: ndarray) → ndarray DIM = 5 SIZES = [ diff --git a/tests/integration/test_split.py b/tests/integration/test_split.py index 3fcf6aa663..b5416cca83 100644 --- a/tests/integration/test_split.py +++ b/tests/integration/test_split.py @@ -18,16 +18,16 @@ import numpy as np import pytest -import cunumeric as num +import cupynumeric as num -# cunumeric.split(a: ndarray, indices: Union[int, ndarray], axis: int = 0) -# → list[cunumeric.array.ndarray] -# cunumeric.vsplit(a: ndarray, indices: Union[int, ndarray]) -# → list[cunumeric.array.ndarray] (axis=0) -# cunumeric.hsplit(a: ndarray, indices: Union[int, ndarray]) -# → list[cunumeric.array.ndarray] (axis=1) -# cunumeric.dsplit(a: ndarray, indices: Union[int, ndarray]) -# → list[cunumeric.array.ndarray] (axis=2) +# cupynumeric.split(a: ndarray, indices: Union[int, ndarray], axis: int = 0) +# → list[cupynumeric.array.ndarray] +# cupynumeric.vsplit(a: ndarray, indices: Union[int, ndarray]) +# → list[cupynumeric.array.ndarray] (axis=0) +# cupynumeric.hsplit(a: ndarray, indices: Union[int, ndarray]) +# → list[cupynumeric.array.ndarray] (axis=1) +# cupynumeric.dsplit(a: ndarray, indices: Union[int, ndarray]) +# → list[cupynumeric.array.ndarray] (axis=2) DIM = 6 @@ -142,7 +142,7 @@ def test_dimensions_vsplit(self): expected_exc = ValueError with pytest.raises(expected_exc): num.vsplit(ary, 1) - # cuNumeric returns [array([], dtype=float64)] + # cuPyNumeric returns [array([], dtype=float64)] with pytest.raises(expected_exc): np.vsplit(ary, 1) # Numpy raises @@ -154,7 +154,7 @@ def test_dimensions_vsplit_1(self): expected_exc = ValueError with pytest.raises(expected_exc): num.vsplit(ary, 1) - # cuNumeric returns the array + # cuPyNumeric returns the array with pytest.raises(expected_exc): np.vsplit(ary, 1) # Numpy raises diff --git a/tests/integration/test_squeeze.py b/tests/integration/test_squeeze.py index c68cae2796..ba3d87b3a3 100644 --- a/tests/integration/test_squeeze.py +++ b/tests/integration/test_squeeze.py @@ -17,7 +17,7 @@ import pytest from utils.utils import AxisError -import cunumeric as num +import cupynumeric as num DIM = 5 SIZES = [ diff --git a/tests/integration/test_stack.py b/tests/integration/test_stack.py index d981a94f22..23121d2567 100644 --- a/tests/integration/test_stack.py +++ b/tests/integration/test_stack.py @@ -17,7 +17,7 @@ import pytest -import cunumeric._utils.stack as m # module under test +import cupynumeric._utils.stack as m # module under test def test_find_last_user_stacklevel() -> None: diff --git a/tests/integration/test_stats.py b/tests/integration/test_stats.py index 12f82e598d..285c74d2de 100644 --- a/tests/integration/test_stats.py +++ b/tests/integration/test_stats.py @@ -19,7 +19,7 @@ import pytest from utils.comparisons import allclose -import cunumeric as num +import cupynumeric as num np.random.seed(143) @@ -43,14 +43,14 @@ def check_result(in_np, out_np, out_num, **isclose_kwargs): and out_np.dtype == out_num.dtype ) if not result and not is_negative_test: - print("cunumeric failed the test") + print("cupynumeric failed the test") print("Input:") print(in_np) print(f"dtype: {in_np.dtype}") print("NumPy output:") print(out_np) print(f"dtype: {out_np.dtype}") - print("cuNumeric output:") + print("cuPyNumeric output:") print(out_num) print(f"dtype: {out_num.dtype}") return result diff --git a/tests/integration/test_svd.py b/tests/integration/test_svd.py index a8c26d22e7..b20bf82991 100644 --- a/tests/integration/test_svd.py +++ b/tests/integration/test_svd.py @@ -17,7 +17,7 @@ import pytest from utils.comparisons import allclose -import cunumeric as num +import cupynumeric as num SIZES = (8, 9, 255) diff --git a/tests/integration/test_swapaxes.py b/tests/integration/test_swapaxes.py index ded45a663c..124a0a00a3 100644 --- a/tests/integration/test_swapaxes.py +++ b/tests/integration/test_swapaxes.py @@ -17,7 +17,7 @@ import pytest from utils.generators import mk_seq_array -import cunumeric as num +import cupynumeric as num a = np.array([[1, 2, 3], [4, 5, 6], [7, 8, 9]]) diff --git a/tests/integration/test_take.py b/tests/integration/test_take.py index 07e34567aa..0cee90a8c2 100644 --- a/tests/integration/test_take.py +++ b/tests/integration/test_take.py @@ -18,7 +18,7 @@ from legate.core import LEGATE_MAX_DIM from utils.generators import mk_seq_array -import cunumeric as num +import cupynumeric as num x = mk_seq_array(np, (3, 4, 5)) x_num = mk_seq_array(num, (3, 4, 5)) @@ -114,7 +114,7 @@ def test_empty_array_and_indices(): def test_ndim_default_mode(ndim, shape_in): # for shape_in=(2, 2) and ndim=4, # In Numpy, pass - # In cuNumeric, it raises ValueError: + # In cuPyNumeric, it raises ValueError: # Point cannot exceed 4 dimensions set from LEGATE_MAX_DIM shape = (5,) * ndim np_arr = mk_seq_array(np, shape) @@ -142,7 +142,7 @@ def test_ndim_default_mode(ndim, shape_in): def test_ndim_mode(ndim, mode, shape_in): # for shape_in=(3, 4) and ndim=4, # In Numpy, pass - # In cuNumeric, it raises ValueError: + # In cuPyNumeric, it raises ValueError: # Point cannot exceed 4 dimensions set from LEGATE_MAX_DIM shape = (5,) * ndim np_arr = mk_seq_array(np, shape) @@ -260,7 +260,7 @@ def test_out_invalid_dtype(self, dtype): # In Numpy, # for np.float32, it raises TypeError # for np.int64 and np.int32, it pass - # In cuNumeric, + # In cuPyNumeric, # for np.float32, it raises ValueError # for np.int32, it raises ValueError # for np.int64, it pass diff --git a/tests/integration/test_take_along_axis.py b/tests/integration/test_take_along_axis.py index a2c930e100..cfbcb59c1a 100644 --- a/tests/integration/test_take_along_axis.py +++ b/tests/integration/test_take_along_axis.py @@ -18,7 +18,7 @@ from legate.core import LEGATE_MAX_DIM from utils.generators import broadcasts_to_along_axis, mk_seq_array -import cunumeric as num +import cupynumeric as num N = 10 @@ -89,7 +89,7 @@ def test_indices_bad_type(self, dtype): ) def test_indices_bad_shape(self, shape): # In Numpy, it raises IndexError. - # In cuNumeric, it raises ValueError. + # In cuPyNumeric, it raises ValueError. ai = num.ones(shape, dtype=int) msg = "shape mismatch: indexing arrays could not be broadcast" with pytest.raises(IndexError, match=msg): diff --git a/tests/integration/test_tensordot.py b/tests/integration/test_tensordot.py index 0091a46277..0a7edc30f7 100644 --- a/tests/integration/test_tensordot.py +++ b/tests/integration/test_tensordot.py @@ -18,8 +18,8 @@ from utils.contractions import check_default from utils.generators import mk_0to1_array -import cunumeric as num -from cunumeric._utils.linalg import tensordot_modes +import cupynumeric as num +from cupynumeric._utils.linalg import tensordot_modes def gen_axes(a_ndim, b_ndim): @@ -71,7 +71,7 @@ def test_axis_invalid_value(self, axis): ) def test_axis_invalid_index(self, axis): # In Numpy, for both cases, it raises IndexError - # In cuNumeric, for both cases, it raises ValueError + # In cuPyNumeric, for both cases, it raises ValueError with pytest.raises(IndexError): num.tensordot(self.A, self.B, axis) diff --git a/tests/integration/test_tile.py b/tests/integration/test_tile.py index 72ce9ee616..3ace08b807 100644 --- a/tests/integration/test_tile.py +++ b/tests/integration/test_tile.py @@ -15,7 +15,7 @@ import numpy as np import pytest -import cunumeric as num +import cupynumeric as num def test_negative(): diff --git a/tests/integration/test_trace.py b/tests/integration/test_trace.py index 4423f83a97..6690d54110 100644 --- a/tests/integration/test_trace.py +++ b/tests/integration/test_trace.py @@ -20,7 +20,7 @@ from legate.core import LEGATE_MAX_DIM from utils.generators import mk_seq_array -import cunumeric as num +import cupynumeric as num def test_2d(): @@ -101,7 +101,7 @@ def test_ndim(ndim): def test_offset(offset): # For -3, -2, 3 # In Numpy, pass and return 0 - # In cuNumeric, it raises ValueError: + # In cuPyNumeric, it raises ValueError: # 'offset' for diag or diagonal must be in range a = np.arange(24).reshape((2, 3, 4)) a_num = num.array(a) @@ -119,7 +119,7 @@ def test_offset(offset): def test_negative_axes(axes): # For all 3 cases, # In Numpy, pass - # In cuNumeric, it raises ValueError: + # In cuPyNumeric, it raises ValueError: # axes must be the same size as ndim for transpose axis1, axis2 = axes a = np.arange(24).reshape((2, 3, 4)) @@ -158,7 +158,7 @@ def test_invalid_arrays(self, array): def test_axes_none(self, axes): # For (None, None) # In Numpy, it raises TypeError - # In cuNumeric, it pass + # In cuPyNumeric, it pass expected_exc = TypeError axis1, axis2 = axes with pytest.raises(expected_exc): diff --git a/tests/integration/test_transpose.py b/tests/integration/test_transpose.py index f97fa3b196..f0ff40165b 100644 --- a/tests/integration/test_transpose.py +++ b/tests/integration/test_transpose.py @@ -15,7 +15,7 @@ import numpy as np import pytest -import cunumeric as num +import cupynumeric as num DIM = 5 SIZES = [ @@ -61,7 +61,7 @@ def test_int_axis(self): size = (2, 3, 4) a = num.random.randint(low=-10, high=10, size=size) # numpy raises "ValueError: axes don't match array". - # cunumeric raises "TypeError". + # cupynumeric raises "TypeError". with pytest.raises(TypeError): num.transpose(a, axes=2) @@ -70,7 +70,7 @@ def test_int_axis_compare(self): size = (2, 3, 4) a = num.random.randint(low=-10, high=10, size=size) # numpy raises "ValueError: axes don't match array". - # cunumeric raises "TypeError". + # cupynumeric raises "TypeError". with pytest.raises(ValueError): num.transpose(a, axes=2) @@ -101,8 +101,8 @@ def test_axes_1d(self, size): @pytest.mark.parametrize("size", (0, 1, DIM)) @pytest.mark.parametrize("axes", (-3, 3)) def test_axes_1d_int(self, size, axes): - # For cunumeric, if array.dim==1, it returns the array itself directly, - # no matter what the axes value is. + # For cupynumeric, if array.dim==1, it returns the array itself + # directly, no matter what the axes value is. # For numpy, it raises # "AxisError: axis * is out of bounds for array of dimension 1". a = np.random.randint(low=-10, high=10, size=size) @@ -115,8 +115,8 @@ def test_axes_1d_int(self, size, axes): @pytest.mark.parametrize("size", (0, 1, DIM)) @pytest.mark.parametrize("axes", ((1,), (3, 1))) def test_axes_1d_tuple(self, size, axes): - # For cunumeric, if array.dim==1, it returns the array itself directly, - # no matter what the axes value is. + # For cupynumeric, if array.dim==1, it returns the array itself + # directly, no matter what the axes value is. # For numpy, it raises "ValueError: axes don't match array". a = np.random.randint(low=-10, high=10, size=size) b = num.array(a) @@ -172,7 +172,7 @@ def test_int_axis(self): size = (2, 3, 4) a = num.random.randint(low=-10, high=10, size=size) # numpy raises "ValueError: axes don't match array". - # cunumeric raises "TypeError". + # cupynumeric raises "TypeError". with pytest.raises(TypeError): a.transpose(axes=2) @@ -181,7 +181,7 @@ def test_int_axis_compare(self): size = (2, 3, 4) a = num.random.randint(low=-10, high=10, size=size) # numpy raises "ValueError: axes don't match array". - # cunumeric raises "TypeError". + # cupynumeric raises "TypeError". with pytest.raises(ValueError): a.transpose(axes=2) @@ -212,8 +212,8 @@ def test_axes_1d(self, size): @pytest.mark.parametrize("size", (0, 1, DIM)) @pytest.mark.parametrize("axes", (-3, 3)) def test_axes_1d_int(self, size, axes): - # For cunumeric, if array.dim==1, it returns the array itself directly, - # no matter what the axes value is. + # For cupynumeric, if array.dim==1, it returns the array itself + # directly, no matter what the axes value is. # For Numpy, it raises # "AxisError: axis * is out of bounds for array of dimension 1". a = np.random.randint(low=-10, high=10, size=size) @@ -226,8 +226,8 @@ def test_axes_1d_int(self, size, axes): @pytest.mark.parametrize("size", (0, 1, DIM)) @pytest.mark.parametrize("axes", ((1,), (3, 1))) def test_axes_1d_tuple(self, size, axes): - # For cunumeric, if array.dim==1, it returns the array itself directly, - # no matter what the axes value is. + # For cupynumeric, if array.dim==1, it returns the array itself + # directly, no matter what the axes value is. # For Numpy, it raises "ValueError: axes don't match array". a = np.random.randint(low=-10, high=10, size=size) b = num.array(a) diff --git a/tests/integration/test_tri.py b/tests/integration/test_tri.py index 194ed2b122..a348614663 100644 --- a/tests/integration/test_tri.py +++ b/tests/integration/test_tri.py @@ -17,7 +17,7 @@ import pytest from utils.utils import check_module_function -import cunumeric as num +import cupynumeric as num KS = (0, -1, 1, -2, 2) N = 100 @@ -25,7 +25,7 @@ @pytest.mark.parametrize("n", (0, 1, N), ids=lambda n: f"(n={n})") def test_tri_n(n): - print_msg = f"np & cunumeric.tri({n})" + print_msg = f"np & cupynumeric.tri({n})" check_module_function("tri", [n], {}, print_msg) @@ -33,13 +33,13 @@ def test_tri_n(n): @pytest.mark.parametrize("m", (1, 10, N), ids=lambda m: f"(M={m})") @pytest.mark.parametrize("n", (1, N), ids=lambda n: f"(n={n})") def test_tri_full(n, m, k): - print_msg = f"np & cunumeric.tri({n}, k={k}, M={m})" + print_msg = f"np & cupynumeric.tri({n}, k={k}, M={m})" check_module_function("tri", [n], {"k": k, "M": m}, print_msg) @pytest.mark.parametrize("m", (0, None), ids=lambda m: f"(M={m})") def test_tri_m(m): - print_msg = f"np & cunumeric.tri({N}, M={m})" + print_msg = f"np & cupynumeric.tri({N}, M={m})" check_module_function("tri", [N], {"M": m}, print_msg) @@ -53,18 +53,18 @@ def test_tri_m(m): @pytest.mark.parametrize("dtype", DTYPES, ids=str) def test_tri_dtype(dtype): - # cuNumeric: returns an array with dtype=int + # cuPyNumeric: returns an array with dtype=int # Numpy: returns an array with dtype=float - print_msg = f"np & cunumeric.tri({N}, dtype={dtype})" + print_msg = f"np & cupynumeric.tri({N}, dtype={dtype})" check_module_function("tri", [N], {"dtype": dtype}, print_msg) @pytest.mark.xfail @pytest.mark.parametrize("k", (-10.5, 0.0, 10.5), ids=lambda k: f"(k={k})") def test_tri_float_k(k): - # cuNumeric: struct.error: required argument is not an integer + # cuPyNumeric: struct.error: required argument is not an integer # Numpy: pass - print_msg = f"np & cunumeric.tri({N}, k={k})" + print_msg = f"np & cupynumeric.tri({N}, k={k})" check_module_function("tri", [N], {"k": k}, print_msg) @@ -140,7 +140,7 @@ def test_n_none(self): @pytest.mark.xfail def test_k_none(self): - # In cuNumeric, it raises struct.error, + # In cuPyNumeric, it raises struct.error, # msg is required argument is not an integer # In Numpy, it raises TypeError, # msg is bad operand type for unary -: 'NoneType' diff --git a/tests/integration/test_trilu.py b/tests/integration/test_trilu.py index 30b22c51ce..447add7c57 100644 --- a/tests/integration/test_trilu.py +++ b/tests/integration/test_trilu.py @@ -16,7 +16,7 @@ import numpy as np import pytest -import cunumeric as num +import cupynumeric as num KS = (0, -1, 1, -2, 2) FUNCTIONS = ("tril", "triu") @@ -61,7 +61,7 @@ def test_trilu(func, shape, dtype, k): @pytest.mark.parametrize("k", (-2.5, 0.0, 2.5), ids=lambda k: f"(k={k})") @pytest.mark.parametrize("func", FUNCTIONS) def test_trilu_float_k(func, k): - # cuNumeric: struct.error: required argument is not an integer + # cuPyNumeric: struct.error: required argument is not an integer # Numpy: pass shape = (10, 10) anp = np.ones(shape) @@ -78,7 +78,7 @@ def test_arr_none(self): @pytest.mark.xfail def test_k_none(self): - # In cuNumeric, it raises struct.error, + # In cuPyNumeric, it raises struct.error, # msg is required argument is not an integer # In Numpy, it raises TypeError, # msg is bad operand type for unary -: 'NoneType' diff --git a/tests/integration/test_trilu_indices.py b/tests/integration/test_trilu_indices.py index 8f1604e457..6233e11649 100644 --- a/tests/integration/test_trilu_indices.py +++ b/tests/integration/test_trilu_indices.py @@ -17,7 +17,7 @@ import pytest from utils.utils import check_module_function -import cunumeric as num +import cupynumeric as num KS = (0, -1, 1, -2, 2) FUNCTIONS_INDICES = ("tril_indices", "triu_indices") @@ -39,7 +39,7 @@ def _test_from(func, shape, k): @pytest.mark.parametrize("n", (0, 1, 100), ids=lambda n: f"(n={n})") @pytest.mark.parametrize("func", FUNCTIONS_INDICES) def test_trilu_indices_default(func, n): - print_msg = f"np & cunumeric.{func}({n})" + print_msg = f"np & cupynumeric.{func}({n})" check_module_function(func, [n], {}, print_msg) @@ -48,14 +48,14 @@ def test_trilu_indices_default(func, n): @pytest.mark.parametrize("n", (1, N), ids=lambda n: f"(n={n})") @pytest.mark.parametrize("func", FUNCTIONS_INDICES) def test_trilu_indices_full(func, n, m, k): - print_msg = f"np & cunumeric.{func}({n}, k={k}, m={m})" + print_msg = f"np & cupynumeric.{func}({n}, k={k}, m={m})" check_module_function(func, [n], {"k": k, "m": m}, print_msg) @pytest.mark.parametrize("m", (0, None), ids=lambda m: f"(m={m})") @pytest.mark.parametrize("func", FUNCTIONS_INDICES) def test_trilu_indices_m(func, m): - print_msg = f"np & cunumeric.{func}({N}, m={m})" + print_msg = f"np & cupynumeric.{func}({N}, m={m})" check_module_function(func, [N], {"m": m}, print_msg) @@ -63,9 +63,9 @@ def test_trilu_indices_m(func, m): @pytest.mark.parametrize("k", (-10.5, 0.0, 10.5), ids=lambda k: f"(k={k})") @pytest.mark.parametrize("func", FUNCTIONS_INDICES) def test_trilu_indices_float_k(func, k): - # cuNumeric: struct.error: required argument is not an integer + # cuPyNumeric: struct.error: required argument is not an integer # Numpy: pass - print_msg = f"np & cunumeric.{func}({N}, k={k})" + print_msg = f"np & cupynumeric.{func}({N}, k={k})" check_module_function(func, [N], {"k": k}, print_msg) @@ -141,7 +141,7 @@ def test_n_none(self): @pytest.mark.xfail def test_k_none(self): - # In cuNumeric, it raises struct.error, + # In cuPyNumeric, it raises struct.error, # msg is required argument is not an integer # In Numpy, it raises TypeError, # msg is bad operand type for unary -: 'NoneType' @@ -181,7 +181,7 @@ def test_trilu_indices_from_empty_array(func, shape): @pytest.mark.parametrize("k", (-10.5, 0.0, 10.5), ids=lambda k: f"(k={k})") @pytest.mark.parametrize("func", FUNCTIONS_INDICES_FROM) def test_trilu_indices_from_float_k(func, k): - # cuNumeric: struct.error: required argument is not an integer + # cuPyNumeric: struct.error: required argument is not an integer # Numpy: pass shape = (10, 10) _test_from(func, shape, k) @@ -221,7 +221,7 @@ def test_arr_none(self, func): @pytest.mark.xfail def test_k_none(self): - # In cuNumeric, it raises struct.error, + # In cuPyNumeric, it raises struct.error, # msg is required argument is not an integer # In Numpy, it raises TypeError, # msg is bad operand type for unary -: 'NoneType' diff --git a/tests/integration/test_unary_functions_2d_complex.py b/tests/integration/test_unary_functions_2d_complex.py index df3a896033..897ccc7630 100644 --- a/tests/integration/test_unary_functions_2d_complex.py +++ b/tests/integration/test_unary_functions_2d_complex.py @@ -16,7 +16,7 @@ import numpy as np import pytest -import cunumeric as num +import cupynumeric as num xn = np.array( [[1 + 2j, 3 - 4j, 5 + 6j], [7 - 8j, -9 + 10j, -11 - 12j]], complex diff --git a/tests/integration/test_unary_ufunc.py b/tests/integration/test_unary_ufunc.py index 5e264c6777..fb889e5aff 100644 --- a/tests/integration/test_unary_ufunc.py +++ b/tests/integration/test_unary_ufunc.py @@ -19,7 +19,7 @@ import pytest from utils.comparisons import allclose -import cunumeric as num +import cupynumeric as num def deterministic_op_test(func): @@ -47,14 +47,14 @@ def check_result(op, in_np, out_np, out_num, **isclose_kwargs): and out_np.dtype == out_num.dtype ) if not result: - print(f"cunumeric.{op} failed the test") + print(f"cupynumeric.{op} failed the test") print("Input:") print(in_np) print(f"dtype: {in_np.dtype}") print("NumPy output:") print(out_np) print(f"dtype: {out_np.dtype}") - print("cuNumeric output:") + print("cuPyNumeric output:") print(out_num) print(f"dtype: {out_num.dtype}") return result @@ -90,7 +90,7 @@ def check_op(op, in_np, out_dtype="d", **check_kwargs): assert check_result(op, in_np, out_np, out_num, **check_kwargs) - # Ask cuNumeric to produce outputs to NumPy ndarrays + # Ask cuPyNumeric to produce outputs to NumPy ndarrays out_num = np.ones(out_np.shape, dtype=out_dtype) op_num(in_num, out_num) diff --git a/tests/integration/test_unique.py b/tests/integration/test_unique.py index a6c7013308..33937a3bad 100644 --- a/tests/integration/test_unique.py +++ b/tests/integration/test_unique.py @@ -17,7 +17,7 @@ import pytest from legate.core import LEGATE_MAX_DIM -import cunumeric as num +import cupynumeric as num def test_with_nonzero(): @@ -56,7 +56,7 @@ def test_parameters(return_index, return_inverse, return_counts, axis): return_inverse=return_inverse, return_counts=return_counts, ) - # cuNumeric raises NotImplementedError: Keyword arguments + # cuPyNumeric raises NotImplementedError: Keyword arguments # for `unique` are not yet supported res_np = np.unique( arr_np, diff --git a/tests/integration/test_unravel_index.py b/tests/integration/test_unravel_index.py index 1fa15aa37d..b1ba07167d 100644 --- a/tests/integration/test_unravel_index.py +++ b/tests/integration/test_unravel_index.py @@ -18,7 +18,7 @@ from legate.core import LEGATE_MAX_DIM from utils.generators import mk_seq_array -import cunumeric as num +import cupynumeric as num class TestUnravelIndexErrors: diff --git a/tests/integration/test_update.py b/tests/integration/test_update.py index 3f4d76b8e0..660b20fe5d 100644 --- a/tests/integration/test_update.py +++ b/tests/integration/test_update.py @@ -15,7 +15,7 @@ import pytest -import cunumeric as num +import cupynumeric as num def test_basic(): diff --git a/tests/integration/test_vdot.py b/tests/integration/test_vdot.py index 0b9a20197b..6a5ee19a68 100644 --- a/tests/integration/test_vdot.py +++ b/tests/integration/test_vdot.py @@ -18,7 +18,7 @@ from utils.comparisons import allclose from utils.generators import mk_0to1_array -import cunumeric as num +import cupynumeric as num DTYPES = [np.float32, np.complex64] @@ -104,7 +104,7 @@ class TestVdotErrors: def test_a_b_invalid_shape(self, shapeAB): # for ((0,), (1,)) and ((1,), (0,)) # In Numpy, it raises ValueError - # In cuNumeric, it pass + # In cuPyNumeric, it pass expected_exc = ValueError shapeA, shapeB = shapeAB A_np = mk_0to1_array(np, shapeA) @@ -126,7 +126,7 @@ def test_a_b_invalid_shape(self, shapeAB): def test_a_b_scalar_and_arrays(self, shapeB): # For shape of (0,), (2,), (1, 2), # In Numpy, it raises ValueError - # In cuNumeric, it pass + # In cuPyNumeric, it pass expected_exc = ValueError A = 5 B_np = mk_0to1_array(np, shapeB) diff --git a/tests/integration/test_view.py b/tests/integration/test_view.py index 1894dc60a6..1be11f49bf 100644 --- a/tests/integration/test_view.py +++ b/tests/integration/test_view.py @@ -16,7 +16,7 @@ import numpy as np import pytest -import cunumeric as num +import cupynumeric as num def test_update_orig(): diff --git a/tests/integration/test_where.py b/tests/integration/test_where.py index a8193f9bf8..89905b6c56 100644 --- a/tests/integration/test_where.py +++ b/tests/integration/test_where.py @@ -17,7 +17,7 @@ import pytest from utils.generators import mk_seq_array -import cunumeric as num +import cupynumeric as num CONDITIONS = [ [[True, False], [True, True]], @@ -73,7 +73,7 @@ def test_broadcast(shape_a): @pytest.mark.xfail def test_condition_none(): # In Numpy, pass and returns [1, 2] - # In cuNumeric, raises AttributeError: + # In cuPyNumeric, raises AttributeError: # 'NoneType' object has no attribute '_maybe_convert' x = 0 y_np = np.array([1, 2]) @@ -90,10 +90,10 @@ def test_condition_none(): def test_x_y_none(values): # For x=None and y=None, # In Numpy, pass and returns [None, None] - # In cuNumeric, pass and returns (array([0]),) + # In cuPyNumeric, pass and returns (array([0]),) # For x=None and y=1 # In Numpy, pass and returns [None, 1] - # In cuNumeric, raises ValueError: both 'x' and 'y' parameters + # In cuPyNumeric, raises ValueError: both 'x' and 'y' parameters # must be specified together for where cond = [True, False] a_np = np.array(cond) @@ -164,7 +164,7 @@ def test_argwhere(input): @pytest.mark.xfail def test_argwhere_none(): # In Numpy, it pass and returns [] - # In cuNumeric, it raises AttributeError: + # In cuPyNumeric, it raises AttributeError: # 'NoneType' object has no attribute '_thunk' assert np.array_equal(np.argwhere(None), num.argwhere(None)) diff --git a/tests/integration/test_window.py b/tests/integration/test_window.py index 71503b8f9a..0ca141145a 100644 --- a/tests/integration/test_window.py +++ b/tests/integration/test_window.py @@ -18,7 +18,7 @@ import pytest from utils.comparisons import allclose -import cunumeric as num +import cupynumeric as num window_functions = ("bartlett", "blackman", "hamming", "hanning") diff --git a/tests/integration/utils/comparisons.py b/tests/integration/utils/comparisons.py index a8dd4a1f6c..cae989c8d1 100644 --- a/tests/integration/utils/comparisons.py +++ b/tests/integration/utils/comparisons.py @@ -20,8 +20,8 @@ def allclose( - a: Any, # numpy or cunumeric array-like - b: Any, # numpy or cunumeric array-like + a: Any, # numpy or cupynumeric array-like + b: Any, # numpy or cupynumeric array-like rtol: float = 1e-5, atol: float = 1e-8, equal_nan: bool = False, @@ -50,7 +50,7 @@ def allclose( inds = islice(zip(*np.where(~close)), diff_limit) diffs = [f" index {i}: {a[i]} {b[i]}" for i in inds] N = len(diffs) - print(f"First {N} difference{'s' if N>1 else ''} for allclose:\n") + print(f"First {N} difference{'s' if N > 1 else ''} for allclose:\n") print("\n".join(diffs)) print(f"\nWith diff_limit={diff_limit}\n") diff --git a/tests/integration/utils/contractions.py b/tests/integration/utils/contractions.py index 641020b4f3..9e61135d16 100644 --- a/tests/integration/utils/contractions.py +++ b/tests/integration/utils/contractions.py @@ -17,7 +17,7 @@ from legate.core import LEGATE_MAX_DIM from legate.core.utils import OrderedSet -import cunumeric as num +import cupynumeric as num from .comparisons import allclose from .generators import mk_0to1_array diff --git a/tests/integration/utils/random.py b/tests/integration/utils/random.py index afca73c31c..0bc990b37e 100644 --- a/tests/integration/utils/random.py +++ b/tests/integration/utils/random.py @@ -15,7 +15,7 @@ import numpy as np -import cunumeric as num +import cupynumeric as num class ModuleGenerator: diff --git a/tests/integration/utils/utils.py b/tests/integration/utils/utils.py index ee885b157a..c75198bcb1 100644 --- a/tests/integration/utils/utils.py +++ b/tests/integration/utils/utils.py @@ -15,8 +15,8 @@ import numpy as np -import cunumeric as num -from cunumeric._utils import is_np2 +import cupynumeric as num +from cupynumeric._utils import is_np2 if is_np2: from numpy.exceptions import AxisError # noqa: F401 @@ -50,8 +50,8 @@ def compare_array_and_print_results(a, b, print_msg, check_type=True): assert is_equal, ( f"Failed, {print_msg}\n" f"numpy result: {err_arr[0]}\n" - f"cunumeric_result: {err_arr[1]}\n" - f"cunumeric and numpy shows" + f"cupynumeric_result: {err_arr[1]}\n" + f"cupynumeric and numpy shows" f" different result\n" ) print(f"Passed, {print_msg}") @@ -61,13 +61,13 @@ def compare_array_and_print_results(a, b, print_msg, check_type=True): assert is_equal, ( f"Failed, {print_msg}\n" f"numpy result: {err_arr[0]}, {a.shape}\n" - f"cunumeric_result: {err_arr[1]}, {b.shape}\n" - f"cunumeric and numpy shows" + f"cupynumeric_result: {err_arr[1]}, {b.shape}\n" + f"cupynumeric and numpy shows" f" different result\n" ) print( f"Passed, {print_msg}, np: ({a.shape}, {a.dtype})" - f", cunumeric: ({b.shape}, {b.dtype})" + f", cupynumeric: ({b.shape}, {b.dtype})" ) diff --git a/tests/todo/2d_reduction_complex.py b/tests/todo/2d_reduction_complex.py index 0ed704be41..429b32cc0b 100644 --- a/tests/todo/2d_reduction_complex.py +++ b/tests/todo/2d_reduction_complex.py @@ -15,7 +15,7 @@ import numpy as np -import cunumeric as num +import cupynumeric as num def test(): diff --git a/tests/todo/assign_slice.py b/tests/todo/assign_slice.py index 75fa19af6e..943300ff27 100644 --- a/tests/todo/assign_slice.py +++ b/tests/todo/assign_slice.py @@ -13,7 +13,7 @@ # limitations under the License. # -import cunumeric as num +import cupynumeric as num def test(): diff --git a/tests/todo/complex_test.py b/tests/todo/complex_test.py index 22f1d667cb..1aa82fe9df 100644 --- a/tests/todo/complex_test.py +++ b/tests/todo/complex_test.py @@ -15,7 +15,7 @@ import numpy as np -import cunumeric as num +import cupynumeric as num M = 32 alpha = 4.0 diff --git a/tests/todo/dot.py b/tests/todo/dot.py index e90bef46d2..c189c020f2 100644 --- a/tests/todo/dot.py +++ b/tests/todo/dot.py @@ -15,7 +15,7 @@ import numpy as np -import cunumeric as num +import cupynumeric as num def test(): diff --git a/tests/todo/indirect.py b/tests/todo/indirect.py index 4e24494217..21ed4d4c56 100644 --- a/tests/todo/indirect.py +++ b/tests/todo/indirect.py @@ -15,7 +15,7 @@ import numpy as np -import cunumeric as num +import cupynumeric as num def test(): diff --git a/tests/todo/kmeans_test.py b/tests/todo/kmeans_test.py index f9d0a6c8c5..52ba16fdf9 100644 --- a/tests/todo/kmeans_test.py +++ b/tests/todo/kmeans_test.py @@ -15,7 +15,7 @@ import numpy as np -import cunumeric as num +import cupynumeric as num def test(): diff --git a/tests/todo/lstm_batch.py b/tests/todo/lstm_batch.py index 97b57a1d2b..6e352857b2 100644 --- a/tests/todo/lstm_batch.py +++ b/tests/todo/lstm_batch.py @@ -16,7 +16,7 @@ """ This is a batched LSTM forward and backward pass """ -import cunumeric as np +import cupynumeric as np class LSTM: diff --git a/tests/todo/lstm_simple_backward.py b/tests/todo/lstm_simple_backward.py index 772af1a91f..a8126573b7 100644 --- a/tests/todo/lstm_simple_backward.py +++ b/tests/todo/lstm_simple_backward.py @@ -15,7 +15,7 @@ import numpy as np -import cunumeric as num +import cupynumeric as num def testtion(): diff --git a/tests/unit/cunumeric/__init__.py b/tests/unit/cupynumeric/__init__.py similarity index 100% rename from tests/unit/cunumeric/__init__.py rename to tests/unit/cupynumeric/__init__.py diff --git a/tests/unit/cunumeric/_array/__init__.py b/tests/unit/cupynumeric/_array/__init__.py similarity index 100% rename from tests/unit/cunumeric/_array/__init__.py rename to tests/unit/cupynumeric/_array/__init__.py diff --git a/tests/unit/cunumeric/_array/test_util.py b/tests/unit/cupynumeric/_array/test_util.py similarity index 97% rename from tests/unit/cunumeric/_array/test_util.py rename to tests/unit/cupynumeric/_array/test_util.py index 1f5c26e79d..f419d32075 100644 --- a/tests/unit/cunumeric/_array/test_util.py +++ b/tests/unit/cupynumeric/_array/test_util.py @@ -17,7 +17,7 @@ from mock import MagicMock from pytest_mock import MockerFixture -import cunumeric._array.util as m # module under test +import cupynumeric._array.util as m # module under test from ...util import powerset @@ -44,7 +44,9 @@ def _where_explicit(a, b, where): @pytest.fixture(autouse=True) def mock_convert(mocker: MockerFixture) -> MagicMock: - return mocker.patch("cunumeric._array.util.convert_to_cunumeric_ndarray") + return mocker.patch( + "cupynumeric._array.util.convert_to_cupynumeric_ndarray" + ) class Test_add_boilerplate_bad: diff --git a/tests/unit/cunumeric/_sphinxext/__init__.py b/tests/unit/cupynumeric/_sphinxext/__init__.py similarity index 100% rename from tests/unit/cunumeric/_sphinxext/__init__.py rename to tests/unit/cupynumeric/_sphinxext/__init__.py diff --git a/tests/unit/cunumeric/_sphinxext/test__comparison_util.py b/tests/unit/cupynumeric/_sphinxext/test__comparison_util.py similarity index 94% rename from tests/unit/cunumeric/_sphinxext/test__comparison_util.py rename to tests/unit/cupynumeric/_sphinxext/test__comparison_util.py index c36e69ffbf..bf9746d505 100644 --- a/tests/unit/cunumeric/_sphinxext/test__comparison_util.py +++ b/tests/unit/cupynumeric/_sphinxext/test__comparison_util.py @@ -16,8 +16,8 @@ import numpy as np import pytest -import cunumeric as num -import cunumeric._sphinxext._comparison_util as m # module under test +import cupynumeric as num +import cupynumeric._sphinxext._comparison_util as m # module under test def test_get_namespaces_None(): diff --git a/tests/unit/cunumeric/_utils/__init__.py b/tests/unit/cupynumeric/_utils/__init__.py similarity index 100% rename from tests/unit/cunumeric/_utils/__init__.py rename to tests/unit/cupynumeric/_utils/__init__.py diff --git a/tests/unit/cunumeric/_utils/test_array.py b/tests/unit/cupynumeric/_utils/test_array.py similarity index 98% rename from tests/unit/cunumeric/_utils/test_array.py rename to tests/unit/cupynumeric/_utils/test_array.py index 34e124c479..01e490eedb 100644 --- a/tests/unit/cunumeric/_utils/test_array.py +++ b/tests/unit/cupynumeric/_utils/test_array.py @@ -16,7 +16,7 @@ import numpy as np import pytest -import cunumeric._utils.array as m # module under test +import cupynumeric._utils.array as m # module under test EXPECTED_SUPPORTED_DTYPES = set( [ diff --git a/tests/unit/cunumeric/_utils/test_coverage.py b/tests/unit/cupynumeric/_utils/test_coverage.py similarity index 86% rename from tests/unit/cunumeric/_utils/test_coverage.py rename to tests/unit/cupynumeric/_utils/test_coverage.py index 5c6bf1aeee..c92e44c4ce 100644 --- a/tests/unit/cunumeric/_utils/test_coverage.py +++ b/tests/unit/cupynumeric/_utils/test_coverage.py @@ -19,14 +19,14 @@ import pytest from mock import MagicMock, patch -import cunumeric -import cunumeric._utils.coverage as m # module under test -from cunumeric.settings import settings +import cupynumeric +import cupynumeric._utils.coverage as m # module under test +from cupynumeric.settings import settings def test_FALLBACK_WARNING() -> None: assert m.FALLBACK_WARNING.format(what="foo") == ( - "cuNumeric has not implemented foo " + "cuPyNumeric has not implemented foo " + "and is falling back to canonical NumPy. " + "You may notice significantly decreased performance " + "for this function call." @@ -104,7 +104,7 @@ def _test_func(a: int, b: int) -> int: return a + b -class _Test_ufunc(cunumeric._ufunc.ufunc): +class _Test_ufunc(cupynumeric._ufunc.ufunc): """docstring""" def __init__(self): @@ -118,7 +118,7 @@ def __call__(self, a: int, b: int) -> int: class Test_implemented: - @patch("cunumeric.runtime.record_api_call") + @patch("cupynumeric.runtime.record_api_call") def test_reporting_True_func( self, mock_record_api_call: MagicMock ) -> None: @@ -140,7 +140,7 @@ def test_reporting_True_func( ) assert int(lineno) - @patch("cunumeric.runtime.record_api_call") + @patch("cupynumeric.runtime.record_api_call") def test_reporting_False_func( self, mock_record_api_call: MagicMock ) -> None: @@ -157,7 +157,7 @@ def test_reporting_False_func( mock_record_api_call.assert_not_called() - @patch("cunumeric.runtime.record_api_call") + @patch("cupynumeric.runtime.record_api_call") def test_reporting_True_ufunc( self, mock_record_api_call: MagicMock ) -> None: @@ -181,7 +181,7 @@ def test_reporting_True_ufunc( ) assert int(lineno) - @patch("cunumeric.runtime.record_api_call") + @patch("cupynumeric.runtime.record_api_call") def test_reporting_False_ufunc( self, mock_record_api_call: MagicMock ) -> None: @@ -202,7 +202,7 @@ def test_reporting_False_ufunc( class Test_unimplemented: - @patch("cunumeric.runtime.record_api_call") + @patch("cupynumeric.runtime.record_api_call") def test_reporting_True_func( self, mock_record_api_call: MagicMock ) -> None: @@ -225,7 +225,7 @@ def test_reporting_True_func( ) assert int(lineno) - @patch("cunumeric.runtime.record_api_call") + @patch("cupynumeric.runtime.record_api_call") def test_reporting_False_func( self, mock_record_api_call: MagicMock ) -> None: @@ -249,7 +249,7 @@ def test_reporting_False_func( mock_record_api_call.assert_not_called() - @patch("cunumeric.runtime.record_api_call") + @patch("cupynumeric.runtime.record_api_call") def test_reporting_True_ufunc( self, mock_record_api_call: MagicMock ) -> None: @@ -270,7 +270,7 @@ def test_reporting_True_ufunc( ) assert int(lineno) - @patch("cunumeric.runtime.record_api_call") + @patch("cupynumeric.runtime.record_api_call") def test_reporting_False_ufunc( self, mock_record_api_call: MagicMock ) -> None: @@ -347,12 +347,12 @@ def test_report_coverage_True(self) -> None: assert _Dest.attr2 == 30 assert _Dest.function1.__wrapped__ is _OriginMod.function1 - assert not _Dest.function1._cunumeric.implemented + assert not _Dest.function1._cupynumeric.implemented assert _Dest.function2.__wrapped__ - assert _Dest.function2._cunumeric.implemented + assert _Dest.function2._cupynumeric.implemented - assert not hasattr(_Dest.extra, "_cunumeric") + assert not hasattr(_Dest.extra, "_cupynumeric") settings.report_coverage.unset_value() @@ -373,12 +373,12 @@ def test_report_coverage_False(self) -> None: assert _Dest.attr2 == 30 assert _Dest.function1.__wrapped__ is _OriginMod.function1 - assert not _Dest.function1._cunumeric.implemented + assert not _Dest.function1._cupynumeric.implemented assert _Dest.function2.__wrapped__ - assert _Dest.function2._cunumeric.implemented + assert _Dest.function2._cupynumeric.implemented - assert not hasattr(_Dest.extra, "_cunumeric") + assert not hasattr(_Dest.extra, "_cupynumeric") settings.report_coverage.unset_value() @@ -428,12 +428,12 @@ def test_report_coverage_True(self) -> None: assert _Test_ndarray.attr2 == 30 assert _Test_ndarray.foo.__wrapped__ is _Orig_ndarray.foo - assert not _Test_ndarray.foo._cunumeric.implemented + assert not _Test_ndarray.foo._cupynumeric.implemented assert _Test_ndarray.bar.__wrapped__ - assert _Test_ndarray.bar._cunumeric.implemented + assert _Test_ndarray.bar._cupynumeric.implemented - assert not hasattr(_Test_ndarray.extra, "_cunumeric") + assert not hasattr(_Test_ndarray.extra, "_cupynumeric") settings.report_coverage.unset_value() @@ -447,12 +447,12 @@ def test_report_coverage_False(self) -> None: assert _Test_ndarray.attr2 == 30 assert _Test_ndarray.foo.__wrapped__ is _Orig_ndarray.foo - assert not _Test_ndarray.foo._cunumeric.implemented + assert not _Test_ndarray.foo._cupynumeric.implemented assert _Test_ndarray.bar.__wrapped__ - assert _Test_ndarray.bar._cunumeric.implemented + assert _Test_ndarray.bar._cupynumeric.implemented - assert not hasattr(_Test_ndarray.extra, "_cunumeric") + assert not hasattr(_Test_ndarray.extra, "_cupynumeric") settings.report_coverage.unset_value() @@ -465,36 +465,36 @@ def test_fallback(self): def test_ufunc_methods_binary() -> None: - import cunumeric as np + import cupynumeric as np # reduce is implemented assert np.add.reduce.__wrapped__ - assert np.add.reduce._cunumeric.implemented + assert np.add.reduce._cupynumeric.implemented # the rest are not assert np.add.reduceat.__wrapped__ - assert not np.add.reduceat._cunumeric.implemented + assert not np.add.reduceat._cupynumeric.implemented assert np.add.outer.__wrapped__ - assert not np.add.outer._cunumeric.implemented + assert not np.add.outer._cupynumeric.implemented assert np.add.at.__wrapped__ - assert not np.add.at._cunumeric.implemented + assert not np.add.at._cupynumeric.implemented assert np.add.accumulate.__wrapped__ - assert not np.add.accumulate._cunumeric.implemented + assert not np.add.accumulate._cupynumeric.implemented def test_ufunc_methods_unary() -> None: - import cunumeric as np + import cupynumeric as np assert np.negative.reduce.__wrapped__ - assert not np.negative.reduce._cunumeric.implemented + assert not np.negative.reduce._cupynumeric.implemented assert np.negative.reduceat.__wrapped__ - assert not np.negative.reduceat._cunumeric.implemented + assert not np.negative.reduceat._cupynumeric.implemented assert np.negative.outer.__wrapped__ - assert not np.negative.outer._cunumeric.implemented + assert not np.negative.outer._cupynumeric.implemented assert np.negative.at.__wrapped__ - assert not np.negative.at._cunumeric.implemented + assert not np.negative.at._cupynumeric.implemented assert np.negative.accumulate.__wrapped__ - assert not np.negative.accumulate._cunumeric.implemented + assert not np.negative.accumulate._cupynumeric.implemented if __name__ == "__main__": diff --git a/tests/unit/cunumeric/_utils/test_linalg.py b/tests/unit/cupynumeric/_utils/test_linalg.py similarity index 99% rename from tests/unit/cunumeric/_utils/test_linalg.py rename to tests/unit/cupynumeric/_utils/test_linalg.py index f863f55afb..51ee3eea4f 100644 --- a/tests/unit/cunumeric/_utils/test_linalg.py +++ b/tests/unit/cupynumeric/_utils/test_linalg.py @@ -16,7 +16,7 @@ import numpy as np import pytest -import cunumeric._utils.linalg as m # module under test +import cupynumeric._utils.linalg as m # module under test def _dot_modes_oracle(a_ndim: int, b_ndim: int) -> bool: diff --git a/tests/unit/cunumeric/random/__init__.py b/tests/unit/cupynumeric/random/__init__.py similarity index 100% rename from tests/unit/cunumeric/random/__init__.py rename to tests/unit/cupynumeric/random/__init__.py diff --git a/tests/unit/cunumeric/random/test_bitgenerator.py b/tests/unit/cupynumeric/random/test_bitgenerator.py similarity index 96% rename from tests/unit/cunumeric/random/test_bitgenerator.py rename to tests/unit/cupynumeric/random/test_bitgenerator.py index d9b83ab1a6..7d38b3279a 100644 --- a/tests/unit/cunumeric/random/test_bitgenerator.py +++ b/tests/unit/cupynumeric/random/test_bitgenerator.py @@ -16,8 +16,8 @@ import pytest from mock import patch -import cunumeric.random._bitgenerator as m # module under test -from cunumeric.config import BitGeneratorType +import cupynumeric.random._bitgenerator as m # module under test +from cupynumeric.config import BitGeneratorType class TestXORWOW: diff --git a/tests/unit/cunumeric/test_config.py b/tests/unit/cupynumeric/test_config.py similarity index 84% rename from tests/unit/cunumeric/test_config.py rename to tests/unit/cupynumeric/test_config.py index e002040304..fa6922998b 100644 --- a/tests/unit/cunumeric/test_config.py +++ b/tests/unit/cupynumeric/test_config.py @@ -15,45 +15,45 @@ import pytest -import cunumeric.config as m # module under test +import cupynumeric.config as m # module under test -class TestCuNumericLib: +class TestCuPyNumericLib: def test___init__(self) -> None: - lib = m.CuNumericLib("foo") + lib = m.CuPyNumericLib("foo") assert lib.name == "foo" def test_get_shared_library(self) -> None: - lib = m.CuNumericLib("foo") + lib = m.CuPyNumericLib("foo") result = lib.get_shared_library() assert isinstance(result, str) - from cunumeric.install_info import libpath + from cupynumeric.install_info import libpath assert result.startswith(libpath) - assert "libcunumeric" in result + assert "libcupynumeric" in result assert result.endswith(lib.get_library_extension()) def test_get_c_header(self) -> None: - lib = m.CuNumericLib("foo") + lib = m.CuPyNumericLib("foo") - from cunumeric.install_info import header + from cupynumeric.install_info import header assert lib.get_c_header() == header -def test_CUNUMERIC_LIB_NAME() -> None: - assert m.CUNUMERIC_LIB_NAME == "cunumeric" +def test_CUPYNUMERIC_LIB_NAME() -> None: + assert m.CUPYNUMERIC_LIB_NAME == "cupynumeric" -def test_cunumeric_lib() -> None: - assert isinstance(m.cunumeric_lib, m.CuNumericLib) +def test_cupynumeric_lib() -> None: + assert isinstance(m.cupynumeric_lib, m.CuPyNumericLib) -def test_CuNumericOpCode() -> None: - assert set(m.CuNumericOpCode.__members__) == { +def test_CuPyNumericOpCode() -> None: + assert set(m.CuPyNumericOpCode.__members__) == { "ADVANCED_INDEXING", "ARANGE", "ARGWHERE", diff --git a/tests/unit/cunumeric/test_nptest.py b/tests/unit/cupynumeric/test_nptest.py similarity index 81% rename from tests/unit/cunumeric/test_nptest.py rename to tests/unit/cupynumeric/test_nptest.py index d2ecf2c5a9..4ee7a16a02 100644 --- a/tests/unit/cunumeric/test_nptest.py +++ b/tests/unit/cupynumeric/test_nptest.py @@ -15,13 +15,13 @@ import pytest -from cunumeric import test as nptest +from cupynumeric import test as nptest MSG = ( - "cuNumeric cannot execute numpy.test() due to reliance " + "cuPyNumeric cannot execute numpy.test() due to reliance " "on Numpy internals. For information about running the " - "cuNumeric test suite, see: " - "https://docs.nvidia.com/cunumeric/latest/developer/index.html" + "cuPyNumeric test suite, see: " + "https://docs.nvidia.com/cupynumeric/latest/developer/index.html" ) diff --git a/tests/unit/cunumeric/test_patch.py b/tests/unit/cupynumeric/test_patch.py similarity index 84% rename from tests/unit/cunumeric/test_patch.py rename to tests/unit/cupynumeric/test_patch.py index 41a5107e80..4bb61406b9 100644 --- a/tests/unit/cunumeric/test_patch.py +++ b/tests/unit/cupynumeric/test_patch.py @@ -25,14 +25,14 @@ @pytest.mark.skip def test_no_patch() -> None: - cmd = "import sys; import cunumeric; import numpy; sys.exit(numpy is cunumeric)" # noqa E501 + cmd = "import sys; import cupynumeric; import numpy; sys.exit(numpy is cupynumeric)" # noqa E501 proc = run([legate, "-c", cmd]) assert proc.returncode == 0, "numpy is unexpectedly patched" @pytest.mark.skip def test_patch() -> None: - cmd = "import sys; import cunumeric.patch; import numpy; sys.exit(numpy is cunumeric)" # noqa E501 + cmd = "import sys; import cupynumeric.patch; import numpy; sys.exit(numpy is cupynumeric)" # noqa E501 proc = run([legate, "-c", cmd]) assert proc.returncode == 1, "numpy failed to patch" diff --git a/tests/unit/cunumeric/test_settings.py b/tests/unit/cupynumeric/test_settings.py similarity index 94% rename from tests/unit/cunumeric/test_settings.py rename to tests/unit/cupynumeric/test_settings.py index 66cee2eb72..00cfe9cf5d 100644 --- a/tests/unit/cunumeric/test_settings.py +++ b/tests/unit/cupynumeric/test_settings.py @@ -20,7 +20,7 @@ from legate.util.fs import read_c_define from legate.util.settings import EnvOnlySetting, PrioritizedSetting -import cunumeric.settings as m +import cupynumeric.settings as m _expected_settings = ( "preload_cudalibs", @@ -61,7 +61,7 @@ def test_standard_settings(self) -> None: @pytest.mark.parametrize("name", _expected_settings) def test_prefix(self, name: str) -> None: ps = getattr(m.settings, name) - assert ps.env_var.startswith("CUNUMERIC_") + assert ps.env_var.startswith("CUPYNUMERIC_") def test_types(self) -> None: assert m.settings.preload_cudalibs.convert_type == 'bool ("0" or "1")' @@ -98,7 +98,7 @@ def test_numpy_compat(self) -> None: @pytest.mark.parametrize("name", _settings_with_test_defaults) def test_default(self, name: str) -> None: setting = getattr(m.settings, name) - define = setting.env_var.removeprefix("CUNUMERIC_") + "_DEFAULT" + define = setting.env_var.removeprefix("CUPYNUMERIC_") + "_DEFAULT" expected = setting._convert(read_c_define(ENV_HEADER, define)) assert setting.default == expected @@ -106,7 +106,7 @@ def test_default(self, name: str) -> None: @pytest.mark.parametrize("name", _settings_with_test_defaults) def test_test_default(self, name: str) -> None: setting = getattr(m.settings, name) - define = setting.env_var.removeprefix("CUNUMERIC_") + "_TEST" + define = setting.env_var.removeprefix("CUPYNUMERIC_") + "_TEST" expected = setting._convert(read_c_define(ENV_HEADER, define)) assert setting.test_default == expected From f44aa8c08cb398d41f0b41d48a2d50387402d98b Mon Sep 17 00:00:00 2001 From: Marcin Zalewski Date: Wed, 13 Nov 2024 22:28:08 -0800 Subject: [PATCH 357/462] Complete cunumeric->cupynumeric renaming (#493) * Renaming * Update .github/ISSUE_TEMPLATE/bug_report.yml --------- Co-authored-by: Manolis Papadakis --- .gitattributes | 2 +- .github/ISSUE_TEMPLATE/bug_report.yml | 4 ++-- .github/workflows/gh-build-and-test.yml | 8 ++++---- .pre-commit-config.yaml | 2 +- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/.gitattributes b/.gitattributes index 8ae3c80128..1215d42fca 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1 +1 @@ -cunumeric/_version.py export-subst +cunpyumeric/_version.py export-subst diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml index 39f252254f..74fb1d45b1 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.yml +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -8,7 +8,7 @@ body: value: "# Bug report" - type: markdown attributes: - value: Thank you for reporting a bug and helping us improve Cunumeric! + value: Thank you for reporting a bug and helping us improve cuPyNumeric! - type: markdown attributes: value: > @@ -29,7 +29,7 @@ body: Platform : Linux-6.8.0-40-generic-x86_64-with-glibc2.35 Legion : (failed to detect) Legate : 24.05.00+255.g2656afbd - Cunumeric : 24.05.00+132.gc4741d57 + cuPynumeric : 24.05.00+132.gc4741d57 Numpy : 1.26.4 Scipy : 1.13.1 Numba : (failed to detect) diff --git a/.github/workflows/gh-build-and-test.yml b/.github/workflows/gh-build-and-test.yml index 7f0b3d6ba9..39401e9d11 100644 --- a/.github/workflows/gh-build-and-test.yml +++ b/.github/workflows/gh-build-and-test.yml @@ -96,10 +96,10 @@ jobs: legate-gh-ci-tag: "v1.19" name: Upload package to Server network: "ucx" - pkgSubString: "cunumeric-" + pkgSubString: "cupynumeric-" platform: ${{ inputs.platform }} python-version: ${{ inputs.python-version }} - repos-Root: "cunumeric" + repos-Root: "cupynumeric" target-device: ${{ inputs.target-device }} upload-action: "upload-package" upload-enabled: ${{ inputs.upload-enabled }} @@ -206,10 +206,10 @@ jobs: legate-gh-ci-tag: "v1.19" name: UpdateTestStatus network: "ucx" - pkgSubString: "cunumeric-" + pkgSubString: "cupynumeric-" platform: ${{ inputs.platform }} python-version: ${{ inputs.python-version }} - repos-Root: "cunumeric" + repos-Root: "cupynumeric" target-device: ${{ inputs.target-device }} upload-action: "update-test-status" upload-enabled: true diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 47514166f3..8c98204508 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -40,7 +40,7 @@ repos: 'types_or': [c++, c, cuda] require_serial: false stages: [pre-commit] - exclude: '^src/cupynumeric/cunumeric_c\.h$' + exclude: '^src/cupynumeric/cupynumeric_c\.h$' ci: skip: [mypy] From 251c2c5028a0945b11a255822a51ad722dd550ce Mon Sep 17 00:00:00 2001 From: Manolis Papadakis Date: Thu, 14 Nov 2024 12:03:13 -0800 Subject: [PATCH 358/462] Update project references in install.py (#496) --- install.py | 44 +++++++++++++++++++++++--------------------- 1 file changed, 23 insertions(+), 21 deletions(-) diff --git a/install.py b/install.py index ce4912edbe..ae0639a7dd 100755 --- a/install.py +++ b/install.py @@ -108,13 +108,15 @@ def find_cmake_val(pattern, filepath): def was_previously_built_with_different_build_isolation( - isolated, cunumeric_build_dir + isolated, cupynumeric_build_dir ): if ( - cunumeric_build_dir is not None - and os.path.exists(cunumeric_build_dir) + cupynumeric_build_dir is not None + and os.path.exists(cupynumeric_build_dir) and os.path.exists( - cmake_cache := os.path.join(cunumeric_build_dir, "CMakeCache.txt") + cmake_cache := os.path.join( + cupynumeric_build_dir, "CMakeCache.txt" + ) ) ): try: @@ -167,7 +169,7 @@ def find_legate_cmake_dir() -> Path: return path -def install_cunumeric( +def install_cupynumeric( arch, build_isolation, with_tests, @@ -251,7 +253,7 @@ def install_cunumeric( dirname = os.path.dirname realpath = os.path.realpath - cunumeric_dir = dirname(realpath(__file__)) + cupynumeric_dir = dirname(realpath(__file__)) if thread_count is None: thread_count = multiprocessing.cpu_count() @@ -260,7 +262,7 @@ def validate_path(path): if path is None or (path := str(path)) == "": return None if not os.path.isabs(path): - path = join(cunumeric_dir, path) + path = join(cupynumeric_dir, path) if not exists(path := realpath(path)): print(f"Error: path does not exist: {path}") sys.exit(1) @@ -288,20 +290,20 @@ def validate_path(path): print("cutensor_dir: ", cutensor_dir) print("openblas_dir: ", openblas_dir) - skbuild_dir = join(cunumeric_dir, "_skbuild") - cunumeric_build_dir = scikit_build_cmake_build_dir(skbuild_dir) + skbuild_dir = join(cupynumeric_dir, "_skbuild") + cupynumeric_build_dir = scikit_build_cmake_build_dir(skbuild_dir) if was_previously_built_with_different_build_isolation( - build_isolation and not editable, cunumeric_build_dir + build_isolation and not editable, cupynumeric_build_dir ): print("Performing a clean build to accommodate build isolation.") clean_first = True cmd_env = dict(os.environ.items()) - # Explicitly uninstall cunumeric if doing a clean/isolated build. + # Explicitly uninstall cupynumeric if doing a clean/isolated build. # - # A prior installation may have built and installed cunumeric C++ + # A prior installation may have built and installed cupynumeric C++ # dependencies (like BLAS or tblis). # # CMake will find and use them for the current build, which would normally @@ -313,19 +315,19 @@ def validate_path(path): # these dependencies, triggering CMake to build and install them again. if clean_first or (build_isolation and not editable): execute_command( - [sys.executable, "-m", "pip", "uninstall", "-y", "cunumeric"], + [sys.executable, "-m", "pip", "uninstall", "-y", "cupynumeric"], verbose, ignore_errors=True, - cwd=cunumeric_dir, + cwd=cupynumeric_dir, env=cmd_env, ) if clean_first: shutil.rmtree(skbuild_dir, ignore_errors=True) - shutil.rmtree(join(cunumeric_dir, "dist"), ignore_errors=True) - shutil.rmtree(join(cunumeric_dir, "build"), ignore_errors=True) + shutil.rmtree(join(cupynumeric_dir, "dist"), ignore_errors=True) + shutil.rmtree(join(cupynumeric_dir, "build"), ignore_errors=True) shutil.rmtree( - join(cunumeric_dir, "cunumeric.egg-info"), + join(cupynumeric_dir, "cupynumeric.egg-info"), ignore_errors=True, ) @@ -389,7 +391,7 @@ def validate_path(path): -DLegion_USE_LLVM={("ON" if llvm else "OFF")} -DLegion_NETWORKS={";".join(networks)} -DLegion_USE_HDF5={("ON" if hdf else "OFF")} --Dcunumeric_BUILD_TESTS={("ON" if with_tests else "OFF")} +-Dcupynumeric_BUILD_TESTS={("ON" if with_tests else "OFF")} """.splitlines() if march: @@ -412,7 +414,7 @@ def validate_path(path): cmake_flags += ["-Dcutensor_DIR=%s" % cutensor_dir] # A custom path to cuRAND is ignored when CUDA support is available if cuda and curand_dir is not None: - cmake_flags += ["-Dcunumeric_cuRAND_INCLUDE_DIR=%s" % curand_dir] + cmake_flags += ["-Dcupynumeric_cuRAND_INCLUDE_DIR=%s" % curand_dir] cmake_flags += ["-Dlegate_ROOT=%s" % str(legate_dir)] cmake_flags += ["-DCMAKE_BUILD_PARALLEL_LEVEL=%s" % thread_count] @@ -433,7 +435,7 @@ def validate_path(path): } ) - execute_command(pip_install_cmd, verbose, cwd=cunumeric_dir, env=cmd_env) + execute_command(pip_install_cmd, verbose, cwd=cupynumeric_dir, env=cmd_env) def driver(): @@ -701,7 +703,7 @@ def driver(): ) args, unknown = parser.parse_known_args() - install_cunumeric(unknown=unknown, **vars(args)) + install_cupynumeric(unknown=unknown, **vars(args)) if __name__ == "__main__": From e23e4d8d13d94fa0fd243f85086b16b56ad6835a Mon Sep 17 00:00:00 2001 From: XiaLuNV <110973296+XiaLuNV@users.noreply.github.com> Date: Fri, 15 Nov 2024 09:36:32 +0800 Subject: [PATCH 359/462] enhance clip (#485) * enhance clip * Update test_clip.py --- tests/integration/test_clip.py | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/tests/integration/test_clip.py b/tests/integration/test_clip.py index 85141c9e45..bbbf5ac59b 100644 --- a/tests/integration/test_clip.py +++ b/tests/integration/test_clip.py @@ -74,6 +74,24 @@ def test_empty_array(): assert np.array_equal(res_np, res_num) +def test_bool() -> None: + np.clip(True, a_min=1, a_max=1) + # Numpy returns 1 + # See https://github.com/nv-legate/cunumeric.internal/issues/491 + msg = r"Expected bytes or NumPy ndarray, but got " + with pytest.raises(ValueError, match=msg): + num.clip(True, a_min=1, a_max=1) + + +def test_bool_None() -> None: + msg = r"One of max or min must be given" + with pytest.raises(ValueError, match=msg): + np.clip(True, a_min=None, a_max=None) + # See https://github.com/nv-legate/cunumeric.internal/issues/492 + num.clip(True, a_min=None, a_max=None) + # cuNumeric returns True, it returns False if array is False + + @pytest.mark.xfail def test_amin_amax(): array = np.arange(0, 10) From a8dd4a7942b18463d3fe2a26bf38d9213b885df1 Mon Sep 17 00:00:00 2001 From: XiaLuNV <110973296+XiaLuNV@users.noreply.github.com> Date: Fri, 15 Nov 2024 15:47:54 +0800 Subject: [PATCH 360/462] enhance for random (#458) * enhance for random --------- Co-authored-by: Jacob Faibussowitsch --- tests/integration/test_randint.py | 41 ++++- tests/integration/test_random.py | 271 +++++++++++++++++++++--------- 2 files changed, 227 insertions(+), 85 deletions(-) diff --git a/tests/integration/test_randint.py b/tests/integration/test_randint.py index 1da1425fd3..616f0f7add 100644 --- a/tests/integration/test_randint.py +++ b/tests/integration/test_randint.py @@ -13,17 +13,46 @@ # limitations under the License. # +import numpy as np import pytest import cupynumeric as num -def test_1d(): - num.random.randint(8000, size=8000) - - -def test_2d(): - num.random.randint(8000, size=(8000, 2)) +class TestRandint: + @pytest.mark.parametrize("size", (1, 8000, (8000, 2))) + def test_randint(self, size: int | tuple[int, ...]) -> None: + L1 = num.random.randint(8000, size=size) + L2 = np.random.randint(8000, size=size) + assert L1.ndim == L2.ndim + assert L1.dtype.kind == "i" + + def test_randint_0(self): + L1 = num.random.randint(8000, size=0) + L2 = np.random.randint(8000, size=0) + assert np.array_equal(L1, L2) + + def test_low(self): + L1 = num.random.randint(500) + L2 = np.random.randint(500) + assert L1 < 500 + assert L2 < 500 + + def test_high(self): + L1 = num.random.randint(500, 800) + L2 = np.random.randint(500, 800) + assert 500 < L1 < 800 + assert 500 < L2 < 800 + + @pytest.mark.xfail( + reason="https://github.com/nv-legate/cunumeric.internal/issues/199" + ) + def test_same_seed(self) -> None: + num.random.seed(13) + L1 = num.random.randint(100) + num.random.seed(13) + L2 = num.random.randint(100) + assert np.array_equal(L1, L2) if __name__ == "__main__": diff --git a/tests/integration/test_random.py b/tests/integration/test_random.py index b317ef8e2a..488cddc74c 100644 --- a/tests/integration/test_random.py +++ b/tests/integration/test_random.py @@ -11,82 +11,203 @@ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. +import re + import numpy as np import pytest import cupynumeric as num -@pytest.mark.xfail( - reason="https://github.com/nv-legate/cupynumeric.internal/issues/199" -) -def test_basic_num() -> None: - num.random.seed(10) - L1 = num.random.randn(3, 3) - num.random.seed(10) - L2 = num.random.randn(3, 3) - assert np.array_equal(L1, L2) - - -@pytest.mark.xfail( - reason="numpy failures in random.mtrand.RandomState.standard_normal" -) -def test_basic_np() -> None: - np.random.seed(10) - L1 = np.random.randn(3, 3) - np.random.seed(10) - L2 = np.random.randn(3, 3) - assert np.array_equal(L1, L2) - - np.random.seed(10) - L1 = np.random.randn(3, 3) - L2 = np.random.randn(3, 3) - assert not np.array_equal(L1, L2) - - -@pytest.mark.xfail( - reason="https://github.com/nv-legate/cupynumeric.internal/issues/199" -) -def test_none_num() -> None: - num.random.seed() - L1 = num.random.randn(3, 3) - num.random.seed() - L2 = num.random.randn(3, 3) - assert np.array_equal(L1, L2) - - num.random.seed() - L1 = num.random.randn(3, 3) - L2 = num.random.randn(3, 3) - assert not np.array_equal(L1, L2) - - -@pytest.mark.xfail( - reason="numpy failures in random.mtrand.RandomState.standard_normal" -) -def test_none_np() -> None: - np.random.seed() - L1 = np.random.randn(3, 3) - np.random.seed() - L2 = np.random.randn(3, 3) - assert not np.array_equal(L1, L2) - - np.random.seed() - L1 = np.random.randn(3, 3) - L2 = np.random.randn(3, 3) - assert not np.array_equal(L1, L2) - - -@pytest.mark.xfail( - reason="numpy failures in random.mtrand.RandomState.standard_normal" -) -def test_basic_num_np() -> None: - np.random.seed(10) - L1 = np.random.randn(3, 3) - num.random.seed(10) - L2 = num.random.randn(3, 3) - assert not np.array_equal(L1, L2) - - +class TestRand: + def test_rand_null(self) -> None: + L1 = num.random.rand() + assert L1.dtype.kind == "f" + assert L1.ndim == 0 + + @pytest.mark.xfail( + reason="numpy failures in random.mtrand.RandomState.standard_normal" + ) + @pytest.mark.parametrize("size", (0, 1, 3)) + def test_rand(self, size: int) -> None: + L1 = num.random.rand(size) + L2 = np.random.rand(size) + assert L1.ndim == L2.ndim == 1 + + @pytest.mark.xfail( + reason="numpy failures in random.mtrand.RandomState.standard_normal" + ) + def test_rand_2d(self) -> None: + L1 = num.random.rand(3, 3) + L2 = np.random.rand(3, 3) + assert L1.ndim == L2.ndim == 2 + + @pytest.mark.xfail( + reason="numpy failures in random.mtrand.RandomState.standard_normal" + ) + def test_float(self) -> None: + msg = r"expected a sequence of integers or a single integer" + with pytest.raises(TypeError, match=msg): + num.random.rand(1.5) + msg = r"'float' object cannot be interpreted as an integer" + with pytest.raises(TypeError, match=msg): + np.random.rand(1.5) + + @pytest.mark.xfail( + reason="numpy failures in random.mtrand.RandomState.standard_normal" + ) + def test_negative_value(self) -> None: + msg = r"Extent must be a positive number" + with pytest.raises(ValueError, match=msg): + num.random.rand(-2, -2) + msg = r"negative dimensions are not allowed" + with pytest.raises(ValueError, match=msg): + np.random.rand(-2, -2) + + @pytest.mark.xfail( + reason="https://github.com/nv-legate/cunumeric.internal/issues/199" + ) + def test_same_seed(self) -> None: + num.random.seed(10) + L1 = num.random.rand(3, 3) + num.random.seed(10) + L2 = num.random.rand(3, 3) + assert np.array_equal(L1, L2) + + +class TestRandn: + def test_randn_null(self) -> None: + L1 = num.random.randn() + assert L1.dtype.kind == "f" + assert L1.ndim == 0 + + @pytest.mark.xfail( + reason="numpy failures in random.mtrand.RandomState.standard_normal" + ) + @pytest.mark.parametrize("size", (0, 1, 3)) + def test_randn(self, size: int) -> None: + L1 = num.random.randn(size) + L2 = np.random.randn(size) + assert L1.ndim == L2.ndim == 1 + + @pytest.mark.xfail( + reason="numpy failures in random.mtrand.RandomState.standard_normal" + ) + def test_2d(self) -> None: + L1 = num.random.randn(3, 3) + L2 = np.random.randn(3, 3) + assert L1.ndim == L2.ndim == 2 + + @pytest.mark.xfail( + reason="numpy failures in random.mtrand.RandomState.standard_normal" + ) + def test_float(self) -> None: + msg = r"expected a sequence of integers or a single integer" + with pytest.raises(TypeError, match=msg): + num.random.randn(1.5) + msg = r"'float' object cannot be interpreted as an integer" + with pytest.raises(TypeError, match=msg): + np.random.randn(1.5) + + def test_negative_value(self) -> None: + with pytest.raises(ValueError): + num.random.randn(-2, -2) + msg = r"negative dimensions are not allowed" + with pytest.raises(ValueError, match=msg): + np.random.randn(-2, -2) + + @pytest.mark.xfail( + reason="https://github.com/nv-legate/cunumeric.internal/issues/199" + ) + def test_same_seed(self) -> None: + num.random.seed(10) + L1 = num.random.randn(3, 3) + num.random.seed(10) + L2 = num.random.randn(3, 3) + assert np.array_equal(L1, L2) + + +class TestRandom: + def test_random_null(self) -> None: + L1 = num.random.random() + assert L1.dtype.kind == "f" + assert L1.ndim == 1 + + @pytest.mark.xfail( + reason="numpy failures in random.mtrand.RandomState.standard_normal" + ) + @pytest.mark.parametrize("size", (0, 1, 3)) + def test_random(self, size: int) -> None: + L1 = num.random.random(size) + L2 = np.random.random(size) + assert L1.ndim == L2.ndim == 1 + + def test_float(self) -> None: + msg = r"expected a sequence of integers or a single integer" + with pytest.raises(TypeError, match=msg): + num.random.random(1.5) + msg = r"expected a sequence of integers or a single integer, got '1.5'" + with pytest.raises(TypeError, match=msg): + np.random.random(1.5) + + def test_negative_value(self) -> None: + with pytest.raises(ValueError): + num.random.random(-2) + msg = r"negative dimensions are not allowed" + with pytest.raises(ValueError, match=msg): + np.random.random(-2) + + @pytest.mark.xfail( + reason="https://github.com/nv-legate/cunumeric.internal/issues/199" + ) + def test_same_seed(self) -> None: + num.random.seed(10) + L1 = num.random.random(3) + num.random.seed(10) + L2 = num.random.random(3) + assert np.array_equal(L1, L2) + + +class TestRandomSeed: + @pytest.mark.xfail( + reason="numpy failures in random.mtrand.RandomState.standard_normal" + ) + def test_none(self) -> None: + num.random.seed() + L1 = num.random.randn(3, 3) + np.random.seed() + L2 = np.random.randn(3, 3) + assert L1.ndim == L2.ndim + + @pytest.mark.xfail( + reason="numpy failures in random.mtrand.RandomState.standard_normal" + ) + @pytest.mark.parametrize("seed", (None, 1, 100, 20000)) + def test_seed(self, seed: int | None) -> None: + num.random.seed(seed) + L1 = num.random.randn(3, 3) + np.random.seed(seed) + L2 = np.random.randn(3, 3) + assert L1.ndim == L2.ndim + + def test_negative_seed(self) -> None: + with pytest.raises(ValueError): + np.random.seed(-10) + num.random.seed(-10) + # See https://github.com/nv-legate/cunumeric.internal/issues/484 + # cuNumeric passed with negative value + + def test_seed_float(self) -> None: + msg = r"Cannot cast scalar from dtype('float64') to dtype('int64') " + " according to the rule 'safe'" + with pytest.raises(TypeError, match=re.escape(msg)): + np.random.seed(10.5) + + num.random.seed(10.5) + # See https://github.com/nv-legate/cunumeric.internal/issues/199 + # cuNumeric passed with float value + + def test_RandomState() -> None: rdm_num = num.random.RandomState(10) L1 = rdm_num.randn(3, 3) @@ -94,15 +215,7 @@ def test_RandomState() -> None: L2 = rdm_np.randn(3, 3) assert np.array_equal(L1, L2) - -def test_float() -> None: - with pytest.raises(TypeError): - np.random.seed(10.5) - # TypeError: 'float' object cannot be interpreted as an integer - num.random.seed(10.5) - # cuPyNumeric passed with float - - + if __name__ == "__main__": import sys From 0c323e2363d403e2da1d0d20dc3af5628a8e9d0b Mon Sep 17 00:00:00 2001 From: Bryan Van de Ven Date: Fri, 15 Nov 2024 11:05:44 -0800 Subject: [PATCH 361/462] update switcher.json (#503) --- docs/cupynumeric/switcher.json | 45 ---------------------------------- 1 file changed, 45 deletions(-) diff --git a/docs/cupynumeric/switcher.json b/docs/cupynumeric/switcher.json index ed1ae384e9..26311c3be6 100644 --- a/docs/cupynumeric/switcher.json +++ b/docs/cupynumeric/switcher.json @@ -1,49 +1,4 @@ [ - { - "name": "22.05", - "version": "22.05", - "url": "https://nv-legate.github.io/cupynumeric/22.05/" - }, - { - "name": "22.08", - "version": "22.08", - "url": "https://nv-legate.github.io/cupynumeric/22.08/" - }, - { - "name": "22.10", - "version": "22.10", - "url": "https://nv-legate.github.io/cupynumeric/22.10/" - }, - { - "name": "23.01", - "version": "23.01", - "url": "https://nv-legate.github.io/cupynumeric/23.01/" - }, - { - "name": "23.03", - "version": "23.03", - "url": "https://nv-legate.github.io/cupynumeric/23.03/" - }, - { - "name": "23.07", - "version": "23.07", - "url": "https://nv-legate.github.io/cupynumeric/23.07/" - }, - { - "name": "23.09", - "version": "23.09", - "url": "https://nv-legate.github.io/cupynumeric/23.09/" - }, - { - "name": "23.11", - "version": "23.11", - "url": "https://nv-legate.github.io/cupynumeric/23.11/" - }, - { - "name": "24.06", - "version": "24.06", - "url": "https://docs.nvidia.com/cupynumeric/24.06/" - }, { "name": "24.11", "version": "24.11", From 48aa2a3a70171f4999c96141bd2b1a8eceb9295f Mon Sep 17 00:00:00 2001 From: Bryan Van de Ven Date: Fri, 15 Nov 2024 12:05:30 -0800 Subject: [PATCH 362/462] cuPy (project) -> CuPy (#504) --- docs/cupynumeric/source/faqs.rst | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/cupynumeric/source/faqs.rst b/docs/cupynumeric/source/faqs.rst index 1165d1bf22..a1d9276d32 100644 --- a/docs/cupynumeric/source/faqs.rst +++ b/docs/cupynumeric/source/faqs.rst @@ -158,10 +158,10 @@ increase the problem size and correspondingly increase the resources needed for the problem size as described in the Usage section. Take a look at our :ref:`practices` on how to do that. -Why is cuPyNumeric slower than cuPy on my laptop? ------------------------------------------------ +Why is cuPyNumeric slower than CuPy on my laptop? +------------------------------------------------- -For small problem sizes, cuPyNumeric might be slower than cuPy. We suggest you +For small problem sizes, cuPyNumeric might be slower than CuPy. We suggest you increase the problem size and correspondingly increase the resources needed for the problem size as described in the :ref:`Usage` section. Take a look at performance :ref:`practices`. From e591c64cc07a1c3589e1267b937d74a8de1ef687 Mon Sep 17 00:00:00 2001 From: Irina Demeshko Date: Fri, 15 Nov 2024 16:25:09 -0800 Subject: [PATCH 363/462] addind TorchSWE into the docs (#499) * addind TorchSWE into the docs * addind TorchSWE into the docs * updating TorchSWE notebook * updating some text in TorchSWE notebook * updating some text in TorchSWE notebook * Update docs/cupynumeric/source/examples/torchswe.ipynb Co-authored-by: Manolis Papadakis * Update docs/cupynumeric/source/examples/torchswe.ipynb Co-authored-by: Manolis Papadakis * Update docs/cupynumeric/source/examples/torchswe.ipynb Co-authored-by: Manolis Papadakis * Update docs/cupynumeric/source/examples/torchswe.ipynb Co-authored-by: Manolis Papadakis * Update docs/cupynumeric/source/examples/torchswe.ipynb Co-authored-by: Manolis Papadakis * Update docs/cupynumeric/source/examples/torchswe.ipynb Co-authored-by: Manolis Papadakis * Update docs/cupynumeric/source/examples/torchswe.ipynb Co-authored-by: Manolis Papadakis * Update docs/cupynumeric/source/examples/torchswe.ipynb Co-authored-by: Manolis Papadakis * Update docs/cupynumeric/source/examples/torchswe.ipynb Co-authored-by: Manolis Papadakis --------- Co-authored-by: Manolis Papadakis --- docs/cupynumeric/source/examples/index.rst | 1 + .../source/examples/torchswe.ipynb | 219 ++++++++++++++++++ 2 files changed, 220 insertions(+) create mode 100644 docs/cupynumeric/source/examples/torchswe.ipynb diff --git a/docs/cupynumeric/source/examples/index.rst b/docs/cupynumeric/source/examples/index.rst index 6c2f0cfba9..bd1adf4e2a 100644 --- a/docs/cupynumeric/source/examples/index.rst +++ b/docs/cupynumeric/source/examples/index.rst @@ -11,3 +11,4 @@ Examples edge_detection newton_raphson_2d compact_finite_difference + torchswe diff --git a/docs/cupynumeric/source/examples/torchswe.ipynb b/docs/cupynumeric/source/examples/torchswe.ipynb new file mode 100644 index 0000000000..fd9c8af4b4 --- /dev/null +++ b/docs/cupynumeric/source/examples/torchswe.ipynb @@ -0,0 +1,219 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "5be6c57b-7cae-4fc1-b78f-899becabc6ee", + "metadata": {}, + "source": [ + "

TorchSWE case study

\n", + "\n", + "\n", + "[TorchSWE](https://github.com/piyueh/TorchSWE) is a shallow-water solver created by Dr. Pi-Yueh Chuang and Prof. Lorena Barba that solves the vertically averaged Navier-Stokes equations using MPI and CuPy. It can simulate free-surface water flow in rivers, channels, and coastal areas, as well as model flood inundation. Given a topography, TorchSWE can predict flood-prone areas and the height of water inundation, making it a valuable tool for risk mapping.\n", + "\n", + "High-resolution numerical simulations—such as those on real topographies requiring hundreds of millions of data points—demand distributed computation across multiple GPUs. Although scalability is achievable with MPI4Py and CuPy, this approach requires manually partitioning the problem and managing inter-GPU data communication, which are complex and error-prone tasks.\n", + "\n", + "cuPyNumeric enables a distributed implementation of TorchSWE using only NumPy operations, without the complexities of MPI+CuPy. After porting TorchSWE to cuPyNumeric by removing all domain decomposition logic, it scaled effortlessly across multiple GPUs and nodes without further code modifications. This scalability enabled high-fidelity simulations exceeding 1.2 billion data points using 32 GPUs, allowing researchers to tackle critical scientific problems in flood inundation modeling without needing specialized distributed computing expertise. Overall, the cuPyNumeric implementation reduced the lines of code by over 20%, and simplified development and maintenance by eliminating complex logic for managing distribution and communication.\n" + ] + }, + { + "cell_type": "markdown", + "id": "0402fb01-748b-48d9-9caa-80e7510ade80", + "metadata": {}, + "source": [ + "\n", + "

Deep dive into the TorchSWE code implementation

\n", + "\n", + "

Original code details

\n", + "\n", + "TorchSWE uses stencil operations to model shallow-water equations on a 2D grid, where each point is updated based on neighboring values, simulating water flow dynamics. The stencil computations are structured to update each grid cell iteratively, based on data from surrounding cells, mimicking fluid behavior over time. Below is an example that mimics the basic structure of the stencil logic from the TorchSWE repository:\n" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "id": "640f0b62-f70f-4d8a-86c5-7b4739e60a33", + "metadata": {}, + "outputs": [], + "source": [ + "import numpy as np\n", + " \n", + "# Example dimensions for the grid\n", + "nx, ny = 128, 128\n", + "grid = np.ones((nx, ny)) # Initialize the grid with \"1\"\n", + "\n", + "# Stencil operation \n", + "for i in range(1, nx - 1):\n", + " for j in range(1, ny - 1):\n", + " grid[i, j] = (grid[i + 1, j] + grid[i - 1, j] + grid[i, j + 1] + grid[i, j - 1]) / 4\n" + ] + }, + { + "cell_type": "markdown", + "id": "0281b3f4-5a48-40cc-9ec8-0fc9d7fd760c", + "metadata": {}, + "source": [ + "This code iteratively updates cell `h[i, j]` using adjacent cells, representing a basic averaging stencil operation that can be extended to various boundary conditions and flow dynamics in the shallow-water model. For full context, refer to [TorchSWE on GitHub](https://github.com/piyueh/TorchSWE).\n", + "\n", + "Parallelizing stencil operations for multi-GPU systems is challenging. When arrays are partitioned across multiple GPUs, any update to a cell requires the updated values to be shared between GPUs to maintain consistency across boundaries. This communication overhead and synchronization make parallelizing stencil code complex and difficult to implement efficiently on multi-GPU architectures.\n", + "\n", + "Below, we outline TorchSWE’s MPI4Py logic in more detail to highlight the complexity involved in this implementation.\n", + "Here’s an example code snippet that mirrors the TorchSWE MPI logic, implementing a simple MPI stencil operation from above:\n" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "0d7db631-3ae9-41ca-a0f1-07390349fbd0", + "metadata": {}, + "outputs": [], + "source": [ + "from mpi4py import MPI\n", + "import cupy as cp\n", + "\n", + "num_timesteps=10\n", + "\n", + "def set_device(comm: MPI.Comm):\n", + " # Device selection for each rank on multi-GPU nodes (TorchSWE-specific)\n", + " n_gpus = cp.cuda.runtime.getDeviceCount()\n", + " local_rank = comm.Get_rank() % n_gpus\n", + " cp.cuda.runtime.setDevice(local_rank)\n", + "\n", + "comm = MPI.COMM_WORLD\n", + "rank = comm.Get_rank()\n", + "size = comm.Get_size()\n", + "\n", + "# Determine grid size and decompose domain\n", + "gnx, gny = 126,126 # global grid dimensions\n", + "local_nx, local_ny = gnx // size, gny # local grid dimensions per rank\n", + "local_grid = cp.ones((local_nx + 2, local_ny + 2)) # with halo boundaries\n", + "\n", + "# Set up MPI data types and boundaries\n", + "send_type, recv_type = MPI.DOUBLE.Create_subarray((local_nx + 2, local_ny + 2), (local_nx, local_ny), (1, 1)), MPI.DOUBLE.Create_subarray((local_nx + 2, local_ny + 2), (local_nx, local_ny), (1, 1))\n", + "send_type.Commit()\n", + "recv_type.Commit()\n", + "\n", + "# Stencil computation loop\n", + "for timestep in range(num_timesteps):\n", + " # Boundary exchange with non-blocking sends/receives\n", + " reqs = []\n", + " if rank > 0:\n", + " reqs.append(comm.Isend(local_grid[1, :], dest=rank - 1))\n", + " reqs.append(comm.Irecv(local_grid[0, :], source=rank - 1))\n", + " if rank < size - 1:\n", + " reqs.append(comm.Isend(local_grid[local_nx, :], dest=rank + 1))\n", + " reqs.append(comm.Irecv(local_grid[local_nx + 1, :], source=rank + 1))\n", + "\n", + " # Ensure all sends/receives are complete\n", + " MPI.Request.Waitall(reqs)\n", + "\n", + " # Perform stencil operation\n", + " for i in range(1, local_nx + 1):\n", + " for j in range(1, local_ny + 1):\n", + " local_grid[i, j] = 0.25 * (local_grid[i - 1, j] + local_grid[i + 1, j] +\n", + " local_grid[i, j - 1] + local_grid[i, j + 1])\n", + "\n", + "# Clean up MPI data types\n", + "send_type.Free()\n", + "recv_type.Free()\n", + "MPI.Finalize()\n" + ] + }, + { + "cell_type": "markdown", + "id": "660621f9-2bc9-49a3-be59-cde1ce87df65", + "metadata": {}, + "source": [ + "This example follows TorchSWE's approach to domain decomposition and parallelization as in the original implementation. It starts with MPI initialization and sets up logic to manage GPU assignment per rank, dividing the global grid into subdomains. Each rank is responsible for a local subgrid with added halo rows to hold neighboring data. Once the domain is decomposed, the user must ensure proper communication of data at processor boundaries, accounting for datatype differences between CuPy and MPI4Py. For optimal performance, the appropriate type of point-to-point communication, such as non-blocking send/recv, must be selected, as incorrect implementation can cause deadlock. Users must also handle varying numbers of neighboring ranks on domain boundaries and ensure data exchange across mesh, topography, and solution variables. Non-blocking `Isend` and `Irecv` functions handle boundary data exchanges, allowing each rank to receive necessary data for stencil computations. After a `Waitall` synchronization step, each rank performs computations on its subdomain. Finally, custom MPI data types are freed, and `MPI_Finalize()` concludes the environment.\n", + "\n", + "The actual TorchSWE code has additional complexities specific to its use of multiple arrays, GPU memory management, one-sided communications etc.\n", + "For the complete implementation, you can refer to the [TorchSWE repository](https://github.com/piyueh/TorchSWE).\n", + "\n", + "Explicit distributed logic, like that in TorchSWE, is difficult to debug and maintain throughout the lifespan of simulation codes. Most applications, including TorchSWE, require specialized validation tests to ensure correct outputs. This results in significant programming effort and further complicates development. \n" + ] + }, + { + "cell_type": "markdown", + "id": "e93aa24e-fc18-4f69-819d-59b5997aa087", + "metadata": {}, + "source": [ + "

cuPyNumeric Implementation

\n", + "\n", + "In the [cuPyNumeric version of TorchSWE](https://github.com/shriram-jagan/TorchSWE), stencil operations are implemented using distributed array handling from cuPyNumeric, simplifying the code and removing the need for manual partitioning or boundary synchronization. The code operates similarly to NumPy slicing but scales across multiple GPUs. For example, the stencil computation in this version would typically involve using simple array slices like below (instead of the nested loops with integrated MPI logic as in the original implementation).\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "b6e15757-a681-4a09-9f82-6304adf82fb4", + "metadata": {}, + "outputs": [], + "source": [ + "import cupynumeric as np\n", + " \n", + "# Example dimensions\n", + "nx, ny = 128, 128\n", + "\n", + "# Initialize the array h\n", + "grid = np.ones((nx, ny))\n", + "\n", + "# Stencil operation using slicing\n", + "grid[1:-1, 1:-1] = (\n", + " grid[2:, 1:-1] + # Below\n", + " grid[:-2, 1:-1] + # Above\n", + " grid[1:-1, 2:] + # Right\n", + " grid[1:-1, :-2] # Left\n", + ") / 4\n" + ] + }, + { + "cell_type": "markdown", + "id": "f29f5387-3408-4bff-948d-55519412de31", + "metadata": {}, + "source": [ + "This operation is automatically managed across nodes and GPUs without needing MPI-specific code. More details can be found in the [cuPyNumeric port of TorchSWE](https://github.com/shriram-jagan/TorchSWE).\n", + "\n", + "The cuPyNumeric version of TorchSWE eliminates 600 lines of code related to domain decomposition, communication, synchronization, and validation that would otherwise be needed when using MPI4Py with CuPy. These 600 lines require substantial knowledge of distributed computing from domain scientists. By using cuPyNumeric, the simplified NumPy code scales efficiently to 1024 GPUs, making high-fidelity flood modeling accessible without requiring specialized expertise in distributed systems." + ] + }, + { + "cell_type": "markdown", + "id": "7e5d6565-ceda-4b61-8826-b6ae5aff3c83", + "metadata": {}, + "source": [ + "

Conclusion

\n", + "\n", + "cuPyNumeric significantly simplifies the development and maintenance of distributed simulations, such as TorchSWE, by abstracting complex parallelization, synchronization, and communication logic. This eliminates the need for specialized HPC knowledge and reduces the risk of errors, allowing domain scientists to focus on their research. With cuPyNumeric, large-scale simulations can scale efficiently across large HPC systems, enhancing productivity, reducing programming effort, and lowering development costs. \n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "eb3a186a-3ea7-4150-8ec0-7760ad2adf1f", + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.12.7" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} From 4222f1721b3a7c140078a3d87ceafe23a28a74d2 Mon Sep 17 00:00:00 2001 From: Bo Dong <1551091+dongb@users.noreply.github.com> Date: Fri, 15 Nov 2024 16:48:08 -0800 Subject: [PATCH 364/462] Move the Note from the top of page to the bottom of the page in README.md (#505) --- README.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 44bc15c90e..6b9b39325f 100644 --- a/README.md +++ b/README.md @@ -15,8 +15,6 @@ limitations under the License. --> -*This project, i.e., cuPyNumeric, is separate and independent of the CuPy project. CuPy is a registered trademark of Preferred Networks.* - [![Build Nightly release package](https://github.com/nv-legate/cupynumeric.internal/actions/workflows/ci-gh-nightly-release.yml/badge.svg)](https://github.com/nv-legate/cupynumeric.internal/actions/workflows/ci-gh-nightly-release.yml) # cuPyNumeric @@ -62,3 +60,6 @@ For technical questions about cuPyNumeric and Legate-based tools, please visit the [community discussion forum](https://github.com/nv-legate/discussion). If you have other questions, please contact us at legate(at)nvidia.com. + +## Note +*This project, i.e., cuPyNumeric, is separate and independent of the CuPy project. CuPy is a registered trademark of Preferred Networks.* From a4b656a1d0e39a8eb68d7749a74a866b91b96fb6 Mon Sep 17 00:00:00 2001 From: Marcin Zalewski Date: Sat, 16 Nov 2024 15:21:30 -0800 Subject: [PATCH 365/462] Documentation improvements (#502) * Initial pass * todos.rst * Address comments * Fix warnings * Update product positioning * Add supported platform info * Move all Jupyter instructions to Legate * more warnings * Remove todos --------- Co-authored-by: Manolis Papadakis --- cupynumeric/_array/array.py | 2 +- cupynumeric/_ufunc/ufunc.py | 6 +- cupynumeric/fft/fft.py | 2 +- cupynumeric/random/_bitgenerator.py | 2 +- cupynumeric/random/_generator.py | 2 +- docs/cupynumeric/source/api/comparison.rst | 2 +- .../source/examples/torchswe.ipynb | 2 +- docs/cupynumeric/source/faqs.rst | 29 +++-- docs/cupynumeric/source/index.rst | 16 ++- docs/cupynumeric/source/installation.rst | 7 +- docs/cupynumeric/source/user/howtos/index.rst | 1 - .../source/user/howtos/jupyter.rst | 107 ------------------ docs/cupynumeric/source/user/practices.rst | 6 +- 13 files changed, 38 insertions(+), 146 deletions(-) delete mode 100644 docs/cupynumeric/source/user/howtos/jupyter.rst diff --git a/cupynumeric/_array/array.py b/cupynumeric/_array/array.py index 3252714425..1787affe62 100644 --- a/cupynumeric/_array/array.py +++ b/cupynumeric/_array/array.py @@ -416,7 +416,7 @@ def flat(self) -> np.flatiter[npt.NDArray[Any]]: flatten : Return a copy of the array collapsed into one dimension. Availability - -------- + ------------ Single CPU """ diff --git a/cupynumeric/_ufunc/ufunc.py b/cupynumeric/_ufunc/ufunc.py index 552079d722..5b2eb11e98 100644 --- a/cupynumeric/_ufunc/ufunc.py +++ b/cupynumeric/_ufunc/ufunc.py @@ -79,7 +79,7 @@ numpy.{} Availability --------- +------------ Multiple GPUs, Multiple CPUs """ @@ -117,7 +117,7 @@ numpy.{} Availability --------- +------------ Multiple GPUs, Multiple CPUs """ @@ -155,7 +155,7 @@ numpy.{} Availability --------- +------------ Multiple GPUs, Multiple CPUs """ diff --git a/cupynumeric/fft/fft.py b/cupynumeric/fft/fft.py index a1fd699488..7576f3dd40 100644 --- a/cupynumeric/fft/fft.py +++ b/cupynumeric/fft/fft.py @@ -104,7 +104,7 @@ def fft( numpy.fft.fft Availability - -------- + ------------ Multiple GPUs """ s = (n,) if n is not None else None diff --git a/cupynumeric/random/_bitgenerator.py b/cupynumeric/random/_bitgenerator.py index 2dbd41a29b..55ecbea8eb 100644 --- a/cupynumeric/random/_bitgenerator.py +++ b/cupynumeric/random/_bitgenerator.py @@ -53,7 +53,7 @@ def __init__( numpy.random.BitGenerator Availability - -------- + ------------ Multiple GPUs, Multiple CPUs """ if type(self) is BitGenerator: diff --git a/cupynumeric/random/_generator.py b/cupynumeric/random/_generator.py index b2d22a1fb9..4736bd8981 100644 --- a/cupynumeric/random/_generator.py +++ b/cupynumeric/random/_generator.py @@ -57,7 +57,7 @@ def __init__(self, bit_generator: BitGenerator) -> None: default_rng : Recommended constructor for `Generator`. Availability - -------- + ------------ Multiple GPUs, Multiple CPUs """ diff --git a/docs/cupynumeric/source/api/comparison.rst b/docs/cupynumeric/source/api/comparison.rst index db552f54e6..eda6dddecb 100644 --- a/docs/cupynumeric/source/api/comparison.rst +++ b/docs/cupynumeric/source/api/comparison.rst @@ -7,6 +7,6 @@ A dot in the cupynumeric column denotes that cuPyNumeric implementation is not provided yet. We welcome contributions for these functions. NumPy vs cuPyNumeric APIs ------------------------ +------------------------- .. comparison-table:: diff --git a/docs/cupynumeric/source/examples/torchswe.ipynb b/docs/cupynumeric/source/examples/torchswe.ipynb index fd9c8af4b4..c4b6173b9e 100644 --- a/docs/cupynumeric/source/examples/torchswe.ipynb +++ b/docs/cupynumeric/source/examples/torchswe.ipynb @@ -5,7 +5,7 @@ "id": "5be6c57b-7cae-4fc1-b78f-899becabc6ee", "metadata": {}, "source": [ - "

TorchSWE case study

\n", + "# TorchSWE case study\n", "\n", "\n", "[TorchSWE](https://github.com/piyueh/TorchSWE) is a shallow-water solver created by Dr. Pi-Yueh Chuang and Prof. Lorena Barba that solves the vertically averaged Navier-Stokes equations using MPI and CuPy. It can simulate free-surface water flow in rivers, channels, and coastal areas, as well as model flood inundation. Given a topography, TorchSWE can predict flood-prone areas and the height of water inundation, making it a valuable tool for risk mapping.\n", diff --git a/docs/cupynumeric/source/faqs.rst b/docs/cupynumeric/source/faqs.rst index a1d9276d32..b581ad7919 100644 --- a/docs/cupynumeric/source/faqs.rst +++ b/docs/cupynumeric/source/faqs.rst @@ -11,7 +11,7 @@ Legate offers three different task variants: CPU, OMP, and GPU. A task variant determines the type of processor Legate chooses to perform the computations. What is the difference between Legate and cuPyNumeric? ----------------------------------------------------- +------------------------------------------------------ Legate is a task-based runtime software stack that enables development of scalable and composable libraries for distributed and accelerated computing. @@ -101,14 +101,13 @@ How to handle Out-Of-Memory errors? .. code-block:: text - [0 - 7fb9fc426000] 0.985000 {5}{cupynumeric.mapper}: Mapper cupynumeric on Node 0 failed to allocate 144000000 bytes on memory 1e00000000000000 (of kind SYSTEM_MEM: Visible to all processors on a node) for region requirement 1 of Task cupynumeric::WhereTask[./script.py:90] (UID 39). + [0 - 7fda18f26000] 0.805182 {5}{cunumeric.mapper}: Failed to allocate 8388608 bytes on memory 1e00000000000000 (of kind SYSTEM_MEM) for region requirement(s) 1 of Task cupynumeric::BinaryOpTask[oom.py:24] (UID 18) The above error indicates that the application ran out of memory during execution. More granular details on the type of memory, the task that triggered -the error are provided in the error message, but this usually indicates that -resources (add more cores/threads/ GPUs, or increase the amount of system -memory or framebuffer memory) or decrease the problem size and confirm that you -are able to run the program to completion. +the error, and what was using up the available memory are provided in the error +message. If possible, try increasing the amount of system memory or framebuffer +memory allocated to the program, or decrease the problem size. Reducing the ``--eager-alloc-percentage`` to, say, 10 or less can also help since this reduces the amount of available memory available to the eager memory @@ -151,7 +150,7 @@ Check out the :ref:`benchmarking` section for information on how to accurately measure cuPyNumeric execution. Why is cuPyNumeric slower than NumPy on my laptop? ------------------------------------------------- +-------------------------------------------------- For small problem sizes, cuPyNumeric might be slower than NumPy. We suggest you increase the problem size and correspondingly increase the resources needed @@ -169,7 +168,7 @@ performance :ref:`practices`. How do I use Jupyter Notebooks? ------------------------------- -Notebooks are useful for experimentation and evaluation on a single node. +See https://docs.nvidia.com/legate/latest/jupyter.html. How to pass Legion and Realm arguments? --------------------------------------- @@ -191,19 +190,17 @@ What are the defaults? The default values for several input arguments to Legate are mentioned in Legate's documentation. -Are there resources where I can read more about Legate? -------------------------------------------------------- +Where I can read more about cuPyNumeric? +---------------------------------------- Check out this `blog post `_ +or this `tutorial `_ to learn more about cuPyNumeric. -Technical questions? --------------------- +Questions? +---------- For technical questions about cuPyNumeric and Legate-based tools, please visit the `community discussion forum `_. -Other questions? ----------------- - -Follow us on `GitHub `_ or reach out to us there. +If you have other questions, please contact us at *legate@nvidia.com*. diff --git a/docs/cupynumeric/source/index.rst b/docs/cupynumeric/source/index.rst index c7a8401c6e..b0e163d8e0 100644 --- a/docs/cupynumeric/source/index.rst +++ b/docs/cupynumeric/source/index.rst @@ -1,15 +1,15 @@ :html_theme.sidebar_secondary.remove: NVIDIA cuPyNumeric -================ +================== -cuPyNumeric is a `Legate`_ library that aims to provide a distributed and -accelerated drop-in replacement for the `NumPy API`_ on top of the `Legion`_ -runtime. +With cuPyNumeric you can write code productively in Python, using the familiar +`NumPy API`_, and have your program scale with no code changes from single-CPU +computers to multi-node-multi-GPU clusters. -Using cuPyNumeric you do things like run the final example of the -`Python CFD course`_ completely unmodified on 2048 A100 GPUs in a -`DGX SuperPOD`_ and achieve good weak scaling. +For example, you can run the final example of the `Python CFD course`_ +completely unmodified on 2048 A100 GPUs in a `DGX SuperPOD`_ and achieve +good weak scaling. .. toctree:: :maxdepth: 1 @@ -30,7 +30,5 @@ Indices and tables * :ref:`search` .. _DGX SuperPOD: https://www.nvidia.com/en-us/data-center/dgx-superpod/ -.. _Legate: https://github.com/nv-legate/legate.core -.. _Legion: https://legion.stanford.edu/ .. _Numpy API: https://numpy.org/doc/stable/reference/ .. _Python CFD course: https://github.com/barbagroup/CFDPython/blob/master/lessons/15_Step_12.ipynb \ No newline at end of file diff --git a/docs/cupynumeric/source/installation.rst b/docs/cupynumeric/source/installation.rst index 690fab6c8e..d5e97c844d 100644 --- a/docs/cupynumeric/source/installation.rst +++ b/docs/cupynumeric/source/installation.rst @@ -4,6 +4,9 @@ Installation Default conda install --------------------- +cuPyNumeric supports the +`same platforms as Legate `_. + cuPyNumeric is available from `conda `_ on the `legate channel `_. @@ -33,7 +36,9 @@ environment, use environment variable ``CONDA_OVERRIDE_CUDA``: conda install -c conda-forge -c legate cupynumeric Once installed, you can verify the installation by running one of the examples -from the cuPyNumeric repository, for instance: +from the +`cuPyNumeric repository `_, +for instance: .. code-block:: sh diff --git a/docs/cupynumeric/source/user/howtos/index.rst b/docs/cupynumeric/source/user/howtos/index.rst index 1e07c8f0b2..72140ffd72 100644 --- a/docs/cupynumeric/source/user/howtos/index.rst +++ b/docs/cupynumeric/source/user/howtos/index.rst @@ -6,5 +6,4 @@ Howtos measuring benchmarking - jupyter patching diff --git a/docs/cupynumeric/source/user/howtos/jupyter.rst b/docs/cupynumeric/source/user/howtos/jupyter.rst deleted file mode 100644 index c0c3f8ffdf..0000000000 --- a/docs/cupynumeric/source/user/howtos/jupyter.rst +++ /dev/null @@ -1,107 +0,0 @@ -Configuring Jupyter kernels -=========================== - -Legate supports single-node execution of programs using Jupyter Notebooks. -Please use the instructions given below to set up IPython kernels that -will be used in the notebooks. - -Setup ------ - -IPython Kernel -~~~~~~~~~~~~~~ - -Inputs that are passed to the Legate launcher will now be passed to the -notebook through IPython kernels. By default, ``LEGATE_SM_GPU`` kernel will -be available and set to use one GPU. - -For each set of inputs to legate, a new kernel will have to be created using -``legate-jupyter`` and then selected from the drop-down menu for -"Select Kernel" from your notebook. - -Use the following to list all the installed kernels. By default, -``LEGATE_SM_GPU`` should be available. - -.. code-block:: sh - - jupyter kernelspec list - -To create a new kernel that corresponds to a particular set of inputs to -``legate``, say, to run on 2 CPUs with 10GB of memory and 10% of memory -reserved for eager allocations, run the following: - -.. code-block:: sh - - legate-jupyter --name "legate_cpus_2" --cpus 2 --sysmem 10000 --eager-alloc-percentage 10 - - jupyter kernelspec list - -This should create a new kernel named ``legate_cpus_2``. The installed kernel -can then be selected from the notebook to run on two CPUs. - -You can also see input arguments that were passed to Legate by the kernel by -using magic commands from a cell in the notebook (including the % character), -like below: - -.. code-block:: text - - %load_ext legate.info - %legate_info - -A sample output from a custom kernel is given below: - -.. code-block:: text - - Kernel 'legate_cpus_2' configured for 1 node(s) - - Cores: - CPUs to use per rank : 2 - GPUs to use per rank : 0 - OpenMP groups to use per rank : 0 - Threads per OpenMP group : 4 - Utility processors per rank : 2 - - Memory: - DRAM memory per rank (in MBs) : 10000 - DRAM memory per NUMA domain per rank (in MBs) : 0 - Framebuffer memory per GPU (in MBs) : 4000 - Zero-copy memory per rank (in MBs) : 32 - Registered CPU-side pinned memory per rank (in MBs) : 0 - -Running on a remote server -~~~~~~~~~~~~~~~~~~~~~~~~~~ - -If you intend to run the notebook on a remote server or a laptop, you will -have to create a tunnel from your localhost to the remote server. Substitute -remote-server-hostname with the hostname of the remote server you plan to use, - -.. code-block:: sh - - ssh -4 -t -L 8888:localhost:8002 username@remote-server-hostname ssh -t -L 8002:localhost:8888 remote-server-hostname - -and then run on your local machine: - -.. code-block:: sh - - jupyter notebook --port=8888 --no-browser - -This should give a URL where the Jupyter server is running and will look like -this: - -.. code-block:: text - - http://localhost:8888/tree?token= - -Where ```` will be different each time you launch jupyter. Launch -the URL from your browser and choose the ``Legate_SM_GPU`` kernel. This ensures -that the underlying computations can be run using the resources specified -in the ``Legate_SM_GPU`` kernel. - -For more information on how this works with the runtime, we refer the readers -to respective sections in Legion and Legate documentation. - -Running Jupyter Notebooks -------------------------- - -You are now set up to run the notebooks using Jupyter with your configured -options. Check out the notebooks in the `examples` section. diff --git a/docs/cupynumeric/source/user/practices.rst b/docs/cupynumeric/source/user/practices.rst index 263e347ce9..063a7a0fb7 100644 --- a/docs/cupynumeric/source/user/practices.rst +++ b/docs/cupynumeric/source/user/practices.rst @@ -17,10 +17,10 @@ etc.) is noted in the docstring of the API. This would be useful to know while designing the application since it can impact the scalability. Guidelines on using cuPyNumeric APIs ----------------------------------- +------------------------------------ Use cuPyNumeric or NumPy arrays, AVOID native lists -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Create a cuPyNumeric array from data structures native to Python like lists, tuples, etc., and operate on the cuPyNumeric array, as shown in the example @@ -232,7 +232,7 @@ Faster I/O Routines As of 23.07, we recommend using `h5py `_ to perform I/O. Guidelines on designing cuPyNumeric applications ----------------------------------------------- +------------------------------------------------ Use output arguments to reduce memory allocation ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ From 1ed98c2fdd815d5a4bf7cde2f4d583917e084199 Mon Sep 17 00:00:00 2001 From: Bryan Van de Ven Date: Tue, 19 Nov 2024 14:15:41 -0800 Subject: [PATCH 366/462] remove typing_extensions cruft (#511) --- cupynumeric/_module/creation_ranges.py | 5 +++-- tests/unit/util.py | 3 +-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/cupynumeric/_module/creation_ranges.py b/cupynumeric/_module/creation_ranges.py index d04d94b535..dc09d8ad09 100644 --- a/cupynumeric/_module/creation_ranges.py +++ b/cupynumeric/_module/creation_ranges.py @@ -15,7 +15,8 @@ from __future__ import annotations import math -from typing import TYPE_CHECKING, Any +from types import EllipsisType +from typing import TYPE_CHECKING import numpy as np @@ -180,7 +181,7 @@ def linspace( delta = stop - start y = arange(0, num, dtype=dt) - out: tuple[Any, ...] # EllipsisType not even in typing_extensions yet + out: tuple[int | EllipsisType | slice, ...] # Reshape these arrays into dimensions that allow them to broadcast if delta.ndim > 0: diff --git a/tests/unit/util.py b/tests/unit/util.py index a6bb0a49e2..17a8c53da7 100644 --- a/tests/unit/util.py +++ b/tests/unit/util.py @@ -16,10 +16,9 @@ from __future__ import annotations from itertools import chain, combinations -from typing import Any, Iterable, Iterator +from typing import Any, Iterable, Iterator, TypeAlias import pytest -from typing_extensions import TypeAlias Capsys: TypeAlias = pytest.CaptureFixture[str] From c9ceba13f50bb9a02db4e17545f558fa251f06bb Mon Sep 17 00:00:00 2001 From: Bryan Van de Ven Date: Mon, 25 Nov 2024 15:47:38 -0800 Subject: [PATCH 367/462] use tester hooks for cunumeric-specific config (#515) --- .pre-commit-config.yaml | 7 +++--- cmake/versions.json | 2 +- test.py | 51 +++++++++++++++++++++++++++-------------- 3 files changed, 39 insertions(+), 21 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 8c98204508..ab351fc46f 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -9,15 +9,16 @@ repos: - repo: https://github.com/PyCQA/isort rev: 5.12.0 hooks: - - id: isort + - id: isort - repo: https://github.com/psf/black rev: 23.9.1 hooks: - - id: black + - id: black + args: ["--target-version", "py310"] - repo: https://github.com/PyCQA/flake8 rev: 6.1.0 hooks: - - id: flake8 + - id: flake8 - repo: https://github.com/pre-commit/mirrors-clang-format rev: 'v16.0.6' # Use the sha / tag you want to point at hooks: diff --git a/cmake/versions.json b/cmake/versions.json index 4cafa0dabe..d229aed56a 100644 --- a/cmake/versions.json +++ b/cmake/versions.json @@ -10,7 +10,7 @@ "git_url" : "git@github.com:nv-legate/legate.core.internal.git", "git_shallow": false, "always_download": false, - "git_tag" : "838956e08b544fa3b63280a87dea0cf8020bef82" + "git_tag" : "388ffdb0a25152e06a1fb02335339115cc11543d" } } } diff --git a/test.py b/test.py index e8111d5824..27752fee0c 100755 --- a/test.py +++ b/test.py @@ -18,29 +18,46 @@ import sys -import legate.tester -from legate.tester import CustomTest +from legate.tester import CustomTest, FeatureType from legate.tester.config import Config +from legate.tester.project import Project from legate.tester.test_plan import TestPlan from legate.tester.test_system import TestSystem +from legate.util.types import EnvDict + + +class CPNProject(Project): + def custom_files(self) -> list[CustomTest]: + return [ + CustomTest("examples/quantiles.py"), + CustomTest("examples/sort.py"), + CustomTest("tests/integration/test_argsort.py"), + CustomTest("tests/integration/test_msort.py"), + CustomTest("tests/integration/test_nanpercentiles.py"), + CustomTest("tests/integration/test_nanquantiles.py"), + CustomTest("tests/integration/test_partition.py"), + CustomTest("tests/integration/test_percentiles.py"), + CustomTest("tests/integration/test_quantiles.py"), + CustomTest("tests/integration/test_sort_complex.py"), + CustomTest("tests/integration/test_sort.py"), + CustomTest("tests/integration/test_unique.py"), + ] + + def stage_env(self, feature: FeatureType) -> EnvDict: + match feature: + case "eager": + return { + "CUPYNUMERIC_FORCE_THUNK": "eager", + "CUPYNUMERIC_MIN_CPU_CHUNK": "2000000000", + "CUPYNUMERIC_MIN_OMP_CHUNK": "2000000000", + "CUPYNUMERIC_MIN_GPU_CHUNK": "2000000000", + } + case _: + return {} -legate.tester.CUSTOM_FILES = [ - CustomTest("examples/quantiles.py"), - CustomTest("examples/sort.py"), - CustomTest("tests/integration/test_argsort.py"), - CustomTest("tests/integration/test_msort.py"), - CustomTest("tests/integration/test_nanpercentiles.py"), - CustomTest("tests/integration/test_nanquantiles.py"), - CustomTest("tests/integration/test_partition.py"), - CustomTest("tests/integration/test_percentiles.py"), - CustomTest("tests/integration/test_quantiles.py"), - CustomTest("tests/integration/test_sort_complex.py"), - CustomTest("tests/integration/test_sort.py"), - CustomTest("tests/integration/test_unique.py"), -] if __name__ == "__main__": - config = Config(sys.argv) + config = Config(sys.argv, project=CPNProject()) system = TestSystem(dry_run=config.dry_run) From e510ff266cef4dd2a0b10bc30910e6765dcb280b Mon Sep 17 00:00:00 2001 From: Irina Demeshko Date: Tue, 26 Nov 2024 19:45:59 -0800 Subject: [PATCH 368/462] Removing `std::` from unary_ops (#514) * repacing math functions from std with CUDA/C++ native ones * testing sign only for numpy 2.0 and newer --- src/cupynumeric/unary/unary_op_util.h | 207 +++++++------------------- tests/integration/test_unary_ufunc.py | 42 ++++++ 2 files changed, 94 insertions(+), 155 deletions(-) diff --git a/src/cupynumeric/unary/unary_op_util.h b/src/cupynumeric/unary/unary_op_util.h index bb592af857..30a9b8272c 100644 --- a/src/cupynumeric/unary/unary_op_util.h +++ b/src/cupynumeric/unary/unary_op_util.h @@ -240,7 +240,6 @@ struct UnaryOp { !std::is_integral<_T>::value>* = nullptr> constexpr _T operator()(const _T& x) const { - using std::fabs; return static_cast<_T>(fabs(x)); } }; @@ -279,11 +278,7 @@ struct UnaryOp { UnaryOp(const std::vector& args) {} - constexpr decltype(auto) operator()(const T& x) const - { - using std::acos; - return acos(x); - } + constexpr decltype(auto) operator()(const T& x) const { return acos(x); } }; template @@ -293,11 +288,7 @@ struct UnaryOp { UnaryOp(const std::vector& args) {} - constexpr decltype(auto) operator()(const T& x) const - { - using std::acosh; - return acosh(x); - } + constexpr decltype(auto) operator()(const T& x) const { return acosh(x); } }; template <> @@ -309,7 +300,6 @@ struct UnaryOp { __CUDA_HD__ __half operator()(const __half& x) const { - using std::acosh; return __half{acosh(static_cast(x))}; } }; @@ -321,11 +311,7 @@ struct UnaryOp { UnaryOp(const std::vector& args) {} - constexpr decltype(auto) operator()(const T& x) const - { - using std::asin; - return asin(x); - } + constexpr decltype(auto) operator()(const T& x) const { return asin(x); } }; template @@ -335,11 +321,7 @@ struct UnaryOp { UnaryOp(const std::vector& args) {} - constexpr decltype(auto) operator()(const T& x) const - { - using std::asinh; - return asinh(x); - } + constexpr decltype(auto) operator()(const T& x) const { return asinh(x); } }; template <> @@ -351,7 +333,6 @@ struct UnaryOp { __CUDA_HD__ __half operator()(const __half& x) const { - using std::asinh; return __half{asinh(static_cast(x))}; } }; @@ -363,11 +344,7 @@ struct UnaryOp { UnaryOp(const std::vector& args) {} - constexpr decltype(auto) operator()(const T& x) const - { - using std::atan; - return atan(x); - } + constexpr decltype(auto) operator()(const T& x) const { return atan(x); } }; template @@ -377,11 +354,7 @@ struct UnaryOp { UnaryOp(const std::vector& args) {} - constexpr decltype(auto) operator()(const T& x) const - { - using std::atanh; - return atanh(x); - } + constexpr decltype(auto) operator()(const T& x) const { return atanh(x); } }; template <> @@ -393,7 +366,6 @@ struct UnaryOp { __CUDA_HD__ __half operator()(const __half& x) const { - using std::atanh; return __half{atanh(static_cast(x))}; } }; @@ -405,11 +377,7 @@ struct UnaryOp { UnaryOp(const std::vector& args) {} - constexpr decltype(auto) operator()(const T& x) const - { - using std::cbrt; - return cbrt(x); - } + constexpr decltype(auto) operator()(const T& x) const { return cbrt(x); } }; template <> @@ -421,7 +389,6 @@ struct UnaryOp { __CUDA_HD__ __half operator()(const __half& x) const { - using std::cbrt; return __half{cbrt(static_cast(x))}; } }; @@ -433,11 +400,7 @@ struct UnaryOp { UnaryOp(const std::vector& args) {} - constexpr decltype(auto) operator()(const T& x) const - { - using std::ceil; - return ceil(x); - } + constexpr decltype(auto) operator()(const T& x) const { return ceil(x); } }; template @@ -494,11 +457,7 @@ struct UnaryOp { UnaryOp(const std::vector& args) {} - constexpr decltype(auto) operator()(const T& x) const - { - using std::cos; - return cos(x); - } + constexpr decltype(auto) operator()(const T& x) const { return cos(x); } }; template @@ -508,11 +467,7 @@ struct UnaryOp { UnaryOp(const std::vector& args) {} - constexpr decltype(auto) operator()(const T& x) const - { - using std::cosh; - return cosh(x); - } + constexpr decltype(auto) operator()(const T& x) const { return cosh(x); } }; template <> @@ -524,7 +479,6 @@ struct UnaryOp { __CUDA_HD__ __half operator()(const __half& x) const { - using std::cosh; return __half{cosh(static_cast(x))}; } }; @@ -559,11 +513,7 @@ struct UnaryOp { UnaryOp(const std::vector& args) {} - constexpr decltype(auto) operator()(const T& x) const - { - using std::exp; - return exp(x); - } + constexpr decltype(auto) operator()(const T& x) const { return exp(x); } }; template @@ -576,12 +526,13 @@ struct UnaryOp { template ::value>* = nullptr> constexpr T operator()(const T& x) const { - return std::exp2(x); + return exp2(x); } template ::value>* = nullptr> constexpr T operator()(const T& x) const { + // we can keep using std:: here since CUDA version will use thrust:: using std::exp; using std::log; #ifdef __NVCC__ @@ -602,7 +553,6 @@ struct UnaryOp { __CUDA_HD__ __half operator()(const __half& x) const { - using std::exp2; return __half{exp2(static_cast(x))}; } }; @@ -617,13 +567,14 @@ struct UnaryOp { template ::value>* = nullptr> constexpr decltype(auto) operator()(const T& x) const { - using std::expm1; return expm1(x); } template ::value>* = nullptr> constexpr decltype(auto) operator()(const T& x) const { + // CUDA's "exp" function does not directly support complex numbers, + // so using one from std using std::exp; return exp(x) - T(1); } @@ -638,7 +589,6 @@ struct UnaryOp { __CUDA_HD__ __half operator()(const __half& x) const { - using std::expm1; return __half{expm1(static_cast(x))}; } }; @@ -650,11 +600,7 @@ struct UnaryOp { UnaryOp(const std::vector& args) {} - constexpr decltype(auto) operator()(const T& x) const - { - using std::floor; - return floor(x); - } + constexpr decltype(auto) operator()(const T& x) const { return floor(x); } }; template @@ -704,13 +650,13 @@ struct UnaryOp { template ::value>* = nullptr> __CUDA_HD__ bool operator()(const T& x) const { - return std::isfinite(x); + return isfinite(x); } template __CUDA_HD__ bool operator()(const complex<_T>& x) const { - return std::isfinite(x.imag()) && std::isfinite(x.real()); + return isfinite(x.imag()) && isfinite(x.real()); } __CUDA_HD__ bool operator()(const __half& x) const { return isfinite(static_cast(x)); } @@ -732,13 +678,13 @@ struct UnaryOp { template ::value>* = nullptr> __CUDA_HD__ bool operator()(const T& x) const { - return std::isinf(x); + return isinf(x); } template __CUDA_HD__ bool operator()(const complex<_T>& x) const { - return std::isinf(x.imag()) || std::isinf(x.real()); + return isinf(x.imag()) || isinf(x.real()); } __CUDA_HD__ bool operator()(const __half& x) const { return isinf(static_cast(x)); } @@ -760,14 +706,13 @@ struct UnaryOp { template ::value>* = nullptr> __CUDA_HD__ bool operator()(const T& x) const { - using std::isnan; return isnan(x); } template __CUDA_HD__ bool operator()(const complex<_T>& x) const { - return std::isnan(x.imag()) || std::isnan(x.real()); + return isnan(x.imag()) || isnan(x.real()); } __CUDA_HD__ bool operator()(const __half& x) const { return isnan(x); } @@ -781,11 +726,7 @@ struct UnaryOp { UnaryOp(const std::vector& args) {} - constexpr decltype(auto) operator()(const T& x) const - { - using std::log; - return log(x); - } + constexpr decltype(auto) operator()(const T& x) const { return log(x); } }; template @@ -796,11 +737,7 @@ struct UnaryOp { UnaryOp(const std::vector& args) {} - constexpr decltype(auto) operator()(const T& x) const - { - using std::log10; - return log10(x); - } + constexpr decltype(auto) operator()(const T& x) const { return log10(x); } }; template <> @@ -812,8 +749,7 @@ struct UnaryOp { __CUDA_HD__ __half operator()(const __half& x) const { - using std::log10; - return __half{log10(static_cast(x))}; + return __half{log10f(static_cast(x))}; } }; @@ -828,14 +764,12 @@ struct UnaryOp { template ::value>* = nullptr> constexpr decltype(auto) operator()(const T& x) const { - using std::log1p; return log1p(x); } template ::value>* = nullptr> constexpr decltype(auto) operator()(const T& x) const { - using std::log; return log(T(1) + x); } }; @@ -849,8 +783,7 @@ struct UnaryOp { __CUDA_HD__ __half operator()(const __half& x) const { - using std::log1p; - return __half{log1p(static_cast(x))}; + return __half{log1pf(static_cast(x))}; } }; @@ -865,14 +798,12 @@ struct UnaryOp { template ::value>* = nullptr> constexpr decltype(auto) operator()(const T& x) const { - using std::log2; return log2(x); } template ::value>* = nullptr> constexpr decltype(auto) operator()(const T& x) const { - using std::log; return log(x) / log(T{2}); } }; @@ -886,8 +817,7 @@ struct UnaryOp { __CUDA_HD__ __half operator()(const __half& x) const { - using std::sinh; - return __half{log2(static_cast(x))}; + return __half{log2f(static_cast(x))}; } }; @@ -991,13 +921,13 @@ struct UnaryOp { template ::value>* = nullptr> constexpr decltype(auto) operator()(const _T& x) const { - return _T(std::rint(x.real()), std::rint(x.imag())); + return _T(rint(x.real()), rint(x.imag())); } template ::value>* = nullptr> constexpr decltype(auto) operator()(const _T& x) const { - return std::rint(x); + return rint(x); } }; @@ -1010,7 +940,6 @@ struct UnaryOp { __CUDA_HD__ __half operator()(const __half& x) const { - using std::rint; return __half{rint(static_cast(x))}; } }; @@ -1032,17 +961,17 @@ struct UnaryOp { { if constexpr (legate::is_complex_type::value) { if (decimals < 0) { - return T{static_cast(std::rint(x.real() / factor) * factor), - static_cast(std::rint(x.imag() / factor) * factor)}; + return T{static_cast(rint(x.real() / factor) * factor), + static_cast(rint(x.imag() / factor) * factor)}; } else { - return T{static_cast(std::rint(x.real() * factor) / factor), - static_cast(std::rint(x.imag() * factor) / factor)}; + return T{static_cast(rint(x.real() * factor) / factor), + static_cast(rint(x.imag() * factor) / factor)}; } } else { if (decimals < 0) { - return static_cast(std::rint(x / factor) * factor); + return static_cast(rint(x / factor) * factor); } else { - return static_cast(std::rint(x * factor) / factor); + return static_cast(rint(x * factor) / factor); } } } @@ -1067,9 +996,9 @@ struct UnaryOp { __CUDA_HD__ __half operator()(const __half& x) const { if (decimals < 0) { - return static_cast<__half>(std::rint(static_cast(x) / factor) * factor); + return static_cast<__half>(rint(static_cast(x) / factor) * factor); } else { - return static_cast<__half>(std::rint(static_cast(x) * factor) / factor); + return static_cast<__half>(rint(static_cast(x) * factor) / factor); } } @@ -1103,11 +1032,11 @@ struct UnaryOp { template ::value>* = nullptr> constexpr decltype(auto) operator()(const _T& x) const { - if (x.real() != 0) { - return _T(detail::sign(x.real()), 0); - } else { - return _T(detail::sign(x.imag()), 0); + auto magnitude = abs(x); // Magnitude of the complex number + if (magnitude == 0) { + return _T(0, 0); // Return 0 if the input is 0 } + return x / magnitude; // Normalize to unit magnitude } template ::value>* = nullptr> @@ -1139,6 +1068,8 @@ struct UnaryOp { constexpr bool operator()(const T& x) const { + // the signbit function is not directly supported by CUDA , + // so using one from std using std::signbit; return signbit(x); } @@ -1153,6 +1084,8 @@ struct UnaryOp { __CUDA_HD__ bool operator()(const __half& x) const { + // the signbit function is not directly supported by CUDA , + // so using one from std using std::signbit; return std::signbit(static_cast(x)); } @@ -1165,11 +1098,7 @@ struct UnaryOp { UnaryOp(const std::vector& args) {} - constexpr decltype(auto) operator()(const T& x) const - { - using std::sin; - return sin(x); - } + constexpr decltype(auto) operator()(const T& x) const { return sin(x); } }; template @@ -1179,11 +1108,7 @@ struct UnaryOp { UnaryOp(const std::vector& args) {} - constexpr decltype(auto) operator()(const T& x) const - { - using std::sinh; - return sinh(x); - } + constexpr decltype(auto) operator()(const T& x) const { return sinh(x); } }; template <> @@ -1195,7 +1120,6 @@ struct UnaryOp { __CUDA_HD__ __half operator()(const __half& x) const { - using std::sinh; return __half{sinh(static_cast(x))}; } }; @@ -1227,11 +1151,7 @@ struct UnaryOp { UnaryOp(const std::vector& args) {} - constexpr decltype(auto) operator()(const T& x) const - { - using std::sqrt; - return sqrt(x); - } + constexpr decltype(auto) operator()(const T& x) const { return sqrt(x); } }; template @@ -1241,11 +1161,7 @@ struct UnaryOp { UnaryOp(const std::vector& args) {} - constexpr decltype(auto) operator()(const T& x) const - { - using std::tan; - return tan(x); - } + constexpr decltype(auto) operator()(const T& x) const { return tan(x); } }; template @@ -1255,11 +1171,7 @@ struct UnaryOp { UnaryOp(const std::vector& args) {} - constexpr decltype(auto) operator()(const T& x) const - { - using std::tanh; - return tanh(x); - } + constexpr decltype(auto) operator()(const T& x) const { return tanh(x); } }; template @@ -1269,11 +1181,7 @@ struct UnaryOp { UnaryOp(const std::vector& args) {} - constexpr decltype(auto) operator()(const T& x) const - { - using std::trunc; - return trunc(x); - } + constexpr decltype(auto) operator()(const T& x) const { return trunc(x); } }; template <> @@ -1285,7 +1193,6 @@ struct UnaryOp { __CUDA_HD__ __half operator()(const __half& x) const { - using std::trunc; return __half{trunc(static_cast(x))}; } }; @@ -1302,11 +1209,7 @@ struct MultiOutUnaryOp { using RHS2 = int32_t; using LHS = RHS1; - __CUDA_HD__ LHS operator()(const RHS1& rhs1, RHS2* rhs2) const - { - using std::frexp; - return frexp(rhs1, rhs2); - } + __CUDA_HD__ LHS operator()(const RHS1& rhs1, RHS2* rhs2) const { return frexp(rhs1, rhs2); } }; template <> @@ -1318,7 +1221,6 @@ struct MultiOutUnaryOp { __CUDA_HD__ LHS operator()(const RHS1& rhs1, RHS2* rhs2) const { - using std::frexp; return static_cast<__half>(frexp(static_cast(rhs1), rhs2)); } }; @@ -1330,11 +1232,7 @@ struct MultiOutUnaryOp { using RHS2 = RHS1; using LHS = RHS1; - __CUDA_HD__ LHS operator()(const RHS1& rhs1, RHS2* rhs2) const - { - using std::modf; - return modf(rhs1, rhs2); - } + __CUDA_HD__ LHS operator()(const RHS1& rhs1, RHS2* rhs2) const { return modf(rhs1, rhs2); } }; template <> @@ -1346,7 +1244,6 @@ struct MultiOutUnaryOp { __CUDA_HD__ LHS operator()(const RHS1& rhs1, RHS2* rhs2) const { - using std::modf; float tmp; float result = modf(static_cast(rhs1), &tmp); *rhs2 = static_cast<__half>(tmp); diff --git a/tests/integration/test_unary_ufunc.py b/tests/integration/test_unary_ufunc.py index fb889e5aff..9d60252599 100644 --- a/tests/integration/test_unary_ufunc.py +++ b/tests/integration/test_unary_ufunc.py @@ -17,10 +17,20 @@ import numpy as np import pytest +from packaging.version import Version from utils.comparisons import allclose import cupynumeric as num +complex_data = [ + 1 + 1j, + -1 - 1j, + 5 + 1j, + 1 + 0.5j, + 2.0 + 1.4j, + -1 + 2j, +] + def deterministic_op_test(func): # Uses the op name to create a deterministic seed. @@ -118,12 +128,15 @@ def check_op_input( astype=None, out_dtype="d", replace_zero=None, + complex_type=False, **check_kwargs, ): if randint: assert a_min is not None assert a_max is not None in_np = np.random.randint(a_min, a_max, size=shape) + elif complex_type: + in_np = np.array(complex_data) else: in_np = np.random.randn(*shape) if offset is not None: @@ -158,6 +171,20 @@ def check_math_ops(op, **kwargs): check_op_input(op, astype="B", **kwargs) check_op_input(op, randint=True, a_min=1, a_max=10, **kwargs) check_op_input(op, shape=(1,), **kwargs) + no_complex_test_list = ( + "fabs", + "logical_not", + ) + numpy_version = Version(np.__version__) + # sign has an incorrect implementation for complex + # numbers in numpy <2.0 + if numpy_version < Version("2.0"): + no_complex_test_list += ("sign",) + + if op not in no_complex_test_list: + check_op_input( + op, complex_type=True, out_dtype=np.complex128, **kwargs + ) # Math operations @@ -224,6 +251,9 @@ def test_log_ops(op): check_op_input(op, randint=True, a_min=3, a_max=10) check_op_input(op, shape=(1,), a_min=0.1, offset=3) + # check with complex data type + check_op_input(op, complex_type=True, out_dtype=np.complex128) + even_root_ops = ("sqrt",) @@ -239,6 +269,8 @@ def test_even_root_ops(op): check_op_input(op, astype="F", out_dtype="D") check_op_input(op, randint=True, a_min=3, a_max=10) check_op_input(op, shape=(1,), a_min=0.1, offset=3) + # check with complex data type + check_op_input(op, complex_type=True, out_dtype=np.complex128) odd_root_ops = ("cbrt",) @@ -276,6 +308,12 @@ def test_trig_ops(op): check_op(op, np.random.uniform(low=-1, high=1, size=(4, 5))) check_op(op, np.random.uniform(low=-1, high=1, size=(4, 5)).astype("e")) check_op(op, np.array(np.random.uniform(low=-1, high=1))) + # check with complex data type + if op not in ( + "deg2rad", + "rad2deg", + ): + check_op_input(op, complex_type=True, out_dtype=np.complex128) arc_hyp_trig_ops = ( @@ -290,6 +328,8 @@ def test_arc_hyp_trig_ops(op): check_op(op, np.random.uniform(low=1, high=5, size=(4, 5))) check_op(op, np.random.uniform(low=1, high=5, size=(4, 5)).astype("e")) check_op(op, np.array(np.random.uniform(low=1, high=5))) + # check with complex data type + check_op_input(op, complex_type=True, out_dtype=np.complex128) bit_ops = ("invert", "~") @@ -344,6 +384,8 @@ def test_nan_ops(op): check_op(op, np.array([-np.inf, 0.0, 1.0, np.inf, np.nan], dtype="F")) check_op(op, np.array([-np.inf, 0.0, 1.0, np.inf, np.nan], dtype="e")) check_op(op, np.array(np.inf)) + # check with complex data type + check_op_input(op, complex_type=True, out_dtype=np.complex128) def parse_inputs(in_str, dtype_str): From ed80b2fea064a6dd7f02709386faf0e699b00661 Mon Sep 17 00:00:00 2001 From: Marcin Zalewski Date: Tue, 26 Nov 2024 20:02:52 -0800 Subject: [PATCH 369/462] Read versions.json for Legate version (#513) --- CMakeLists.txt | 2 ++ cmake/thirdparty/get_legate.cmake | 54 +++++++++++++++++++++++++++---- conda/conda-build/build.sh | 2 +- 3 files changed, 50 insertions(+), 8 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 9a063e2c3b..e966a64cd6 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -16,6 +16,8 @@ cmake_minimum_required(VERSION 3.22.1 FATAL_ERROR) +cmake_path(SET CUPYNUMERIC_CMAKE_DIR NORMALIZE "${CMAKE_CURRENT_LIST_DIR}/cmake") + if(POLICY CMP0077) cmake_policy(SET CMP0077 NEW) set(CMAKE_POLICY_DEFAULT_CMP0077 NEW) diff --git a/cmake/thirdparty/get_legate.cmake b/cmake/thirdparty/get_legate.cmake index 68db8207b3..3050634759 100644 --- a/cmake/thirdparty/get_legate.cmake +++ b/cmake/thirdparty/get_legate.cmake @@ -14,17 +14,61 @@ # limitations under the License. #============================================================================= +# This is based on the similar function for Legion in the Legate code +function(cupynumeric_maybe_override_legate user_repository user_branch user_version) + # CPM_ARGS GIT_TAG and GIT_REPOSITORY don't do anything if you have already overridden + # those options via a rapids_cpm_package_override() call. So we have to conditionally + # override the defaults (by creating a temporary json file in build dir) only if the + # user sets them. + + # See https://github.com/rapidsai/rapids-cmake/issues/575. Specifically, this function + # is pretty much identical to + # https://github.com/rapidsai/rapids-cmake/issues/575#issuecomment-2045374410. + cmake_path(SET legate_overrides_json NORMALIZE + "${CUPYNUMERIC_CMAKE_DIR}/versions.json") + if(user_repository OR user_branch OR user_version) + # The user has set either one of these, time to create our cludge. + file(READ "${legate_overrides_json}" default_legate_json) + set(new_legate_json "${default_legate_json}") + + if(user_repository) + string(JSON new_legate_json SET "${new_legate_json}" "packages" "Legate" "git_url" + "\"${user_repository}\"") + endif() + + if(user_branch) + string(JSON new_legate_json SET "${new_legate_json}" "packages" "Legate" "git_tag" + "\"${user_branch}\"") + endif() + + if(user_version) + string(JSON new_legate_json SET "${new_legate_json}" "packages" "Legate" "version" + "\"${user_version}\"") + endif() + + string(JSON eq_json EQUAL "${default_legate_json}" "${new_legate_json}") + if(NOT eq_json) + cmake_path(SET legate_overrides_json NORMALIZE + "${CMAKE_CURRENT_BINARY_DIR}/versions.json") + file(WRITE "${legate_overrides_json}" "${new_legate_json}") + endif() + endif() + rapids_cpm_package_override("${legate_overrides_json}") +endfunction() + function(find_or_configure_legate) + set(options) set(oneValueArgs VERSION REPOSITORY BRANCH EXCLUDE_FROM_ALL) + set(multiValueArgs) cmake_parse_arguments(PKG "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN}) - include("${rapids-cmake-dir}/export/detail/parse_version.cmake") - rapids_export_parse_version(${PKG_VERSION} legate PKG_VERSION) + cupynumeric_maybe_override_legate("${PKG_REPOSITORY}" "${PKG_BRANCH}" "${PKG_VERSION}") include("${rapids-cmake-dir}/cpm/detail/package_details.cmake") rapids_cpm_package_details(legate version git_repo git_branch shallow exclude_from_all) - set(version ${PKG_VERSION}) + string(REPLACE "00" "0" version "${version}") + set(exclude_from_all ${PKG_EXCLUDE_FROM_ALL}) if(PKG_BRANCH) set(git_branch "${PKG_BRANCH}") @@ -93,10 +137,6 @@ foreach(_var IN ITEMS "cupynumeric_LEGATE_VERSION" endif() endforeach() -if(NOT DEFINED cupynumeric_LEGATE_VERSION) - set(cupynumeric_LEGATE_VERSION "${cupynumeric_VERSION}") -endif() - find_or_configure_legate(VERSION ${cupynumeric_LEGATE_VERSION} REPOSITORY ${cupynumeric_LEGATE_REPOSITORY} BRANCH ${cupynumeric_LEGATE_BRANCH} diff --git a/conda/conda-build/build.sh b/conda/conda-build/build.sh index 2a7b6589fc..5780d7c90a 100644 --- a/conda/conda-build/build.sh +++ b/conda/conda-build/build.sh @@ -40,7 +40,7 @@ echo "Build starting on $(date)" CUDAFLAGS="-isystem ${PREFIX}/include -L${PREFIX}/lib" export CUDAFLAGS -cmake -S . -B build ${CMAKE_ARGS} -DCMAKE_BUILD_PARALLEL_LEVEL=$CPU_COUNT +cmake -S . -B build ${CMAKE_ARGS} -DCMAKE_BUILD_PARALLEL_LEVEL=$CPU_COUNT --trace-expand cmake --build build -j$CPU_COUNT --verbose cmake --install build From fc1bc6a2322c5a4f378065d69b8a8713f70a8e0b Mon Sep 17 00:00:00 2001 From: Parag Kulkarni Date: Wed, 27 Nov 2024 23:37:18 +0530 Subject: [PATCH 370/462] Update legate-gh-ci version to v1.21 (#494) --- .github/workflows/gh-build-and-test.yml | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/.github/workflows/gh-build-and-test.yml b/.github/workflows/gh-build-and-test.yml index 39401e9d11..6959393c76 100644 --- a/.github/workflows/gh-build-and-test.yml +++ b/.github/workflows/gh-build-and-test.yml @@ -48,13 +48,13 @@ jobs: needs: setup-build name: "Build (${{ inputs.platform }}, ${{ inputs.target-device }}, ${{ inputs.build-type }}, Python ${{ inputs.python-version }})" uses: - nv-legate/legate-gh-ci/.github/workflows/gh-build.yml@v1.19 + nv-legate/legate-gh-ci/.github/workflows/gh-build.yml@v1.21 with: build-mode: "" build-type: ${{ inputs.build-type }} client-repo: ${{ github.event.repository.name }} dependencies-file: "cmake/versions.json" - legate-gh-ci-tag: "v1.19" + legate-gh-ci-tag: "v1.21" network: "ucx" platform: ${{ inputs.platform }} python-version: ${{ inputs.python-version }} @@ -68,13 +68,13 @@ jobs: needs: setup-build name: "Check if legate.internal nightly exists for SHA specified in versions.json (${{ inputs.platform }}, ${{ inputs.target-device }}, ${{ inputs.build-type }})" uses: - nv-legate/legate-gh-ci/.github/workflows/gh-check-if-nightly-exists-for-all-dependencies.yml@v1.19 + nv-legate/legate-gh-ci/.github/workflows/gh-check-if-nightly-exists-for-all-dependencies.yml@v1.21 with: build-mode: "" build-type: ${{ inputs.build-type }} client-repo: ${{ github.event.repository.name }} dependencies-file: "cmake/versions.json" - legate-gh-ci-tag: "v1.19" + legate-gh-ci-tag: "v1.21" network: "ucx" platform: ${{ inputs.platform }} python-version: ${{ inputs.python-version }} @@ -88,12 +88,12 @@ jobs: if: ${{ github.repository_owner == 'nv-legate' && contains(github.workflow, 'release') && inputs.upload-enabled == true }} name: Upload package to Server uses: - nv-legate/legate-gh-ci/.github/workflows/gh-upload.yml@v1.19 + nv-legate/legate-gh-ci/.github/workflows/gh-upload.yml@v1.21 with: build-mode: "" build-type: ${{ inputs.build-type }} client-repo: ${{ github.event.repository.name }} - legate-gh-ci-tag: "v1.19" + legate-gh-ci-tag: "v1.21" name: Upload package to Server network: "ucx" pkgSubString: "cupynumeric-" @@ -101,7 +101,7 @@ jobs: python-version: ${{ inputs.python-version }} repos-Root: "cupynumeric" target-device: ${{ inputs.target-device }} - upload-action: "upload-package" + upload-action: "upload-package-ALL" upload-enabled: ${{ inputs.upload-enabled }} secrets: inherit @@ -176,13 +176,13 @@ jobs: matrix: ${{fromJson(needs.setup-test.outputs.matrix)}} uses: - nv-legate/legate-gh-ci/.github/workflows/gh-test-within-container.yml@v1.19 + nv-legate/legate-gh-ci/.github/workflows/gh-test-within-container.yml@v1.21 with: build-mode: "" build-type: ${{ inputs.build-type }} client-repo: ${{ github.event.repository.name }} has-gpu: ${{ matrix.runner.type == 'gpu' }} - legate-gh-ci-tag: "v1.19" + legate-gh-ci-tag: "v1.21" name: ${{ matrix.test-config.name }} network: "ucx" platform: ${{ inputs.platform }} @@ -198,12 +198,12 @@ jobs: name: Update Test status on Server if: ${{ (github.repository_owner == 'nv-legate') && contains(github.workflow, 'Nightly') && (inputs.upload-enabled == true) }} uses: - nv-legate/legate-gh-ci/.github/workflows/gh-upload.yml@v1.19 + nv-legate/legate-gh-ci/.github/workflows/gh-upload.yml@v1.21 with: build-mode: "" build-type: ${{ inputs.build-type }} client-repo: ${{ github.event.repository.name }} - legate-gh-ci-tag: "v1.19" + legate-gh-ci-tag: "v1.21" name: UpdateTestStatus network: "ucx" pkgSubString: "cupynumeric-" From 758b8e794a5c998c3f8c06a46b005e7982bb6d9a Mon Sep 17 00:00:00 2001 From: Mark Vaz Date: Thu, 28 Nov 2024 12:51:27 -0500 Subject: [PATCH 371/462] Update repo name for legate (#525) --- cmake/versions.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cmake/versions.json b/cmake/versions.json index d229aed56a..b207a9c9f9 100644 --- a/cmake/versions.json +++ b/cmake/versions.json @@ -1,13 +1,13 @@ { "packages" : { "legate" : { - "repo": "legate.core.internal", + "repo": "legate.internal", "artifact_name": "${{ inputs.platform }}-${{ inputs.build-type }}-<>-python${{ inputs.python-version }}-${{ inputs.target-device }}-release-with_tests-${{ inputs.network }}-<>", "org": "nv-legate", "artifact_workflow": "ci-gh.yml", "nightly_workflow": "ci-gh-nightly-release.yml", "version": "25.01.00", - "git_url" : "git@github.com:nv-legate/legate.core.internal.git", + "git_url" : "git@github.com:nv-legate/legate.internal.git", "git_shallow": false, "always_download": false, "git_tag" : "388ffdb0a25152e06a1fb02335339115cc11543d" From fdfa554c71d8e3045faff36d41c69b1bc76f56c1 Mon Sep 17 00:00:00 2001 From: Robin Wang <104830875+robinwnv@users.noreply.github.com> Date: Sat, 30 Nov 2024 09:18:44 +0800 Subject: [PATCH 372/462] Port dot from Python to C++. (#11) * Port and test dot for various dimensions. * Address comments. * Address comments - part2 --- src/cupynumeric/ndarray.cc | 251 +++++++++++++++++++- src/cupynumeric/ndarray.h | 14 +- src/cupynumeric/operators.cc | 129 +++++++--- src/cupynumeric/operators.h | 18 +- tests/cpp/integration/common_utils.h | 18 ++ tests/cpp/integration/test_dot.cc | 339 ++++++++++++++++++++++++--- 6 files changed, 694 insertions(+), 75 deletions(-) diff --git a/src/cupynumeric/ndarray.cc b/src/cupynumeric/ndarray.cc index c3d6ebcbd3..b1b5af4091 100644 --- a/src/cupynumeric/ndarray.cc +++ b/src/cupynumeric/ndarray.cc @@ -560,22 +560,17 @@ void NDArray::unary_reduction(int32_t op_code_, NDArray input) uint64_t ceildiv(uint64_t a, uint64_t b) { return (a + b - 1) / b; } -void NDArray::dot(NDArray rhs1, NDArray rhs2) +void NDArray::dot_MM(legate::LogicalStore& rhs1_store, legate::LogicalStore& rhs2_store) { - if (size() == 0) { - return; - } - auto runtime = CuPyNumericRuntime::get_runtime(); fill(get_reduction_identity(UnaryRedCode::SUM, type())); - assert(dim() == 2 && rhs1.dim() == 2 && rhs2.dim() == 2); - - auto m = rhs1.shape()[0]; - auto n = rhs2.shape()[1]; - auto k = rhs1.shape()[1]; + assert(dim() == 2 && rhs1_store.dim() == 2 && rhs2_store.dim() == 2); + auto m = rhs1_store.shape()[0]; + auto n = rhs2_store.shape()[1]; + auto k = rhs1_store.shape()[1]; // compute tilesize for lhs and batch_size for k // TODO make generic std::vector initial_tile_shape = {512, 512}; @@ -600,8 +595,8 @@ void NDArray::dot(NDArray rhs1, NDArray rhs2) auto color_k = ceildiv(k, k_batch_size); auto p_lhs = store_.partition_by_tiling(tile_shape); - auto p_rhs1 = rhs1.store_.partition_by_tiling(tile_shape_rhs1); - auto p_rhs2 = rhs2.store_.partition_by_tiling(tile_shape_rhs2); + auto p_rhs1 = rhs1_store.partition_by_tiling(tile_shape_rhs1); + auto p_rhs2 = rhs2_store.partition_by_tiling(tile_shape_rhs2); for (std::uint64_t i = 0; i < color_k; ++i) { auto task = runtime->create_task(CuPyNumericOpCode::CUPYNUMERIC_MATMUL, color_shape); @@ -1853,6 +1848,238 @@ NDArray NDArray::_maybe_convert(const legate::Type& type) const } } +void NDArray::_verify_mode_extent(const std::map& mode2extent, + const std::vector& modes, + const std::vector& shape) const +{ + for (int32_t i = 0; i < modes.size(); i++) { + assert(mode2extent.at(modes[i]) == shape[i]); + } +} + +legate::LogicalStore NDArray::_alphabetical_transpose(legate::LogicalStore store, + const std::vector& modes) const +{ + std::map map_mode_id; + for (int i = 0; i < modes.size(); i++) { + map_mode_id[modes[i]] = i; + } + + auto modes_copy{modes}; + std::sort(modes_copy.begin(), modes_copy.end()); + std::vector axes; + for (int i = 0; i < modes_copy.size(); i++) { + axes.push_back(map_mode_id[modes_copy[i]]); + } + return store.transpose(std::move(axes)); +} + +enum BlasOps { + BlasOperationNone = 0, + BlasOperationVV = 1, + BlasOperationMV = 2, + BlasOperationMM = 3, +}; + +// This function ports contract function in cupynumeric/_thunk/deferred.py +// to-do: handle store overlap +// to-do: support np.float16 +void NDArray::contract(const std::vector& lhs_modes, + NDArray rhs1, + const std::vector& rhs1_modes, + NDArray rhs2, + const std::vector& rhs2_modes, + const std::map& mode2extent) +{ + // Sanity checks + // no duplicate modes within an array + std::set s_lhs_modes(lhs_modes.begin(), lhs_modes.end()); + assert(lhs_modes.size() == s_lhs_modes.size()); + std::set s_rhs1_modes(rhs1_modes.begin(), rhs1_modes.end()); + assert(rhs1_modes.size() == s_rhs1_modes.size()); + std::set s_rhs2_modes(rhs2_modes.begin(), rhs2_modes.end()); + assert(rhs2_modes.size() == s_rhs2_modes.size()); + // no singleton modes + std::unordered_map mode_counts; + for (auto v : lhs_modes) { + ++mode_counts[v]; + } + for (auto v : rhs1_modes) { + ++mode_counts[v]; + } + for (auto v : rhs2_modes) { + ++mode_counts[v]; + } + for (auto const& count : mode_counts) { + assert(count.second == 2 || count.second == 3); + } + + // arrays and mode lists agree on dimensionality + assert(dim() == lhs_modes.size()); + assert(rhs1.dim() == rhs1_modes.size()); + assert(rhs2.dim() == rhs2_modes.size()); + // array shapes agree with mode extents (broadcasting should have been + // handled by the frontend) + _verify_mode_extent(mode2extent, lhs_modes, shape()); + _verify_mode_extent(mode2extent, rhs1_modes, rhs1.shape()); + _verify_mode_extent(mode2extent, rhs2_modes, rhs2.shape()); + // casting has been handled by the frontend + assert(type() == rhs1.type()); + assert(type() == rhs2.type()); + + // to-do: Handle store overlap + + enum BlasOps blas_op = BlasOperationNone; + bool count_unequals_two = false; + for (auto const& count : mode_counts) { + if (count.second != 2) { + count_unequals_two = true; + break; + } + } + if (!count_unequals_two) { + if (lhs_modes.size() == 0 && rhs1_modes.size() == 1 && rhs2_modes.size() == 1) { + blas_op = BlasOperationVV; + } else if (lhs_modes.size() == 1 && (rhs1_modes.size() == 2 and rhs2_modes.size() == 1 || + rhs1_modes.size() == 1 and rhs2_modes.size() == 2)) { + blas_op = BlasOperationMV; + } else if (lhs_modes.size() == 2 and rhs1_modes.size() == 2 and rhs2_modes.size() == 2) { + blas_op = BlasOperationMM; + } + } + // to-do: support np.float16 + + // Clear output array + auto zero = legate::type_dispatch(type().code(), generate_zero_fn{}); + fill(zero); + // Pull out the stores + auto lhs_s = store_; + auto rhs1_s = rhs1.store_; + auto rhs2_s = rhs2.store_; + auto runtime = CuPyNumericRuntime::get_runtime(); + + if (blas_op != BlasOperationNone) { + if (blas_op == BlasOperationVV) { // for Scalar, dim() == 0 or 1? + auto task = runtime->create_task(CuPyNumericOpCode::CUPYNUMERIC_DOT); + auto redop = get_reduction_op(UnaryRedCode::SUM); + task.add_reduction(lhs_s, redop); + auto p_rhs1 = task.add_input(rhs1_s); + auto p_rhs2 = task.add_input(rhs2_s); + task.add_constraint(align(p_rhs1, p_rhs2)); + runtime->submit(std::move(task)); + } else if (blas_op == BlasOperationMV) { + // Matrix-vector or vector-matrix multiply + // b,(ab/ba)->a --> (ab/ba),b->a + if (rhs1_modes.size() == 1) { + std::swap(rhs1_s, rhs2_s); + } + // ba,b->a --> ab,b->a + if (rhs1_modes[0] == rhs2_modes[0]) { + rhs1_s = rhs1_s.transpose({1, 0}); + } + auto m = rhs1_s.extents().data()[0]; + auto n = rhs1_s.extents().data()[1]; + rhs2_s = rhs2_s.promote(0, m); + lhs_s = lhs_s.promote(1, n); + + auto task = runtime->create_task(CuPyNumericOpCode::CUPYNUMERIC_MATVECMUL); + auto redop = get_reduction_op(UnaryRedCode::SUM); + auto p_lhs = task.add_reduction(lhs_s, redop); + auto p_rhs1 = task.add_input(rhs1_s); + auto p_rhs2 = task.add_input(rhs2_s); + task.add_constraint(align(p_lhs, p_rhs1)); + task.add_constraint(align(p_rhs1, p_rhs2)); + runtime->submit(std::move(task)); + } else if (blas_op == BlasOperationMM) { + auto rhs1_modes_copy{rhs1_modes}; + auto rhs2_modes_copy{rhs2_modes}; + // (cb/bc),(ab/ba)->ac --> (ab/ba),(cb/bc)->ac + if (!_is_in_vector(rhs1_modes, lhs_modes[0])) { + std::swap(rhs1_s, rhs2_s); + std::swap(rhs1_modes_copy, rhs2_modes_copy); + } + assert(_is_in_vector(rhs1_modes_copy, lhs_modes[0]) && + _is_in_vector(rhs2_modes_copy, lhs_modes[1])); + // ba,?->ac --> ab,?->ac + if (lhs_modes[0] != rhs1_modes_copy[0]) { + rhs1_s = rhs1_s.transpose({1, 0}); + } + // ?,cb->ac --> ?,bc->ac + if (lhs_modes[1] != rhs2_modes_copy[1]) { + rhs2_s = rhs2_s.transpose({1, 0}); + } + auto m = shape()[0]; + auto n = shape()[1]; + auto k = rhs1.shape()[1]; + assert(m == rhs1.shape()[0]); + assert(n == rhs2.shape()[1]); + assert(k == rhs2.shape()[0]); + + dot_MM(rhs1_s, rhs2_s); + } else { + assert(false); + } + return; + } + + lhs_s = _alphabetical_transpose(lhs_s, lhs_modes); + rhs1_s = _alphabetical_transpose(rhs1_s, rhs1_modes); + rhs2_s = _alphabetical_transpose(rhs2_s, rhs2_modes); + + std::vector lhs_dim_mask; + std::vector rhs1_dim_mask; + std::vector rhs2_dim_mask; + + std::vector sorted_modes; + for (std::map::const_iterator it = mode2extent.begin(); it != mode2extent.end(); + it++) { + sorted_modes.push_back(it->first); + } + std::sort(sorted_modes.begin(), sorted_modes.end()); + for (int i = 0; i < sorted_modes.size(); i++) { + auto dim = i; + auto mode = sorted_modes[i]; + auto extent = mode2extent.at(mode); + + auto add_mode = [&](legate::LogicalStore store, + const std::vector& modes, + std::vector& dim_mask) { + if (!_is_in_vector(modes, mode)) { + dim_mask.emplace_back(false); + return store.promote(dim, extent); + } else { + dim_mask.emplace_back(true); + return store; + } + }; + + lhs_s = add_mode(lhs_s, lhs_modes, lhs_dim_mask); + rhs1_s = add_mode(rhs1_s, rhs1_modes, rhs1_dim_mask); + rhs2_s = add_mode(rhs2_s, rhs2_modes, rhs2_dim_mask); + } + + assert(lhs_s.extents().data() == rhs1_s.extents().data()); + assert(lhs_s.extents().data() == rhs2_s.extents().data()); + + auto task = runtime->create_task(CuPyNumericOpCode::CUPYNUMERIC_CONTRACT); + auto redop = get_reduction_op(UnaryRedCode::SUM); + auto p_lhs = task.add_reduction(lhs_s, redop); + auto p_rhs1 = task.add_input(rhs1_s); + auto p_rhs2 = task.add_input(rhs2_s); + + auto add_scalar_arg = [&](std::vector dim_mask) { + auto arg_type = legate::fixed_array_type(legate::bool_(), dim_mask.size()); + task.add_scalar_arg(legate::Scalar(arg_type, dim_mask.data(), true)); + }; + add_scalar_arg(lhs_dim_mask); + add_scalar_arg(rhs1_dim_mask); + add_scalar_arg(rhs2_dim_mask); + + task.add_constraint(align(p_lhs, p_rhs1)); + task.add_constraint(align(p_rhs1, p_rhs2)); + runtime->submit(std::move(task)); +} + legate::LogicalStore NDArray::get_store() { return store_; } legate::LogicalStore NDArray::broadcast(const std::vector& shape, diff --git a/src/cupynumeric/ndarray.h b/src/cupynumeric/ndarray.h index 87b2c5292f..7602166c88 100644 --- a/src/cupynumeric/ndarray.h +++ b/src/cupynumeric/ndarray.h @@ -75,7 +75,6 @@ class NDArray { void unary_reduction(int32_t op_code, NDArray input); void eye(int32_t k); void trilu(NDArray rhs, int32_t k, bool lower); - void dot(NDArray rhs1, NDArray rhs2); void arange(Scalar start, Scalar stop, Scalar step); std::vector nonzero(); NDArray unique(); @@ -114,6 +113,12 @@ class NDArray { NDArray squeeze( std::optional const>> axis = std::nullopt) const; void where(NDArray rhs1, NDArray rhs2, NDArray rhs3); + void contract(const std::vector& lhs_modes, + NDArray rhs1, + const std::vector& rhs1_modes, + NDArray rhs2, + const std::vector& rhs2_modes, + const std::map& mode2extent); public: NDArray as_type(const legate::Type& type) const; @@ -163,6 +168,13 @@ class NDArray { std::optional out = std::nullopt); void _fill(legate::LogicalStore const& value); + void dot_MM(legate::LogicalStore& rhs1_store, legate::LogicalStore& rhs2_store); + void _verify_mode_extent(const std::map& mode2extent, + const std::vector& modes, + const std::vector& shape) const; + legate::LogicalStore _alphabetical_transpose(legate::LogicalStore store, + const std::vector& modes) const; + public: static legate::Library get_library(); diff --git a/src/cupynumeric/operators.cc b/src/cupynumeric/operators.cc index e20ee156c6..a51414ef63 100644 --- a/src/cupynumeric/operators.cc +++ b/src/cupynumeric/operators.cc @@ -260,37 +260,6 @@ NDArray tril(NDArray rhs, int32_t k) { return trilu(rhs, k, true); } NDArray triu(NDArray rhs, int32_t k) { return trilu(rhs, k, false); } -NDArray dot(NDArray rhs1, NDArray rhs2) -{ - if (rhs1.dim() != 2 || rhs2.dim() != 2) { - LEGATE_ABORT("cupynumeric::dot only supports matrices now"); - } - - auto& rhs1_shape = rhs1.shape(); - auto& rhs2_shape = rhs2.shape(); - - if (rhs1_shape[1] != rhs2_shape[0]) { - LEGATE_ABORT("Incompatible matrices: (", - rhs1_shape[0], - ", ", - rhs1_shape[1], - ") x (", - rhs2_shape[0], - ", ", - rhs2_shape[1], - ")"); - } - - auto runtime = CuPyNumericRuntime::get_runtime(); - std::vector shape; - shape.push_back(rhs1_shape[0]); - shape.push_back(rhs2_shape[1]); - - auto out = runtime->create_array(std::move(shape), rhs1.type()); - out.dot(std::move(rhs1), std::move(rhs2)); - return out; -} - NDArray all(NDArray input, std::vector axis, std::optional out, @@ -611,4 +580,102 @@ legate::Type find_common_type(const std::vector& arrays) return max_type; } +NDArray dot(NDArray a, NDArray b) +{ + if (a.type().code() != b.type().code()) { + throw std::invalid_argument("Type of array a is not equal to type of array b"); + } + + if (a.dim() == 0 || b.dim() == 0) { + return multiply(a, b); + } + + auto modes_vec = dot_modes(a.dim(), b.dim()); + auto a_modes = modes_vec[0]; + auto b_modes = modes_vec[1]; + auto c_modes = modes_vec[2]; + std::map mode2extent; + for (uint i = 0; i < a_modes.size(); i++) { + auto mode = a_modes[i]; + auto extent = a.shape()[i]; + auto search = mode2extent.find(mode); + if (search != mode2extent.end()) { + if (search->second != extent) { + throw std::invalid_argument("Incompatible sizes between matched dimensions"); + } + } + mode2extent[mode] = extent; + } + for (uint i = 0; i < b_modes.size(); i++) { + auto mode = b_modes[i]; + auto extent = b.shape()[i]; + auto search = mode2extent.find(mode); + if (search != mode2extent.end()) { + if (search->second != extent) { + throw std::invalid_argument("Incompatible sizes between matched dimensions"); + } + } + mode2extent[mode] = extent; + } + + std::vector c_shape; + for (auto mode : c_modes) { + auto search = mode2extent.find(mode); + if (search != mode2extent.end()) { + c_shape.push_back(mode2extent[mode]); + } else { + c_shape.push_back(1); + } + } + + auto runtime = CuPyNumericRuntime::get_runtime(); + auto c_dtype = a.type(); + auto c = runtime->create_array(std::move(c_shape), c_dtype); + // Perform operation + c.contract(modes_vec[2], std::move(a), modes_vec[0], std::move(b), modes_vec[1], mode2extent); + return c; +} + +template +std::vector merge_vectors( + std::vector a, std::vector b, uint start_a, uint end_a, uint start_b, uint end_b) +{ + std::vector out; + for (uint i = start_a; i < end_a; i++) { + out.push_back(a[i]); + } + for (uint i = start_b; i < end_b; i++) { + out.push_back(b[i]); + } + return out; +} + +std::vector> dot_modes(uint a_ndim, uint b_ndim) +{ + std::vector a_modes, b_modes, out_modes; + assert(a_ndim < 26); + assert(b_ndim < 26); + for (uint i = 0; i < a_ndim; i++) { + a_modes.push_back('a' + i); + } + for (uint i = 0; i < b_ndim; i++) { + b_modes.push_back('A' + i); + } + if (a_ndim == 0) { + out_modes = b_modes; + } else if (b_ndim == 0) { + out_modes = a_modes; + } else if (b_ndim == 1) { + b_modes[b_modes.size() - 1] = a_modes[a_modes.size() - 1]; + for (int i = 0; i < a_modes.size() - 1; i++) { + out_modes.push_back(a_modes[i]); + } + } else { + b_modes[b_modes.size() - 2] = a_modes[a_modes.size() - 1]; + out_modes = merge_vectors(a_modes, b_modes, 0, a_modes.size() - 1, 0, b_modes.size() - 2); + out_modes.push_back(b_modes[b_modes.size() - 1]); + } + return {a_modes, b_modes, out_modes}; +} + } // namespace cupynumeric diff --git a/src/cupynumeric/operators.h b/src/cupynumeric/operators.h index 0e562c0675..84263a6210 100644 --- a/src/cupynumeric/operators.h +++ b/src/cupynumeric/operators.h @@ -37,7 +37,7 @@ NDArray add(NDArray rhs1, NDArray rhs2, std::optional out = std::nullop NDArray multiply(NDArray rhs1, NDArray rhs2, std::optional out = std::nullopt); -NDArray dot(NDArray rhs1, NDArray rhs2); +NDArray dot(NDArray a, NDArray b); NDArray negative(NDArray input); @@ -196,5 +196,21 @@ std::vector vec_convert(const std::vector& input) return output; } +template +bool _is_in_vector(const std::vector& vec, T item) +{ + if (std::find(vec.begin(), vec.end(), item) != vec.end()) { + return true; + } else { + return false; + } +} + +std::vector> dot_modes(uint a_ndim, uint b_ndim); + +template +std::vector merge_vectors( + std::vector a, std::vector b, uint start_a, uint end_a, uint start_b, uint end_b); + } // namespace cupynumeric #include "cupynumeric/operators.inl" diff --git a/tests/cpp/integration/common_utils.h b/tests/cpp/integration/common_utils.h index 47e84b1c11..605ce5f623 100644 --- a/tests/cpp/integration/common_utils.h +++ b/tests/cpp/integration/common_utils.h @@ -193,4 +193,22 @@ std::vector as_type_vector(std::vector const& in) return out; } +template +std::vector to_vector(NDArray a) +{ + std::vector result; + if (a.size() == 0) { + return result; + } + if (a.dim() > 1) { + a = a._wrap(a.size()); + } + auto acc = a.get_read_accessor(); + result.reserve(a.size()); + for (size_t i = 0; i < a.size(); ++i) { + result.push_back(acc[i]); + } + return result; +} + } // namespace cupynumeric diff --git a/tests/cpp/integration/test_dot.cc b/tests/cpp/integration/test_dot.cc index b13b029f2c..cc4a87cb03 100644 --- a/tests/cpp/integration/test_dot.cc +++ b/tests/cpp/integration/test_dot.cc @@ -14,8 +14,6 @@ * */ -#include -#include #include #include @@ -24,50 +22,331 @@ #include "cupynumeric.h" #include "common_utils.h" -using namespace cupynumeric; +std::vector calc_c_shape_scalar(const std::vector& a_shape, + const std::vector& b_shape) +{ + assert(a_shape.size() == 0 || b_shape.size() == 0); + + std::vector c_shape; + if (a_shape.size() == 0 && b_shape.size() == 0) { + c_shape = {}; + } else { + c_shape = a_shape.size() == 0 ? b_shape : a_shape; + } + return c_shape; +} + +template +std::vector calc_result_scalar(const std::vector& vec_a, + const std::vector& a_shape, + const std::vector& vec_b, + const std::vector& b_shape, + const std::vector& c_shape) +{ + if (a_shape.size() == 0) { + assert(vec_a.size() == 1); + } + if (b_shape.size() == 0) { + assert(vec_b.size() == 1); + } + + std::vector vec_c; + if (a_shape.size() == 0 && b_shape.size() == 0) { + vec_c.push_back(vec_a[0] * vec_b[0]); + return vec_c; + } + + auto vec_scalar = a_shape.size() == 0 ? vec_a : vec_b; + auto vec_non_scalar = a_shape.size() == 0 ? vec_b : vec_a; + for (int i = 0; i < vec_non_scalar.size(); i++) { + vec_c.push_back(vec_non_scalar[i] * vec_scalar[0]); + } + return vec_c; +} + +std::vector calc_c_shape_b_is_vector(const std::vector& a_shape, + const std::vector& b_shape) +{ + assert(a_shape[a_shape.size() - 1] == b_shape[b_shape.size() - 1]); + std::vector c_shape; + int a_size_in_c = 1; + for (int i = 0; i < a_shape.size() - 1; i++) { + c_shape.push_back(a_shape[i]); + a_size_in_c *= a_shape[i]; + } + return c_shape; +} + +template +std::vector calc_result_b_is_vector(const std::vector& vec_a, + const std::vector& a_shape, + const std::vector& vec_b, + const std::vector& b_shape, + const std::vector& c_shape) +{ + int a_size_in_c = 1; + for (int i = 0; i < a_shape.size() - 1; i++) { + a_size_in_c *= a_shape[i]; + } + + std::vector vec_c; + auto x = a_shape[a_shape.size() - 1]; + int offset_a = 0; + for (int i_a = 0; i_a < a_size_in_c; i_a++) { + T sum = 0; + for (int j = 0; j < x; j++) { + sum += vec_a[offset_a + j] * vec_b[j]; + } + vec_c.push_back(sum); + offset_a += x; + } + return vec_c; +} + +std::vector calc_c_shape_contract(const std::vector& a_shape, + const std::vector& b_shape) +{ + std::vector c_shape = {}; + for (int i = 0; i < a_shape.size() - 1; i++) { + c_shape.push_back(a_shape[i]); + } + for (int i = 0; i < b_shape.size() - 2; i++) { + c_shape.push_back(b_shape[i]); + } + c_shape.push_back(b_shape[b_shape.size() - 1]); + return c_shape; +} + +template +std::vector calc_result_contract(const std::vector& vec_a, + const std::vector& a_shape, + const std::vector& vec_b, + const std::vector& b_shape, + const std::vector& c_shape) +{ + int a_size_in_c = 1, b_size_in_c = 1; + for (int i = 0; i < a_shape.size() - 1; i++) { + a_size_in_c *= a_shape[i]; + } + for (int i = 0; i < b_shape.size() - 2; i++) { + b_size_in_c *= b_shape[i]; + } + b_size_in_c *= b_shape[b_shape.size() - 1]; + + std::vector vec_c; + assert(a_shape[a_shape.size() - 1] == b_shape[b_shape.size() - 2]); -namespace { + auto x = a_shape[a_shape.size() - 1]; + auto m = b_shape[b_shape.size() - 1]; + int offset_a = 0; + for (int i_a = 0; i_a < a_size_in_c; i_a++) { + int offset_b = 0, b_i = 0; + for (int i_b = 0; i_b < b_size_in_c; i_b++) { + T sum = 0; + for (int j = 0; j < x; j++) { + sum += vec_a[offset_a + j] * vec_b[offset_b + j * m]; + } + vec_c.push_back(sum); + if (++b_i >= m) { + offset_b = offset_b + m * x - m + 1; + b_i = 0; + } else { + offset_b += 1; + } + } + offset_a += x; + } + return vec_c; +} template -auto test_standard(uint64_t m, uint64_t n, uint64_t k, legate::Type leg_type) +void verify_dot_output(cupynumeric::NDArray A, cupynumeric::NDArray B, cupynumeric::NDArray C) { - std::vector data_a(m * k); - std::vector data_b(n * k); - std::iota(data_a.begin(), data_a.end(), 0); - std::iota(data_b.begin(), data_b.end(), 0.0); + auto vec_a = cupynumeric::to_vector(A); + auto vec_b = cupynumeric::to_vector(B); + std::vector vec_c; + auto a_shape = A.shape(); + auto b_shape = B.shape(); + std::vector vec_c_shape = {}; - auto A = cupynumeric::zeros({m, k}, leg_type); - auto B = cupynumeric::zeros({k, n}, leg_type); + if (A.dim() == 0 || B.dim() == 0) { + vec_c_shape = calc_c_shape_scalar(a_shape, b_shape); + vec_c = calc_result_scalar(vec_a, a_shape, vec_b, b_shape, vec_c_shape); + } else if (B.dim() == 1 && A.dim() >= 1) { + vec_c_shape = calc_c_shape_b_is_vector(a_shape, b_shape); + vec_c = calc_result_b_is_vector(vec_a, a_shape, vec_b, b_shape, vec_c_shape); + } else { + vec_c_shape = calc_c_shape_contract(a_shape, b_shape); + vec_c = calc_result_contract(vec_a, a_shape, vec_b, b_shape, vec_c_shape); + } - assign_values_to_array(A, data_a.data(), m * k); - assign_values_to_array(B, data_b.data(), n * k); + auto leg_type = legate::primitive_type(legate::type_code_of_v); + if (leg_type == legate::float32() || leg_type == legate::float64()) { + double abs_error = 1.e-4; + cupynumeric::check_array_near(C, vec_c, vec_c_shape, abs_error); + } +} + +template +void test_contract_full(std::vector a_shape, std::vector b_shape) +{ + auto leg_type = legate::primitive_type(legate::type_code_of_v); + if (leg_type == legate::float64()) { + auto A = a_shape.size() == 0 ? cupynumeric::mk_array({10}) : cupynumeric::random(a_shape); + auto B = b_shape.size() == 0 ? cupynumeric::mk_array({10}) : cupynumeric::random(b_shape); + auto C = cupynumeric::dot(A, B); + verify_dot_output(A, B, C); + } else { + auto A = a_shape.size() == 0 ? cupynumeric::mk_array({10}) + : cupynumeric::random(a_shape).as_type(leg_type); + auto B = b_shape.size() == 0 ? cupynumeric::mk_array({10}) + : cupynumeric::random(b_shape).as_type(leg_type); + auto C = cupynumeric::dot(A, B); + if (leg_type == legate::float32()) { + verify_dot_output(A, B, C); + } + } +} + +template +void test_contract_standard(std::vector a_shape, std::vector b_shape) +{ + auto A = + a_shape.size() == 0 + ? cupynumeric::mk_array({10}) + : cupynumeric::random(a_shape).as_type(legate::primitive_type(legate::type_code_of_v)); + auto B = + b_shape.size() == 0 + ? cupynumeric::mk_array({10}) + : cupynumeric::random(b_shape).as_type(legate::primitive_type(legate::type_code_of_v)); + auto C = cupynumeric::dot(A, B); - auto C = dot(A, B); - std::vector exp_shape = {m, n}; + auto leg_type = legate::primitive_type(legate::type_code_of_v); + std::vector vec_c_shape = {}; + if (A.dim() == 0 || B.dim() == 0) { + vec_c_shape = calc_c_shape_scalar(a_shape, b_shape); + } else if (B.dim() == 1 && A.dim() >= 1) { + vec_c_shape = calc_c_shape_b_is_vector(a_shape, b_shape); + } else { + vec_c_shape = calc_c_shape_contract(a_shape, b_shape); + } EXPECT_EQ(C.type(), leg_type); - EXPECT_EQ(C.shape(), exp_shape); + EXPECT_EQ(C.shape(), vec_c_shape); } -TEST(Dot, Standard) +template +void test_contract_full_all(void) +{ + test_contract_full({}, {}); // 0x0 + test_contract_full({}, + { + 3, + }); // 0x1 + test_contract_full({3}, {}); // 1x0 + test_contract_full( + { + 3, + }, + { + 3, + }); // 1x1 + test_contract_full( + { + 3, + }, + {3, 4}); // 1x2 + test_contract_full({2, 3}, + { + 3, + }); // 2x1 + test_contract_full( + { + 3, + }, + {2, 3, 4}); // 1x3 + test_contract_full({2, 3, 4}, + { + 4, + }); // 3x1 + test_contract_full({2, 3}, {3, 4}); // 2x2 + test_contract_full({2, 3}, {5, 3, 4}); // 2x3 + test_contract_full({2, 3, 4}, {4, 2}); // 3x2 +#if LEGATE_MAX_DIM >= 5 + test_contract_full({2, 3, 4}, {5, 4, 7}); // 3x3 + test_contract_full({2, 3}, {5, 2, 3, 4}); // 2x4 +#endif +} + +template +void test_contract_standard_all(void) { - test_standard(124, 95, 30, legate::float32()); - test_standard(124, 95, 30, legate::float64()); + test_contract_standard({}, {}); // 0x0 + test_contract_standard({}, + { + 3, + }); // 0x1 + test_contract_standard({3}, {}); // 1x0 + test_contract_standard( + { + 3, + }, + { + 3, + }); // 1x1 + test_contract_standard( + { + 3, + }, + {3, 4}); // 1x2 + test_contract_standard({2, 3}, + { + 3, + }); // 2x1 + test_contract_standard( + { + 3, + }, + {2, 3, 4}); // 1x3 + test_contract_standard({2, 3, 4}, + { + 4, + }); // 3x1 + test_contract_standard({2, 3}, {3, 4}); // 2x2 + test_contract_standard({2, 3}, {5, 3, 4}); // 2x3 + test_contract_standard({2, 3, 4}, {4, 2}); // 3x2 +#if LEGATE_MAX_DIM >= 5 + test_contract_standard({2, 3, 4}, {5, 4, 7}); // 3x3 + test_contract_standard({2, 3}, {5, 2, 3, 4}); // 2x4 +#endif } -TEST(Dot, Complex) +TEST(Dot, MMStandard) { - test_standard>(124, 95, 30, legate::complex64()); - test_standard>(124, 95, 30, legate::complex128()); + test_contract_full({124, 30}, {30, 95}); + test_contract_full({124, 30}, {30, 95}); } -TEST(Dot, Large) +TEST(Dot, MMComplex) { - // activate tiling (m,n) and/or batching (k) - test_standard(513, 12, 4, legate::float32()); - test_standard(12, 518, 30, legate::float32()); - test_standard(513, 513, 30, legate::float32()); - test_standard(512, 512, 4097, legate::float64()); - test_standard(1024, 1024, 4097, legate::float64()); + test_contract_standard>({124, 30}, {30, 95}); + test_contract_standard>({124, 30}, {30, 95}); } -} // namespace +TEST(Dot, MMLarge) +{ + test_contract_full({513, 4}, {4, 12}); + test_contract_full({12, 30}, {30, 518}); + test_contract_full({513, 30}, {30, 513}); + test_contract_full({512, 4097}, {4097, 512}); + // test_contract_full({1024, 4097}, {4097, 1024}); # There is not enough space because + // Legate is reserving 67125248 of the available 268435456 bytes (minus the eager pool allocation) + // for the following LogicalStores +} + +TEST(Dot, AllFloat) { test_contract_full_all(); } + +TEST(Dot, AllDouble) { test_contract_full_all(); } + +TEST(Dot, AllComplex64) { test_contract_standard_all>(); } + +TEST(Dot, AllComplex128) { test_contract_standard_all>(); } \ No newline at end of file From 196dfe092e2cab1ba85e65ebceae8663a0c10b92 Mon Sep 17 00:00:00 2001 From: Parag Kulkarni Date: Tue, 3 Dec 2024 11:15:37 +0530 Subject: [PATCH 373/462] Update legate-gh-ci version (#523) * Verify _use_skip_existing_ in legate-gh-ci * Update to legate-gh-ci to v1.22 * s/legate.internal/legate.core.internal * Check latest * Revert version git tag * Space * Cleanup --- .github/workflows/gh-build-and-test.yml | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/.github/workflows/gh-build-and-test.yml b/.github/workflows/gh-build-and-test.yml index 6959393c76..a079356e1d 100644 --- a/.github/workflows/gh-build-and-test.yml +++ b/.github/workflows/gh-build-and-test.yml @@ -48,13 +48,13 @@ jobs: needs: setup-build name: "Build (${{ inputs.platform }}, ${{ inputs.target-device }}, ${{ inputs.build-type }}, Python ${{ inputs.python-version }})" uses: - nv-legate/legate-gh-ci/.github/workflows/gh-build.yml@v1.21 + nv-legate/legate-gh-ci/.github/workflows/gh-build.yml@v1.22 with: build-mode: "" build-type: ${{ inputs.build-type }} client-repo: ${{ github.event.repository.name }} dependencies-file: "cmake/versions.json" - legate-gh-ci-tag: "v1.21" + legate-gh-ci-tag: "v1.22" network: "ucx" platform: ${{ inputs.platform }} python-version: ${{ inputs.python-version }} @@ -68,13 +68,13 @@ jobs: needs: setup-build name: "Check if legate.internal nightly exists for SHA specified in versions.json (${{ inputs.platform }}, ${{ inputs.target-device }}, ${{ inputs.build-type }})" uses: - nv-legate/legate-gh-ci/.github/workflows/gh-check-if-nightly-exists-for-all-dependencies.yml@v1.21 + nv-legate/legate-gh-ci/.github/workflows/gh-check-if-nightly-exists-for-all-dependencies.yml@v1.22 with: build-mode: "" build-type: ${{ inputs.build-type }} client-repo: ${{ github.event.repository.name }} dependencies-file: "cmake/versions.json" - legate-gh-ci-tag: "v1.21" + legate-gh-ci-tag: "v1.22" network: "ucx" platform: ${{ inputs.platform }} python-version: ${{ inputs.python-version }} @@ -88,12 +88,12 @@ jobs: if: ${{ github.repository_owner == 'nv-legate' && contains(github.workflow, 'release') && inputs.upload-enabled == true }} name: Upload package to Server uses: - nv-legate/legate-gh-ci/.github/workflows/gh-upload.yml@v1.21 + nv-legate/legate-gh-ci/.github/workflows/gh-upload.yml@v1.22 with: build-mode: "" build-type: ${{ inputs.build-type }} client-repo: ${{ github.event.repository.name }} - legate-gh-ci-tag: "v1.21" + legate-gh-ci-tag: "v1.22" name: Upload package to Server network: "ucx" pkgSubString: "cupynumeric-" @@ -176,13 +176,13 @@ jobs: matrix: ${{fromJson(needs.setup-test.outputs.matrix)}} uses: - nv-legate/legate-gh-ci/.github/workflows/gh-test-within-container.yml@v1.21 + nv-legate/legate-gh-ci/.github/workflows/gh-test-within-container.yml@v1.22 with: build-mode: "" build-type: ${{ inputs.build-type }} client-repo: ${{ github.event.repository.name }} has-gpu: ${{ matrix.runner.type == 'gpu' }} - legate-gh-ci-tag: "v1.21" + legate-gh-ci-tag: "v1.22" name: ${{ matrix.test-config.name }} network: "ucx" platform: ${{ inputs.platform }} @@ -198,12 +198,12 @@ jobs: name: Update Test status on Server if: ${{ (github.repository_owner == 'nv-legate') && contains(github.workflow, 'Nightly') && (inputs.upload-enabled == true) }} uses: - nv-legate/legate-gh-ci/.github/workflows/gh-upload.yml@v1.21 + nv-legate/legate-gh-ci/.github/workflows/gh-upload.yml@v1.22 with: build-mode: "" build-type: ${{ inputs.build-type }} client-repo: ${{ github.event.repository.name }} - legate-gh-ci-tag: "v1.21" + legate-gh-ci-tag: "v1.22" name: UpdateTestStatus network: "ucx" pkgSubString: "cupynumeric-" From 471385ad588d91abbe210d88048718cb13af7559 Mon Sep 17 00:00:00 2001 From: Mark Vaz Date: Tue, 3 Dec 2024 09:06:39 -0500 Subject: [PATCH 374/462] Re-updating the repo name because the repo name needs to be updated with the sha (#527) * update repo name for legate * update repo name for legate * update repo name for legate * update repo name for legate --- cmake/versions.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmake/versions.json b/cmake/versions.json index b207a9c9f9..cf38e55be7 100644 --- a/cmake/versions.json +++ b/cmake/versions.json @@ -10,7 +10,7 @@ "git_url" : "git@github.com:nv-legate/legate.internal.git", "git_shallow": false, "always_download": false, - "git_tag" : "388ffdb0a25152e06a1fb02335339115cc11543d" + "git_tag" : "703abcd17f3784b50e80ea3bf15f157e85172a89" } } } From 7d614e2ca99d7accb5729c52a6eebdccdd7abede Mon Sep 17 00:00:00 2001 From: Bryan Van de Ven Date: Tue, 3 Dec 2024 15:48:59 -0800 Subject: [PATCH 375/462] fix comparison table API scraping (#530) --- cupynumeric/_sphinxext/_comparison_config.py | 11 ++-- cupynumeric/_sphinxext/_comparison_util.py | 37 +++++++++---- cupynumeric/_utils/coverage.py | 34 +++++++----- tests/integration/test_array_fallback.py | 2 +- tests/integration/test_fallback.py | 2 +- .../_sphinxext/test__comparison_util.py | 33 +++++++----- .../unit/cupynumeric/_utils/test_coverage.py | 53 ++++++++++++------- 7 files changed, 111 insertions(+), 61 deletions(-) diff --git a/cupynumeric/_sphinxext/_comparison_config.py b/cupynumeric/_sphinxext/_comparison_config.py index 3623a61a0c..911e487973 100644 --- a/cupynumeric/_sphinxext/_comparison_config.py +++ b/cupynumeric/_sphinxext/_comparison_config.py @@ -83,12 +83,11 @@ class SectionConfig: UFUNCS = (numpy.ufunc,) NUMPY_CONFIGS = [ - SectionConfig("Module-Level", None, types=FUNCTIONS), - SectionConfig("Ufuncs", None, types=UFUNCS), - SectionConfig("Multi-Dimensional Array", "ndarray", types=METHODS), - SectionConfig("Linear Algebra", "linalg", types=FUNCTIONS), - SectionConfig("Discrete Fourier Transform", "fft", types=FUNCTIONS), - SectionConfig("Random Sampling", "random", types=FUNCTIONS), + SectionConfig("Module-Level", None), + SectionConfig("Multi-Dimensional Array", "ndarray"), + SectionConfig("Linear Algebra", "linalg"), + SectionConfig("Discrete Fourier Transform", "fft"), + SectionConfig("Random Sampling", "random"), ] CONVOLVE = ("convolve", "correlate") diff --git a/cupynumeric/_sphinxext/_comparison_util.py b/cupynumeric/_sphinxext/_comparison_util.py index a76d1cf676..5992fac72b 100644 --- a/cupynumeric/_sphinxext/_comparison_util.py +++ b/cupynumeric/_sphinxext/_comparison_util.py @@ -16,9 +16,9 @@ from dataclasses import dataclass from types import ModuleType -from typing import TYPE_CHECKING, Any, Iterable, Iterator, Type +from typing import TYPE_CHECKING, Any, Iterable, Iterator -from .._utils.coverage import is_implemented, is_multi, is_single +from .._utils.coverage import is_implemented, is_multi, is_single, is_wrapped from ._comparison_config import MISSING_NP_REFS, SKIP if TYPE_CHECKING: @@ -73,17 +73,31 @@ def _lgref(name: str, obj: Any, implemented: bool) -> str: return f":{role}:`{full_name}`" -def filter_names( +def filter_wrapped_names( obj: Any, - types: tuple[Type[Any], ...] | None = None, + *, skip: Iterable[str] = (), ) -> Iterator[str]: names = (n for n in dir(obj)) # every name in the module or class + names = ( + n for n in names if is_wrapped(getattr(obj, n)) + ) # that is wrapped + names = (n for n in names if n not in skip) # except the ones we skip + names = (n for n in names if not n.startswith("_")) # or any private names + return names + + +def filter_type_names( + obj: Any, + *, + skip: Iterable[str] = (), +) -> Iterator[str]: + names = (n for n in dir(obj)) # every name in the module or class + names = ( + n for n in names if isinstance(getattr(obj, n), type) + ) # that is a type (class, dtype, etc) names = (n for n in names if n not in skip) # except the ones we skip names = (n for n in names if not n.startswith("_")) # or any private names - if types: - # optionally filtered by type - names = (n for n in names if isinstance(getattr(obj, n), types)) return names @@ -123,9 +137,14 @@ def generate_section(config: SectionConfig) -> SectionDetail: names: Iterable[str] if config.names: - names = config.names + names = set(config.names) else: - names = filter_names(np_obj, config.types, skip=SKIP) + wrapped_names = filter_wrapped_names(lg_obj, skip=SKIP) + type_names = filter_type_names(lg_obj, skip=SKIP) + names = set(wrapped_names) | set(type_names) + + # we can omit anything that isn't in np namespace to begin with + names = {n for n in names if n in dir(np_obj)} items = [get_item(name, np_obj, lg_obj) for name in names] diff --git a/cupynumeric/_utils/coverage.py b/cupynumeric/_utils/coverage.py index d0d0f0eba4..ca2b4cfe83 100644 --- a/cupynumeric/_utils/coverage.py +++ b/cupynumeric/_utils/coverage.py @@ -17,7 +17,7 @@ import warnings from dataclasses import dataclass from functools import WRAPPER_ASSIGNMENTS, wraps -from types import BuiltinFunctionType, FunctionType, ModuleType +from types import BuiltinFunctionType, ModuleType from typing import Any, Callable, Container, Iterable, Mapping, Protocol, cast from legate.core import track_provenance @@ -69,7 +69,7 @@ class CuWrapperMetadata: class CuWrapped(AnyCallable, Protocol): - _cupynumeric: CuWrapperMetadata + _cupynumeric_metadata: CuWrapperMetadata __wrapped__: AnyCallable __name__: str __qualname__: str @@ -116,7 +116,7 @@ def wrapper(*args: Any, **kwargs: Any) -> Any: multi = "Multiple GPUs" in (getattr(func, "__doc__", None) or "") single = "Single GPU" in (getattr(func, "__doc__", None) or "") or multi - wrapper._cupynumeric = CuWrapperMetadata( + wrapper._cupynumeric_metadata = CuWrapperMetadata( implemented=True, single=single, multi=multi ) @@ -185,7 +185,7 @@ def wrapper(*args: Any, **kwargs: Any) -> Any: -------- {name} """ - wrapper._cupynumeric = CuWrapperMetadata(implemented=False) + wrapper._cupynumeric_metadata = CuWrapperMetadata(implemented=False) return wrapper @@ -242,7 +242,7 @@ def clone_module( # Only need to wrap things that are in the origin module to begin with if attr not in origin_module.__dict__: continue - if isinstance(value, (FunctionType, lgufunc)) or ( + if should_wrap(value) or ( include_builtin_function_type and isinstance(value, BuiltinFunctionType) ): @@ -273,7 +273,7 @@ def clone_module( from numpy import ufunc as npufunc for attr, value in missing.items(): - if isinstance(value, (FunctionType, npufunc)) or ( + if should_wrap(value) or ( include_builtin_function_type and isinstance(value, BuiltinFunctionType) ): @@ -300,13 +300,19 @@ def clone_module( def should_wrap(obj: object) -> bool: - # custom callables, e.g. cython used in np2, do not inherit anything. See - # https://github.com/nv-legate/cupynumeric.internal/issues/179#issuecomment-2423813051 + from numpy import ufunc as npufunc + + from .._ufunc.ufunc import ufunc as lgufunc + + # Custom callables, e.g. cython functions used in np2, do not inherit + # anything, so we check callable() instead (and include the __get__/__set__ + # checks to filter out classes). OTOH ufuncs need to be checked specially + # because they do not have __get__. return ( callable(obj) and hasattr(obj, "__get__") and not hasattr(obj, "__set__") - ) + ) or isinstance(obj, (lgufunc, npufunc)) def clone_class( @@ -363,13 +369,17 @@ def _clone_class(cls: type) -> type: return _clone_class +def is_wrapped(obj: Any) -> bool: + return hasattr(obj, "_cupynumeric_metadata") + + def is_implemented(obj: Any) -> bool: - return hasattr(obj, "_cupynumeric") and obj._cupynumeric.implemented + return is_wrapped(obj) and obj._cupynumeric_metadata.implemented def is_single(obj: Any) -> bool: - return hasattr(obj, "_cupynumeric") and obj._cupynumeric.single + return is_wrapped(obj) and obj._cupynumeric_metadata.single def is_multi(obj: Any) -> bool: - return hasattr(obj, "_cupynumeric") and obj._cupynumeric.multi + return is_wrapped(obj) and obj._cupynumeric_metadata.multi diff --git a/tests/integration/test_array_fallback.py b/tests/integration/test_array_fallback.py index 7ab602ebc9..a20032ad6c 100644 --- a/tests/integration/test_array_fallback.py +++ b/tests/integration/test_array_fallback.py @@ -27,7 +27,7 @@ def test_unimplemented_method_self_fallback(): # to verify a behaviour of unimplemented ndarray method wrappers. If std # becomes implemeneted in the future, this assertion will start to fail, # and a new (unimplemented) ndarray method should be found to replace it - assert not ones.std._cupynumeric.implemented + assert not ones.std._cupynumeric_metadata.implemented ones.std() diff --git a/tests/integration/test_fallback.py b/tests/integration/test_fallback.py index 2971b78358..7d71f6aa0f 100644 --- a/tests/integration/test_fallback.py +++ b/tests/integration/test_fallback.py @@ -33,7 +33,7 @@ def test_ufunc(): # methods. If logical_and.accumulate becomes implemented in the future, # this assertion will start to fail, and a new (unimplemented) ufunc method # should be found to replace it - assert not num.logical_and.accumulate._cupynumeric.implemented + assert not num.logical_and.accumulate._cupynumeric_metadata.implemented out_num = num.logical_and.accumulate(in_num) out_np = np.logical_and.accumulate(in_np) diff --git a/tests/unit/cupynumeric/_sphinxext/test__comparison_util.py b/tests/unit/cupynumeric/_sphinxext/test__comparison_util.py index bf9746d505..97d2880ac7 100644 --- a/tests/unit/cupynumeric/_sphinxext/test__comparison_util.py +++ b/tests/unit/cupynumeric/_sphinxext/test__comparison_util.py @@ -35,26 +35,31 @@ def test_get_namespaces_attr(attr): assert res[1] is getattr(num, attr) +class _wrapped: + class _cupynumeric_metadata: + implemeneted = True + + class _TestObj: - a = 10 - b = 10.2 - c = "str" - _priv = "priv" + a = _wrapped + b = _wrapped + c = _wrapped + d = 10 + _priv = _wrapped -class Test_filter_names: +class Test_filter_wrapped_names: def test_default(self): - assert set(m.filter_names(_TestObj)) == {"a", "b", "c"} - - def test_types(self): - assert set(m.filter_names(_TestObj, (int,))) == {"a"} - assert set(m.filter_names(_TestObj, (int, str))) == {"a", "c"} - assert set(m.filter_names(_TestObj, (int, set))) == {"a"} - assert set(m.filter_names(_TestObj, (set,))) == set() + assert set(m.filter_wrapped_names(_TestObj())) == {"a", "b", "c"} def test_skip(self): - assert set(m.filter_names(_TestObj, skip=("a",))) == {"b", "c"} - assert set(m.filter_names(_TestObj, skip=("a", "c"))) == {"b"} + assert set(m.filter_wrapped_names(_TestObj(), skip=("a",))) == { + "b", + "c", + } + assert set(m.filter_wrapped_names(_TestObj(), skip=("a", "c"))) == { + "b" + } if __name__ == "__main__": diff --git a/tests/unit/cupynumeric/_utils/test_coverage.py b/tests/unit/cupynumeric/_utils/test_coverage.py index c92e44c4ce..b58f8940b6 100644 --- a/tests/unit/cupynumeric/_utils/test_coverage.py +++ b/tests/unit/cupynumeric/_utils/test_coverage.py @@ -117,6 +117,23 @@ def __call__(self, a: int, b: int) -> int: _test_ufunc = _Test_ufunc() +class Test_helpers: + def test_is_wrapped_true(self) -> None: + wrapped = m.implemented(_test_func, "foo", "_test_func") + assert m.is_wrapped(wrapped) + + def test_is_wrapped_false(self) -> None: + assert not m.is_wrapped(10) + + def test_is_implemented_true(self) -> None: + wrapped = m.implemented(_test_func, "foo", "_test_func") + assert m.is_implemented(wrapped) + + def test_is_implemented_false(self) -> None: + wrapped = m.unimplemented(_test_func, "foo", "_test_func") + assert not m.is_implemented(wrapped) + + class Test_implemented: @patch("cupynumeric.runtime.record_api_call") def test_reporting_True_func( @@ -347,10 +364,10 @@ def test_report_coverage_True(self) -> None: assert _Dest.attr2 == 30 assert _Dest.function1.__wrapped__ is _OriginMod.function1 - assert not _Dest.function1._cupynumeric.implemented + assert not _Dest.function1._cupynumeric_metadata.implemented assert _Dest.function2.__wrapped__ - assert _Dest.function2._cupynumeric.implemented + assert _Dest.function2._cupynumeric_metadata.implemented assert not hasattr(_Dest.extra, "_cupynumeric") @@ -373,10 +390,10 @@ def test_report_coverage_False(self) -> None: assert _Dest.attr2 == 30 assert _Dest.function1.__wrapped__ is _OriginMod.function1 - assert not _Dest.function1._cupynumeric.implemented + assert not _Dest.function1._cupynumeric_metadata.implemented assert _Dest.function2.__wrapped__ - assert _Dest.function2._cupynumeric.implemented + assert _Dest.function2._cupynumeric_metadata.implemented assert not hasattr(_Dest.extra, "_cupynumeric") @@ -428,10 +445,10 @@ def test_report_coverage_True(self) -> None: assert _Test_ndarray.attr2 == 30 assert _Test_ndarray.foo.__wrapped__ is _Orig_ndarray.foo - assert not _Test_ndarray.foo._cupynumeric.implemented + assert not _Test_ndarray.foo._cupynumeric_metadata.implemented assert _Test_ndarray.bar.__wrapped__ - assert _Test_ndarray.bar._cupynumeric.implemented + assert _Test_ndarray.bar._cupynumeric_metadata.implemented assert not hasattr(_Test_ndarray.extra, "_cupynumeric") @@ -447,10 +464,10 @@ def test_report_coverage_False(self) -> None: assert _Test_ndarray.attr2 == 30 assert _Test_ndarray.foo.__wrapped__ is _Orig_ndarray.foo - assert not _Test_ndarray.foo._cupynumeric.implemented + assert not _Test_ndarray.foo._cupynumeric_metadata.implemented assert _Test_ndarray.bar.__wrapped__ - assert _Test_ndarray.bar._cupynumeric.implemented + assert _Test_ndarray.bar._cupynumeric_metadata.implemented assert not hasattr(_Test_ndarray.extra, "_cupynumeric") @@ -469,32 +486,32 @@ def test_ufunc_methods_binary() -> None: # reduce is implemented assert np.add.reduce.__wrapped__ - assert np.add.reduce._cupynumeric.implemented + assert np.add.reduce._cupynumeric_metadata.implemented # the rest are not assert np.add.reduceat.__wrapped__ - assert not np.add.reduceat._cupynumeric.implemented + assert not np.add.reduceat._cupynumeric_metadata.implemented assert np.add.outer.__wrapped__ - assert not np.add.outer._cupynumeric.implemented + assert not np.add.outer._cupynumeric_metadata.implemented assert np.add.at.__wrapped__ - assert not np.add.at._cupynumeric.implemented + assert not np.add.at._cupynumeric_metadata.implemented assert np.add.accumulate.__wrapped__ - assert not np.add.accumulate._cupynumeric.implemented + assert not np.add.accumulate._cupynumeric_metadata.implemented def test_ufunc_methods_unary() -> None: import cupynumeric as np assert np.negative.reduce.__wrapped__ - assert not np.negative.reduce._cupynumeric.implemented + assert not np.negative.reduce._cupynumeric_metadata.implemented assert np.negative.reduceat.__wrapped__ - assert not np.negative.reduceat._cupynumeric.implemented + assert not np.negative.reduceat._cupynumeric_metadata.implemented assert np.negative.outer.__wrapped__ - assert not np.negative.outer._cupynumeric.implemented + assert not np.negative.outer._cupynumeric_metadata.implemented assert np.negative.at.__wrapped__ - assert not np.negative.at._cupynumeric.implemented + assert not np.negative.at._cupynumeric_metadata.implemented assert np.negative.accumulate.__wrapped__ - assert not np.negative.accumulate._cupynumeric.implemented + assert not np.negative.accumulate._cupynumeric_metadata.implemented if __name__ == "__main__": From 7e7fe8dee092f4c05c4ada1e871a0af43178bce6 Mon Sep 17 00:00:00 2001 From: Irina Demeshko Date: Wed, 11 Dec 2024 19:26:34 -0800 Subject: [PATCH 376/462] Reducing number of dimensions used in tests +fix ROUND logic for __half (#520) --- cmake/versions.json | 2 +- src/cupynumeric/unary/unary_op_util.h | 3 +- tests/integration/test_advanced_indexing.py | 15 ++- tests/integration/test_amax_amin.py | 34 +++++- tests/integration/test_angle.py | 11 +- tests/integration/test_arg_reduce.py | 33 +++++- tests/integration/test_atleast_nd.py | 12 ++- tests/integration/test_bits.py | 77 ++++++++++++-- tests/integration/test_broadcast.py | 11 +- tests/integration/test_clip.py | 22 +++- tests/integration/test_compress.py | 33 +++++- tests/integration/test_diag_indices.py | 22 +++- tests/integration/test_dot.py | 24 ++++- tests/integration/test_fill_diagonal.py | 10 +- tests/integration/test_flip.py | 10 +- tests/integration/test_gradient.py | 42 +++++++- tests/integration/test_index_routines.py | 25 ++++- tests/integration/test_indices.py | 48 ++++++++- tests/integration/test_inner.py | 24 ++++- tests/integration/test_intra_array_copy.py | 11 +- tests/integration/test_item.py | 12 ++- tests/integration/test_itemset.py | 12 ++- tests/integration/test_logical.py | 12 ++- tests/integration/test_logical_reduction.py | 11 +- tests/integration/test_matmul.py | 22 +++- tests/integration/test_median.py | 26 ++++- tests/integration/test_moveaxis.py | 10 +- tests/integration/test_nan_reduction.py | 108 ++++++++++++++++++-- tests/integration/test_nanarg_reduction.py | 68 ++++++++++-- tests/integration/test_ndim.py | 12 ++- tests/integration/test_nonzero.py | 3 +- tests/integration/test_norm.py | 41 +++++++- tests/integration/test_put.py | 22 +++- tests/integration/test_put_along_axis.py | 2 +- tests/integration/test_putmask.py | 11 +- tests/integration/test_repeat.py | 33 +++++- tests/integration/test_round.py | 50 +++++++-- tests/integration/test_searchsorted.py | 12 ++- tests/integration/test_setflags.py | 44 +++++++- tests/integration/test_singleton_access.py | 8 +- tests/integration/test_take.py | 13 ++- tests/integration/test_take_along_axis.py | 11 +- tests/integration/test_tensordot.py | 22 +++- tests/integration/test_trace.py | 10 +- tests/integration/test_unique.py | 11 +- tests/integration/test_unravel_index.py | 26 ++++- 46 files changed, 968 insertions(+), 113 deletions(-) diff --git a/cmake/versions.json b/cmake/versions.json index cf38e55be7..0a58acb01d 100644 --- a/cmake/versions.json +++ b/cmake/versions.json @@ -10,7 +10,7 @@ "git_url" : "git@github.com:nv-legate/legate.internal.git", "git_shallow": false, "always_download": false, - "git_tag" : "703abcd17f3784b50e80ea3bf15f157e85172a89" + "git_tag" : "d357867e1b8b2b26613bb31addb460a340df14aa" } } } diff --git a/src/cupynumeric/unary/unary_op_util.h b/src/cupynumeric/unary/unary_op_util.h index 30a9b8272c..bb9d617c48 100644 --- a/src/cupynumeric/unary/unary_op_util.h +++ b/src/cupynumeric/unary/unary_op_util.h @@ -998,7 +998,8 @@ struct UnaryOp { if (decimals < 0) { return static_cast<__half>(rint(static_cast(x) / factor) * factor); } else { - return static_cast<__half>(rint(static_cast(x) * factor) / factor); + __half fh = static_cast<__half>(factor); + return static_cast<__half>(rint(static_cast(x * fh)) / factor); } } diff --git a/tests/integration/test_advanced_indexing.py b/tests/integration/test_advanced_indexing.py index c55d781da1..9cf63c3d30 100644 --- a/tests/integration/test_advanced_indexing.py +++ b/tests/integration/test_advanced_indexing.py @@ -120,7 +120,13 @@ def mk_deferred_array(lib, shape): def gen_args(): - for arr_ndim in range(1, LEGATE_MAX_DIM + 1): + for arr_ndim in ( + 1, + 2, + 3, + 4, + LEGATE_MAX_DIM - 1, + ): for idx_ndim in range(1, arr_ndim + 1): for zero_dim in range(arr_ndim): yield arr_ndim, idx_ndim, zero_dim @@ -919,7 +925,12 @@ def test(): # we do less than LEGATE_MAX_DIM becasue the dimension will be increased by # 1 when passig 2d index array - for ndim in range(2, LEGATE_MAX_DIM): + for ndim in ( + 2, + 3, + 4, + LEGATE_MAX_DIM - 1, + ): a_shape = tuple(np.random.randint(2, 5) for i in range(ndim)) np_array = mk_seq_array(np, a_shape) num_array = mk_seq_array(num, a_shape) diff --git a/tests/integration/test_amax_amin.py b/tests/integration/test_amax_amin.py index fac1f959bb..3314e156d7 100755 --- a/tests/integration/test_amax_amin.py +++ b/tests/integration/test_amax_amin.py @@ -24,7 +24,17 @@ @pytest.mark.parametrize("initial", (None, -2, 0, 0.5, 2)) @pytest.mark.parametrize("keepdims", [True, False]) -@pytest.mark.parametrize("ndim", range(LEGATE_MAX_DIM + 1)) +@pytest.mark.parametrize( + "ndim", + ( + 0, + 1, + 2, + 3, + 4, + LEGATE_MAX_DIM, + ), +) @pytest.mark.parametrize("func_name", FUNCS) def test_basic(func_name, ndim, keepdims, initial): shape = (5,) * ndim @@ -71,7 +81,17 @@ def test_src_dt(func_name, keepdims, src_dt): @pytest.mark.parametrize("initial", (None, -2, 0, 0.5, 2)) @pytest.mark.parametrize("keepdims", [True, False]) -@pytest.mark.parametrize("ndim", range(LEGATE_MAX_DIM + 1)) +@pytest.mark.parametrize( + "ndim", + ( + 0, + 1, + 2, + 3, + 4, + LEGATE_MAX_DIM, + ), +) @pytest.mark.parametrize("func_name", FUNCS) def test_axis(func_name, ndim, keepdims, initial): shape = (5,) * ndim @@ -151,7 +171,15 @@ def test_out_dim1(func_name, keepdims): @pytest.mark.parametrize("initial", (None, -2, 0, 0.5, 2)) @pytest.mark.parametrize("keepdims", [True, False]) -@pytest.mark.parametrize("ndim", range(2, LEGATE_MAX_DIM + 1)) +@pytest.mark.parametrize( + "ndim", + ( + 2, + 3, + 4, + LEGATE_MAX_DIM, + ), +) @pytest.mark.parametrize("func_name", FUNCS) def test_out(func_name, ndim, keepdims, initial): shape = (5,) * ndim diff --git a/tests/integration/test_angle.py b/tests/integration/test_angle.py index 02570f13f9..0190e44ea5 100644 --- a/tests/integration/test_angle.py +++ b/tests/integration/test_angle.py @@ -51,7 +51,16 @@ def test_pure_real_and_imaginary(self): assert np.array_equal(num.angle(5j), np.angle(5j)) assert np.array_equal(num.angle(-5j), np.angle(-5j)) - @pytest.mark.parametrize("ndim", range(1, LEGATE_MAX_DIM + 1)) + @pytest.mark.parametrize( + "ndim", + ( + 1, + 2, + 3, + 4, + LEGATE_MAX_DIM, + ), + ) @pytest.mark.parametrize("in_type", (int, float, complex)) @pytest.mark.parametrize("deg", (False, True)) def test_basic(self, ndim, in_type, deg): diff --git a/tests/integration/test_arg_reduce.py b/tests/integration/test_arg_reduce.py index c4ef2d2e39..54f407bb43 100644 --- a/tests/integration/test_arg_reduce.py +++ b/tests/integration/test_arg_reduce.py @@ -104,7 +104,17 @@ class TestArgMaxAndArgMin: """ @pytest.mark.parametrize("func_name", ARG_FUNCS) - @pytest.mark.parametrize("ndim", range(LEGATE_MAX_DIM + 1)) + @pytest.mark.parametrize( + "ndim", + ( + 0, + 1, + 2, + 3, + 4, + LEGATE_MAX_DIM, + ), + ) @pytest.mark.parametrize("keepdims", [True, False]) def test_argmax_and_argmin_basic(self, func_name, ndim, keepdims): shape = (5,) * ndim @@ -120,7 +130,16 @@ def test_argmax_and_argmin_basic(self, func_name, ndim, keepdims): ) @pytest.mark.parametrize("func_name", ARG_FUNCS) - @pytest.mark.parametrize("ndim", range(1, LEGATE_MAX_DIM + 1)) + @pytest.mark.parametrize( + "ndim", + ( + 1, + 2, + 3, + 4, + LEGATE_MAX_DIM, + ), + ) @pytest.mark.parametrize("keepdims", [True, False]) def test_argmax_and_argmin_axis(self, func_name, ndim, keepdims): shape = (5,) * ndim @@ -175,7 +194,15 @@ def test_argmax_and_argmin_out_1dim(self, func_name, keepdims): assert np.array_equal(res_np, res_num) @pytest.mark.parametrize("func_name", ARG_FUNCS) - @pytest.mark.parametrize("ndim", range(2, LEGATE_MAX_DIM + 1)) + @pytest.mark.parametrize( + "ndim", + ( + 2, + 3, + 4, + LEGATE_MAX_DIM, + ), + ) @pytest.mark.parametrize("keepdims", [True, False]) def test_argmax_and_argmin_out(self, func_name, ndim, keepdims): shape = (5,) * ndim diff --git a/tests/integration/test_atleast_nd.py b/tests/integration/test_atleast_nd.py index 5f679c4809..f4d8180a7d 100644 --- a/tests/integration/test_atleast_nd.py +++ b/tests/integration/test_atleast_nd.py @@ -22,7 +22,17 @@ DIM = 10 -SIZE_CASES = list((DIM,) * ndim for ndim in range(LEGATE_MAX_DIM + 1)) +SIZE_CASES = list( + (DIM,) * ndim + for ndim in ( + 0, + 1, + 2, + 3, + 4, + LEGATE_MAX_DIM, + ) +) SIZE_CASES += [ (0,), # empty array diff --git a/tests/integration/test_bits.py b/tests/integration/test_bits.py index 612c584d48..030b65d67c 100644 --- a/tests/integration/test_bits.py +++ b/tests/integration/test_bits.py @@ -64,7 +64,16 @@ def test_arr(self, arr, dtype, bitorder): out_num = num.packbits(in_num, bitorder=bitorder) assert np.array_equal(out_np, out_num) - @pytest.mark.parametrize("ndim", range(1, LEGATE_MAX_DIM + 1)) + @pytest.mark.parametrize( + "ndim", + ( + 1, + 2, + 3, + 4, + LEGATE_MAX_DIM, + ), + ) @pytest.mark.parametrize("dtype", ("B", "i", "?")) @pytest.mark.parametrize("bitorder", ("little", "big")) def test_common(self, ndim, dtype, bitorder): @@ -76,7 +85,16 @@ def test_common(self, ndim, dtype, bitorder): out_num = num.packbits(in_num, bitorder=bitorder) assert np.array_equal(out_np, out_num) - @pytest.mark.parametrize("ndim", range(1, LEGATE_MAX_DIM + 1)) + @pytest.mark.parametrize( + "ndim", + ( + 1, + 2, + 3, + 4, + LEGATE_MAX_DIM, + ), + ) @pytest.mark.parametrize("dtype", ("B", "i", "?")) @pytest.mark.parametrize("bitorder", ("little", "big")) def test_axis(self, ndim, dtype, bitorder): @@ -145,7 +163,16 @@ def test_arr(self, arr, bitorder): out_num = num.unpackbits(in_num, bitorder=bitorder) assert np.array_equal(out_np, out_num) - @pytest.mark.parametrize("ndim", range(1, LEGATE_MAX_DIM + 1)) + @pytest.mark.parametrize( + "ndim", + ( + 1, + 2, + 3, + 4, + LEGATE_MAX_DIM, + ), + ) @pytest.mark.parametrize("bitorder", ("little", "big")) def test_common(self, ndim, bitorder): shape = (5,) * ndim @@ -157,7 +184,16 @@ def test_common(self, ndim, bitorder): assert np.array_equal(out_np, out_num) @pytest.mark.parametrize("count", (-9, 4, -1, 0, 4, 8, 9)) - @pytest.mark.parametrize("ndim", range(1, LEGATE_MAX_DIM + 1)) + @pytest.mark.parametrize( + "ndim", + ( + 1, + 2, + 3, + 4, + LEGATE_MAX_DIM, + ), + ) @pytest.mark.parametrize("bitorder", ("little", "big")) def test_count(self, ndim, count, bitorder): shape = (5,) * ndim @@ -168,7 +204,16 @@ def test_count(self, ndim, count, bitorder): out_num = num.unpackbits(in_num, count=count, bitorder=bitorder) assert np.array_equal(out_np, out_num) - @pytest.mark.parametrize("ndim", range(1, LEGATE_MAX_DIM + 1)) + @pytest.mark.parametrize( + "ndim", + ( + 1, + 2, + 3, + 4, + LEGATE_MAX_DIM, + ), + ) @pytest.mark.parametrize("bitorder", ("little", "big")) def test_axis(self, ndim, bitorder): shape = (5,) * ndim @@ -180,7 +225,16 @@ def test_axis(self, ndim, bitorder): out_num = num.unpackbits(in_num, axis=axis, bitorder=bitorder) assert np.array_equal(out_np, out_num) - @pytest.mark.parametrize("ndim", range(1, LEGATE_MAX_DIM + 1)) + @pytest.mark.parametrize( + "ndim", + ( + 1, + 2, + 3, + 4, + LEGATE_MAX_DIM, + ), + ) @pytest.mark.parametrize("bitorder", ("little", "big")) @pytest.mark.parametrize("count", (-2, 0, 2, 5)) def test_axis_count(self, ndim, bitorder, count): @@ -198,7 +252,16 @@ def test_axis_count(self, ndim, bitorder, count): assert np.array_equal(out_np, out_num) -@pytest.mark.parametrize("ndim", range(1, LEGATE_MAX_DIM + 1)) +@pytest.mark.parametrize( + "ndim", + ( + 1, + 2, + 3, + 4, + LEGATE_MAX_DIM, + ), +) @pytest.mark.parametrize("bitorder", ("little", "big")) @pytest.mark.parametrize("dtype", ("B", "i", "?")) def test_pack_unpack(ndim, bitorder, dtype): diff --git a/tests/integration/test_broadcast.py b/tests/integration/test_broadcast.py index d95e3975c5..d192a4e958 100644 --- a/tests/integration/test_broadcast.py +++ b/tests/integration/test_broadcast.py @@ -19,7 +19,7 @@ import cupynumeric as num -DIM_CASES = [5, 40] +DIM_CASES = [5, 20] def _check_result(print_msg, err_arrs): @@ -135,8 +135,13 @@ def _check(*args, params: list, routine: str): def gen_shapes(dim): base = (dim,) result = [base] - for i in range(1, LEGATE_MAX_DIM): - base = base + (1,) if i % 2 == 0 else base + (dim,) + for i in ( + 1, + 2, + 3, + LEGATE_MAX_DIM, + ): + base = base + (1,) if (i % 2 == 0 and i <= 4) else base + (dim,) result.append(base) return result diff --git a/tests/integration/test_clip.py b/tests/integration/test_clip.py index bbbf5ac59b..b22d005691 100644 --- a/tests/integration/test_clip.py +++ b/tests/integration/test_clip.py @@ -171,7 +171,16 @@ def test_out_np_array(): assert np.array_equal(out_np, out_num) -@pytest.mark.parametrize("ndim", range(1, LEGATE_MAX_DIM + 1)) +@pytest.mark.parametrize( + "ndim", + ( + 1, + 2, + 3, + 4, + LEGATE_MAX_DIM, + ), +) def test_basic(ndim): shape = (5,) * ndim np_arr = mk_seq_array(np, shape) @@ -185,7 +194,16 @@ def test_basic(ndim): assert np.array_equal(res_num, res_np) -@pytest.mark.parametrize("ndim", range(1, LEGATE_MAX_DIM + 1)) +@pytest.mark.parametrize( + "ndim", + ( + 1, + 2, + 3, + 4, + LEGATE_MAX_DIM, + ), +) def test_out(ndim): shape = (5,) * ndim np_arr = mk_seq_array(np, shape) diff --git a/tests/integration/test_compress.py b/tests/integration/test_compress.py index 2e6aa4e334..c702fec5c2 100644 --- a/tests/integration/test_compress.py +++ b/tests/integration/test_compress.py @@ -120,7 +120,16 @@ def test_bool_condition(): assert np.array_equal(res_num, res_np) -@pytest.mark.parametrize("ndim", range(1, LEGATE_MAX_DIM + 1)) +@pytest.mark.parametrize( + "ndim", + ( + 1, + 2, + 3, + 4, + LEGATE_MAX_DIM, + ), +) def test_ndim_basic(ndim): shape = (5,) * ndim np_arr = mk_seq_array(np, shape) @@ -134,7 +143,16 @@ def test_ndim_basic(ndim): assert np.array_equal(res_num, res_np) -@pytest.mark.parametrize("ndim", range(1, LEGATE_MAX_DIM + 1)) +@pytest.mark.parametrize( + "ndim", + ( + 1, + 2, + 3, + 4, + LEGATE_MAX_DIM, + ), +) def test_ndim_axis(ndim): shape = (5,) * ndim np_arr = mk_seq_array(np, shape) @@ -149,7 +167,16 @@ def test_ndim_axis(ndim): assert np.array_equal(res_num, res_np) -@pytest.mark.parametrize("ndim", range(1, LEGATE_MAX_DIM + 1)) +@pytest.mark.parametrize( + "ndim", + ( + 1, + 2, + 3, + 4, + LEGATE_MAX_DIM, + ), +) def test_ndim_out(ndim): shape = (5,) * ndim np_arr = mk_seq_array(np, shape) diff --git a/tests/integration/test_diag_indices.py b/tests/integration/test_diag_indices.py index 386ca43734..5e29c7e5e6 100644 --- a/tests/integration/test_diag_indices.py +++ b/tests/integration/test_diag_indices.py @@ -27,7 +27,17 @@ def test_diag_indices_default_ndim(n): assert np.array_equal(a_np, a_num) -@pytest.mark.parametrize("ndim", range(0, LEGATE_MAX_DIM + 1)) +@pytest.mark.parametrize( + "ndim", + ( + 0, + 1, + 2, + 3, + 4, + LEGATE_MAX_DIM, + ), +) def test_diag_indices_basic(ndim): a_np = np.diag_indices(10, ndim) a_num = num.diag_indices(10, ndim) @@ -72,7 +82,15 @@ def test_none_ndim(self): @pytest.mark.parametrize("size", [(5,), (0,)], ids=str) -@pytest.mark.parametrize("ndim", range(2, LEGATE_MAX_DIM + 1)) +@pytest.mark.parametrize( + "ndim", + ( + 2, + 3, + 4, + LEGATE_MAX_DIM, + ), +) def test_diag_indices_from_basic(size, ndim): shape = size * ndim a = np.ones(shape, dtype=int) diff --git a/tests/integration/test_dot.py b/tests/integration/test_dot.py index c45e1f0ae8..f58c8e43a9 100644 --- a/tests/integration/test_dot.py +++ b/tests/integration/test_dot.py @@ -22,8 +22,28 @@ from cupynumeric._utils.linalg import dot_modes -@pytest.mark.parametrize("b_ndim", range(LEGATE_MAX_DIM + 1)) -@pytest.mark.parametrize("a_ndim", range(LEGATE_MAX_DIM + 1)) +@pytest.mark.parametrize( + "b_ndim", + ( + 0, + 1, + 2, + 3, + 4, + LEGATE_MAX_DIM, + ), +) +@pytest.mark.parametrize( + "a_ndim", + ( + 0, + 1, + 2, + 3, + 4, + LEGATE_MAX_DIM, + ), +) def test_dot(a_ndim, b_ndim): name = f"dot({a_ndim} x {b_ndim})" modes = dot_modes(a_ndim, b_ndim) diff --git a/tests/integration/test_fill_diagonal.py b/tests/integration/test_fill_diagonal.py index 0ce8eb3dd5..2af81ba563 100644 --- a/tests/integration/test_fill_diagonal.py +++ b/tests/integration/test_fill_diagonal.py @@ -34,7 +34,15 @@ def test_wrap(wrap): assert np.array_equal(np_array, num_array) -@pytest.mark.parametrize("ndim", range(2, LEGATE_MAX_DIM + 1)) +@pytest.mark.parametrize( + "ndim", + ( + 2, + 3, + 4, + LEGATE_MAX_DIM, + ), +) @pytest.mark.parametrize("val_shape", ((0,), (3,), (6,), (2, 2), (2, 2, 6))) @pytest.mark.parametrize("wrap", WRAP, ids=str) def test_basic(ndim, val_shape, wrap): diff --git a/tests/integration/test_flip.py b/tests/integration/test_flip.py index 6df8b7962e..1a83edfba1 100644 --- a/tests/integration/test_flip.py +++ b/tests/integration/test_flip.py @@ -148,7 +148,15 @@ def test_wrong_dim(self): @pytest.mark.parametrize("func_name", FLIP_FUNCS) -@pytest.mark.parametrize("ndim", range(2, LEGATE_MAX_DIM + 1)) +@pytest.mark.parametrize( + "ndim", + ( + 2, + 3, + 4, + LEGATE_MAX_DIM, + ), +) def test_max_dims(func_name, ndim): func_np = getattr(np, func_name) func_num = getattr(num, func_name) diff --git a/tests/integration/test_gradient.py b/tests/integration/test_gradient.py index 786d579f63..7fd2498d7a 100644 --- a/tests/integration/test_gradient.py +++ b/tests/integration/test_gradient.py @@ -38,7 +38,16 @@ def test_gradient_1d(): assert np.allclose(res_np, res_cn) -@pytest.mark.parametrize("ndim", range(1, LEGATE_MAX_DIM + 1)) +@pytest.mark.parametrize( + "ndim", + ( + 1, + 2, + 3, + 4, + LEGATE_MAX_DIM, + ), +) @pytest.mark.parametrize("edge_order", [1, 2]) def test_nd_arrays(ndim, edge_order): shape = (5,) * ndim @@ -53,7 +62,16 @@ def test_nd_arrays(ndim, edge_order): assert np.allclose(res_np, res_cn) -@pytest.mark.parametrize("ndim", range(1, LEGATE_MAX_DIM + 1)) +@pytest.mark.parametrize( + "ndim", + ( + 1, + 2, + 3, + 4, + LEGATE_MAX_DIM, + ), +) @pytest.mark.parametrize("varargs", [0.5, 1, 2, 0.3, 0]) def test_scalar_varargs(ndim, varargs): shape = (5,) * ndim @@ -66,7 +84,15 @@ def test_scalar_varargs(ndim, varargs): assert np.allclose(res_np, res_cn, equal_nan=True) -@pytest.mark.parametrize("ndim", range(2, LEGATE_MAX_DIM + 1)) +@pytest.mark.parametrize( + "ndim", + ( + 2, + 3, + 4, + LEGATE_MAX_DIM, + ), +) def test_array_1d_varargs(ndim): shape = (5,) * ndim size = prod(shape) @@ -79,7 +105,15 @@ def test_array_1d_varargs(ndim): assert np.allclose(res_np, res_cn) -@pytest.mark.parametrize("ndim", range(2, LEGATE_MAX_DIM + 1)) +@pytest.mark.parametrize( + "ndim", + ( + 2, + 3, + 4, + LEGATE_MAX_DIM, + ), +) def test_list_of_axes(ndim): shape = (5,) * ndim size = prod(shape) diff --git a/tests/integration/test_index_routines.py b/tests/integration/test_index_routines.py index 526bab1b89..1242b47e79 100644 --- a/tests/integration/test_index_routines.py +++ b/tests/integration/test_index_routines.py @@ -87,7 +87,16 @@ def test_choose_2d(): ) -@pytest.mark.parametrize("ndim", range(1, LEGATE_MAX_DIM + 1)) +@pytest.mark.parametrize( + "ndim", + ( + 1, + 2, + 3, + 4, + LEGATE_MAX_DIM, + ), +) def test_choose_target_ndim(ndim): tgt_shape = (5,) * ndim # try various shapes that broadcast to the target shape @@ -343,7 +352,12 @@ def test_select(size): def test_select_maxdim(): - for ndim in range(2, LEGATE_MAX_DIM + 1): + for ndim in ( + 2, + 3, + 4, + LEGATE_MAX_DIM, + ): a_shape = tuple(np.random.randint(1, 9) for i in range(ndim)) arr = mk_seq_array(np, a_shape) condlist_np = list() @@ -405,7 +419,12 @@ def test_diagonal(): assert np.array_equal(ad.diagonal(-1, 0, 2), num_ad.diagonal(-1, 0, 2)) # test diagonal - for ndim in range(2, LEGATE_MAX_DIM + 1): + for ndim in ( + 2, + 3, + 4, + LEGATE_MAX_DIM, + ): a_shape = tuple(np.random.randint(1, 9) for i in range(ndim)) np_array = mk_seq_array(np, a_shape) num_array = mk_seq_array(num, a_shape) diff --git a/tests/integration/test_indices.py b/tests/integration/test_indices.py index 5ca02e3ba8..abb15370d8 100644 --- a/tests/integration/test_indices.py +++ b/tests/integration/test_indices.py @@ -75,7 +75,17 @@ def test_indices_zero(self, dimensions): assert np.array_equal(np_res, num_res) - @pytest.mark.parametrize("ndim", range(0, LEGATE_MAX_DIM)) + @pytest.mark.parametrize( + "ndim", + ( + 0, + 1, + 2, + 3, + 4, + LEGATE_MAX_DIM - 1, + ), + ) def test_indices_basic(self, ndim): dimensions = tuple(np.random.randint(1, 5) for _ in range(ndim)) @@ -83,7 +93,17 @@ def test_indices_basic(self, ndim): num_res = num.indices(dimensions) assert np.array_equal(np_res, num_res) - @pytest.mark.parametrize("ndim", range(0, LEGATE_MAX_DIM)) + @pytest.mark.parametrize( + "ndim", + ( + 0, + 1, + 2, + 3, + 4, + LEGATE_MAX_DIM - 1, + ), + ) def test_indices_dtype_none(self, ndim): dimensions = tuple(np.random.randint(1, 5) for _ in range(ndim)) @@ -91,14 +111,34 @@ def test_indices_dtype_none(self, ndim): num_res = num.indices(dimensions, dtype=None) assert np.array_equal(np_res, num_res) - @pytest.mark.parametrize("ndim", range(0, LEGATE_MAX_DIM)) + @pytest.mark.parametrize( + "ndim", + ( + 0, + 1, + 2, + 3, + 4, + LEGATE_MAX_DIM - 1, + ), + ) def test_indices_dtype_float(self, ndim): dimensions = tuple(np.random.randint(1, 5) for _ in range(ndim)) np_res = np.indices(dimensions, dtype=float) num_res = num.indices(dimensions, dtype=float) assert np.array_equal(np_res, num_res) - @pytest.mark.parametrize("ndim", range(0, LEGATE_MAX_DIM)) + @pytest.mark.parametrize( + "ndim", + ( + 0, + 1, + 2, + 3, + 4, + LEGATE_MAX_DIM - 1, + ), + ) def test_indices_sparse(self, ndim): dimensions = tuple(np.random.randint(1, 5) for _ in range(ndim)) np_res = np.indices(dimensions, sparse=True) diff --git a/tests/integration/test_inner.py b/tests/integration/test_inner.py index 01543d506a..0073dd045c 100644 --- a/tests/integration/test_inner.py +++ b/tests/integration/test_inner.py @@ -22,8 +22,28 @@ from cupynumeric._utils.linalg import inner_modes -@pytest.mark.parametrize("b_ndim", range(LEGATE_MAX_DIM + 1)) -@pytest.mark.parametrize("a_ndim", range(LEGATE_MAX_DIM + 1)) +@pytest.mark.parametrize( + "b_ndim", + ( + 0, + 1, + 2, + 3, + 4, + LEGATE_MAX_DIM, + ), +) +@pytest.mark.parametrize( + "a_ndim", + ( + 0, + 1, + 2, + 3, + 4, + LEGATE_MAX_DIM, + ), +) def test_inner(a_ndim, b_ndim): name = f"inner({a_ndim} x {b_ndim})" modes = inner_modes(a_ndim, b_ndim) diff --git a/tests/integration/test_intra_array_copy.py b/tests/integration/test_intra_array_copy.py index 241a4c06ea..861bb4fefc 100644 --- a/tests/integration/test_intra_array_copy.py +++ b/tests/integration/test_intra_array_copy.py @@ -137,7 +137,16 @@ def array_gen(lib, ndim): yield from full_overlap(lib, ndim) -@pytest.mark.parametrize("ndim", range(1, LEGATE_MAX_DIM + 1)) +@pytest.mark.parametrize( + "ndim", + ( + 1, + 2, + 3, + 4, + LEGATE_MAX_DIM, + ), +) def test_overlap(ndim): for np_arr, num_arr in zip(array_gen(np, ndim), array_gen(num, ndim)): assert np.array_equal(np_arr, num_arr) diff --git a/tests/integration/test_item.py b/tests/integration/test_item.py index 35cb43da18..8bba443916 100644 --- a/tests/integration/test_item.py +++ b/tests/integration/test_item.py @@ -76,7 +76,17 @@ def test_empty_no_item(): assert np.array_equal(res_np, res_num) -@pytest.mark.parametrize("ndim", range(LEGATE_MAX_DIM + 1)) +@pytest.mark.parametrize( + "ndim", + ( + 0, + 1, + 2, + 3, + 4, + LEGATE_MAX_DIM, + ), +) def test_ndim(ndim): shape = (4,) * ndim arr_num = num.random.randint(0, 3, size=shape) diff --git a/tests/integration/test_itemset.py b/tests/integration/test_itemset.py index 108198511d..32bf7be827 100644 --- a/tests/integration/test_itemset.py +++ b/tests/integration/test_itemset.py @@ -97,7 +97,17 @@ def test_tuple_out_of_index(): # dimension 0 with index 3 for a store of shape Shape((3,)) -@pytest.mark.parametrize("ndim", range(LEGATE_MAX_DIM + 1)) +@pytest.mark.parametrize( + "ndim", + ( + 0, + 1, + 2, + 3, + 4, + LEGATE_MAX_DIM, + ), +) def test_ndim(ndim): shape = (4,) * ndim arr_num = num.random.randint(0, 30, size=shape) diff --git a/tests/integration/test_logical.py b/tests/integration/test_logical.py index bcaa9a6ea0..a128e59225 100644 --- a/tests/integration/test_logical.py +++ b/tests/integration/test_logical.py @@ -77,7 +77,17 @@ def test_axis_tuple(func, axes): assert np.array_equal(out_np, out_num) -@pytest.mark.parametrize("ndim", range(LEGATE_MAX_DIM + 1)) +@pytest.mark.parametrize( + "ndim", + ( + 0, + 1, + 2, + 3, + 4, + LEGATE_MAX_DIM, + ), +) @pytest.mark.parametrize("func", FUNCTIONS) def test_nd_inputs(ndim, func): shape = (3,) * ndim diff --git a/tests/integration/test_logical_reduction.py b/tests/integration/test_logical_reduction.py index 452381656c..d49aea9bdd 100644 --- a/tests/integration/test_logical_reduction.py +++ b/tests/integration/test_logical_reduction.py @@ -36,7 +36,16 @@ def test_logical_reductions(axis): assert num.array_equal(out_num, out_np) -@pytest.mark.parametrize("ndim", range(1, LEGATE_MAX_DIM)) +@pytest.mark.parametrize( + "ndim", + ( + 1, + 2, + 3, + 4, + LEGATE_MAX_DIM - 1, + ), +) @pytest.mark.parametrize( "axis", [ diff --git a/tests/integration/test_matmul.py b/tests/integration/test_matmul.py index be7e4caf33..e56a0eeabf 100644 --- a/tests/integration/test_matmul.py +++ b/tests/integration/test_matmul.py @@ -28,8 +28,26 @@ from cupynumeric._utils.linalg import matmul_modes -@pytest.mark.parametrize("a_ndim", range(1, LEGATE_MAX_DIM + 1)) -@pytest.mark.parametrize("b_ndim", range(1, LEGATE_MAX_DIM + 1)) +@pytest.mark.parametrize( + "a_ndim", + ( + 1, + 2, + 3, + 4, + LEGATE_MAX_DIM, + ), +) +@pytest.mark.parametrize( + "b_ndim", + ( + 1, + 2, + 3, + 4, + LEGATE_MAX_DIM, + ), +) def test_function(a_ndim, b_ndim): name = f"matmul({a_ndim} x {b_ndim})" modes = matmul_modes(a_ndim, b_ndim) diff --git a/tests/integration/test_median.py b/tests/integration/test_median.py index c0c4315896..c0d6e855e2 100644 --- a/tests/integration/test_median.py +++ b/tests/integration/test_median.py @@ -56,7 +56,16 @@ def test_median_empty_array(self): class TestMedian: - @pytest.mark.parametrize("ndim", range(1, LEGATE_MAX_DIM)) + @pytest.mark.parametrize( + "ndim", + ( + 1, + 2, + 3, + 4, + LEGATE_MAX_DIM - 1, + ), + ) @pytest.mark.parametrize( "keepdims", ( @@ -65,7 +74,7 @@ class TestMedian: ), ) def test_median_basic(self, ndim, keepdims): - shape = np.random.randint(1, 6, ndim, dtype=int) + shape = np.random.randint(1, 4, ndim, dtype=int) size = 1 for dim in shape: size *= dim @@ -170,9 +179,18 @@ def test_median_overwrite_input(self): class TestNanmedian: - @pytest.mark.parametrize("ndim", range(1, LEGATE_MAX_DIM + 1)) + @pytest.mark.parametrize( + "ndim", + ( + 1, + 2, + 3, + 4, + LEGATE_MAX_DIM, + ), + ) def test_nanmedian_basic(self, ndim): - shape = np.random.randint(2, 6, ndim, dtype=int) + shape = np.random.randint(2, 5, ndim, dtype=int) size = 1 for dim in shape: size *= dim diff --git a/tests/integration/test_moveaxis.py b/tests/integration/test_moveaxis.py index 296f902d1a..8f9d5c9611 100644 --- a/tests/integration/test_moveaxis.py +++ b/tests/integration/test_moveaxis.py @@ -31,7 +31,15 @@ ) -@pytest.mark.parametrize("ndim", range(2, LEGATE_MAX_DIM + 1)) +@pytest.mark.parametrize( + "ndim", + ( + 2, + 3, + 4, + LEGATE_MAX_DIM, + ), +) @pytest.mark.parametrize("axes", AXES) def test_moveaxis(ndim, axes): source, destination = axes diff --git a/tests/integration/test_nan_reduction.py b/tests/integration/test_nan_reduction.py index a9b393c7ab..a5cfdd3578 100644 --- a/tests/integration/test_nan_reduction.py +++ b/tests/integration/test_nan_reduction.py @@ -28,7 +28,14 @@ EAGER_TEST = os.environ.get("CUPYNUMERIC_FORCE_THUNK", None) == "eager" -NDIMS = range(LEGATE_MAX_DIM + 1) +NDIMS = ( + 0, + 1, + 2, + 3, + 4, + LEGATE_MAX_DIM, +) DTYPE = ["l", "L", "f", "d", "h", "i", "H", "I", "?", "b", "B"] @@ -43,7 +50,16 @@ class TestNanReductions: """ @pytest.mark.parametrize("func_name", ("nansum", "nanprod")) - @pytest.mark.parametrize("ndim", range(1, LEGATE_MAX_DIM + 1)) + @pytest.mark.parametrize( + "ndim", + ( + 1, + 2, + 3, + 4, + LEGATE_MAX_DIM, + ), + ) @pytest.mark.parametrize("keepdims", [True, False]) def test_basic_nan_sum_prod(self, func_name, ndim, keepdims): """This test sets an element to NaN and checks if the output @@ -68,7 +84,16 @@ def test_basic_nan_sum_prod(self, func_name, ndim, keepdims): assert allclose(out_num, out_np, rtol=1e-4) @pytest.mark.parametrize("func_name", ("nanmin", "nanmax")) - @pytest.mark.parametrize("ndim", range(1, LEGATE_MAX_DIM + 1)) + @pytest.mark.parametrize( + "ndim", + ( + 1, + 2, + 3, + 4, + LEGATE_MAX_DIM, + ), + ) @pytest.mark.parametrize("keepdims", [True, False]) def test_basic_nan_min_max(self, func_name, ndim, keepdims): """This test sets an element to NaN and checks if the output @@ -93,7 +118,16 @@ def test_basic_nan_min_max(self, func_name, ndim, keepdims): assert np.array_equal(out_num, out_np) @pytest.mark.parametrize("func_name", NAN_FUNCS) - @pytest.mark.parametrize("ndim", range(1, LEGATE_MAX_DIM + 1)) + @pytest.mark.parametrize( + "ndim", + ( + 1, + 2, + 3, + 4, + LEGATE_MAX_DIM, + ), + ) def test_out(self, func_name, ndim): """This test checks that the out argument is updated with the output""" @@ -118,7 +152,16 @@ def test_out(self, func_name, ndim): assert allclose(out_num, out_np, rtol=1e-4) - @pytest.mark.parametrize("ndim", range(1, LEGATE_MAX_DIM + 1)) + @pytest.mark.parametrize( + "ndim", + ( + 1, + 2, + 3, + 4, + LEGATE_MAX_DIM, + ), + ) @pytest.mark.parametrize("dtype", (np.float32, np.float64)) @pytest.mark.parametrize("keepdims", [True, False]) def test_complex_dtype_nansum(self, ndim, dtype, keepdims): @@ -151,7 +194,16 @@ def test_complex_dtype_nansum(self, ndim, dtype, keepdims): assert allclose(out_num, out_np, rtol=1e-4) - @pytest.mark.parametrize("ndim", range(1, LEGATE_MAX_DIM + 1)) + @pytest.mark.parametrize( + "ndim", + ( + 1, + 2, + 3, + 4, + LEGATE_MAX_DIM, + ), + ) @pytest.mark.parametrize("keepdims", [True, False]) def test_complex_dtype_nanprod(self, ndim, keepdims): """This test checks if nanprod works as expected for complex @@ -230,7 +282,16 @@ def test_slice_nan_no_numpy_compat(self, identity, func_name): settings.numpy_compat.unset_value() @pytest.mark.parametrize("func_name", ("nanmin", "nanmax")) - @pytest.mark.parametrize("ndim", range(1, LEGATE_MAX_DIM + 1)) + @pytest.mark.parametrize( + "ndim", + ( + 1, + 2, + 3, + 4, + LEGATE_MAX_DIM, + ), + ) def test_all_nans_numpy_compat(self, ndim, func_name): """This test checks if we comply with the expected behavior when the array contains only NaNs. @@ -260,7 +321,16 @@ def test_all_nans_numpy_compat(self, ndim, func_name): ], ids=str, ) - @pytest.mark.parametrize("ndim", range(1, LEGATE_MAX_DIM + 1)) + @pytest.mark.parametrize( + "ndim", + ( + 1, + 2, + 3, + 4, + LEGATE_MAX_DIM, + ), + ) def test_all_nans_no_numpy_compat(self, ndim, identity, func_name): """This test checks if we comply with the expected behavior when the array contains only NaNs for nanmin and nanmax. @@ -278,7 +348,16 @@ def test_all_nans_no_numpy_compat(self, ndim, identity, func_name): settings.numpy_compat.unset_value() - @pytest.mark.parametrize("ndim", range(1, LEGATE_MAX_DIM + 1)) + @pytest.mark.parametrize( + "ndim", + ( + 1, + 2, + 3, + 4, + LEGATE_MAX_DIM, + ), + ) def test_all_nans_nanprod(self, ndim): shape = (3,) * ndim in_num = num.random.random(shape) @@ -310,7 +389,16 @@ def test_dtype_nansum(self, dtype) -> None: out_num = num.nansum(in_num) assert allclose(out_np, out_num) - @pytest.mark.parametrize("ndim", range(1, LEGATE_MAX_DIM + 1)) + @pytest.mark.parametrize( + "ndim", + ( + 1, + 2, + 3, + 4, + LEGATE_MAX_DIM, + ), + ) def test_all_nans_nansum(self, ndim): shape = (3,) * ndim in_num = num.random.random(shape) diff --git a/tests/integration/test_nanarg_reduction.py b/tests/integration/test_nanarg_reduction.py index c9bf247626..f57c2871c0 100644 --- a/tests/integration/test_nanarg_reduction.py +++ b/tests/integration/test_nanarg_reduction.py @@ -46,7 +46,16 @@ class TestNanArgReductions: """ @pytest.mark.parametrize("func_name", NAN_ARG_FUNCS) - @pytest.mark.parametrize("ndim", range(1, LEGATE_MAX_DIM + 1)) + @pytest.mark.parametrize( + "ndim", + ( + 1, + 2, + 3, + 4, + LEGATE_MAX_DIM, + ), + ) @pytest.mark.parametrize("keepdims", [True, False]) def test_basic(self, func_name, ndim, keepdims): """This test inserts a NaN in the array and checks if the @@ -77,7 +86,16 @@ def test_basic(self, func_name, ndim, keepdims): assert np.array_equal(index_array_num, index_array_np) @pytest.mark.parametrize("func_name", NAN_ARG_FUNCS) - @pytest.mark.parametrize("ndim", range(1, LEGATE_MAX_DIM + 1)) + @pytest.mark.parametrize( + "ndim", + ( + 1, + 2, + 3, + 4, + LEGATE_MAX_DIM, + ), + ) def test_out(self, func_name, ndim): """This test checks that the out argument is updated with the output""" @@ -103,7 +121,17 @@ def test_out(self, func_name, ndim): assert np.array_equal(out_np, out_num) @pytest.mark.parametrize("func_name", NAN_ARG_FUNCS) - @pytest.mark.parametrize("ndim", range(0, LEGATE_MAX_DIM + 1)) + @pytest.mark.parametrize( + "ndim", + ( + 0, + 1, + 2, + 3, + 4, + LEGATE_MAX_DIM, + ), + ) @pytest.mark.parametrize("dtype", (np.float32, np.float64)) def test_floating_point_types(self, func_name, ndim, dtype): """This test checks the most frequently used datatypes @@ -127,7 +155,16 @@ def test_floating_point_types(self, func_name, ndim, dtype): assert np.array_equal(out_num, out_np) @pytest.mark.parametrize("func_name", NAN_ARG_FUNCS) - @pytest.mark.parametrize("ndim", range(1, LEGATE_MAX_DIM + 1)) + @pytest.mark.parametrize( + "ndim", + ( + 1, + 2, + 3, + 4, + LEGATE_MAX_DIM, + ), + ) def test_all_nan_numpy_compat(self, func_name, ndim): """This test checks if we comply with the expected behavior when the array contains only NaNs. The expected behavior is to @@ -152,7 +189,16 @@ def test_all_nan_numpy_compat(self, func_name, ndim): reason="Eager and Deferred mode will give different results", ) @pytest.mark.parametrize("func_name", NAN_ARG_FUNCS) - @pytest.mark.parametrize("ndim", range(1, LEGATE_MAX_DIM + 1)) + @pytest.mark.parametrize( + "ndim", + ( + 1, + 2, + 3, + 4, + LEGATE_MAX_DIM, + ), + ) def test_all_nan_no_numpy_compat(self, func_name, ndim): """This test checks that we return identity for all-NaN arrays. Note that scalar reductions (e.g., argmin/argmax) on arrays @@ -248,7 +294,17 @@ class TestXFail: @pytest.mark.xfail @pytest.mark.parametrize("func_name", NAN_ARG_FUNCS) - @pytest.mark.parametrize("ndim", range(LEGATE_MAX_DIM + 1)) + @pytest.mark.parametrize( + "ndim", + ( + 0, + 1, + 2, + 3, + 4, + LEGATE_MAX_DIM, + ), + ) @pytest.mark.parametrize("disallowed_dtype", DISALLOWED_DTYPES) def test_disallowed_dtypes(self, func_name, ndim, disallowed_dtype): """This test checks if we raise an error for types that are diff --git a/tests/integration/test_ndim.py b/tests/integration/test_ndim.py index 114ff691bd..e564f2886c 100644 --- a/tests/integration/test_ndim.py +++ b/tests/integration/test_ndim.py @@ -20,7 +20,17 @@ import cupynumeric as num -@pytest.mark.parametrize("ndim", range(LEGATE_MAX_DIM + 1)) +@pytest.mark.parametrize( + "ndim", + ( + 0, + 1, + 2, + 3, + 4, + LEGATE_MAX_DIM, + ), +) def test_ndarray(ndim): shape = (4,) * ndim a = num.ones(shape) diff --git a/tests/integration/test_nonzero.py b/tests/integration/test_nonzero.py index 83b53f1158..67f42a7d61 100644 --- a/tests/integration/test_nonzero.py +++ b/tests/integration/test_nonzero.py @@ -55,8 +55,7 @@ @pytest.mark.skipif(not is_np2, reason="numpy 1.0 does not raise") @pytest.mark.parametrize("value", (0, 1, 2, 7)) def test_0d_error(value): - with pytest.raises(ValueError): - num.nonzero(value) + num.nonzero(value) @pytest.mark.parametrize("size", EMPTY_SIZES) diff --git a/tests/integration/test_norm.py b/tests/integration/test_norm.py index 94938653a6..ce40370489 100644 --- a/tests/integration/test_norm.py +++ b/tests/integration/test_norm.py @@ -28,11 +28,25 @@ np_arrays = [ mk_0to1_array(np, (3,) * ndim) - 0.5 - for ndim in range(0, LEGATE_MAX_DIM + 1) + for ndim in ( + 0, + 1, + 2, + 3, + 4, + LEGATE_MAX_DIM, + ) ] num_arrays = [ mk_0to1_array(num, (3,) * ndim) - 0.5 - for ndim in range(0, LEGATE_MAX_DIM + 1) + for ndim in ( + 0, + 1, + 2, + 3, + 4, + LEGATE_MAX_DIM, + ) ] @@ -67,7 +81,7 @@ def test_noaxis_2d(ord, keepdims, dtype): assert allclose(np_res, num_res) -@pytest.mark.parametrize("ndim", [0] + list(range(3, LEGATE_MAX_DIM + 1))) +@pytest.mark.parametrize("ndim", [0] + list(range(3, LEGATE_MAX_DIM))) @pytest.mark.parametrize("keepdims", [False, True]) @pytest.mark.parametrize("dtype", (np.float64, np.complex64)) def test_noaxis_other(ndim, keepdims, dtype): @@ -78,7 +92,16 @@ def test_noaxis_other(ndim, keepdims, dtype): assert allclose(np_res, num_res) -@pytest.mark.parametrize("ndim", range(1, LEGATE_MAX_DIM + 1)) +@pytest.mark.parametrize( + "ndim", + ( + 1, + 2, + 3, + 4, + LEGATE_MAX_DIM - 1, + ), +) @pytest.mark.parametrize("ord", VECTOR_ORDS) @pytest.mark.parametrize("keepdims", [False, True]) def test_axis_1d(ndim, ord, keepdims): @@ -91,7 +114,15 @@ def test_axis_1d(ndim, ord, keepdims): assert allclose(np_res, num_res) -@pytest.mark.parametrize("ndim", range(2, LEGATE_MAX_DIM + 1)) +@pytest.mark.parametrize( + "ndim", + ( + 2, + 3, + 4, + LEGATE_MAX_DIM - 1, + ), +) @pytest.mark.parametrize("ord", MATRIX_ORDS) @pytest.mark.parametrize("keepdims", [False, True]) @pytest.mark.parametrize( diff --git a/tests/integration/test_put.py b/tests/integration/test_put.py index 7284febf3d..a47fff6f7f 100644 --- a/tests/integration/test_put.py +++ b/tests/integration/test_put.py @@ -137,7 +137,16 @@ def test_indices_array_and_shape_array(shape, indices_values_shape): assert np.array_equal(np_arr, num_arr) -@pytest.mark.parametrize("ndim", range(1, LEGATE_MAX_DIM + 1)) +@pytest.mark.parametrize( + "ndim", + ( + 1, + 2, + 3, + 4, + LEGATE_MAX_DIM, + ), +) def test_ndim_default_mode(ndim): shape = (5,) * ndim np_arr = mk_seq_array(np, shape) @@ -157,7 +166,16 @@ def test_ndim_default_mode(ndim): INDICES = ([1, 2, 3.2, 100], [[2, 1], [3, 100]], [1], [100]) -@pytest.mark.parametrize("ndim", range(1, LEGATE_MAX_DIM + 1)) +@pytest.mark.parametrize( + "ndim", + ( + 1, + 2, + 3, + 4, + LEGATE_MAX_DIM, + ), +) @pytest.mark.parametrize("mode", ("wrap", "clip")) @pytest.mark.parametrize( "indices", INDICES, ids=lambda indices: f"(indices={indices})" diff --git a/tests/integration/test_put_along_axis.py b/tests/integration/test_put_along_axis.py index 03894824dc..ab1414b957 100644 --- a/tests/integration/test_put_along_axis.py +++ b/tests/integration/test_put_along_axis.py @@ -50,7 +50,7 @@ def test_axis_None(): N = 10 -@pytest.mark.parametrize("ndim", range(1, LEGATE_MAX_DIM + 1)) +@pytest.mark.parametrize("ndim", (1, 2, 3, 4, LEGATE_MAX_DIM)) def test_ndim(ndim): shape = (N,) * ndim np_arr = mk_seq_array(np, shape) diff --git a/tests/integration/test_putmask.py b/tests/integration/test_putmask.py index 8d62b6e0b4..9f76653978 100644 --- a/tests/integration/test_putmask.py +++ b/tests/integration/test_putmask.py @@ -118,7 +118,16 @@ def test_type_convert(): assert np.array_equal(x_num, x) -@pytest.mark.parametrize("ndim", range(1, LEGATE_MAX_DIM + 1)) +@pytest.mark.parametrize( + "ndim", + ( + 1, + 2, + 3, + 4, + LEGATE_MAX_DIM, + ), +) def test_ndim(ndim): shape = (5,) * ndim np_arr = mk_seq_array(np, shape) diff --git a/tests/integration/test_repeat.py b/tests/integration/test_repeat.py index 51addb46fc..abd2cffad4 100644 --- a/tests/integration/test_repeat.py +++ b/tests/integration/test_repeat.py @@ -201,7 +201,16 @@ def test_array_axis_negative_equal(): assert np.array_equal(res_np, res_num) -@pytest.mark.parametrize("ndim", range(1, LEGATE_MAX_DIM + 1)) +@pytest.mark.parametrize( + "ndim", + ( + 1, + 2, + 3, + 4, + LEGATE_MAX_DIM, + ), +) def test_nd_basic(ndim): a_shape = tuple(np.random.randint(1, 9) for _ in range(ndim)) np_array = mk_seq_array(np, a_shape) @@ -212,7 +221,16 @@ def test_nd_basic(ndim): assert np.array_equal(res_num, res_np) -@pytest.mark.parametrize("ndim", range(1, LEGATE_MAX_DIM + 1)) +@pytest.mark.parametrize( + "ndim", + ( + 1, + 2, + 3, + 4, + LEGATE_MAX_DIM, + ), +) def test_nd_axis(ndim): for axis in range(0, ndim): a_shape = tuple(np.random.randint(1, 9) for _ in range(ndim)) @@ -224,7 +242,16 @@ def test_nd_axis(ndim): assert np.array_equal(res_num2, res_np2) -@pytest.mark.parametrize("ndim", range(1, LEGATE_MAX_DIM + 1)) +@pytest.mark.parametrize( + "ndim", + ( + 1, + 2, + 3, + 4, + LEGATE_MAX_DIM, + ), +) def test_nd_repeats(ndim): a_shape = tuple(np.random.randint(1, 9) for _ in range(ndim)) np_array = mk_seq_array(np, a_shape) diff --git a/tests/integration/test_round.py b/tests/integration/test_round.py index c47766b1c9..2e433ceb4a 100644 --- a/tests/integration/test_round.py +++ b/tests/integration/test_round.py @@ -40,9 +40,18 @@ def test_empty_array(decimals): @pytest.mark.parametrize("decimals", range(-3, 3)) -@pytest.mark.parametrize("ndim", range(1, LEGATE_MAX_DIM)) +@pytest.mark.parametrize( + "ndim", + ( + 1, + 2, + 3, + 4, + LEGATE_MAX_DIM, + ), +) def test_basic_float16(ndim, decimals): - shape = (5,) * ndim + shape = (3,) * ndim np_arr = mk_0to1_array(np, shape, dtype=np.float16) num_arr = mk_0to1_array(num, shape, dtype=np.float16) @@ -53,10 +62,19 @@ def test_basic_float16(ndim, decimals): @pytest.mark.parametrize("decimals", range(-5, 5)) -@pytest.mark.parametrize("ndim", range(1, LEGATE_MAX_DIM)) +@pytest.mark.parametrize( + "ndim", + ( + 1, + 2, + 3, + 4, + LEGATE_MAX_DIM, + ), +) @pytest.mark.parametrize("dtype", FLOAT) def test_basic_float(dtype, ndim, decimals): - shape = (5,) * ndim + shape = (3,) * ndim np_arr = mk_0to1_array(np, shape, dtype=dtype) num_arr = mk_0to1_array(num, shape, dtype=dtype) @@ -67,10 +85,19 @@ def test_basic_float(dtype, ndim, decimals): @pytest.mark.parametrize("decimals", range(-5, 5)) -@pytest.mark.parametrize("ndim", range(1, LEGATE_MAX_DIM)) +@pytest.mark.parametrize( + "ndim", + ( + 1, + 2, + 3, + 4, + LEGATE_MAX_DIM, + ), +) @pytest.mark.parametrize("dtype", FLOAT) def test_randomized_float(dtype, ndim, decimals): - shape = (5,) * ndim + shape = (3,) * ndim values = np.random.uniform(-10, 10, shape) * 10**6 np_arr = np.array(values, dtype=dtype) num_arr = num.array(values, dtype=dtype) @@ -82,7 +109,16 @@ def test_randomized_float(dtype, ndim, decimals): @pytest.mark.parametrize("decimals", range(-5, 5)) -@pytest.mark.parametrize("ndim", range(1, LEGATE_MAX_DIM)) +@pytest.mark.parametrize( + "ndim", + ( + 1, + 2, + 3, + 4, + LEGATE_MAX_DIM, + ), +) @pytest.mark.parametrize("dtype", COMPLEX) def test_randomized_complex(dtype, ndim, decimals): shape = (1,) * ndim diff --git a/tests/integration/test_searchsorted.py b/tests/integration/test_searchsorted.py index bbed6e1d89..992575afa8 100644 --- a/tests/integration/test_searchsorted.py +++ b/tests/integration/test_searchsorted.py @@ -208,7 +208,17 @@ def test_standard_cases(volume, dtype, side): check_api(generate_random(volume, dtype), side=side) -@pytest.mark.parametrize("ndim", range(0, LEGATE_MAX_DIM + 1)) +@pytest.mark.parametrize( + "ndim", + ( + 0, + 1, + 2, + 3, + 4, + LEGATE_MAX_DIM, + ), +) @pytest.mark.parametrize("side", SIDES) def test_ndim(ndim, side): a = np.random.randint(-100, 100, size=100) diff --git a/tests/integration/test_setflags.py b/tests/integration/test_setflags.py index c8d3fc896f..9e06b8f0ba 100644 --- a/tests/integration/test_setflags.py +++ b/tests/integration/test_setflags.py @@ -98,7 +98,16 @@ def test_logic(): # cuPyNumeric: ValueError: cannot set WRITEBACKIFCOPY flag to True -@pytest.mark.parametrize("ndim", range(1, LEGATE_MAX_DIM + 1)) +@pytest.mark.parametrize( + "ndim", + ( + 1, + 2, + 3, + 4, + LEGATE_MAX_DIM, + ), +) def test_set_write_true(ndim): shape = (3,) * ndim array_np = np.random.randint(1, 100, shape, dtype=int) @@ -108,7 +117,16 @@ def test_set_write_true(ndim): assert array_np.flags["WRITEABLE"] == array_num.flags["WRITEABLE"] -@pytest.mark.parametrize("ndim", range(1, LEGATE_MAX_DIM + 1)) +@pytest.mark.parametrize( + "ndim", + ( + 1, + 2, + 3, + 4, + LEGATE_MAX_DIM, + ), +) def test_set_write_false(ndim): shape = (3,) * ndim array_np = np.random.randint(1, 100, shape, dtype=int) @@ -118,7 +136,16 @@ def test_set_write_false(ndim): assert array_np.flags["WRITEABLE"] == array_num.flags["WRITEABLE"] -@pytest.mark.parametrize("ndim", range(1, LEGATE_MAX_DIM + 1)) +@pytest.mark.parametrize( + "ndim", + ( + 1, + 2, + 3, + 4, + LEGATE_MAX_DIM, + ), +) def test_set_align_true(ndim): shape = (3,) * ndim array_np = np.random.randint(1, 100, shape, dtype=int) @@ -129,7 +156,16 @@ def test_set_align_true(ndim): @pytest.mark.xfail -@pytest.mark.parametrize("ndim", range(1, LEGATE_MAX_DIM + 1)) +@pytest.mark.parametrize( + "ndim", + ( + 1, + 2, + 3, + 4, + LEGATE_MAX_DIM, + ), +) def test_set_align_false(ndim): shape = (3,) * ndim array_np = np.random.randint(1, 100, shape, dtype=int) diff --git a/tests/integration/test_singleton_access.py b/tests/integration/test_singleton_access.py index 6167c2a748..c728a17977 100644 --- a/tests/integration/test_singleton_access.py +++ b/tests/integration/test_singleton_access.py @@ -22,7 +22,13 @@ def nonscalar_gen(lib): - for ndim in range(1, LEGATE_MAX_DIM + 1): + for ndim in ( + 1, + 2, + 3, + 4, + LEGATE_MAX_DIM, + ): yield mk_0to1_array(lib, ndim * (5,)) diff --git a/tests/integration/test_take.py b/tests/integration/test_take.py index 0cee90a8c2..d20bc6506b 100644 --- a/tests/integration/test_take.py +++ b/tests/integration/test_take.py @@ -110,7 +110,16 @@ def test_empty_array_and_indices(): ((4,), (0,), pytest.param((2, 2), marks=pytest.mark.xfail)), ids=lambda shape_in: f"(shape_in={shape_in})", ) -@pytest.mark.parametrize("ndim", range(1, LEGATE_MAX_DIM + 1)) +@pytest.mark.parametrize( + "ndim", + ( + 1, + 2, + 3, + 4, + LEGATE_MAX_DIM, + ), +) def test_ndim_default_mode(ndim, shape_in): # for shape_in=(2, 2) and ndim=4, # In Numpy, pass @@ -138,7 +147,7 @@ def test_ndim_default_mode(ndim, shape_in): ((8,), pytest.param((3, 4), marks=pytest.mark.xfail)), ids=lambda shape_in: f"(shape_in={shape_in})", ) -@pytest.mark.parametrize("ndim", range(1, LEGATE_MAX_DIM + 1)) +@pytest.mark.parametrize("ndim", (1, 2, 3, 4, LEGATE_MAX_DIM)) def test_ndim_mode(ndim, mode, shape_in): # for shape_in=(3, 4) and ndim=4, # In Numpy, pass diff --git a/tests/integration/test_take_along_axis.py b/tests/integration/test_take_along_axis.py index cfbcb59c1a..ce2a701039 100644 --- a/tests/integration/test_take_along_axis.py +++ b/tests/integration/test_take_along_axis.py @@ -23,7 +23,16 @@ N = 10 -@pytest.mark.parametrize("ndim", range(1, LEGATE_MAX_DIM + 1)) +@pytest.mark.parametrize( + "ndim", + ( + 1, + 2, + 3, + 4, + LEGATE_MAX_DIM, + ), +) def test_ndim(ndim): shape = (N,) * ndim np_arr = mk_seq_array(np, shape) diff --git a/tests/integration/test_tensordot.py b/tests/integration/test_tensordot.py index 0a7edc30f7..70c712ecec 100644 --- a/tests/integration/test_tensordot.py +++ b/tests/integration/test_tensordot.py @@ -29,8 +29,26 @@ def gen_axes(a_ndim, b_ndim): yield ([0, 1], [1, 0]) -@pytest.mark.parametrize("b_ndim", range(LEGATE_MAX_DIM + 1)) -@pytest.mark.parametrize("a_ndim", range(LEGATE_MAX_DIM + 1)) +@pytest.mark.parametrize( + "b_ndim", + ( + 1, + 2, + 3, + 4, + LEGATE_MAX_DIM, + ), +) +@pytest.mark.parametrize( + "a_ndim", + ( + 1, + 2, + 3, + 4, + LEGATE_MAX_DIM, + ), +) def test_tensordot(a_ndim, b_ndim): for axes in gen_axes(a_ndim, b_ndim): name = f"tensordot({a_ndim} x {b_ndim}, axes={axes})" diff --git a/tests/integration/test_trace.py b/tests/integration/test_trace.py index 6690d54110..1e462b8dae 100644 --- a/tests/integration/test_trace.py +++ b/tests/integration/test_trace.py @@ -69,7 +69,15 @@ def test_4d(): assert np.array_equal(res, res_num) -@pytest.mark.parametrize("ndim", range(2, LEGATE_MAX_DIM + 1)) +@pytest.mark.parametrize( + "ndim", + ( + 2, + 3, + 4, + LEGATE_MAX_DIM, + ), +) def test_ndim(ndim): a_shape = tuple(np.random.randint(1, 9) for i in range(ndim)) np_array = mk_seq_array(np, a_shape) diff --git a/tests/integration/test_unique.py b/tests/integration/test_unique.py index 33937a3bad..5f48786fca 100644 --- a/tests/integration/test_unique.py +++ b/tests/integration/test_unique.py @@ -30,7 +30,16 @@ def test_with_nonzero(): assert np.array_equal(b, b_np) -@pytest.mark.parametrize("ndim", range(LEGATE_MAX_DIM + 1)) +@pytest.mark.parametrize( + "ndim", + ( + 1, + 2, + 3, + 4, + LEGATE_MAX_DIM, + ), +) def test_ndim(ndim): shape = (4,) * ndim a = num.random.randint(0, 3, size=shape) diff --git a/tests/integration/test_unravel_index.py b/tests/integration/test_unravel_index.py index b1ba07167d..b3f6a31ffc 100644 --- a/tests/integration/test_unravel_index.py +++ b/tests/integration/test_unravel_index.py @@ -122,7 +122,16 @@ def test_large_index(): assert np.array_equal(res_num, res_np) -@pytest.mark.parametrize("ndim", range(1, LEGATE_MAX_DIM + 1)) +@pytest.mark.parametrize( + "ndim", + ( + 1, + 2, + 3, + 4, + LEGATE_MAX_DIM, + ), +) @pytest.mark.parametrize( "order", ( @@ -141,7 +150,16 @@ def test_basic(ndim, order): assert np.array_equal(res_num, res_np) -@pytest.mark.parametrize("ndim", range(1, LEGATE_MAX_DIM + 1)) +@pytest.mark.parametrize( + "ndim", + ( + 1, + 2, + 3, + 4, + LEGATE_MAX_DIM, + ), +) @pytest.mark.parametrize( "order", ( @@ -152,8 +170,8 @@ def test_basic(ndim, order): def test_uneven_shape(ndim, order): shape = np.random.randint(1, 6, ndim, dtype=int) size = ndim - np_arr = mk_seq_array(np, size) - num_arr = mk_seq_array(num, size) + np_arr = mk_seq_array(np, size) - 1 + num_arr = mk_seq_array(num, size) - 1 res_np = np.unravel_index(np_arr, shape, order) res_num = num.unravel_index(num_arr, shape, order) From ebf0e2a1fae29ee5a2a2b9e631127209cf3b1301 Mon Sep 17 00:00:00 2001 From: Irina Demeshko Date: Wed, 11 Dec 2024 22:02:26 -0800 Subject: [PATCH 377/462] bringing logic for "ceil", "floor" and "trunc" on-par with numpy 2.1 (#537) --- cupynumeric/_ufunc/ufunc.py | 10 ++++++++++ cupynumeric/_utils/__init__.py | 1 + tests/integration/test_nonzero.py | 8 +++++++- 3 files changed, 18 insertions(+), 1 deletion(-) diff --git a/cupynumeric/_ufunc/ufunc.py b/cupynumeric/_ufunc/ufunc.py index 5b2eb11e98..2eca1ccee2 100644 --- a/cupynumeric/_ufunc/ufunc.py +++ b/cupynumeric/_ufunc/ufunc.py @@ -19,6 +19,8 @@ import numpy as np from legate.core.utils import OrderedSet +from cupynumeric._utils import is_np2_1 + from .._array.thunk import perform_unary_reduction from .._array.util import ( add_boilerplate, @@ -486,6 +488,14 @@ def __call__( precision_fixed = True x = self._maybe_cast_input(x, dtype, casting) + if ( + self._name in {"ceil", "floor", "trunc"} + and is_np2_1 + and np.issubdtype(x.dtype, np.integer) + ): + result = x + return self._maybe_cast_output(out, result) + # Resolve the dtype to use for the computation and cast the input # if necessary. If the dtype is already fixed by the caller, # the dtype must be one of the dtypes supported by this operation. diff --git a/cupynumeric/_utils/__init__.py b/cupynumeric/_utils/__init__.py index 626ef7aae5..d292c29016 100644 --- a/cupynumeric/_utils/__init__.py +++ b/cupynumeric/_utils/__init__.py @@ -17,3 +17,4 @@ import numpy as np is_np2 = np.lib.NumpyVersion(np.__version__) >= "2.0.0b1" +is_np2_1 = np.lib.NumpyVersion(np.__version__) >= "2.1.0b1" diff --git a/tests/integration/test_nonzero.py b/tests/integration/test_nonzero.py index 67f42a7d61..86fcaf97a5 100644 --- a/tests/integration/test_nonzero.py +++ b/tests/integration/test_nonzero.py @@ -15,6 +15,7 @@ import numpy as np import pytest +from packaging import version from utils.utils import AxisError import cupynumeric as num @@ -55,7 +56,12 @@ @pytest.mark.skipif(not is_np2, reason="numpy 1.0 does not raise") @pytest.mark.parametrize("value", (0, 1, 2, 7)) def test_0d_error(value): - num.nonzero(value) + numpy_version = np.__version__ + # numpy v2.0 doesn't return an error, but the verstion + # before and after does + if version.parse(numpy_version) != version.parse("2.0"): + with pytest.raises(ValueError): + num.nonzero(value) @pytest.mark.parametrize("size", EMPTY_SIZES) From 88313556bcda166d8c7b0b9c91120774dd5b6c05 Mon Sep 17 00:00:00 2001 From: Parag Kulkarni Date: Thu, 12 Dec 2024 16:40:30 +0530 Subject: [PATCH 378/462] Pass github refname (PR Id) to upload in gh-legate-ci (#536) * Pass github refname (PR Id) to upload in gh-legate-ci * Pass ref_name and default_branch to legate-gh-ci setup. * Use legate-gh-ci v1.23 --- .github/workflows/ci-gh-nightly-release.yml | 2 ++ .github/workflows/ci-gh-release.yml | 2 ++ .github/workflows/gh-build-and-test.yml | 31 +++++++++++++-------- 3 files changed, 24 insertions(+), 11 deletions(-) diff --git a/.github/workflows/ci-gh-nightly-release.yml b/.github/workflows/ci-gh-nightly-release.yml index bae423f10d..b4fb0b3b90 100644 --- a/.github/workflows/ci-gh-nightly-release.yml +++ b/.github/workflows/ci-gh-nightly-release.yml @@ -36,4 +36,6 @@ jobs: target-device: ${{ matrix.target-device }} upload-enabled: ${{ matrix.upload-enabled }} waive-gpu-tests: ${{ github.workflow == 'Build Release package' && matrix.platform == 'linux-aarch64' }} + refname: ${{ github.ref_name }} + default-branch: ${{ github.event.repository.default_branch }} secrets: inherit diff --git a/.github/workflows/ci-gh-release.yml b/.github/workflows/ci-gh-release.yml index 654fad29ef..b2286a583b 100644 --- a/.github/workflows/ci-gh-release.yml +++ b/.github/workflows/ci-gh-release.yml @@ -39,4 +39,6 @@ jobs: target-device: ${{ matrix.target-device }} upload-enabled: ${{ matrix.upload-enabled }} waive-gpu-tests: ${{ github.workflow == 'Build Release package' && matrix.platform == 'linux-aarch64' }} + refname: ${{ github.ref_name }} + default-branch: ${{ github.event.repository.default_branch }} secrets: inherit diff --git a/.github/workflows/gh-build-and-test.yml b/.github/workflows/gh-build-and-test.yml index a079356e1d..272b05ca23 100644 --- a/.github/workflows/gh-build-and-test.yml +++ b/.github/workflows/gh-build-and-test.yml @@ -21,7 +21,12 @@ on: required: false type: string default: "3.12" - + refname: + required: true + type: string + default-branch: + required: true + type: string jobs: setup-build: @@ -48,13 +53,13 @@ jobs: needs: setup-build name: "Build (${{ inputs.platform }}, ${{ inputs.target-device }}, ${{ inputs.build-type }}, Python ${{ inputs.python-version }})" uses: - nv-legate/legate-gh-ci/.github/workflows/gh-build.yml@v1.22 + nv-legate/legate-gh-ci/.github/workflows/gh-build.yml@v1.23 with: build-mode: "" build-type: ${{ inputs.build-type }} client-repo: ${{ github.event.repository.name }} dependencies-file: "cmake/versions.json" - legate-gh-ci-tag: "v1.22" + legate-gh-ci-tag: "v1.23" network: "ucx" platform: ${{ inputs.platform }} python-version: ${{ inputs.python-version }} @@ -68,13 +73,13 @@ jobs: needs: setup-build name: "Check if legate.internal nightly exists for SHA specified in versions.json (${{ inputs.platform }}, ${{ inputs.target-device }}, ${{ inputs.build-type }})" uses: - nv-legate/legate-gh-ci/.github/workflows/gh-check-if-nightly-exists-for-all-dependencies.yml@v1.22 + nv-legate/legate-gh-ci/.github/workflows/gh-check-if-nightly-exists-for-all-dependencies.yml@v1.23 with: build-mode: "" build-type: ${{ inputs.build-type }} client-repo: ${{ github.event.repository.name }} dependencies-file: "cmake/versions.json" - legate-gh-ci-tag: "v1.22" + legate-gh-ci-tag: "v1.23" network: "ucx" platform: ${{ inputs.platform }} python-version: ${{ inputs.python-version }} @@ -88,12 +93,12 @@ jobs: if: ${{ github.repository_owner == 'nv-legate' && contains(github.workflow, 'release') && inputs.upload-enabled == true }} name: Upload package to Server uses: - nv-legate/legate-gh-ci/.github/workflows/gh-upload.yml@v1.22 + nv-legate/legate-gh-ci/.github/workflows/gh-upload.yml@v1.23 with: build-mode: "" build-type: ${{ inputs.build-type }} client-repo: ${{ github.event.repository.name }} - legate-gh-ci-tag: "v1.22" + legate-gh-ci-tag: "v1.23" name: Upload package to Server network: "ucx" pkgSubString: "cupynumeric-" @@ -103,6 +108,8 @@ jobs: target-device: ${{ inputs.target-device }} upload-action: "upload-package-ALL" upload-enabled: ${{ inputs.upload-enabled }} + refname: ${{ inputs.refname }} + default-branch: ${{ inputs.default-branch }} secrets: inherit setup-test: @@ -176,13 +183,13 @@ jobs: matrix: ${{fromJson(needs.setup-test.outputs.matrix)}} uses: - nv-legate/legate-gh-ci/.github/workflows/gh-test-within-container.yml@v1.22 + nv-legate/legate-gh-ci/.github/workflows/gh-test-within-container.yml@v1.23 with: build-mode: "" build-type: ${{ inputs.build-type }} client-repo: ${{ github.event.repository.name }} has-gpu: ${{ matrix.runner.type == 'gpu' }} - legate-gh-ci-tag: "v1.22" + legate-gh-ci-tag: "v1.23" name: ${{ matrix.test-config.name }} network: "ucx" platform: ${{ inputs.platform }} @@ -198,12 +205,12 @@ jobs: name: Update Test status on Server if: ${{ (github.repository_owner == 'nv-legate') && contains(github.workflow, 'Nightly') && (inputs.upload-enabled == true) }} uses: - nv-legate/legate-gh-ci/.github/workflows/gh-upload.yml@v1.22 + nv-legate/legate-gh-ci/.github/workflows/gh-upload.yml@v1.23 with: build-mode: "" build-type: ${{ inputs.build-type }} client-repo: ${{ github.event.repository.name }} - legate-gh-ci-tag: "v1.22" + legate-gh-ci-tag: "v1.23" name: UpdateTestStatus network: "ucx" pkgSubString: "cupynumeric-" @@ -213,4 +220,6 @@ jobs: target-device: ${{ inputs.target-device }} upload-action: "update-test-status" upload-enabled: true + refname: ${{ inputs.refname }} + default-branch: ${{ inputs.default-branch }} secrets: inherit From d43e1fa308a9d8a27968e8a9c2f4fa8a19bad5f7 Mon Sep 17 00:00:00 2001 From: Bryan Van de Ven Date: Thu, 12 Dec 2024 12:14:54 -0800 Subject: [PATCH 379/462] update test_clip for np2 (#539) * update test_clip for np2 * missing comparison * Apply suggestions from code review Co-authored-by: Jacob Faibussowitsch --------- Co-authored-by: Jacob Faibussowitsch --- tests/integration/test_clip.py | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/tests/integration/test_clip.py b/tests/integration/test_clip.py index b22d005691..50d175c7ab 100644 --- a/tests/integration/test_clip.py +++ b/tests/integration/test_clip.py @@ -83,13 +83,11 @@ def test_bool() -> None: num.clip(True, a_min=1, a_max=1) -def test_bool_None() -> None: - msg = r"One of max or min must be given" - with pytest.raises(ValueError, match=msg): - np.clip(True, a_min=None, a_max=None) - # See https://github.com/nv-legate/cunumeric.internal/issues/492 - num.clip(True, a_min=None, a_max=None) - # cuNumeric returns True, it returns False if array is False +@pytest.mark.parametrize("v", (True, False)) +def test_bool_None(v: bool) -> None: + # Different Numpy versions error variously with both bounds None + res = num.clip(v, a_min=None, a_max=None) + assert np.array_equal(res, np.asarray(v)) @pytest.mark.xfail From 69988dca43f536c6c3a6f650c3557cac7fd50114 Mon Sep 17 00:00:00 2001 From: Manolis Papadakis Date: Thu, 12 Dec 2024 16:16:04 -0800 Subject: [PATCH 380/462] Bump Legate to get HDF5 fixes (#538) * Bump Legate to get HDF5 fixes * Use commit that corresponds to a nightly build * Bump to latest legate sha --- cmake/versions.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmake/versions.json b/cmake/versions.json index 0a58acb01d..5f0143c864 100644 --- a/cmake/versions.json +++ b/cmake/versions.json @@ -10,7 +10,7 @@ "git_url" : "git@github.com:nv-legate/legate.internal.git", "git_shallow": false, "always_download": false, - "git_tag" : "d357867e1b8b2b26613bb31addb460a340df14aa" + "git_tag" : "a66a81804aed194b3f9d69b774aab5eb55aebf82" } } } From fc68ff7cff1de92ec5c3e332de4e27808be544d1 Mon Sep 17 00:00:00 2001 From: XiaLuNV <110973296+XiaLuNV@users.noreply.github.com> Date: Fri, 13 Dec 2024 09:45:05 +0800 Subject: [PATCH 381/462] fix fft assertion failure (#533) * fix fft assertion failure --- tests/integration/test_fft_c2c.py | 9 +++++++-- tests/integration/test_fft_c2r.py | 9 +++++++-- tests/integration/test_fft_r2c.py | 9 +++++++-- 3 files changed, 21 insertions(+), 6 deletions(-) diff --git a/tests/integration/test_fft_c2c.py b/tests/integration/test_fft_c2c.py index c16a4bbe1f..6e5fb2125f 100644 --- a/tests/integration/test_fft_c2c.py +++ b/tests/integration/test_fft_c2c.py @@ -21,8 +21,13 @@ import cupynumeric as num -def allclose(A, B): - if B.dtype == np.float32 or B.dtype == np.complex64: +def allclose(A: np.ndarray, B: np.ndarray) -> bool: + if ( + B.dtype == np.float32 + or B.dtype == np.float64 + or B.dtype == np.complex64 + or B.dtype == np.complex128 + ): l2 = (A - B) * np.conj(A - B) l2 = np.sqrt(np.sum(l2) / np.sum(A * np.conj(A))) return l2 < 1e-6 diff --git a/tests/integration/test_fft_c2r.py b/tests/integration/test_fft_c2r.py index 7ee6a824c9..a5ae7340c8 100644 --- a/tests/integration/test_fft_c2r.py +++ b/tests/integration/test_fft_c2r.py @@ -20,8 +20,13 @@ import cupynumeric as num -def allclose(A, B): - if B.dtype == np.float32 or B.dtype == np.complex64: +def allclose(A: np.ndarray, B: np.ndarray) -> bool: + if ( + B.dtype == np.float32 + or B.dtype == np.float64 + or B.dtype == np.complex64 + or B.dtype == np.complex128 + ): l2 = (A - B) * np.conj(A - B) l2 = np.sqrt(np.sum(l2) / np.sum(A * np.conj(A))) return l2 < 1e-6 diff --git a/tests/integration/test_fft_r2c.py b/tests/integration/test_fft_r2c.py index c21a8c6494..38e9a31023 100644 --- a/tests/integration/test_fft_r2c.py +++ b/tests/integration/test_fft_r2c.py @@ -20,8 +20,13 @@ import cupynumeric as num -def allclose(A, B): - if B.dtype == np.float32 or B.dtype == np.complex64: +def allclose(A: np.ndarray, B: np.ndarray) -> bool: + if ( + B.dtype == np.float32 + or B.dtype == np.float64 + or B.dtype == np.complex64 + or B.dtype == np.complex128 + ): l2 = (A - B) * np.conj(A - B) l2 = np.sqrt(np.sum(l2) / np.sum(A * np.conj(A))) return l2 < 1e-6 From 01344ef4f04f35d101ad210858a4598d3a031740 Mon Sep 17 00:00:00 2001 From: Irina Demeshko Date: Fri, 13 Dec 2024 15:51:52 -0800 Subject: [PATCH 382/462] fix tests for all LEGATE_MAX_DIMS (#541) --- tests/integration/test_advanced_indexing.py | 16 +-- tests/integration/test_amax_amin.py | 36 +------ tests/integration/test_angle.py | 13 +-- tests/integration/test_arg_reduce.py | 41 ++------ tests/integration/test_atleast_nd.py | 15 +-- tests/integration/test_bits.py | 79 ++------------ tests/integration/test_broadcast.py | 9 +- tests/integration/test_clip.py | 26 +---- tests/integration/test_compress.py | 35 +------ tests/integration/test_diag_indices.py | 24 +---- tests/integration/test_dot.py | 26 +---- tests/integration/test_fill_diagonal.py | 12 +-- tests/integration/test_flip.py | 13 +-- tests/integration/test_gradient.py | 44 +------- tests/integration/test_index_routines.py | 27 +---- tests/integration/test_indices.py | 50 +-------- tests/integration/test_inner.py | 26 +---- tests/integration/test_intra_array_copy.py | 13 +-- tests/integration/test_item.py | 14 +-- tests/integration/test_itemset.py | 14 +-- tests/integration/test_logical.py | 14 +-- tests/integration/test_logical_reduction.py | 13 +-- tests/integration/test_matmul.py | 24 +---- tests/integration/test_median.py | 24 +---- tests/integration/test_moveaxis.py | 13 +-- tests/integration/test_nan_reduction.py | 110 ++------------------ tests/integration/test_nanarg_reduction.py | 70 ++----------- tests/integration/test_ndim.py | 12 +-- tests/integration/test_nonzero.py | 13 +-- tests/integration/test_norm.py | 49 ++------- tests/integration/test_put.py | 24 +---- tests/integration/test_put_along_axis.py | 4 +- tests/integration/test_putmask.py | 13 +-- tests/integration/test_repeat.py | 36 +------ tests/integration/test_round.py | 46 +------- tests/integration/test_searchsorted.py | 14 +-- tests/integration/test_setflags.py | 46 +------- tests/integration/test_singleton_access.py | 10 +- tests/integration/test_take.py | 15 +-- tests/integration/test_take_along_axis.py | 13 +-- tests/integration/test_tensordot.py | 24 +---- tests/integration/test_trace.py | 12 +-- tests/integration/test_unique.py | 13 +-- tests/integration/test_unravel_index.py | 24 +---- tests/integration/utils/utils.py | 11 ++ 45 files changed, 163 insertions(+), 1017 deletions(-) diff --git a/tests/integration/test_advanced_indexing.py b/tests/integration/test_advanced_indexing.py index 9cf63c3d30..29aa538399 100644 --- a/tests/integration/test_advanced_indexing.py +++ b/tests/integration/test_advanced_indexing.py @@ -17,6 +17,7 @@ import pytest from legate.core import LEGATE_MAX_DIM from utils.generators import mk_seq_array +from utils.utils import ONE_MAX_DIM_RANGE, TWO_MAX_DIM_RANGE import cupynumeric as num @@ -120,13 +121,7 @@ def mk_deferred_array(lib, shape): def gen_args(): - for arr_ndim in ( - 1, - 2, - 3, - 4, - LEGATE_MAX_DIM - 1, - ): + for arr_ndim in ONE_MAX_DIM_RANGE[:-1]: for idx_ndim in range(1, arr_ndim + 1): for zero_dim in range(arr_ndim): yield arr_ndim, idx_ndim, zero_dim @@ -925,12 +920,7 @@ def test(): # we do less than LEGATE_MAX_DIM becasue the dimension will be increased by # 1 when passig 2d index array - for ndim in ( - 2, - 3, - 4, - LEGATE_MAX_DIM - 1, - ): + for ndim in TWO_MAX_DIM_RANGE[:-1]: a_shape = tuple(np.random.randint(2, 5) for i in range(ndim)) np_array = mk_seq_array(np, a_shape) num_array = mk_seq_array(num, a_shape) diff --git a/tests/integration/test_amax_amin.py b/tests/integration/test_amax_amin.py index 3314e156d7..2c99f35cef 100755 --- a/tests/integration/test_amax_amin.py +++ b/tests/integration/test_amax_amin.py @@ -15,7 +15,7 @@ import numpy as np import pytest -from legate.core import LEGATE_MAX_DIM +from utils.utils import MAX_DIM_RANGE, TWO_MAX_DIM_RANGE import cupynumeric as num @@ -24,17 +24,7 @@ @pytest.mark.parametrize("initial", (None, -2, 0, 0.5, 2)) @pytest.mark.parametrize("keepdims", [True, False]) -@pytest.mark.parametrize( - "ndim", - ( - 0, - 1, - 2, - 3, - 4, - LEGATE_MAX_DIM, - ), -) +@pytest.mark.parametrize("ndim", MAX_DIM_RANGE) @pytest.mark.parametrize("func_name", FUNCS) def test_basic(func_name, ndim, keepdims, initial): shape = (5,) * ndim @@ -81,17 +71,7 @@ def test_src_dt(func_name, keepdims, src_dt): @pytest.mark.parametrize("initial", (None, -2, 0, 0.5, 2)) @pytest.mark.parametrize("keepdims", [True, False]) -@pytest.mark.parametrize( - "ndim", - ( - 0, - 1, - 2, - 3, - 4, - LEGATE_MAX_DIM, - ), -) +@pytest.mark.parametrize("ndim", MAX_DIM_RANGE) @pytest.mark.parametrize("func_name", FUNCS) def test_axis(func_name, ndim, keepdims, initial): shape = (5,) * ndim @@ -171,15 +151,7 @@ def test_out_dim1(func_name, keepdims): @pytest.mark.parametrize("initial", (None, -2, 0, 0.5, 2)) @pytest.mark.parametrize("keepdims", [True, False]) -@pytest.mark.parametrize( - "ndim", - ( - 2, - 3, - 4, - LEGATE_MAX_DIM, - ), -) +@pytest.mark.parametrize("ndim", TWO_MAX_DIM_RANGE) @pytest.mark.parametrize("func_name", FUNCS) def test_out(func_name, ndim, keepdims, initial): shape = (5,) * ndim diff --git a/tests/integration/test_angle.py b/tests/integration/test_angle.py index 0190e44ea5..76ec6658b0 100644 --- a/tests/integration/test_angle.py +++ b/tests/integration/test_angle.py @@ -15,8 +15,8 @@ import numpy as np import pytest -from legate.core import LEGATE_MAX_DIM from utils.generators import mk_seq_array +from utils.utils import ONE_MAX_DIM_RANGE import cupynumeric as num @@ -51,16 +51,7 @@ def test_pure_real_and_imaginary(self): assert np.array_equal(num.angle(5j), np.angle(5j)) assert np.array_equal(num.angle(-5j), np.angle(-5j)) - @pytest.mark.parametrize( - "ndim", - ( - 1, - 2, - 3, - 4, - LEGATE_MAX_DIM, - ), - ) + @pytest.mark.parametrize("ndim", ONE_MAX_DIM_RANGE) @pytest.mark.parametrize("in_type", (int, float, complex)) @pytest.mark.parametrize("deg", (False, True)) def test_basic(self, ndim, in_type, deg): diff --git a/tests/integration/test_arg_reduce.py b/tests/integration/test_arg_reduce.py index 54f407bb43..4e9e24f486 100644 --- a/tests/integration/test_arg_reduce.py +++ b/tests/integration/test_arg_reduce.py @@ -15,8 +15,12 @@ import numpy as np import pytest -from legate.core import LEGATE_MAX_DIM -from utils.utils import AxisError +from utils.utils import ( + MAX_DIM_RANGE, + ONE_MAX_DIM_RANGE, + TWO_MAX_DIM_RANGE, + AxisError, +) import cupynumeric as num @@ -104,17 +108,7 @@ class TestArgMaxAndArgMin: """ @pytest.mark.parametrize("func_name", ARG_FUNCS) - @pytest.mark.parametrize( - "ndim", - ( - 0, - 1, - 2, - 3, - 4, - LEGATE_MAX_DIM, - ), - ) + @pytest.mark.parametrize("ndim", MAX_DIM_RANGE) @pytest.mark.parametrize("keepdims", [True, False]) def test_argmax_and_argmin_basic(self, func_name, ndim, keepdims): shape = (5,) * ndim @@ -130,16 +124,7 @@ def test_argmax_and_argmin_basic(self, func_name, ndim, keepdims): ) @pytest.mark.parametrize("func_name", ARG_FUNCS) - @pytest.mark.parametrize( - "ndim", - ( - 1, - 2, - 3, - 4, - LEGATE_MAX_DIM, - ), - ) + @pytest.mark.parametrize("ndim", ONE_MAX_DIM_RANGE) @pytest.mark.parametrize("keepdims", [True, False]) def test_argmax_and_argmin_axis(self, func_name, ndim, keepdims): shape = (5,) * ndim @@ -194,15 +179,7 @@ def test_argmax_and_argmin_out_1dim(self, func_name, keepdims): assert np.array_equal(res_np, res_num) @pytest.mark.parametrize("func_name", ARG_FUNCS) - @pytest.mark.parametrize( - "ndim", - ( - 2, - 3, - 4, - LEGATE_MAX_DIM, - ), - ) + @pytest.mark.parametrize("ndim", TWO_MAX_DIM_RANGE) @pytest.mark.parametrize("keepdims", [True, False]) def test_argmax_and_argmin_out(self, func_name, ndim, keepdims): shape = (5,) * ndim diff --git a/tests/integration/test_atleast_nd.py b/tests/integration/test_atleast_nd.py index f4d8180a7d..f499e7f84f 100644 --- a/tests/integration/test_atleast_nd.py +++ b/tests/integration/test_atleast_nd.py @@ -15,24 +15,13 @@ import numpy as np import pytest -from legate.core import LEGATE_MAX_DIM -from utils.utils import check_module_function +from utils.utils import MAX_DIM_RANGE, check_module_function import cupynumeric as num DIM = 10 -SIZE_CASES = list( - (DIM,) * ndim - for ndim in ( - 0, - 1, - 2, - 3, - 4, - LEGATE_MAX_DIM, - ) -) +SIZE_CASES = list((DIM,) * ndim for ndim in MAX_DIM_RANGE) SIZE_CASES += [ (0,), # empty array diff --git a/tests/integration/test_bits.py b/tests/integration/test_bits.py index 030b65d67c..f8e13da18a 100644 --- a/tests/integration/test_bits.py +++ b/tests/integration/test_bits.py @@ -16,7 +16,7 @@ import numpy as np import pytest -from legate.core import LEGATE_MAX_DIM +from utils.utils import ONE_MAX_DIM_RANGE import cupynumeric as num @@ -64,16 +64,7 @@ def test_arr(self, arr, dtype, bitorder): out_num = num.packbits(in_num, bitorder=bitorder) assert np.array_equal(out_np, out_num) - @pytest.mark.parametrize( - "ndim", - ( - 1, - 2, - 3, - 4, - LEGATE_MAX_DIM, - ), - ) + @pytest.mark.parametrize("ndim", ONE_MAX_DIM_RANGE) @pytest.mark.parametrize("dtype", ("B", "i", "?")) @pytest.mark.parametrize("bitorder", ("little", "big")) def test_common(self, ndim, dtype, bitorder): @@ -85,16 +76,7 @@ def test_common(self, ndim, dtype, bitorder): out_num = num.packbits(in_num, bitorder=bitorder) assert np.array_equal(out_np, out_num) - @pytest.mark.parametrize( - "ndim", - ( - 1, - 2, - 3, - 4, - LEGATE_MAX_DIM, - ), - ) + @pytest.mark.parametrize("ndim", ONE_MAX_DIM_RANGE) @pytest.mark.parametrize("dtype", ("B", "i", "?")) @pytest.mark.parametrize("bitorder", ("little", "big")) def test_axis(self, ndim, dtype, bitorder): @@ -163,16 +145,7 @@ def test_arr(self, arr, bitorder): out_num = num.unpackbits(in_num, bitorder=bitorder) assert np.array_equal(out_np, out_num) - @pytest.mark.parametrize( - "ndim", - ( - 1, - 2, - 3, - 4, - LEGATE_MAX_DIM, - ), - ) + @pytest.mark.parametrize("ndim", ONE_MAX_DIM_RANGE) @pytest.mark.parametrize("bitorder", ("little", "big")) def test_common(self, ndim, bitorder): shape = (5,) * ndim @@ -184,16 +157,7 @@ def test_common(self, ndim, bitorder): assert np.array_equal(out_np, out_num) @pytest.mark.parametrize("count", (-9, 4, -1, 0, 4, 8, 9)) - @pytest.mark.parametrize( - "ndim", - ( - 1, - 2, - 3, - 4, - LEGATE_MAX_DIM, - ), - ) + @pytest.mark.parametrize("ndim", ONE_MAX_DIM_RANGE) @pytest.mark.parametrize("bitorder", ("little", "big")) def test_count(self, ndim, count, bitorder): shape = (5,) * ndim @@ -204,16 +168,7 @@ def test_count(self, ndim, count, bitorder): out_num = num.unpackbits(in_num, count=count, bitorder=bitorder) assert np.array_equal(out_np, out_num) - @pytest.mark.parametrize( - "ndim", - ( - 1, - 2, - 3, - 4, - LEGATE_MAX_DIM, - ), - ) + @pytest.mark.parametrize("ndim", ONE_MAX_DIM_RANGE) @pytest.mark.parametrize("bitorder", ("little", "big")) def test_axis(self, ndim, bitorder): shape = (5,) * ndim @@ -225,16 +180,7 @@ def test_axis(self, ndim, bitorder): out_num = num.unpackbits(in_num, axis=axis, bitorder=bitorder) assert np.array_equal(out_np, out_num) - @pytest.mark.parametrize( - "ndim", - ( - 1, - 2, - 3, - 4, - LEGATE_MAX_DIM, - ), - ) + @pytest.mark.parametrize("ndim", ONE_MAX_DIM_RANGE) @pytest.mark.parametrize("bitorder", ("little", "big")) @pytest.mark.parametrize("count", (-2, 0, 2, 5)) def test_axis_count(self, ndim, bitorder, count): @@ -252,16 +198,7 @@ def test_axis_count(self, ndim, bitorder, count): assert np.array_equal(out_np, out_num) -@pytest.mark.parametrize( - "ndim", - ( - 1, - 2, - 3, - 4, - LEGATE_MAX_DIM, - ), -) +@pytest.mark.parametrize("ndim", ONE_MAX_DIM_RANGE) @pytest.mark.parametrize("bitorder", ("little", "big")) @pytest.mark.parametrize("dtype", ("B", "i", "?")) def test_pack_unpack(ndim, bitorder, dtype): diff --git a/tests/integration/test_broadcast.py b/tests/integration/test_broadcast.py index d192a4e958..586746189e 100644 --- a/tests/integration/test_broadcast.py +++ b/tests/integration/test_broadcast.py @@ -135,13 +135,8 @@ def _check(*args, params: list, routine: str): def gen_shapes(dim): base = (dim,) result = [base] - for i in ( - 1, - 2, - 3, - LEGATE_MAX_DIM, - ): - base = base + (1,) if (i % 2 == 0 and i <= 4) else base + (dim,) + for i in range(1, min(4, LEGATE_MAX_DIM)): + base = base + (1,) if i % 2 == 0 else base + (dim,) result.append(base) return result diff --git a/tests/integration/test_clip.py b/tests/integration/test_clip.py index 50d175c7ab..fd9a317352 100644 --- a/tests/integration/test_clip.py +++ b/tests/integration/test_clip.py @@ -15,8 +15,8 @@ import numpy as np import pytest -from legate.core import LEGATE_MAX_DIM from utils.generators import mk_seq_array +from utils.utils import ONE_MAX_DIM_RANGE import cupynumeric as num @@ -86,7 +86,7 @@ def test_bool() -> None: @pytest.mark.parametrize("v", (True, False)) def test_bool_None(v: bool) -> None: # Different Numpy versions error variously with both bounds None - res = num.clip(v, a_min=None, a_max=None) + res = num.clip(v, a_min=None, a_max=None) assert np.array_equal(res, np.asarray(v)) @@ -169,16 +169,7 @@ def test_out_np_array(): assert np.array_equal(out_np, out_num) -@pytest.mark.parametrize( - "ndim", - ( - 1, - 2, - 3, - 4, - LEGATE_MAX_DIM, - ), -) +@pytest.mark.parametrize("ndim", ONE_MAX_DIM_RANGE) def test_basic(ndim): shape = (5,) * ndim np_arr = mk_seq_array(np, shape) @@ -192,16 +183,7 @@ def test_basic(ndim): assert np.array_equal(res_num, res_np) -@pytest.mark.parametrize( - "ndim", - ( - 1, - 2, - 3, - 4, - LEGATE_MAX_DIM, - ), -) +@pytest.mark.parametrize("ndim", ONE_MAX_DIM_RANGE) def test_out(ndim): shape = (5,) * ndim np_arr = mk_seq_array(np, shape) diff --git a/tests/integration/test_compress.py b/tests/integration/test_compress.py index c702fec5c2..ce5a3a3652 100644 --- a/tests/integration/test_compress.py +++ b/tests/integration/test_compress.py @@ -15,8 +15,8 @@ import numpy as np import pytest -from legate.core import LEGATE_MAX_DIM from utils.generators import mk_seq_array +from utils.utils import ONE_MAX_DIM_RANGE import cupynumeric as num @@ -120,16 +120,7 @@ def test_bool_condition(): assert np.array_equal(res_num, res_np) -@pytest.mark.parametrize( - "ndim", - ( - 1, - 2, - 3, - 4, - LEGATE_MAX_DIM, - ), -) +@pytest.mark.parametrize("ndim", ONE_MAX_DIM_RANGE) def test_ndim_basic(ndim): shape = (5,) * ndim np_arr = mk_seq_array(np, shape) @@ -143,16 +134,7 @@ def test_ndim_basic(ndim): assert np.array_equal(res_num, res_np) -@pytest.mark.parametrize( - "ndim", - ( - 1, - 2, - 3, - 4, - LEGATE_MAX_DIM, - ), -) +@pytest.mark.parametrize("ndim", ONE_MAX_DIM_RANGE) def test_ndim_axis(ndim): shape = (5,) * ndim np_arr = mk_seq_array(np, shape) @@ -167,16 +149,7 @@ def test_ndim_axis(ndim): assert np.array_equal(res_num, res_np) -@pytest.mark.parametrize( - "ndim", - ( - 1, - 2, - 3, - 4, - LEGATE_MAX_DIM, - ), -) +@pytest.mark.parametrize("ndim", ONE_MAX_DIM_RANGE) def test_ndim_out(ndim): shape = (5,) * ndim np_arr = mk_seq_array(np, shape) diff --git a/tests/integration/test_diag_indices.py b/tests/integration/test_diag_indices.py index 5e29c7e5e6..d1fddb9557 100644 --- a/tests/integration/test_diag_indices.py +++ b/tests/integration/test_diag_indices.py @@ -15,7 +15,7 @@ import numpy as np import pytest -from legate.core import LEGATE_MAX_DIM +from utils.utils import MAX_DIM_RANGE, TWO_MAX_DIM_RANGE import cupynumeric as num @@ -27,17 +27,7 @@ def test_diag_indices_default_ndim(n): assert np.array_equal(a_np, a_num) -@pytest.mark.parametrize( - "ndim", - ( - 0, - 1, - 2, - 3, - 4, - LEGATE_MAX_DIM, - ), -) +@pytest.mark.parametrize("ndim", MAX_DIM_RANGE) def test_diag_indices_basic(ndim): a_np = np.diag_indices(10, ndim) a_num = num.diag_indices(10, ndim) @@ -82,15 +72,7 @@ def test_none_ndim(self): @pytest.mark.parametrize("size", [(5,), (0,)], ids=str) -@pytest.mark.parametrize( - "ndim", - ( - 2, - 3, - 4, - LEGATE_MAX_DIM, - ), -) +@pytest.mark.parametrize("ndim", TWO_MAX_DIM_RANGE) def test_diag_indices_from_basic(size, ndim): shape = size * ndim a = np.ones(shape, dtype=int) diff --git a/tests/integration/test_dot.py b/tests/integration/test_dot.py index f58c8e43a9..49c1762900 100644 --- a/tests/integration/test_dot.py +++ b/tests/integration/test_dot.py @@ -14,36 +14,16 @@ # import numpy as np import pytest -from legate.core import LEGATE_MAX_DIM from utils.contractions import check_default from utils.generators import mk_0to1_array +from utils.utils import MAX_DIM_RANGE import cupynumeric as num from cupynumeric._utils.linalg import dot_modes -@pytest.mark.parametrize( - "b_ndim", - ( - 0, - 1, - 2, - 3, - 4, - LEGATE_MAX_DIM, - ), -) -@pytest.mark.parametrize( - "a_ndim", - ( - 0, - 1, - 2, - 3, - 4, - LEGATE_MAX_DIM, - ), -) +@pytest.mark.parametrize("b_ndim", MAX_DIM_RANGE) +@pytest.mark.parametrize("a_ndim", MAX_DIM_RANGE) def test_dot(a_ndim, b_ndim): name = f"dot({a_ndim} x {b_ndim})" modes = dot_modes(a_ndim, b_ndim) diff --git a/tests/integration/test_fill_diagonal.py b/tests/integration/test_fill_diagonal.py index 2af81ba563..f384d03285 100644 --- a/tests/integration/test_fill_diagonal.py +++ b/tests/integration/test_fill_diagonal.py @@ -15,8 +15,8 @@ import numpy as np import pytest -from legate.core import LEGATE_MAX_DIM from utils.generators import mk_seq_array +from utils.utils import TWO_MAX_DIM_RANGE import cupynumeric as num @@ -34,15 +34,7 @@ def test_wrap(wrap): assert np.array_equal(np_array, num_array) -@pytest.mark.parametrize( - "ndim", - ( - 2, - 3, - 4, - LEGATE_MAX_DIM, - ), -) +@pytest.mark.parametrize("ndim", TWO_MAX_DIM_RANGE) @pytest.mark.parametrize("val_shape", ((0,), (3,), (6,), (2, 2), (2, 2, 6))) @pytest.mark.parametrize("wrap", WRAP, ids=str) def test_basic(ndim, val_shape, wrap): diff --git a/tests/integration/test_flip.py b/tests/integration/test_flip.py index 1a83edfba1..59cb85e2bd 100644 --- a/tests/integration/test_flip.py +++ b/tests/integration/test_flip.py @@ -16,8 +16,7 @@ import numpy as np import pytest -from legate.core import LEGATE_MAX_DIM -from utils.utils import AxisError +from utils.utils import TWO_MAX_DIM_RANGE, AxisError import cupynumeric as num @@ -148,15 +147,7 @@ def test_wrong_dim(self): @pytest.mark.parametrize("func_name", FLIP_FUNCS) -@pytest.mark.parametrize( - "ndim", - ( - 2, - 3, - 4, - LEGATE_MAX_DIM, - ), -) +@pytest.mark.parametrize("ndim", TWO_MAX_DIM_RANGE) def test_max_dims(func_name, ndim): func_np = getattr(np, func_name) func_num = getattr(num, func_name) diff --git a/tests/integration/test_gradient.py b/tests/integration/test_gradient.py index 7fd2498d7a..3d891a8166 100644 --- a/tests/integration/test_gradient.py +++ b/tests/integration/test_gradient.py @@ -16,7 +16,7 @@ import numpy as np import pytest -from legate.core import LEGATE_MAX_DIM +from utils.utils import ONE_MAX_DIM_RANGE, TWO_MAX_DIM_RANGE import cupynumeric as cn @@ -38,16 +38,7 @@ def test_gradient_1d(): assert np.allclose(res_np, res_cn) -@pytest.mark.parametrize( - "ndim", - ( - 1, - 2, - 3, - 4, - LEGATE_MAX_DIM, - ), -) +@pytest.mark.parametrize("ndim", ONE_MAX_DIM_RANGE) @pytest.mark.parametrize("edge_order", [1, 2]) def test_nd_arrays(ndim, edge_order): shape = (5,) * ndim @@ -62,16 +53,7 @@ def test_nd_arrays(ndim, edge_order): assert np.allclose(res_np, res_cn) -@pytest.mark.parametrize( - "ndim", - ( - 1, - 2, - 3, - 4, - LEGATE_MAX_DIM, - ), -) +@pytest.mark.parametrize("ndim", ONE_MAX_DIM_RANGE) @pytest.mark.parametrize("varargs", [0.5, 1, 2, 0.3, 0]) def test_scalar_varargs(ndim, varargs): shape = (5,) * ndim @@ -84,15 +66,7 @@ def test_scalar_varargs(ndim, varargs): assert np.allclose(res_np, res_cn, equal_nan=True) -@pytest.mark.parametrize( - "ndim", - ( - 2, - 3, - 4, - LEGATE_MAX_DIM, - ), -) +@pytest.mark.parametrize("ndim", TWO_MAX_DIM_RANGE) def test_array_1d_varargs(ndim): shape = (5,) * ndim size = prod(shape) @@ -105,15 +79,7 @@ def test_array_1d_varargs(ndim): assert np.allclose(res_np, res_cn) -@pytest.mark.parametrize( - "ndim", - ( - 2, - 3, - 4, - LEGATE_MAX_DIM, - ), -) +@pytest.mark.parametrize("ndim", TWO_MAX_DIM_RANGE) def test_list_of_axes(ndim): shape = (5,) * ndim size = prod(shape) diff --git a/tests/integration/test_index_routines.py b/tests/integration/test_index_routines.py index 1242b47e79..300d1ce384 100644 --- a/tests/integration/test_index_routines.py +++ b/tests/integration/test_index_routines.py @@ -19,7 +19,7 @@ import pytest from legate.core import LEGATE_MAX_DIM from utils.generators import mk_seq_array -from utils.utils import AxisError +from utils.utils import ONE_MAX_DIM_RANGE, TWO_MAX_DIM_RANGE, AxisError import cupynumeric as num from cupynumeric._thunk.eager import diagonal_reference @@ -87,16 +87,7 @@ def test_choose_2d(): ) -@pytest.mark.parametrize( - "ndim", - ( - 1, - 2, - 3, - 4, - LEGATE_MAX_DIM, - ), -) +@pytest.mark.parametrize("ndim", ONE_MAX_DIM_RANGE) def test_choose_target_ndim(ndim): tgt_shape = (5,) * ndim # try various shapes that broadcast to the target shape @@ -352,12 +343,7 @@ def test_select(size): def test_select_maxdim(): - for ndim in ( - 2, - 3, - 4, - LEGATE_MAX_DIM, - ): + for ndim in TWO_MAX_DIM_RANGE: a_shape = tuple(np.random.randint(1, 9) for i in range(ndim)) arr = mk_seq_array(np, a_shape) condlist_np = list() @@ -419,12 +405,7 @@ def test_diagonal(): assert np.array_equal(ad.diagonal(-1, 0, 2), num_ad.diagonal(-1, 0, 2)) # test diagonal - for ndim in ( - 2, - 3, - 4, - LEGATE_MAX_DIM, - ): + for ndim in TWO_MAX_DIM_RANGE: a_shape = tuple(np.random.randint(1, 9) for i in range(ndim)) np_array = mk_seq_array(np, a_shape) num_array = mk_seq_array(num, a_shape) diff --git a/tests/integration/test_indices.py b/tests/integration/test_indices.py index abb15370d8..4de187dcd9 100644 --- a/tests/integration/test_indices.py +++ b/tests/integration/test_indices.py @@ -15,7 +15,7 @@ import numpy as np import pytest -from legate.core import LEGATE_MAX_DIM +from utils.utils import MAX_DIM_RANGE import cupynumeric as num @@ -75,17 +75,7 @@ def test_indices_zero(self, dimensions): assert np.array_equal(np_res, num_res) - @pytest.mark.parametrize( - "ndim", - ( - 0, - 1, - 2, - 3, - 4, - LEGATE_MAX_DIM - 1, - ), - ) + @pytest.mark.parametrize("ndim", MAX_DIM_RANGE[:-1]) def test_indices_basic(self, ndim): dimensions = tuple(np.random.randint(1, 5) for _ in range(ndim)) @@ -93,17 +83,7 @@ def test_indices_basic(self, ndim): num_res = num.indices(dimensions) assert np.array_equal(np_res, num_res) - @pytest.mark.parametrize( - "ndim", - ( - 0, - 1, - 2, - 3, - 4, - LEGATE_MAX_DIM - 1, - ), - ) + @pytest.mark.parametrize("ndim", MAX_DIM_RANGE[:-1]) def test_indices_dtype_none(self, ndim): dimensions = tuple(np.random.randint(1, 5) for _ in range(ndim)) @@ -111,34 +91,14 @@ def test_indices_dtype_none(self, ndim): num_res = num.indices(dimensions, dtype=None) assert np.array_equal(np_res, num_res) - @pytest.mark.parametrize( - "ndim", - ( - 0, - 1, - 2, - 3, - 4, - LEGATE_MAX_DIM - 1, - ), - ) + @pytest.mark.parametrize("ndim", MAX_DIM_RANGE[:-1]) def test_indices_dtype_float(self, ndim): dimensions = tuple(np.random.randint(1, 5) for _ in range(ndim)) np_res = np.indices(dimensions, dtype=float) num_res = num.indices(dimensions, dtype=float) assert np.array_equal(np_res, num_res) - @pytest.mark.parametrize( - "ndim", - ( - 0, - 1, - 2, - 3, - 4, - LEGATE_MAX_DIM - 1, - ), - ) + @pytest.mark.parametrize("ndim", MAX_DIM_RANGE[:-1]) def test_indices_sparse(self, ndim): dimensions = tuple(np.random.randint(1, 5) for _ in range(ndim)) np_res = np.indices(dimensions, sparse=True) diff --git a/tests/integration/test_inner.py b/tests/integration/test_inner.py index 0073dd045c..982342e277 100644 --- a/tests/integration/test_inner.py +++ b/tests/integration/test_inner.py @@ -14,36 +14,16 @@ # import pytest -from legate.core import LEGATE_MAX_DIM from utils.contractions import check_default from utils.generators import mk_0to1_array +from utils.utils import MAX_DIM_RANGE import cupynumeric as num from cupynumeric._utils.linalg import inner_modes -@pytest.mark.parametrize( - "b_ndim", - ( - 0, - 1, - 2, - 3, - 4, - LEGATE_MAX_DIM, - ), -) -@pytest.mark.parametrize( - "a_ndim", - ( - 0, - 1, - 2, - 3, - 4, - LEGATE_MAX_DIM, - ), -) +@pytest.mark.parametrize("b_ndim", MAX_DIM_RANGE) +@pytest.mark.parametrize("a_ndim", MAX_DIM_RANGE) def test_inner(a_ndim, b_ndim): name = f"inner({a_ndim} x {b_ndim})" modes = inner_modes(a_ndim, b_ndim) diff --git a/tests/integration/test_intra_array_copy.py b/tests/integration/test_intra_array_copy.py index 861bb4fefc..eb943f7235 100644 --- a/tests/integration/test_intra_array_copy.py +++ b/tests/integration/test_intra_array_copy.py @@ -15,8 +15,8 @@ import numpy as np import pytest -from legate.core import LEGATE_MAX_DIM from utils.generators import mk_0to1_array +from utils.utils import ONE_MAX_DIM_RANGE import cupynumeric as num @@ -137,16 +137,7 @@ def array_gen(lib, ndim): yield from full_overlap(lib, ndim) -@pytest.mark.parametrize( - "ndim", - ( - 1, - 2, - 3, - 4, - LEGATE_MAX_DIM, - ), -) +@pytest.mark.parametrize("ndim", ONE_MAX_DIM_RANGE) def test_overlap(ndim): for np_arr, num_arr in zip(array_gen(np, ndim), array_gen(num, ndim)): assert np.array_equal(np_arr, num_arr) diff --git a/tests/integration/test_item.py b/tests/integration/test_item.py index 8bba443916..a242ec790e 100644 --- a/tests/integration/test_item.py +++ b/tests/integration/test_item.py @@ -14,8 +14,8 @@ # import numpy as np import pytest -from legate.core import LEGATE_MAX_DIM from utils.generators import generate_item +from utils.utils import MAX_DIM_RANGE import cupynumeric as num @@ -76,17 +76,7 @@ def test_empty_no_item(): assert np.array_equal(res_np, res_num) -@pytest.mark.parametrize( - "ndim", - ( - 0, - 1, - 2, - 3, - 4, - LEGATE_MAX_DIM, - ), -) +@pytest.mark.parametrize("ndim", MAX_DIM_RANGE) def test_ndim(ndim): shape = (4,) * ndim arr_num = num.random.randint(0, 3, size=shape) diff --git a/tests/integration/test_itemset.py b/tests/integration/test_itemset.py index 32bf7be827..935c613a79 100644 --- a/tests/integration/test_itemset.py +++ b/tests/integration/test_itemset.py @@ -14,8 +14,8 @@ # import numpy as np import pytest -from legate.core import LEGATE_MAX_DIM from utils.generators import generate_item +from utils.utils import MAX_DIM_RANGE import cupynumeric as num from cupynumeric._utils import is_np2 @@ -97,17 +97,7 @@ def test_tuple_out_of_index(): # dimension 0 with index 3 for a store of shape Shape((3,)) -@pytest.mark.parametrize( - "ndim", - ( - 0, - 1, - 2, - 3, - 4, - LEGATE_MAX_DIM, - ), -) +@pytest.mark.parametrize("ndim", MAX_DIM_RANGE) def test_ndim(ndim): shape = (4,) * ndim arr_num = num.random.randint(0, 30, size=shape) diff --git a/tests/integration/test_logical.py b/tests/integration/test_logical.py index a128e59225..dfd1f8d228 100644 --- a/tests/integration/test_logical.py +++ b/tests/integration/test_logical.py @@ -15,7 +15,7 @@ import numpy as np import pytest -from legate.core import LEGATE_MAX_DIM +from utils.utils import MAX_DIM_RANGE import cupynumeric as num @@ -77,17 +77,7 @@ def test_axis_tuple(func, axes): assert np.array_equal(out_np, out_num) -@pytest.mark.parametrize( - "ndim", - ( - 0, - 1, - 2, - 3, - 4, - LEGATE_MAX_DIM, - ), -) +@pytest.mark.parametrize("ndim", MAX_DIM_RANGE) @pytest.mark.parametrize("func", FUNCTIONS) def test_nd_inputs(ndim, func): shape = (3,) * ndim diff --git a/tests/integration/test_logical_reduction.py b/tests/integration/test_logical_reduction.py index d49aea9bdd..405836c2bf 100644 --- a/tests/integration/test_logical_reduction.py +++ b/tests/integration/test_logical_reduction.py @@ -15,8 +15,8 @@ import numpy as np import pytest -from legate.core import LEGATE_MAX_DIM from utils.generators import mk_seq_array +from utils.utils import ONE_MAX_DIM_RANGE import cupynumeric as num @@ -36,16 +36,7 @@ def test_logical_reductions(axis): assert num.array_equal(out_num, out_np) -@pytest.mark.parametrize( - "ndim", - ( - 1, - 2, - 3, - 4, - LEGATE_MAX_DIM - 1, - ), -) +@pytest.mark.parametrize("ndim", ONE_MAX_DIM_RANGE[:-1]) @pytest.mark.parametrize( "axis", [ diff --git a/tests/integration/test_matmul.py b/tests/integration/test_matmul.py index e56a0eeabf..4ebdab42d5 100644 --- a/tests/integration/test_matmul.py +++ b/tests/integration/test_matmul.py @@ -15,7 +15,6 @@ import numpy as np import pytest -from legate.core import LEGATE_MAX_DIM from utils.comparisons import allclose from utils.contractions import ( check_default, @@ -23,31 +22,14 @@ check_shapes, check_types, ) +from utils.utils import ONE_MAX_DIM_RANGE import cupynumeric as num from cupynumeric._utils.linalg import matmul_modes -@pytest.mark.parametrize( - "a_ndim", - ( - 1, - 2, - 3, - 4, - LEGATE_MAX_DIM, - ), -) -@pytest.mark.parametrize( - "b_ndim", - ( - 1, - 2, - 3, - 4, - LEGATE_MAX_DIM, - ), -) +@pytest.mark.parametrize("a_ndim", ONE_MAX_DIM_RANGE) +@pytest.mark.parametrize("b_ndim", ONE_MAX_DIM_RANGE) def test_function(a_ndim, b_ndim): name = f"matmul({a_ndim} x {b_ndim})" modes = matmul_modes(a_ndim, b_ndim) diff --git a/tests/integration/test_median.py b/tests/integration/test_median.py index c0d6e855e2..6e388ef792 100644 --- a/tests/integration/test_median.py +++ b/tests/integration/test_median.py @@ -15,8 +15,8 @@ import numpy as np import pytest -from legate.core import LEGATE_MAX_DIM from utils.generators import mk_seq_array +from utils.utils import ONE_MAX_DIM_RANGE import cupynumeric as num @@ -56,16 +56,7 @@ def test_median_empty_array(self): class TestMedian: - @pytest.mark.parametrize( - "ndim", - ( - 1, - 2, - 3, - 4, - LEGATE_MAX_DIM - 1, - ), - ) + @pytest.mark.parametrize("ndim", ONE_MAX_DIM_RANGE[:-1]) @pytest.mark.parametrize( "keepdims", ( @@ -179,16 +170,7 @@ def test_median_overwrite_input(self): class TestNanmedian: - @pytest.mark.parametrize( - "ndim", - ( - 1, - 2, - 3, - 4, - LEGATE_MAX_DIM, - ), - ) + @pytest.mark.parametrize("ndim", ONE_MAX_DIM_RANGE) def test_nanmedian_basic(self, ndim): shape = np.random.randint(2, 5, ndim, dtype=int) size = 1 diff --git a/tests/integration/test_moveaxis.py b/tests/integration/test_moveaxis.py index 8f9d5c9611..860026a41f 100644 --- a/tests/integration/test_moveaxis.py +++ b/tests/integration/test_moveaxis.py @@ -15,9 +15,8 @@ import numpy as np import pytest -from legate.core import LEGATE_MAX_DIM from utils.generators import mk_0to1_array -from utils.utils import AxisError +from utils.utils import TWO_MAX_DIM_RANGE, AxisError import cupynumeric as num @@ -31,15 +30,7 @@ ) -@pytest.mark.parametrize( - "ndim", - ( - 2, - 3, - 4, - LEGATE_MAX_DIM, - ), -) +@pytest.mark.parametrize("ndim", TWO_MAX_DIM_RANGE) @pytest.mark.parametrize("axes", AXES) def test_moveaxis(ndim, axes): source, destination = axes diff --git a/tests/integration/test_nan_reduction.py b/tests/integration/test_nan_reduction.py index a5cfdd3578..837a2e5d13 100644 --- a/tests/integration/test_nan_reduction.py +++ b/tests/integration/test_nan_reduction.py @@ -18,8 +18,8 @@ import numpy as np import pytest -from legate.core import LEGATE_MAX_DIM from utils.comparisons import allclose +from utils.utils import MAX_DIM_RANGE, ONE_MAX_DIM_RANGE import cupynumeric as num from cupynumeric.settings import settings @@ -28,14 +28,7 @@ EAGER_TEST = os.environ.get("CUPYNUMERIC_FORCE_THUNK", None) == "eager" -NDIMS = ( - 0, - 1, - 2, - 3, - 4, - LEGATE_MAX_DIM, -) +NDIMS = MAX_DIM_RANGE DTYPE = ["l", "L", "f", "d", "h", "i", "H", "I", "?", "b", "B"] @@ -50,16 +43,7 @@ class TestNanReductions: """ @pytest.mark.parametrize("func_name", ("nansum", "nanprod")) - @pytest.mark.parametrize( - "ndim", - ( - 1, - 2, - 3, - 4, - LEGATE_MAX_DIM, - ), - ) + @pytest.mark.parametrize("ndim", ONE_MAX_DIM_RANGE) @pytest.mark.parametrize("keepdims", [True, False]) def test_basic_nan_sum_prod(self, func_name, ndim, keepdims): """This test sets an element to NaN and checks if the output @@ -84,16 +68,7 @@ def test_basic_nan_sum_prod(self, func_name, ndim, keepdims): assert allclose(out_num, out_np, rtol=1e-4) @pytest.mark.parametrize("func_name", ("nanmin", "nanmax")) - @pytest.mark.parametrize( - "ndim", - ( - 1, - 2, - 3, - 4, - LEGATE_MAX_DIM, - ), - ) + @pytest.mark.parametrize("ndim", ONE_MAX_DIM_RANGE) @pytest.mark.parametrize("keepdims", [True, False]) def test_basic_nan_min_max(self, func_name, ndim, keepdims): """This test sets an element to NaN and checks if the output @@ -118,16 +93,7 @@ def test_basic_nan_min_max(self, func_name, ndim, keepdims): assert np.array_equal(out_num, out_np) @pytest.mark.parametrize("func_name", NAN_FUNCS) - @pytest.mark.parametrize( - "ndim", - ( - 1, - 2, - 3, - 4, - LEGATE_MAX_DIM, - ), - ) + @pytest.mark.parametrize("ndim", ONE_MAX_DIM_RANGE) def test_out(self, func_name, ndim): """This test checks that the out argument is updated with the output""" @@ -152,16 +118,7 @@ def test_out(self, func_name, ndim): assert allclose(out_num, out_np, rtol=1e-4) - @pytest.mark.parametrize( - "ndim", - ( - 1, - 2, - 3, - 4, - LEGATE_MAX_DIM, - ), - ) + @pytest.mark.parametrize("ndim", ONE_MAX_DIM_RANGE) @pytest.mark.parametrize("dtype", (np.float32, np.float64)) @pytest.mark.parametrize("keepdims", [True, False]) def test_complex_dtype_nansum(self, ndim, dtype, keepdims): @@ -194,16 +151,7 @@ def test_complex_dtype_nansum(self, ndim, dtype, keepdims): assert allclose(out_num, out_np, rtol=1e-4) - @pytest.mark.parametrize( - "ndim", - ( - 1, - 2, - 3, - 4, - LEGATE_MAX_DIM, - ), - ) + @pytest.mark.parametrize("ndim", ONE_MAX_DIM_RANGE) @pytest.mark.parametrize("keepdims", [True, False]) def test_complex_dtype_nanprod(self, ndim, keepdims): """This test checks if nanprod works as expected for complex @@ -282,16 +230,7 @@ def test_slice_nan_no_numpy_compat(self, identity, func_name): settings.numpy_compat.unset_value() @pytest.mark.parametrize("func_name", ("nanmin", "nanmax")) - @pytest.mark.parametrize( - "ndim", - ( - 1, - 2, - 3, - 4, - LEGATE_MAX_DIM, - ), - ) + @pytest.mark.parametrize("ndim", ONE_MAX_DIM_RANGE) def test_all_nans_numpy_compat(self, ndim, func_name): """This test checks if we comply with the expected behavior when the array contains only NaNs. @@ -321,16 +260,7 @@ def test_all_nans_numpy_compat(self, ndim, func_name): ], ids=str, ) - @pytest.mark.parametrize( - "ndim", - ( - 1, - 2, - 3, - 4, - LEGATE_MAX_DIM, - ), - ) + @pytest.mark.parametrize("ndim", ONE_MAX_DIM_RANGE) def test_all_nans_no_numpy_compat(self, ndim, identity, func_name): """This test checks if we comply with the expected behavior when the array contains only NaNs for nanmin and nanmax. @@ -348,16 +278,7 @@ def test_all_nans_no_numpy_compat(self, ndim, identity, func_name): settings.numpy_compat.unset_value() - @pytest.mark.parametrize( - "ndim", - ( - 1, - 2, - 3, - 4, - LEGATE_MAX_DIM, - ), - ) + @pytest.mark.parametrize("ndim", ONE_MAX_DIM_RANGE) def test_all_nans_nanprod(self, ndim): shape = (3,) * ndim in_num = num.random.random(shape) @@ -389,16 +310,7 @@ def test_dtype_nansum(self, dtype) -> None: out_num = num.nansum(in_num) assert allclose(out_np, out_num) - @pytest.mark.parametrize( - "ndim", - ( - 1, - 2, - 3, - 4, - LEGATE_MAX_DIM, - ), - ) + @pytest.mark.parametrize("ndim", ONE_MAX_DIM_RANGE) def test_all_nans_nansum(self, ndim): shape = (3,) * ndim in_num = num.random.random(shape) diff --git a/tests/integration/test_nanarg_reduction.py b/tests/integration/test_nanarg_reduction.py index f57c2871c0..4ec51e5344 100644 --- a/tests/integration/test_nanarg_reduction.py +++ b/tests/integration/test_nanarg_reduction.py @@ -18,7 +18,7 @@ import numpy as np import pytest -from legate.core import LEGATE_MAX_DIM +from utils.utils import MAX_DIM_RANGE, ONE_MAX_DIM_RANGE import cupynumeric as num from cupynumeric.settings import settings @@ -46,16 +46,7 @@ class TestNanArgReductions: """ @pytest.mark.parametrize("func_name", NAN_ARG_FUNCS) - @pytest.mark.parametrize( - "ndim", - ( - 1, - 2, - 3, - 4, - LEGATE_MAX_DIM, - ), - ) + @pytest.mark.parametrize("ndim", ONE_MAX_DIM_RANGE) @pytest.mark.parametrize("keepdims", [True, False]) def test_basic(self, func_name, ndim, keepdims): """This test inserts a NaN in the array and checks if the @@ -86,16 +77,7 @@ def test_basic(self, func_name, ndim, keepdims): assert np.array_equal(index_array_num, index_array_np) @pytest.mark.parametrize("func_name", NAN_ARG_FUNCS) - @pytest.mark.parametrize( - "ndim", - ( - 1, - 2, - 3, - 4, - LEGATE_MAX_DIM, - ), - ) + @pytest.mark.parametrize("ndim", ONE_MAX_DIM_RANGE) def test_out(self, func_name, ndim): """This test checks that the out argument is updated with the output""" @@ -121,17 +103,7 @@ def test_out(self, func_name, ndim): assert np.array_equal(out_np, out_num) @pytest.mark.parametrize("func_name", NAN_ARG_FUNCS) - @pytest.mark.parametrize( - "ndim", - ( - 0, - 1, - 2, - 3, - 4, - LEGATE_MAX_DIM, - ), - ) + @pytest.mark.parametrize("ndim", MAX_DIM_RANGE) @pytest.mark.parametrize("dtype", (np.float32, np.float64)) def test_floating_point_types(self, func_name, ndim, dtype): """This test checks the most frequently used datatypes @@ -155,16 +127,7 @@ def test_floating_point_types(self, func_name, ndim, dtype): assert np.array_equal(out_num, out_np) @pytest.mark.parametrize("func_name", NAN_ARG_FUNCS) - @pytest.mark.parametrize( - "ndim", - ( - 1, - 2, - 3, - 4, - LEGATE_MAX_DIM, - ), - ) + @pytest.mark.parametrize("ndim", ONE_MAX_DIM_RANGE) def test_all_nan_numpy_compat(self, func_name, ndim): """This test checks if we comply with the expected behavior when the array contains only NaNs. The expected behavior is to @@ -189,16 +152,7 @@ def test_all_nan_numpy_compat(self, func_name, ndim): reason="Eager and Deferred mode will give different results", ) @pytest.mark.parametrize("func_name", NAN_ARG_FUNCS) - @pytest.mark.parametrize( - "ndim", - ( - 1, - 2, - 3, - 4, - LEGATE_MAX_DIM, - ), - ) + @pytest.mark.parametrize("ndim", ONE_MAX_DIM_RANGE) def test_all_nan_no_numpy_compat(self, func_name, ndim): """This test checks that we return identity for all-NaN arrays. Note that scalar reductions (e.g., argmin/argmax) on arrays @@ -294,17 +248,7 @@ class TestXFail: @pytest.mark.xfail @pytest.mark.parametrize("func_name", NAN_ARG_FUNCS) - @pytest.mark.parametrize( - "ndim", - ( - 0, - 1, - 2, - 3, - 4, - LEGATE_MAX_DIM, - ), - ) + @pytest.mark.parametrize("ndim", MAX_DIM_RANGE) @pytest.mark.parametrize("disallowed_dtype", DISALLOWED_DTYPES) def test_disallowed_dtypes(self, func_name, ndim, disallowed_dtype): """This test checks if we raise an error for types that are diff --git a/tests/integration/test_ndim.py b/tests/integration/test_ndim.py index e564f2886c..05ce740701 100644 --- a/tests/integration/test_ndim.py +++ b/tests/integration/test_ndim.py @@ -20,17 +20,7 @@ import cupynumeric as num -@pytest.mark.parametrize( - "ndim", - ( - 0, - 1, - 2, - 3, - 4, - LEGATE_MAX_DIM, - ), -) +@pytest.mark.parametrize("ndim", range(LEGATE_MAX_DIM)) def test_ndarray(ndim): shape = (4,) * ndim a = num.ones(shape) diff --git a/tests/integration/test_nonzero.py b/tests/integration/test_nonzero.py index 86fcaf97a5..3dd7e4e529 100644 --- a/tests/integration/test_nonzero.py +++ b/tests/integration/test_nonzero.py @@ -15,11 +15,10 @@ import numpy as np import pytest -from packaging import version from utils.utils import AxisError import cupynumeric as num -from cupynumeric._utils import is_np2 +from cupynumeric._utils import is_np2_1 # cupynumeric.count_nonzero(a: ndarray, # axis: Optional[Union[int, tuple[int, ...]]] = None) → Union[int, ndarray] @@ -53,15 +52,11 @@ SIZES = NO_EMPTY_SIZE + EMPTY_SIZES -@pytest.mark.skipif(not is_np2, reason="numpy 1.0 does not raise") +@pytest.mark.skipif(not is_np2_1, reason="numpy 1.0 does not raise") @pytest.mark.parametrize("value", (0, 1, 2, 7)) def test_0d_error(value): - numpy_version = np.__version__ - # numpy v2.0 doesn't return an error, but the verstion - # before and after does - if version.parse(numpy_version) != version.parse("2.0"): - with pytest.raises(ValueError): - num.nonzero(value) + with pytest.raises(ValueError): + num.nonzero(value) @pytest.mark.parametrize("size", EMPTY_SIZES) diff --git a/tests/integration/test_norm.py b/tests/integration/test_norm.py index ce40370489..348b5a3ddb 100644 --- a/tests/integration/test_norm.py +++ b/tests/integration/test_norm.py @@ -15,9 +15,9 @@ import numpy as np import pytest -from legate.core import LEGATE_MAX_DIM from utils.comparisons import allclose from utils.generators import mk_0to1_array +from utils.utils import MAX_DIM_RANGE, ONE_MAX_DIM_RANGE, TWO_MAX_DIM_RANGE import cupynumeric as num @@ -26,28 +26,8 @@ # TODO: Add "nuc", 2, -2 once they are implemented MATRIX_ORDS = [None, "fro", np.inf, -np.inf, 1, -1] -np_arrays = [ - mk_0to1_array(np, (3,) * ndim) - 0.5 - for ndim in ( - 0, - 1, - 2, - 3, - 4, - LEGATE_MAX_DIM, - ) -] -num_arrays = [ - mk_0to1_array(num, (3,) * ndim) - 0.5 - for ndim in ( - 0, - 1, - 2, - 3, - 4, - LEGATE_MAX_DIM, - ) -] +np_arrays = [mk_0to1_array(np, (3,) * ndim) - 0.5 for ndim in MAX_DIM_RANGE] +num_arrays = [mk_0to1_array(num, (3,) * ndim) - 0.5 for ndim in MAX_DIM_RANGE] @pytest.mark.parametrize("ord", VECTOR_ORDS) @@ -81,7 +61,7 @@ def test_noaxis_2d(ord, keepdims, dtype): assert allclose(np_res, num_res) -@pytest.mark.parametrize("ndim", [0] + list(range(3, LEGATE_MAX_DIM))) +@pytest.mark.parametrize("ndim", MAX_DIM_RANGE[:-1]) @pytest.mark.parametrize("keepdims", [False, True]) @pytest.mark.parametrize("dtype", (np.float64, np.complex64)) def test_noaxis_other(ndim, keepdims, dtype): @@ -92,16 +72,7 @@ def test_noaxis_other(ndim, keepdims, dtype): assert allclose(np_res, num_res) -@pytest.mark.parametrize( - "ndim", - ( - 1, - 2, - 3, - 4, - LEGATE_MAX_DIM - 1, - ), -) +@pytest.mark.parametrize("ndim", ONE_MAX_DIM_RANGE[:-1]) @pytest.mark.parametrize("ord", VECTOR_ORDS) @pytest.mark.parametrize("keepdims", [False, True]) def test_axis_1d(ndim, ord, keepdims): @@ -114,15 +85,7 @@ def test_axis_1d(ndim, ord, keepdims): assert allclose(np_res, num_res) -@pytest.mark.parametrize( - "ndim", - ( - 2, - 3, - 4, - LEGATE_MAX_DIM - 1, - ), -) +@pytest.mark.parametrize("ndim", TWO_MAX_DIM_RANGE[:-1]) @pytest.mark.parametrize("ord", MATRIX_ORDS) @pytest.mark.parametrize("keepdims", [False, True]) @pytest.mark.parametrize( diff --git a/tests/integration/test_put.py b/tests/integration/test_put.py index a47fff6f7f..ca1947868f 100644 --- a/tests/integration/test_put.py +++ b/tests/integration/test_put.py @@ -15,8 +15,8 @@ import numpy as np import pytest -from legate.core import LEGATE_MAX_DIM from utils.generators import mk_seq_array +from utils.utils import ONE_MAX_DIM_RANGE import cupynumeric as num @@ -137,16 +137,7 @@ def test_indices_array_and_shape_array(shape, indices_values_shape): assert np.array_equal(np_arr, num_arr) -@pytest.mark.parametrize( - "ndim", - ( - 1, - 2, - 3, - 4, - LEGATE_MAX_DIM, - ), -) +@pytest.mark.parametrize("ndim", ONE_MAX_DIM_RANGE) def test_ndim_default_mode(ndim): shape = (5,) * ndim np_arr = mk_seq_array(np, shape) @@ -166,16 +157,7 @@ def test_ndim_default_mode(ndim): INDICES = ([1, 2, 3.2, 100], [[2, 1], [3, 100]], [1], [100]) -@pytest.mark.parametrize( - "ndim", - ( - 1, - 2, - 3, - 4, - LEGATE_MAX_DIM, - ), -) +@pytest.mark.parametrize("ndim", ONE_MAX_DIM_RANGE) @pytest.mark.parametrize("mode", ("wrap", "clip")) @pytest.mark.parametrize( "indices", INDICES, ids=lambda indices: f"(indices={indices})" diff --git a/tests/integration/test_put_along_axis.py b/tests/integration/test_put_along_axis.py index ab1414b957..05c6aca741 100644 --- a/tests/integration/test_put_along_axis.py +++ b/tests/integration/test_put_along_axis.py @@ -15,12 +15,12 @@ import numpy as np import pytest -from legate.core import LEGATE_MAX_DIM from utils.generators import ( broadcasts_to, broadcasts_to_along_axis, mk_seq_array, ) +from utils.utils import ONE_MAX_DIM_RANGE import cupynumeric as num @@ -50,7 +50,7 @@ def test_axis_None(): N = 10 -@pytest.mark.parametrize("ndim", (1, 2, 3, 4, LEGATE_MAX_DIM)) +@pytest.mark.parametrize("ndim", ONE_MAX_DIM_RANGE) def test_ndim(ndim): shape = (N,) * ndim np_arr = mk_seq_array(np, shape) diff --git a/tests/integration/test_putmask.py b/tests/integration/test_putmask.py index 9f76653978..0272648c87 100644 --- a/tests/integration/test_putmask.py +++ b/tests/integration/test_putmask.py @@ -15,8 +15,8 @@ import numpy as np import pytest -from legate.core import LEGATE_MAX_DIM from utils.generators import mk_0to1_array, mk_seq_array +from utils.utils import ONE_MAX_DIM_RANGE import cupynumeric as num @@ -118,16 +118,7 @@ def test_type_convert(): assert np.array_equal(x_num, x) -@pytest.mark.parametrize( - "ndim", - ( - 1, - 2, - 3, - 4, - LEGATE_MAX_DIM, - ), -) +@pytest.mark.parametrize("ndim", ONE_MAX_DIM_RANGE) def test_ndim(ndim): shape = (5,) * ndim np_arr = mk_seq_array(np, shape) diff --git a/tests/integration/test_repeat.py b/tests/integration/test_repeat.py index abd2cffad4..eb4b4c4814 100644 --- a/tests/integration/test_repeat.py +++ b/tests/integration/test_repeat.py @@ -14,9 +14,8 @@ # import numpy as np import pytest -from legate.core import LEGATE_MAX_DIM from utils.generators import mk_seq_array -from utils.utils import AxisError +from utils.utils import ONE_MAX_DIM_RANGE, AxisError import cupynumeric as num @@ -201,16 +200,7 @@ def test_array_axis_negative_equal(): assert np.array_equal(res_np, res_num) -@pytest.mark.parametrize( - "ndim", - ( - 1, - 2, - 3, - 4, - LEGATE_MAX_DIM, - ), -) +@pytest.mark.parametrize("ndim", ONE_MAX_DIM_RANGE) def test_nd_basic(ndim): a_shape = tuple(np.random.randint(1, 9) for _ in range(ndim)) np_array = mk_seq_array(np, a_shape) @@ -221,16 +211,7 @@ def test_nd_basic(ndim): assert np.array_equal(res_num, res_np) -@pytest.mark.parametrize( - "ndim", - ( - 1, - 2, - 3, - 4, - LEGATE_MAX_DIM, - ), -) +@pytest.mark.parametrize("ndim", ONE_MAX_DIM_RANGE) def test_nd_axis(ndim): for axis in range(0, ndim): a_shape = tuple(np.random.randint(1, 9) for _ in range(ndim)) @@ -242,16 +223,7 @@ def test_nd_axis(ndim): assert np.array_equal(res_num2, res_np2) -@pytest.mark.parametrize( - "ndim", - ( - 1, - 2, - 3, - 4, - LEGATE_MAX_DIM, - ), -) +@pytest.mark.parametrize("ndim", ONE_MAX_DIM_RANGE) def test_nd_repeats(ndim): a_shape = tuple(np.random.randint(1, 9) for _ in range(ndim)) np_array = mk_seq_array(np, a_shape) diff --git a/tests/integration/test_round.py b/tests/integration/test_round.py index 2e433ceb4a..22bce80c02 100644 --- a/tests/integration/test_round.py +++ b/tests/integration/test_round.py @@ -15,8 +15,8 @@ import numpy as np import pytest -from legate.core import LEGATE_MAX_DIM from utils.generators import mk_0to1_array +from utils.utils import MAX_DIM_RANGE import cupynumeric as num @@ -40,16 +40,7 @@ def test_empty_array(decimals): @pytest.mark.parametrize("decimals", range(-3, 3)) -@pytest.mark.parametrize( - "ndim", - ( - 1, - 2, - 3, - 4, - LEGATE_MAX_DIM, - ), -) +@pytest.mark.parametrize("ndim", MAX_DIM_RANGE) def test_basic_float16(ndim, decimals): shape = (3,) * ndim np_arr = mk_0to1_array(np, shape, dtype=np.float16) @@ -62,16 +53,7 @@ def test_basic_float16(ndim, decimals): @pytest.mark.parametrize("decimals", range(-5, 5)) -@pytest.mark.parametrize( - "ndim", - ( - 1, - 2, - 3, - 4, - LEGATE_MAX_DIM, - ), -) +@pytest.mark.parametrize("ndim", MAX_DIM_RANGE) @pytest.mark.parametrize("dtype", FLOAT) def test_basic_float(dtype, ndim, decimals): shape = (3,) * ndim @@ -85,16 +67,7 @@ def test_basic_float(dtype, ndim, decimals): @pytest.mark.parametrize("decimals", range(-5, 5)) -@pytest.mark.parametrize( - "ndim", - ( - 1, - 2, - 3, - 4, - LEGATE_MAX_DIM, - ), -) +@pytest.mark.parametrize("ndim", MAX_DIM_RANGE) @pytest.mark.parametrize("dtype", FLOAT) def test_randomized_float(dtype, ndim, decimals): shape = (3,) * ndim @@ -109,16 +82,7 @@ def test_randomized_float(dtype, ndim, decimals): @pytest.mark.parametrize("decimals", range(-5, 5)) -@pytest.mark.parametrize( - "ndim", - ( - 1, - 2, - 3, - 4, - LEGATE_MAX_DIM, - ), -) +@pytest.mark.parametrize("ndim", MAX_DIM_RANGE) @pytest.mark.parametrize("dtype", COMPLEX) def test_randomized_complex(dtype, ndim, decimals): shape = (1,) * ndim diff --git a/tests/integration/test_searchsorted.py b/tests/integration/test_searchsorted.py index 992575afa8..43a749fd00 100644 --- a/tests/integration/test_searchsorted.py +++ b/tests/integration/test_searchsorted.py @@ -15,7 +15,7 @@ import numpy as np import pytest -from legate.core import LEGATE_MAX_DIM +from utils.utils import MAX_DIM_RANGE import cupynumeric as num @@ -208,17 +208,7 @@ def test_standard_cases(volume, dtype, side): check_api(generate_random(volume, dtype), side=side) -@pytest.mark.parametrize( - "ndim", - ( - 0, - 1, - 2, - 3, - 4, - LEGATE_MAX_DIM, - ), -) +@pytest.mark.parametrize("ndim", MAX_DIM_RANGE) @pytest.mark.parametrize("side", SIDES) def test_ndim(ndim, side): a = np.random.randint(-100, 100, size=100) diff --git a/tests/integration/test_setflags.py b/tests/integration/test_setflags.py index 9e06b8f0ba..86cbbf0faf 100644 --- a/tests/integration/test_setflags.py +++ b/tests/integration/test_setflags.py @@ -14,7 +14,7 @@ # import numpy as np import pytest -from legate.core import LEGATE_MAX_DIM +from utils.utils import MAX_DIM_RANGE import cupynumeric as num @@ -98,16 +98,7 @@ def test_logic(): # cuPyNumeric: ValueError: cannot set WRITEBACKIFCOPY flag to True -@pytest.mark.parametrize( - "ndim", - ( - 1, - 2, - 3, - 4, - LEGATE_MAX_DIM, - ), -) +@pytest.mark.parametrize("ndim", MAX_DIM_RANGE) def test_set_write_true(ndim): shape = (3,) * ndim array_np = np.random.randint(1, 100, shape, dtype=int) @@ -117,16 +108,7 @@ def test_set_write_true(ndim): assert array_np.flags["WRITEABLE"] == array_num.flags["WRITEABLE"] -@pytest.mark.parametrize( - "ndim", - ( - 1, - 2, - 3, - 4, - LEGATE_MAX_DIM, - ), -) +@pytest.mark.parametrize("ndim", MAX_DIM_RANGE) def test_set_write_false(ndim): shape = (3,) * ndim array_np = np.random.randint(1, 100, shape, dtype=int) @@ -136,16 +118,7 @@ def test_set_write_false(ndim): assert array_np.flags["WRITEABLE"] == array_num.flags["WRITEABLE"] -@pytest.mark.parametrize( - "ndim", - ( - 1, - 2, - 3, - 4, - LEGATE_MAX_DIM, - ), -) +@pytest.mark.parametrize("ndim", MAX_DIM_RANGE) def test_set_align_true(ndim): shape = (3,) * ndim array_np = np.random.randint(1, 100, shape, dtype=int) @@ -156,16 +129,7 @@ def test_set_align_true(ndim): @pytest.mark.xfail -@pytest.mark.parametrize( - "ndim", - ( - 1, - 2, - 3, - 4, - LEGATE_MAX_DIM, - ), -) +@pytest.mark.parametrize("ndim", MAX_DIM_RANGE) def test_set_align_false(ndim): shape = (3,) * ndim array_np = np.random.randint(1, 100, shape, dtype=int) diff --git a/tests/integration/test_singleton_access.py b/tests/integration/test_singleton_access.py index c728a17977..8278d9f14d 100644 --- a/tests/integration/test_singleton_access.py +++ b/tests/integration/test_singleton_access.py @@ -15,20 +15,14 @@ import numpy as np import pytest -from legate.core import LEGATE_MAX_DIM from utils.generators import mk_0to1_array, scalar_gen +from utils.utils import MAX_DIM_RANGE import cupynumeric as num def nonscalar_gen(lib): - for ndim in ( - 1, - 2, - 3, - 4, - LEGATE_MAX_DIM, - ): + for ndim in MAX_DIM_RANGE: yield mk_0to1_array(lib, ndim * (5,)) diff --git a/tests/integration/test_take.py b/tests/integration/test_take.py index d20bc6506b..ee967b77d6 100644 --- a/tests/integration/test_take.py +++ b/tests/integration/test_take.py @@ -15,8 +15,8 @@ import numpy as np import pytest -from legate.core import LEGATE_MAX_DIM from utils.generators import mk_seq_array +from utils.utils import ONE_MAX_DIM_RANGE import cupynumeric as num @@ -110,16 +110,7 @@ def test_empty_array_and_indices(): ((4,), (0,), pytest.param((2, 2), marks=pytest.mark.xfail)), ids=lambda shape_in: f"(shape_in={shape_in})", ) -@pytest.mark.parametrize( - "ndim", - ( - 1, - 2, - 3, - 4, - LEGATE_MAX_DIM, - ), -) +@pytest.mark.parametrize("ndim", ONE_MAX_DIM_RANGE) def test_ndim_default_mode(ndim, shape_in): # for shape_in=(2, 2) and ndim=4, # In Numpy, pass @@ -147,7 +138,7 @@ def test_ndim_default_mode(ndim, shape_in): ((8,), pytest.param((3, 4), marks=pytest.mark.xfail)), ids=lambda shape_in: f"(shape_in={shape_in})", ) -@pytest.mark.parametrize("ndim", (1, 2, 3, 4, LEGATE_MAX_DIM)) +@pytest.mark.parametrize("ndim", ONE_MAX_DIM_RANGE) def test_ndim_mode(ndim, mode, shape_in): # for shape_in=(3, 4) and ndim=4, # In Numpy, pass diff --git a/tests/integration/test_take_along_axis.py b/tests/integration/test_take_along_axis.py index ce2a701039..abbe466ded 100644 --- a/tests/integration/test_take_along_axis.py +++ b/tests/integration/test_take_along_axis.py @@ -15,24 +15,15 @@ import numpy as np import pytest -from legate.core import LEGATE_MAX_DIM from utils.generators import broadcasts_to_along_axis, mk_seq_array +from utils.utils import ONE_MAX_DIM_RANGE import cupynumeric as num N = 10 -@pytest.mark.parametrize( - "ndim", - ( - 1, - 2, - 3, - 4, - LEGATE_MAX_DIM, - ), -) +@pytest.mark.parametrize("ndim", ONE_MAX_DIM_RANGE) def test_ndim(ndim): shape = (N,) * ndim np_arr = mk_seq_array(np, shape) diff --git a/tests/integration/test_tensordot.py b/tests/integration/test_tensordot.py index 70c712ecec..0c6212173b 100644 --- a/tests/integration/test_tensordot.py +++ b/tests/integration/test_tensordot.py @@ -14,9 +14,9 @@ # import pytest -from legate.core import LEGATE_MAX_DIM from utils.contractions import check_default from utils.generators import mk_0to1_array +from utils.utils import MAX_DIM_RANGE import cupynumeric as num from cupynumeric._utils.linalg import tensordot_modes @@ -29,26 +29,8 @@ def gen_axes(a_ndim, b_ndim): yield ([0, 1], [1, 0]) -@pytest.mark.parametrize( - "b_ndim", - ( - 1, - 2, - 3, - 4, - LEGATE_MAX_DIM, - ), -) -@pytest.mark.parametrize( - "a_ndim", - ( - 1, - 2, - 3, - 4, - LEGATE_MAX_DIM, - ), -) +@pytest.mark.parametrize("b_ndim", MAX_DIM_RANGE) +@pytest.mark.parametrize("a_ndim", MAX_DIM_RANGE) def test_tensordot(a_ndim, b_ndim): for axes in gen_axes(a_ndim, b_ndim): name = f"tensordot({a_ndim} x {b_ndim}, axes={axes})" diff --git a/tests/integration/test_trace.py b/tests/integration/test_trace.py index 1e462b8dae..105d40bbae 100644 --- a/tests/integration/test_trace.py +++ b/tests/integration/test_trace.py @@ -17,8 +17,8 @@ import numpy as np import pytest -from legate.core import LEGATE_MAX_DIM from utils.generators import mk_seq_array +from utils.utils import TWO_MAX_DIM_RANGE import cupynumeric as num @@ -69,15 +69,7 @@ def test_4d(): assert np.array_equal(res, res_num) -@pytest.mark.parametrize( - "ndim", - ( - 2, - 3, - 4, - LEGATE_MAX_DIM, - ), -) +@pytest.mark.parametrize("ndim", TWO_MAX_DIM_RANGE) def test_ndim(ndim): a_shape = tuple(np.random.randint(1, 9) for i in range(ndim)) np_array = mk_seq_array(np, a_shape) diff --git a/tests/integration/test_unique.py b/tests/integration/test_unique.py index 5f48786fca..67e040e5be 100644 --- a/tests/integration/test_unique.py +++ b/tests/integration/test_unique.py @@ -15,7 +15,7 @@ import numpy as np import pytest -from legate.core import LEGATE_MAX_DIM +from utils.utils import MAX_DIM_RANGE import cupynumeric as num @@ -30,16 +30,7 @@ def test_with_nonzero(): assert np.array_equal(b, b_np) -@pytest.mark.parametrize( - "ndim", - ( - 1, - 2, - 3, - 4, - LEGATE_MAX_DIM, - ), -) +@pytest.mark.parametrize("ndim", MAX_DIM_RANGE) def test_ndim(ndim): shape = (4,) * ndim a = num.random.randint(0, 3, size=shape) diff --git a/tests/integration/test_unravel_index.py b/tests/integration/test_unravel_index.py index b3f6a31ffc..a8a1d59e1e 100644 --- a/tests/integration/test_unravel_index.py +++ b/tests/integration/test_unravel_index.py @@ -15,8 +15,8 @@ import numpy as np import pytest -from legate.core import LEGATE_MAX_DIM from utils.generators import mk_seq_array +from utils.utils import ONE_MAX_DIM_RANGE import cupynumeric as num @@ -122,16 +122,7 @@ def test_large_index(): assert np.array_equal(res_num, res_np) -@pytest.mark.parametrize( - "ndim", - ( - 1, - 2, - 3, - 4, - LEGATE_MAX_DIM, - ), -) +@pytest.mark.parametrize("ndim", ONE_MAX_DIM_RANGE) @pytest.mark.parametrize( "order", ( @@ -150,16 +141,7 @@ def test_basic(ndim, order): assert np.array_equal(res_num, res_np) -@pytest.mark.parametrize( - "ndim", - ( - 1, - 2, - 3, - 4, - LEGATE_MAX_DIM, - ), -) +@pytest.mark.parametrize("ndim", ONE_MAX_DIM_RANGE[:-1]) @pytest.mark.parametrize( "order", ( diff --git a/tests/integration/utils/utils.py b/tests/integration/utils/utils.py index c75198bcb1..215b8b6478 100644 --- a/tests/integration/utils/utils.py +++ b/tests/integration/utils/utils.py @@ -14,6 +14,7 @@ # import numpy as np +from legate.core import LEGATE_MAX_DIM import cupynumeric as num from cupynumeric._utils import is_np2 @@ -103,3 +104,13 @@ def check_module_function( a = getattr(np, fn)(*args, **kwargs) b = getattr(num, fn)(*args, **kwargs) compare_array_and_print_results(a, b, print_msg, check_type=check_type) + + +# MAX_DIM_RANGE is a list of array dimensions, that is used to test APIs +# on different array dims. We reduce this list to a sub-set of possible +# dimensions to reduce walltime for testing +MAX_DIM_RANGE = list(range(min(4, LEGATE_MAX_DIM))) +if LEGATE_MAX_DIM > MAX_DIM_RANGE[-1]: + MAX_DIM_RANGE.append(LEGATE_MAX_DIM) +ONE_MAX_DIM_RANGE = MAX_DIM_RANGE[1:] +TWO_MAX_DIM_RANGE = MAX_DIM_RANGE[2:] From 150dead85a39b46f15a5af5a7182c1dd8aaf2749 Mon Sep 17 00:00:00 2001 From: XiaLuNV <110973296+XiaLuNV@users.noreply.github.com> Date: Wed, 18 Dec 2024 10:01:09 +0800 Subject: [PATCH 383/462] add cases to enhance fft/fft.py (#542) * fix fft assertion failure --- tests/integration/test_fftshift.py | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/tests/integration/test_fftshift.py b/tests/integration/test_fftshift.py index 1d3fb280e5..d2262ef818 100644 --- a/tests/integration/test_fftshift.py +++ b/tests/integration/test_fftshift.py @@ -40,6 +40,13 @@ def test_fftshift_axis(): assert np.array_equal(a_num, a_np) +def test_fftshift_axis_int() -> None: + freqs = np.fft.fftfreq(9, d=1.0 / 9).reshape(3, 3) + a_np = np.fft.fftshift(freqs, axes=1) + a_num = num.fft.fftshift(freqs, axes=1) + assert np.array_equal(a_num, a_np) + + def test_ifftshift_1d(): freqs = np.fft.fftshift(np.fft.fftfreq(10, 0.1)) a_np = np.fft.ifftshift(freqs) @@ -64,6 +71,15 @@ def test_ifftshift_axis(): assert np.array_equal(a_num, a_np) +def test_ifftshift_axis_int() -> None: + freqs = np.fft.fftshift( + np.fft.fftfreq(9, d=1.0 / 9).reshape(3, 3), axes=(1,) + ) + a_np = np.fft.ifftshift(freqs, axes=1) + a_num = num.fft.ifftshift(freqs, axes=1) + assert np.array_equal(a_num, a_np) + + if __name__ == "__main__": import sys From 9f0e103f3c3402f1e8128c5a6671adb86d0f58cd Mon Sep 17 00:00:00 2001 From: XiaLuNV <110973296+XiaLuNV@users.noreply.github.com> Date: Fri, 20 Dec 2024 09:53:37 +0800 Subject: [PATCH 384/462] enhance for test_squeeze.py (#524) * enhance for test_squeeze.py --- tests/integration/test_squeeze.py | 32 ++++++++++++------------------- 1 file changed, 12 insertions(+), 20 deletions(-) diff --git a/tests/integration/test_squeeze.py b/tests/integration/test_squeeze.py index ba3d87b3a3..ade60a1acc 100644 --- a/tests/integration/test_squeeze.py +++ b/tests/integration/test_squeeze.py @@ -55,36 +55,28 @@ def test_none_array(): num.squeeze(None) -def test_num_invalid_axis(): +def test_invalid_axis() -> None: size = (1, 2, 1) - a = num.random.randint(low=-10, high=10, size=size) + a_np = np.random.randint(low=-10, high=10, size=size) msg = r"one" with pytest.raises(ValueError, match=msg): - num.squeeze(a, axis=1) + np.squeeze(a_np, axis=1) - -def test_array_invalid_axis(): - size = (1, 2, 1) - a = num.random.randint(low=-10, high=10, size=size) - msg = r"one" + a_num = num.array(a_np) with pytest.raises(ValueError, match=msg): - a.squeeze(axis=1) + num.squeeze(a_num, axis=1) -def test_num_axis_out_bound(): +def test_axis_out_bound() -> None: size = (1, 2, 1) - a = num.random.randint(low=-10, high=10, size=size) - msg = r"bounds" + a_np = np.random.randint(low=-10, high=10, size=size) + msg = r"axis 3 is out of bounds for array of dimension 3" with pytest.raises(AxisError, match=msg): - num.squeeze(a, axis=3) - + np.squeeze(a_np, axis=3) -def test_array_axis_out_bound(): - size = (1, 2, 1) - a = num.random.randint(-10, 10, size=size) - msg = r"bounds" - with pytest.raises(AxisError, match=msg): - a.squeeze(axis=3) + a_num = num.array(a_np) + with pytest.raises(ValueError, match=msg): + num.squeeze(a_num, axis=3) @pytest.mark.parametrize("axes", (-1, -3)) From b18e3258a218ca4ba9e2bed0eb7c7a24cf9e84c2 Mon Sep 17 00:00:00 2001 From: Jacob Faibussowitsch Date: Fri, 20 Dec 2024 09:45:34 -0600 Subject: [PATCH 385/462] Remove `Mapper::task_target()` callback (#545) * See https://github.com/nv-legate/legate.internal/pull/1619 --- cmake/versions.json | 2 +- src/cupynumeric/mapper.cc | 6 ------ src/cupynumeric/mapper.h | 3 --- tests/cpp/integration/test_repartition.cc | 6 ------ 4 files changed, 1 insertion(+), 16 deletions(-) diff --git a/cmake/versions.json b/cmake/versions.json index 5f0143c864..6a627c5580 100644 --- a/cmake/versions.json +++ b/cmake/versions.json @@ -10,7 +10,7 @@ "git_url" : "git@github.com:nv-legate/legate.internal.git", "git_shallow": false, "always_download": false, - "git_tag" : "a66a81804aed194b3f9d69b774aab5eb55aebf82" + "git_tag" : "1d2013372045f33d40c6bfee692e2940b01289d3" } } } diff --git a/src/cupynumeric/mapper.cc b/src/cupynumeric/mapper.cc index bd6583a854..57d5120ddc 100644 --- a/src/cupynumeric/mapper.cc +++ b/src/cupynumeric/mapper.cc @@ -21,12 +21,6 @@ using namespace legate::mapping; namespace cupynumeric { -TaskTarget CuPyNumericMapper::task_target(const legate::mapping::Task& task, - const std::vector& options) -{ - return *options.begin(); -} - Scalar CuPyNumericMapper::tunable_value(TunableID tunable_id) { LEGATE_ABORT("cuPyNumeric does not use any tunable values"); diff --git a/src/cupynumeric/mapper.h b/src/cupynumeric/mapper.h index c1128f7102..7b4022c6c0 100644 --- a/src/cupynumeric/mapper.h +++ b/src/cupynumeric/mapper.h @@ -23,9 +23,6 @@ namespace cupynumeric { class CuPyNumericMapper final : public legate::mapping::Mapper { // Legate mapping functions public: - [[nodiscard]] legate::mapping::TaskTarget task_target( - const legate::mapping::Task& task, - const std::vector& options) override; [[nodiscard]] std::vector store_mappings( const legate::mapping::Task& task, const std::vector& options) override; diff --git a/tests/cpp/integration/test_repartition.cc b/tests/cpp/integration/test_repartition.cc index 4acd665641..030f6338bb 100644 --- a/tests/cpp/integration/test_repartition.cc +++ b/tests/cpp/integration/test_repartition.cc @@ -42,12 +42,6 @@ struct CheckRepartitionTask }; class RepartitionLayoutMapper : public legate::mapping::Mapper { - legate::mapping::TaskTarget task_target( - const legate::mapping::Task& /*task*/, - const std::vector& options) override - { - return options.front(); - } std::vector store_mappings( const legate::mapping::Task& task, const std::vector& options) override From 0c783653561361c966627c362247ecdcbecf93ea Mon Sep 17 00:00:00 2001 From: Manolis Papadakis Date: Tue, 31 Dec 2024 01:35:01 +0200 Subject: [PATCH 386/462] Explicit fallback to __array__() on __buffer__ (#508) (#509) --- cupynumeric/_array/array.py | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/cupynumeric/_array/array.py b/cupynumeric/_array/array.py index 1787affe62..388147185f 100644 --- a/cupynumeric/_array/array.py +++ b/cupynumeric/_array/array.py @@ -313,6 +313,17 @@ def data(self) -> memoryview: """ return self.__array__().data + def __buffer__(self, flags: int, /) -> memoryview: + """ + Python buffer object pointing to the start of the array's data. + + Notes + ----- + This causes the entire (potentially distributed) array to be collected + into one memory. + """ + return self.__array__().__buffer__(flags) # type: ignore + @property def dtype(self) -> np.dtype[Any]: """ From 574e4041fb93a4a4c0ddb3de04ca4f484519747f Mon Sep 17 00:00:00 2001 From: "Marcus D. Hanwell" Date: Tue, 31 Dec 2024 14:28:48 -0500 Subject: [PATCH 387/462] BUG: Improve `CMAKE_CUDA_ARCHITECTURES` logic (#555) This pulls some CMake code from #528 to improve the logic around initializing the `CMAKE_CUDA_ARCHITECTURES` variable in the project. It fixes builds using CMake directly and offers more robust logic in light of `rapids-cmake` deprecating and removing the use of empty string there. This fixes issue #554. --- cmake/thirdparty/get_legate.cmake | 2 ++ cupynumeric_cpp.cmake | 32 ++++++------------------------- 2 files changed, 8 insertions(+), 26 deletions(-) diff --git a/cmake/thirdparty/get_legate.cmake b/cmake/thirdparty/get_legate.cmake index 3050634759..c2c003c3e8 100644 --- a/cmake/thirdparty/get_legate.cmake +++ b/cmake/thirdparty/get_legate.cmake @@ -114,10 +114,12 @@ function(find_or_configure_legate) endif() set(Legion_USE_CUDA ${Legion_USE_CUDA} PARENT_SCOPE) + set(Legion_CUDA_ARCH ${Legion_CUDA_ARCH} PARENT_SCOPE) set(Legion_USE_OpenMP ${Legion_USE_OpenMP} PARENT_SCOPE) set(Legion_BOUNDS_CHECKS ${Legion_BOUNDS_CHECKS} PARENT_SCOPE) message(VERBOSE "Legion_USE_CUDA=${Legion_USE_CUDA}") + message(VERBOSE "Legion_CUDA_ARCH=${Legion_CUDA_ARCH}") message(VERBOSE "Legion_USE_OpenMP=${Legion_USE_OpenMP}") message(VERBOSE "Legion_BOUNDS_CHECKS=${Legion_BOUNDS_CHECKS}") endfunction() diff --git a/cupynumeric_cpp.cmake b/cupynumeric_cpp.cmake index f9d7cbb011..eab57e27ed 100644 --- a/cupynumeric_cpp.cmake +++ b/cupynumeric_cpp.cmake @@ -62,28 +62,6 @@ option(Legion_USE_CUDA "Use CUDA" ON) option(Legion_USE_OpenMP "Use OpenMP" ${OpenMP_FOUND}) option(Legion_BOUNDS_CHECKS "Build cuPyNumeric with bounds checks (expensive)" OFF) -# If legate has CUDA support, then including it in a project will automatically call -# enable_language(CUDA). However, this does not play nice with the rapids-cmake CUDA utils -# which support a wider range of values for CMAKE_CUDA_ARCHITECTURES than cmake does. You -# end up with the following error: -# -# CMAKE_CUDA_ARCHITECTURES: -# -# RAPIDS -# -# is not one of the following: -# -# * a semicolon-separated list of integers, each optionally -# followed by '-real' or '-virtual' -# * a special value: all, all-major, native -# -set(cmake_cuda_arch_backup "${CMAKE_CUDA_ARCHITECTURES}") -set(cmake_cuda_arch_cache_backup "$CACHE{CMAKE_CUDA_ARCHITECTURES}") -if(("${CMAKE_CUDA_ARCHITECTURES}" STREQUAL "RAPIDS") OR ("${CMAKE_CUDA_ARCHITECTURES}" STREQUAL "NATIVE")) - unset(CMAKE_CUDA_ARCHITECTURES) - unset(CMAKE_CUDA_ARCHITECTURES CACHE) -endif() - ### # If we find legate already configured on the system, it will report # whether it was compiled with bounds checking (Legion_BOUNDS_CHECKS), @@ -96,10 +74,11 @@ endif() ### include(cmake/thirdparty/get_legate.cmake) -set(CMAKE_CUDA_ARCHITECTURES "${cmake_cuda_arch_cache_backup}" CACHE STRING "" FORCE) -set(CMAKE_CUDA_ARCHITECTURES "${cmake_cuda_arch_backup}") -unset(cmake_cuda_arch_backup) -unset(cmake_cuda_arch_cache_backup) +# Use of DEFINED is deliberate. CMAKE_CUDA_ARCHITECTURES may be OFF which we want to leave +# in place. Legion_CUDA_ARCH is defined by Legate. +if(NOT DEFINED CMAKE_CUDA_ARCHITECTURES) + set(CMAKE_CUDA_ARCHITECTURES "${Legion_CUDA_ARCH}") +endif() if(Legion_USE_CUDA) include(${CMAKE_CURRENT_SOURCE_DIR}/cmake/Modules/cuda_arch_helpers.cmake) @@ -107,6 +86,7 @@ if(Legion_USE_CUDA) set_cuda_arch_from_names() # Needs to run before `enable_language(CUDA)` rapids_cuda_init_architectures(cupynumeric) + message(STATUS "CMAKE_CUDA_ARCHITECTURES=${CMAKE_CUDA_ARCHITECTURES}") enable_language(CUDA) # Since cupynumeric only enables CUDA optionally we need to manually include # the file that rapids_cuda_init_architectures relies on `project` calling From 6e1a539a01faaa9ced0f07912e1fed836fc93429 Mon Sep 17 00:00:00 2001 From: "Marcus D. Hanwell" Date: Tue, 31 Dec 2024 16:33:02 -0500 Subject: [PATCH 388/462] Bump CMake minimum to 3.26.4 (#556) Bump the minimum CMake version to 3.26.4 to match Legate, remove policies that were added before this version. * 77 - CMake 3.13 * 96 - CMake 3.16 * 132 and 135 - CMake 3.24 --- CMakeLists.txt | 27 +-------------------------- 1 file changed, 1 insertion(+), 26 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index e966a64cd6..38de8c6111 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -14,35 +14,10 @@ # limitations under the License. #============================================================================= -cmake_minimum_required(VERSION 3.22.1 FATAL_ERROR) +cmake_minimum_required(VERSION 3.26.4 FATAL_ERROR) cmake_path(SET CUPYNUMERIC_CMAKE_DIR NORMALIZE "${CMAKE_CURRENT_LIST_DIR}/cmake") -if(POLICY CMP0077) - cmake_policy(SET CMP0077 NEW) - set(CMAKE_POLICY_DEFAULT_CMP0077 NEW) -endif() - -if(POLICY CMP0096) - cmake_policy(SET CMP0096 NEW) - set(CMAKE_POLICY_DEFAULT_CMP0096 NEW) -endif() - -if(POLICY CMP0135) - # make the timestamps of ExternalProject_ADD match the download time - # https://cmake.org/cmake/help/latest/policy/CMP0135.html - cmake_policy(SET CMP0135 NEW) - set(CMAKE_POLICY_DEFAULT_CMP0135 NEW) -endif() - -if(POLICY CMP0132) - # Avoid an inconsistency, where cmake would only set the CC/CXX env vars on - # the first run, but not subsequent ones. This would come up when building - # TBLIS. - cmake_policy(SET CMP0132 NEW) - set(CMAKE_POLICY_DEFAULT_CMP0132 NEW) -endif() - set(CMAKE_CXX_STANDARD 17 CACHE STRING "" FORCE) set(CMAKE_CXX_STANDARD_REQUIRED ON CACHE STRING "" FORCE) From d9c849b3fd35edb96f3534de27e44ec9aeb42f08 Mon Sep 17 00:00:00 2001 From: "Marcus D. Hanwell" Date: Thu, 2 Jan 2025 15:12:45 -0500 Subject: [PATCH 389/462] Update to `rapids-cmake` 24.12 (#557) --- CMakeLists.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 38de8c6111..6ba2d85807 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -27,8 +27,8 @@ set(CMAKE_CUDA_STANDARD_REQUIRED ON CACHE STRING "" FORCE) ############################################################################## # - Download and initialize RAPIDS CMake helpers ----------------------------- -set(rapids-cmake-version 24.04) -set(rapids-cmake-sha "365322aca32fd6ecd7027f5d7ec7be50b7f3cc2a") +set(rapids-cmake-version 24.12) +set(rapids-cmake-sha "4cb2123dc08ef5d47ecdc9cc51c96bea7b5bb79c") if(NOT EXISTS ${CMAKE_BINARY_DIR}/RAPIDS.cmake) file(DOWNLOAD https://raw.githubusercontent.com/rapidsai/rapids-cmake/branch-${rapids-cmake-version}/RAPIDS.cmake ${CMAKE_BINARY_DIR}/RAPIDS.cmake) From 43413c097c575ef3eb5327edf433bf20e63b00e5 Mon Sep 17 00:00:00 2001 From: "Marcus D. Hanwell" Date: Thu, 2 Jan 2025 18:26:05 -0500 Subject: [PATCH 390/462] Update to a more modern CMake using target properties (#558) This makes a few changes moving to a more modern style. It adds our CMake module path to the CMake module path, it declares an empty target, adding source files to it instead of accumulating in variables. Also switched to add compiler definitions to the target, and avoid repetition for definitions used in C++ and CUDA. --- CMakeLists.txt | 1 + cupynumeric_cpp.cmake | 61 ++++++++++++++++++------------------------- 2 files changed, 27 insertions(+), 35 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 6ba2d85807..a0bce70437 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -17,6 +17,7 @@ cmake_minimum_required(VERSION 3.26.4 FATAL_ERROR) cmake_path(SET CUPYNUMERIC_CMAKE_DIR NORMALIZE "${CMAKE_CURRENT_LIST_DIR}/cmake") +list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_LIST_DIR}/cmake") set(CMAKE_CXX_STANDARD 17 CACHE STRING "" FORCE) set(CMAKE_CXX_STANDARD_REQUIRED ON CACHE STRING "" FORCE) diff --git a/cupynumeric_cpp.cmake b/cupynumeric_cpp.cmake index eab57e27ed..9871a58875 100644 --- a/cupynumeric_cpp.cmake +++ b/cupynumeric_cpp.cmake @@ -72,7 +72,7 @@ option(Legion_BOUNDS_CHECKS "Build cuPyNumeric with bounds checks (expensive)" O # make sense to build cuPyNumeric's CUDA bindings if legate wasn't built # with CUDA support). ### -include(cmake/thirdparty/get_legate.cmake) +include(thirdparty/get_legate) # Use of DEFINED is deliberate. CMAKE_CUDA_ARCHITECTURES may be OFF which we want to leave # in place. Legion_CUDA_ARCH is defined by Legate. @@ -81,7 +81,7 @@ if(NOT DEFINED CMAKE_CUDA_ARCHITECTURES) endif() if(Legion_USE_CUDA) - include(${CMAKE_CURRENT_SOURCE_DIR}/cmake/Modules/cuda_arch_helpers.cmake) + include(Modules/cuda_arch_helpers) # Needs to run before `rapids_cuda_init_architectures` set_cuda_arch_from_names() # Needs to run before `enable_language(CUDA)` @@ -105,28 +105,28 @@ if(Legion_USE_CUDA) INSTALL_EXPORT_SET cupynumeric-exports ) - include(cmake/thirdparty/get_nccl.cmake) - include(cmake/thirdparty/get_cutensor.cmake) + include(thirdparty/get_nccl) + include(thirdparty/get_cutensor) endif() -include(cmake/thirdparty/get_openblas.cmake) +include(thirdparty/get_openblas) -include(cmake/thirdparty/get_tblis.cmake) +include(thirdparty/get_tblis) ############################################################################## # - cuPyNumeric ---------------------------------------------------------------- -set(cupynumeric_SOURCES "") -set(cupynumeric_CXX_DEFS "") -set(cupynumeric_CUDA_DEFS "") +add_library(cupynumeric) +add_library(cupynumeric::cupynumeric ALIAS cupynumeric) + set(cupynumeric_CXX_OPTIONS "") set(cupynumeric_CUDA_OPTIONS "") -include(cmake/Modules/set_cpu_arch_flags.cmake) +include(Modules/set_cpu_arch_flags) set_cpu_arch_flags(cupynumeric_CXX_OPTIONS) # Add `src/cupynumeric.mk` sources -list(APPEND cupynumeric_SOURCES +target_sources(cupynumeric PRIVATE src/cupynumeric/ternary/where.cc src/cupynumeric/scan/scan_global.cc src/cupynumeric/scan/scan_local.cc @@ -193,7 +193,7 @@ list(APPEND cupynumeric_SOURCES ) if(Legion_USE_OpenMP) - list(APPEND cupynumeric_SOURCES + target_sources(cupynumeric PRIVATE src/cupynumeric/ternary/where_omp.cc src/cupynumeric/scan/scan_global_omp.cc src/cupynumeric/scan/scan_local_omp.cc @@ -245,7 +245,7 @@ if(Legion_USE_OpenMP) endif() if(Legion_USE_CUDA) - list(APPEND cupynumeric_SOURCES + target_sources(cupynumeric PRIVATE src/cupynumeric/ternary/where.cu src/cupynumeric/scan/scan_global.cu src/cupynumeric/scan/scan_local.cu @@ -302,20 +302,20 @@ if(Legion_USE_CUDA) endif() # Add `src/cupynumeric/sort/sort.mk` sources -list(APPEND cupynumeric_SOURCES +target_sources(cupynumeric PRIVATE src/cupynumeric/sort/sort.cc src/cupynumeric/sort/searchsorted.cc ) if(Legion_USE_OpenMP) - list(APPEND cupynumeric_SOURCES + target_sources(cupynumeric PRIVATE src/cupynumeric/sort/sort_omp.cc src/cupynumeric/sort/searchsorted_omp.cc ) endif() if(Legion_USE_CUDA) - list(APPEND cupynumeric_SOURCES + target_sources(cupynumeric PRIVATE src/cupynumeric/sort/sort.cu src/cupynumeric/sort/searchsorted.cu src/cupynumeric/sort/cub_sort_bool.cu @@ -349,23 +349,23 @@ endif() # Add `src/cupynumeric/random/random.mk` sources if(Legion_USE_CUDA) - list(APPEND cupynumeric_SOURCES + target_sources(cupynumeric PRIVATE src/cupynumeric/random/bitgenerator.cu src/cupynumeric/random/randutil/generator_device.cu src/cupynumeric/random/randutil/generator_device_straightforward.cu src/cupynumeric/random/randutil/generator_device_advanced.cu -) + ) endif() # add sources for cusolverMp if(Legion_USE_CUDA AND CUSOLVERMP_DIR) - list(APPEND cupynumeric_SOURCES + target_sources(cupynumeric PRIVATE src/cupynumeric/matrix/mp_potrf.cu src/cupynumeric/matrix/mp_solve.cu ) endif() -list(APPEND cupynumeric_SOURCES +target_sources(cupynumeric PRIVATE # This must always be the last file! # It guarantees we do our registration callback # only after all task variants are recorded @@ -373,13 +373,11 @@ list(APPEND cupynumeric_SOURCES ) if(CMAKE_BUILD_TYPE STREQUAL "Debug") - list(APPEND cupynumeric_CXX_DEFS DEBUG_CUPYNUMERIC) - list(APPEND cupynumeric_CUDA_DEFS DEBUG_CUPYNUMERIC) + target_compile_definitions(cupynumeric PUBLIC "$<$:DEBUG_CUPYNUMERIC>") endif() if(Legion_BOUNDS_CHECKS) - list(APPEND cupynumeric_CXX_DEFS BOUNDS_CHECKS) - list(APPEND cupynumeric_CUDA_DEFS BOUNDS_CHECKS) + target_compile_definitions(cupynumeric PUBLIC "$<$:BOUNDS_CHECKS>") endif() list(APPEND cupynumeric_CUDA_OPTIONS -Xfatbin=-compress-all) @@ -388,9 +386,6 @@ list(APPEND cupynumeric_CUDA_OPTIONS --expt-relaxed-constexpr) list(APPEND cupynumeric_CXX_OPTIONS -Wno-deprecated-declarations) list(APPEND cupynumeric_CUDA_OPTIONS -Wno-deprecated-declarations) -add_library(cupynumeric ${cupynumeric_SOURCES}) -add_library(cupynumeric::cupynumeric ALIAS cupynumeric) - if (CMAKE_SYSTEM_NAME STREQUAL "Linux") set(platform_rpath_origin "\$ORIGIN") elseif (CMAKE_SYSTEM_NAME STREQUAL "Darwin") @@ -409,7 +404,7 @@ set_target_properties(cupynumeric LIBRARY_OUTPUT_DIRECTORY lib) target_link_libraries(cupynumeric - PUBLIC legate::legate + PUBLIC legate::legate $ PRIVATE BLAS::BLAS tblis::tblis @@ -422,14 +417,14 @@ target_link_libraries(cupynumeric $) if(NOT Legion_USE_CUDA AND cupynumeric_cuRAND_INCLUDE_DIR) - list(APPEND cupynumeric_CXX_DEFS CUPYNUMERIC_CURAND_FOR_CPU_BUILD) + target_compile_definitions(cupynumeric + PUBLIC "$<$:CUPYNUMERIC_CURAND_FOR_CPU_BUILD>") target_include_directories(cupynumeric PRIVATE ${cupynumeric_cuRAND_INCLUDE_DIR}) endif() if(Legion_USE_CUDA AND CUSOLVERMP_DIR) message(VERBOSE "cupynumeric: CUSOLVERMP_DIR ${CUSOLVERMP_DIR}") - list(APPEND cupynumeric_CXX_DEFS CUPYNUMERIC_USE_CUSOLVERMP) - list(APPEND cupynumeric_CUDA_DEFS CUPYNUMERIC_USE_CUSOLVERMP) + target_compile_definitions(cupynumeric PUBLIC "$<$:CUPYNUMERIC_USE_CUSOLVERMP>") target_include_directories(cupynumeric PRIVATE ${CUSOLVERMP_DIR}/include) target_link_libraries(cupynumeric PRIVATE ${CUSOLVERMP_DIR}/lib/libcusolverMp.so) endif() @@ -438,10 +433,6 @@ target_compile_options(cupynumeric PRIVATE "$<$:${cupynumeric_CXX_OPTIONS}>" "$<$:${cupynumeric_CUDA_OPTIONS}>") -target_compile_definitions(cupynumeric - PUBLIC "$<$:${cupynumeric_CXX_DEFS}>" - "$<$:${cupynumeric_CUDA_DEFS}>") - target_include_directories(cupynumeric PUBLIC $ From 1e54aa4e4569cba57b247d69fdb23dde01898720 Mon Sep 17 00:00:00 2001 From: Shijie Chen Date: Mon, 6 Jan 2025 13:08:56 +0800 Subject: [PATCH 391/462] Update reshape() implementation to avoid unnecessary deepcopy (#543) * Update reshape() implementation to avoid unnecessary deepcopy * Optimize reshape function and update tests * Refactor reshaping logic and add type hints to test --- cupynumeric/_thunk/deferred.py | 167 ++++++++----------------- tests/integration/test_reshape_copy.py | 42 +++++++ 2 files changed, 91 insertions(+), 118 deletions(-) create mode 100644 tests/integration/test_reshape_copy.py diff --git a/cupynumeric/_thunk/deferred.py b/cupynumeric/_thunk/deferred.py index b94a16aeda..9ddd0b6310 100644 --- a/cupynumeric/_thunk/deferred.py +++ b/cupynumeric/_thunk/deferred.py @@ -14,6 +14,7 @@ # from __future__ import annotations +import math import weakref from collections import Counter from collections.abc import Iterable @@ -1091,134 +1092,64 @@ def reshape(self, newshape: NdShape, order: OrderType) -> NumPyThunk: # performance issues, but we will revisit this decision later once # we have enough evidence that that's not the case. - in_dim = 0 - out_dim = 0 - in_shape = self.shape out_shape = newshape - in_ndim = len(in_shape) - out_ndim = len(out_shape) - - groups = [] - - while in_dim < in_ndim and out_dim < out_ndim: - prev_in_dim = in_dim - prev_out_dim = out_dim - - in_prod = 1 + needs_copy = False + out_pos = 0 + for in_elem in in_shape: out_prod = 1 - - while True: - if in_prod < out_prod: - in_prod *= in_shape[in_dim] - in_dim += 1 - else: - out_prod *= out_shape[out_dim] - out_dim += 1 - if in_prod == out_prod: - if in_dim < in_ndim and in_shape[in_dim] == 1: - in_dim += 1 - break - - in_group = in_shape[prev_in_dim:in_dim] - out_group = out_shape[prev_out_dim:out_dim] - groups.append((in_group, out_group)) - - while in_dim < in_ndim: - assert in_shape[in_dim] == 1 - groups.append(((1,), ())) - in_dim += 1 - - while out_dim < out_ndim: - assert out_shape[out_dim] == 1 - groups.append(((), (1,))) - out_dim += 1 - - needs_linearization = any(len(src_g) > 1 for src_g, _ in groups) - needs_delinearization = any(len(tgt_g) > 1 for _, tgt_g in groups) - needs_copy = needs_linearization or needs_delinearization + while out_prod < in_elem and out_pos < len(out_shape): + out_prod *= out_shape[out_pos] + out_pos += 1 + if out_prod != in_elem: + needs_copy = True + break if needs_copy: - tmp_shape: NdShape = () - for src_g, tgt_g in groups: - if len(src_g) > 1 and len(tgt_g) > 1: - tmp_shape += (_prod(tgt_g),) - else: - tmp_shape += tgt_g - - result = runtime.create_empty_thunk( - tmp_shape, dtype=self.base.type, inputs=[self] + flat_size = math.prod(in_shape) + flat_array = runtime.create_empty_thunk( + (flat_size,), dtype=self.base.type, inputs=[self] ) + in_shape_store = flat_array.base.delinearize(0, in_shape) + out_shape_store = flat_array.base.delinearize(0, out_shape) + in_shape_array = DeferredArray(in_shape_store) + in_shape_array.copy(self, deep=True) + result = DeferredArray(out_shape_store) + return result + + src = self.base + + # Process each dimension from right to left + out_pos = len(out_shape) - 1 + for src_dim, elem_in in zip( + range(len(in_shape) - 1, -1, -1), reversed(in_shape) + ): + if out_pos >= 0 and elem_in == out_shape[out_pos]: + # Case 1: Dimensions match exactly + out_pos -= 1 + continue + + if elem_in == 1: + # Case 2: Input dimension is 1 (projection) + src = src.project(src_dim, 0) + continue + + # Case 3: Delinearize operation + new_sizes = [] + out_prod = 1 + while out_prod < elem_in and out_pos >= 0: + out_prod *= out_shape[out_pos] + new_sizes.append(out_shape[out_pos]) + out_pos -= 1 - src = self.base - tgt = result.base # type: ignore - - src_dim = 0 - tgt_dim = 0 - for src_g, tgt_g in groups: - diff = 1 - if src_g == tgt_g: - assert len(src_g) == 1 - elif len(src_g) == 0: - assert tgt_g == (1,) - src = src.promote(src_dim, 1) - elif len(tgt_g) == 0: - assert src_g == (1,) - tgt = tgt.promote(tgt_dim, 1) - elif len(src_g) == 1: - src = src.delinearize(src_dim, tgt_g) - diff = len(tgt_g) - else: - tgt = tgt.delinearize(tgt_dim, src_g) - diff = len(src_g) - - src_dim += diff - tgt_dim += diff - - assert src.shape == tgt.shape - - src_array = DeferredArray(src) - tgt_array = DeferredArray(tgt) - tgt_array.copy(src_array, deep=True) - - if needs_delinearization and needs_linearization: - src = result.base # type: ignore - src_dim = 0 - for src_g, tgt_g in groups: - if len(src_g) > 1 and len(tgt_g) > 1: - src = src.delinearize(src_dim, tgt_g) - src_dim += len(tgt_g) - - assert src.shape == newshape - src_array = DeferredArray(src) - result = runtime.create_empty_thunk( - newshape, dtype=self.base.type, inputs=[self] - ) - result.copy(src_array, deep=True) - - else: - src = self.base - src_dim = 0 - for src_g, tgt_g in groups: - diff = 1 - if src_g == tgt_g: - assert len(src_g) == 1 - elif len(src_g) == 0: - assert tgt_g == (1,) - src = src.promote(src_dim, 1) - elif len(tgt_g) == 0: - assert src_g == (1,) - src = src.project(src_dim, 0) - diff = 0 - else: - # unreachable - assert False - - src_dim += diff + src = src.delinearize(src_dim, tuple(reversed(new_sizes))) - result = DeferredArray(src) + # Process remaining output dimensions by adding new dimensions (promotion) + for _ in range(out_pos + 1): + src = src.promote(0, 1) + result = DeferredArray(src) return result def squeeze(self, axis: int | tuple[int, ...] | None) -> DeferredArray: diff --git a/tests/integration/test_reshape_copy.py b/tests/integration/test_reshape_copy.py new file mode 100644 index 0000000000..207727ab27 --- /dev/null +++ b/tests/integration/test_reshape_copy.py @@ -0,0 +1,42 @@ +# Copyright 2024 NVIDIA Corporation +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +import pytest + +import cupynumeric as num + + +@pytest.mark.parametrize( + "in_shape, out_shape", + [ + ((1, 1, 10), (10,)), + ((6, 1, 1), (1, 1, 6)), + ((12, 1), (1, 3, 4)), + ((12, 1, 4), (2, 3, 2, 4)), + ], +) +def test_reshape_no_copy(in_shape: tuple, out_shape: tuple) -> None: + x = num.zeros(in_shape, dtype=num.int32) + y = num.reshape(x, out_shape) + x.fill(1) + + assert y.shape == out_shape + assert num.sum(y) != 0 + + +if __name__ == "__main__": + import sys + + sys.exit(pytest.main(sys.argv)) From bd5f558cfc3eb897f6117e8a01456818bbcf4fd9 Mon Sep 17 00:00:00 2001 From: Shijie Chen Date: Mon, 6 Jan 2025 22:02:58 +0800 Subject: [PATCH 392/462] Revert "Update reshape() implementation to avoid unnecessary deepcopy (#543)" (#559) This reverts commit 1e54aa4e4569cba57b247d69fdb23dde01898720. --- cupynumeric/_thunk/deferred.py | 167 +++++++++++++++++-------- tests/integration/test_reshape_copy.py | 42 ------- 2 files changed, 118 insertions(+), 91 deletions(-) delete mode 100644 tests/integration/test_reshape_copy.py diff --git a/cupynumeric/_thunk/deferred.py b/cupynumeric/_thunk/deferred.py index 9ddd0b6310..b94a16aeda 100644 --- a/cupynumeric/_thunk/deferred.py +++ b/cupynumeric/_thunk/deferred.py @@ -14,7 +14,6 @@ # from __future__ import annotations -import math import weakref from collections import Counter from collections.abc import Iterable @@ -1092,64 +1091,134 @@ def reshape(self, newshape: NdShape, order: OrderType) -> NumPyThunk: # performance issues, but we will revisit this decision later once # we have enough evidence that that's not the case. + in_dim = 0 + out_dim = 0 + in_shape = self.shape out_shape = newshape - needs_copy = False - out_pos = 0 - for in_elem in in_shape: + in_ndim = len(in_shape) + out_ndim = len(out_shape) + + groups = [] + + while in_dim < in_ndim and out_dim < out_ndim: + prev_in_dim = in_dim + prev_out_dim = out_dim + + in_prod = 1 out_prod = 1 - while out_prod < in_elem and out_pos < len(out_shape): - out_prod *= out_shape[out_pos] - out_pos += 1 - if out_prod != in_elem: - needs_copy = True - break + + while True: + if in_prod < out_prod: + in_prod *= in_shape[in_dim] + in_dim += 1 + else: + out_prod *= out_shape[out_dim] + out_dim += 1 + if in_prod == out_prod: + if in_dim < in_ndim and in_shape[in_dim] == 1: + in_dim += 1 + break + + in_group = in_shape[prev_in_dim:in_dim] + out_group = out_shape[prev_out_dim:out_dim] + groups.append((in_group, out_group)) + + while in_dim < in_ndim: + assert in_shape[in_dim] == 1 + groups.append(((1,), ())) + in_dim += 1 + + while out_dim < out_ndim: + assert out_shape[out_dim] == 1 + groups.append(((), (1,))) + out_dim += 1 + + needs_linearization = any(len(src_g) > 1 for src_g, _ in groups) + needs_delinearization = any(len(tgt_g) > 1 for _, tgt_g in groups) + needs_copy = needs_linearization or needs_delinearization if needs_copy: - flat_size = math.prod(in_shape) - flat_array = runtime.create_empty_thunk( - (flat_size,), dtype=self.base.type, inputs=[self] + tmp_shape: NdShape = () + for src_g, tgt_g in groups: + if len(src_g) > 1 and len(tgt_g) > 1: + tmp_shape += (_prod(tgt_g),) + else: + tmp_shape += tgt_g + + result = runtime.create_empty_thunk( + tmp_shape, dtype=self.base.type, inputs=[self] ) - in_shape_store = flat_array.base.delinearize(0, in_shape) - out_shape_store = flat_array.base.delinearize(0, out_shape) - in_shape_array = DeferredArray(in_shape_store) - in_shape_array.copy(self, deep=True) - result = DeferredArray(out_shape_store) - return result - - src = self.base - - # Process each dimension from right to left - out_pos = len(out_shape) - 1 - for src_dim, elem_in in zip( - range(len(in_shape) - 1, -1, -1), reversed(in_shape) - ): - if out_pos >= 0 and elem_in == out_shape[out_pos]: - # Case 1: Dimensions match exactly - out_pos -= 1 - continue - - if elem_in == 1: - # Case 2: Input dimension is 1 (projection) - src = src.project(src_dim, 0) - continue - - # Case 3: Delinearize operation - new_sizes = [] - out_prod = 1 - while out_prod < elem_in and out_pos >= 0: - out_prod *= out_shape[out_pos] - new_sizes.append(out_shape[out_pos]) - out_pos -= 1 - src = src.delinearize(src_dim, tuple(reversed(new_sizes))) + src = self.base + tgt = result.base # type: ignore + + src_dim = 0 + tgt_dim = 0 + for src_g, tgt_g in groups: + diff = 1 + if src_g == tgt_g: + assert len(src_g) == 1 + elif len(src_g) == 0: + assert tgt_g == (1,) + src = src.promote(src_dim, 1) + elif len(tgt_g) == 0: + assert src_g == (1,) + tgt = tgt.promote(tgt_dim, 1) + elif len(src_g) == 1: + src = src.delinearize(src_dim, tgt_g) + diff = len(tgt_g) + else: + tgt = tgt.delinearize(tgt_dim, src_g) + diff = len(src_g) + + src_dim += diff + tgt_dim += diff + + assert src.shape == tgt.shape + + src_array = DeferredArray(src) + tgt_array = DeferredArray(tgt) + tgt_array.copy(src_array, deep=True) + + if needs_delinearization and needs_linearization: + src = result.base # type: ignore + src_dim = 0 + for src_g, tgt_g in groups: + if len(src_g) > 1 and len(tgt_g) > 1: + src = src.delinearize(src_dim, tgt_g) + src_dim += len(tgt_g) + + assert src.shape == newshape + src_array = DeferredArray(src) + result = runtime.create_empty_thunk( + newshape, dtype=self.base.type, inputs=[self] + ) + result.copy(src_array, deep=True) + + else: + src = self.base + src_dim = 0 + for src_g, tgt_g in groups: + diff = 1 + if src_g == tgt_g: + assert len(src_g) == 1 + elif len(src_g) == 0: + assert tgt_g == (1,) + src = src.promote(src_dim, 1) + elif len(tgt_g) == 0: + assert src_g == (1,) + src = src.project(src_dim, 0) + diff = 0 + else: + # unreachable + assert False + + src_dim += diff - # Process remaining output dimensions by adding new dimensions (promotion) - for _ in range(out_pos + 1): - src = src.promote(0, 1) + result = DeferredArray(src) - result = DeferredArray(src) return result def squeeze(self, axis: int | tuple[int, ...] | None) -> DeferredArray: diff --git a/tests/integration/test_reshape_copy.py b/tests/integration/test_reshape_copy.py deleted file mode 100644 index 207727ab27..0000000000 --- a/tests/integration/test_reshape_copy.py +++ /dev/null @@ -1,42 +0,0 @@ -# Copyright 2024 NVIDIA Corporation -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# - -import pytest - -import cupynumeric as num - - -@pytest.mark.parametrize( - "in_shape, out_shape", - [ - ((1, 1, 10), (10,)), - ((6, 1, 1), (1, 1, 6)), - ((12, 1), (1, 3, 4)), - ((12, 1, 4), (2, 3, 2, 4)), - ], -) -def test_reshape_no_copy(in_shape: tuple, out_shape: tuple) -> None: - x = num.zeros(in_shape, dtype=num.int32) - y = num.reshape(x, out_shape) - x.fill(1) - - assert y.shape == out_shape - assert num.sum(y) != 0 - - -if __name__ == "__main__": - import sys - - sys.exit(pytest.main(sys.argv)) From 73d18675e08d87afb2902511c572dcc315fa263c Mon Sep 17 00:00:00 2001 From: XiaLuNV <110973296+XiaLuNV@users.noreply.github.com> Date: Tue, 7 Jan 2025 10:44:34 +0800 Subject: [PATCH 393/462] enhance test_nanquantiles.py (#549) --- tests/integration/test_nanquantiles.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/integration/test_nanquantiles.py b/tests/integration/test_nanquantiles.py index 33c7f92e28..31a24484f3 100644 --- a/tests/integration/test_nanquantiles.py +++ b/tests/integration/test_nanquantiles.py @@ -39,7 +39,7 @@ @pytest.mark.parametrize("str_method", ALL_METHODS) -@pytest.mark.parametrize("axes", (0, 1)) +@pytest.mark.parametrize("axes", (0, 1, (0, 1), (0,))) @pytest.mark.parametrize( "qin_arr", (0.5, [0.001, 0.37, 0.42, 0.67, 0.83, 0.99, 0.39, 0.49, 0.5]) ) From f4cf540bec58ef407e22be18e4b05b3c2f19979a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Malte=20F=C3=B6rster?= <97973773+mfoerste4@users.noreply.github.com> Date: Tue, 7 Jan 2025 13:25:37 +0100 Subject: [PATCH 394/462] override stream on cached handle (#561) --- src/cupynumeric/cudalibs.cu | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/cupynumeric/cudalibs.cu b/src/cupynumeric/cudalibs.cu index a4e89638aa..93f19ca61e 100644 --- a/src/cupynumeric/cudalibs.cu +++ b/src/cupynumeric/cudalibs.cu @@ -264,6 +264,8 @@ cufftPlan* cufftPlanCache::get_cufft_plan(const cufftPlanParams& params) } entry.lru_index = 0; } + auto stream = get_cached_stream(); + CHECK_CUFFT(cufftSetStream(result->handle, stream)); } return result; } From 1d59df56d037e208c9597ff509883adce71ada14 Mon Sep 17 00:00:00 2001 From: Wonchan Lee Date: Tue, 7 Jan 2025 22:18:59 -0800 Subject: [PATCH 395/462] Changes for the one pool world (#387) * Make sure transfer buffers are non-empty * Start specifying pool sizes * Give some reasonable upper bounds for zip, argwhere, and nonzero * Nonzero seems to need more space on the zero-copy memory * Allow temporary allocations for the rest of the tasks * Make sure output buffers are aligned in the sorting task * More updates for temporary allocations * Mark more operations as allocable * Tighter upper bounds for some of the tasks (not tight enough though) * Fill in pool sizes for the rest of tasks * Make sure cuda library handles are destroyed after all tasks are done * Workaround to the hang with multiple sorting calls * Fix the compile issue * Review comments * Bump the legate commit * Address review comments * More review comments * Use the TOT legate --- cmake/versions.json | 2 +- src/cupynumeric/binary/binary_red.h | 2 + src/cupynumeric/convolution/convolve.h | 2 + src/cupynumeric/cupynumeric.cc | 10 +- .../device_scalar_reduction_buffer.h | 4 +- src/cupynumeric/fft/fft.h | 2 + src/cupynumeric/index/advanced_indexing.h | 4 + src/cupynumeric/index/choose.h | 2 + src/cupynumeric/index/repeat.cu | 5 +- src/cupynumeric/index/repeat.h | 4 + src/cupynumeric/index/select.h | 2 + src/cupynumeric/index/wrap.h | 2 + src/cupynumeric/index/zip.h | 2 + src/cupynumeric/mapper.cc | 268 +++++++++++++++++- src/cupynumeric/mapper.h | 2 + src/cupynumeric/matrix/batched_cholesky.h | 2 + src/cupynumeric/matrix/contract.h | 4 + src/cupynumeric/matrix/dot.h | 2 + src/cupynumeric/matrix/matmul.h | 4 + src/cupynumeric/matrix/matvecmul.h | 4 + src/cupynumeric/matrix/potrf.h | 2 + src/cupynumeric/matrix/qr.h | 4 + src/cupynumeric/matrix/solve.h | 4 + src/cupynumeric/matrix/svd.h | 6 +- src/cupynumeric/scan/scan_local.h | 4 + src/cupynumeric/search/argwhere.h | 4 + src/cupynumeric/search/nonzero.cu | 5 +- src/cupynumeric/search/nonzero.h | 4 + src/cupynumeric/set/unique.cc | 6 +- src/cupynumeric/set/unique.cu | 2 +- src/cupynumeric/set/unique.h | 5 + src/cupynumeric/set/unique_reduce.h | 3 + src/cupynumeric/sort/sort.cc | 7 +- src/cupynumeric/sort/sort.cu | 158 +++++++---- src/cupynumeric/sort/sort.h | 7 + src/cupynumeric/sort/sort_cpu.inl | 72 +++-- src/cupynumeric/stat/histogram.h | 4 + src/cupynumeric/transform/flip.cu | 2 +- src/cupynumeric/transform/flip.h | 2 + src/cupynumeric/unary/scalar_unary_red.h | 2 + 40 files changed, 516 insertions(+), 116 deletions(-) diff --git a/cmake/versions.json b/cmake/versions.json index 6a627c5580..80b81707a3 100644 --- a/cmake/versions.json +++ b/cmake/versions.json @@ -10,7 +10,7 @@ "git_url" : "git@github.com:nv-legate/legate.internal.git", "git_shallow": false, "always_download": false, - "git_tag" : "1d2013372045f33d40c6bfee692e2940b01289d3" + "git_tag" : "e22ffadbcf4bb645dd566fef95348e728860ff9a" } } } diff --git a/src/cupynumeric/binary/binary_red.h b/src/cupynumeric/binary/binary_red.h index ec6cfce30a..3d24157485 100644 --- a/src/cupynumeric/binary/binary_red.h +++ b/src/cupynumeric/binary/binary_red.h @@ -33,6 +33,8 @@ class BinaryRedTask : public CuPyNumericTask { public: static constexpr auto TASK_ID = legate::LocalTaskID{CUPYNUMERIC_BINARY_RED}; + static constexpr auto GPU_VARIANT_OPTIONS = legate::VariantOptions{}.with_has_allocations(true); + public: static void cpu_variant(legate::TaskContext context); #if LEGATE_DEFINED(LEGATE_USE_OPENMP) diff --git a/src/cupynumeric/convolution/convolve.h b/src/cupynumeric/convolution/convolve.h index cc6f6aa404..3515c2714e 100644 --- a/src/cupynumeric/convolution/convolve.h +++ b/src/cupynumeric/convolution/convolve.h @@ -41,6 +41,8 @@ class ConvolveTask : public CuPyNumericTask { public: static constexpr auto TASK_ID = legate::LocalTaskID{CUPYNUMERIC_CONVOLVE}; + static constexpr auto GPU_VARIANT_OPTIONS = legate::VariantOptions{}.with_has_allocations(true); + public: static void cpu_variant(legate::TaskContext context); #if LEGATE_DEFINED(LEGATE_USE_OPENMP) diff --git a/src/cupynumeric/cupynumeric.cc b/src/cupynumeric/cupynumeric.cc index 5c2365871a..1674f3fcf4 100644 --- a/src/cupynumeric/cupynumeric.cc +++ b/src/cupynumeric/cupynumeric.cc @@ -59,9 +59,13 @@ void registration_callback() config.max_tasks = CUPYNUMERIC_MAX_TASKS; config.max_reduction_ops = CUPYNUMERIC_MAX_REDOPS; - auto runtime = legate::Runtime::get_runtime(); - auto library = runtime->create_library( - cupynumeric_library_name, config, std::make_unique()); + auto runtime = legate::Runtime::get_runtime(); + constexpr auto options = legate::VariantOptions{}.with_has_allocations(false); + auto library = runtime->create_library( + cupynumeric_library_name, + config, + std::make_unique(), + {{LEGATE_CPU_VARIANT, options}, {LEGATE_GPU_VARIANT, options}, {LEGATE_OMP_VARIANT, options}}); CuPyNumericRegistrar::get_registrar().register_all_tasks(library); CuPyNumericRuntime::initialize(runtime, library); diff --git a/src/cupynumeric/device_scalar_reduction_buffer.h b/src/cupynumeric/device_scalar_reduction_buffer.h index edd94a6e61..e4701df2ac 100644 --- a/src/cupynumeric/device_scalar_reduction_buffer.h +++ b/src/cupynumeric/device_scalar_reduction_buffer.h @@ -27,8 +27,8 @@ class DeviceScalarReductionBuffer { using VAL = typename REDOP::RHS; public: - DeviceScalarReductionBuffer(cudaStream_t stream) - : buffer_(legate::create_buffer(1, legate::Memory::Kind::GPU_FB_MEM)) + DeviceScalarReductionBuffer(cudaStream_t stream, std::size_t alignment = 16) + : buffer_(legate::create_buffer(1, legate::Memory::Kind::GPU_FB_MEM, alignment)) { VAL identity{REDOP::identity}; ptr_ = buffer_.ptr(0); diff --git a/src/cupynumeric/fft/fft.h b/src/cupynumeric/fft/fft.h index f4ed02a151..a5e6d4dd69 100644 --- a/src/cupynumeric/fft/fft.h +++ b/src/cupynumeric/fft/fft.h @@ -34,6 +34,8 @@ class FFTTask : public CuPyNumericTask { public: static constexpr auto TASK_ID = legate::LocalTaskID{CUPYNUMERIC_FFT}; + static constexpr auto GPU_VARIANT_OPTIONS = legate::VariantOptions{}.with_has_allocations(true); + public: #if LEGATE_DEFINED(LEGATE_USE_CUDA) static void gpu_variant(legate::TaskContext context); diff --git a/src/cupynumeric/index/advanced_indexing.h b/src/cupynumeric/index/advanced_indexing.h index c4342cb599..d75100e465 100644 --- a/src/cupynumeric/index/advanced_indexing.h +++ b/src/cupynumeric/index/advanced_indexing.h @@ -32,6 +32,10 @@ class AdvancedIndexingTask : public CuPyNumericTask { public: static constexpr auto TASK_ID = legate::LocalTaskID{CUPYNUMERIC_ADVANCED_INDEXING}; + static constexpr auto CPU_VARIANT_OPTIONS = legate::VariantOptions{}.with_has_allocations(true); + static constexpr auto GPU_VARIANT_OPTIONS = legate::VariantOptions{}.with_has_allocations(true); + static constexpr auto OMP_VARIANT_OPTIONS = legate::VariantOptions{}.with_has_allocations(true); + public: static void cpu_variant(legate::TaskContext context); #if LEGATE_DEFINED(LEGATE_USE_OPENMP) diff --git a/src/cupynumeric/index/choose.h b/src/cupynumeric/index/choose.h index 4a4959c749..930c357db1 100644 --- a/src/cupynumeric/index/choose.h +++ b/src/cupynumeric/index/choose.h @@ -29,6 +29,8 @@ class ChooseTask : public CuPyNumericTask { public: static constexpr auto TASK_ID = legate::LocalTaskID{CUPYNUMERIC_CHOOSE}; + static constexpr auto GPU_VARIANT_OPTIONS = legate::VariantOptions{}.with_has_allocations(true); + public: static void cpu_variant(legate::TaskContext context); #if LEGATE_DEFINED(LEGATE_USE_OPENMP) diff --git a/src/cupynumeric/index/repeat.cu b/src/cupynumeric/index/repeat.cu index f59a3906d7..88d69aa6b1 100644 --- a/src/cupynumeric/index/repeat.cu +++ b/src/cupynumeric/index/repeat.cu @@ -110,7 +110,10 @@ struct RepeatImplBody { auto out = out_array.write_accessor(out_rect); Pitches pitches{}; - auto out_volume = pitches.flatten(out_rect); + const auto out_volume = pitches.flatten(out_rect); + if (out_volume == 0) { + return; + } const auto blocks = (out_volume + THREADS_PER_BLOCK - 1) / THREADS_PER_BLOCK; auto stream = get_cached_stream(); diff --git a/src/cupynumeric/index/repeat.h b/src/cupynumeric/index/repeat.h index b9a0f97945..23f15d423f 100644 --- a/src/cupynumeric/index/repeat.h +++ b/src/cupynumeric/index/repeat.h @@ -33,6 +33,10 @@ class RepeatTask : public CuPyNumericTask { public: static constexpr auto TASK_ID = legate::LocalTaskID{CUPYNUMERIC_REPEAT}; + static constexpr auto CPU_VARIANT_OPTIONS = legate::VariantOptions{}.with_has_allocations(true); + static constexpr auto GPU_VARIANT_OPTIONS = legate::VariantOptions{}.with_has_allocations(true); + static constexpr auto OMP_VARIANT_OPTIONS = legate::VariantOptions{}.with_has_allocations(true); + public: static void cpu_variant(legate::TaskContext context); #if LEGATE_DEFINED(LEGATE_USE_OPENMP) diff --git a/src/cupynumeric/index/select.h b/src/cupynumeric/index/select.h index f96c26a963..11ae5b00dc 100644 --- a/src/cupynumeric/index/select.h +++ b/src/cupynumeric/index/select.h @@ -30,6 +30,8 @@ class SelectTask : public CuPyNumericTask { public: static constexpr auto TASK_ID = legate::LocalTaskID{CUPYNUMERIC_SELECT}; + static constexpr auto GPU_VARIANT_OPTIONS = legate::VariantOptions{}.with_has_allocations(true); + public: static void cpu_variant(legate::TaskContext context); #if LEGATE_DEFINED(LEGATE_USE_OPENMP) diff --git a/src/cupynumeric/index/wrap.h b/src/cupynumeric/index/wrap.h index 809de5cf3e..472b07cb8c 100644 --- a/src/cupynumeric/index/wrap.h +++ b/src/cupynumeric/index/wrap.h @@ -34,6 +34,8 @@ class WrapTask : public CuPyNumericTask { public: static constexpr auto TASK_ID = legate::LocalTaskID{CUPYNUMERIC_WRAP}; + static constexpr auto GPU_VARIANT_OPTIONS = legate::VariantOptions{}.with_has_allocations(true); + public: static void cpu_variant(legate::TaskContext context); #if LEGATE_DEFINED(LEGATE_USE_OPENMP) diff --git a/src/cupynumeric/index/zip.h b/src/cupynumeric/index/zip.h index 54dc0a0ea4..34e9305a39 100644 --- a/src/cupynumeric/index/zip.h +++ b/src/cupynumeric/index/zip.h @@ -33,6 +33,8 @@ class ZipTask : public CuPyNumericTask { public: static constexpr auto TASK_ID = legate::LocalTaskID{CUPYNUMERIC_ZIP}; + static constexpr auto GPU_VARIANT_OPTIONS = legate::VariantOptions{}.with_has_allocations(true); + public: static void cpu_variant(legate::TaskContext context); #if LEGATE_DEFINED(LEGATE_USE_OPENMP) diff --git a/src/cupynumeric/mapper.cc b/src/cupynumeric/mapper.cc index 57d5120ddc..8c39667f9b 100644 --- a/src/cupynumeric/mapper.cc +++ b/src/cupynumeric/mapper.cc @@ -15,6 +15,7 @@ */ #include "cupynumeric/mapper.h" +#include "legate/utilities/assert.h" using namespace legate; using namespace legate::mapping; @@ -29,7 +30,9 @@ Scalar CuPyNumericMapper::tunable_value(TunableID tunable_id) std::vector CuPyNumericMapper::store_mappings( const mapping::Task& task, const std::vector& options) { - switch (static_cast(task.task_id())) { + const auto task_id = static_cast(task.task_id()); + + switch (task_id) { case CUPYNUMERIC_CONVOLVE: { std::vector mappings; auto inputs = task.inputs(); @@ -216,7 +219,268 @@ std::vector CuPyNumericMapper::store_mappings( return {}; } } - assert(false); + LEGATE_ABORT("Unsupported task id: " + std::to_string(task_id)); + return {}; +} + +namespace { + +// Use an accessor type with the maximum number of dimensions for the size approximation +using ACC_TYPE = legate::AccessorRO; + +[[nodiscard]] constexpr std::size_t aligned_size(std::size_t size, std::size_t alignment) +{ + return (size + alignment - 1) / alignment * alignment; +} + +constexpr std::size_t DEFAULT_ALIGNMENT = 16; + +} // namespace + +std::optional CuPyNumericMapper::allocation_pool_size( + const legate::mapping::Task& task, legate::mapping::StoreTarget memory_kind) +{ + const auto task_id = static_cast(task.task_id()); + + switch (task_id) { + case CUPYNUMERIC_ADVANCED_INDEXING: { + if (memory_kind == legate::mapping::StoreTarget::ZCMEM) { + return 0; + } + return std::nullopt; + } + case CUPYNUMERIC_ARGWHERE: { + auto&& input = task.input(0); + auto in_count = input.domain().get_volume(); + auto out_size = in_count * input.dim() * sizeof(std::int64_t); + switch (memory_kind) { + case legate::mapping::StoreTarget::SYSMEM: [[fallthrough]]; + case legate::mapping::StoreTarget::SOCKETMEM: { + return out_size; + } + case legate::mapping::StoreTarget::FBMEM: { + return out_size + in_count * sizeof(std::int64_t); + } + case legate::mapping::StoreTarget::ZCMEM: { + return 0; + } + } + } + case CUPYNUMERIC_BATCHED_CHOLESKY: [[fallthrough]]; + case CUPYNUMERIC_POTRF: [[fallthrough]]; + // FIXME(wonchanl): These tasks actually don't need unbound pools on CPUs. They are being used + // only to finish up the first implementation quickly + case CUPYNUMERIC_QR: [[fallthrough]]; + case CUPYNUMERIC_SOLVE: [[fallthrough]]; + case CUPYNUMERIC_SVD: { + if (memory_kind == legate::mapping::StoreTarget::ZCMEM) { + return aligned_size(sizeof(std::int32_t), DEFAULT_ALIGNMENT); + } + return std::nullopt; + } + case CUPYNUMERIC_BINARY_RED: { + return memory_kind == legate::mapping::StoreTarget::FBMEM + ? aligned_size(sizeof(bool), DEFAULT_ALIGNMENT) + : 0; + } + case CUPYNUMERIC_CHOOSE: { + return memory_kind == legate::mapping::StoreTarget::ZCMEM + ? sizeof(ACC_TYPE) * task.num_inputs() + : 0; + } + case CUPYNUMERIC_CONTRACT: { + switch (memory_kind) { + case legate::mapping::StoreTarget::SYSMEM: [[fallthrough]]; + case legate::mapping::StoreTarget::SOCKETMEM: { + auto&& lhs = task.reduction(0); + if (lhs.type().code() != legate::Type::Code::FLOAT16) { + return 0; + } + constexpr auto compute_buffer_size = [](auto&& arr) { + return aligned_size(arr.domain().get_volume() * sizeof(float), DEFAULT_ALIGNMENT); + }; + return compute_buffer_size(lhs) + compute_buffer_size(task.input(0)) + + compute_buffer_size(task.input(1)); + } + case legate::mapping::StoreTarget::FBMEM: { + return std::nullopt; + } + case legate::mapping::StoreTarget::ZCMEM: { + return 0; + } + } + } + case CUPYNUMERIC_CONVOLVE: { + if (memory_kind == legate::mapping::StoreTarget::ZCMEM) { + return 0; + } + return std::nullopt; + } + case CUPYNUMERIC_DOT: { + return memory_kind == legate::mapping::StoreTarget::FBMEM + ? aligned_size(task.reduction(0).type().size(), DEFAULT_ALIGNMENT) + : 0; + } + case CUPYNUMERIC_FFT: { + if (memory_kind == legate::mapping::StoreTarget::ZCMEM) { + return 0; + } + return std::nullopt; + } + case CUPYNUMERIC_FLIP: { + return memory_kind == legate::mapping::StoreTarget::ZCMEM + ? sizeof(std::int32_t) * task.scalar(0).values().size() + : 0; + } + case CUPYNUMERIC_HISTOGRAM: { + if (memory_kind == legate::mapping::StoreTarget::ZCMEM) { + return 0; + } + return std::nullopt; + } + case CUPYNUMERIC_MATMUL: [[fallthrough]]; + case CUPYNUMERIC_MATVECMUL: { + switch (memory_kind) { + case legate::mapping::StoreTarget::SYSMEM: [[fallthrough]]; + case legate::mapping::StoreTarget::SOCKETMEM: { + const auto rhs1_idx = task.num_inputs() - 2; + const auto rhs2_idx = task.num_inputs() - 1; + auto&& rhs1 = task.input(rhs1_idx); + if (rhs1.type().code() != legate::Type::Code::FLOAT16) { + return 0; + } + constexpr auto compute_buffer_size = [](auto&& arr) { + return aligned_size(arr.domain().get_volume() * sizeof(float), DEFAULT_ALIGNMENT); + }; + return compute_buffer_size(rhs1) + compute_buffer_size(task.input(rhs2_idx)); + } + // The GPU implementation needs no temporary allocations + case legate::mapping::StoreTarget::FBMEM: [[fallthrough]]; + case legate::mapping::StoreTarget::ZCMEM: { + LEGATE_ABORT("GPU tasks shouldn't reach here"); + return 0; + } + } + } + case CUPYNUMERIC_NONZERO: { + auto&& input = task.input(0); + auto&& output = task.output(0); + auto in_count = input.domain().get_volume(); + auto max_out_size = in_count * output.type().size() * input.dim(); + switch (memory_kind) { + case legate::mapping::StoreTarget::SYSMEM: [[fallthrough]]; + case legate::mapping::StoreTarget::SOCKETMEM: { + return max_out_size; + } + case legate::mapping::StoreTarget::FBMEM: { + // The GPU task creates a buffer to keep offsets + return max_out_size + in_count * sizeof(std::int64_t) + + aligned_size(sizeof(std::uint64_t), DEFAULT_ALIGNMENT); + } + case legate::mapping::StoreTarget::ZCMEM: { + // The doubling here shouldn't be necessary, but the memory fragmentation seems to be + // causing allocation failures even though there's enough space. + return input.dim() * sizeof(std::int64_t*) * 2; + } + } + } + case CUPYNUMERIC_REPEAT: { + if (memory_kind == legate::mapping::StoreTarget::ZCMEM) { + if (const auto scalar_repeats = task.scalar(1).value(); scalar_repeats) { + return 0; + } + const auto axis = task.scalar(0).value(); + const auto in_domain = task.input(0).domain(); + const auto lo = in_domain.lo(); + const auto hi = in_domain.hi(); + return aligned_size((hi[axis] - lo[axis] + 1) * sizeof(std::int64_t), DEFAULT_ALIGNMENT); + } + return std::nullopt; + } + case CUPYNUMERIC_SCALAR_UNARY_RED: { + return memory_kind == legate::mapping::StoreTarget::FBMEM + ? aligned_size(task.reduction(0).type().size(), DEFAULT_ALIGNMENT) + : 0; + } + case CUPYNUMERIC_SCAN_LOCAL: { + if (memory_kind == legate::mapping::StoreTarget::ZCMEM) { + return 0; + } + const auto output = task.output(0); + const auto domain = output.domain(); + const auto ndim = domain.dim; + auto tmp_volume = std::size_t{1}; + for (std::int32_t dim = 0; dim < ndim; ++dim) { + tmp_volume *= + std::max<>(legate::coord_t{0}, domain.rect_data[dim + ndim] - domain.rect_data[dim] + 1); + } + return aligned_size(tmp_volume * output.type().size(), output.type().alignment()); + } + case CUPYNUMERIC_SELECT: { + if (memory_kind == legate::mapping::StoreTarget::ZCMEM) { + return aligned_size(sizeof(ACC_TYPE) * task.num_inputs(), DEFAULT_ALIGNMENT); + } + return 0; + } + case CUPYNUMERIC_SORT: { + // There can be up to seven buffers on the zero-copy memory holding pointers and sizes + auto compute_zc_alloc_size = [&]() -> std::optional { + return task.is_single_task() ? 0 + : 7 * task.get_launch_domain().get_volume() * sizeof(void*); + }; + return memory_kind == legate::mapping::StoreTarget::ZCMEM ? compute_zc_alloc_size() + : std::nullopt; + } + case CUPYNUMERIC_UNIQUE: { + switch (memory_kind) { + case legate::mapping::StoreTarget::SYSMEM: [[fallthrough]]; + case legate::mapping::StoreTarget::SOCKETMEM: { + auto&& input = task.input(0); + return input.domain().get_volume() * input.type().size(); + } + case legate::mapping::StoreTarget::FBMEM: { + return std::nullopt; + } + case legate::mapping::StoreTarget::ZCMEM: { + return task.get_launch_domain().get_volume() * sizeof(std::size_t); + } + } + } + case CUPYNUMERIC_UNIQUE_REDUCE: { + switch (memory_kind) { + case legate::mapping::StoreTarget::SYSMEM: [[fallthrough]]; + case legate::mapping::StoreTarget::SOCKETMEM: { + auto inputs = task.inputs(); + auto elem_type = inputs.front().type(); + auto total_volume = std::size_t{0}; + + for (auto&& input : inputs) { + total_volume += input.domain().get_volume(); + } + return aligned_size(total_volume * elem_type.size(), elem_type.alignment()); + } + // The GPU implementation needs no temporary allocations + case legate::mapping::StoreTarget::FBMEM: [[fallthrough]]; + case legate::mapping::StoreTarget::ZCMEM: { + LEGATE_ABORT("GPU tasks shouldn't reach here"); + return 0; + } + } + } + case CUPYNUMERIC_WRAP: { + if (memory_kind == legate::mapping::StoreTarget::ZCMEM) { + return 0; + } + return aligned_size(sizeof(bool), DEFAULT_ALIGNMENT); + } + case CUPYNUMERIC_ZIP: { + using ACC = legate::AccessorRO; + return memory_kind == legate::mapping::StoreTarget::ZCMEM + ? (task.num_inputs() * sizeof(ACC_TYPE) + 15) + : 0; + } + } + LEGATE_ABORT("Unsupported task id: " + std::to_string(task_id)); return {}; } diff --git a/src/cupynumeric/mapper.h b/src/cupynumeric/mapper.h index 7b4022c6c0..a3d122f4a5 100644 --- a/src/cupynumeric/mapper.h +++ b/src/cupynumeric/mapper.h @@ -27,6 +27,8 @@ class CuPyNumericMapper final : public legate::mapping::Mapper { const legate::mapping::Task& task, const std::vector& options) override; [[nodiscard]] legate::Scalar tunable_value(legate::TunableID tunable_id) override; + [[nodiscard]] std::optional allocation_pool_size( + const legate::mapping::Task& task, legate::mapping::StoreTarget memory_kind) override; }; } // namespace cupynumeric diff --git a/src/cupynumeric/matrix/batched_cholesky.h b/src/cupynumeric/matrix/batched_cholesky.h index e3d9218ca3..36192741c6 100644 --- a/src/cupynumeric/matrix/batched_cholesky.h +++ b/src/cupynumeric/matrix/batched_cholesky.h @@ -25,6 +25,8 @@ class BatchedCholeskyTask : public CuPyNumericTask { public: static constexpr auto TASK_ID = legate::LocalTaskID{CUPYNUMERIC_BATCHED_CHOLESKY}; + static constexpr auto GPU_VARIANT_OPTIONS = legate::VariantOptions{}.with_has_allocations(true); + public: static void cpu_variant(legate::TaskContext context); #if LEGATE_DEFINED(LEGATE_USE_OPENMP) diff --git a/src/cupynumeric/matrix/contract.h b/src/cupynumeric/matrix/contract.h index 25821dbd83..12a0ba8afe 100644 --- a/src/cupynumeric/matrix/contract.h +++ b/src/cupynumeric/matrix/contract.h @@ -33,6 +33,10 @@ class ContractTask : public CuPyNumericTask { public: static constexpr auto TASK_ID = legate::LocalTaskID{CUPYNUMERIC_CONTRACT}; + static constexpr auto CPU_VARIANT_OPTIONS = legate::VariantOptions{}.with_has_allocations(true); + static constexpr auto GPU_VARIANT_OPTIONS = legate::VariantOptions{}.with_has_allocations(true); + static constexpr auto OMP_VARIANT_OPTIONS = legate::VariantOptions{}.with_has_allocations(true); + public: static void cpu_variant(legate::TaskContext context); #if LEGATE_DEFINED(LEGATE_USE_OPENMP) diff --git a/src/cupynumeric/matrix/dot.h b/src/cupynumeric/matrix/dot.h index a680db3928..34c938ca02 100644 --- a/src/cupynumeric/matrix/dot.h +++ b/src/cupynumeric/matrix/dot.h @@ -30,6 +30,8 @@ class DotTask : public CuPyNumericTask { public: static constexpr auto TASK_ID = legate::LocalTaskID{CUPYNUMERIC_DOT}; + static constexpr auto GPU_VARIANT_OPTIONS = legate::VariantOptions{}.with_has_allocations(true); + public: static void cpu_variant(legate::TaskContext context); #if LEGATE_DEFINED(LEGATE_USE_OPENMP) diff --git a/src/cupynumeric/matrix/matmul.h b/src/cupynumeric/matrix/matmul.h index c5fc98340e..d27e1b98f1 100644 --- a/src/cupynumeric/matrix/matmul.h +++ b/src/cupynumeric/matrix/matmul.h @@ -30,6 +30,10 @@ class MatMulTask : public CuPyNumericTask { public: static constexpr auto TASK_ID = legate::LocalTaskID{CUPYNUMERIC_MATMUL}; + // Only the CPU implementation needs temporary allocations due to lack of float16 support + static constexpr auto CPU_VARIANT_OPTIONS = legate::VariantOptions{}.with_has_allocations(true); + static constexpr auto OMP_VARIANT_OPTIONS = legate::VariantOptions{}.with_has_allocations(true); + public: static void cpu_variant(legate::TaskContext context); #if LEGATE_DEFINED(LEGATE_USE_OPENMP) diff --git a/src/cupynumeric/matrix/matvecmul.h b/src/cupynumeric/matrix/matvecmul.h index f53073ad59..8cb1ed23b1 100644 --- a/src/cupynumeric/matrix/matvecmul.h +++ b/src/cupynumeric/matrix/matvecmul.h @@ -30,6 +30,10 @@ class MatVecMulTask : public CuPyNumericTask { public: static constexpr auto TASK_ID = legate::LocalTaskID{CUPYNUMERIC_MATVECMUL}; + // Only the CPU implementation needs temporary allocations due to lack of float16 support + static constexpr auto CPU_VARIANT_OPTIONS = legate::VariantOptions{}.with_has_allocations(true); + static constexpr auto OMP_VARIANT_OPTIONS = legate::VariantOptions{}.with_has_allocations(true); + public: static void cpu_variant(legate::TaskContext context); #if LEGATE_DEFINED(LEGATE_USE_OPENMP) diff --git a/src/cupynumeric/matrix/potrf.h b/src/cupynumeric/matrix/potrf.h index 4d3f4b4211..1c6430a12b 100644 --- a/src/cupynumeric/matrix/potrf.h +++ b/src/cupynumeric/matrix/potrf.h @@ -24,6 +24,8 @@ class PotrfTask : public CuPyNumericTask { public: static constexpr auto TASK_ID = legate::LocalTaskID{CUPYNUMERIC_POTRF}; + static constexpr auto GPU_VARIANT_OPTIONS = legate::VariantOptions{}.with_has_allocations(true); + public: static void cpu_variant(legate::TaskContext context); #if LEGATE_DEFINED(LEGATE_USE_OPENMP) diff --git a/src/cupynumeric/matrix/qr.h b/src/cupynumeric/matrix/qr.h index 4085c1d5ed..f06a3c5828 100644 --- a/src/cupynumeric/matrix/qr.h +++ b/src/cupynumeric/matrix/qr.h @@ -25,6 +25,10 @@ class QrTask : public CuPyNumericTask { static constexpr auto TASK_ID = legate::LocalTaskID{CUPYNUMERIC_QR}; static const char* ERROR_MESSAGE; + static constexpr auto CPU_VARIANT_OPTIONS = legate::VariantOptions{}.with_has_allocations(true); + static constexpr auto GPU_VARIANT_OPTIONS = legate::VariantOptions{}.with_has_allocations(true); + static constexpr auto OMP_VARIANT_OPTIONS = legate::VariantOptions{}.with_has_allocations(true); + public: static void cpu_variant(legate::TaskContext context); #if LEGATE_DEFINED(LEGATE_USE_OPENMP) diff --git a/src/cupynumeric/matrix/solve.h b/src/cupynumeric/matrix/solve.h index ebe88dab4c..a1f8072a5c 100644 --- a/src/cupynumeric/matrix/solve.h +++ b/src/cupynumeric/matrix/solve.h @@ -25,6 +25,10 @@ class SolveTask : public CuPyNumericTask { static constexpr auto TASK_ID = legate::LocalTaskID{CUPYNUMERIC_SOLVE}; static const char* ERROR_MESSAGE; + static constexpr auto CPU_VARIANT_OPTIONS = legate::VariantOptions{}.with_has_allocations(true); + static constexpr auto GPU_VARIANT_OPTIONS = legate::VariantOptions{}.with_has_allocations(true); + static constexpr auto OMP_VARIANT_OPTIONS = legate::VariantOptions{}.with_has_allocations(true); + public: static void cpu_variant(legate::TaskContext context); #if LEGATE_DEFINED(LEGATE_USE_OPENMP) diff --git a/src/cupynumeric/matrix/svd.h b/src/cupynumeric/matrix/svd.h index d6f8cb722a..5144040623 100644 --- a/src/cupynumeric/matrix/svd.h +++ b/src/cupynumeric/matrix/svd.h @@ -25,6 +25,10 @@ class SvdTask : public CuPyNumericTask { static constexpr auto TASK_ID = legate::LocalTaskID{CUPYNUMERIC_SVD}; static const char* ERROR_MESSAGE; + static constexpr auto CPU_VARIANT_OPTIONS = legate::VariantOptions{}.with_has_allocations(true); + static constexpr auto GPU_VARIANT_OPTIONS = legate::VariantOptions{}.with_has_allocations(true); + static constexpr auto OMP_VARIANT_OPTIONS = legate::VariantOptions{}.with_has_allocations(true); + public: static void cpu_variant(legate::TaskContext context); #if LEGATE_DEFINED(LEGATE_USE_OPENMP) @@ -35,4 +39,4 @@ class SvdTask : public CuPyNumericTask { #endif }; -} // namespace cupynumeric \ No newline at end of file +} // namespace cupynumeric diff --git a/src/cupynumeric/scan/scan_local.h b/src/cupynumeric/scan/scan_local.h index 8eb8858a17..10c5e15bb7 100644 --- a/src/cupynumeric/scan/scan_local.h +++ b/src/cupynumeric/scan/scan_local.h @@ -33,6 +33,10 @@ class ScanLocalTask : public CuPyNumericTask { public: static constexpr auto TASK_ID = legate::LocalTaskID{CUPYNUMERIC_SCAN_LOCAL}; + static constexpr auto CPU_VARIANT_OPTIONS = legate::VariantOptions{}.with_has_allocations(true); + static constexpr auto GPU_VARIANT_OPTIONS = legate::VariantOptions{}.with_has_allocations(true); + static constexpr auto OMP_VARIANT_OPTIONS = legate::VariantOptions{}.with_has_allocations(true); + public: static void cpu_variant(legate::TaskContext context); #if LEGATE_DEFINED(LEGATE_USE_OPENMP) diff --git a/src/cupynumeric/search/argwhere.h b/src/cupynumeric/search/argwhere.h index aecb7a6f9b..3a732281bf 100644 --- a/src/cupynumeric/search/argwhere.h +++ b/src/cupynumeric/search/argwhere.h @@ -29,6 +29,10 @@ class ArgWhereTask : public CuPyNumericTask { public: static constexpr auto TASK_ID = legate::LocalTaskID{CUPYNUMERIC_ARGWHERE}; + static constexpr auto CPU_VARIANT_OPTIONS = legate::VariantOptions{}.with_has_allocations(true); + static constexpr auto GPU_VARIANT_OPTIONS = legate::VariantOptions{}.with_has_allocations(true); + static constexpr auto OMP_VARIANT_OPTIONS = legate::VariantOptions{}.with_has_allocations(true); + public: static void cpu_variant(legate::TaskContext context); #if LEGATE_DEFINED(LEGATE_USE_OPENMP) diff --git a/src/cupynumeric/search/nonzero.cu b/src/cupynumeric/search/nonzero.cu index 8e9b2f0a9d..b68298368d 100644 --- a/src/cupynumeric/search/nonzero.cu +++ b/src/cupynumeric/search/nonzero.cu @@ -74,8 +74,9 @@ struct NonzeroImplBody { { auto stream = get_cached_stream(); - auto offsets = create_buffer(volume, legate::Memory::Kind::GPU_FB_MEM); - auto size = compute_offsets(in, pitches, rect, volume, offsets, stream); + auto offsets = + create_buffer(volume, legate::Memory::Kind::GPU_FB_MEM, sizeof(std::int64_t)); + auto size = compute_offsets(in, pitches, rect, volume, offsets, stream); std::vector> results; for (auto& output : outputs) { diff --git a/src/cupynumeric/search/nonzero.h b/src/cupynumeric/search/nonzero.h index 5366d280b5..f799ca4a34 100644 --- a/src/cupynumeric/search/nonzero.h +++ b/src/cupynumeric/search/nonzero.h @@ -29,6 +29,10 @@ class NonzeroTask : public CuPyNumericTask { public: static constexpr auto TASK_ID = legate::LocalTaskID{CUPYNUMERIC_NONZERO}; + static constexpr auto CPU_VARIANT_OPTIONS = legate::VariantOptions{}.with_has_allocations(true); + static constexpr auto GPU_VARIANT_OPTIONS = legate::VariantOptions{}.with_has_allocations(true); + static constexpr auto OMP_VARIANT_OPTIONS = legate::VariantOptions{}.with_has_allocations(true); + public: static void cpu_variant(legate::TaskContext context); #if LEGATE_DEFINED(LEGATE_USE_OPENMP) diff --git a/src/cupynumeric/set/unique.cc b/src/cupynumeric/set/unique.cc index 1aaa0571a2..d3b1a7f095 100644 --- a/src/cupynumeric/set/unique.cc +++ b/src/cupynumeric/set/unique.cc @@ -56,11 +56,7 @@ struct UniqueImplBody { namespace // unnamed { -static void __attribute__((constructor)) register_tasks(void) -{ - UniqueTask::register_variants( - {{LEGATE_GPU_VARIANT, legate::VariantOptions{}.with_concurrent(true)}}); -} +static void __attribute__((constructor)) register_tasks(void) { UniqueTask::register_variants(); } } // namespace } // namespace cupynumeric diff --git a/src/cupynumeric/set/unique.cu b/src/cupynumeric/set/unique.cu index 9f661172b2..7866cc5fb2 100644 --- a/src/cupynumeric/set/unique.cu +++ b/src/cupynumeric/set/unique.cu @@ -59,7 +59,7 @@ static Piece tree_reduce(legate::PhysicalStore& output, { size_t remaining = num_ranks; size_t radix = 2; - auto all_sizes = create_buffer(num_ranks, Memory::Z_COPY_MEM); + auto all_sizes = create_buffer(num_ranks, Memory::Z_COPY_MEM, alignof(std::size_t)); while (remaining > 1) { // TODO: This could be point-to-point, as we don't need all the sizes, diff --git a/src/cupynumeric/set/unique.h b/src/cupynumeric/set/unique.h index 68ca2913a7..7f87e6193f 100644 --- a/src/cupynumeric/set/unique.h +++ b/src/cupynumeric/set/unique.h @@ -24,6 +24,11 @@ class UniqueTask : public CuPyNumericTask { public: static constexpr auto TASK_ID = legate::LocalTaskID{CUPYNUMERIC_UNIQUE}; + static constexpr auto CPU_VARIANT_OPTIONS = legate::VariantOptions{}.with_has_allocations(true); + static constexpr auto GPU_VARIANT_OPTIONS = + legate::VariantOptions{}.with_concurrent(true).with_has_allocations(true); + static constexpr auto OMP_VARIANT_OPTIONS = legate::VariantOptions{}.with_has_allocations(true); + public: static void cpu_variant(legate::TaskContext context); #if LEGATE_DEFINED(LEGATE_USE_OPENMP) diff --git a/src/cupynumeric/set/unique_reduce.h b/src/cupynumeric/set/unique_reduce.h index 34e3fb1dfc..0f03e7c7a3 100644 --- a/src/cupynumeric/set/unique_reduce.h +++ b/src/cupynumeric/set/unique_reduce.h @@ -24,6 +24,9 @@ class UniqueReduceTask : public CuPyNumericTask { public: static constexpr auto TASK_ID = legate::LocalTaskID{CUPYNUMERIC_UNIQUE_REDUCE}; + static constexpr auto CPU_VARIANT_OPTIONS = legate::VariantOptions{}.with_has_allocations(true); + static constexpr auto OMP_VARIANT_OPTIONS = legate::VariantOptions{}.with_has_allocations(true); + public: static void cpu_variant(legate::TaskContext context); #if LEGATE_DEFINED(LEGATE_USE_OPENMP) diff --git a/src/cupynumeric/sort/sort.cc b/src/cupynumeric/sort/sort.cc index 21b24580ef..660476b558 100644 --- a/src/cupynumeric/sort/sort.cc +++ b/src/cupynumeric/sort/sort.cc @@ -73,12 +73,7 @@ struct SortImplBody { namespace // unnamed { -static void __attribute__((constructor)) register_tasks(void) -{ - auto options = legate::VariantOptions{}.with_concurrent(true); - SortTask::register_variants( - {{LEGATE_CPU_VARIANT, options}, {LEGATE_GPU_VARIANT, options}, {LEGATE_OMP_VARIANT, options}}); -} +static void __attribute__((constructor)) register_tasks(void) { SortTask::register_variants(); } } // namespace } // namespace cupynumeric diff --git a/src/cupynumeric/sort/sort.cu b/src/cupynumeric/sort/sort.cu index 7479618069..1109f78da1 100644 --- a/src/cupynumeric/sort/sort.cu +++ b/src/cupynumeric/sort/sort.cu @@ -42,6 +42,17 @@ namespace cupynumeric { +namespace { + +template +legate::Buffer create_non_empty_buffer( + size_t size, legate::Memory::Kind kind = legate::Memory::Kind::NO_MEMKIND) +{ + return legate::create_buffer(std::max(size, size_t{1}), kind, alignof(T)); +} + +} // namespace + template struct support_cub : std::true_type {}; template <> @@ -612,22 +623,22 @@ SegmentMergePiece> merge_all_buffers( size_t merged_size = 0; size_t num_sort_ranks = merge_buffers.size(); Buffer target_offsets = - create_buffer(num_sort_ranks, legate::Memory::Z_COPY_MEM); + create_non_empty_buffer(num_sort_ranks, legate::Memory::Z_COPY_MEM); // loop comparably small -> no init kernel for (size_t i = 0; i < num_sort_ranks; ++i) { target_offsets[i] = merged_size; merged_size += merge_buffers[i].size; } - result.values = create_buffer(merged_size); - result.indices = create_buffer(argsort ? merged_size : 0); - result.segments = create_buffer(segmented ? merged_size : 0); + result.values = create_non_empty_buffer(merged_size); + result.indices = create_non_empty_buffer(argsort ? merged_size : 0); + result.segments = create_non_empty_buffer(segmented ? merged_size : 0); result.size = merged_size; // copy data into result { Buffer val_buffers_ptr = - create_buffer(num_sort_ranks, legate::Memory::Z_COPY_MEM); + create_non_empty_buffer(num_sort_ranks, legate::Memory::Z_COPY_MEM); for (size_t r = 0; r < num_sort_ranks; r++) { val_buffers_ptr[r] = merge_buffers[r].values.ptr(0); } @@ -640,7 +651,7 @@ SegmentMergePiece> merge_all_buffers( val_buffers_ptr, target_offsets, result.values, merged_size, num_sort_ranks); if (argsort) { Buffer idc_buffers_ptr = - create_buffer(num_sort_ranks, legate::Memory::Z_COPY_MEM); + create_non_empty_buffer(num_sort_ranks, legate::Memory::Z_COPY_MEM); for (size_t r = 0; r < num_sort_ranks; r++) { idc_buffers_ptr[r] = merge_buffers[r].indices.ptr(0); } @@ -684,12 +695,12 @@ SegmentMergePiece> merge_all_buffers( SegmentMergePiece source1 = merge_buffers[pos]; SegmentMergePiece source2 = merge_buffers[pos + stride]; auto merged_size = source1.size + source2.size; - auto merged_values = create_buffer(merged_size); - auto merged_indices = create_buffer(argsort ? merged_size : 0); - auto merged_segments = create_buffer(segmented ? merged_size : 0); - auto p_merged_values = merged_values.ptr(0); - auto p_values1 = source1.values.ptr(0); - auto p_values2 = source2.values.ptr(0); + auto merged_values = create_non_empty_buffer(merged_size); + auto merged_indices = create_non_empty_buffer(argsort ? merged_size : 0); + auto merged_segments = create_non_empty_buffer(segmented ? merged_size : 0); + auto p_merged_values = merged_values.ptr(0); + auto p_values1 = source1.values.ptr(0); + auto p_values2 = source2.values.ptr(0); if (segmented) { auto p_merged_segments = merged_segments.ptr(0); @@ -816,7 +827,8 @@ void rebalance_data(SegmentMergePiece& merge_buffer, { // compute diff for each segment const size_t num_segments_l_aligned = get_16b_aligned_count(num_segments_l, sizeof(size_t)); - auto segment_diff = create_buffer(num_segments_l_aligned, legate::Memory::GPU_FB_MEM); + auto segment_diff = + create_non_empty_buffer(num_segments_l_aligned, legate::Memory::GPU_FB_MEM); { // start kernel to search from merge_buffer.segments const size_t num_blocks = (num_segments_l + THREADS_PER_BLOCK - 1) / THREADS_PER_BLOCK; @@ -843,8 +855,8 @@ void rebalance_data(SegmentMergePiece& merge_buffer, #endif // allocate target - Buffer segment_diff_buffers = - create_buffer(num_segments_l_aligned * num_sort_ranks, legate::Memory::GPU_FB_MEM); + Buffer segment_diff_buffers = create_non_empty_buffer( + num_segments_l_aligned * num_sort_ranks, legate::Memory::GPU_FB_MEM); // communicate segment diffs CHECK_NCCL(ncclGroupStart()); @@ -861,8 +873,8 @@ void rebalance_data(SegmentMergePiece& merge_buffer, CHECK_NCCL(ncclGroupEnd()); // copy to transpose structure [segments][ranks] - auto segment_diff_2d = - create_buffer(num_segments_l_aligned * num_sort_ranks, legate::Memory::GPU_FB_MEM); + auto segment_diff_2d = create_non_empty_buffer(num_segments_l_aligned * num_sort_ranks, + legate::Memory::GPU_FB_MEM); // Transpose { @@ -901,12 +913,12 @@ void rebalance_data(SegmentMergePiece& merge_buffer, edge case --> send more than whole line should not happen due to sample choice! */ // 2 (signed) arrays - left/right for every segment - auto send_left = create_buffer(num_segments_l, legate::Memory::GPU_FB_MEM); - auto send_right = create_buffer(num_segments_l, legate::Memory::GPU_FB_MEM); + auto send_left = create_non_empty_buffer(num_segments_l, legate::Memory::GPU_FB_MEM); + auto send_right = create_non_empty_buffer(num_segments_l, legate::Memory::GPU_FB_MEM); // compute data to send.... auto segment_diff_2d_scan = - create_buffer(num_segments_l * num_sort_ranks, legate::Memory::GPU_FB_MEM); + create_non_empty_buffer(num_segments_l * num_sort_ranks, legate::Memory::GPU_FB_MEM); thrust::device_ptr segment_diff_2d_ptr(segment_diff_2d.ptr(0)); thrust::device_ptr segment_diff_2d_scan_ptr(segment_diff_2d_scan.ptr(0)); thrust::inclusive_scan(exec_policy, @@ -970,24 +982,35 @@ void rebalance_data(SegmentMergePiece& merge_buffer, send_right_data.size = send_right_size; recv_right_data.size = recv_right_size; if (argsort) { - send_left_data.indices = create_buffer(send_left_size, legate::Memory::GPU_FB_MEM); - recv_left_data.indices = create_buffer(recv_left_size, legate::Memory::GPU_FB_MEM); - send_right_data.indices = create_buffer(send_right_size, legate::Memory::GPU_FB_MEM); - recv_right_data.indices = create_buffer(recv_right_size, legate::Memory::GPU_FB_MEM); + send_left_data.indices = + create_non_empty_buffer(send_left_size, legate::Memory::GPU_FB_MEM); + recv_left_data.indices = + create_non_empty_buffer(recv_left_size, legate::Memory::GPU_FB_MEM); + send_right_data.indices = + create_non_empty_buffer(send_right_size, legate::Memory::GPU_FB_MEM); + recv_right_data.indices = + create_non_empty_buffer(recv_right_size, legate::Memory::GPU_FB_MEM); } else { - send_left_data.values = create_buffer(send_left_size, legate::Memory::GPU_FB_MEM); - recv_left_data.values = create_buffer(recv_left_size, legate::Memory::GPU_FB_MEM); - send_right_data.values = create_buffer(send_right_size, legate::Memory::GPU_FB_MEM); - recv_right_data.values = create_buffer(recv_right_size, legate::Memory::GPU_FB_MEM); + send_left_data.values = + create_non_empty_buffer(send_left_size, legate::Memory::GPU_FB_MEM); + recv_left_data.values = + create_non_empty_buffer(recv_left_size, legate::Memory::GPU_FB_MEM); + send_right_data.values = + create_non_empty_buffer(send_right_size, legate::Memory::GPU_FB_MEM); + recv_right_data.values = + create_non_empty_buffer(recv_right_size, legate::Memory::GPU_FB_MEM); } Buffer segment_diff_pos; { // need scan of segment_diff // need scan of (positive!) send_left, send_right - segment_diff_pos = create_buffer(num_segments_l, legate::Memory::GPU_FB_MEM); - auto send_left_pos = create_buffer(num_segments_l, legate::Memory::GPU_FB_MEM); - auto send_right_pos = create_buffer(num_segments_l, legate::Memory::GPU_FB_MEM); + segment_diff_pos = + create_non_empty_buffer(num_segments_l, legate::Memory::GPU_FB_MEM); + auto send_left_pos = + create_non_empty_buffer(num_segments_l, legate::Memory::GPU_FB_MEM); + auto send_right_pos = + create_non_empty_buffer(num_segments_l, legate::Memory::GPU_FB_MEM); { thrust::device_ptr segment_diff_ptr(segment_diff.ptr(0)); thrust::device_ptr segment_diff_pos_ptr(segment_diff_pos.ptr(0)); @@ -1138,8 +1161,10 @@ void rebalance_data(SegmentMergePiece& merge_buffer, // merge data into target { // need scan of (negative!) send_left, send_right - auto recv_left_pos = create_buffer(num_segments_l, legate::Memory::GPU_FB_MEM); - auto recv_right_pos = create_buffer(num_segments_l, legate::Memory::GPU_FB_MEM); + auto recv_left_pos = + create_non_empty_buffer(num_segments_l, legate::Memory::GPU_FB_MEM); + auto recv_right_pos = + create_non_empty_buffer(num_segments_l, legate::Memory::GPU_FB_MEM); { thrust::device_ptr recv_left_ptr(send_left.ptr(0)); thrust::device_ptr recv_left_pos_ptr(recv_left_pos.ptr(0)); @@ -1258,7 +1283,7 @@ void sample_sort_nccl_nd( // sort ranks. Note that if segment_size_l>0 && volume==0 means that we have // a full sort group being empty, this should not affect local sort rank size. { - auto worker_count_d = create_buffer(1, legate::Memory::GPU_FB_MEM); + auto worker_count_d = create_non_empty_buffer(1, legate::Memory::GPU_FB_MEM); size_t worker_count = (segment_size_l > 0 ? 1 : 0); CUPYNUMERIC_CHECK_CUDA(cudaMemcpyAsync( worker_count_d.ptr(0), &worker_count, sizeof(int32_t), cudaMemcpyHostToDevice, stream)); @@ -1280,10 +1305,10 @@ void sample_sort_nccl_nd( if (is_unbound_1d_storage) { // we need to return an empty buffer here if (argsort) { - auto buffer = create_buffer(0, legate::Memory::GPU_FB_MEM); + auto buffer = create_non_empty_buffer(0, legate::Memory::GPU_FB_MEM); output_array_unbound.bind_data(buffer, Point<1>(0)); } else { - auto buffer = create_buffer(0, legate::Memory::GPU_FB_MEM); + auto buffer = create_non_empty_buffer(0, legate::Memory::GPU_FB_MEM); output_array_unbound.bind_data(buffer, Point<1>(0)); } } @@ -1302,7 +1327,8 @@ void sample_sort_nccl_nd( size_t num_samples_l = num_samples_per_segment_l * num_segments_l; size_t num_samples_per_segment_g = num_samples_per_segment_l * num_sort_ranks; size_t num_samples_g = num_samples_per_segment_g * num_segments_l; - auto samples = create_buffer>(num_samples_g, legate::Memory::GPU_FB_MEM); + auto samples = + create_non_empty_buffer>(num_samples_g, legate::Memory::GPU_FB_MEM); size_t offset = num_samples_l * my_sort_rank; { @@ -1324,15 +1350,16 @@ void sample_sort_nccl_nd( { // allocate receive buffer const size_t aligned_count = get_16b_aligned_count(num_samples_l, sizeof(SegmentSample)); - auto send_buffer = create_buffer>(aligned_count, legate::Memory::GPU_FB_MEM); + auto send_buffer = + create_non_empty_buffer>(aligned_count, legate::Memory::GPU_FB_MEM); CUPYNUMERIC_CHECK_CUDA(cudaMemcpyAsync(send_buffer.ptr(0), samples.ptr(offset), sizeof(SegmentSample) * num_samples_l, cudaMemcpyDeviceToDevice, stream)); - auto recv_buffer = - create_buffer>(aligned_count * num_sort_ranks, legate::Memory::GPU_FB_MEM); + auto recv_buffer = create_non_empty_buffer>(aligned_count * num_sort_ranks, + legate::Memory::GPU_FB_MEM); CHECK_NCCL(ncclGroupStart()); for (size_t r = 0; r < num_sort_ranks; r++) { @@ -1395,7 +1422,7 @@ void sample_sort_nccl_nd( // select splitters / positions based on samples (on device) // the indexing is split_positions[segments][positions] const size_t num_splitters = (num_sort_ranks - 1) * num_segments_l; - auto split_positions = create_buffer(num_splitters, legate::Memory::GPU_FB_MEM); + auto split_positions = create_non_empty_buffer(num_splitters, legate::Memory::GPU_FB_MEM); { const size_t num_blocks = (num_splitters + THREADS_PER_BLOCK - 1) / THREADS_PER_BLOCK; extract_split_positions_segments<<>>( @@ -1413,12 +1440,12 @@ void sample_sort_nccl_nd( // segment_blocks[r][segment]->position of data in segment for process r // perform blocksize wide scan on size_send[r][block*blocksize] within warp Buffer segment_blocks = - create_buffer(num_segments_l * num_sort_ranks, legate::Memory::GPU_FB_MEM); + create_non_empty_buffer(num_segments_l * num_sort_ranks, legate::Memory::GPU_FB_MEM); // initialize sizes to send const size_t num_segments_l_aligned = get_16b_aligned_count(num_segments_l + 1, sizeof(size_t)); - Buffer size_send = - create_buffer(num_segments_l_aligned * num_sort_ranks, legate::Memory::GPU_FB_MEM); + Buffer size_send = create_non_empty_buffer( + num_segments_l_aligned * num_sort_ranks, legate::Memory::GPU_FB_MEM); { const size_t num_send_parts = num_sort_ranks * num_segments_l; @@ -1446,8 +1473,8 @@ void sample_sort_nccl_nd( ///////////////////////////////////////////////////////////////////////////////////////////////// // all2all exchange send/receive sizes - Buffer size_recv = - create_buffer(num_segments_l_aligned * num_sort_ranks, legate::Memory::GPU_FB_MEM); + Buffer size_recv = create_non_empty_buffer( + num_segments_l_aligned * num_sort_ranks, legate::Memory::GPU_FB_MEM); CHECK_NCCL(ncclGroupStart()); for (size_t r = 0; r < num_sort_ranks; r++) { CHECK_NCCL(ncclSend(size_send.ptr(r * num_segments_l_aligned), @@ -1467,9 +1494,9 @@ void sample_sort_nccl_nd( // we need the amount of data to transfer on the host --> get it Buffer size_send_total = - create_buffer(num_sort_ranks, legate::Memory::Z_COPY_MEM); + create_non_empty_buffer(num_sort_ranks, legate::Memory::Z_COPY_MEM); Buffer size_recv_total = - create_buffer(num_sort_ranks, legate::Memory::Z_COPY_MEM); + create_non_empty_buffer(num_sort_ranks, legate::Memory::Z_COPY_MEM); { CUPYNUMERIC_CHECK_CUDA(cudaMemcpy2DAsync(size_send_total.ptr(0), 1 * sizeof(size_t), @@ -1497,16 +1524,17 @@ void sample_sort_nccl_nd( std::vector> idc_send_buffers(num_sort_ranks); { for (size_t r = 0; r < num_sort_ranks; r++) { - val_send_buffers[r] = create_buffer(size_send_total[r], legate::Memory::GPU_FB_MEM); + val_send_buffers[r] = + create_non_empty_buffer(size_send_total[r], legate::Memory::GPU_FB_MEM); if (argsort) { idc_send_buffers[r] = - create_buffer(size_send_total[r], legate::Memory::GPU_FB_MEM); + create_non_empty_buffer(size_send_total[r], legate::Memory::GPU_FB_MEM); } } { Buffer val_send_buffers_ptr = - create_buffer(num_sort_ranks, legate::Memory::Z_COPY_MEM); + create_non_empty_buffer(num_sort_ranks, legate::Memory::Z_COPY_MEM); for (size_t r = 0; r < num_sort_ranks; r++) { val_send_buffers_ptr[r] = val_send_buffers[r].ptr(0); } @@ -1526,7 +1554,7 @@ void sample_sort_nccl_nd( if (argsort) { Buffer idc_send_buffers_ptr = - create_buffer(num_sort_ranks, legate::Memory::Z_COPY_MEM); + create_non_empty_buffer(num_sort_ranks, legate::Memory::Z_COPY_MEM); for (size_t r = 0; r < num_sort_ranks; r++) { idc_send_buffers_ptr[r] = idc_send_buffers[r].ptr(0); } @@ -1566,7 +1594,8 @@ void sample_sort_nccl_nd( // initialize segment information if (num_segments_l > 1) { - merge_buffers[r].segments = create_buffer(size, legate::Memory::GPU_FB_MEM); + merge_buffers[r].segments = + create_non_empty_buffer(size, legate::Memory::GPU_FB_MEM); // 0 1 2 1 3 // counts per segment to receive // 0 1 3 4 7 // 0 1 2 3 4 5 6 @@ -1592,11 +1621,12 @@ void sample_sort_nccl_nd( merge_buffers[r].segments.ptr(0)); } - merge_buffers[r].values = create_buffer(size, legate::Memory::GPU_FB_MEM); + merge_buffers[r].values = create_non_empty_buffer(size, legate::Memory::GPU_FB_MEM); if (argsort) { - merge_buffers[r].indices = create_buffer(size, legate::Memory::GPU_FB_MEM); + merge_buffers[r].indices = + create_non_empty_buffer(size, legate::Memory::GPU_FB_MEM); } else { - merge_buffers[r].indices = create_buffer(0, legate::Memory::GPU_FB_MEM); + merge_buffers[r].indices = create_non_empty_buffer(0, legate::Memory::GPU_FB_MEM); } } @@ -1744,13 +1774,14 @@ struct SortImplBody { VAL* values_ptr = nullptr; if (argsort) { // make a buffer for input - auto input_copy = create_buffer(volume, legate::Memory::Kind::GPU_FB_MEM); + auto input_copy = create_non_empty_buffer(volume, legate::Memory::Kind::GPU_FB_MEM); local_sorted.values = input_copy; values_ptr = input_copy.ptr(0); // initialize indices if (need_distributed_sort) { - auto indices_buffer = create_buffer(volume, legate::Memory::Kind::GPU_FB_MEM); + auto indices_buffer = + create_non_empty_buffer(volume, legate::Memory::Kind::GPU_FB_MEM); indices_ptr = indices_buffer.ptr(0); local_sorted.indices = indices_buffer; local_sorted.size = volume; @@ -1775,11 +1806,12 @@ struct SortImplBody { } else { // initialize output if (need_distributed_sort) { - auto input_copy = create_buffer(volume, legate::Memory::Kind::GPU_FB_MEM); - values_ptr = input_copy.ptr(0); - local_sorted.values = input_copy; - local_sorted.indices = create_buffer(0, legate::Memory::Kind::GPU_FB_MEM); - local_sorted.size = volume; + auto input_copy = create_non_empty_buffer(volume, legate::Memory::Kind::GPU_FB_MEM); + values_ptr = input_copy.ptr(0); + local_sorted.values = input_copy; + local_sorted.indices = + create_non_empty_buffer(0, legate::Memory::Kind::GPU_FB_MEM); + local_sorted.size = volume; } else { AccessorWO output = output_array.write_accessor(rect); assert(rect.empty() || output.accessor.is_dense_row_major(rect)); diff --git a/src/cupynumeric/sort/sort.h b/src/cupynumeric/sort/sort.h index f3c0cd2c58..04ccf04281 100644 --- a/src/cupynumeric/sort/sort.h +++ b/src/cupynumeric/sort/sort.h @@ -96,6 +96,13 @@ class SortTask : public CuPyNumericTask { public: static constexpr auto TASK_ID = legate::LocalTaskID{CUPYNUMERIC_SORT}; + static constexpr auto CPU_VARIANT_OPTIONS = + legate::VariantOptions{}.with_concurrent(true).with_has_allocations(true); + static constexpr auto GPU_VARIANT_OPTIONS = + legate::VariantOptions{}.with_concurrent(true).with_has_allocations(true); + static constexpr auto OMP_VARIANT_OPTIONS = + legate::VariantOptions{}.with_concurrent(true).with_has_allocations(true); + public: static void cpu_variant(legate::TaskContext context); #if LEGATE_DEFINED(LEGATE_USE_OPENMP) diff --git a/src/cupynumeric/sort/sort_cpu.inl b/src/cupynumeric/sort/sort_cpu.inl index e38fcc1b55..cdcba6cebb 100644 --- a/src/cupynumeric/sort/sort_cpu.inl +++ b/src/cupynumeric/sort/sort_cpu.inl @@ -34,6 +34,17 @@ namespace cupynumeric { +namespace { + +template +legate::Buffer create_non_empty_buffer(size_t size) +{ + return legate::create_buffer( + std::max(size, size_t{1}), legate::Memory::Kind::NO_MEMKIND, alignof(T)); +} + +} // namespace + using namespace legate; // sorts inptr in-place, if argptr not nullptr it returns sort indices @@ -96,7 +107,7 @@ void rebalance_data(SegmentMergePiece& merge_buffer, { // compute diff for each segment - auto segment_diff = create_buffer(num_segments_l); + auto segment_diff = create_non_empty_buffer(num_segments_l); { if (num_segments_l > 1) { auto* p_segments = merge_buffer.segments.ptr(0); @@ -130,7 +141,7 @@ void rebalance_data(SegmentMergePiece& merge_buffer, #endif // allocate target - auto segment_diff_buffers = create_buffer(num_segments_l * num_sort_ranks); + auto segment_diff_buffers = create_non_empty_buffer(num_segments_l * num_sort_ranks); { // using alltoallv to mimic allgather on subset @@ -163,7 +174,7 @@ void rebalance_data(SegmentMergePiece& merge_buffer, } // copy to transpose structure [segments][ranks] (not in-place for now) - auto segment_diff_2d = create_buffer(num_segments_l * num_sort_ranks); + auto segment_diff_2d = create_non_empty_buffer(num_segments_l * num_sort_ranks); { int pos = 0; for (size_t segment = 0; segment < num_segments_l; ++segment) { @@ -199,11 +210,11 @@ void rebalance_data(SegmentMergePiece& merge_buffer, edge case --> send more than whole line should not happen due to sample choice! */ // 2 (signed) arrays - left/right for every segment - auto send_left = create_buffer(num_segments_l); - auto send_right = create_buffer(num_segments_l); + auto send_left = create_non_empty_buffer(num_segments_l); + auto send_right = create_non_empty_buffer(num_segments_l); // compute data to send.... - auto segment_diff_2d_scan = create_buffer(num_segments_l * num_sort_ranks); + auto segment_diff_2d_scan = create_non_empty_buffer(num_segments_l * num_sort_ranks); auto* segment_diff_2d_ptr = segment_diff_2d.ptr(0); auto* segment_diff_2d_scan_ptr = segment_diff_2d_scan.ptr(0); @@ -249,11 +260,11 @@ void rebalance_data(SegmentMergePiece& merge_buffer, recv_leftright_data.size = recv_left_size + recv_right_size; if (argsort) { - send_leftright_data.indices = create_buffer(send_leftright_data.size); - recv_leftright_data.indices = create_buffer(recv_leftright_data.size); + send_leftright_data.indices = create_non_empty_buffer(send_leftright_data.size); + recv_leftright_data.indices = create_non_empty_buffer(recv_leftright_data.size); } else { - send_leftright_data.values = create_buffer(send_leftright_data.size); - recv_leftright_data.values = create_buffer(recv_leftright_data.size); + send_leftright_data.values = create_non_empty_buffer(send_leftright_data.size); + recv_leftright_data.values = create_non_empty_buffer(recv_leftright_data.size); } // copy into send buffer @@ -523,8 +534,8 @@ void sample_sort_nd( size_t num_samples_l = num_samples_per_segment_l * num_segments_l; size_t num_samples_per_segment_g = num_samples_per_segment_l * num_sort_ranks; size_t num_samples_g = num_samples_per_segment_g * num_segments_l; - auto samples_l = create_buffer>(num_samples_l); - auto samples_g = create_buffer>(num_samples_g); + auto samples_l = create_non_empty_buffer>(num_samples_l); + auto samples_g = create_non_empty_buffer>(num_samples_g); auto* p_samples = samples_l.ptr(0); auto* local_values = local_sorted.values.ptr(0); @@ -619,10 +630,10 @@ void sample_sort_nd( // segment_blocks[r][segment]->global position in data for segment and r // perform blocksize wide scan on size_send[r][block*blocksize] within warp - auto segment_blocks = create_buffer(num_sort_ranks * num_segments_l); + auto segment_blocks = create_non_empty_buffer(num_sort_ranks * num_segments_l); // initialize sizes to send [r][segment] - auto size_send = create_buffer(num_sort_ranks * (num_segments_l + 1)); + auto size_send = create_non_empty_buffer(num_sort_ranks * (num_segments_l + 1)); auto p_size_send = size_send.ptr(0); std::fill(p_size_send, p_size_send + num_sort_ranks * (num_segments_l + 1), 0); @@ -685,7 +696,7 @@ void sample_sort_nd( ///////////////////////////////////////////////////////////////////////////////////////////////// // all2all exchange send/receive sizes [r][segment] - auto size_recv = create_buffer(num_sort_ranks * (num_segments_l + 1)); + auto size_recv = create_non_empty_buffer(num_sort_ranks * (num_segments_l + 1)); { // workaround - using alltoallv @@ -714,11 +725,13 @@ void sample_sort_nd( } // copy values into send buffer - auto val_send_buffer = create_buffer(volume); - auto idc_send_buffer = create_buffer(argsort ? volume : 0); + auto val_send_buffer = create_non_empty_buffer(volume); + auto idc_send_buffer = create_non_empty_buffer(argsort ? volume : 0); auto* local_indices = local_sorted.indices.ptr(0); - auto positions = create_buffer(num_sort_ranks); + // This line is particularly problematic, as the following line makes an out-of-bounds access when + // teh buffer is empty + auto positions = create_non_empty_buffer(num_sort_ranks); positions[0] = 0; for (size_t sort_rank = 1; sort_rank < num_sort_ranks; ++sort_rank) { positions[sort_rank] = @@ -759,7 +772,7 @@ void sample_sort_nd( total_receive += size_recv[sort_rank * (num_segments_l + 1) + num_segments_l]; } - merge_buffer.segments = create_buffer(num_segments_l > 1 ? total_receive : 0); + merge_buffer.segments = create_non_empty_buffer(num_segments_l > 1 ? total_receive : 0); if (num_segments_l > 1) { auto* p_segments = merge_buffer.segments.ptr(0); // initialize segment information @@ -774,8 +787,8 @@ void sample_sort_nd( assert(start_pos == total_receive); } - merge_buffer.values = create_buffer(total_receive); - merge_buffer.indices = create_buffer(argsort ? total_receive : 0); + merge_buffer.values = create_non_empty_buffer(total_receive); + merge_buffer.indices = create_non_empty_buffer(argsort ? total_receive : 0); merge_buffer.size = total_receive; } @@ -944,13 +957,14 @@ struct SortImplBodyCpu { VAL* values_ptr = nullptr; if (argsort) { // make a buffer for input - auto input_copy = create_buffer(volume); + auto input_copy = create_buffer(volume, legate::Memory::Kind::NO_MEMKIND, alignof(VAL)); local_sorted.values = input_copy; values_ptr = input_copy.ptr(0); // initialize indices if (need_distributed_sort) { - auto indices_buffer = create_buffer(volume); + auto indices_buffer = + create_buffer(volume, legate::Memory::Kind::NO_MEMKIND, alignof(int64_t)); indices_ptr = indices_buffer.ptr(0); local_sorted.indices = indices_buffer; local_sorted.size = volume; @@ -975,11 +989,13 @@ struct SortImplBodyCpu { } else { // initialize output if (need_distributed_sort) { - auto input_copy = create_buffer(volume); - values_ptr = input_copy.ptr(0); - local_sorted.values = input_copy; - local_sorted.indices = create_buffer(0); - local_sorted.size = volume; + auto input_copy = + create_buffer(volume, legate::Memory::Kind::NO_MEMKIND, alignof(VAL)); + values_ptr = input_copy.ptr(0); + local_sorted.values = input_copy; + local_sorted.indices = + create_buffer(0, legate::Memory::Kind::NO_MEMKIND, alignof(int64_t)); + local_sorted.size = volume; } else { AccessorWO output = output_array.write_accessor(rect); assert(rect.empty() || output.accessor.is_dense_row_major(rect)); diff --git a/src/cupynumeric/stat/histogram.h b/src/cupynumeric/stat/histogram.h index 5d5bb27ae9..a986b4e1a4 100644 --- a/src/cupynumeric/stat/histogram.h +++ b/src/cupynumeric/stat/histogram.h @@ -31,6 +31,10 @@ class HistogramTask : public CuPyNumericTask { public: static constexpr auto TASK_ID = legate::LocalTaskID{CUPYNUMERIC_HISTOGRAM}; + static constexpr auto CPU_VARIANT_OPTIONS = legate::VariantOptions{}.with_has_allocations(true); + static constexpr auto OMP_VARIANT_OPTIONS = legate::VariantOptions{}.with_has_allocations(true); + static constexpr auto GPU_VARIANT_OPTIONS = legate::VariantOptions{}.with_has_allocations(true); + public: static void cpu_variant(legate::TaskContext context); #if LEGATE_DEFINED(LEGATE_USE_OPENMP) diff --git a/src/cupynumeric/transform/flip.cu b/src/cupynumeric/transform/flip.cu index a8eee9b87a..b56c29d9f4 100644 --- a/src/cupynumeric/transform/flip.cu +++ b/src/cupynumeric/transform/flip.cu @@ -59,7 +59,7 @@ struct FlipImplBody { const size_t volume = rect.volume(); const size_t blocks = (volume + THREADS_PER_BLOCK - 1) / THREADS_PER_BLOCK; auto num_axes = axes.size(); - auto gpu_axes = create_buffer(num_axes, Memory::Kind::Z_COPY_MEM); + auto gpu_axes = create_buffer(num_axes, Memory::Kind::Z_COPY_MEM, sizeof(int32_t)); for (uint32_t idx = 0; idx < num_axes; ++idx) { gpu_axes[idx] = axes[idx]; } diff --git a/src/cupynumeric/transform/flip.h b/src/cupynumeric/transform/flip.h index 56a637099c..25bfb1d96d 100644 --- a/src/cupynumeric/transform/flip.h +++ b/src/cupynumeric/transform/flip.h @@ -30,6 +30,8 @@ class FlipTask : public CuPyNumericTask { public: static constexpr auto TASK_ID = legate::LocalTaskID{CUPYNUMERIC_FLIP}; + static constexpr auto GPU_VARIANT_OPTIONS = legate::VariantOptions{}.with_has_allocations(true); + public: static void cpu_variant(legate::TaskContext context); #if LEGATE_DEFINED(LEGATE_USE_OPENMP) diff --git a/src/cupynumeric/unary/scalar_unary_red.h b/src/cupynumeric/unary/scalar_unary_red.h index 37f1148f7a..537423be88 100644 --- a/src/cupynumeric/unary/scalar_unary_red.h +++ b/src/cupynumeric/unary/scalar_unary_red.h @@ -35,6 +35,8 @@ class ScalarUnaryRedTask : public CuPyNumericTask { public: static constexpr auto TASK_ID = legate::LocalTaskID{CUPYNUMERIC_SCALAR_UNARY_RED}; + static constexpr auto GPU_VARIANT_OPTIONS = legate::VariantOptions{}.with_has_allocations(true); + public: static void cpu_variant(legate::TaskContext context); #if LEGATE_DEFINED(LEGATE_USE_OPENMP) From e988fc27dd4152506d0b05bf2edcfcc7adb26d2e Mon Sep 17 00:00:00 2001 From: Bryan Van de Ven Date: Wed, 8 Jan 2025 13:35:05 -0800 Subject: [PATCH 396/462] enable version banner (#562) --- docs/cupynumeric/source/conf.py | 1 + docs/cupynumeric/switcher.json | 1 + 2 files changed, 2 insertions(+) diff --git a/docs/cupynumeric/source/conf.py b/docs/cupynumeric/source/conf.py index a0dcd2af8b..a29cf12f33 100644 --- a/docs/cupynumeric/source/conf.py +++ b/docs/cupynumeric/source/conf.py @@ -72,6 +72,7 @@ "This project, i.e., cuPyNumeric, is separate and independent of the CuPy project. CuPy is a registered trademark of Preferred Networks.", # NOQA '', # NOQA ], + "show_version_warning_banner": True, } templates_path = ["_templates"] diff --git a/docs/cupynumeric/switcher.json b/docs/cupynumeric/switcher.json index 26311c3be6..2578b6f35c 100644 --- a/docs/cupynumeric/switcher.json +++ b/docs/cupynumeric/switcher.json @@ -1,6 +1,7 @@ [ { "name": "24.11", + "preferred": true, "version": "24.11", "url": "https://docs.nvidia.com/cupynumeric/24.11/" } From 2533c1ec40a144bb4b8e78329ce515b5a37890c4 Mon Sep 17 00:00:00 2001 From: Bryan Van de Ven Date: Thu, 9 Jan 2025 14:08:30 -0800 Subject: [PATCH 397/462] fix bad blog link (#565) --- docs/cupynumeric/source/faqs.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/cupynumeric/source/faqs.rst b/docs/cupynumeric/source/faqs.rst index b581ad7919..e3acc8b6f7 100644 --- a/docs/cupynumeric/source/faqs.rst +++ b/docs/cupynumeric/source/faqs.rst @@ -22,7 +22,7 @@ array programming library widely used in scientific computing. cuPyNumeric scale idiomatic NumPy programs to multiple GPUs and CPUs and seamlessly interoperates with other Legate libraries. -Check out this `blog post `_ +Check out this `blog post `_ to learn more about cuPyNumeric. When to use python vs legate? From bc19ac2a400648bcea705aa139a2fc2c6d2da5d3 Mon Sep 17 00:00:00 2001 From: Mona Han <129724851+mona-nv@users.noreply.github.com> Date: Fri, 10 Jan 2025 18:38:43 +0800 Subject: [PATCH 398/462] Fix the convolve test failure by adding latest update in c++ code (#563) * Fix the convolve test failure by adding latest update in c++ implementation * Address review comments --- src/cupynumeric/ndarray.cc | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/cupynumeric/ndarray.cc b/src/cupynumeric/ndarray.cc index b1b5af4091..961d118ddc 100644 --- a/src/cupynumeric/ndarray.cc +++ b/src/cupynumeric/ndarray.cc @@ -756,6 +756,8 @@ void NDArray::convolve(NDArray input, NDArray filter) task.add_input(input.store_, p_halo); auto p_output = task.add_output(store_); task.add_scalar_arg(legate::Scalar(shape())); + task.add_scalar_arg( + legate::Scalar(static_cast(CuPyNumericConvolveMethod::CUPYNUMERIC_CONVOLVE_AUTO))); auto offsets = (filter.store_.extents() + 1) / 2; From 5a99959816d63fb510369d84fd7ff8808dcc92c5 Mon Sep 17 00:00:00 2001 From: Mona Han <129724851+mona-nv@users.noreply.github.com> Date: Wed, 15 Jan 2025 09:19:25 +0800 Subject: [PATCH 399/462] Fix the build failure caused by adding new pure virtual method in legate::mapping::Mapper (#570) --- tests/cpp/integration/test_repartition.cc | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/tests/cpp/integration/test_repartition.cc b/tests/cpp/integration/test_repartition.cc index 030f6338bb..f144bcca5b 100644 --- a/tests/cpp/integration/test_repartition.cc +++ b/tests/cpp/integration/test_repartition.cc @@ -77,6 +77,11 @@ class RepartitionLayoutMapper : public legate::mapping::Mapper { { return legate::Scalar{}; } + std::optional allocation_pool_size( + const legate::mapping::Task& /*task*/, legate::mapping::StoreTarget /*memory_kind*/) override + { + return std::nullopt; + } }; int get_rank_row_major(legate::Domain domain, legate::DomainPoint index_point) From 096605396af46f3c1313da6caf73fb878dc2bb88 Mon Sep 17 00:00:00 2001 From: "Marcus D. Hanwell" Date: Tue, 14 Jan 2025 23:02:44 -0500 Subject: [PATCH 400/462] CMake: Remove the `--trace-expand` argument (#571) This is only really necessary when debugging and is too verbose for normal CI runs. --- conda/conda-build/build.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/conda/conda-build/build.sh b/conda/conda-build/build.sh index 5780d7c90a..2a7b6589fc 100644 --- a/conda/conda-build/build.sh +++ b/conda/conda-build/build.sh @@ -40,7 +40,7 @@ echo "Build starting on $(date)" CUDAFLAGS="-isystem ${PREFIX}/include -L${PREFIX}/lib" export CUDAFLAGS -cmake -S . -B build ${CMAKE_ARGS} -DCMAKE_BUILD_PARALLEL_LEVEL=$CPU_COUNT --trace-expand +cmake -S . -B build ${CMAKE_ARGS} -DCMAKE_BUILD_PARALLEL_LEVEL=$CPU_COUNT cmake --build build -j$CPU_COUNT --verbose cmake --install build From 6de86c42ff46fc7f6863d52a57a71cd19fb57d34 Mon Sep 17 00:00:00 2001 From: XiaLuNV <110973296+XiaLuNV@users.noreply.github.com> Date: Thu, 16 Jan 2025 14:09:31 +0800 Subject: [PATCH 401/462] enhance test_diff.py (#564) * enhance test_nanquantiles.py * enhance test_diff.py --- tests/integration/test_diff.py | 24 +++++++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-) diff --git a/tests/integration/test_diff.py b/tests/integration/test_diff.py index 759badb2fc..8eeafd4cf5 100644 --- a/tests/integration/test_diff.py +++ b/tests/integration/test_diff.py @@ -37,6 +37,8 @@ ((5,), 6, 0, None, None), ((5, 5), 5, 1, None, None), ((5, 5), 6, 1, None, None), + ((5, 5), 6, 1, np.array(2), None), + ((5, 5), 6, 1, None, np.array(2)), ], ) def test_diff(args): @@ -54,12 +56,32 @@ def test_diff(args): assert allclose(res_np, res_cn) -def test_diff_nzero(): +def test_diff_nzero() -> None: a = num.ones(100) ad = num.diff(a, n=0) assert a is ad +def test_negative_time() -> None: + arr_np = np.random.random((5, 5)) + arr_num = num.array(arr_np) + msg = r"order must be non-negative but got -1" + with pytest.raises(ValueError, match=msg): + num.diff(arr_num, n=-1) + with pytest.raises(ValueError, match=msg): + np.diff(arr_np, n=-1) + + +def test_scalar() -> None: + arr_np = np.array(2) + arr_num = num.array(2) + msg = "diff requires input that is at least one dimensional" + with pytest.raises(ValueError, match=msg): + np.diff(arr_np) + with pytest.raises(ValueError, match=msg): + num.diff(arr_num) + + if __name__ == "__main__": import sys From f098abe0c42ca2a88a0da24d7a4b5a2b45bb839b Mon Sep 17 00:00:00 2001 From: Manolis Papadakis Date: Thu, 16 Jan 2025 20:17:57 -0800 Subject: [PATCH 402/462] Bump Legate hash (#572) --- cmake/versions.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmake/versions.json b/cmake/versions.json index 80b81707a3..c05eaa3bc5 100644 --- a/cmake/versions.json +++ b/cmake/versions.json @@ -10,7 +10,7 @@ "git_url" : "git@github.com:nv-legate/legate.internal.git", "git_shallow": false, "always_download": false, - "git_tag" : "e22ffadbcf4bb645dd566fef95348e728860ff9a" + "git_tag" : "43294f1355d85f21e1941cd31e31d717162b3a2a" } } } From e5f53eb6e13fae3957489e01ba65492b44e53197 Mon Sep 17 00:00:00 2001 From: Sandeep Datta <128171450+sandeepd-nv@users.noreply.github.com> Date: Sat, 18 Jan 2025 00:13:42 +0530 Subject: [PATCH 403/462] =?UTF-8?q?Added=20a=20tests-pass=20job=20which=20?= =?UTF-8?q?aggregates=20results=20from=20all=20instances=20of=E2=80=A6=20(?= =?UTF-8?q?#576)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Added a tests-pass job which aggregates results from all instances of the build-and-test job to signal overall success or failure. * Updated legate.internal SHA in versions.json. * Fix documentation and unit test setup issue. Note: I am ignoring the one CPU test failing here since it seems unrelated. --- .github/workflows/ci-gh-release.yml | 10 ++++++++++ cmake/versions.json | 2 +- continuous_integration/scripts/test | 4 ++-- 3 files changed, 13 insertions(+), 3 deletions(-) diff --git a/.github/workflows/ci-gh-release.yml b/.github/workflows/ci-gh-release.yml index b2286a583b..115c06af27 100644 --- a/.github/workflows/ci-gh-release.yml +++ b/.github/workflows/ci-gh-release.yml @@ -42,3 +42,13 @@ jobs: refname: ${{ github.ref_name }} default-branch: ${{ github.event.repository.default_branch }} secrets: inherit + + tests-pass: + if: always() + needs: + - build-and-test + runs-on: linux-amd64-cpu4 + steps: + - name: Check job results + if: contains(needs.*.result, 'failure') || contains(needs.*.result, 'cancelled') + run: exit 1 diff --git a/cmake/versions.json b/cmake/versions.json index c05eaa3bc5..960aaea069 100644 --- a/cmake/versions.json +++ b/cmake/versions.json @@ -10,7 +10,7 @@ "git_url" : "git@github.com:nv-legate/legate.internal.git", "git_shallow": false, "always_download": false, - "git_tag" : "43294f1355d85f21e1941cd31e31d717162b3a2a" + "git_tag" : "a34f0cfcaef917a352cb486a06452d0d33fc6da5" } } } diff --git a/continuous_integration/scripts/test b/continuous_integration/scripts/test index 72a5f67ec3..c9b79656dd 100755 --- a/continuous_integration/scripts/test +++ b/continuous_integration/scripts/test @@ -22,7 +22,7 @@ setup_test_env() { setup_docs_env() { mamba install -y pandoc doxygen - pip install ipython jinja2 "markdown<3.4.0" myst-parser nbsphinx sphinx-copybutton "sphinx>=8" nvidia-sphinx-theme + pip install ipython jinja2 "markdown<3.4.0" myst-parser nbsphinx sphinx-copybutton "sphinx>=8" nvidia-sphinx-theme cffi } setup_mypy_env() { @@ -30,7 +30,7 @@ setup_mypy_env() { } setup_unit_env() { - mamba install -y pytest pytest-mock mock + mamba install -y pytest pytest-mock mock cffi } test_cupynumeric() { From 92b966fee2dd1f2d4ef24b2882343d3d229f5c30 Mon Sep 17 00:00:00 2001 From: Manolis Papadakis Date: Sun, 19 Jan 2025 21:56:07 -0800 Subject: [PATCH 404/462] Add missing opt_einsum setup.py dependency (#568) * Add missing opt_einsum setup.py dependency * Add version limits from conda build spec * Also add missing cffi * Bump to latest Legate --- cmake/versions.json | 2 +- conda/conda-build/meta.yaml | 1 + setup.py | 2 +- 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/cmake/versions.json b/cmake/versions.json index 960aaea069..d5575b5642 100644 --- a/cmake/versions.json +++ b/cmake/versions.json @@ -10,7 +10,7 @@ "git_url" : "git@github.com:nv-legate/legate.internal.git", "git_shallow": false, "always_download": false, - "git_tag" : "a34f0cfcaef917a352cb486a06452d0d33fc6da5" + "git_tag" : "009a3855fa42a270ffef14ab055f7cb99442875a" } } } diff --git a/conda/conda-build/meta.yaml b/conda/conda-build/meta.yaml index 2aaddb22b1..a9b273b02b 100644 --- a/conda/conda-build/meta.yaml +++ b/conda/conda-build/meta.yaml @@ -130,6 +130,7 @@ requirements: {% endif %} run: + - cffi - numpy {{ numpy_version }} - opt_einsum >=3.3 - scipy diff --git a/setup.py b/setup.py index 0f4019067c..bc9b8918c1 100644 --- a/setup.py +++ b/setup.py @@ -44,6 +44,6 @@ package_data={"cupynumeric": ["_sphinxext/_templates/*.rst"]}, include_package_data=True, cmdclass=versioneer.get_cmdclass(), - install_requires=["numpy>=1.22,<2"], + install_requires=["cffi", "numpy>=1.22,<2", "opt_einsum>=3.3"], zip_safe=False, ) From e10c542a95881ceea1e1f29ed30f10f1283605d8 Mon Sep 17 00:00:00 2001 From: Jacob Faibussowitsch Date: Wed, 22 Jan 2025 14:56:00 -0500 Subject: [PATCH 405/462] Final version bumps before 25.01 release cut (#582) * Final version bumps before 25.01 release cut --- cmake/versions.json | 2 +- docs/cupynumeric/switcher.json | 19 ++++++++++++------- 2 files changed, 13 insertions(+), 8 deletions(-) diff --git a/cmake/versions.json b/cmake/versions.json index d5575b5642..f0547a85d0 100644 --- a/cmake/versions.json +++ b/cmake/versions.json @@ -10,7 +10,7 @@ "git_url" : "git@github.com:nv-legate/legate.internal.git", "git_shallow": false, "always_download": false, - "git_tag" : "009a3855fa42a270ffef14ab055f7cb99442875a" + "git_tag" : "300b7f5fbf4ad509b53532d27c50ee407a87bfd0" } } } diff --git a/docs/cupynumeric/switcher.json b/docs/cupynumeric/switcher.json index 2578b6f35c..07524ee89a 100644 --- a/docs/cupynumeric/switcher.json +++ b/docs/cupynumeric/switcher.json @@ -1,8 +1,13 @@ [ - { - "name": "24.11", - "preferred": true, - "version": "24.11", - "url": "https://docs.nvidia.com/cupynumeric/24.11/" - } -] \ No newline at end of file + { + "name": "24.11", + "version": "24.11", + "url": "https://docs.nvidia.com/cupynumeric/24.11/" + }, + { + "name": "25.01", + "preferred": true, + "version": "25.01", + "url": "https://docs.nvidia.com/cupynumeric/25.01/" + } +] From 6d9dd46afbaa45bad21d1307425d6ddac323f096 Mon Sep 17 00:00:00 2001 From: Bryan Van de Ven Date: Thu, 23 Jan 2025 09:25:23 -0800 Subject: [PATCH 406/462] Add docs option for annotation overlay (#581) --- docs/cupynumeric/source/conf.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docs/cupynumeric/source/conf.py b/docs/cupynumeric/source/conf.py index a29cf12f33..997125fda9 100644 --- a/docs/cupynumeric/source/conf.py +++ b/docs/cupynumeric/source/conf.py @@ -21,6 +21,8 @@ SWITCHER_DEV = "http://localhost:8000/switcher.json" JSON_URL = SWITCHER_DEV if getenv("SWITCHER_DEV") == "1" else SWITCHER_PROD +ANNOTATE = getenv("LEGATE_ANNOTATION_DOCS") == "1" + # -- Project information ----------------------------------------------------- project = "NVIDIA cuPyNumeric" @@ -98,4 +100,6 @@ def setup(app): + if ANNOTATE: + app.add_js_file("https://hypothes.is/embed.js", kind="hypothesis") app.add_css_file("params.css") From 24aab868c79f187c7aaa717b3f7e656c682bb6b5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Malte=20F=C3=B6rster?= <97973773+mfoerste4@users.noreply.github.com> Date: Mon, 27 Jan 2025 12:46:28 +0100 Subject: [PATCH 407/462] prevent matmul batching in case we are on a single proc (#586) --- cupynumeric/_thunk/deferred.py | 4 ++++ src/cupynumeric/ndarray.cc | 9 ++++++--- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/cupynumeric/_thunk/deferred.py b/cupynumeric/_thunk/deferred.py index b94a16aeda..b64f7fb669 100644 --- a/cupynumeric/_thunk/deferred.py +++ b/cupynumeric/_thunk/deferred.py @@ -1615,6 +1615,10 @@ def choose_2d_color_shape( def choose_batchsize( tilesize: tuple[int, int], k: int, itemsize: int ) -> int: + # don't batch in case we only have 1 proc + if runtime.num_procs == 1: + return k + # default corresponds to 128MB (to store A and B tile) from ..settings import settings diff --git a/src/cupynumeric/ndarray.cc b/src/cupynumeric/ndarray.cc index 961d118ddc..b9a585fbc8 100644 --- a/src/cupynumeric/ndarray.cc +++ b/src/cupynumeric/ndarray.cc @@ -562,7 +562,8 @@ uint64_t ceildiv(uint64_t a, uint64_t b) { return (a + b - 1) / b; } void NDArray::dot_MM(legate::LogicalStore& rhs1_store, legate::LogicalStore& rhs2_store) { - auto runtime = CuPyNumericRuntime::get_runtime(); + auto runtime = CuPyNumericRuntime::get_runtime(); + bool is_single_proc = legate::Runtime::get_runtime()->get_machine().count() == 1; fill(get_reduction_identity(UnaryRedCode::SUM, type())); @@ -573,7 +574,8 @@ void NDArray::dot_MM(legate::LogicalStore& rhs1_store, legate::LogicalStore& rhs auto k = rhs1_store.shape()[1]; // compute tilesize for lhs and batch_size for k // TODO make generic - std::vector initial_tile_shape = {512, 512}; + std::vector initial_tile_shape = {is_single_proc ? m : 512, + is_single_proc ? n : 512}; legate::tuple color_shape = {ceildiv(m, initial_tile_shape[0]), ceildiv(n, initial_tile_shape[1])}; @@ -588,7 +590,8 @@ void NDArray::dot_MM(legate::LogicalStore& rhs1_store, legate::LogicalStore& rhs uint64_t batch_size = ceildiv(k, num_batches); return batch_size; }; - std::uint64_t k_batch_size = get_batchsize(tile_shape, k); + + std::uint64_t k_batch_size = is_single_proc ? k : get_batchsize(tile_shape, k); std::vector tile_shape_rhs1 = {tile_shape[0], k_batch_size}; std::vector tile_shape_rhs2 = {k_batch_size, tile_shape[1]}; From b1c644c160c098445db54ac807981fbc17945e36 Mon Sep 17 00:00:00 2001 From: Jacob Faibussowitsch Date: Mon, 27 Jan 2025 09:01:36 -0500 Subject: [PATCH 408/462] Post branch-cut version bumps (#583) * Post branch-cut version bumps --- CMakeLists.txt | 2 +- cmake/versions.json | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index a0bce70437..c52c5eb224 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -41,7 +41,7 @@ include(rapids-cuda) include(rapids-export) include(rapids-find) -set(cupynumeric_version 25.01.00) +set(cupynumeric_version 25.03.00) # For now we want the optimization flags to match on both normal make and cmake # builds so we override the cmake defaults here for release, this changes diff --git a/cmake/versions.json b/cmake/versions.json index f0547a85d0..fca6d048af 100644 --- a/cmake/versions.json +++ b/cmake/versions.json @@ -6,11 +6,11 @@ "org": "nv-legate", "artifact_workflow": "ci-gh.yml", "nightly_workflow": "ci-gh-nightly-release.yml", - "version": "25.01.00", + "version": "25.03.00", "git_url" : "git@github.com:nv-legate/legate.internal.git", "git_shallow": false, "always_download": false, - "git_tag" : "300b7f5fbf4ad509b53532d27c50ee407a87bfd0" + "git_tag" : "016ec28b73786459822c715abfe999b487d5aa8e" } } } From 5483b09dd313331d742f399402b429f44977cfbc Mon Sep 17 00:00:00 2001 From: Irina Demeshko Date: Wed, 29 Jan 2025 20:30:25 -0800 Subject: [PATCH 409/462] updating legate commit hash (#590) --- cmake/versions.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmake/versions.json b/cmake/versions.json index fca6d048af..8dea0eaf56 100644 --- a/cmake/versions.json +++ b/cmake/versions.json @@ -10,7 +10,7 @@ "git_url" : "git@github.com:nv-legate/legate.internal.git", "git_shallow": false, "always_download": false, - "git_tag" : "016ec28b73786459822c715abfe999b487d5aa8e" + "git_tag" : "d1473e3edc84849eaa73cd99013933f44deba136" } } } From 5620c5c9218c2c96344951b1bf21a246a1f71f2b Mon Sep 17 00:00:00 2001 From: Wonchan Lee Date: Thu, 30 Jan 2025 15:14:44 -0800 Subject: [PATCH 410/462] Catch up the renaming change (#457) * Catch up the renaming change * Bump the legate commit --- cmake/versions.json | 2 +- src/cupynumeric/matrix/batched_cholesky.cc | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/cmake/versions.json b/cmake/versions.json index 8dea0eaf56..999e129238 100644 --- a/cmake/versions.json +++ b/cmake/versions.json @@ -10,7 +10,7 @@ "git_url" : "git@github.com:nv-legate/legate.internal.git", "git_shallow": false, "always_download": false, - "git_tag" : "d1473e3edc84849eaa73cd99013933f44deba136" + "git_tag" : "5971afd9fd6d4e40188a7fdf562446c534be2c77" } } } diff --git a/src/cupynumeric/matrix/batched_cholesky.cc b/src/cupynumeric/matrix/batched_cholesky.cc index a683f32ebf..2abc51e9fd 100644 --- a/src/cupynumeric/matrix/batched_cholesky.cc +++ b/src/cupynumeric/matrix/batched_cholesky.cc @@ -19,7 +19,7 @@ #include "cupynumeric/matrix/batched_cholesky_template.inl" #include -#include +#include #include namespace cupynumeric { From e04b935704684ea6d1a8ff674136f27b7eb129bd Mon Sep 17 00:00:00 2001 From: Parag Kulkarni Date: Fri, 31 Jan 2025 11:00:21 +0530 Subject: [PATCH 411/462] Disable upload on URM (#591) --- .github/workflows/gh-build-and-test.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/gh-build-and-test.yml b/.github/workflows/gh-build-and-test.yml index 272b05ca23..50addc228c 100644 --- a/.github/workflows/gh-build-and-test.yml +++ b/.github/workflows/gh-build-and-test.yml @@ -106,7 +106,7 @@ jobs: python-version: ${{ inputs.python-version }} repos-Root: "cupynumeric" target-device: ${{ inputs.target-device }} - upload-action: "upload-package-ALL" + upload-action: "upload-package-Anaconda" upload-enabled: ${{ inputs.upload-enabled }} refname: ${{ inputs.refname }} default-branch: ${{ inputs.default-branch }} @@ -203,7 +203,7 @@ jobs: updateTestStatus: needs: test name: Update Test status on Server - if: ${{ (github.repository_owner == 'nv-legate') && contains(github.workflow, 'Nightly') && (inputs.upload-enabled == true) }} + if: ${{ false }} uses: nv-legate/legate-gh-ci/.github/workflows/gh-upload.yml@v1.23 with: From 5ce4f9c731b7a8db82e77bb6a913c5c4def9c1ae Mon Sep 17 00:00:00 2001 From: Parag Kulkarni Date: Tue, 4 Feb 2025 10:56:55 +0530 Subject: [PATCH 412/462] Verify legate SHA [Not for review] (#597) --- cmake/versions.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmake/versions.json b/cmake/versions.json index 999e129238..1d20e9e548 100644 --- a/cmake/versions.json +++ b/cmake/versions.json @@ -10,7 +10,7 @@ "git_url" : "git@github.com:nv-legate/legate.internal.git", "git_shallow": false, "always_download": false, - "git_tag" : "5971afd9fd6d4e40188a7fdf562446c534be2c77" + "git_tag" : "79203c033610f10b237cc95769b4f4b8016cc2bc" } } } From 97d7dae18a8f7631fee19971a1dbf7521cbc3608 Mon Sep 17 00:00:00 2001 From: Wonchan Lee Date: Mon, 3 Feb 2025 22:39:31 -0800 Subject: [PATCH 413/462] Use `uniform()` instead of `rand()` to avoid temporary allocations (#585) --- examples/gemm.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/examples/gemm.py b/examples/gemm.py index 183f65a7b2..3830459be7 100644 --- a/examples/gemm.py +++ b/examples/gemm.py @@ -21,8 +21,8 @@ def initialize(M, N, K, ft): - A = np.random.rand(N, N).astype(ft) - B = np.random.rand(N, N).astype(ft) + A = np.random.uniform(size=(N, N), dtype=ft) + B = np.random.uniform(size=(N, N), dtype=ft) C = np.zeros((N, N), dtype=ft) return A, B, C From 5fff8e5f50f712671bbd20c25e6c2984cf4d8920 Mon Sep 17 00:00:00 2001 From: Bryan Van de Ven Date: Wed, 5 Feb 2025 09:32:44 -0800 Subject: [PATCH 414/462] Add local_task_array helper (#589) * add local_task_array helper * handle arrays as well as stores * unused imports * add cupy as dependency for gpu packages * fix reversed logic * mypy * tests --- conda/conda-build/meta.yaml | 1 + cupynumeric/__init__.py | 2 +- cupynumeric/_utils/array.py | 33 +++++++++++++++- tests/integration/test_local_task_array.py | 45 ++++++++++++++++++++++ 4 files changed, 79 insertions(+), 2 deletions(-) create mode 100644 tests/integration/test_local_task_array.py diff --git a/conda/conda-build/meta.yaml b/conda/conda-build/meta.yaml index a9b273b02b..d950da6ad6 100644 --- a/conda/conda-build/meta.yaml +++ b/conda/conda-build/meta.yaml @@ -136,6 +136,7 @@ requirements: - scipy - openblas =* =*openmp* {% if gpu_enabled_bool %} + - cupy - libnvjitlink - libcusparse - cutensor >=2.0 =*_* diff --git a/cupynumeric/__init__.py b/cupynumeric/__init__.py index 01037dfa36..1893a42d59 100644 --- a/cupynumeric/__init__.py +++ b/cupynumeric/__init__.py @@ -31,7 +31,7 @@ from ._array.util import maybe_convert_to_np_ndarray from ._module import * from ._ufunc import * -from ._utils.array import is_supported_dtype +from ._utils.array import is_supported_dtype, local_task_array from ._utils.coverage import clone_module clone_module(_np, globals(), maybe_convert_to_np_ndarray) diff --git a/cupynumeric/_utils/array.py b/cupynumeric/_utils/array.py index eda134045e..ac9bfce082 100644 --- a/cupynumeric/_utils/array.py +++ b/cupynumeric/_utils/array.py @@ -15,13 +15,17 @@ from __future__ import annotations from functools import reduce -from typing import Any +from typing import TYPE_CHECKING, Any +from legate.core import PhysicalArray, StoreTarget import legate.core.types as ty import numpy as np from ..types import NdShape +if TYPE_CHECKING: + from legate.core import PhysicalStore + SUPPORTED_DTYPES = { np.dtype(bool): ty.bool_, np.dtype(np.int8): ty.int8, @@ -111,3 +115,30 @@ def min_identity( return True else: raise ValueError(f"Unsupported dtype: {ty}") + +def local_task_array(obj: PhysicalArray | PhysicalStore) -> Any: + """ + Generate an appropriate local-memory ndarray object, that is backed by the + portion of a Legate array or store that was passed to a task. + + Parameters + ---------- + obj : PhysicalArray | PhysicalStore + A Legate physical array or store to adapt. + + Returns + ------- + arr : cupy.ndarray or np.ndarray + If the array or store is located on GPU, then this function will return + a CuPy array. Otherwise, a NumPy array is returned. + + """ + store = obj.data() if isinstance(obj, PhysicalArray) else obj + + if store.target in {StoreTarget.FBMEM, StoreTarget.ZCMEM}: + # cupy is only a dependency for GPU packages -- but we should + # only hit this import in case the store is located on a GPU + import cupy # type: ignore [import-untyped,import-not-found] + return cupy.asarray(store) + else: + return np.asarray(store) \ No newline at end of file diff --git a/tests/integration/test_local_task_array.py b/tests/integration/test_local_task_array.py new file mode 100644 index 0000000000..cea98d5956 --- /dev/null +++ b/tests/integration/test_local_task_array.py @@ -0,0 +1,45 @@ +# Copyright 2024 NVIDIA Corporation +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +import numpy as np +import pytest + +from legate.core import StoreTarget, get_legate_runtime, types as ty + +import cupynumeric as num + +runtime = get_legate_runtime() + +def test_local_task_array_with_array() -> None: + array = runtime.create_array(ty.int64, shape=(10,)).get_physical_array() + result = num.local_task_array(array) + assert result.shape == (10,) + assert result.dtype == np.int64 + on_cpu = array.data().target not in {StoreTarget.FBMEM, StoreTarget.ZCMEM} + assert isinstance(result, np.ndarray) == on_cpu + +def test_local_task_array_with_store() -> None: + store = runtime.create_store(ty.int64, shape=(20,)).get_physical_store() + result = num.local_task_array(store) + assert result.shape == (20,) + assert result.dtype == np.int64 + on_cpu = store.target not in {StoreTarget.FBMEM, StoreTarget.ZCMEM} + assert isinstance(result, np.ndarray) == on_cpu + + +if __name__ == "__main__": + import sys + + sys.exit(pytest.main(sys.argv)) From d11b8a755e0ed18a250236aa0d404f27c6d97704 Mon Sep 17 00:00:00 2001 From: Mona Han <129724851+mona-nv@users.noreply.github.com> Date: Fri, 7 Feb 2025 10:15:49 +0800 Subject: [PATCH 415/462] Fix the insame amount of output when compare the array (#595) * Fix the insame amount of output when compare the array * Address review comments and remove some test log --- tests/cpp/integration/common_utils.h | 2 +- tests/cpp/integration/test_argsort.cc | 1 - tests/cpp/integration/test_convolve.cc | 3 -- tests/cpp/integration/test_msort.cc | 1 - tests/cpp/integration/test_unique.cc | 4 --- tests/cpp/integration/util.inl | 50 +++++++++++++------------- 6 files changed, 25 insertions(+), 36 deletions(-) diff --git a/tests/cpp/integration/common_utils.h b/tests/cpp/integration/common_utils.h index 605ce5f623..89663310d9 100644 --- a/tests/cpp/integration/common_utils.h +++ b/tests/cpp/integration/common_utils.h @@ -127,7 +127,7 @@ void check_array_near(NDArray a, auto acc = a.get_read_accessor(); for (size_t i = 0; i < values.size(); ++i) { - EXPECT_NEAR(acc[i], values[i], abs_error) << err_msg(i); + ASSERT_NEAR(acc[i], values[i], abs_error) << err_msg(i); } } diff --git a/tests/cpp/integration/test_argsort.cc b/tests/cpp/integration/test_argsort.cc index ffe9cda33b..17c394887c 100644 --- a/tests/cpp/integration/test_argsort.cc +++ b/tests/cpp/integration/test_argsort.cc @@ -244,7 +244,6 @@ void argsort_basic_axis_impl( auto test_shape = test_shapes[i]; int32_t dim = test_shape.size(); for (int32_t axis = -dim + 1; axis < dim; ++axis) { - std::cout << "Axis is: " << axis << std::endl; auto expect_val = expect_result[i][axis]; if (dim == 1) { test_argsort( diff --git a/tests/cpp/integration/test_convolve.cc b/tests/cpp/integration/test_convolve.cc index f67cc1c357..fe9ca7e323 100644 --- a/tests/cpp/integration/test_convolve.cc +++ b/tests/cpp/integration/test_convolve.cc @@ -95,7 +95,6 @@ TEST(Convolve, test_int) auto v = mk_array(v_in, shape_v); auto out = convolve(a, v); check_array(out, out_gt, shape_a); - debug_array(out, false); } } @@ -106,7 +105,6 @@ TEST(Convolve, test_double) auto v = mk_array(as_type_vector(v_in), shape_v); auto out = convolve(a, v); check_array(out, as_type_vector(out_gt), shape_a); - debug_array(out, false); } } @@ -125,7 +123,6 @@ TEST(Convolve, test_ndim) if (ndim <= 3) { auto out = convolve(a, v); check_array(out, a_in, shape); - debug_array(out, false); } else { EXPECT_ANY_THROW(convolve(a, v)); } diff --git a/tests/cpp/integration/test_msort.cc b/tests/cpp/integration/test_msort.cc index 34d8138d83..0fa004bb8a 100644 --- a/tests/cpp/integration/test_msort.cc +++ b/tests/cpp/integration/test_msort.cc @@ -200,7 +200,6 @@ void test_msort(std::array& in_array, } else { assign_values_to_array(A1, in_array.data(), in_array.size()); } - print_array(A1); } auto B1 = cupynumeric::msort(A1); diff --git a/tests/cpp/integration/test_unique.cc b/tests/cpp/integration/test_unique.cc index 36ee0797a0..baf3702a69 100644 --- a/tests/cpp/integration/test_unique.cc +++ b/tests/cpp/integration/test_unique.cc @@ -49,8 +49,6 @@ TEST(Unique, test_basic) auto x = mk_array(x_in); auto x_out = unique(x); check_array(x_out, x_gt); - debug_array(x); - debug_array(x_out); } } @@ -88,8 +86,6 @@ TEST(Unique, test_ndim) auto x = mk_array(x_in, shape); auto x_out = unique(x); check_array(x_out, x_gt); - debug_array(x, false); - debug_array(x_out); } } diff --git a/tests/cpp/integration/util.inl b/tests/cpp/integration/util.inl index c532c2b24e..232ba31b23 100644 --- a/tests/cpp/integration/util.inl +++ b/tests/cpp/integration/util.inl @@ -98,34 +98,35 @@ std::string to_string(legate::AccessorRO acc, } template -std::string check_array_eq(legate::AccessorRO acc, - T* values_ptr, - const std::vector& shape, - legate::Rect rect) +void check_array_eq(legate::AccessorRO acc, + T* values_ptr, + const std::vector& shape, + legate::Rect rect) { - std::stringstream ss; - auto index = 0; - auto size = shape.size(); - ss << "size: " << size << "\n"; for (legate::PointInRectIterator itr(rect, false); itr.valid(); ++itr) { - auto q = *itr; - ss << std::left << std::setprecision(3); - ss << std::setw(13) << "Array value: " << std::setw(10); - print_value(ss, acc[q]) << ", "; - ss << std::setw(16) << "Expected value: " << std::setw(10); - print_value(ss, acc[q]) << ", "; - if (size > 0) { - ss << std::setw(8) << "index: ["; - for (uint32_t i = 0; i < size - 1; ++i) { - ss << q[i] << ","; + auto q = *itr; + auto value = acc[q]; + auto expect = values_ptr[index++]; + if (value != expect) { + std::stringstream ss; + auto size = shape.size(); + ss << "size: " << size << "\n"; + ss << std::left << std::setprecision(3); + ss << std::setw(13) << "Array value: " << std::setw(10); + print_value(ss, value) << ", "; + ss << std::setw(16) << "Expected value: " << std::setw(10); + print_value(ss, expect) << ", "; + if (size > 0) { + ss << std::setw(8) << "index: ["; + for (uint32_t i = 0; i < size - 1; ++i) { + ss << q[i] << ","; + } + ss << q[size - 1] << "]\n"; } - ss << q[size - 1] << "]\n"; + FAIL() << ss.str(); } - EXPECT_EQ(acc[q], values_ptr[index++]); } - - return ss.str(); } template @@ -145,10 +146,7 @@ struct check_array_eq_fn { const std::vector& shape, legate::Rect rect) { - auto string_result = check_array_eq(acc, values_ptr, shape, rect); - if (rect.volume() <= 256) { - std::cerr << string_result << std::endl; - } + check_array_eq(acc, values_ptr, shape, rect); } }; From a856e859aa2473f04a4ffd0113882c2db8ff7d85 Mon Sep 17 00:00:00 2001 From: "Marcus D. Hanwell" Date: Mon, 10 Feb 2025 11:23:11 -0500 Subject: [PATCH 416/462] Add cryos to the CODEOWNERS file (#600) --- .github/CODEOWNERS | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 5ac9b710d8..df699eb401 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -1,4 +1,4 @@ # Code Ownership -.github @marcinz @m3vaz @sandeepd-nv @mag1cp1n -continuous_integration @marcinz @m3vaz @sandeepd-nv @mag1cp1n -conda @marcinz @m3vaz @sandeepd-nv @mag1cp1n +.github @marcinz @m3vaz @sandeepd-nv @mag1cp1n @cryos +continuous_integration @marcinz @m3vaz @sandeepd-nv @mag1cp1n @cryos +conda @marcinz @m3vaz @sandeepd-nv @mag1cp1n @cryos From 532e5dac2ad4787886a17fae8be5eb653de0631b Mon Sep 17 00:00:00 2001 From: XiaLuNV <110973296+XiaLuNV@users.noreply.github.com> Date: Thu, 13 Feb 2025 18:07:23 +0800 Subject: [PATCH 417/462] enhance test_index_routines.py (#602) * enhance test_index_routines.py --- tests/integration/test_index_routines.py | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/tests/integration/test_index_routines.py b/tests/integration/test_index_routines.py index 300d1ce384..56d22b6d96 100644 --- a/tests/integration/test_index_routines.py +++ b/tests/integration/test_index_routines.py @@ -14,6 +14,7 @@ # from itertools import chain, combinations, permutations +from typing import Iterator import numpy as np import pytest @@ -686,6 +687,28 @@ def test_ix_(seqs): assert all(np.array_equal(*elts) for elts in zip(a, an)) +def test_ix_bool() -> None: + a = num.ix_([0, 1], [True]) + an = np.ix_([0, 1], [True]) + assert all(isinstance(elt, num.ndarray) for elt in a) + assert all(np.array_equal(*elts) for elts in zip(a, an)) + + +def test_ix_empty() -> None: + a = num.ix_([0, 1], []) + an = np.ix_([0, 1], []) + assert all(isinstance(elt, num.ndarray) for elt in a) + assert all(np.array_equal(*elts) for elts in zip(a, an)) + + +def test_ix_2d() -> None: + msg = r"Cross index must be 1 dimensional" + with pytest.raises(ValueError, match=msg): + num.ix_([0, 1], [[1, 2], [2, 3]]) + with pytest.raises(ValueError, match=msg): + np.ix_([0, 1], [[1, 2], [2, 3]]) + + if __name__ == "__main__": import sys From 453603db139d84e0e27c8fd50c341caf7f7a5a61 Mon Sep 17 00:00:00 2001 From: Wonchan Lee Date: Fri, 14 Feb 2025 11:04:57 -0800 Subject: [PATCH 418/462] Fixes for cuNumeric.jl (#607) * Some changes for cuNumeric.jl * Add C++ gemm test and update the gemm batching in C++ * Remove a redundant file * Address review comments --- examples/cpp/gemm/CMakeLists.txt | 31 +++++++++ examples/cpp/gemm/build.sh | 22 ++++++ examples/cpp/gemm/gemm.cc | 111 +++++++++++++++++++++++++++++++ src/cupynumeric/ndarray.cc | 49 +++++++++++--- src/cupynumeric/ndarray.h | 5 +- src/cupynumeric/operators.cc | 5 ++ src/cupynumeric/operators.h | 2 + src/cupynumeric/runtime.cc | 42 ++++++++---- src/cupynumeric/runtime.h | 2 + 9 files changed, 243 insertions(+), 26 deletions(-) create mode 100644 examples/cpp/gemm/CMakeLists.txt create mode 100755 examples/cpp/gemm/build.sh create mode 100644 examples/cpp/gemm/gemm.cc diff --git a/examples/cpp/gemm/CMakeLists.txt b/examples/cpp/gemm/CMakeLists.txt new file mode 100644 index 0000000000..91c1ff2723 --- /dev/null +++ b/examples/cpp/gemm/CMakeLists.txt @@ -0,0 +1,31 @@ +#============================================================================= +# Copyright 2024 NVIDIA Corporation +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +#============================================================================= + +cmake_minimum_required(VERSION 3.22.1 FATAL_ERROR) + +project(stencil VERSION 0.1 LANGUAGES C CXX) + +if (NOT CMAKE_CXX_STANDARD) + set(CMAKE_CXX_STANDARD 17) +endif() + +find_package(cupynumeric REQUIRED) + +add_executable(gemm gemm.cc) + +target_link_libraries(gemm PRIVATE cupynumeric::cupynumeric) + +install(TARGETS gemm DESTINATION "${CMAKE_CURRENT_BINARY_DIR}/cmake-install") diff --git a/examples/cpp/gemm/build.sh b/examples/cpp/gemm/build.sh new file mode 100755 index 0000000000..53ed6d6c09 --- /dev/null +++ b/examples/cpp/gemm/build.sh @@ -0,0 +1,22 @@ +#!/bin/bash + +# Copyright 2024 NVIDIA Corporation +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +legate_root=`python -c 'import legate.install_info as i; from pathlib import Path; print(Path(i.libpath).parent.resolve())'` +echo "Using Legate at $legate_root" +cupynumeric_root=`python -c 'import cupynumeric.install_info as i; from pathlib import Path; print(Path(i.libpath).parent.resolve())'` +echo "Using cuPyNumeric at $cupynumeric_root" +cmake -S . -B build -D legate_ROOT="$legate_root" -D cupynumeric_ROOT="$cupynumeric_root" -D CMAKE_BUILD_TYPE=RelWithDebInfo +cmake --build build --parallel 8 diff --git a/examples/cpp/gemm/gemm.cc b/examples/cpp/gemm/gemm.cc new file mode 100644 index 0000000000..7ddc290522 --- /dev/null +++ b/examples/cpp/gemm/gemm.cc @@ -0,0 +1,111 @@ +/* Copyright 2024 NVIDIA Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +#include +#include +#include +#include + +#include +#include +#include + +namespace gemm { + +struct Config { + bool timing{false}; + std::int32_t iter{100}; + std::int32_t warmup{5}; + std::uint64_t N{100}; +}; + +[[nodiscard]] std::tuple +initialize(std::uint64_t N, const legate::Type& ft) +{ + auto A = cupynumeric::random({N, N}).as_type(ft); + auto B = cupynumeric::random({N, N}).as_type(ft); + auto C = cupynumeric::zeros({N, N}, ft); + return {A, B, C}; +} + +[[nodiscard]] std::size_t total_flops(std::uint64_t M, std::uint64_t N, std::uint64_t K) +{ + return M * N * (2 * K - 1); +} + +[[nodiscard]] std::size_t total_space(std::uint64_t M, + std::uint64_t N, + std::uint64_t K, + const legate::Type& ft) +{ + return (M * N + M * K + K * N) * ft.size(); +} + +void run_gemm(const Config& config) +{ + const auto ft = legate::float32(); + const auto N = config.N; + std::printf("Problem Size: M=%lu N=%lu K=%lu\n", N, N, N); + std::printf("Total Iterations: %d\n", config.iter); + const auto flops = total_flops(N, N, N); + std::printf("Total Flops: %lf GFLOPS/iter\n", flops / 1e9); + const auto space = total_space(N, N, N, ft); + std::printf("Total Size: %lf MB\n", space / 1e6); + auto [A, B, C] = initialize(config.N, legate::float32()); + + auto start = legate::timing::measure_microseconds(); + auto max_iter = config.iter + config.warmup; + for (int32_t iter = 0; iter < max_iter; ++iter) { + if (iter == config.warmup) { + start = legate::timing::measure_microseconds(); + } + C.dot(A, B); + // We need to rotate the matrices to keep Legate honest + // about moving data so it can't just duplicate A and B + // on the first iteration and reuse them, this means + // that A, B, C all need to be square + A, B, C = B, C, A; + } + auto stop = legate::timing::measure_microseconds(); + + const auto total = (stop.value() - start.value()) / 1e3; + std::printf("Elapsed Time: %lf ms\n", total); + const auto average = total / config.iter; + std::printf("Average GEMM: %lf ms\n", average); + std::printf("FLOPS/s: %lf GFLOPS/s\n", flops / (average * 1e6)); +} + +} // namespace gemm + +int main(int argc, char** argv) +{ + legate::start(); + + cupynumeric::initialize(argc, argv); + + gemm::Config config{}; + + Realm::CommandLineParser cp; + cp.add_option_int("--iter", config.iter) + .add_option_int("--warmup", config.warmup) + .add_option_int("--num", config.N) + .add_option_bool("--time", config.timing) + .parse_command_line(argc, argv); + + gemm::run_gemm(config); + + return legate::finish(); +} diff --git a/src/cupynumeric/ndarray.cc b/src/cupynumeric/ndarray.cc index b9a585fbc8..d3d1c4cfd4 100644 --- a/src/cupynumeric/ndarray.cc +++ b/src/cupynumeric/ndarray.cc @@ -191,6 +191,15 @@ NDArray& NDArray::operator*=(const NDArray& other) return *this; } +NDArray NDArray::operator/(const NDArray& other) const { return divide(*this, other); } + +NDArray NDArray::operator/(const legate::Scalar& other) const +{ + auto runtime = CuPyNumericRuntime::get_runtime(); + auto scalar = runtime->create_scalar_store(other); + return operator/(NDArray(std::move(scalar))); +} + NDArray NDArray::operator[](std::initializer_list slices) const { if (slices.size() > static_cast(dim())) { @@ -202,7 +211,7 @@ NDArray NDArray::operator[](std::initializer_list slices) const uint32_t dim = 0; auto sliced = store_; for (const auto& sl : slices) { - sliced = sliced.slice(0, sl); + sliced = sliced.slice(dim, sl); ++dim; } @@ -448,6 +457,8 @@ void NDArray::trilu(NDArray rhs, int32_t k, bool lower) runtime->submit(std::move(task)); } +void NDArray::dot(NDArray rhs1, NDArray rhs2) { dot_MM(rhs1.get_store(), rhs2.get_store()); } + void NDArray::binary_op(int32_t op_code, NDArray rhs1, NDArray rhs2) { if (rhs1.type() != rhs2.type()) { @@ -560,10 +571,11 @@ void NDArray::unary_reduction(int32_t op_code_, NDArray input) uint64_t ceildiv(uint64_t a, uint64_t b) { return (a + b - 1) / b; } -void NDArray::dot_MM(legate::LogicalStore& rhs1_store, legate::LogicalStore& rhs2_store) +void NDArray::dot_MM(const legate::LogicalStore& rhs1_store, const legate::LogicalStore& rhs2_store) { - auto runtime = CuPyNumericRuntime::get_runtime(); - bool is_single_proc = legate::Runtime::get_runtime()->get_machine().count() == 1; + auto runtime = CuPyNumericRuntime::get_runtime(); + const auto num_procs = legate::Runtime::get_runtime()->get_machine().count(); + bool is_single_proc = num_procs == 1; fill(get_reduction_identity(UnaryRedCode::SUM, type())); @@ -572,14 +584,23 @@ void NDArray::dot_MM(legate::LogicalStore& rhs1_store, legate::LogicalStore& rhs auto m = rhs1_store.shape()[0]; auto n = rhs2_store.shape()[1]; auto k = rhs1_store.shape()[1]; - // compute tilesize for lhs and batch_size for k - // TODO make generic - std::vector initial_tile_shape = {is_single_proc ? m : 512, - is_single_proc ? n : 512}; - legate::tuple color_shape = {ceildiv(m, initial_tile_shape[0]), - ceildiv(n, initial_tile_shape[1])}; - std::vector tile_shape = {ceildiv(m, color_shape[0]), ceildiv(n, color_shape[1])}; + static constexpr std::size_t MIN_MATRIX_SIZE = 1 << 20; + + auto get_color_shape = + [&](const std::vector& shape) -> std::vector { + if (!is_in_test_mode() && shape[0] * shape[1] <= MIN_MATRIX_SIZE) { + return {1, 1}; + } + auto color_shape = std::vector{num_procs, 1}; + + while ((shape[0] / color_shape[0] < 2 * shape[1] / color_shape[1]) && color_shape[0] % 2 == 0) { + color_shape[0] /= 2; + color_shape[1] *= 2; + } + + return color_shape; + }; auto get_batchsize = [&](const std::vector& tilesize, std::uint64_t k) { uint64_t typesize = legate::type_dispatch(type().code(), get_typesize_fn{}); @@ -591,6 +612,12 @@ void NDArray::dot_MM(legate::LogicalStore& rhs1_store, legate::LogicalStore& rhs return batch_size; }; + auto initial_color_shape = get_color_shape({m, n}); + auto tile_shape = std::vector{ceildiv(m, initial_color_shape[0]), + ceildiv(n, initial_color_shape[1])}; + auto color_shape = + legate::tuple{ceildiv(m, tile_shape[0]), ceildiv(n, tile_shape[1])}; + std::uint64_t k_batch_size = is_single_proc ? k : get_batchsize(tile_shape, k); std::vector tile_shape_rhs1 = {tile_shape[0], k_batch_size}; diff --git a/src/cupynumeric/ndarray.h b/src/cupynumeric/ndarray.h index 7602166c88..ac0c332f34 100644 --- a/src/cupynumeric/ndarray.h +++ b/src/cupynumeric/ndarray.h @@ -57,6 +57,8 @@ class NDArray { NDArray& operator+=(const NDArray& other); NDArray operator*(const NDArray& other) const; NDArray operator*(const legate::Scalar& other) const; + NDArray operator/(const NDArray& other) const; + NDArray operator/(const legate::Scalar& other) const; NDArray& operator*=(const NDArray& other); NDArray operator[](std::initializer_list slices) const; operator bool() const; @@ -75,6 +77,7 @@ class NDArray { void unary_reduction(int32_t op_code, NDArray input); void eye(int32_t k); void trilu(NDArray rhs, int32_t k, bool lower); + void dot(NDArray rhs1, NDArray rhs2); void arange(Scalar start, Scalar stop, Scalar step); std::vector nonzero(); NDArray unique(); @@ -168,7 +171,7 @@ class NDArray { std::optional out = std::nullopt); void _fill(legate::LogicalStore const& value); - void dot_MM(legate::LogicalStore& rhs1_store, legate::LogicalStore& rhs2_store); + void dot_MM(const legate::LogicalStore& rhs1_store, const legate::LogicalStore& rhs2_store); void _verify_mode_extent(const std::map& mode2extent, const std::vector& modes, const std::vector& shape) const; diff --git a/src/cupynumeric/operators.cc b/src/cupynumeric/operators.cc index a51414ef63..79bb3587be 100644 --- a/src/cupynumeric/operators.cc +++ b/src/cupynumeric/operators.cc @@ -82,6 +82,11 @@ NDArray multiply(NDArray rhs1, NDArray rhs2, std::optional out) return binary_op(BinaryOpCode::MULTIPLY, std::move(rhs1), std::move(rhs2), std::move(out)); } +NDArray divide(NDArray rhs1, NDArray rhs2, std::optional out) +{ + return binary_op(BinaryOpCode::DIVIDE, std::move(rhs1), std::move(rhs2), std::move(out)); +} + NDArray negative(NDArray input) { return unary_op(UnaryOpCode::NEGATIVE, std::move(input)); } NDArray random(std::vector shape) diff --git a/src/cupynumeric/operators.h b/src/cupynumeric/operators.h index 84263a6210..31b4093f75 100644 --- a/src/cupynumeric/operators.h +++ b/src/cupynumeric/operators.h @@ -37,6 +37,8 @@ NDArray add(NDArray rhs1, NDArray rhs2, std::optional out = std::nullop NDArray multiply(NDArray rhs1, NDArray rhs2, std::optional out = std::nullopt); +NDArray divide(NDArray rhs1, NDArray rhs2, std::optional out = std::nullopt); + NDArray dot(NDArray a, NDArray b); NDArray negative(NDArray input); diff --git a/src/cupynumeric/runtime.cc b/src/cupynumeric/runtime.cc index 04068a8ed5..db0a63bf8d 100644 --- a/src/cupynumeric/runtime.cc +++ b/src/cupynumeric/runtime.cc @@ -114,28 +114,42 @@ uint32_t CuPyNumericRuntime::get_next_random_epoch() { return next_epoch_++; } namespace { -std::uint32_t extract_env(const char* env_name, - std::uint32_t default_value, - std::uint32_t test_value) +std::uint32_t parse_value(const char* value_char) { - auto parse_value = [](const char* value_char) { - auto value_sv = std::string_view{value_char}; + auto value_sv = std::string_view{value_char}; - std::uint32_t result{}; - if (auto&& [_, ec] = std::from_chars(value_sv.begin(), value_sv.end(), result); - ec != std::errc{}) { - throw std::runtime_error{std::make_error_code(ec).message()}; - } + std::uint32_t result{}; + if (auto&& [_, ec] = std::from_chars(value_sv.begin(), value_sv.end(), result); + ec != std::errc{}) { + throw std::runtime_error{std::make_error_code(ec).message()}; + } - return result; - }; + return result; +} + +} // namespace + +bool is_in_test_mode() +{ + static const auto value = [] { + const auto* is_in_test_mode = std::getenv("LEGATE_TEST"); + return is_in_test_mode && static_cast(parse_value(is_in_test_mode)); + }(); + return value; +} + +namespace { + +std::uint32_t extract_env(const char* env_name, + std::uint32_t default_value, + std::uint32_t test_value) +{ if (const auto* env_value = std::getenv(env_name); env_value) { return parse_value(env_value); } - if (const auto* is_in_test_mode = std::getenv("LEGATE_TEST"); - is_in_test_mode && parse_value(is_in_test_mode)) { + if (is_in_test_mode()) { return test_value; } diff --git a/src/cupynumeric/runtime.h b/src/cupynumeric/runtime.h index f640a81e60..da2884467e 100644 --- a/src/cupynumeric/runtime.h +++ b/src/cupynumeric/runtime.h @@ -70,4 +70,6 @@ class CuPyNumericRuntime { std::unordered_map argred_types_; }; +[[nodiscard]] bool is_in_test_mode(); + } // namespace cupynumeric From 9654c97dd18ce6b3a825e7260d850ca7fde7b526 Mon Sep 17 00:00:00 2001 From: Parag Kulkarni Date: Tue, 18 Feb 2025 16:30:54 +0530 Subject: [PATCH 419/462] Use Use new label policy. (#605) * Prefix gex to label * s/v1.23/v1.25 * Move to v1.26 --- .github/workflows/gh-build-and-test.yml | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/.github/workflows/gh-build-and-test.yml b/.github/workflows/gh-build-and-test.yml index 50addc228c..07a41cd702 100644 --- a/.github/workflows/gh-build-and-test.yml +++ b/.github/workflows/gh-build-and-test.yml @@ -53,13 +53,13 @@ jobs: needs: setup-build name: "Build (${{ inputs.platform }}, ${{ inputs.target-device }}, ${{ inputs.build-type }}, Python ${{ inputs.python-version }})" uses: - nv-legate/legate-gh-ci/.github/workflows/gh-build.yml@v1.23 + nv-legate/legate-gh-ci/.github/workflows/gh-build.yml@v1.26 with: build-mode: "" build-type: ${{ inputs.build-type }} client-repo: ${{ github.event.repository.name }} dependencies-file: "cmake/versions.json" - legate-gh-ci-tag: "v1.23" + legate-gh-ci-tag: "v1.26" network: "ucx" platform: ${{ inputs.platform }} python-version: ${{ inputs.python-version }} @@ -73,13 +73,13 @@ jobs: needs: setup-build name: "Check if legate.internal nightly exists for SHA specified in versions.json (${{ inputs.platform }}, ${{ inputs.target-device }}, ${{ inputs.build-type }})" uses: - nv-legate/legate-gh-ci/.github/workflows/gh-check-if-nightly-exists-for-all-dependencies.yml@v1.23 + nv-legate/legate-gh-ci/.github/workflows/gh-check-if-nightly-exists-for-all-dependencies.yml@v1.26 with: build-mode: "" build-type: ${{ inputs.build-type }} client-repo: ${{ github.event.repository.name }} dependencies-file: "cmake/versions.json" - legate-gh-ci-tag: "v1.23" + legate-gh-ci-tag: "v1.26" network: "ucx" platform: ${{ inputs.platform }} python-version: ${{ inputs.python-version }} @@ -93,12 +93,12 @@ jobs: if: ${{ github.repository_owner == 'nv-legate' && contains(github.workflow, 'release') && inputs.upload-enabled == true }} name: Upload package to Server uses: - nv-legate/legate-gh-ci/.github/workflows/gh-upload.yml@v1.23 + nv-legate/legate-gh-ci/.github/workflows/gh-upload.yml@v1.26 with: build-mode: "" build-type: ${{ inputs.build-type }} client-repo: ${{ github.event.repository.name }} - legate-gh-ci-tag: "v1.23" + legate-gh-ci-tag: "v1.26" name: Upload package to Server network: "ucx" pkgSubString: "cupynumeric-" @@ -183,13 +183,13 @@ jobs: matrix: ${{fromJson(needs.setup-test.outputs.matrix)}} uses: - nv-legate/legate-gh-ci/.github/workflows/gh-test-within-container.yml@v1.23 + nv-legate/legate-gh-ci/.github/workflows/gh-test-within-container.yml@v1.26 with: build-mode: "" build-type: ${{ inputs.build-type }} client-repo: ${{ github.event.repository.name }} has-gpu: ${{ matrix.runner.type == 'gpu' }} - legate-gh-ci-tag: "v1.23" + legate-gh-ci-tag: "v1.26" name: ${{ matrix.test-config.name }} network: "ucx" platform: ${{ inputs.platform }} @@ -205,12 +205,12 @@ jobs: name: Update Test status on Server if: ${{ false }} uses: - nv-legate/legate-gh-ci/.github/workflows/gh-upload.yml@v1.23 + nv-legate/legate-gh-ci/.github/workflows/gh-upload.yml@v1.26 with: build-mode: "" build-type: ${{ inputs.build-type }} client-repo: ${{ github.event.repository.name }} - legate-gh-ci-tag: "v1.23" + legate-gh-ci-tag: "v1.26" name: UpdateTestStatus network: "ucx" pkgSubString: "cupynumeric-" From 696951065246fa5afb91a9f9f9f122bd1e1657f3 Mon Sep 17 00:00:00 2001 From: Parag Kulkarni Date: Wed, 19 Feb 2025 15:34:06 +0530 Subject: [PATCH 420/462] Download and install legate from Anaconda (#596) * Use legate from Anaconda instead of artifacts * Call setup_conda_download * cleanup cupynumeric version check * Update legate_version * Reformat the meta * Set cpu_gpu_tag earlier * Define legate in run as well * Typo + Use uploaded legate version * Strip __cpu to _cpu * Cleanup-1 * Add a common script. Should be transferred to legate-gh-ci * Cleanup-2 * Check * Min. change and test * chk2 * chk3 * Enable all builds * Define and use legate label from versions.json * Placed label at the end * Syntax/Typo --- cmake/versions.json | 3 +- conda/conda-build/meta.yaml | 20 ++++++------ continuous_integration/scripts/build | 11 +++---- .../scripts/conda-dnld-utils | 31 +++++++++++++++++++ continuous_integration/scripts/test | 4 ++- 5 files changed, 51 insertions(+), 18 deletions(-) create mode 100755 continuous_integration/scripts/conda-dnld-utils diff --git a/cmake/versions.json b/cmake/versions.json index 1d20e9e548..832f595826 100644 --- a/cmake/versions.json +++ b/cmake/versions.json @@ -10,7 +10,8 @@ "git_url" : "git@github.com:nv-legate/legate.internal.git", "git_shallow": false, "always_download": false, - "git_tag" : "79203c033610f10b237cc95769b4f4b8016cc2bc" + "git_tag" : "79203c033610f10b237cc95769b4f4b8016cc2bc", + "anaconda_label": "experimental" } } } diff --git a/conda/conda-build/meta.yaml b/conda/conda-build/meta.yaml index d950da6ad6..590cf9f0a7 100644 --- a/conda/conda-build/meta.yaml +++ b/conda/conda-build/meta.yaml @@ -1,12 +1,16 @@ {% set name = "cupynumeric" %} {% if gpu_enabled == "true" %} {% set gpu_enabled_bool = true %} + {% set cpu_gpu_tag='_gpu' %} {% elif gpu_enabled == "false" %} {% set gpu_enabled_bool = false %} + {% set cpu_gpu_tag='_cpu' %} {% else %} {# We need to have a default value for the initial pass over the recipe #} {% set gpu_enabled_bool = false %} + {% set cpu_gpu_tag='_cpu' %} {% endif %} + {% if upload_build == "true" %} {% set upload_build_bool = true %} {% elif upload_build == "false" %} @@ -37,11 +41,13 @@ ## Note: default values are only given to make conda build work. They should not be necessary in principle. {% elif 'dev' in environ.get('GIT_DESCRIBE_TAG', placeholder_version) %} {% set version = (environ.get('GIT_DESCRIBE_TAG', placeholder_version) ~ environ.get('GIT_DESCRIBE_NUMBER', '')).lstrip('v') %} - {% set legate_version = (version.rsplit('.',1)[0] ~ ".dev" ~ "|>=" ~ version.rsplit('.',1)[0]) %} + {% set legate_version_default = (version.rsplit('.',1)[0] ~ ".dev" ~ "|>=" ~ version.rsplit('.',1)[0]) %} + {% set legate_version = os.environ.get("LEGATE_VERSION", legate_version_default) %} {% else %} {% set version = environ.get('GIT_DESCRIBE_TAG', placeholder_version).lstrip('v') %} - {% set legate_version = version %} + {% set legate_version = os.environ.get("LEGATE_VERSION", version) %} {% endif %} +{% set legate_buildstr = "_".join(["cuda" ~ cuda_major, "py" ~ py_version, os.environ.get("LEGATE_BUILDSTR", ""), cpu_gpu_tag.strip('_') ]) %} package: name: {{ name|lower }} @@ -61,11 +67,6 @@ build: number: {{ build_number }} missing_dso_whitelist: - '*libcuda.so*' -{% if gpu_enabled_bool %} -{% set cpu_gpu_tag='_gpu' %} -{% else %} -{% set cpu_gpu_tag='_cpu' %} -{% endif %} {% set upload_tag='' if upload_build_bool else '_with_tests' %} {% if use_local_path is not defined %} # use git hash @@ -115,8 +116,8 @@ requirements: - python - scikit-build - openblas =* =*openmp* + - legate ={{ legate_version }}={{ legate_buildstr }} {% if gpu_enabled_bool %} - - legate >={{ legate_version }} =*_gpu* - cuda-cccl - cutensor >=2.0 =*_* - libcublas-dev @@ -125,8 +126,6 @@ requirements: - libcurand-dev - libcufile-dev - cuda-version ={{ cuda_version }} -{% else %} - - legate >={{ legate_version }} =*_cpu* {% endif %} run: @@ -135,6 +134,7 @@ requirements: - opt_einsum >=3.3 - scipy - openblas =* =*openmp* + - legate ={{ legate_version }}={{ legate_buildstr }} {% if gpu_enabled_bool %} - cupy - libnvjitlink diff --git a/continuous_integration/scripts/build b/continuous_integration/scripts/build index bf95d91e7d..c5fbaa707a 100755 --- a/continuous_integration/scripts/build +++ b/continuous_integration/scripts/build @@ -8,15 +8,11 @@ build_release_product() { mkdir -p /tmp/env_yaml /tmp/conda-build /tmp/out - cp -r "${ARTIFACTS_DIR}/conda-build/legate" /tmp/conda-build/ - local conda_build_args=(); - conda_build_args+=(--override-channels); - # The channel sequence below needs to be preserved + conda_build_args+=(-c https://conda.anaconda.org/${CONDA_CHANNEL}/label/${CONDA_LABEL}); conda_build_args+=(-c conda-forge); conda_build_args+=(--override-channels); - conda_build_args+=(-c file:///tmp/conda-build/legate); conda_build_args+=(--croot /tmp/conda-build/cupynumeric); conda_build_args+=(--numpy 1.22); conda_build_args+=(--no-test); @@ -93,6 +89,10 @@ build_project() { init_build_env "$@"; + . conda-dnld-utils; + setup_conda_channel; + generate_legate_version + case "$BUILD_TYPE" in ci) build_ci_product;; release) build_release_product;; @@ -100,5 +100,4 @@ build_project() { esac } - (build_project "$@"); diff --git a/continuous_integration/scripts/conda-dnld-utils b/continuous_integration/scripts/conda-dnld-utils new file mode 100755 index 0000000000..cffe1261eb --- /dev/null +++ b/continuous_integration/scripts/conda-dnld-utils @@ -0,0 +1,31 @@ +#!/usr/bin/env bash + +set -x + +generate_legate_version() { + legate_json_version="$(jq -r '.packages.legate.version' ${REPO_DIR}/cmake/versions.json)"; + legate_SHA="$(jq -r '.packages.legate.git_tag' ${REPO_DIR}/cmake/versions.json)"; + legate_hash="g${legate_SHA:0:8}" + export LEGATE_VERSION="${legate_json_version}*" + export LEGATE_BUILDSTR="*${legate_hash}*" + echo "LEGATE_VERSION=${LEGATE_VERSION} : LEGATE_BUILDSTR=${LEGATE_BUILDSTR}" +} + +verify_legate_version() { + mamba search legate=${LEGATE_VERSION} --channel https://conda.anaconda.org/${CONDA_CHANNEL}/label/${CONDA_LABEL} + if [ $? -ne 0 ]; then + echo "Error: conda search failed for legate." >&2; exit 1 + fi +} + +setup_conda_channel() { + if ! command -v jq &> /dev/null; then + echo "Installing jq" + apt-get update -q + apt-get -q install -y jq + fi + legate_conda_label="$(jq -r '.packages.legate.anaconda_label' ${REPO_DIR}/cmake/versions.json)"; + export CONDA_CHANNEL="legate" + export CONDA_LABEL="${legate_conda_label}" + echo "CONDA_CHANNEL=${CONDA_CHANNEL} : CONDA_LABEL=${CONDA_LABEL}" +} diff --git a/continuous_integration/scripts/test b/continuous_integration/scripts/test index c9b79656dd..5b26dddb37 100755 --- a/continuous_integration/scripts/test +++ b/continuous_integration/scripts/test @@ -5,13 +5,15 @@ set -x setup_env() { set -xeuo pipefail + . conda-dnld-utils + setup_conda_channel; export DEBIAN_FRONTEND=non-interactive # Run package updates and install packages apt-get update apt-get install -y numactl make - mamba create -yn legate -c "${ARTIFACTS_DIR}/conda-build/legate" -c "${ARTIFACTS_DIR}/conda-build/cupynumeric" -c conda-forge legate cupynumeric + mamba create -yn legate -c https://conda.anaconda.org/${CONDA_CHANNEL}/label/${CONDA_LABEL} -c "${ARTIFACTS_DIR}/conda-build/cupynumeric" -c conda-forge legate cupynumeric } setup_test_env() { From af2b21d9e3774c3a0e601b9df334ffba6f4d3f77 Mon Sep 17 00:00:00 2001 From: Manolis Papadakis Date: Wed, 19 Feb 2025 14:04:08 -0800 Subject: [PATCH 421/462] Bump Legate SHA (#614) --- cmake/versions.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmake/versions.json b/cmake/versions.json index 832f595826..acce3d29d2 100644 --- a/cmake/versions.json +++ b/cmake/versions.json @@ -10,7 +10,7 @@ "git_url" : "git@github.com:nv-legate/legate.internal.git", "git_shallow": false, "always_download": false, - "git_tag" : "79203c033610f10b237cc95769b4f4b8016cc2bc", + "git_tag" : "71cb412305c6b441fafb28ff1de1b9a4bcbf0a50", "anaconda_label": "experimental" } } From a66eea207d5b84cffdd3d0c44b94d3a67a5be20b Mon Sep 17 00:00:00 2001 From: Parag Kulkarni Date: Fri, 21 Feb 2025 10:41:10 +0530 Subject: [PATCH 422/462] Don't share the dependency file. (#620) * Don't share the dependency file. * Disable Nightly verification. * Remove the nightly-exists job --- .github/workflows/gh-build-and-test.yml | 21 +-------------------- 1 file changed, 1 insertion(+), 20 deletions(-) diff --git a/.github/workflows/gh-build-and-test.yml b/.github/workflows/gh-build-and-test.yml index 07a41cd702..9751b4a226 100644 --- a/.github/workflows/gh-build-and-test.yml +++ b/.github/workflows/gh-build-and-test.yml @@ -58,7 +58,7 @@ jobs: build-mode: "" build-type: ${{ inputs.build-type }} client-repo: ${{ github.event.repository.name }} - dependencies-file: "cmake/versions.json" + dependencies-file: "" legate-gh-ci-tag: "v1.26" network: "ucx" platform: ${{ inputs.platform }} @@ -69,25 +69,6 @@ jobs: use-container: ${{ inputs.platform == 'linux' || inputs.platform == 'linux-aarch64' }} secrets: inherit - nightly-exists: - needs: setup-build - name: "Check if legate.internal nightly exists for SHA specified in versions.json (${{ inputs.platform }}, ${{ inputs.target-device }}, ${{ inputs.build-type }})" - uses: - nv-legate/legate-gh-ci/.github/workflows/gh-check-if-nightly-exists-for-all-dependencies.yml@v1.26 - with: - build-mode: "" - build-type: ${{ inputs.build-type }} - client-repo: ${{ github.event.repository.name }} - dependencies-file: "cmake/versions.json" - legate-gh-ci-tag: "v1.26" - network: "ucx" - platform: ${{ inputs.platform }} - python-version: ${{ inputs.python-version }} - runs-on: linux-amd64-cpu4 - target-device: ${{ inputs.target-device }} - upload-enabled: ${{ inputs.upload-enabled }} - secrets: inherit - upload: needs: build if: ${{ github.repository_owner == 'nv-legate' && contains(github.workflow, 'release') && inputs.upload-enabled == true }} From 90684e8cd097e2f59d6b84a66d4f59b6e3afdbaf Mon Sep 17 00:00:00 2001 From: Sandeep Datta <128171450+sandeepd-nv@users.noreply.github.com> Date: Fri, 21 Feb 2025 22:01:38 +0530 Subject: [PATCH 423/462] =?UTF-8?q?Renamed:=20=20=20=20.github/workflows/c?= =?UTF-8?q?i-gh-release.yml=20->=20.github/workflows/=E2=80=A6=20(#622)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Note: the test failures are not related to this change. * Renamed: .github/workflows/ci-gh-release.yml -> .github/workflows/ci-gh.yml. Also changed the build-type to ci. * Use build_release_product to build ci package. * Use make_release_env() for ci builds too. * The nightly build now has build-type set to nightly instead of release. * Updated versions.json --- .github/workflows/ci-gh-nightly-release.yml | 2 +- .../{ci-gh-release.yml => ci-gh.yml} | 4 +- cmake/versions.json | 2 +- continuous_integration/scripts/build | 40 +------ .../scripts/build-cupynumeric-conda | 100 ------------------ .../scripts/build-cupynumeric-cpp | 33 ------ .../scripts/build-cupynumeric-wheel | 32 ------ continuous_integration/scripts/make-conda-env | 27 +---- 8 files changed, 8 insertions(+), 232 deletions(-) rename .github/workflows/{ci-gh-release.yml => ci-gh.yml} (96%) delete mode 100755 continuous_integration/scripts/build-cupynumeric-conda delete mode 100755 continuous_integration/scripts/build-cupynumeric-cpp delete mode 100755 continuous_integration/scripts/build-cupynumeric-wheel diff --git a/.github/workflows/ci-gh-nightly-release.yml b/.github/workflows/ci-gh-nightly-release.yml index b4fb0b3b90..44a15644de 100644 --- a/.github/workflows/ci-gh-nightly-release.yml +++ b/.github/workflows/ci-gh-nightly-release.yml @@ -30,7 +30,7 @@ jobs: uses: ./.github/workflows/gh-build-and-test.yml with: - build-type: release + build-type: nightly platform: ${{ matrix.platform }} python-version: ${{ matrix.python-version }} target-device: ${{ matrix.target-device }} diff --git a/.github/workflows/ci-gh-release.yml b/.github/workflows/ci-gh.yml similarity index 96% rename from .github/workflows/ci-gh-release.yml rename to .github/workflows/ci-gh.yml index 115c06af27..62d51df5af 100644 --- a/.github/workflows/ci-gh-release.yml +++ b/.github/workflows/ci-gh.yml @@ -1,4 +1,4 @@ -name: Build Release package +name: Build CI package concurrency: group: ${{ startsWith(github.ref_name, 'main') && format('unique-{0}', github.run_id) || format('ci-build-and-test-on-{0}-from-{1}', github.event_name, github.ref_name) }} @@ -33,7 +33,7 @@ jobs: uses: ./.github/workflows/gh-build-and-test.yml with: - build-type: release + build-type: ci platform: ${{ matrix.platform }} python-version: "3.10" target-device: ${{ matrix.target-device }} diff --git a/cmake/versions.json b/cmake/versions.json index acce3d29d2..8721b38fab 100644 --- a/cmake/versions.json +++ b/cmake/versions.json @@ -10,7 +10,7 @@ "git_url" : "git@github.com:nv-legate/legate.internal.git", "git_shallow": false, "always_download": false, - "git_tag" : "71cb412305c6b441fafb28ff1de1b9a4bcbf0a50", + "git_tag" : "01dcfc9f9d689e79fc3fb937d270f847a21f111a", "anaconda_label": "experimental" } } diff --git a/continuous_integration/scripts/build b/continuous_integration/scripts/build index c5fbaa707a..1893ce10eb 100755 --- a/continuous_integration/scripts/build +++ b/continuous_integration/scripts/build @@ -48,42 +48,6 @@ copy_release_artifacts() { ls -lahR $ARTIFACTS_DIR } -copy_ci_artifacts() { - set -xeuo pipefail; - echo Copying CI artifacts - - mkdir -p "$ARTIFACTS_DIR" - - cp -r /tmp/out "$ARTIFACTS_DIR" - cp -r /tmp/conda-build "$ARTIFACTS_DIR" -} - -build_ci_product() { - set -xeuo pipefail; - - printf "\n\n\n\n********* BUILDING CUPYNUMERIC CPP *********\n" - build-cupynumeric-cpp; - - printf "\n\n\n\n********* BUILDING CUPYNUMERIC WHEEL *********\n" - build-cupynumeric-wheel; - - printf "\n\n\n\n********* BUILDING CUPYNUMERIC CONDA *********\n" - build-cupynumeric-conda; - - copy_ci_artifacts; -} - -build_cupynumeric_fake() { - set -xeuo pipefail; - - mkdir -p /tmp/out /tmp/conda-build/legate /tmp/conda-build/cupynumeric - touch /tmp/out/legate-23.11.00-dummy.tar.bz2 - touch /tmp/conda-build/legate/dummy.txt - touch /tmp/conda-build/cupynumeric/dummy.txt - - copy_ci_artifacts -} - build_project() { . setup-utils; @@ -94,8 +58,8 @@ build_project() { generate_legate_version case "$BUILD_TYPE" in - ci) build_ci_product;; - release) build_release_product;; + ci) build_release_product;; + nightly) build_release_product;; *) return 1;; esac } diff --git a/continuous_integration/scripts/build-cupynumeric-conda b/continuous_integration/scripts/build-cupynumeric-conda deleted file mode 100755 index 4a6a1b2469..0000000000 --- a/continuous_integration/scripts/build-cupynumeric-conda +++ /dev/null @@ -1,100 +0,0 @@ -#!/usr/bin/env bash - -build_cupynumeric_conda_package() { - set -xeuo pipefail; - - local python_version="${PYTHON_VERSION:-}"; - - if [ -z "${python_version}" ]; then - python_version="$(python3 --version 2>&1 | cut -d' ' -f2 | cut -d'.' -f3 --complement)"; - fi - - mkdir -p /tmp/conda-build /tmp/out - cp -r "${ARTIFACTS_DIR}/conda-build/legate" /tmp/conda-build/ - - local conda_build_args=(); - conda_build_args+=(--override-channels); - conda_build_args+=(-c conda-forge); - # the ucx channel is only necessary as a WAR until the real ucx 1.17 package is available on conda-forge - conda_build_args+=(-c https://github.com/nv-legate/ucx-package/raw/main); - conda_build_args+=(-c file:///tmp/conda-build/legate); - conda_build_args+=(--croot /tmp/conda-build/cupynumeric); - conda_build_args+=(--numpy 1.22); - conda_build_args+=(--python ${python_version}); - conda_build_args+=(--no-test); - conda_build_args+=(--no-verify); - conda_build_args+=(--no-build-id); - conda_build_args+=("--build-id-pat=''"); - conda_build_args+=(--no-include-recipe); - conda_build_args+=(--no-anaconda-upload); - - GPU_ENABLED=true - [ "${USE_CUDA}" = "OFF" ] && GPU_ENABLED=false - - UPLOAD_BUILD=true - [ "${UPLOAD_ENABLED:-}" = "OFF" ] && UPLOAD_BUILD=false - - conda_build_args+=(--variants "{gpu_enabled:${GPU_ENABLED},python:${python_version}}"); - - rm -rf /tmp/conda-build/cupynumeric; - mkdir -p /tmp/conda-build/cupynumeric; - - # Synthesize new cupynumeric conda-build build.sh script - - cat < "${REPO_DIR}/conda/conda-build/conda_build_config.yaml" -gpu_enabled: - - "${GPU_ENABLED}" - -upload_build: - - "${UPLOAD_BUILD}" - -python: - - "${python_version}" - -numpy_version: - - ">=1.22,<2" - -cmake_version: - - ">=3.20.1,!=3.23.0" - -use_local_path: - - "true" - -numpy: - - 1.22 - -package_version: - - "$(git -C "${REPO_DIR}" describe --abbrev=0 --tags | sed 's/[a-zA-Z]//g' | cut -d '.' -f -2).00" -EOF - - cat <<"EOF" > "${REPO_DIR}/conda/conda-build/build.sh" -# Install cupynumeric C++ libs -tar -C "$PREFIX" --exclude="*.a" --strip-components=1 -xvf /tmp/out/cupynumeric-*-Linux.tar.gz; - -# Install cupynumeric Python wheel -pip install --no-deps --root / --prefix "$PREFIX" /tmp/out/cupynumeric-*.whl; -EOF - - git -C "${REPO_DIR}" add .; - git -C "${REPO_DIR}" commit --allow-empty --allow-empty-message -n -m ""; - - # Build cuPyNumeric conda package - set +ux - eval "$(conda shell.bash hook)" - conda deactivate - conda create -n build - conda activate build - set -ux - conda install boa - - CUDA=${CUDA_VERSION} \ - conda mambabuild ${conda_build_args[@]} "${REPO_DIR}/conda/conda-build"; - - git -C "${REPO_DIR}" reset --hard HEAD~1; - - cp /tmp/conda-build/cupynumeric/linux-64/cupynumeric-*.tar.bz2 /tmp/out/; - - { set +x; } 2>/dev/null; -} - -(build_cupynumeric_conda_package "$@"); diff --git a/continuous_integration/scripts/build-cupynumeric-cpp b/continuous_integration/scripts/build-cupynumeric-cpp deleted file mode 100755 index 1133f05352..0000000000 --- a/continuous_integration/scripts/build-cupynumeric-cpp +++ /dev/null @@ -1,33 +0,0 @@ -#!/usr/bin/env bash - -build_cupynumeric_cpp() { - set -xeuo pipefail; - - # Build + package cuPyNumeric C++ libs - local cmake_args=(${CMAKE_ARGS:-}); - cmake_args+=(-DBUILD_SHARED_LIBS=ON); - cmake_args+=(-DBUILD_MARCH=${BUILD_MARCH}); - cmake_args+=(-DCMAKE_BUILD_TYPE=Release); - cmake_args+=(-DCMAKE_CUDA_ARCHITECTURES=RAPIDS); - cmake_args+=(-DCMAKE_BUILD_PARALLEL_LEVEL=${JOBS:-$(nproc --ignore=1)}); - cmake_args+=(${@}); - - cmake -S "${REPO_DIR}" -B "${REPO_DIR}/build" ${cmake_args[@]} -GNinja; - - sccache --show-stats; - - time cmake --build "${REPO_DIR}/build" --verbose --parallel ${JOBS:-$(nproc --ignore=1)}; - - sccache --show-stats; - - ( - mkdir -p /tmp/out; - cd "${REPO_DIR}/build"; - cpack -G TGZ; - cp ./*-Linux.tar.gz /tmp/out/; - ); - - { set +x; } 2>/dev/null; -} - -(build_cupynumeric_cpp "$@"); diff --git a/continuous_integration/scripts/build-cupynumeric-wheel b/continuous_integration/scripts/build-cupynumeric-wheel deleted file mode 100755 index 93a0259006..0000000000 --- a/continuous_integration/scripts/build-cupynumeric-wheel +++ /dev/null @@ -1,32 +0,0 @@ -#!/usr/bin/env bash - -build_cupynumeric_wheel() { - set -xeuo pipefail; - - mkdir -p /tmp/out; - - local pip_args=(-vv); - pip_args+=(--wheel-dir /tmp/out); - - if type conda 2>&1 >/dev/null; then - pip_args+=(--no-deps); - pip_args+=(--no-build-isolation); - fi - - local cmake_args=(${CMAKE_ARGS:-}); - cmake_args+=("-DFIND_CUPYNUMERIC_CPP=ON"); - - pwd - echo $REPO_DIR - ls -lahR $REPO_DIR - - cmake_args+=("-Dcupynumeric_ROOT=$REPO_DIR/build"); - - # Build + package cuPyNumeric Python wheel - CMAKE_ARGS="${cmake_args[@]}" \ - pip wheel ${pip_args[@]} "${REPO_DIR}"; - - { set +x; } 2>/dev/null; -} - -(build_cupynumeric_wheel "$@"); diff --git a/continuous_integration/scripts/make-conda-env b/continuous_integration/scripts/make-conda-env index 597d9ee613..286086417e 100755 --- a/continuous_integration/scripts/make-conda-env +++ b/continuous_integration/scripts/make-conda-env @@ -4,29 +4,6 @@ set -x . conda-utils -make_ci_env() { - set -xeuo pipefail - yaml_file=$(find "${ARTIFACTS_DIR}" -name "environment*.yaml" | head -n 1) - - sed -i '$ d' ${yaml_file} - echo " - legate" >> "${yaml_file}" - sed -i "/channels:/!b;:a;n;/^- /ba;i\- ${ARTIFACTS_DIR}/conda-build/legate" ${yaml_file} - [ "${USE_CUDA}" = "ON" ] && - echo " - libcublas-dev" >> "${yaml_file}" && - echo " - libcufft-dev" >> "${yaml_file}" && - echo " - libcurand-dev" >> "${yaml_file}" && - echo " - libcusolver-dev" >> "${yaml_file}"; - - echo "YAML file..." - cat "${yaml_file}" - - mkdir -p /tmp/out; - - cp "${yaml_file}" /tmp/out - - mamba env create -n legate -f "$yaml_file" -} - make_release_env() { mamba create -q -y -n "${CONDA_ENV}" -c conda-forge boa } @@ -35,8 +12,8 @@ make_conda_env() { set -xeuo pipefail case "$1" in - ci) make_ci_env;; - release) make_release_env;; + ci) make_release_env;; + nightly) make_release_env;; *) return 1;; esac From 4654fb192148ef7e635aaf0938adf26147f2405f Mon Sep 17 00:00:00 2001 From: Manolis Papadakis Date: Mon, 24 Feb 2025 14:33:34 -0800 Subject: [PATCH 424/462] Bump Legate, for communicator return size fix (#623) --- cmake/versions.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmake/versions.json b/cmake/versions.json index 8721b38fab..f29fd1ac7f 100644 --- a/cmake/versions.json +++ b/cmake/versions.json @@ -10,7 +10,7 @@ "git_url" : "git@github.com:nv-legate/legate.internal.git", "git_shallow": false, "always_download": false, - "git_tag" : "01dcfc9f9d689e79fc3fb937d270f847a21f111a", + "git_tag" : "d1c9b9c657976c440c5eaad0a37586d9268ee8c5", "anaconda_label": "experimental" } } From 1d40f670074d71b35fc5dda9978c268cddc7a665 Mon Sep 17 00:00:00 2001 From: XiaLuNV <110973296+XiaLuNV@users.noreply.github.com> Date: Tue, 25 Feb 2025 16:29:49 +0800 Subject: [PATCH 425/462] enhance fft_c2r.py (#619) * Update test_fft_c2r.py --- cupynumeric/config.py | 5 ++++- tests/integration/test_fft_c2r.py | 35 +++++++++++++++++++++++++++++++ 2 files changed, 39 insertions(+), 1 deletion(-) diff --git a/cupynumeric/config.py b/cupynumeric/config.py index b3cac0573b..2e07da314e 100644 --- a/cupynumeric/config.py +++ b/cupynumeric/config.py @@ -823,7 +823,10 @@ def from_string(in_string: str) -> FFTNormalization | None: elif in_string == "backward" or in_string is None: return FFTNormalization.INVERSE else: - return None + raise ValueError( + f'Invalid norm value {in_string}; should be "backward",' + '"ortho" or "forward".' + ) @staticmethod def reverse(in_string: str | None) -> str: diff --git a/tests/integration/test_fft_c2r.py b/tests/integration/test_fft_c2r.py index a5ae7340c8..3c16fd6dbd 100644 --- a/tests/integration/test_fft_c2r.py +++ b/tests/integration/test_fft_c2r.py @@ -41,6 +41,8 @@ def check_1d_c2r(N, dtype=np.float64): all_kwargs = ( {}, {"norm": "forward"}, + {"norm": "ortho"}, + {"norm": "backward"}, {"n": N // 2}, {"n": N // 2 + 1}, {"n": N * 2}, @@ -53,6 +55,11 @@ def check_1d_c2r(N, dtype=np.float64): out_num = num.fft.irfft(Z_num, **kwargs) assert allclose(out, out_num) + out = np.fft.hfft(Z, **kwargs) + out_num = num.fft.hfft(Z_num, **kwargs) + assert allclose(out, out_num) + assert allclose(Z, Z_num) + # Odd types out = np.fft.rfft(Z.real) out_num = num.fft.rfft(Z_num.real) @@ -73,6 +80,8 @@ def check_2d_c2r(N, dtype=np.float64): all_kwargs = ( {}, {"norm": "forward"}, + {"norm": "ortho"}, + {"norm": "backward"}, {"s": (N[0] // 2, N[1] - 2)}, {"s": (N[0] + 1, N[0] + 2)}, {"s": (N[0] // 2 + 1, N[0] + 2)}, @@ -216,6 +225,32 @@ def test_4d(): check_4d_c2r(N=(6, 12, 10, 8), dtype=np.float32) +def test_1d_int() -> None: + Z = np.random.randint(1, 10, size=8) + Z_num = num.array(Z) + msg = r"Data type for FFT not supported" + with pytest.raises(TypeError, match=msg): + num.fft.rfft(Z_num) + msg = r"Data type for FFT not supported" + with pytest.raises(TypeError, match=msg): + num.fft.irfft(Z_num) + msg = r"Data type for FFT not supported" + with pytest.raises(TypeError, match=msg): + num.fft.ihfft(Z_num) + + +def test_norm_invalid() -> None: + Z = ( + np.random.rand(8).astype(np.float64) + + np.random.rand(8).astype(np.float64) * 1j + ) + Z_num = num.array(Z) + msg = r"Invalid norm value" + with pytest.raises(ValueError, match=msg): + np.fft.rfft(Z, norm="other") + with pytest.raises(ValueError, match=msg): + num.fft.rfft(Z_num, norm="other") + if __name__ == "__main__": import sys From b0cf59cb2ed3e6000b2f43413a25997f987fc0ab Mon Sep 17 00:00:00 2001 From: Marcin Zalewski Date: Wed, 26 Feb 2025 23:02:56 -0800 Subject: [PATCH 426/462] Fix channel order in conda package installation (#626) --- .github/workflows/ci-gh.yml | 2 +- conda/conda-build/meta.yaml | 1 - continuous_integration/scripts/conda-dnld-utils | 2 ++ continuous_integration/scripts/test | 5 ++++- 4 files changed, 7 insertions(+), 3 deletions(-) diff --git a/.github/workflows/ci-gh.yml b/.github/workflows/ci-gh.yml index 62d51df5af..8b233a6b72 100644 --- a/.github/workflows/ci-gh.yml +++ b/.github/workflows/ci-gh.yml @@ -35,7 +35,7 @@ jobs: with: build-type: ci platform: ${{ matrix.platform }} - python-version: "3.10" + python-version: ${{ matrix.python-version }} target-device: ${{ matrix.target-device }} upload-enabled: ${{ matrix.upload-enabled }} waive-gpu-tests: ${{ github.workflow == 'Build Release package' && matrix.platform == 'linux-aarch64' }} diff --git a/conda/conda-build/meta.yaml b/conda/conda-build/meta.yaml index 590cf9f0a7..103727fdc9 100644 --- a/conda/conda-build/meta.yaml +++ b/conda/conda-build/meta.yaml @@ -134,7 +134,6 @@ requirements: - opt_einsum >=3.3 - scipy - openblas =* =*openmp* - - legate ={{ legate_version }}={{ legate_buildstr }} {% if gpu_enabled_bool %} - cupy - libnvjitlink diff --git a/continuous_integration/scripts/conda-dnld-utils b/continuous_integration/scripts/conda-dnld-utils index cffe1261eb..37ea9551fb 100755 --- a/continuous_integration/scripts/conda-dnld-utils +++ b/continuous_integration/scripts/conda-dnld-utils @@ -24,6 +24,8 @@ setup_conda_channel() { apt-get update -q apt-get -q install -y jq fi + # strict channel ordering is required for prioritizing packages from artifacts + conda config --set channel_priority strict legate_conda_label="$(jq -r '.packages.legate.anaconda_label' ${REPO_DIR}/cmake/versions.json)"; export CONDA_CHANNEL="legate" export CONDA_LABEL="${legate_conda_label}" diff --git a/continuous_integration/scripts/test b/continuous_integration/scripts/test index 5b26dddb37..0da9fb67d2 100755 --- a/continuous_integration/scripts/test +++ b/continuous_integration/scripts/test @@ -13,7 +13,10 @@ setup_env() { apt-get update apt-get install -y numactl make - mamba create -yn legate -c https://conda.anaconda.org/${CONDA_CHANNEL}/label/${CONDA_LABEL} -c "${ARTIFACTS_DIR}/conda-build/cupynumeric" -c conda-forge legate cupynumeric + mamba search --override-channels -c "${ARTIFACTS_DIR}/conda-build/cupynumeric" --info cupynumeric + + # This requires strict channel priority to work (prioritize local channel) + mamba create -y -n legate -c "${ARTIFACTS_DIR}/conda-build/cupynumeric" -c https://conda.anaconda.org/${CONDA_CHANNEL}/label/${CONDA_LABEL} -c conda-forge legate cupynumeric } setup_test_env() { From 8bb56fc3f8d122afbc71b3bb8d38d76939d58f7e Mon Sep 17 00:00:00 2001 From: Andrei Schaffer <37386037+aschaffer@users.noreply.github.com> Date: Thu, 27 Feb 2025 12:19:05 -0600 Subject: [PATCH 427/462] Matrix Exponential (#445) * Preliminary Arnoldi. * Better overall algorithm that skips partial fraction decomposition. * Out-of-loop logic and truncated loop. * Make [U, V]. * Refactor U,V construction and use. * Preliminary implementation. * Improved comments. * Minor fixes. * More minor fixes. * np.power() not np.pow(). * Fixed n var creation. * Updated comments on factorization. * Fixes and optimizations. * Added SH test. * Fixed path to zeros. * Fixed test. * Polished the test, a bit. * Fixed the ndarray.__buffer__ warnings. * Fixed cupynumeric in test. * More tests and interface somilar to SciPy, etc. * Fixed the multi-dimensional case ndim > 2. * Tensor test. * Added Taylor variant. * Default to Pade, again. * 2 GPU alternate runs and default method flag. * expm documentation. * expm rst documentation and clean-up. * Update cupynumeric/linalg/linalg.py Co-authored-by: Manolis Papadakis * Update cupynumeric/linalg/linalg.py Co-authored-by: Manolis Papadakis * Update cupynumeric/linalg/linalg.py Co-authored-by: Manolis Papadakis * Update cupynumeric/linalg/linalg.py Co-authored-by: Manolis Papadakis * Update cupynumeric/linalg/linalg.py Co-authored-by: Manolis Papadakis * Update cupynumeric/linalg/linalg.py Co-authored-by: Manolis Papadakis * Update cupynumeric/linalg/linalg.py Co-authored-by: Manolis Papadakis * Update cupynumeric/linalg/linalg.py Co-authored-by: Manolis Papadakis * Update cupynumeric/linalg/linalg.py Co-authored-by: Manolis Papadakis * Update cupynumeric/linalg/linalg.py Co-authored-by: Manolis Papadakis * Update cupynumeric/linalg/linalg.py Co-authored-by: Manolis Papadakis * Update cupynumeric/linalg/linalg.py Co-authored-by: Manolis Papadakis * Addressed code review comment (ACRC) on num_GPUs and fixed zeros_like. * ACRC method string + matrix_norm fix. * ACRC removed unused Arnoldi. * Fix: replaced np.matrix_norm. * ACRC removed expensive assert. * ACRC on memoizing constants. * ACRC on modifying output in-place. * ACRC on array.py: __buffer__() redefinition. * ACRC on assignments on separate lines. * Added expm Taylor tests. * Fixed mypy failures. * Update cupynumeric/linalg/linalg.py Co-authored-by: Manolis Papadakis * ACRC on squaring output in-place. * (Almost) Empty commit to attempt to fix github hang. --------- Co-authored-by: Manolis Papadakis Co-authored-by: Manolis Papadakis --- cupynumeric/linalg/linalg.py | 326 ++++++++++++++++++++++- docs/cupynumeric/source/api/linalg.rst | 9 + tests/integration/test_expm_sh.py | 193 ++++++++++++++ tests/integration/test_expm_sh_cpx_qr.py | 158 +++++++++++ 4 files changed, 685 insertions(+), 1 deletion(-) create mode 100644 tests/integration/test_expm_sh.py create mode 100644 tests/integration/test_expm_sh_cpx_qr.py diff --git a/cupynumeric/linalg/linalg.py b/cupynumeric/linalg/linalg.py index 25f9ed964a..1e025b03fb 100644 --- a/cupynumeric/linalg/linalg.py +++ b/cupynumeric/linalg/linalg.py @@ -14,7 +14,7 @@ # from __future__ import annotations -from typing import TYPE_CHECKING, Sequence +from typing import TYPE_CHECKING, Sequence, Any import numpy as np @@ -35,6 +35,8 @@ from .._module import dot, empty_like, eye, matmul, ndarray from .._ufunc.math import add, sqrt as _sqrt from ._exception import LinAlgError +from .._module.creation_shape import zeros, zeros_like +from legate.core import get_machine, TaskTarget if TYPE_CHECKING: import numpy.typing as npt @@ -833,3 +835,325 @@ def _thunk_svd(a: ndarray, full_matrices: bool) -> tuple[ndarray, ...]: a._thunk.svd(out_u._thunk, out_s._thunk, out_vh._thunk) return out_u, out_s, out_vh + + +# helper function to construct rational Pade +# numerator / denominator for expm(A): +# +def make_uv(A: ndarray, + b: Any, + m: int) -> tuple[ndarray, ndarray]: + # 1 + floor(m/2): + # + k = 1 + m // 2 + n = A.shape[0] + + U = zeros((n, n), dtype=A.dtype) + V = zeros((n, n), dtype=A.dtype) + + # U := A * ∑_{j=0, k} b_{2j+1} * A^{2j}; + # V := ∑_{j=0, k} b_{2j} * A^{2j}; + # + A2 = matmul(A, A) + A2k = eye(n, dtype=A.dtype) + for j in range(k): + U = U + b[2*j+1] * A2k + V = V + b[2*j] * A2k + A2k = matmul(A2k, A2) + + U = matmul(A, U) + + return (U, V) + + +class ExpmConstants: + """ + Aggregates all the necessary expm(A) constants. + """ + # Pade `b` coefficient generators + # for both numerator `p(x)` and + # denominator `q(x)` coefficients + # + # dictionary key := `m`, degree of + # both `p(x)` and `q(x)` for + # diagonal Pade implementation; + # + b_coeff = { + 3: np.array([120, 60, 12, 1], dtype = np.float64), + 5: np.array([30240, 15120, 3360, 420, 30, 1], dtype = np.float64), + 7: np.array([17297280, 8648640, 1995840, 277200, 25200, 1512, 56, 1], + dtype = np.float64), + 9: np.array( + [17643225600, 8821612800, 2075673600, 302702400, 30270240, + 2162160, 110880, 3960, 90, 1], dtype = np.float64 + ), + 13: np.array( + [64764752532480000, 32382376266240000, 7771770303897600, + 1187353796428800, 129060195264000, 10559470521600, + 670442572800, 33522128640, 1323241920, 40840800, + 960960, 16380, 182, 1], dtype = np.float64 + ), + } + + # Pade error control: absolute error tolerance + # parameter `theta`, also degree `m` dependent: + # + theta = { + 3: 1.5e-2, + 5: 2.5e-1, + 7: 9.5e-1, + 9: 2.1, + 13:5.4, + } + + # Taylor-18 coefficients + # + a01 = 0 + a11 = -0.10036558103014462001 + a21 = -0.00802924648241156960 + a31 = -0.00089213849804572995 + + b01 = 0 + b11 = 0.39784974949964507614 + b21 = 1.36783778460411719922 + b31 = 0.49828962252538267755 + b61 = -0.00063789819459472330 + b02 = -10.9676396052962062593 + b12 = 1.68015813878906197182 + b22 = 0.05717798464788655127 + b32 = -0.00698210122488052084 + b62 = 0.00003349750170860705 + b03 = -0.09043168323908105619 + b13 = -0.06764045190713819075 + b23 = 0.06759613017704596460 + b33 = 0.02955525704293155274 + b63 = -0.00001391802575160607 + b04 = 0 + b14 = 0 + b24 = -0.09233646193671185927 + b34 = -0.01693649390020817171 + b64 = -0.00001400867981820361 + + # Taylor-18 error control (squaring and scalling decision): + # + theta_m = 1.09 + + +def expm_impl(a: ndarray, output: ndarray) -> tuple[int, int]: + """ + Implements Pade rational aproximant of + Algorithm 10.20, p.246-247 in + "Functions of Matrices - Theory and Computation", + Nicholas J. Higham, SIAM 2008. + """ + + n = a.shape[0] + lst_keys = list(ExpmConstants.theta.keys()) + + # maximum polynomial degree for [p(x)/q(x)]: + max_deg = lst_keys[-1] + + # L1 norm of matrix input: + l1_norm_a = norm(a, 1) + + # loop decides which Pade degree, `m`, to + # use, starting with the lowest degree + # up to the one before last degree; + # + # if neither satisfies the theta tolerance + # then exit the loop and proceed by using + # m=max_deg degree + scaling (to achieve + # desired tolerance); + # + requires_scaling = True + s = 0 + a_scaled = a + + for m in lst_keys[0:-1]: + tol_m = ExpmConstants.theta[m] + b_arr = ExpmConstants.b_coeff[m] + if l1_norm_a <= tol_m: + requires_scaling = False + break + + # at this point scaling + squaring with [max_deg/max_deg] + # Pade rational approximation is done; + # + # using [max_deg/max_deg] Pade with scaling A/(2^s) + # until || A / (2^s) ||_1 <= tol_13; + # i.e., s = ceil(log_2(|| A / (2^s) ||_1)): + # + if requires_scaling: + m = max_deg + tol_m = ExpmConstants.theta[m] + b_arr = ExpmConstants.b_coeff[m] + + s = np.maximum(1, int(np.ceil(np.log2(l1_norm_a / tol_m)))) + # + # scale `a` by sfactor = 1.0/2^s = 2^(-s): + # + sfactor = np.power(2.0, s) + # + # A' <- A / sfactor + # + a_scaled = a / sfactor + + # evaluate U, V matrices, via Eq. 10.33 of [1] + # k = 1 + floor(m/2): + # U := A * ∑_{j=0, k} b_{2j+1} * A^{2j}; + # V := ∑_{j=0, k} b_{2j} * A^{2j}; + # + (U, V) = make_uv(a_scaled, b_arr, m) + A = V - U + B = V + U + + # independently solve for each column: + # TODO: can more parallelism be harvested here? + # at the very least avoid oversolving by + # doing LU / QR factorization once, followed + # by `n` backward-forward substitutions; + # + output[:] = solve(A, B) + + # if scaling by 1/2^s was done then + # squaring s times is necessary: + # + if requires_scaling: + for j in range(s): + output[:] = matmul(output, output) + + return (m, s) + + +def expm_expl(a: ndarray, output: ndarray) -> tuple[int, int]: + """ + Implements Taylor expansion, algorithm T_18 + in "Computing the Matrix Exponential with an + Optimized Taylor Polynomial Approximation", + Philipp Bader et. al., + which minimizes the number of matrix products + for given number of terms in the expansion. + """ + + tol_m = ExpmConstants.theta_m # may vary w/ degree, m, in future impls. + + # L1 norm of matrix input: + l1_norm_a = norm(a, 1) + + requires_scaling = (l1_norm_a > tol_m) + + s = 0 + A = a + m = 18 + + if requires_scaling: + s = np.maximum(1, int(np.ceil(np.log2(l1_norm_a / tol_m)))) + # + # scale `a` by sfactor = 1.0/2^s = 2^(-s): + # + sfactor = np.power(2.0, s) + # + # A' <- A / sfactor + # + A = a / sfactor + + I = eye(A.shape[0], dtype=A.dtype) + A2 = matmul(A, A) + A3 = matmul(A2, A) + A6 = matmul(A3, A3) + B1 = ExpmConstants.a11*A + ExpmConstants.a21*A2 + ExpmConstants.a31*A3 + B2 = ExpmConstants.b11*A + ExpmConstants.b21*A2 + ExpmConstants.b31*A3 \ + + ExpmConstants.b61*A6 + B3 = ExpmConstants.b02*I + ExpmConstants.b12*A + ExpmConstants.b22*A2 \ + + ExpmConstants.b32*A3 + ExpmConstants.b62*A6 + B4 = ExpmConstants.b03*I + ExpmConstants.b13*A + ExpmConstants.b23*A2 \ + + ExpmConstants.b33*A3 + ExpmConstants.b63*A6 + B5 = ExpmConstants.b24*A2 + ExpmConstants.b34*A3 + ExpmConstants.b64*A6 + + A9 = B4 + matmul(B1, B5) + B39 = B3 + A9 + + output[:] = B2 + matmul(B39, A9) + + # if scaling by 1/2^s was done then + # squaring s times is necessary: + # + if requires_scaling: + for j in range(s): + output[:] = matmul(output, output) + + return (m, s) + + +@add_boilerplate("a") +def expm(a: ndarray, method: str = "pade") -> ndarray: + """ + Matrix exponential. + + Returns exp(A) for each (M x M) slice into a multi-dimensional + array, assumed to be of shape (..., M, M); + + By default Pade (implicit) implementation is used. + However, explicit Taylor(deg = 18) implementation can be used, + by supplying additional flag `use_explicit = True`. + + Parameters + ---------- + a : (..., M, M) array_like + Input matrix or multi-dimensional array of shape (..., M, M). + + method : String method selector to use explicit ('taylor') + or implicit ('pade'); default = 'pade'. + + Returns + ------- + exp(A): matrix exponential of input, or a matrix exponential + for each slice in the input. + + Notes + ----- + Implicit Pade implementation is more stable but more computationally intensive than + explicit Taylor, which is less stable when matrix norm is big enough. + Also, Taylor can be slightly more performant for matrices of small + enough norms, but more memory consuming. + + See Also + -------- + scipy.linalg.expm + + Availability + -------- + Multiple GPUs, Multiple CPUs + """ + + if a.ndim < 2 or a.shape[-1] != a.shape[-2] or a.size <= 1: + raise ValueError(f"Invalid input shape for expm: {a.shape}") + + output = zeros_like(a) + + m_info = get_machine() + num_PEs = m_info.count() + + # run implicit (Pade) method by default: + # + if method == "pade": + expm_func = expm_impl + elif method == "taylor": + expm_func = expm_expl + else: + raise ValueError(f"Method {method} not supported.") + + if num_PEs < 2: + for idx in np.ndindex(a.shape[:-2]): + mdeg, s = expm_func(a[idx], output[idx]) + else: + for idx in np.ndindex(a.shape[:-2]): + flat_index = np.ravel_multi_index(idx, a.shape[:-2]) + + # assign work to multiple GPUs in round-robin way: + # + findx = int(flat_index) + with m_info[findx % num_PEs]: + mdeg, s = expm_func(a[idx], output[idx]) + + return output diff --git a/docs/cupynumeric/source/api/linalg.rst b/docs/cupynumeric/source/api/linalg.rst index 0ccd9d4e67..d57692d726 100644 --- a/docs/cupynumeric/source/api/linalg.rst +++ b/docs/cupynumeric/source/api/linalg.rst @@ -49,3 +49,12 @@ Solving equations and inverting matrices :toctree: generated/ linalg.solve + + +Matrix Functions +---------------- + +.. autosummary:: + :toctree: generated/ + + linalg.expm diff --git a/tests/integration/test_expm_sh.py b/tests/integration/test_expm_sh.py new file mode 100644 index 0000000000..4042410ca7 --- /dev/null +++ b/tests/integration/test_expm_sh.py @@ -0,0 +1,193 @@ +# Copyright 2024 NVIDIA Corporation +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +import numpy as np +import pytest +from utils.comparisons import allclose + +import cupynumeric as num +import scipy as sp + +SIZES = (4, 10) + +RTOL = { + np.dtype(np.float32): 1e-1, + np.dtype(np.complex64): 1e-1, + np.dtype(np.float64): 1e-5, + np.dtype(np.complex128): 1e-5, +} + +ATOL = { + np.dtype(np.float32): 1e-3, + np.dtype(np.complex64): 1e-3, + np.dtype(np.float64): 1e-8, + np.dtype(np.complex128): 1e-8, +} + +def make_skew_hermitian(n: int, + min_v: float = 0.0, + max_v: float = 100.0) -> np.ndarray: + num_off_d = int(n*(n-1)/2) + + r_array = np.array([ np.random.uniform(min_v, max_v) + for k in range(num_off_d)], dtype=np.dtype('float64')) + + i_array = np.array([ np.random.uniform(min_v, max_v) + for k in range(num_off_d)], dtype=np.dtype('float64')) + + d_array = np.array([ np.random.uniform(min_v, max_v) + for k in range(n)], dtype=np.dtype('float64')) + + mat = np.zeros((n, n), dtype=np.dtype('complex64')) + + arr_index = 0 + for col in range(1, n): + for row in range(0, col): + mat[row, col] = r_array[arr_index] + i_array[arr_index]*1.j + mat[col, row] = -np.conjugate(mat[row, col]) + + arr_index = arr_index + 1 + + c_1 = col - 1 + mat[c_1, c_1] = d_array[c_1]*1.j + + mat[n-1][n-1] = d_array[n-1]*1.j + + return mat + + +def check_skew_hermitian(A: np.ndarray) -> bool: + assert A.ndim == 2 + n = A.shape[0] + assert n == A.shape[1] + num_half_off_d = int(n*(n-1)/2) + + arr_off_d = np.array([A[i, j] + np.conjugate(A[j, i]) for i in range(n) + for j in range(i)], + dtype=np.dtype('complex64')) + + check_arr = np.zeros((num_half_off_d, ), dtype=np.dtype('complex64')) + assert arr_off_d.size == num_half_off_d + + assert allclose(arr_off_d, check_arr, atol=ATOL[A.dtype], + check_dtype=False + ) + + assert np.all([np.real(A[k, k]) for k in range(n)] == np.zeros(n)) + return True + + +@pytest.mark.parametrize("n", SIZES) +@pytest.mark.parametrize( + "min_v", (0.0, ) +) +@pytest.mark.parametrize( + "max_v", (10.0,) +) +def test_expm_rnd_sh_tensor_pade(n, min_v, max_v): + m = 3 + a = np.zeros(shape=(m,n,n), dtype=np.complex64) + for idx in np.ndindex(a.shape[:-2]): + a[idx] = make_skew_hermitian(n, min_v, max_v) + + # more info for debug purposes: + # (out_num, m, s) = num.linalg.expm_impl(a) + # + out_num = num.linalg.expm(a, method='pade') + out_s = sp.linalg.expm(a) + + rtol = RTOL[out_num.dtype] + atol = ATOL[out_num.dtype] + if n > 1024: + atol *= 20.0 + + tol_satisfied = allclose( + out_num, out_s, rtol=rtol, atol=atol, check_dtype=False + ) + + # scipy result may not be reliable, + # hence check which exp L2 norm is + # closer to unity: + # + if tol_satisfied == False: + for i in range(m): + # check diff in ||exp(A)||_2: + # + norm_exp_s = np.linalg.norm(out_s[i], ord=2) + norm_exp_num = np.linalg.norm(out_num[i], ord=2) + # + # conversion to string shows more decimals... + # + print("external ||exp(A)|| = %s\n"%(str(norm_exp_s))) + print("Cupynumeric ||exp(A)|| = %s\n"%(str(norm_exp_num))) + + assert np.abs(1.0 - norm_exp_num) <= np.abs(1.0 - norm_exp_s) + + assert True + + +@pytest.mark.parametrize("n", SIZES) +@pytest.mark.parametrize( + "min_v", (0.0, ) +) +@pytest.mark.parametrize( + "max_v", (10.0,) +) +def test_expm_rnd_sh_tensor_taylor(n, min_v, max_v): + m = 3 + a = np.zeros(shape=(m,n,n), dtype=np.complex64) + for idx in np.ndindex(a.shape[:-2]): + a[idx] = make_skew_hermitian(n, min_v, max_v) + + # more info for debug purposes: + # (out_num, m, s) = num.linalg.expm_impl(a) + # + out_num = num.linalg.expm(a, method='taylor') + out_s = sp.linalg.expm(a) + + rtol = RTOL[out_num.dtype] + atol = ATOL[out_num.dtype] + if n > 1024: + atol *= 20.0 + + tol_satisfied = allclose( + out_num, out_s, rtol=rtol, atol=atol, check_dtype=False + ) + + # scipy result may not be reliable, + # hence check which exp L2 norm is + # closer to unity: + # + if tol_satisfied == False: + for i in range(m): + # check diff in ||exp(A)||_2: + # + norm_exp_s = np.linalg.norm(out_s[i], ord=2) + norm_exp_num = np.linalg.norm(out_num[i], ord=2) + # + # conversion to string shows more decimals... + # + print("external ||exp(A)|| = %s\n"%(str(norm_exp_s))) + print("Cupynumeric ||exp(A)|| = %s\n"%(str(norm_exp_num))) + + assert np.abs(1.0 - norm_exp_num) <= np.abs(1.0 - norm_exp_s) + + assert True + + +if __name__ == "__main__": + import sys + + sys.exit(pytest.main(sys.argv)) diff --git a/tests/integration/test_expm_sh_cpx_qr.py b/tests/integration/test_expm_sh_cpx_qr.py new file mode 100644 index 0000000000..3f595dcffd --- /dev/null +++ b/tests/integration/test_expm_sh_cpx_qr.py @@ -0,0 +1,158 @@ +# Copyright 2024 NVIDIA Corporation +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +import numpy as np +# import cupy as cp +# import cupyx.scipy.linalg as cpxl +import pytest +from utils.comparisons import allclose + +import cupynumeric as num +import scipy as sp + +SIZES = (4, 10, 50) + +RTOL = { + np.dtype(np.float32): 1e-1, + np.dtype(np.complex64): 1e-1, + np.dtype(np.float64): 1e-5, + np.dtype(np.complex128): 1e-5, +} + +ATOL = { + np.dtype(np.float32): 1e-3, + np.dtype(np.complex64): 1e-3, + np.dtype(np.float64): 1e-6, + np.dtype(np.complex128): 1e-6, +} + +def make_skew_hermitian(n: int, + min_v: float = 0.0, + max_v: float = 100.0) -> np.ndarray: + num_off_d = int(n*(n-1)/2) + + np.random.seed(1729) + + r_array = np.array([ np.random.uniform(min_v, max_v) + for k in range(num_off_d)], dtype=np.dtype('float64')) + + i_array = np.array([ np.random.uniform(min_v, max_v) + for k in range(num_off_d)], dtype=np.dtype('float64')) + + d_array = np.array([ np.random.uniform(min_v, max_v) + for k in range(n)], dtype=np.dtype('float64')) + + mat = np.zeros((n, n), dtype=np.dtype('complex64')) + + arr_index = 0 + for col in range(1, n): + for row in range(0, col): + mat[row, col] = r_array[arr_index] + i_array[arr_index]*1.j + mat[col, row] = -np.conjugate(mat[row, col]) + + arr_index = arr_index + 1 + + c_1 = col - 1 + mat[c_1, c_1] = d_array[c_1]*1.j + + mat[n-1][n-1] = d_array[n-1]*1.j + + return mat + + +def check_skew_hermitian(A: np.ndarray) -> bool: + assert A.ndim == 2 + n = A.shape[0] + assert n == A.shape[1] + num_half_off_d = int(n*(n-1)/2) + + arr_off_d = np.array([A[i, j] + np.conjugate(A[j, i]) for i in range(n) + for j in range(i)], + dtype=np.dtype('complex64')) + + check_arr = np.zeros((num_half_off_d, ), dtype=np.dtype('complex64')) + assert arr_off_d.size == num_half_off_d + + assert allclose(arr_off_d, check_arr, atol=ATOL[A.dtype], + check_dtype=False + ) + + assert np.all([np.real(A[k, k]) for k in range(n)] == np.zeros(n)) + return True + + +@pytest.mark.parametrize("n", SIZES) +@pytest.mark.parametrize( + "min_v", (0.0, )#10.0) +) +@pytest.mark.parametrize( + "max_v", (2.0,) #100.0) +) +def test_expm_rnd_skew_h(n, min_v, max_v): + a = make_skew_hermitian(n, min_v, max_v) + check_skew_hermitian(a) + + # more info for debug purposes: + # (out_num, m, s) = num.linalg.expm_impl(a) + # + out_num = num.linalg.expm(a) + out_s = sp.linalg.expm(a) + + # cupy experiments: + # (keep this code for possible future use) + # + # a_cp = cp.asarray(a) + # out_cp = cpxl.expm(a_cp) + # out_s = cp.asnumpy(out_cp) + + rtol = RTOL[out_num.dtype] + atol = ATOL[out_num.dtype] + if n > 1024: + atol *= 20.0 + + print("\nexternal solver: %s\n"%(str(out_s))) + print("CuPyNumeric: %s\n"%(str(out_num))) + + tol_satisfied = allclose( + out_num, out_s, rtol=rtol, atol=atol, check_dtype=False + ) + + if tol_satisfied == False: + # check diff in ||exp(A)||_2: + # + norm_exp_s = np.linalg.norm(out_s, ord=2) + norm_exp_num = np.linalg.norm(out_num, ord=2) + # + # conversion to string shows more decimals... + # + print("external ||exp(A)|| = %s\n"%(str(norm_exp_s))) + print("Cupynumeric ||exp(A)|| = %s\n"%(str(norm_exp_num))) + assert np.abs(1.0 - norm_exp_num) <= np.abs(1.0 - norm_exp_s) + + (_, R) = np.linalg.qr(a) + min_abs_diag = np.min([np.abs(R[k, k]) for k in range(a.shape[0])]) + if min_abs_diag.item() < atol: + print("source matrix close to singular!") + assert False + + return + + assert True + + +if __name__ == "__main__": + import sys + + sys.exit(pytest.main(sys.argv)) From e5f963c60cf3dd1e35098c2d94eebc8b91779f4e Mon Sep 17 00:00:00 2001 From: Bryan Van de Ven Date: Thu, 27 Feb 2025 12:09:53 -0800 Subject: [PATCH 428/462] add version fixups from legate (#629) --- cupynumeric/__init__.py | 15 ++++++- cupynumeric/_utils/array.py | 6 ++- docs/cupynumeric/source/conf.py | 23 ++++++++-- docs/cupynumeric/switcher.json | 9 +++- tests/integration/test_fft_c2r.py | 1 + tests/integration/test_index_routines.py | 1 - tests/integration/test_local_task_array.py | 5 ++- tests/integration/test_random.py | 4 +- tests/unit/cupynumeric/test___init__.py | 49 ++++++++++++++++++++++ 9 files changed, 98 insertions(+), 15 deletions(-) create mode 100644 tests/unit/cupynumeric/test___init__.py diff --git a/cupynumeric/__init__.py b/cupynumeric/__init__.py index 1893a42d59..51d4b415b9 100644 --- a/cupynumeric/__init__.py +++ b/cupynumeric/__init__.py @@ -40,6 +40,17 @@ del clone_module del _np -from . import _version -__version__ = _version.get_versions()["version"] # type: ignore [no-untyped-call] +def _fixup_version() -> str: + import os + + if (v := os.environ.get("CUPYNUMERIC_USE_VERSION")) is not None: + return v + + from . import _version + + return _version.get_versions()["version"] # type: ignore [no-untyped-call] + + +__version__ = _fixup_version() +del _fixup_version diff --git a/cupynumeric/_utils/array.py b/cupynumeric/_utils/array.py index ac9bfce082..5ad037e39b 100644 --- a/cupynumeric/_utils/array.py +++ b/cupynumeric/_utils/array.py @@ -17,9 +17,9 @@ from functools import reduce from typing import TYPE_CHECKING, Any -from legate.core import PhysicalArray, StoreTarget import legate.core.types as ty import numpy as np +from legate.core import PhysicalArray, StoreTarget from ..types import NdShape @@ -116,6 +116,7 @@ def min_identity( else: raise ValueError(f"Unsupported dtype: {ty}") + def local_task_array(obj: PhysicalArray | PhysicalStore) -> Any: """ Generate an appropriate local-memory ndarray object, that is backed by the @@ -139,6 +140,7 @@ def local_task_array(obj: PhysicalArray | PhysicalStore) -> Any: # cupy is only a dependency for GPU packages -- but we should # only hit this import in case the store is located on a GPU import cupy # type: ignore [import-untyped,import-not-found] + return cupy.asarray(store) else: - return np.asarray(store) \ No newline at end of file + return np.asarray(store) diff --git a/docs/cupynumeric/source/conf.py b/docs/cupynumeric/source/conf.py index 997125fda9..832a83ebac 100644 --- a/docs/cupynumeric/source/conf.py +++ b/docs/cupynumeric/source/conf.py @@ -15,7 +15,7 @@ from os import getenv -from cupynumeric import __version__ +import cupynumeric SWITCHER_PROD = "https://docs.nvidia.com/cupynumeric/switcher.json" SWITCHER_DEV = "http://localhost:8000/switcher.json" @@ -23,14 +23,29 @@ ANNOTATE = getenv("LEGATE_ANNOTATION_DOCS") == "1" +# This is the "YY.MM" version string that we want users to see +BASE_VERSION = ".".join(cupynumeric.__version__.split(".", 2)[:2]) + +# make sure BASE VERSION is formatted as expected +_yy, _mm = BASE_VERSION.split(".") +assert _yy.isdigit() +assert _mm.isdigit() + # -- Project information ----------------------------------------------------- project = "NVIDIA cuPyNumeric" -if "dev" in __version__: - project += f" ({__version__})" copyright = "2024, NVIDIA" author = "NVIDIA Corporation" +if "dev" in cupynumeric.__version__ or "rc" in cupynumeric.__version__: + # for dev/rc versions just use the entire version with everything, and + # add it to the page title as well, for easy recognition + version = release = cupynumeric.__version__ + project += f" ({cupynumeric.__version__})" +else: + # otherwise, we actually only want the YY.MM to be visible for releases + version = release = BASE_VERSION + # -- General configuration --------------------------------------------------- extensions = [ @@ -68,7 +83,7 @@ "switcher": { "json_url": JSON_URL, "navbar_start": ["navbar-logo", "version-switcher"], - "version_match": ".".join(__version__.split(".", 2)[:2]), + "version_match": BASE_VERSION, }, "extra_footer": [ "This project, i.e., cuPyNumeric, is separate and independent of the CuPy project. CuPy is a registered trademark of Preferred Networks.", # NOQA diff --git a/docs/cupynumeric/switcher.json b/docs/cupynumeric/switcher.json index 07524ee89a..257436bb46 100644 --- a/docs/cupynumeric/switcher.json +++ b/docs/cupynumeric/switcher.json @@ -6,8 +6,13 @@ }, { "name": "25.01", - "preferred": true, "version": "25.01", "url": "https://docs.nvidia.com/cupynumeric/25.01/" - } + }, + { + "name": "25.03", + "version": "25.03", + "preferred": true, + "url": "https://docs.nvidia.com/cupynumeric/25.03/" + } ] diff --git a/tests/integration/test_fft_c2r.py b/tests/integration/test_fft_c2r.py index 3c16fd6dbd..2fa4aefc0d 100644 --- a/tests/integration/test_fft_c2r.py +++ b/tests/integration/test_fft_c2r.py @@ -251,6 +251,7 @@ def test_norm_invalid() -> None: with pytest.raises(ValueError, match=msg): num.fft.rfft(Z_num, norm="other") + if __name__ == "__main__": import sys diff --git a/tests/integration/test_index_routines.py b/tests/integration/test_index_routines.py index 56d22b6d96..13c1670edf 100644 --- a/tests/integration/test_index_routines.py +++ b/tests/integration/test_index_routines.py @@ -14,7 +14,6 @@ # from itertools import chain, combinations, permutations -from typing import Iterator import numpy as np import pytest diff --git a/tests/integration/test_local_task_array.py b/tests/integration/test_local_task_array.py index cea98d5956..3ff3c27caa 100644 --- a/tests/integration/test_local_task_array.py +++ b/tests/integration/test_local_task_array.py @@ -15,12 +15,12 @@ import numpy as np import pytest - from legate.core import StoreTarget, get_legate_runtime, types as ty import cupynumeric as num -runtime = get_legate_runtime() +runtime = get_legate_runtime() + def test_local_task_array_with_array() -> None: array = runtime.create_array(ty.int64, shape=(10,)).get_physical_array() @@ -30,6 +30,7 @@ def test_local_task_array_with_array() -> None: on_cpu = array.data().target not in {StoreTarget.FBMEM, StoreTarget.ZCMEM} assert isinstance(result, np.ndarray) == on_cpu + def test_local_task_array_with_store() -> None: store = runtime.create_store(ty.int64, shape=(20,)).get_physical_store() result = num.local_task_array(store) diff --git a/tests/integration/test_random.py b/tests/integration/test_random.py index 488cddc74c..46f9f370b3 100644 --- a/tests/integration/test_random.py +++ b/tests/integration/test_random.py @@ -207,7 +207,7 @@ def test_seed_float(self) -> None: # See https://github.com/nv-legate/cunumeric.internal/issues/199 # cuNumeric passed with float value - + def test_RandomState() -> None: rdm_num = num.random.RandomState(10) L1 = rdm_num.randn(3, 3) @@ -215,7 +215,7 @@ def test_RandomState() -> None: L2 = rdm_np.randn(3, 3) assert np.array_equal(L1, L2) - + if __name__ == "__main__": import sys diff --git a/tests/unit/cupynumeric/test___init__.py b/tests/unit/cupynumeric/test___init__.py new file mode 100644 index 0000000000..d0de454d59 --- /dev/null +++ b/tests/unit/cupynumeric/test___init__.py @@ -0,0 +1,49 @@ +# Copyright 2024 NVIDIA Corporation +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +from __future__ import annotations + +import re +from importlib import reload +from typing import TYPE_CHECKING + +if TYPE_CHECKING: + import pytest + +import cupynumeric # noqa: [F401] + + +def test___version___override(monkeypatch: pytest.MonkeyPatch) -> None: + global cupynumeric # noqa: PLW0603 + monkeypatch.setenv("CUPYNUMERIC_USE_VERSION", "24.01.00") + cupynumeric = reload(cupynumeric) + assert cupynumeric.__version__ == "24.01.00" + + +def test___version___format() -> None: + global cupynumeric # noqa: PLW0603 + cupynumeric = reload(cupynumeric) + + # just being cautious, if the test are functioning properly, the + # actual non-overriden version should never equal the bogus version + # from test___version___override above + assert cupynumeric.__version__ != "24.01.00" + + assert re.match(r"^\d{2}\.\d{2}\.\d{2}$", cupynumeric.__version__[:8]) + + +if __name__ == "__main__": + import sys + + sys.exit(pytest.main(sys.argv)) From 7525f53d8be94987166cba47bbb7ccfcf835ee33 Mon Sep 17 00:00:00 2001 From: "Marcus D. Hanwell" Date: Fri, 28 Feb 2025 15:30:11 -0500 Subject: [PATCH 429/462] Build binary pip wheels for cupynumeric (#610) Build a Python pip binary wheel for the project. One odd thing to note is the `cupynumeric/_version.py` file needs to be moved out of the way for pip wheel builds. I would like to move conda builds to scikit-build-core, but keep this hack temporarily for wheels builds. There is also logic to avoid building for the CPU on the system in `cupynumeric` and `openblas`. The wheel build fetches a wheel from the `legate` action, and that also needs fixing once we have pip wheels getting published. The tests are being run for CPU and CUDA on the different architectures we build for. --- .github/workflows/pr.yml | 35 ++++ .github/workflows/wheels-build.yml | 155 ++++++++++++++++++ .github/workflows/wheels-test.yml | 143 ++++++++++++++++ cmake/thirdparty/get_legate.cmake | 7 +- cmake/thirdparty/get_openblas.cmake | 9 +- continuous_integration/requirements-build.txt | 10 ++ .../scripts/build_wheel_linux.bash | 132 +++++++++++++++ .../scripts/test_wheel_linux.bash | 54 ++++++ cupynumeric/__init__.py | 6 +- cupynumeric/install_info.py.in | 17 +- .../build/python/cupynumeric/CMakeLists.txt | 27 +++ .../build/python/cupynumeric/pyproject.toml | 79 +++++++++ 12 files changed, 663 insertions(+), 11 deletions(-) create mode 100644 .github/workflows/pr.yml create mode 100644 .github/workflows/wheels-build.yml create mode 100644 .github/workflows/wheels-test.yml create mode 100644 continuous_integration/requirements-build.txt create mode 100755 continuous_integration/scripts/build_wheel_linux.bash create mode 100755 continuous_integration/scripts/test_wheel_linux.bash create mode 100644 scripts/build/python/cupynumeric/CMakeLists.txt create mode 100644 scripts/build/python/cupynumeric/pyproject.toml diff --git a/.github/workflows/pr.yml b/.github/workflows/pr.yml new file mode 100644 index 0000000000..bed67961d3 --- /dev/null +++ b/.github/workflows/pr.yml @@ -0,0 +1,35 @@ +name: pr + +on: + push: + branches: + - "pull-request/[0-9]+" + - "branch-*" + - "main" + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +defaults: + run: + shell: bash + +jobs: + wheels-build: + secrets: inherit + uses: ./.github/workflows/wheels-build.yml + with: + build-type: pull-request + # TODO(cryos): https://github.com/nv-legate/legate.internal/issues/1893 + # Remove this once we have uploads to PyPi. + legate-sha: c53cdb270394ce11576c4fd34149a2497f70d301 + wheels-test: + needs: wheels-build + secrets: inherit + uses: ./.github/workflows/wheels-test.yml + with: + build-type: pull-request + # TODO(cryos): https://github.com/nv-legate/legate.internal/issues/1893 + # Remove this once we have uploads to PyPi. + legate-sha: c53cdb270394ce11576c4fd34149a2497f70d301 diff --git a/.github/workflows/wheels-build.yml b/.github/workflows/wheels-build.yml new file mode 100644 index 0000000000..06abb34d73 --- /dev/null +++ b/.github/workflows/wheels-build.yml @@ -0,0 +1,155 @@ +on: + workflow_call: + inputs: + build-type: + required: true + type: string + legate-sha: + type: string + required: true + branch: + type: string + sha: + type: string + repo: + type: string + node_type: + type: string + default: "cpu16" + cuda_ver: + type: string + default: "12.5.1" + linux_ver: + type: string + default: "rockylinux8" + script: + type: string + default: "continuous_integration/scripts/build_wheel_linux.bash" + matrix_filter: + type: string + default: "." + +defaults: + run: + shell: bash -eou pipefail {0} + +permissions: + actions: read + checks: none + contents: read + deployments: none + discussions: none + id-token: write + issues: none + packages: read + pages: none + pull-requests: read + repository-projects: none + security-events: none + statuses: none + +jobs: + compute-matrix: + runs-on: linux-amd64-cpu4 + outputs: + MATRIX: ${{ steps.compute-matrix.outputs.MATRIX }} + steps: + - name: Compute Build Matrix + id: compute-matrix + run: | + set -eo pipefail + + # please keep the matrices sorted in ascending order by the following: + # + # [ARCH, PY_VER, CUDA_VER, LINUX_VER] + # + export MATRIX=" + # amd64 + - { ARCH: 'amd64', PY_VER: '3.10', TARGET_DEV: 'gpu', BUILD_MODE: 'release' } + - { ARCH: 'amd64', PY_VER: '3.11', TARGET_DEV: 'gpu', BUILD_MODE: 'release' } + - { ARCH: 'amd64', PY_VER: '3.12', TARGET_DEV: 'gpu', BUILD_MODE: 'release' } + # arm64 + - { ARCH: 'arm64', PY_VER: '3.10', TARGET_DEV: 'gpu', BUILD_MODE: 'release' } + - { ARCH: 'arm64', PY_VER: '3.11', TARGET_DEV: 'gpu', BUILD_MODE: 'release' } + - { ARCH: 'arm64', PY_VER: '3.12', TARGET_DEV: 'gpu', BUILD_MODE: 'release' } + " + + MATRIX="$( + yq -n -o json 'env(MATRIX)' | \ + jq -c '${{ inputs.matrix_filter }} | if (. | length) > 0 then {include: .} else "Error: Empty matrix\n" | halt_error(1) end' + )" + + echo "MATRIX=${MATRIX}" | tee --append "${GITHUB_OUTPUT}" + build: + name: ${{ matrix.ARCH }}, py${{ matrix.PY_VER }}, ${{ matrix.TARGET_DEV }}, ${{ matrix.BUILD_MODE }} + needs: compute-matrix + timeout-minutes: 480 + strategy: + fail-fast: false + matrix: ${{ fromJSON(needs.compute-matrix.outputs.MATRIX) }} + runs-on: "linux-${{ matrix.ARCH }}-${{ inputs.node_type }}" + container: + image: rapidsai/ci-wheel:cuda${{ inputs.cuda_ver }}-${{ inputs.linux_ver }}-py${{ matrix.PY_VER }} + env: + BUILD_MODE: ${{ matrix.BUILD_MODE }} + steps: + - name: Get the SHA + id: get-sha + run: | + sha=$(echo ${{github.sha}} | head -c 10) + echo "sha=$sha" >> $GITHUB_OUTPUT + - name: Dump GitHub context + env: + GITHUB_CONTEXT: ${{ toJson(github) }} + run: echo "$GITHUB_CONTEXT" + - name: Check it worked... + run: echo "${{steps.get-sha.outputs.sha}}" + - if: github.repository_owner == 'nv-legate' + name: Get AWS credentials for sccache bucket + uses: aws-actions/configure-aws-credentials@v4 + with: + aws-region: us-east-2 + role-duration-seconds: 28800 # 8 hours + role-to-assume: arn:aws:iam::279114543810:role/gha-oidc-nv-legate + - uses: actions/checkout@v4 + with: + repository: ${{ inputs.repo }} + ref: ${{ inputs.sha }} + fetch-depth: 0 + - name: Add default paths to the env + run: | + echo $(pwd)/continuous_integration/scripts/tools >> "${GITHUB_PATH}" + - name: Get the legate wheel + id: cache + env: + BUILD_NAME: ${{ matrix.ARCH }}-${{ matrix.TARGET_DEV }}-cuda${{ inputs.cuda_ver }}-py${{ matrix.PY_VER }} + uses: dawidd6/action-download-artifact@v8 + with: + github_token: ${{ secrets.NV_LEGATE_INTER_REPOS_ACCESS_RO }} + repo: nv-legate/legate.internal + workflow: pr.yml + commit: ${{ inputs.legate-sha }} + name: "legate-wheel-${{ env.BUILD_NAME }}" + check_artifacts: true + path: wheel + - name: Wheel build + run: ${{ inputs.script }} + env: + STEP_NAME: "C++ build" + GH_TOKEN: ${{ github.token }} + - name: Wheel upload + env: + BUILD_SHA: ${{ steps.get-sha.outputs.sha }} + BUILD_NAME: ${{ matrix.ARCH }}-${{ matrix.TARGET_DEV }}-cuda${{ inputs.cuda_ver }}-py${{ matrix.PY_VER }} + uses: actions/upload-artifact@v4 + with: + name: cupynumeric-wheel-${{ env.BUILD_NAME }}-g${{ env.BUILD_SHA }} + path: final-dist/*.whl + - name: Cache the wheel + env: + BUILD_SHA: ${{ steps.get-sha.outputs.sha }} + BUILD_NAME: ${{ matrix.ARCH }}-${{ matrix.TARGET_DEV }}-cuda${{ inputs.cuda_ver }}-py${{ matrix.PY_VER }} + uses: actions/cache@v4 + with: + path: final-dist/*.whl + key: wheel-${{ env.BUILD_NAME }}-g${{ env.BUILD_SHA }} diff --git a/.github/workflows/wheels-test.yml b/.github/workflows/wheels-test.yml new file mode 100644 index 0000000000..50c1799644 --- /dev/null +++ b/.github/workflows/wheels-test.yml @@ -0,0 +1,143 @@ +on: + workflow_call: + inputs: + build-type: + required: true + type: string + legate-sha: + type: string + required: true + branch: + type: string + sha: + type: string + repo: + type: string + node_type: + type: string + default: "cpu16" + cuda_ver: + type: string + default: "12.8.0" + script: + type: string + default: "continuous_integration/scripts/test_wheel_linux.bash" + matrix_filter: + type: string + default: "." + +defaults: + run: + shell: bash -eou pipefail {0} + +permissions: + actions: read + checks: none + contents: read + deployments: none + discussions: none + id-token: write + issues: none + packages: read + pages: none + pull-requests: read + repository-projects: none + security-events: none + statuses: none + +jobs: + compute-matrix: + runs-on: linux-amd64-cpu4 + outputs: + MATRIX: ${{ steps.compute-matrix.outputs.MATRIX }} + steps: + - name: Compute Build Matrix + id: compute-matrix + run: | + set -eo pipefail + + # please keep the matrices sorted in ascending order by the following: + # + # [ARCH, PY_VER, CUDA_VER, LINUX_VER] + # + export MATRIX=" + # amd64 + - { ARCH: 'amd64', PY_VER: '3.10', TARGET_DEV: 'gpu', GPU: 'l4', LINUX_VER: 'ubuntu22.04' } + - { ARCH: 'amd64', PY_VER: '3.11', TARGET_DEV: 'gpu', GPU: 'l4', LINUX_VER: 'ubuntu22.04' } + - { ARCH: 'amd64', PY_VER: '3.12', TARGET_DEV: 'gpu', GPU: 'l4', LINUX_VER: 'ubuntu24.04' } + # arm64 + - { ARCH: 'arm64', PY_VER: '3.10', TARGET_DEV: 'gpu', GPU: 'a100', LINUX_VER: 'ubuntu22.04' } + - { ARCH: 'arm64', PY_VER: '3.11', TARGET_DEV: 'gpu', GPU: 'a100', LINUX_VER: 'ubuntu22.04' } + - { ARCH: 'arm64', PY_VER: '3.12', TARGET_DEV: 'gpu', GPU: 'a100', LINUX_VER: 'ubuntu24.04' } + " + + MATRIX="$( + yq -n -o json 'env(MATRIX)' | \ + jq -c '${{ inputs.matrix_filter }} | if (. | length) > 0 then {include: .} else "Error: Empty matrix\n" | halt_error(1) end' + )" + + echo "MATRIX=${MATRIX}" | tee --append "${GITHUB_OUTPUT}" + + build: + name: ${{ matrix.ARCH }}, py${{ matrix.PY_VER }}, ${{ matrix.LINUX_VER }}, ${{ matrix.GPU }} + needs: compute-matrix + timeout-minutes: 60 + strategy: + fail-fast: false + matrix: ${{ fromJSON(needs.compute-matrix.outputs.MATRIX) }} + runs-on: ${{ matrix.ARCH == 'arm64' && 'linux-aarch64-2gpu' || format('linux-{0}-gpu-{1}-latest-1', matrix.ARCH, matrix.GPU) }} + container: + image: rapidsai/citestwheel:cuda${{ inputs.cuda_ver }}-${{ matrix.LINUX_VER }}-py${{ matrix.PY_VER }} + env: + NVIDIA_VISIBLE_DEVICES: ${{ env.NVIDIA_VISIBLE_DEVICES }} + steps: + - name: Get the SHA + id: get-sha + run: | + sha=$(echo ${{github.sha}} | head -c 10) + echo "sha=$sha" >> $GITHUB_OUTPUT + - name: Dump GitHub context + env: + GITHUB_CONTEXT: ${{ toJson(github) }} + run: echo "$GITHUB_CONTEXT" + - name: Check it worked... + run: echo "${{steps.get-sha.outputs.sha}}" + - uses: actions/checkout@v4 + with: + repository: ${{ inputs.repo }} + ref: ${{ inputs.sha }} + fetch-depth: 0 + - name: Add default paths to the env + run: | + echo $(pwd)/continuous_integration/scripts/tools >> "${GITHUB_PATH}" + - name: Run nvidia-smi to make sure GPU is working + run: nvidia-smi + - name: Setup proxy cache + uses: nv-gha-runners/setup-proxy-cache@main + continue-on-error: true + # Skip the cache on RDS Lab nodes + if: ${{ matrix.GPU != 'v100' && matrix.GPU != 'a100' }} + - name: Get the legate wheel + id: cache + env: + BUILD_NAME: ${{ matrix.ARCH }}-${{ matrix.TARGET_DEV }}-cuda12.5.1-py${{ matrix.PY_VER }} + uses: dawidd6/action-download-artifact@v8 + with: + github_token: ${{ secrets.NV_LEGATE_INTER_REPOS_ACCESS_RO }} + repo: nv-legate/legate.internal + workflow: pr.yml + commit: ${{ inputs.legate-sha }} + name: "legate-wheel-${{ env.BUILD_NAME }}" + check_artifacts: true + path: wheel + - name: Restore the cached wheel + id: cached-wheel + env: + BUILD_SHA: ${{ steps.get-sha.outputs.sha }} + BUILD_NAME: ${{ matrix.ARCH }}-${{ matrix.TARGET_DEV }}-cuda12.5.1-py${{ matrix.PY_VER }} + uses: actions/cache@v4 + with: + path: final-dist/*.whl + key: wheel-${{ env.BUILD_NAME }}-g${{ env.BUILD_SHA }} + - name: Run tests + run: ${{ inputs.script }} diff --git a/cmake/thirdparty/get_legate.cmake b/cmake/thirdparty/get_legate.cmake index c2c003c3e8..7951bd2919 100644 --- a/cmake/thirdparty/get_legate.cmake +++ b/cmake/thirdparty/get_legate.cmake @@ -86,9 +86,10 @@ function(find_or_configure_legate) # so the `Legion_USE_*` variables are visible # Use QUIET find by default. set(_find_mode QUIET) - # If legate_DIR/legate_ROOT are defined as something other than empty or NOTFOUND - # use a REQUIRED find so that the build does not silently download legate. - if(legate_DIR OR legate_ROOT) + # If legate_DIR/legate_ROOT or CUPYNUMERIC_BUILD_PIP_WHEELS are defined as + # something other than empty or NOTFOUND use a REQUIRED find so that the + # build does not silently download legate. + if(legate_DIR OR legate_ROOT OR CUPYNUMERIC_BUILD_PIP_WHEELS) set(_find_mode REQUIRED) endif() rapids_find_package(legate ${version} EXACT CONFIG ${_find_mode} ${FIND_PKG_ARGS}) diff --git a/cmake/thirdparty/get_openblas.cmake b/cmake/thirdparty/get_openblas.cmake index aa7030ca56..6c0212eaf0 100644 --- a/cmake/thirdparty/get_openblas.cmake +++ b/cmake/thirdparty/get_openblas.cmake @@ -50,6 +50,12 @@ function(find_or_configure_OpenBLAS) set(CMAKE_POLICY_DEFAULT_CMP0048 OLD) set(CMAKE_POLICY_DEFAULT_CMP0054 NEW) + # Force a base CPU type for the openblas build. + set(_target HASWELL) + if(CMAKE_SYSTEM_PROCESSOR STREQUAL "aarch64" OR CMAKE_SYSTEM_PROCESSOR STREQUAL "arm64") + set(_target ARMV8) + endif() + rapids_cpm_find(BLAS ${FIND_PKG_ARGS} CPM_ARGS ${BLAS_cpm_git_args} @@ -62,6 +68,7 @@ function(find_or_configure_OpenBLAS) "BUILD_WITHOUT_CBLAS OFF" "BUILD_WITHOUT_LAPACK OFF" "INTERFACE64 ${INTERFACE64}" + "TARGET ${_target}" "USE_OPENMP ${Legion_USE_OpenMP}") set(CMAKE_POLICY_DEFAULT_CMP0048 ${CMP0048_orig}) @@ -116,7 +123,7 @@ endfunction() if(NOT DEFINED cupynumeric_OPENBLAS_VERSION) # Before v0.3.18, OpenBLAS's throws CMake errors when configuring - set(cupynumeric_OPENBLAS_VERSION "0.3.20") + set(cupynumeric_OPENBLAS_VERSION "0.3.29") endif() if(NOT DEFINED cupynumeric_OPENBLAS_BRANCH) diff --git a/continuous_integration/requirements-build.txt b/continuous_integration/requirements-build.txt new file mode 100644 index 0000000000..6ce8269a49 --- /dev/null +++ b/continuous_integration/requirements-build.txt @@ -0,0 +1,10 @@ +--extra-index-url=https://pypi.anaconda.org/rapidsai-wheels-nightly/simple +--extra-index-url=https://pypi.nvidia.com +cmake>=3.26.4,!=3.30.0 +ninja +nvidia-nccl-cu12 +cutensor-cu12 +scikit-build +scikit-build-core[pyproject]>=0.10.0 +setuptools_scm +cython diff --git a/continuous_integration/scripts/build_wheel_linux.bash b/continuous_integration/scripts/build_wheel_linux.bash new file mode 100755 index 0000000000..24a7ce5b0d --- /dev/null +++ b/continuous_integration/scripts/build_wheel_linux.bash @@ -0,0 +1,132 @@ +#!/usr/bin/env bash + +# SPDX-FileCopyrightText: Copyright (c) 2025-2025 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# SPDX-License-Identifier: LicenseRef-NvidiaProprietary +# +# NVIDIA CORPORATION, its affiliates and licensors retain all intellectual +# property and proprietary rights in and to this material, related +# documentation and any modifications thereto. Any use, reproduction, +# disclosure or distribution of this material and related documentation +# without an express license agreement from NVIDIA CORPORATION or +# its affiliates is strictly prohibited. + +set -euo pipefail + +echo "Building a wheel..." + +pwd + +ls -lah + +ls -lh wheel + +export PARALLEL_LEVEL=${PARALLEL_LEVEL:-$(nproc --all --ignore=2)} +export CMAKE_BUILD_PARALLEL_LEVEL=${PARALLEL_LEVEL} + +if [[ "${CI:-false}" == "true" ]]; then + echo "Installing extra system packages" + dnf install -y gcc-toolset-11-libatomic-devel + # Enable gcc-toolset-11 environment + source /opt/rh/gcc-toolset-11/enable + # Verify compiler version + gcc --version + g++ --version +fi + +echo "PATH: ${PATH}" + +if [[ "${CUPYNUMERIC_DIR:-}" == "" ]]; then + # If we are running in an action then GITHUB_WORKSPACE is set. + if [[ "${GITHUB_WORKSPACE:-}" == "" ]]; then + script_dir="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)" + CUPYNUMERIC_DIR="$(realpath "${script_dir}"/../../)" + else + # Simple path witin GitHub actions workflows. + CUPYNUMERIC_DIR="${GITHUB_WORKSPACE}" + fi + export CUPYNUMERIC_DIR +fi +package_name="cupynumeric" +package_dir="${CUPYNUMERIC_DIR}/scripts/build/python/cupynumeric" + +# This is all very hackish and needs to be fixed up. +echo "Installing build requirements" +python -m pip install -v --prefer-binary -r continuous_integration/requirements-build.txt + +# Install the legate wheel that was downloaded. +pip install wheel/*.whl + +sitepkgs=$(python -c 'import site; print(site.getsitepackages()[0], end="")') +# Add in the symbolic links for cuTensor so that CMake can find it (hack) +ln -fs "${sitepkgs}"/cutensor/lib/libcutensor.so.2 "${sitepkgs}"/cutensor/lib/libcutensor.so +ln -fs "${sitepkgs}"/cutensor/lib/libcutensorMg.so.2 "${sitepkgs}"/cutensor/lib/libcutensorMg.so + +# build with '--no-build-isolation', for better sccache hit rate +# 0 really means "add --no-build-isolation" (ref: https://github.com/pypa/pip/issues/5735) +export PIP_NO_BUILD_ISOLATION=0 + +# The cupynumeric build system defaults to -march=native, which is not going to work +# for packages we want to reuse! Set some reasonable defaults for the wheels. +ARCH=$(uname -m) +echo "Building on architecture: ${ARCH}" +if [[ "$ARCH" == "aarch64" ]]; then + BUILD_MARCH=armv8-a +else + BUILD_MARCH=haswell +fi + +echo "Building ${package_name}" +# TODO(cryos): https://github.com/nv-legate/legate.internal/issues/1894 +# Improve the use of CMAKE_PREFIX_PATH to find legate and cutensor once +# scikit-build supports it. +CMAKE_ARGS="-DCMAKE_PREFIX_PATH=${sitepkgs}/legate;${sitepkgs}/cutensor" +export CMAKE_ARGS +SKBUILD_CMAKE_ARGS="-DCMAKE_CUDA_ARCHITECTURES:STRING=all-major;-DBUILD_SHARED_LIBS:BOOL=ON;-DBUILD_MARCH=${BUILD_MARCH}" +export SKBUILD_CMAKE_ARGS +echo "SKBUILD_CMAKE_ARGS='${SKBUILD_CMAKE_ARGS}'" + +# TODO: Remove this hackish removal of scikit-build files needed as conda +# uses scikit-build and wheels are using scikit-build-core. Migrate conda to +# be consistent with legate and wheels. If not deleted we get inconsistent +# metadata failure during the pip wheel build. +mv "${CUPYNUMERIC_DIR}"/cupynumeric/_version.py "${CUPYNUMERIC_DIR}"/cupynumeric/_version.py.bak +echo "Removed scikit-build _version.py file" +ls -lah + +echo "Building wheel..." +cd "${package_dir}" + +python -m pip wheel \ + -w "${CUPYNUMERIC_DIR}"/dist \ + -v \ + --no-deps \ + --disable-pip-version-check \ + . + +echo "Show dist contents" +pwd +ls -lh "${CUPYNUMERIC_DIR}"/dist + +echo "Repairing the wheel" +mkdir -p "${CUPYNUMERIC_DIR}"/final-dist +python -m auditwheel repair \ + --exclude libnvJitLink.so* \ + --exclude libcuda.so* \ + --exclude liblegate.so* \ + --exclude libcublas.so* \ + --exclude libcublasLt.so* \ + --exclude libnccl.so* \ + --exclude libcusparse.so* \ + --exclude libcutensor.so* \ + --exclude libcufft.so* \ + --exclude libcusolver.so* \ + --exclude liblegion-legate.so* \ + --exclude librealm-legate.so* \ + -w "${CUPYNUMERIC_DIR}"/final-dist \ + "${CUPYNUMERIC_DIR}"/dist/*.whl + +echo "Wheel has been repaired. Contents:" +ls -lh "${CUPYNUMERIC_DIR}"/final-dist + +echo "Restoring scikit-build _verion.py file" +mv "${CUPYNUMERIC_DIR}"/cupynumeric/_version.py.bak "${CUPYNUMERIC_DIR}"/cupynumeric/_version.py diff --git a/continuous_integration/scripts/test_wheel_linux.bash b/continuous_integration/scripts/test_wheel_linux.bash new file mode 100755 index 0000000000..0a1ddda58f --- /dev/null +++ b/continuous_integration/scripts/test_wheel_linux.bash @@ -0,0 +1,54 @@ +#!/usr/bin/env bash + +# SPDX-FileCopyrightText: Copyright (c) 2025-2025 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# SPDX-License-Identifier: LicenseRef-NvidiaProprietary +# +# NVIDIA CORPORATION, its affiliates and licensors retain all intellectual +# property and proprietary rights in and to this material, related +# documentation and any modifications thereto. Any use, reproduction, +# disclosure or distribution of this material and related documentation +# without an express license agreement from NVIDIA CORPORATION or +# its affiliates is strictly prohibited. + +set -euo pipefail + +echo "Are my wheels there???" + +ls -lh + +ls -lh wheel +ls -lh final-dist + +# Install legate first and then cupynumeric. +pip install wheel/*.whl final-dist/*.whl + +echo "Let's explore the wheels and see if they are installed correctly." +sitepkgs=$(python -c 'import site; print(site.getsitepackages()[0], end="")') +echo "=== cupynumeric ===" +ls -lh "${sitepkgs}/cupynumeric" +echo "=== legate ===" +ls -lh "${sitepkgs}/legate" + +echo "Lamest of proof of life tests for legate" +export LEGATE_SHOW_CONFIG=1 +export LEGATE_CONFIG="--fbmem 1024" +export LEGION_DEFAULT_ARGS="-ll:show_rsrv" + +# Attempt to run the tests... +mv cupynumeric cupynumeric-moved +pip install pytest pynvml psutil scipy + +echo "Attempt to run an example" +legate examples/gemm.py + +echo "Example done, attempt to import cupynumeric" +python -c 'import cupynumeric as np' +echo "Maybe that worked" + +echo "Running the CPU tests" +python test.py +echo "Done" + +echo "Running the GPU tests" +python test.py --use cuda +echo "Done" diff --git a/cupynumeric/__init__.py b/cupynumeric/__init__.py index 51d4b415b9..54bfcf3dcd 100644 --- a/cupynumeric/__init__.py +++ b/cupynumeric/__init__.py @@ -48,8 +48,12 @@ def _fixup_version() -> str: return v from . import _version + if hasattr(_version, "get_versions"): + return _version.get_versions()["version"] # type: ignore [no-untyped-call] + if hasattr(_version, "__version__"): + return _version.__version__ - return _version.get_versions()["version"] # type: ignore [no-untyped-call] + raise RuntimeError("Failed to determine version") __version__ = _fixup_version() diff --git a/cupynumeric/install_info.py.in b/cupynumeric/install_info.py.in index c582492df4..9175f52a37 100644 --- a/cupynumeric/install_info.py.in +++ b/cupynumeric/install_info.py.in @@ -35,12 +35,17 @@ def get_libpath(): return libdir return None - return ( - find_libcupynumeric(join(cn_path, "build", "lib")) or - find_libcupynumeric(join(dirname(dirname(dirname(cn_path))), "lib")) or - find_libcupynumeric(join(dirname(dirname(sys.executable)), "lib")) or - "" - ) + for libdir in ("lib", "lib64"): + if ret := find_libcupynumeric(join(cn_path, "build", libdir)): + return ret + if ret := find_libcupynumeric(join(cn_path, "cupynumeric", libdir)): + return ret + if ret := find_libcupynumeric(join(dirname(dirname(dirname(cn_path))), libdir)): + return ret + if ret := find_libcupynumeric(join(dirname(dirname(sys.executable)), libdir)): + return ret + + return "" libpath: str = get_libpath() diff --git a/scripts/build/python/cupynumeric/CMakeLists.txt b/scripts/build/python/cupynumeric/CMakeLists.txt new file mode 100644 index 0000000000..61f5d8a4c5 --- /dev/null +++ b/scripts/build/python/cupynumeric/CMakeLists.txt @@ -0,0 +1,27 @@ +#============================================================================= +# SPDX-FileCopyrightText: Copyright (c) 2022-2025 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# SPDX-License-Identifier: LicenseRef-NvidiaProprietary +# +# NVIDIA CORPORATION, its affiliates and licensors retain all intellectual +# property and proprietary rights in and to this material, related +# documentation and any modifications thereto. Any use, reproduction, +# disclosure or distribution of this material and related documentation +# without an express license agreement from NVIDIA CORPORATION or +# its affiliates is strictly prohibited. +#============================================================================= + +cmake_minimum_required(VERSION 3.26.4) + +project(cupynumeric-python VERSION 25.03.00 LANGUAGES CXX) + +set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${PROJECT_BINARY_DIR}/lib) +set(CUPYNUMERIC_BUILD_PIP_WHEELS ON) + +add_subdirectory(../../../.. cupynumeric-all) + +set(rpaths "$ORIGIN/../../cutensor/lib" "$ORIGIN/../../legate/lib64") +set_property( + TARGET cupynumeric + PROPERTY INSTALL_RPATH ${rpaths} + APPEND +) diff --git a/scripts/build/python/cupynumeric/pyproject.toml b/scripts/build/python/cupynumeric/pyproject.toml new file mode 100644 index 0000000000..62702f7088 --- /dev/null +++ b/scripts/build/python/cupynumeric/pyproject.toml @@ -0,0 +1,79 @@ +# SPDX-FileCopyrightText: Copyright (c) 2021-2025 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# SPDX-License-Identifier: LicenseRef-NvidiaProprietary +# +# NVIDIA CORPORATION, its affiliates and licensors retain all intellectual +# property and proprietary rights in and to this material, related +# documentation and any modifications thereto. Any use, reproduction, +# disclosure or distribution of this material and related documentation +# without an express license agreement from NVIDIA CORPORATION or +# its affiliates is strictly prohibited. + +[build-system] +requires = [ + "scikit-build-core", + "cython>=3.0.1", + "rich", +] +build-backend = "scikit_build_core.build" +python-requires = ">=3.10" + +[project] +name = "cupynumeric" +authors = [{name = "NVIDIA Corporation"}] +license = {text = "Proprietary"} +description = "cupynumeric - drop in replacement for numpy" +classifiers = [ + "Intended Audience :: Developers", + "Topic :: Database", + "Topic :: Scientific/Engineering", + "License :: Proprietary :: Nvidia Proprietary", + "Programming Language :: Python", + "Programming Language :: Python :: 3.10", + "Programming Language :: Python :: 3.11", + "Programming Language :: Python :: 3.12" +] +dependencies = [ + "numpy!=2.1.0", + "cffi", + "opt_einsum", + "legate", + "cutensor-cu12", +] +dynamic = ["version"] + +[project.urls] +homepage = "https://github.com/nv-legate/cupynumeric" + +[project.entry-points."cmake.prefix"] +cupynumeric = "cupynumeric" + +[tool.scikit-build.cmake] +version = ">=3.26.4" + +[tool.scikit-build.metadata.version] +provider = "scikit_build_core.metadata.setuptools_scm" + +[tool.scikit-build.sdist] +include = [ + "../../../../cupynumeric/_version.py", +] + +[tool.setuptools_scm] +write_to = "cupynumeric/_version.py" +root = "../../../../" + +[tool.scikit-build.build] +verbose = true + +[tool.scikit-build.logging] +level = "DEBUG" + +[tool.scikit-build.wheel] +exclude = ["**.pyx", "**CMakeLists.txt", "**.pxd"] +install-dir = "cupynumeric" + +[tool.scikit-build] +build-dir = "buildwheel" + +[tool.scikit-build.wheel.packages] +"cupynumeric" = "../../../../cupynumeric" From 625f4c77394977c4832e1a4d639cb0d949639fe6 Mon Sep 17 00:00:00 2001 From: Andrei Schaffer <37386037+aschaffer@users.noreply.github.com> Date: Mon, 3 Mar 2025 11:22:18 -0600 Subject: [PATCH 430/462] Added seeding to fix intermittent CI failures. (#633) --- tests/integration/test_expm_sh.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tests/integration/test_expm_sh.py b/tests/integration/test_expm_sh.py index 4042410ca7..d369e5e573 100644 --- a/tests/integration/test_expm_sh.py +++ b/tests/integration/test_expm_sh.py @@ -41,6 +41,8 @@ def make_skew_hermitian(n: int, max_v: float = 100.0) -> np.ndarray: num_off_d = int(n*(n-1)/2) + np.random.seed(1729) + r_array = np.array([ np.random.uniform(min_v, max_v) for k in range(num_off_d)], dtype=np.dtype('float64')) From 280010bb5f549fe96acc364f42b96296ccb03628 Mon Sep 17 00:00:00 2001 From: "Marcus D. Hanwell" Date: Tue, 4 Mar 2025 15:12:55 -0500 Subject: [PATCH 431/462] Rename the wheel: cupynumeric -> nvidia-cupynumeric (#636) This is needed in the short-term. Also improve the legate dependency. --- scripts/build/python/cupynumeric/pyproject.toml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/scripts/build/python/cupynumeric/pyproject.toml b/scripts/build/python/cupynumeric/pyproject.toml index 62702f7088..fcff8ea0c4 100644 --- a/scripts/build/python/cupynumeric/pyproject.toml +++ b/scripts/build/python/cupynumeric/pyproject.toml @@ -18,7 +18,7 @@ build-backend = "scikit_build_core.build" python-requires = ">=3.10" [project] -name = "cupynumeric" +name = "nvidia-cupynumeric" authors = [{name = "NVIDIA Corporation"}] license = {text = "Proprietary"} description = "cupynumeric - drop in replacement for numpy" @@ -36,7 +36,7 @@ dependencies = [ "numpy!=2.1.0", "cffi", "opt_einsum", - "legate", + "legate==25.3.*,>=0.0.0a0", "cutensor-cu12", ] dynamic = ["version"] From ee49d5c215cf6ca1016d292355dcd7485b4f126a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Malte=20F=C3=B6rster?= <97973773+mfoerste4@users.noreply.github.com> Date: Wed, 5 Mar 2025 19:48:48 +0100 Subject: [PATCH 432/462] Add 'linalg.eigvals' & 'linalg.eig' API (#628) * initial commit * add tests and fixes * add >3D input * Apply suggestions from code review Co-authored-by: Manolis Papadakis * rework task launch * add allocation pool size for geev * switch geev to unbounded allocation * replace custom kernel by thrust transpose * draft dlopen/dlsym of cusolver extra symbols * add API for eigvals, generalize geev task * remove unsupported diagflat in test * review suggestions --------- Co-authored-by: Manolis Papadakis --- cupynumeric/_thunk/deferred.py | 17 ++ cupynumeric/_thunk/eager.py | 31 +++ cupynumeric/_thunk/thunk.py | 8 + cupynumeric/config.py | 6 + cupynumeric/linalg/_eigen.py | 87 ++++++ cupynumeric/linalg/linalg.py | 159 ++++++++++- cupynumeric/runtime.py | 7 + cupynumeric_cpp.cmake | 3 + docs/cupynumeric/source/api/linalg.rst | 2 + src/cupynumeric/cuda_help.h | 64 +++++ src/cupynumeric/cudalibs.cu | 55 ++++ src/cupynumeric/cudalibs.h | 1 + src/cupynumeric/cupynumeric.cc | 4 + src/cupynumeric/cupynumeric_c.h | 3 + src/cupynumeric/mapper.cc | 37 +++ src/cupynumeric/matrix/geev.cc | 40 +++ src/cupynumeric/matrix/geev.cu | 261 +++++++++++++++++ src/cupynumeric/matrix/geev.h | 42 +++ src/cupynumeric/matrix/geev_cpu.inl | 341 +++++++++++++++++++++++ src/cupynumeric/matrix/geev_omp.cc | 31 +++ src/cupynumeric/matrix/geev_template.inl | 194 +++++++++++++ tests/integration/test_eig.py | 161 +++++++++++ tests/unit/cupynumeric/test_config.py | 1 + 23 files changed, 1553 insertions(+), 2 deletions(-) create mode 100644 cupynumeric/linalg/_eigen.py create mode 100644 src/cupynumeric/matrix/geev.cc create mode 100644 src/cupynumeric/matrix/geev.cu create mode 100644 src/cupynumeric/matrix/geev.h create mode 100644 src/cupynumeric/matrix/geev_cpu.inl create mode 100644 src/cupynumeric/matrix/geev_omp.cc create mode 100644 src/cupynumeric/matrix/geev_template.inl create mode 100644 tests/integration/test_eig.py diff --git a/cupynumeric/_thunk/deferred.py b/cupynumeric/_thunk/deferred.py index b64f7fb669..a2b6060665 100644 --- a/cupynumeric/_thunk/deferred.py +++ b/cupynumeric/_thunk/deferred.py @@ -69,6 +69,7 @@ UnaryRedCode, ) from ..linalg._cholesky import cholesky_deferred +from ..linalg._eigen import eig_deferred from ..linalg._qr import qr_deferred from ..linalg._solve import solve_deferred from ..linalg._svd import svd_deferred @@ -3465,6 +3466,22 @@ def compute_strides(shape: NdShape) -> tuple[int, ...]: def cholesky(self, src: Any) -> None: cholesky_deferred(self, src) + @auto_convert("ew", "ev") + def eig(self, ew: Any, ev: Any) -> None: + if runtime.num_gpus > 0 and not runtime.cusolver_has_geev(): + lhs = runtime.to_eager_array(self) + lhs.eig(runtime.to_eager_array(ew), runtime.to_eager_array(ev)) + else: + eig_deferred(self, ew, ev) + + @auto_convert("ew") + def eigvals(self, ew: Any) -> None: + if runtime.num_gpus > 0 and not runtime.cusolver_has_geev(): + lhs = runtime.to_eager_array(self) + lhs.eigvals(runtime.to_eager_array(ew)) + else: + eig_deferred(self, ew) + @auto_convert("q", "r") def qr(self, q: Any, r: Any) -> None: qr_deferred(self, q, r) diff --git a/cupynumeric/_thunk/eager.py b/cupynumeric/_thunk/eager.py index 74a02214f6..ae2551f99f 100644 --- a/cupynumeric/_thunk/eager.py +++ b/cupynumeric/_thunk/eager.py @@ -1689,6 +1689,37 @@ def cholesky(self, src: Any) -> None: self.array[:] = result + def eig(self, ew: Any, ev: Any) -> None: + self.check_eager_args(ew, ev) + if self.deferred is not None and ( + runtime.num_gpus == 0 or runtime.cusolver_has_geev() + ): + self.deferred.eig(ew, ev) + else: + try: + result_ew, result_ev = np.linalg.eig(self.array) + except np.linalg.LinAlgError as e: + from ..linalg import LinAlgError + + raise LinAlgError(e) from e + ew.array[:] = result_ew + ev.array[:] = result_ev + + def eigvals(self, ew: Any) -> None: + self.check_eager_args(ew) + if self.deferred is not None and ( + runtime.num_gpus == 0 or runtime.cusolver_has_geev() + ): + self.deferred.eigvals(ew) + else: + try: + result_ew = np.linalg.eigvals(self.array) + except np.linalg.LinAlgError as e: + from ..linalg import LinAlgError + + raise LinAlgError(e) from e + ew.array[:] = result_ew + def qr(self, q: Any, r: Any) -> None: self.check_eager_args(q, r) if self.deferred is not None: diff --git a/cupynumeric/_thunk/thunk.py b/cupynumeric/_thunk/thunk.py index 37f1578819..132168dfab 100644 --- a/cupynumeric/_thunk/thunk.py +++ b/cupynumeric/_thunk/thunk.py @@ -707,6 +707,14 @@ def where(self, rhs1: Any, rhs2: Any, rhs3: Any) -> None: def cholesky(self, src: Any) -> None: ... + @abstractmethod + def eig(self, ew: Any, ev: Any) -> None: + ... + + @abstractmethod + def eigvals(self, ew: Any) -> None: + ... + @abstractmethod def qr(self, q: Any, r: Any) -> None: ... diff --git a/cupynumeric/config.py b/cupynumeric/config.py index 2e07da314e..b931dead9f 100644 --- a/cupynumeric/config.py +++ b/cupynumeric/config.py @@ -166,6 +166,7 @@ class _CupynumericSharedLib: CUPYNUMERIC_FFT_Z2Z: int CUPYNUMERIC_FILL: int CUPYNUMERIC_FLIP: int + CUPYNUMERIC_GEEV: int CUPYNUMERIC_GEMM: int CUPYNUMERIC_HISTOGRAM: int CUPYNUMERIC_LOAD_CUDALIBS: int @@ -287,6 +288,10 @@ class _CupynumericSharedLib: def cupynumeric_has_cusolvermp(self) -> bool: ... + @abstractmethod + def cupynumeric_cusolver_has_geev(self) -> bool: + ... + @abstractmethod def cupynumeric_max_eager_volume(self) -> int: ... @@ -384,6 +389,7 @@ class CuPyNumericOpCode(IntEnum): FFT = _cupynumeric.CUPYNUMERIC_FFT FILL = _cupynumeric.CUPYNUMERIC_FILL FLIP = _cupynumeric.CUPYNUMERIC_FLIP + GEEV = _cupynumeric.CUPYNUMERIC_GEEV GEMM = _cupynumeric.CUPYNUMERIC_GEMM HISTOGRAM = _cupynumeric.CUPYNUMERIC_HISTOGRAM LOAD_CUDALIBS = _cupynumeric.CUPYNUMERIC_LOAD_CUDALIBS diff --git a/cupynumeric/linalg/_eigen.py b/cupynumeric/linalg/_eigen.py new file mode 100644 index 0000000000..cb6b0dd057 --- /dev/null +++ b/cupynumeric/linalg/_eigen.py @@ -0,0 +1,87 @@ +# Copyright 2024 NVIDIA Corporation +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +from __future__ import annotations + +from typing import TYPE_CHECKING, Optional + +from legate.core import dimension, get_legate_runtime + +from cupynumeric.config import CuPyNumericOpCode + +from ..runtime import runtime +from ._exception import LinAlgError + +if TYPE_CHECKING: + from .._thunk.deferred import DeferredArray + + +def eig_deferred( + a: DeferredArray, ew: DeferredArray, ev: Optional[DeferredArray] = None +) -> None: + library = a.library + + m = a.shape[-1] + + if m == 0: + raise ValueError("Input shape dimension 0 not allowed!") + + def choose_nd_color_shape(shape: tuple[int, ...]) -> tuple[int, ...]: + # start with 1D and re-balance by powers of 2 + # (don't worry about other primes) + color_shape = [1 for i in shape] + if len(shape) > 2: + color_shape[0] = runtime.num_procs + + done = False + while not done and color_shape[0] % 2 == 0: + # find max idx + # if large enough --> switch + weight_per_dim = list( + map(lambda x, y: x / y, list(shape), color_shape) + )[:-2] + + max_weight = max(weight_per_dim) + idx = weight_per_dim.index(max_weight) + + if weight_per_dim[idx] > 2 * weight_per_dim[0]: + color_shape[0] = color_shape[0] // 2 + color_shape[idx] = color_shape[idx] * 2 + else: + done = True + + return tuple(color_shape) + + # coloring via num_procs to get utilization + initial_color_shape = choose_nd_color_shape(a.shape) + tilesize = tuple( + map(lambda x, y: (x + y - 1) // y, a.shape, initial_color_shape) + ) + color_shape = tuple(map(lambda x, y: (x + y - 1) // y, a.shape, tilesize)) + + # partition defined py local batchsize + tiled_a = a.base.partition_by_tiling(tilesize) + tiled_ew = ew.base.partition_by_tiling(tilesize[:-1]) + + task = get_legate_runtime().create_manual_task( + library, CuPyNumericOpCode.GEEV, color_shape + ) + task.throws_exception(LinAlgError) + partition = tuple(dimension(i) for i in range(len(color_shape))) + task.add_input(tiled_a, partition) + task.add_output(tiled_ew, partition[:-1]) + if ev is not None: + tiled_ev = ev.base.partition_by_tiling(tilesize) + task.add_output(tiled_ev, partition) + task.execute() diff --git a/cupynumeric/linalg/linalg.py b/cupynumeric/linalg/linalg.py index 1e025b03fb..d1cfd73e1e 100644 --- a/cupynumeric/linalg/linalg.py +++ b/cupynumeric/linalg/linalg.py @@ -91,6 +91,108 @@ def cholesky(a: ndarray) -> ndarray: return _thunk_cholesky(a) +@add_boilerplate("a") +def eig(a: ndarray) -> tuple[ndarray, ...]: + """ + Compute the eigenvalues and right eigenvectors of a square array. + + Parameters + ---------- + a : (..., M, M) array_like + Matrices for which the eigenvalues and right eigenvectors will be + computed, at least dimension 2. + + Returns + ------- + eigenvalues : (…, M) array_like + The eigenvalues, each repeated according to its multiplicity. + eigenvectors : (…, M, M) array + The normalized (unit “length”) eigenvectors, such that the column + eigenvectors[:,i] is the eigenvector corresponding to the eigenvalue + eigenvalues[i]. + + Raises + ------ + LinAlgError + If the eigenvalue computation does not converge. + + Notes + ----- + Unlike NumPy, cuPyNumeric always returns complex-dtype results, even if the + imaginary part is zero. + + Multi-GPU/CPU usage is limited to data parallel matrix-wise batching. + + See Also + -------- + numpy.linalg.eig + + Availability + -------- + Multiple GPU, Multiple CPU + """ + shape = a.shape + if len(shape) < 2: + raise LinAlgError( + f"{len(shape)}-dimensional array given. " + "Array must be at least two-dimensional" + ) + if shape[-2] != shape[-1]: + raise LinAlgError("Last 2 dimensions of the array must be square") + if np.dtype("e") == a.dtype: + raise TypeError("array type float16 is unsupported in linalg") + return _thunk_eig(a) + + +@add_boilerplate("a") +def eigvals(a: ndarray) -> ndarray: + """ + Compute the eigenvalues of a square array. + + Parameters + ---------- + a : (..., M, M) array_like + Matrices for which the eigenvalues will be computed, at least + dimension 2. + + Returns + ------- + w : (…, M) array_like + The eigenvalues, each repeated according to its multiplicity. + + Raises + ------ + LinAlgError + If the eigenvalue computation does not converge. + + Notes + ----- + Unlike NumPy, cuPyNumeric always returns complex-dtype results, even if the + imaginary part is zero. + + Multi-GPU/CPU usage is limited to data parallel matrix-wise batching. + + See Also + -------- + numpy.linalg.eigvals + + Availability + -------- + Multiple GPU, Multiple CPU + """ + shape = a.shape + if len(shape) < 2: + raise LinAlgError( + f"{len(shape)}-dimensional array given. " + "Array must be at least two-dimensional" + ) + if shape[-2] != shape[-1]: + raise LinAlgError("Last 2 dimensions of the array must be square") + if np.dtype("e") == a.dtype: + raise TypeError("array type float16 is unsupported in linalg") + return _thunk_eigvals(a) + + @add_boilerplate("a") def qr(a: ndarray) -> tuple[ndarray, ...]: """ @@ -248,8 +350,7 @@ def svd(a: ndarray, full_matrices: bool = True) -> tuple[ndarray, ...]: Notes ----- - Currently does not support the parameters 'full_matrices', 'compute_uv', - and 'hermitian'. + Currently does not support the parameters 'compute_uv' and 'hermitian'. See Also -------- @@ -750,6 +851,60 @@ def _thunk_cholesky(a: ndarray) -> ndarray: return output +def _thunk_eig(a: ndarray) -> tuple[ndarray, ...]: + if a.dtype.kind not in ("f", "c"): + a = a.astype("float64") + + if a.dtype == np.float32: + complex_dtype = np.dtype(np.complex64) + elif a.dtype == np.float64: + complex_dtype = np.dtype(np.complex128) # type: ignore + elif a.dtype.kind in ("c"): + complex_dtype = a.dtype + else: + raise TypeError("Eig input not supported (missing a conversion?)") + + out_ew = ndarray( + shape=a.shape[:-1], + dtype=complex_dtype, + inputs=(a,), + ) + + out_ev = ndarray( + shape=a.shape, + dtype=complex_dtype, + inputs=(a,), + ) + + if a.shape[-1] > 0: + a._thunk.eig(out_ew._thunk, out_ev._thunk) + return out_ew, out_ev + + +def _thunk_eigvals(a: ndarray) -> ndarray: + if a.dtype.kind not in ("f", "c"): + a = a.astype("float64") + + if a.dtype == np.float32: + complex_dtype = np.dtype(np.complex64) + elif a.dtype == np.float64: + complex_dtype = np.dtype(np.complex128) # type: ignore + elif a.dtype.kind in ("c"): + complex_dtype = a.dtype + else: + raise TypeError("Eigvals input not supported (missing a conversion?)") + + out_ew = ndarray( + shape=a.shape[:-1], + dtype=complex_dtype, + inputs=(a,), + ) + + if a.shape[-1] > 0: + a._thunk.eigvals(out_ew._thunk) + return out_ew + + def _thunk_qr(a: ndarray) -> tuple[ndarray, ...]: if a.dtype.kind not in ("f", "c"): a = a.astype("float64") diff --git a/cupynumeric/runtime.py b/cupynumeric/runtime.py index 7af064d463..d2f6f65da2 100644 --- a/cupynumeric/runtime.py +++ b/cupynumeric/runtime.py @@ -103,6 +103,13 @@ def __init__(self) -> None: # Maps value types to struct types used in argmin/argmax self._cached_argred_types: dict[ty.Type, ty.Type] = dict() + def cusolver_has_geev(self) -> bool: + if not hasattr(self, "cusolver_has_geev_"): + self.cusolver_has_geev_ = ( + cupynumeric_lib.shared_object.cupynumeric_cusolver_has_geev() + ) + return self.cusolver_has_geev_ + @property def num_procs(self) -> int: return len(legate_runtime.machine) diff --git a/cupynumeric_cpp.cmake b/cupynumeric_cpp.cmake index 9871a58875..2a56ccbc0a 100644 --- a/cupynumeric_cpp.cmake +++ b/cupynumeric_cpp.cmake @@ -155,6 +155,7 @@ target_sources(cupynumeric PRIVATE src/cupynumeric/matrix/batched_cholesky.cc src/cupynumeric/matrix/contract.cc src/cupynumeric/matrix/diag.cc + src/cupynumeric/matrix/geev.cc src/cupynumeric/matrix/gemm.cc src/cupynumeric/matrix/matmul.cc src/cupynumeric/matrix/matvecmul.cc @@ -219,6 +220,7 @@ if(Legion_USE_OpenMP) src/cupynumeric/matrix/batched_cholesky_omp.cc src/cupynumeric/matrix/contract_omp.cc src/cupynumeric/matrix/diag_omp.cc + src/cupynumeric/matrix/geev_omp.cc src/cupynumeric/matrix/gemm_omp.cc src/cupynumeric/matrix/matmul_omp.cc src/cupynumeric/matrix/matvecmul_omp.cc @@ -273,6 +275,7 @@ if(Legion_USE_CUDA) src/cupynumeric/matrix/batched_cholesky.cu src/cupynumeric/matrix/contract.cu src/cupynumeric/matrix/diag.cu + src/cupynumeric/matrix/geev.cu src/cupynumeric/matrix/gemm.cu src/cupynumeric/matrix/matmul.cu src/cupynumeric/matrix/matvecmul.cu diff --git a/docs/cupynumeric/source/api/linalg.rst b/docs/cupynumeric/source/api/linalg.rst index d57692d726..5b843c7e46 100644 --- a/docs/cupynumeric/source/api/linalg.rst +++ b/docs/cupynumeric/source/api/linalg.rst @@ -29,6 +29,8 @@ Decompositions :toctree: generated/ linalg.cholesky + linalg.eig + linalg.eigvals linalg.qr linalg.svd diff --git a/src/cupynumeric/cuda_help.h b/src/cupynumeric/cuda_help.h index 3e487f2baf..dd81f60224 100644 --- a/src/cupynumeric/cuda_help.h +++ b/src/cupynumeric/cuda_help.h @@ -311,6 +311,69 @@ struct cufftPlanParams { std::string to_string() const; }; +typedef cusolverStatus_t (*cusolverDnXgeev_bufferSize_handle)(cusolverDnHandle_t handle, + cusolverDnParams_t params, + cusolverEigMode_t jobvl, + cusolverEigMode_t jobvr, + int64_t n, + cudaDataType dataTypeA, + const void* A, + int64_t lda, + cudaDataType dataTypeW, + const void* W, + cudaDataType dataTypeVL, + const void* VL, + int64_t ldvl, + cudaDataType dataTypeVR, + const void* VR, + int64_t ldvr, + cudaDataType computeType, + size_t* workspaceInBytesOnDevice, + size_t* workspaceInBytesOnHost); + +typedef cusolverStatus_t (*cusolverDnXgeev_handle)(cusolverDnHandle_t handle, + cusolverDnParams_t params, + cusolverEigMode_t jobvl, + cusolverEigMode_t jobvr, + int64_t n, + cudaDataType dataTypeA, + void* A, + int64_t lda, + cudaDataType dataTypeW, + void* W, + cudaDataType dataTypeVL, + void* VL, + int64_t ldvl, + cudaDataType dataTypeVR, + void* VR, + int64_t ldvr, + cudaDataType computeType, + void* bufferOnDevice, + size_t workspaceInBytesOnDevice, + void* bufferOnHost, + size_t workspaceInBytesOnHost, + int* info); + +struct CuSolverExtraSymbols { + private: + void* cusolver_lib; + + public: + // geev support (since 12.6) + cusolverDnXgeev_bufferSize_handle cusolver_geev_bufferSize; + cusolverDnXgeev_handle cusolver_geev; + bool has_geev; + + CuSolverExtraSymbols(); + ~CuSolverExtraSymbols(); + + // Prevent copying and overwriting + CuSolverExtraSymbols(const CuSolverExtraSymbols& rhs) = delete; + CuSolverExtraSymbols& operator=(const CuSolverExtraSymbols& rhs) = delete; + + void finalize(); +}; + // Defined in cudalibs.cu // Return a cached stream for the current GPU @@ -319,6 +382,7 @@ int get_device_ordinal(); const cudaDeviceProp& get_device_properties(); cublasHandle_t get_cublas(); cusolverDnHandle_t get_cusolver(); +CuSolverExtraSymbols* get_cusolver_extra_symbols(); #if LEGATE_DEFINED(CUPYNUMERIC_USE_CUSOLVERMP) cusolverMpHandle_t get_cusolvermp(); #endif diff --git a/src/cupynumeric/cudalibs.cu b/src/cupynumeric/cudalibs.cu index 93f19ca61e..2dec1d7dbc 100644 --- a/src/cupynumeric/cudalibs.cu +++ b/src/cupynumeric/cudalibs.cu @@ -19,6 +19,7 @@ #include "cudalibs.h" +#include #include using namespace legate; @@ -270,6 +271,39 @@ cufftPlan* cufftPlanCache::get_cufft_plan(const cufftPlanParams& params) return result; } +CuSolverExtraSymbols::CuSolverExtraSymbols() +{ + cusolver_lib = dlopen("libcusolver.so", RTLD_LAZY | RTLD_DEEPBIND); + void* fn1 = dlsym(cusolver_lib, "cusolverDnXgeev_bufferSize"); + if (fn1 == nullptr) { + dlerror(); + } else { + cusolver_geev_bufferSize = (cusolverDnXgeev_bufferSize_handle)fn1; + has_geev = true; + } + + void* fn2 = dlsym(cusolver_lib, "cusolverDnXgeev"); + if (fn2 == nullptr) { + has_geev = false; + cusolver_geev_bufferSize = nullptr; + dlerror(); + } else { + cusolver_geev = (cusolverDnXgeev_handle)fn2; + } +} + +void CuSolverExtraSymbols::finalize() +{ + cusolver_geev = nullptr; + cusolver_geev_bufferSize = nullptr; + has_geev = false; + if (cusolver_lib != nullptr) { + dlclose(cusolver_lib); + } +} + +CuSolverExtraSymbols::~CuSolverExtraSymbols() { finalize(); } + CUDALibraries::CUDALibraries() : finalized_(false), cublas_(nullptr), @@ -294,6 +328,7 @@ void CUDALibraries::finalize() if (cusolver_ != nullptr) { finalize_cusolver(); } + #if LEGATE_DEFINED(CUPYNUMERIC_USE_CUSOLVERMP) if (cusolvermp_ != nullptr) { finalize_cusolvermp(); @@ -445,6 +480,18 @@ cusolverDnContext* get_cusolver() return lib.get_cusolver(); } +static CuSolverExtraSymbols& static_cusolver_extra_symbols() +{ + static CuSolverExtraSymbols cusolver_extra_symbols; + return cusolver_extra_symbols; +} + +CuSolverExtraSymbols* get_cusolver_extra_symbols() +{ + auto& symbols = static_cusolver_extra_symbols(); + return &symbols; +} + #if LEGATE_DEFINED(CUPYNUMERIC_USE_CUSOLVERMP) cusolverMpHandle* get_cusolvermp() { @@ -493,6 +540,7 @@ class LoadCUDALibsTask : public CuPyNumericTask { auto& lib = get_cuda_libraries(proc); lib.get_cublas(); lib.get_cusolver(); + auto* extra = get_cusolver_extra_symbols(); #if LEGATE_DEFINED(CUPYNUMERIC_USE_CUSOLVERMP) lib.get_cusolvermp(); #endif @@ -510,6 +558,8 @@ class UnloadCUDALibsTask : public CuPyNumericTask { const auto proc = legate::Processor::get_executing_processor(); auto& lib = get_cuda_libraries(proc); lib.finalize(); + auto* extra = get_cusolver_extra_symbols(); + extra->finalize(); destroy_bitgenerator(proc); } }; @@ -521,3 +571,8 @@ static void __attribute__((constructor)) register_tasks(void) } } // namespace cupynumeric + +extern "C" { + +bool cupynumeric_cusolver_has_geev() { return cupynumeric::get_cusolver_extra_symbols()->has_geev; } +} diff --git a/src/cupynumeric/cudalibs.h b/src/cupynumeric/cudalibs.h index 8615ba3225..a0718b8500 100644 --- a/src/cupynumeric/cudalibs.h +++ b/src/cupynumeric/cudalibs.h @@ -58,6 +58,7 @@ struct CUDALibraries { std::unique_ptr device_prop_{}; cublasContext* cublas_; cusolverDnContext* cusolver_; + #if LEGATE_DEFINED(CUPYNUMERIC_USE_CUSOLVERMP) cusolverMpHandle* cusolvermp_; #endif diff --git a/src/cupynumeric/cupynumeric.cc b/src/cupynumeric/cupynumeric.cc index 1674f3fcf4..09661b77a0 100644 --- a/src/cupynumeric/cupynumeric.cc +++ b/src/cupynumeric/cupynumeric.cc @@ -83,4 +83,8 @@ bool cupynumeric_has_cusolvermp() { return LEGATE_DEFINED(LEGATE_USE_CUDA) && LEGATE_DEFINED(CUPYNUMERIC_USE_CUSOLVERMP); } + +#if !LEGATE_DEFINED(LEGATE_USE_CUDA) +bool cupynumeric_cusolver_has_geev() { return false; } +#endif } diff --git a/src/cupynumeric/cupynumeric_c.h b/src/cupynumeric/cupynumeric_c.h index fae0123a2f..4c24685d66 100644 --- a/src/cupynumeric/cupynumeric_c.h +++ b/src/cupynumeric/cupynumeric_c.h @@ -41,6 +41,7 @@ enum CuPyNumericOpCode { CUPYNUMERIC_FFT, CUPYNUMERIC_FILL, CUPYNUMERIC_FLIP, + CUPYNUMERIC_GEEV, CUPYNUMERIC_GEMM, CUPYNUMERIC_HISTOGRAM, CUPYNUMERIC_LOAD_CUDALIBS, @@ -342,6 +343,8 @@ typedef struct ReductionOpIds { void cupynumeric_perform_registration(); bool cupynumeric_has_cusolvermp(); +bool cupynumeric_cusolver_has_geev(); + unsigned cupynumeric_max_eager_volume(); unsigned cupynumeric_matmul_cache_size(); diff --git a/src/cupynumeric/mapper.cc b/src/cupynumeric/mapper.cc index 8c39667f9b..d3365fad2f 100644 --- a/src/cupynumeric/mapper.cc +++ b/src/cupynumeric/mapper.cc @@ -120,6 +120,42 @@ std::vector CuPyNumericMapper::store_mappings( } return mappings; } + case CUPYNUMERIC_GEEV: { + std::vector mappings; + auto input_a = task.input(0); + auto output_ew = task.output(0); + + auto dimensions = input_a.dim(); + + // last 2 (matrix) dimensions col-major + // batch dimensions 0, ..., dim-3 row-major + std::vector dim_order; + dim_order.push_back(dimensions - 2); + dim_order.push_back(dimensions - 1); + for (int32_t i = dimensions - 3; i >= 0; i--) { + dim_order.push_back(i); + } + + mappings.push_back( + StoreMapping::default_mapping(input_a.data(), options.front(), true /*exact*/)); + mappings.back().policy().ordering.set_custom_order(dim_order); + + // eigenvalue computation is optional + if (task.outputs().size() > 1) { + auto output_ev = task.output(1); + mappings.push_back( + StoreMapping::default_mapping(output_ev.data(), options.front(), true /*exact*/)); + mappings.back().policy().ordering.set_custom_order(dim_order); + } + + // remove last dimension for eigenvalues + dim_order.erase(std::next(dim_order.begin())); + mappings.push_back( + StoreMapping::default_mapping(output_ew.data(), options.front(), true /*exact*/)); + mappings.back().policy().ordering.set_custom_order(dim_order); + + return mappings; + } // CHANGE: If this code is changed, make sure all layouts are // consistent with those assumed in batched_cholesky.cu, etc case CUPYNUMERIC_BATCHED_CHOLESKY: { @@ -267,6 +303,7 @@ std::optional CuPyNumericMapper::allocation_pool_size( } } case CUPYNUMERIC_BATCHED_CHOLESKY: [[fallthrough]]; + case CUPYNUMERIC_GEEV: [[fallthrough]]; case CUPYNUMERIC_POTRF: [[fallthrough]]; // FIXME(wonchanl): These tasks actually don't need unbound pools on CPUs. They are being used // only to finish up the first implementation quickly diff --git a/src/cupynumeric/matrix/geev.cc b/src/cupynumeric/matrix/geev.cc new file mode 100644 index 0000000000..6974c6ef16 --- /dev/null +++ b/src/cupynumeric/matrix/geev.cc @@ -0,0 +1,40 @@ +/* Copyright 2024 NVIDIA Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +#include "cupynumeric/matrix/geev.h" +#include "cupynumeric/matrix/geev_template.inl" +#include "cupynumeric/matrix/geev_cpu.inl" + +namespace cupynumeric { + +using namespace legate; + +/*static*/ const char* GeevTask::ERROR_MESSAGE = "Factorization failed"; + +/*static*/ void GeevTask::cpu_variant(TaskContext context) +{ +#if LEGATE_DEFINED(LEGATE_USE_OPENMP) + openblas_set_num_threads(1); // make sure this isn't overzealous +#endif + geev_template(context); +} + +namespace // unnamed +{ +static void __attribute__((constructor)) register_tasks(void) { GeevTask::register_variants(); } +} // namespace + +} // namespace cupynumeric \ No newline at end of file diff --git a/src/cupynumeric/matrix/geev.cu b/src/cupynumeric/matrix/geev.cu new file mode 100644 index 0000000000..66655ce60e --- /dev/null +++ b/src/cupynumeric/matrix/geev.cu @@ -0,0 +1,261 @@ +/* Copyright 2024 NVIDIA Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +#include "cupynumeric/matrix/geev.h" +#include "cupynumeric/matrix/geev_template.inl" +#include "cupynumeric/utilities/thrust_util.h" + +#include +#include + +#include "cupynumeric/cuda_help.h" +#include + +namespace cupynumeric { + +using namespace legate; + +template +struct assembleEvs : public thrust::unary_function { + const VAL_COMPLEX* ew_in_; + const VAL* ev_in_; + const int64_t m_; + + assembleEvs(VAL_COMPLEX* ew_in, VAL* ev_in, int64_t m) : ew_in_(ew_in), ev_in_(ev_in), m_(m) {} + + __CUDA_HD__ VAL_COMPLEX operator()(const int64_t& idx) const + { + int64_t col_idx = idx / m_; + auto ew_i = ew_in_[col_idx].y; + // if img == 0 -> ev = ev[idx] + // if img positive -> ev = ev[idx] + i*ev[idx+1] + // if img negative -> ev = ev[idx-1] - i*ev[idx] + const int64_t real_idx = idx - ((ew_i < 0) ? m_ : 0); + const int64_t img_idx = idx + ((ew_i > 0) ? m_ : 0); + VAL factor = ((ew_i > 0) ? VAL(1.0) : ((ew_i < 0) ? VAL(-1.0) : VAL(0.0))); + VAL_COMPLEX result; + result.x = ev_in_[real_idx]; + result.y = factor * ev_in_[img_idx]; + return result; + } +}; + +template +void assemble_complex_evs(VAL_COMPLEX* ev_out, VAL_COMPLEX* ew_in, VAL* ev_in, int64_t m) +{ + auto stream = get_cached_stream(); + thrust::transform(DEFAULT_POLICY.on(stream), + thrust::make_counting_iterator(0), + thrust::make_counting_iterator(m * m), + ev_out, + assembleEvs(ew_in, ev_in, m)); +} + +template +static inline void geev_template( + DataType valTypeC, DataType valTypeA, int64_t m, const void* a, void* ew, void* ev) +{ + auto handle = get_cusolver(); + auto stream = get_cached_stream(); + auto geev_handles = get_cusolver_extra_symbols(); + + bool compute_evs = ev != nullptr; + + auto a_copy = create_buffer(m * m, Memory::Kind::GPU_FB_MEM); + + CUPYNUMERIC_CHECK_CUDA( + cudaMemcpyAsync(a_copy.ptr(0), a, m * m * sizeof(VAL), cudaMemcpyDeviceToDevice, stream)); + + CHECK_CUSOLVER(cusolverDnSetStream(handle, stream)); + + size_t lwork_device, lwork_host; + CHECK_CUSOLVER(geev_handles->cusolver_geev_bufferSize( + handle, + nullptr, + CUSOLVER_EIG_MODE_NOVECTOR, + compute_evs ? CUSOLVER_EIG_MODE_VECTOR : CUSOLVER_EIG_MODE_NOVECTOR, + m, + valTypeA, + reinterpret_cast(a_copy.ptr(0)), + m, + valTypeC, + ew, + valTypeA, + nullptr, // left EVs + m, + valTypeA, + ev, + m, + valTypeA, + &lwork_device, + &lwork_host)); + + auto buffer = create_buffer(lwork_device, Memory::Kind::GPU_FB_MEM); + std::vector buffer_host(std::max(1ul, lwork_host)); + auto info = create_buffer(1, Memory::Kind::Z_COPY_MEM); + + CHECK_CUSOLVER( + geev_handles->cusolver_geev(handle, + nullptr, + CUSOLVER_EIG_MODE_NOVECTOR, + compute_evs ? CUSOLVER_EIG_MODE_VECTOR : CUSOLVER_EIG_MODE_NOVECTOR, + m, + valTypeA, + reinterpret_cast(a_copy.ptr(0)), + m, + valTypeC, + ew, + valTypeA, + nullptr, // left EVs + m, + valTypeA, + ev, + m, + valTypeA, + buffer.ptr(0), + lwork_device, + buffer_host.data(), + lwork_host, + info.ptr(0))); + + CUPYNUMERIC_CHECK_CUDA_STREAM(stream); + + if (info[0] != 0) { + throw legate::TaskException(GeevTask::ERROR_MESSAGE); + } +} + +template <> +struct GeevImplBody { + void operator()(int64_t m, + int64_t num_batches, + int64_t batch_stride_ew, + int64_t batch_stride_ev, + const float* a, + complex* ew, + complex* ev) + { + bool compute_evs = ev != nullptr; + + // for real input --> create real buffer and assemble afterwards + auto ev_tmp = create_buffer(compute_evs ? m * m : 0, Memory::Kind::GPU_FB_MEM); + + for (int64_t batch_idx = 0; batch_idx < num_batches; ++batch_idx) { + geev_template(CUDA_C_32F, + CUDA_R_32F, + m, + a + batch_idx * batch_stride_ev, + reinterpret_cast(ew + batch_idx * batch_stride_ew), + compute_evs ? reinterpret_cast(ev_tmp.ptr(0)) : nullptr); + + if (compute_evs) { + assemble_complex_evs(reinterpret_cast(ev + batch_idx * batch_stride_ev), + reinterpret_cast(ew + batch_idx * batch_stride_ew), + ev_tmp.ptr(0), + m); + } + } + } +}; + +template <> +struct GeevImplBody { + void operator()(int64_t m, + int64_t num_batches, + int64_t batch_stride_ew, + int64_t batch_stride_ev, + const double* a, + complex* ew, + complex* ev) + { + bool compute_evs = ev != nullptr; + + // for real input --> create real buffer and assemble afterwards + auto ev_tmp = create_buffer(compute_evs ? m * m : 0, Memory::Kind::GPU_FB_MEM); + + for (int64_t batch_idx = 0; batch_idx < num_batches; ++batch_idx) { + geev_template(CUDA_C_64F, + CUDA_R_64F, + m, + a + batch_idx * batch_stride_ev, + reinterpret_cast(ew + batch_idx * batch_stride_ew), + compute_evs ? reinterpret_cast(ev_tmp.ptr(0)) : nullptr); + + if (compute_evs) { + assemble_complex_evs(reinterpret_cast(ev + batch_idx * batch_stride_ev), + reinterpret_cast(ew + batch_idx * batch_stride_ew), + ev_tmp.ptr(0), + m); + } + } + } +}; + +template <> +struct GeevImplBody { + void operator()(int64_t m, + int64_t num_batches, + int64_t batch_stride_ew, + int64_t batch_stride_ev, + const complex* a, + complex* ew, + complex* ev) + { + bool compute_evs = ev != nullptr; + + for (int64_t batch_idx = 0; batch_idx < num_batches; ++batch_idx) { + geev_template>( + CUDA_C_32F, + CUDA_C_32F, + m, + reinterpret_cast(a + batch_idx * batch_stride_ev), + reinterpret_cast(ew + batch_idx * batch_stride_ew), + compute_evs ? reinterpret_cast(ev + batch_idx * batch_stride_ev) : nullptr); + } + } +}; + +template <> +struct GeevImplBody { + void operator()(int64_t m, + int64_t num_batches, + int64_t batch_stride_ew, + int64_t batch_stride_ev, + const complex* a, + complex* ew, + complex* ev) + { + bool compute_evs = ev != nullptr; + + for (int64_t batch_idx = 0; batch_idx < num_batches; ++batch_idx) { + geev_template>( + CUDA_C_64F, + CUDA_C_64F, + m, + reinterpret_cast(a + batch_idx * batch_stride_ev), + reinterpret_cast(ew + batch_idx * batch_stride_ew), + compute_evs ? reinterpret_cast(ev + batch_idx * batch_stride_ev) + : nullptr); + } + } +}; + +/*static*/ void GeevTask::gpu_variant(TaskContext context) +{ + geev_template(context); +} + +} // namespace cupynumeric \ No newline at end of file diff --git a/src/cupynumeric/matrix/geev.h b/src/cupynumeric/matrix/geev.h new file mode 100644 index 0000000000..6c1365841e --- /dev/null +++ b/src/cupynumeric/matrix/geev.h @@ -0,0 +1,42 @@ +/* Copyright 2024 NVIDIA Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +#pragma once + +#include "cupynumeric/cupynumeric_task.h" + +namespace cupynumeric { + +class GeevTask : public CuPyNumericTask { + public: + static constexpr auto TASK_ID = legate::LocalTaskID{CUPYNUMERIC_GEEV}; + static const char* ERROR_MESSAGE; + + static constexpr auto CPU_VARIANT_OPTIONS = legate::VariantOptions{}.with_has_allocations(true); + static constexpr auto OMP_VARIANT_OPTIONS = legate::VariantOptions{}.with_has_allocations(true); + static constexpr auto GPU_VARIANT_OPTIONS = legate::VariantOptions{}.with_has_allocations(true); + + public: + static void cpu_variant(legate::TaskContext context); +#if LEGATE_DEFINED(LEGATE_USE_OPENMP) + static void omp_variant(legate::TaskContext context); +#endif +#if LEGATE_DEFINED(LEGATE_USE_CUDA) + static void gpu_variant(legate::TaskContext context); +#endif +}; + +} // namespace cupynumeric \ No newline at end of file diff --git a/src/cupynumeric/matrix/geev_cpu.inl b/src/cupynumeric/matrix/geev_cpu.inl new file mode 100644 index 0000000000..f777c26ac0 --- /dev/null +++ b/src/cupynumeric/matrix/geev_cpu.inl @@ -0,0 +1,341 @@ +/* Copyright 2024 NVIDIA Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +#pragma once + +#include +#include +#include + +namespace cupynumeric { + +using namespace legate; + +namespace { + +template +void assemble_complex(complex* ew, complex* ev, T* ew_r, T* ew_i, T* ev_r, size_t m) +{ + bool skip_next_ev = false; + for (int i = 0; i < m; ++i) { + ew[i] = complex(ew_r[i], ew_i[i]); + if (ev != nullptr) { + if (skip_next_ev) { + skip_next_ev = false; + } else { + T* src1 = &ev_r[i * m]; + complex* dst1 = &ev[i * m]; + if (ew_i[i] != T(0)) { + // define next 2 EVs + T* src2 = src1 + m; + complex* dst2 = dst1 + m; + for (int k = 0; k < m; ++k) { + dst1[k] = complex(src1[k], src2[k]); + dst2[k] = complex(src1[k], T(-1) * src2[k]); + } + skip_next_ev = true; + } else { + for (int k = 0; k < m; ++k) { + dst1[k] = complex(src1[k], T(0)); + } + } + } + } + } +} +} // namespace + +template +struct GeevImplBody { + void operator()(int32_t m, + int32_t num_batches, + int32_t batch_stride_ew, + int32_t batch_stride_ev, + const float* a, + complex* ew, + complex* ev) + { + bool compute_evs = ev != nullptr; + auto a_copy = create_buffer(m * m); + + // for real input --> create real buffer and assemble afterwards + auto ev_tmp = create_buffer(m * m); + float* ev_tmp_prt = ev_tmp.ptr(0); + + for (int64_t batch_idx = 0; batch_idx < num_batches; ++batch_idx) { + std::copy(a, a + (m * m), a_copy.ptr(0)); + + std::vector ew_r(m); + std::vector ew_i(m); + + int32_t info = 0; + float wkopt = 0; + int32_t lwork = -1; + LAPACK_sgeev("N", + compute_evs ? "V" : "N", + &m, + a_copy.ptr(0), + &m, + ew_r.data(), + ew_i.data(), + nullptr, + &m, + ev_tmp_prt, + &m, + &wkopt, + &lwork, + &info); + lwork = (int)wkopt; + + std::vector work_tmp(lwork); + LAPACK_sgeev("N", + compute_evs ? "V" : "N", + &m, + a_copy.ptr(0), + &m, + ew_r.data(), + ew_i.data(), + nullptr, + &m, + ev_tmp_prt, + &m, + work_tmp.data(), + &lwork, + &info); + + if (info != 0) { + throw legate::TaskException(GeevTask::ERROR_MESSAGE); + } + + assemble_complex(ew, ev, ew_r.data(), ew_i.data(), ev_tmp_prt, m); + + a += batch_stride_ev; + ew += batch_stride_ew; + if (compute_evs) { + ev += batch_stride_ev; + } + } + } +}; + +template +struct GeevImplBody { + void operator()(int32_t m, + int32_t num_batches, + int32_t batch_stride_ew, + int32_t batch_stride_ev, + const double* a, + complex* ew, + complex* ev) + { + bool compute_evs = ev != nullptr; + auto a_copy = create_buffer(m * m); + + // for real input --> create real buffer and assemble afterwards + auto ev_tmp = create_buffer(m * m); + double* ev_tmp_prt = ev_tmp.ptr(0); + + for (int64_t batch_idx = 0; batch_idx < num_batches; ++batch_idx) { + std::copy(a, a + (m * m), a_copy.ptr(0)); + + std::vector ew_r(m); + std::vector ew_i(m); + + int32_t info = 0; + double wkopt = 0; + int32_t lwork = -1; + + LAPACK_dgeev("N", + compute_evs ? "V" : "N", + &m, + a_copy.ptr(0), + &m, + ew_r.data(), + ew_i.data(), + nullptr, + &m, + ev_tmp_prt, + &m, + &wkopt, + &lwork, + &info); + lwork = (int)wkopt; + + std::vector work_tmp(lwork); + LAPACK_dgeev("N", + compute_evs ? "V" : "N", + &m, + a_copy.ptr(0), + &m, + ew_r.data(), + ew_i.data(), + nullptr, + &m, + ev_tmp_prt, + &m, + work_tmp.data(), + &lwork, + &info); + + if (info != 0) { + throw legate::TaskException(GeevTask::ERROR_MESSAGE); + } + + assemble_complex(ew, ev, ew_r.data(), ew_i.data(), ev_tmp_prt, m); + + a += batch_stride_ev; + ew += batch_stride_ew; + if (compute_evs) { + ev += batch_stride_ev; + } + } + } +}; + +template +struct GeevImplBody { + void operator()(int32_t m, + int32_t num_batches, + int32_t batch_stride_ew, + int32_t batch_stride_ev, + const complex* a, + complex* ew, + complex* ev) + { + bool compute_evs = ev != nullptr; + auto a_copy = create_buffer>(m * m); + + for (int64_t batch_idx = 0; batch_idx < num_batches; ++batch_idx) { + std::copy(a, a + (m * m), a_copy.ptr(0)); + + int32_t info = 0; + int32_t lwork = -1; + __complex__ float wkopt = 0; + std::vector rwork(2 * m); + + LAPACK_cgeev("N", + compute_evs ? "V" : "N", + &m, + reinterpret_cast<__complex__ float*>(a_copy.ptr(0)), + &m, + reinterpret_cast<__complex__ float*>(ew), + nullptr, + &m, + reinterpret_cast<__complex__ float*>(ev), + &m, + &wkopt, + &lwork, + rwork.data(), + &info); + + lwork = __real__ wkopt; + + std::vector<__complex__ float> work_tmp(lwork); + LAPACK_cgeev("N", + compute_evs ? "V" : "N", + &m, + reinterpret_cast<__complex__ float*>(a_copy.ptr(0)), + &m, + reinterpret_cast<__complex__ float*>(ew), + nullptr, + &m, + reinterpret_cast<__complex__ float*>(ev), + &m, + work_tmp.data(), + &lwork, + rwork.data(), + &info); + + if (info != 0) { + throw legate::TaskException(GeevTask::ERROR_MESSAGE); + } + + a += batch_stride_ev; + ew += batch_stride_ew; + if (compute_evs) { + ev += batch_stride_ev; + } + } + } +}; + +template +struct GeevImplBody { + void operator()(int32_t m, + int32_t num_batches, + int32_t batch_stride_ew, + int32_t batch_stride_ev, + const complex* a, + complex* ew, + complex* ev) + { + bool compute_evs = ev != nullptr; + auto a_copy = create_buffer>(m * m); + + for (int64_t batch_idx = 0; batch_idx < num_batches; ++batch_idx) { + std::copy(a, a + (m * m), a_copy.ptr(0)); + + int32_t info = 0; + int32_t lwork = -1; + __complex__ double wkopt = 0; + std::vector rwork(2 * m); + LAPACK_zgeev("N", + compute_evs ? "V" : "N", + &m, + reinterpret_cast<__complex__ double*>(a_copy.ptr(0)), + &m, + reinterpret_cast<__complex__ double*>(ew), + nullptr, + &m, + reinterpret_cast<__complex__ double*>(ev), + &m, + &wkopt, + &lwork, + rwork.data(), + &info); + + lwork = __real__ wkopt; + + std::vector<__complex__ double> work_tmp(lwork); + LAPACK_zgeev("N", + compute_evs ? "V" : "N", + &m, + reinterpret_cast<__complex__ double*>(a_copy.ptr(0)), + &m, + reinterpret_cast<__complex__ double*>(ew), + nullptr, + &m, + reinterpret_cast<__complex__ double*>(ev), + &m, + work_tmp.data(), + &lwork, + rwork.data(), + &info); + + if (info != 0) { + throw legate::TaskException(GeevTask::ERROR_MESSAGE); + } + + a += batch_stride_ev; + ew += batch_stride_ew; + if (compute_evs) { + ev += batch_stride_ev; + } + } + } +}; + +} // namespace cupynumeric \ No newline at end of file diff --git a/src/cupynumeric/matrix/geev_omp.cc b/src/cupynumeric/matrix/geev_omp.cc new file mode 100644 index 0000000000..e9dccc0cda --- /dev/null +++ b/src/cupynumeric/matrix/geev_omp.cc @@ -0,0 +1,31 @@ +/* Copyright 2024 NVIDIA Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +#include "cupynumeric/matrix/geev.h" +#include "cupynumeric/matrix/geev_template.inl" +#include "cupynumeric/matrix/geev_cpu.inl" + +#include + +namespace cupynumeric { + +/*static*/ void GeevTask::omp_variant(TaskContext context) +{ + openblas_set_num_threads(omp_get_max_threads()); + geev_template(context); +} + +} // namespace cupynumeric \ No newline at end of file diff --git a/src/cupynumeric/matrix/geev_template.inl b/src/cupynumeric/matrix/geev_template.inl new file mode 100644 index 0000000000..d11122d340 --- /dev/null +++ b/src/cupynumeric/matrix/geev_template.inl @@ -0,0 +1,194 @@ +/* Copyright 2024 NVIDIA Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +#pragma once + +#include + +// Useful for IDEs +#include "cupynumeric/matrix/geev.h" + +namespace cupynumeric { + +using namespace legate; + +template +struct GeevImplBody; + +template +struct support_geev : std::false_type {}; +template <> +struct support_geev : std::true_type {}; +template <> +struct support_geev : std::true_type {}; +template <> +struct support_geev : std::true_type {}; +template <> +struct support_geev : std::true_type {}; + +template +struct complex_type { + using TYPE = complex; +}; +template <> +struct complex_type { + using TYPE = complex; +}; +template <> +struct complex_type { + using TYPE = complex; +}; + +template +struct GeevImpl { + template ::value && DIM >= 2>* = nullptr> + void operator()(TaskContext& context) const + { + using VAL = type_of; + using VAL_COMPLEX = typename complex_type::TYPE; + + legate::PhysicalStore a_array = context.input(0); + legate::PhysicalStore ew_array = context.output(0); + +#ifdef DEBUG_CUPYNUMERIC + assert(a_array.dim() >= 2); + assert(a_array.dim() == DIM); + assert(ew_array.dim() == DIM - 1); +#endif + const auto a_shape = a_array.shape(); + const auto ew_shape = ew_array.shape(); + + if (a_shape.empty()) { + return; + } + + int64_t batchsize_total = 1; + std::vector batchdims; + for (auto i = 0; i < DIM - 2; ++i) { + batchdims.push_back(a_shape.hi[i] - a_shape.lo[i] + 1); + batchsize_total *= batchdims.back(); + } + + const int64_t m = a_shape.hi[DIM - 1] - a_shape.lo[DIM - 1] + 1; + +#ifdef DEBUG_CUPYNUMERIC + assert(m > 0); + assert(batchsize_total > 0); + assert(a_shape.hi[DIM - 2] - a_shape.lo[DIM - 2] + 1 == m); + assert(ew_shape.hi[DIM - 2] - ew_shape.lo[DIM - 2] + 1 == m); + for (auto i = 0; i < batchdims.size(); ++i) { + assert(ew_shape.hi[i] - ew_shape.lo[i] + 1 == batchdims[i]); + } +#endif + size_t a_strides[DIM]; + size_t ew_strides[DIM - 1]; + size_t ev_strides[DIM]; + + auto* a_acc = a_array.read_accessor(a_shape).ptr(a_shape, a_strides); + auto* ew_acc = + ew_array.write_accessor(ew_shape).ptr(ew_shape, ew_strides); + VAL_COMPLEX* ev_acc = nullptr; + + // optional computation of eigenvectors + bool compute_evs = context.outputs().size() > 1; + if (compute_evs) { + legate::PhysicalStore ev_array = context.output(1); +#ifdef DEBUG_CUPYNUMERIC + assert(ev_array.dim() == DIM); +#endif + const auto ev_shape = ev_array.shape(); +#ifdef DEBUG_CUPYNUMERIC + assert(ev_shape.hi[DIM - 2] - ev_shape.lo[DIM - 2] + 1 == m); + assert(ev_shape.hi[DIM - 1] - ev_shape.lo[DIM - 1] + 1 == m); + for (auto i = 0; i < batchdims.size(); ++i) { + assert(ev_shape.hi[i] - ev_shape.lo[i] + 1 == batchdims[i]); + } +#endif + ev_acc = ev_array.write_accessor(ev_shape).ptr(ev_shape, ev_strides); + } + + // Find the outer most batch dimension on which we can iterate with constant batch stride. + // Then loop over remaining 'outer' batches + // Example: + // a-shape = (1, 4, 2, 7, 1, M, M) + // => inner_batch_dim=3, inner_batch_size=7 + // => outer_batch_size=8 + + // 1. find batch dimension to perform computation with constant stride + int64_t inner_batch_dim = -1; + int64_t inner_batch_size = 1; + int64_t inner_batch_stride_ev = m * m; + int64_t inner_batch_stride_ew = m; + for (int i = batchdims.size() - 1; i >= 0; --i) { + if (batchdims[i] > 1) { + inner_batch_dim = i; + inner_batch_size = batchdims[i]; + inner_batch_stride_ev = a_strides[i]; + inner_batch_stride_ew = ew_strides[i]; + break; + } + } + + const int64_t outer_batch_size = batchsize_total / inner_batch_size; + + // 2. loop over prod(dims(0..idx-1)), need to update offsets every start + for (int64_t batch_idx = 0; batch_idx < outer_batch_size; ++batch_idx) { + // duplicate pointers to data + auto a_acc_cur = a_acc; + auto ew_acc_cur = ew_acc; + auto ev_acc_cur = ev_acc; + + // apply offsets for pointers / assuming row wise batch order + int64_t remainder_idx = batch_idx; + for (int i = inner_batch_dim - 1; i >= 0; i--) { + int64_t dim_position = remainder_idx % batchdims[i]; + a_acc_cur += a_strides[i] * dim_position; + ew_acc_cur += ew_strides[i] * dim_position; + if (compute_evs) { + ev_acc_cur += ev_strides[i] * dim_position; + } + remainder_idx /= batchdims[i]; + } + + GeevImplBody()(m, + inner_batch_size, + inner_batch_stride_ew, + inner_batch_stride_ev, + a_acc_cur, + ew_acc_cur, + ev_acc_cur); + } + } + + template ::value || DIM<2>* = nullptr> void + operator()(TaskContext& context) const + { + assert(false); + } +}; + +template +static void geev_template(TaskContext& context) +{ + auto a_array = context.input(0); + double_dispatch(a_array.dim(), a_array.type().code(), GeevImpl{}, context); +} + +} // namespace cupynumeric diff --git a/tests/integration/test_eig.py b/tests/integration/test_eig.py new file mode 100644 index 0000000000..943d46b1af --- /dev/null +++ b/tests/integration/test_eig.py @@ -0,0 +1,161 @@ +# Copyright 2024 NVIDIA Corporation +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +import numpy as np +import pytest + +import cupynumeric as num + +SIZES = [ + (5, 5), + ( + 3, + 3, + ), + (2, 5, 5), + (12, 3, 3), + (1, 5, 5), + (3, 1, 1), + ( + 10, + 2, + 2, + ), + (1, 4, 4), + (1, 0, 0), + (1, 1, 1), +] + + +SIZES_4D = [ + (3, 2, 5, 5), + (1, 2, 5, 5), + (4, 1, 5, 5), + (2, 1, 0, 0), +] + + +def assert_individual(a, ew, ev): + assert num.linalg.norm(ev, ord=np.inf) > 0 + + ew_diag = num.array(np.diagflat(ew)) + a_ev = num.matmul(a, ev) + ev_ew = num.matmul(ev, ew_diag) + + if ev_ew.dtype is np.dtype(np.complex64): + rtol = 1e-02 + atol = 1e-04 + else: + rtol = 1e-05 + atol = 1e-08 + + assert num.allclose(a_ev, ev_ew, rtol=rtol, atol=atol) + + +def assert_result(a, ew, ev): + m = a.shape[-1] + if m == 0: + return + num_matrices = int(np.prod(a.shape) // (m * m)) + batch_view_a = a.reshape(num_matrices, m, m) + batch_view_ew = ew.reshape(num_matrices, m) + batch_view_ev = ev.reshape(num_matrices, m, m) + + for idx in range(num_matrices): + assert_individual( + batch_view_a[idx, :, :], + batch_view_ew[idx, :], + batch_view_ev[idx, :, :], + ) + + +class TestEig(object): + @pytest.mark.xfail + def test_arr_none(self): + res_np = np.linalg.eig( + None + ) # AxisError: axis -1 is out of bounds for array of dimension 0 + res_num = num.linalg.eig( + None + ) # AttributeError: 'NoneType' object has no attribute 'shape' + assert np.equal(res_np, res_num) + + @pytest.mark.xfail + @pytest.mark.parametrize("arr", ([], [[]], [[], []])) + def test_arr_empty(self, arr): + res_np = np.linalg.eig(arr) + res_num = num.linalg.eig(arr) + assert np.equal(res_np, res_num) + + @pytest.mark.xfail + @pytest.mark.parametrize( + "arr", ([1], [[2]], [[2], [1]], [[[2], [1]], [[3], [4]]]) + ) + def atest_arr_dim_1(self, arr): + res_np = np.linalg.eig(arr) + res_num = num.linalg.eig(arr) + assert np.equal(res_np, res_num) + + @pytest.mark.parametrize("size", SIZES) + @pytest.mark.parametrize("dtype", (np.float32, np.float64)) + def test_arr_basic_real(self, size, dtype): + arr_np = np.random.randint(-100, 100, size).astype(dtype) + arr_num = num.array(arr_np) + ew, ev = num.linalg.eig(arr_num) + assert_result(arr_num, ew, ev) + + @pytest.mark.parametrize("size", SIZES) + @pytest.mark.parametrize("dtype", (np.complex64, np.complex128)) + def test_arr_basic_complex(self, size, dtype): + arr_np = ( + np.random.randint(-100, 100, size) + + np.random.randint(-100, 100, size) * 1.0j + ).astype(dtype) + arr_num = num.array(arr_np) + ew, ev = num.linalg.eig(arr_num) + assert_result(arr_num, ew, ev) + + @pytest.mark.parametrize("size", SIZES) + @pytest.mark.parametrize("dtype", (np.int32, np.int64)) + def test_arr_basic_int(self, size, dtype): + arr_np = np.random.randint(-100, 100, size).astype(dtype) + arr_num = num.array(arr_np) + ew, ev = num.linalg.eig(arr_num) + assert_result(arr_num, ew, ev) + + @pytest.mark.parametrize("size", SIZES_4D) + @pytest.mark.parametrize("dtype", (np.float32, np.float64)) + def test_arr_4d_real(self, size, dtype): + arr_np = np.random.randint(-100, 100, size).astype(dtype) + arr_num = num.array(arr_np) + ew, ev = num.linalg.eig(arr_num) + assert_result(arr_num, ew, ev) + + @pytest.mark.parametrize("size", SIZES_4D) + @pytest.mark.parametrize("dtype", (np.complex64, np.complex128)) + def test_arr_4d_complex(self, size, dtype): + arr_np = ( + np.random.randint(-100, 100, size) + + np.random.randint(-100, 100, size) * 1.0j + ).astype(dtype) + arr_num = num.array(arr_np) + ew, ev = num.linalg.eig(arr_num) + assert_result(arr_num, ew, ev) + + +if __name__ == "__main__": + import sys + + sys.exit(pytest.main(sys.argv)) diff --git a/tests/unit/cupynumeric/test_config.py b/tests/unit/cupynumeric/test_config.py index fa6922998b..5a7edf2f03 100644 --- a/tests/unit/cupynumeric/test_config.py +++ b/tests/unit/cupynumeric/test_config.py @@ -72,6 +72,7 @@ def test_CuPyNumericOpCode() -> None: "FFT", "FILL", "FLIP", + "GEEV", "GEMM", "HISTOGRAM", "LOAD_CUDALIBS", From 1caa3949aa39117597be44482731c67681a071ef Mon Sep 17 00:00:00 2001 From: "Marcus D. Hanwell" Date: Wed, 5 Mar 2025 16:46:38 -0500 Subject: [PATCH 433/462] Add in missing CTK wheel dependencies (#637) This adds in the missing CUDA toolkit libraries and the rpath entries for them relative to our wheel. --- .github/workflows/pr.yml | 4 ++-- scripts/build/python/cupynumeric/CMakeLists.txt | 10 +++++++++- scripts/build/python/cupynumeric/pyproject.toml | 5 +++++ 3 files changed, 16 insertions(+), 3 deletions(-) diff --git a/.github/workflows/pr.yml b/.github/workflows/pr.yml index bed67961d3..2a6964b1c6 100644 --- a/.github/workflows/pr.yml +++ b/.github/workflows/pr.yml @@ -23,7 +23,7 @@ jobs: build-type: pull-request # TODO(cryos): https://github.com/nv-legate/legate.internal/issues/1893 # Remove this once we have uploads to PyPi. - legate-sha: c53cdb270394ce11576c4fd34149a2497f70d301 + legate-sha: 546f767a7e6c54470f4ce77a2b5449f7ffe1b2a0 wheels-test: needs: wheels-build secrets: inherit @@ -32,4 +32,4 @@ jobs: build-type: pull-request # TODO(cryos): https://github.com/nv-legate/legate.internal/issues/1893 # Remove this once we have uploads to PyPi. - legate-sha: c53cdb270394ce11576c4fd34149a2497f70d301 + legate-sha: 546f767a7e6c54470f4ce77a2b5449f7ffe1b2a0 diff --git a/scripts/build/python/cupynumeric/CMakeLists.txt b/scripts/build/python/cupynumeric/CMakeLists.txt index 61f5d8a4c5..9fa56b1043 100644 --- a/scripts/build/python/cupynumeric/CMakeLists.txt +++ b/scripts/build/python/cupynumeric/CMakeLists.txt @@ -19,7 +19,15 @@ set(CUPYNUMERIC_BUILD_PIP_WHEELS ON) add_subdirectory(../../../.. cupynumeric-all) -set(rpaths "$ORIGIN/../../cutensor/lib" "$ORIGIN/../../legate/lib64") +set(rpaths + "$ORIGIN/../../legate/lib64" + "$ORIGIN/../../cutensor/lib" + "$ORIGIN/../../nvidia/cublas/lib" + "$ORIGIN/../../nvidia/cufft/lib" + "$ORIGIN/../../nvidia/cusolver/lib" + "$ORIGIN/../../nvidia/cusparse/lib" + "$ORIGIN/../../nvidia/nvjitlink/lib" +) set_property( TARGET cupynumeric PROPERTY INSTALL_RPATH ${rpaths} diff --git a/scripts/build/python/cupynumeric/pyproject.toml b/scripts/build/python/cupynumeric/pyproject.toml index fcff8ea0c4..423fbd251b 100644 --- a/scripts/build/python/cupynumeric/pyproject.toml +++ b/scripts/build/python/cupynumeric/pyproject.toml @@ -38,6 +38,11 @@ dependencies = [ "opt_einsum", "legate==25.3.*,>=0.0.0a0", "cutensor-cu12", + "nvidia-cublas-cu12", + "nvidia-cufft-cu12", + "nvidia-cusolver-cu12", + "nvidia-cusparse-cu12", + "nvidia-nvjitlink-cu12", ] dynamic = ["version"] From e44b99fd1f438396e0514d96c8da0992d843b653 Mon Sep 17 00:00:00 2001 From: Irina Demeshko Date: Wed, 5 Mar 2025 21:23:53 -0800 Subject: [PATCH 434/462] updating Legate hash (#638) * updating Legate hash * Update to latest legate sha --------- Co-authored-by: Manolis Papadakis --- cmake/versions.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmake/versions.json b/cmake/versions.json index f29fd1ac7f..c6cd348e46 100644 --- a/cmake/versions.json +++ b/cmake/versions.json @@ -10,7 +10,7 @@ "git_url" : "git@github.com:nv-legate/legate.internal.git", "git_shallow": false, "always_download": false, - "git_tag" : "d1c9b9c657976c440c5eaad0a37586d9268ee8c5", + "git_tag" : "6fd728b90e25cddbc450e8dbc63f4bcf077a0fb0", "anaconda_label": "experimental" } } From 78997f8b9f33cb9bc3563e379c43eae848276e97 Mon Sep 17 00:00:00 2001 From: "Marcus D. Hanwell" Date: Fri, 7 Mar 2025 12:39:47 -0500 Subject: [PATCH 435/462] Update to build new format conda packages (#577) This updates the build scripts to build conda packages using the new format. --- continuous_integration/scripts/build | 2 ++ 1 file changed, 2 insertions(+) diff --git a/continuous_integration/scripts/build b/continuous_integration/scripts/build index 1893ce10eb..b5f21379da 100755 --- a/continuous_integration/scripts/build +++ b/continuous_integration/scripts/build @@ -34,6 +34,8 @@ build_release_product() { # https://github.com/nv-legate/cupynumeric.internal/pull/351#issuecomment-2286922486 export CONDA_OVERRIDE_CUDA="${CUDA_VERSION}" + # Use the new .conda format. + conda config --set conda_build.pkg_format 2 conda mambabuild "${conda_build_args[@]}" "${REPO_DIR}/conda/conda-build"; copy_release_artifacts From eb4e972e4f39148231f11b5983b150d41b43e10c Mon Sep 17 00:00:00 2001 From: XiaLuNV <110973296+XiaLuNV@users.noreply.github.com> Date: Tue, 11 Mar 2025 14:30:26 +0800 Subject: [PATCH 436/462] enhance test_flags.py (#640) * enhance test_flags.py --- tests/integration/test_flags.py | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/tests/integration/test_flags.py b/tests/integration/test_flags.py index f041e918d4..1995dd2e9b 100644 --- a/tests/integration/test_flags.py +++ b/tests/integration/test_flags.py @@ -113,6 +113,17 @@ def test_non_writeable(self): with pytest.raises(ValueError, match="not writeable"): arr[0, 0] = 12 + def test_flags(self) -> None: + arr = num.zeros(shape=DIM_CASE) + np_arr = np.zeros(shape=DIM_CASE) + arr.flags.writeable = True + np_arr.flags.writeable = True + assert arr.flags.writeable == np_arr.flags.writeable + + arr.flags.aligned = True + np_arr.flags.aligned = True + assert arr.flags.aligned == np_arr.flags.aligned + def test_cannot_make_nonwriteable_writeable(self): arr = num.zeros(shape=DIM_CASE) arr.flags["W"] = False From 079679b0ad56e9bf6589adef84efcb66ec092765 Mon Sep 17 00:00:00 2001 From: Jacob Faibussowitsch Date: Tue, 11 Mar 2025 10:50:44 -0400 Subject: [PATCH 437/462] Bump to 25.05 (#643) --- CMakeLists.txt | 2 +- cmake/versions.json | 4 ++-- docs/cupynumeric/switcher.json | 7 ++++++- scripts/build/python/cupynumeric/CMakeLists.txt | 2 +- 4 files changed, 10 insertions(+), 5 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index c52c5eb224..866be2eab4 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -41,7 +41,7 @@ include(rapids-cuda) include(rapids-export) include(rapids-find) -set(cupynumeric_version 25.03.00) +set(cupynumeric_version 25.05.00) # For now we want the optimization flags to match on both normal make and cmake # builds so we override the cmake defaults here for release, this changes diff --git a/cmake/versions.json b/cmake/versions.json index c6cd348e46..9d97adfb36 100644 --- a/cmake/versions.json +++ b/cmake/versions.json @@ -6,11 +6,11 @@ "org": "nv-legate", "artifact_workflow": "ci-gh.yml", "nightly_workflow": "ci-gh-nightly-release.yml", - "version": "25.03.00", + "version": "25.05.00", "git_url" : "git@github.com:nv-legate/legate.internal.git", "git_shallow": false, "always_download": false, - "git_tag" : "6fd728b90e25cddbc450e8dbc63f4bcf077a0fb0", + "git_tag" : "37d52d7c7d0a6fa8c27224115334c97daf6f7cb7", "anaconda_label": "experimental" } } diff --git a/docs/cupynumeric/switcher.json b/docs/cupynumeric/switcher.json index 257436bb46..7d049e3dd7 100644 --- a/docs/cupynumeric/switcher.json +++ b/docs/cupynumeric/switcher.json @@ -12,7 +12,12 @@ { "name": "25.03", "version": "25.03", - "preferred": true, "url": "https://docs.nvidia.com/cupynumeric/25.03/" + }, + { + "name": "25.05", + "version": "25.05", + "preferred": true, + "url": "https://docs.nvidia.com/cupynumeric/25.05/" } ] diff --git a/scripts/build/python/cupynumeric/CMakeLists.txt b/scripts/build/python/cupynumeric/CMakeLists.txt index 9fa56b1043..ceb4fdc79e 100644 --- a/scripts/build/python/cupynumeric/CMakeLists.txt +++ b/scripts/build/python/cupynumeric/CMakeLists.txt @@ -12,7 +12,7 @@ cmake_minimum_required(VERSION 3.26.4) -project(cupynumeric-python VERSION 25.03.00 LANGUAGES CXX) +project(cupynumeric-python VERSION 25.05.00 LANGUAGES CXX) set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${PROJECT_BINARY_DIR}/lib) set(CUPYNUMERIC_BUILD_PIP_WHEELS ON) From da8c96f621f3767282b263008ff9b0f41e90f3c4 Mon Sep 17 00:00:00 2001 From: Jacob Faibussowitsch Date: Tue, 11 Mar 2025 12:55:16 -0400 Subject: [PATCH 438/462] Add TaskConfig support (#641) * Add TaskConfig support, see https://github.com/nv-legate/legate.internal/pull/1895 --- .github/workflows/pr.yml | 4 +- cmake/thirdparty/get_openblas.cmake | 16 +- src/cupynumeric/binary/binary_op.cc | 5 +- src/cupynumeric/binary/binary_op.h | 3 +- src/cupynumeric/binary/binary_red.cc | 6 +- src/cupynumeric/binary/binary_red.h | 3 +- src/cupynumeric/bits/packbits.cc | 5 +- src/cupynumeric/bits/packbits.h | 3 +- src/cupynumeric/bits/unpackbits.cc | 6 +- src/cupynumeric/bits/unpackbits.h | 3 +- src/cupynumeric/convolution/convolve.cc | 5 +- src/cupynumeric/convolution/convolve.h | 3 +- src/cupynumeric/cudalibs.cu | 12 +- src/cupynumeric/fft/fft.cu | 5 +- src/cupynumeric/fft/fft.h | 2 +- src/cupynumeric/index/advanced_indexing.cc | 6 +- src/cupynumeric/index/advanced_indexing.h | 3 +- src/cupynumeric/index/choose.cc | 5 +- src/cupynumeric/index/choose.h | 3 +- src/cupynumeric/index/putmask.cc | 5 +- src/cupynumeric/index/putmask.h | 3 +- src/cupynumeric/index/repeat.cc | 5 +- src/cupynumeric/index/repeat.h | 3 +- src/cupynumeric/index/select.cc | 5 +- src/cupynumeric/index/select.h | 3 +- src/cupynumeric/index/wrap.cc | 5 +- src/cupynumeric/index/wrap.h | 2 +- src/cupynumeric/index/zip.cc | 5 +- src/cupynumeric/index/zip.h | 2 +- src/cupynumeric/item/read.cc | 5 +- src/cupynumeric/item/read.h | 2 +- src/cupynumeric/item/write.cc | 5 +- src/cupynumeric/item/write.h | 2 +- src/cupynumeric/matrix/batched_cholesky.cc | 6 +- src/cupynumeric/matrix/batched_cholesky.h | 3 +- src/cupynumeric/matrix/contract.cc | 5 +- src/cupynumeric/matrix/contract.h | 3 +- src/cupynumeric/matrix/diag.cc | 5 +- src/cupynumeric/matrix/diag.h | 2 +- src/cupynumeric/matrix/dot.cc | 5 +- src/cupynumeric/matrix/dot.h | 2 +- src/cupynumeric/matrix/geev.cc | 5 +- src/cupynumeric/matrix/geev.h | 2 +- src/cupynumeric/matrix/gemm.cc | 5 +- src/cupynumeric/matrix/gemm.h | 2 +- src/cupynumeric/matrix/matmul.cc | 5 +- src/cupynumeric/matrix/matmul.h | 3 +- src/cupynumeric/matrix/matvecmul.cc | 6 +- src/cupynumeric/matrix/matvecmul.h | 3 +- src/cupynumeric/matrix/mp_potrf.cu | 5 +- src/cupynumeric/matrix/mp_potrf.h | 3 +- src/cupynumeric/matrix/mp_solve.cu | 5 +- src/cupynumeric/matrix/mp_solve.h | 3 +- src/cupynumeric/matrix/potrf.cc | 5 +- src/cupynumeric/matrix/potrf.h | 2 +- src/cupynumeric/matrix/qr.cc | 5 +- src/cupynumeric/matrix/qr.h | 2 +- src/cupynumeric/matrix/solve.cc | 5 +- src/cupynumeric/matrix/solve.h | 2 +- src/cupynumeric/matrix/svd.cc | 5 +- src/cupynumeric/matrix/svd.h | 2 +- src/cupynumeric/matrix/syrk.cc | 5 +- src/cupynumeric/matrix/syrk.h | 2 +- src/cupynumeric/matrix/tile.cc | 5 +- src/cupynumeric/matrix/tile.h | 2 +- src/cupynumeric/matrix/transpose.cc | 6 +- src/cupynumeric/matrix/transpose.h | 3 +- src/cupynumeric/matrix/trilu.cc | 5 +- src/cupynumeric/matrix/trilu.h | 2 +- src/cupynumeric/matrix/trsm.cc | 5 +- src/cupynumeric/matrix/trsm.h | 2 +- src/cupynumeric/ndarray.cc | 2 +- src/cupynumeric/ndarray.h | 2 +- src/cupynumeric/nullary/arange.cc | 5 +- src/cupynumeric/nullary/arange.h | 3 +- src/cupynumeric/nullary/eye.cc | 5 +- src/cupynumeric/nullary/eye.h | 2 +- src/cupynumeric/nullary/fill.cc | 5 +- src/cupynumeric/nullary/fill.h | 2 +- src/cupynumeric/nullary/window.cc | 5 +- src/cupynumeric/nullary/window.h | 3 +- src/cupynumeric/operators.cc | 2 +- src/cupynumeric/random/bitgenerator.cc | 6 +- src/cupynumeric/random/bitgenerator.h | 3 +- src/cupynumeric/random/rand.cc | 5 +- src/cupynumeric/random/rand.h | 2 +- src/cupynumeric/scan/scan_global.cc | 6 +- src/cupynumeric/scan/scan_global.h | 3 +- src/cupynumeric/scan/scan_local.cc | 6 +- src/cupynumeric/scan/scan_local.h | 3 +- src/cupynumeric/search/argwhere.cc | 5 +- src/cupynumeric/search/argwhere.h | 3 +- src/cupynumeric/search/nonzero.cc | 5 +- src/cupynumeric/search/nonzero.h | 3 +- src/cupynumeric/set/unique.cc | 5 +- src/cupynumeric/set/unique.h | 3 +- src/cupynumeric/set/unique_reduce.cc | 6 +- src/cupynumeric/set/unique_reduce.h | 3 +- src/cupynumeric/sort/searchsorted.cc | 6 +- src/cupynumeric/sort/searchsorted.h | 3 +- src/cupynumeric/sort/sort.cc | 5 +- src/cupynumeric/sort/sort.h | 2 +- src/cupynumeric/stat/bincount.cc | 5 +- src/cupynumeric/stat/bincount.h | 3 +- src/cupynumeric/stat/histogram.cc | 6 +- src/cupynumeric/stat/histogram.h | 3 +- src/cupynumeric/ternary/where.cc | 5 +- src/cupynumeric/ternary/where.h | 2 +- src/cupynumeric/transform/flip.cc | 5 +- src/cupynumeric/transform/flip.h | 2 +- src/cupynumeric/unary/convert.cc | 5 +- src/cupynumeric/unary/convert.h | 3 +- src/cupynumeric/unary/scalar_unary_red.cc | 6 +- src/cupynumeric/unary/scalar_unary_red.h | 3 +- src/cupynumeric/unary/unary_op.cc | 5 +- src/cupynumeric/unary/unary_op.h | 3 +- src/cupynumeric/unary/unary_red.cc | 5 +- src/cupynumeric/unary/unary_red.h | 3 +- tests/cpp/integration/common_utils.h | 10 +- tests/cpp/integration/test_amax.cc | 212 ++++++++++----------- tests/cpp/integration/test_amin.cc | 212 ++++++++++----------- tests/cpp/integration/test_dot.cc | 52 ++--- tests/cpp/integration/test_repartition.cc | 4 +- tests/cpp/integration/test_reshape.cc | 12 +- tests/cpp/integration/test_squeeze.cc | 20 +- tests/cpp/integration/test_unique.cc | 6 +- tests/cpp/integration/test_where.cc | 36 ++-- tests/cpp/integration/test_window.cc | 6 +- 128 files changed, 610 insertions(+), 436 deletions(-) diff --git a/.github/workflows/pr.yml b/.github/workflows/pr.yml index 2a6964b1c6..22ef63c648 100644 --- a/.github/workflows/pr.yml +++ b/.github/workflows/pr.yml @@ -23,7 +23,7 @@ jobs: build-type: pull-request # TODO(cryos): https://github.com/nv-legate/legate.internal/issues/1893 # Remove this once we have uploads to PyPi. - legate-sha: 546f767a7e6c54470f4ce77a2b5449f7ffe1b2a0 + legate-sha: 37d52d7c7d0a6fa8c27224115334c97daf6f7cb7 wheels-test: needs: wheels-build secrets: inherit @@ -32,4 +32,4 @@ jobs: build-type: pull-request # TODO(cryos): https://github.com/nv-legate/legate.internal/issues/1893 # Remove this once we have uploads to PyPi. - legate-sha: 546f767a7e6c54470f4ce77a2b5449f7ffe1b2a0 + legate-sha: 37d52d7c7d0a6fa8c27224115334c97daf6f7cb7 diff --git a/cmake/thirdparty/get_openblas.cmake b/cmake/thirdparty/get_openblas.cmake index 6c0212eaf0..b384ecf024 100644 --- a/cmake/thirdparty/get_openblas.cmake +++ b/cmake/thirdparty/get_openblas.cmake @@ -55,11 +55,19 @@ function(find_or_configure_OpenBLAS) if(CMAKE_SYSTEM_PROCESSOR STREQUAL "aarch64" OR CMAKE_SYSTEM_PROCESSOR STREQUAL "arm64") set(_target ARMV8) endif() - + + # BLAS emits a bunch of warnings, -w is the "silence all warnings" flag for clang and + # GCC + if(MSVC) + message(FATAL_ERROR "Don't know how to silence warnings with MSVC") + endif() + set(c_flags "${CMAKE_C_FLAGS} -w") + set(f_flags "${CMAKE_Fortran_FLAGS} -w") rapids_cpm_find(BLAS ${FIND_PKG_ARGS} CPM_ARGS ${BLAS_cpm_git_args} EXCLUDE_FROM_ALL ${PKG_EXCLUDE_FROM_ALL} + SYSTEM TRUE OPTIONS "USE_CUDA 0" "C_LAPACK ON" "USE_THREAD ON" @@ -69,7 +77,9 @@ function(find_or_configure_OpenBLAS) "BUILD_WITHOUT_LAPACK OFF" "INTERFACE64 ${INTERFACE64}" "TARGET ${_target}" - "USE_OPENMP ${Legion_USE_OpenMP}") + "USE_OPENMP ${Legion_USE_OpenMP}" + "CMAKE_C_FLAGS ${c_flags}" + "CMAKE_Fortran_FLAGS ${f_flags}") set(CMAKE_POLICY_DEFAULT_CMP0048 ${CMP0048_orig}) set(CMAKE_POLICY_DEFAULT_CMP0054 ${CMP0054_orig}) @@ -96,7 +106,7 @@ function(find_or_configure_OpenBLAS) $ # contains cblas.h and f77blas.h $ - ) + ) string(JOIN "\n" code_string "if(NOT TARGET BLAS::BLAS)" diff --git a/src/cupynumeric/binary/binary_op.cc b/src/cupynumeric/binary/binary_op.cc index f72ca85204..e8a271b729 100644 --- a/src/cupynumeric/binary/binary_op.cc +++ b/src/cupynumeric/binary/binary_op.cc @@ -60,7 +60,10 @@ struct BinaryOpImplBody { namespace // unnamed { -static void __attribute__((constructor)) register_tasks(void) { BinaryOpTask::register_variants(); } +static const auto cupynumeric_reg_task_ = []() -> char { + BinaryOpTask::register_variants(); + return 0; +}(); } // namespace } // namespace cupynumeric diff --git a/src/cupynumeric/binary/binary_op.h b/src/cupynumeric/binary/binary_op.h index 2088a2f4da..34ac087835 100644 --- a/src/cupynumeric/binary/binary_op.h +++ b/src/cupynumeric/binary/binary_op.h @@ -31,7 +31,8 @@ struct BinaryOpArgs { class BinaryOpTask : public CuPyNumericTask { public: - static constexpr auto TASK_ID = legate::LocalTaskID{CUPYNUMERIC_BINARY_OP}; + static inline const auto TASK_CONFIG = + legate::TaskConfig{legate::LocalTaskID{CUPYNUMERIC_BINARY_OP}}; public: static void cpu_variant(legate::TaskContext context); diff --git a/src/cupynumeric/binary/binary_red.cc b/src/cupynumeric/binary/binary_red.cc index dbd9cf87ac..9e2f0d0df0 100644 --- a/src/cupynumeric/binary/binary_red.cc +++ b/src/cupynumeric/binary/binary_red.cc @@ -66,10 +66,10 @@ struct BinaryRedImplBody { namespace // unnamed { -static void __attribute__((constructor)) register_tasks(void) -{ +const auto reg_ = []() -> char { BinaryRedTask::register_variants(); -} + return 0; +}(); } // namespace } // namespace cupynumeric diff --git a/src/cupynumeric/binary/binary_red.h b/src/cupynumeric/binary/binary_red.h index 3d24157485..906300e95b 100644 --- a/src/cupynumeric/binary/binary_red.h +++ b/src/cupynumeric/binary/binary_red.h @@ -31,7 +31,8 @@ struct BinaryRedArgs { class BinaryRedTask : public CuPyNumericTask { public: - static constexpr auto TASK_ID = legate::LocalTaskID{CUPYNUMERIC_BINARY_RED}; + static inline const auto TASK_CONFIG = + legate::TaskConfig{legate::LocalTaskID{CUPYNUMERIC_BINARY_RED}}; static constexpr auto GPU_VARIANT_OPTIONS = legate::VariantOptions{}.with_has_allocations(true); diff --git a/src/cupynumeric/bits/packbits.cc b/src/cupynumeric/bits/packbits.cc index 2f48903ce5..f563a5d4fe 100644 --- a/src/cupynumeric/bits/packbits.cc +++ b/src/cupynumeric/bits/packbits.cc @@ -57,7 +57,10 @@ struct PackbitsImplBody { namespace // unnamed { -static void __attribute__((constructor)) register_tasks(void) { PackbitsTask::register_variants(); } +static const auto cupynumeric_reg_task_ = []() -> char { + PackbitsTask::register_variants(); + return 0; +}(); } // namespace } // namespace cupynumeric diff --git a/src/cupynumeric/bits/packbits.h b/src/cupynumeric/bits/packbits.h index de286b4233..6d32bef8a1 100644 --- a/src/cupynumeric/bits/packbits.h +++ b/src/cupynumeric/bits/packbits.h @@ -103,7 +103,8 @@ struct Pack { class PackbitsTask : public CuPyNumericTask { public: - static constexpr auto TASK_ID = legate::LocalTaskID{CUPYNUMERIC_PACKBITS}; + static inline const auto TASK_CONFIG = + legate::TaskConfig{legate::LocalTaskID{CUPYNUMERIC_PACKBITS}}; public: static void cpu_variant(legate::TaskContext context); diff --git a/src/cupynumeric/bits/unpackbits.cc b/src/cupynumeric/bits/unpackbits.cc index 4b5728c6eb..7454c1f56b 100644 --- a/src/cupynumeric/bits/unpackbits.cc +++ b/src/cupynumeric/bits/unpackbits.cc @@ -45,10 +45,10 @@ struct UnpackbitsImplBody { namespace // unnamed { -static void __attribute__((constructor)) register_tasks(void) -{ +const auto reg_ = []() -> char { UnpackbitsTask::register_variants(); -} + return 0; +}(); } // namespace } // namespace cupynumeric diff --git a/src/cupynumeric/bits/unpackbits.h b/src/cupynumeric/bits/unpackbits.h index f010e9fc8e..92061ae43b 100644 --- a/src/cupynumeric/bits/unpackbits.h +++ b/src/cupynumeric/bits/unpackbits.h @@ -60,7 +60,8 @@ struct Unpack { class UnpackbitsTask : public CuPyNumericTask { public: - static constexpr auto TASK_ID = legate::LocalTaskID{CUPYNUMERIC_UNPACKBITS}; + static inline const auto TASK_CONFIG = + legate::TaskConfig{legate::LocalTaskID{CUPYNUMERIC_UNPACKBITS}}; public: static void cpu_variant(legate::TaskContext context); diff --git a/src/cupynumeric/convolution/convolve.cc b/src/cupynumeric/convolution/convolve.cc index b86364d758..9335606175 100644 --- a/src/cupynumeric/convolution/convolve.cc +++ b/src/cupynumeric/convolution/convolve.cc @@ -273,7 +273,10 @@ struct ConvolveImplBody { namespace // unnamed { -static void __attribute__((constructor)) register_tasks(void) { ConvolveTask::register_variants(); } +static const auto cupynumeric_reg_task_ = []() -> char { + ConvolveTask::register_variants(); + return 0; +}(); } // namespace } // namespace cupynumeric diff --git a/src/cupynumeric/convolution/convolve.h b/src/cupynumeric/convolution/convolve.h index 3515c2714e..1e2707dd45 100644 --- a/src/cupynumeric/convolution/convolve.h +++ b/src/cupynumeric/convolution/convolve.h @@ -39,7 +39,8 @@ struct ConvolveArgs { class ConvolveTask : public CuPyNumericTask { public: - static constexpr auto TASK_ID = legate::LocalTaskID{CUPYNUMERIC_CONVOLVE}; + static inline const auto TASK_CONFIG = + legate::TaskConfig{legate::LocalTaskID{CUPYNUMERIC_CONVOLVE}}; static constexpr auto GPU_VARIANT_OPTIONS = legate::VariantOptions{}.with_has_allocations(true); diff --git a/src/cupynumeric/cudalibs.cu b/src/cupynumeric/cudalibs.cu index 2dec1d7dbc..d2b06cc221 100644 --- a/src/cupynumeric/cudalibs.cu +++ b/src/cupynumeric/cudalibs.cu @@ -531,7 +531,8 @@ int get_device_ordinal() class LoadCUDALibsTask : public CuPyNumericTask { public: - static constexpr auto TASK_ID = legate::LocalTaskID{CUPYNUMERIC_LOAD_CUDALIBS}; + static inline const auto TASK_CONFIG = + legate::TaskConfig{legate::LocalTaskID{CUPYNUMERIC_LOAD_CUDALIBS}}; public: static void gpu_variant(legate::TaskContext context) @@ -550,7 +551,8 @@ class LoadCUDALibsTask : public CuPyNumericTask { class UnloadCUDALibsTask : public CuPyNumericTask { public: - static constexpr auto TASK_ID = legate::LocalTaskID{CUPYNUMERIC_UNLOAD_CUDALIBS}; + static inline const auto TASK_CONFIG = + legate::TaskConfig{legate::LocalTaskID{CUPYNUMERIC_UNLOAD_CUDALIBS}}; public: static void gpu_variant(legate::TaskContext context) @@ -564,11 +566,11 @@ class UnloadCUDALibsTask : public CuPyNumericTask { } }; -static void __attribute__((constructor)) register_tasks(void) -{ +const auto reg_ = []() -> char { LoadCUDALibsTask::register_variants(); UnloadCUDALibsTask::register_variants(); -} + return 0; +}(); } // namespace cupynumeric diff --git a/src/cupynumeric/fft/fft.cu b/src/cupynumeric/fft/fft.cu index d7bbbfffd1..f00261b568 100644 --- a/src/cupynumeric/fft/fft.cu +++ b/src/cupynumeric/fft/fft.cu @@ -377,7 +377,10 @@ struct FFTImplBody { namespace // unnamed { -static void __attribute__((constructor)) register_tasks(void) { FFTTask::register_variants(); } +static const auto cupynumeric_reg_task_ = []() -> char { + FFTTask::register_variants(); + return 0; +}(); } // namespace } // namespace cupynumeric diff --git a/src/cupynumeric/fft/fft.h b/src/cupynumeric/fft/fft.h index a5e6d4dd69..938fb960c0 100644 --- a/src/cupynumeric/fft/fft.h +++ b/src/cupynumeric/fft/fft.h @@ -32,7 +32,7 @@ struct FFTArgs { class FFTTask : public CuPyNumericTask { public: - static constexpr auto TASK_ID = legate::LocalTaskID{CUPYNUMERIC_FFT}; + static inline const auto TASK_CONFIG = legate::TaskConfig{legate::LocalTaskID{CUPYNUMERIC_FFT}}; static constexpr auto GPU_VARIANT_OPTIONS = legate::VariantOptions{}.with_has_allocations(true); diff --git a/src/cupynumeric/index/advanced_indexing.cc b/src/cupynumeric/index/advanced_indexing.cc index 9fe3ceb0bb..a186b06da4 100644 --- a/src/cupynumeric/index/advanced_indexing.cc +++ b/src/cupynumeric/index/advanced_indexing.cc @@ -111,10 +111,10 @@ struct AdvancedIndexingImplBody { namespace // unnamed { -static void __attribute__((constructor)) register_tasks(void) -{ +const auto reg_ = []() -> char { AdvancedIndexingTask::register_variants(); -} + return 0; +}(); } // namespace } // namespace cupynumeric diff --git a/src/cupynumeric/index/advanced_indexing.h b/src/cupynumeric/index/advanced_indexing.h index d75100e465..5e2d591640 100644 --- a/src/cupynumeric/index/advanced_indexing.h +++ b/src/cupynumeric/index/advanced_indexing.h @@ -30,7 +30,8 @@ struct AdvancedIndexingArgs { class AdvancedIndexingTask : public CuPyNumericTask { public: - static constexpr auto TASK_ID = legate::LocalTaskID{CUPYNUMERIC_ADVANCED_INDEXING}; + static inline const auto TASK_CONFIG = + legate::TaskConfig{legate::LocalTaskID{CUPYNUMERIC_ADVANCED_INDEXING}}; static constexpr auto CPU_VARIANT_OPTIONS = legate::VariantOptions{}.with_has_allocations(true); static constexpr auto GPU_VARIANT_OPTIONS = legate::VariantOptions{}.with_has_allocations(true); diff --git a/src/cupynumeric/index/choose.cc b/src/cupynumeric/index/choose.cc index 02b395f7bc..6f63bf989b 100644 --- a/src/cupynumeric/index/choose.cc +++ b/src/cupynumeric/index/choose.cc @@ -62,7 +62,10 @@ struct ChooseImplBody { namespace // unnamed { -static void __attribute__((constructor)) register_tasks(void) { ChooseTask::register_variants(); } +static const auto cupynumeric_reg_task_ = []() -> char { + ChooseTask::register_variants(); + return 0; +}(); } // namespace } // namespace cupynumeric diff --git a/src/cupynumeric/index/choose.h b/src/cupynumeric/index/choose.h index 930c357db1..eaa0e9177d 100644 --- a/src/cupynumeric/index/choose.h +++ b/src/cupynumeric/index/choose.h @@ -27,7 +27,8 @@ struct ChooseArgs { class ChooseTask : public CuPyNumericTask { public: - static constexpr auto TASK_ID = legate::LocalTaskID{CUPYNUMERIC_CHOOSE}; + static inline const auto TASK_CONFIG = + legate::TaskConfig{legate::LocalTaskID{CUPYNUMERIC_CHOOSE}}; static constexpr auto GPU_VARIANT_OPTIONS = legate::VariantOptions{}.with_has_allocations(true); diff --git a/src/cupynumeric/index/putmask.cc b/src/cupynumeric/index/putmask.cc index b5a267c09f..7c7e6b2908 100644 --- a/src/cupynumeric/index/putmask.cc +++ b/src/cupynumeric/index/putmask.cc @@ -26,7 +26,10 @@ namespace cupynumeric { namespace // unnamed { -static void __attribute__((constructor)) register_tasks(void) { PutmaskTask::register_variants(); } +static const auto cupynumeric_reg_task_ = []() -> char { + PutmaskTask::register_variants(); + return 0; +}(); } // namespace } // namespace cupynumeric diff --git a/src/cupynumeric/index/putmask.h b/src/cupynumeric/index/putmask.h index 0a0fc17e9a..9827a7da06 100644 --- a/src/cupynumeric/index/putmask.h +++ b/src/cupynumeric/index/putmask.h @@ -28,7 +28,8 @@ struct PutmaskArgs { class PutmaskTask : public CuPyNumericTask { public: - static constexpr auto TASK_ID = legate::LocalTaskID{CUPYNUMERIC_PUTMASK}; + static inline const auto TASK_CONFIG = + legate::TaskConfig{legate::LocalTaskID{CUPYNUMERIC_PUTMASK}}; public: static void cpu_variant(legate::TaskContext context); diff --git a/src/cupynumeric/index/repeat.cc b/src/cupynumeric/index/repeat.cc index b763919d98..cf16231423 100644 --- a/src/cupynumeric/index/repeat.cc +++ b/src/cupynumeric/index/repeat.cc @@ -119,7 +119,10 @@ struct RepeatImplBody { namespace // unnamed { -static void __attribute__((constructor)) register_tasks(void) { RepeatTask::register_variants(); } +static const auto cupynumeric_reg_task_ = []() -> char { + RepeatTask::register_variants(); + return 0; +}(); } // namespace } // namespace cupynumeric diff --git a/src/cupynumeric/index/repeat.h b/src/cupynumeric/index/repeat.h index 23f15d423f..aadb48f406 100644 --- a/src/cupynumeric/index/repeat.h +++ b/src/cupynumeric/index/repeat.h @@ -31,7 +31,8 @@ struct RepeatArgs { class RepeatTask : public CuPyNumericTask { public: - static constexpr auto TASK_ID = legate::LocalTaskID{CUPYNUMERIC_REPEAT}; + static inline const auto TASK_CONFIG = + legate::TaskConfig{legate::LocalTaskID{CUPYNUMERIC_REPEAT}}; static constexpr auto CPU_VARIANT_OPTIONS = legate::VariantOptions{}.with_has_allocations(true); static constexpr auto GPU_VARIANT_OPTIONS = legate::VariantOptions{}.with_has_allocations(true); diff --git a/src/cupynumeric/index/select.cc b/src/cupynumeric/index/select.cc index 9b56763310..56f28d7545 100644 --- a/src/cupynumeric/index/select.cc +++ b/src/cupynumeric/index/select.cc @@ -77,7 +77,10 @@ struct SelectImplBody { namespace // unnamed { -static void __attribute__((constructor)) register_tasks(void) { SelectTask::register_variants(); } +static const auto cupynumeric_reg_task_ = []() -> char { + SelectTask::register_variants(); + return 0; +}(); } // namespace } // namespace cupynumeric diff --git a/src/cupynumeric/index/select.h b/src/cupynumeric/index/select.h index 11ae5b00dc..7f210dce36 100644 --- a/src/cupynumeric/index/select.h +++ b/src/cupynumeric/index/select.h @@ -28,7 +28,8 @@ struct SelectArgs { class SelectTask : public CuPyNumericTask { public: - static constexpr auto TASK_ID = legate::LocalTaskID{CUPYNUMERIC_SELECT}; + static inline const auto TASK_CONFIG = + legate::TaskConfig{legate::LocalTaskID{CUPYNUMERIC_SELECT}}; static constexpr auto GPU_VARIANT_OPTIONS = legate::VariantOptions{}.with_has_allocations(true); diff --git a/src/cupynumeric/index/wrap.cc b/src/cupynumeric/index/wrap.cc index 3794d1e6ff..1b768c34af 100644 --- a/src/cupynumeric/index/wrap.cc +++ b/src/cupynumeric/index/wrap.cc @@ -66,7 +66,10 @@ struct WrapImplBody { namespace // unnamed { -static void __attribute__((constructor)) register_tasks(void) { WrapTask::register_variants(); } +static const auto cupynumeric_reg_task_ = []() -> char { + WrapTask::register_variants(); + return 0; +}(); } // namespace } // namespace cupynumeric diff --git a/src/cupynumeric/index/wrap.h b/src/cupynumeric/index/wrap.h index 472b07cb8c..91f22c7df8 100644 --- a/src/cupynumeric/index/wrap.h +++ b/src/cupynumeric/index/wrap.h @@ -32,7 +32,7 @@ struct WrapArgs { class WrapTask : public CuPyNumericTask { public: - static constexpr auto TASK_ID = legate::LocalTaskID{CUPYNUMERIC_WRAP}; + static inline const auto TASK_CONFIG = legate::TaskConfig{legate::LocalTaskID{CUPYNUMERIC_WRAP}}; static constexpr auto GPU_VARIANT_OPTIONS = legate::VariantOptions{}.with_has_allocations(true); diff --git a/src/cupynumeric/index/zip.cc b/src/cupynumeric/index/zip.cc index b9cf0969ce..0da3d0ec44 100644 --- a/src/cupynumeric/index/zip.cc +++ b/src/cupynumeric/index/zip.cc @@ -89,7 +89,10 @@ struct ZipImplBody { namespace // unnamed { -static void __attribute__((constructor)) register_tasks(void) { ZipTask::register_variants(); } +static const auto cupynumeric_reg_task_ = []() -> char { + ZipTask::register_variants(); + return 0; +}(); } // namespace } // namespace cupynumeric diff --git a/src/cupynumeric/index/zip.h b/src/cupynumeric/index/zip.h index 34e9305a39..967f401ec6 100644 --- a/src/cupynumeric/index/zip.h +++ b/src/cupynumeric/index/zip.h @@ -31,7 +31,7 @@ struct ZipArgs { class ZipTask : public CuPyNumericTask { public: - static constexpr auto TASK_ID = legate::LocalTaskID{CUPYNUMERIC_ZIP}; + static inline const auto TASK_CONFIG = legate::TaskConfig{legate::LocalTaskID{CUPYNUMERIC_ZIP}}; static constexpr auto GPU_VARIANT_OPTIONS = legate::VariantOptions{}.with_has_allocations(true); diff --git a/src/cupynumeric/item/read.cc b/src/cupynumeric/item/read.cc index 4ee28d66f1..f3cb8c2a85 100644 --- a/src/cupynumeric/item/read.cc +++ b/src/cupynumeric/item/read.cc @@ -33,7 +33,10 @@ struct ReadImplBody { namespace // unnamed { -static void __attribute__((constructor)) register_tasks(void) { ReadTask::register_variants(); } +static const auto cupynumeric_reg_task_ = []() -> char { + ReadTask::register_variants(); + return 0; +}(); } // namespace } // namespace cupynumeric diff --git a/src/cupynumeric/item/read.h b/src/cupynumeric/item/read.h index 8820d3ae14..1541736e60 100644 --- a/src/cupynumeric/item/read.h +++ b/src/cupynumeric/item/read.h @@ -22,7 +22,7 @@ namespace cupynumeric { class ReadTask : public CuPyNumericTask { public: - static constexpr auto TASK_ID = legate::LocalTaskID{CUPYNUMERIC_READ}; + static inline const auto TASK_CONFIG = legate::TaskConfig{legate::LocalTaskID{CUPYNUMERIC_READ}}; public: static void cpu_variant(legate::TaskContext context); diff --git a/src/cupynumeric/item/write.cc b/src/cupynumeric/item/write.cc index fd2049fb4d..4ec987ea5f 100644 --- a/src/cupynumeric/item/write.cc +++ b/src/cupynumeric/item/write.cc @@ -36,7 +36,10 @@ struct WriteImplBody { namespace // unnamed { -static void __attribute__((constructor)) register_tasks(void) { WriteTask::register_variants(); } +static const auto cupynumeric_reg_task_ = []() -> char { + WriteTask::register_variants(); + return 0; +}(); } // namespace } // namespace cupynumeric diff --git a/src/cupynumeric/item/write.h b/src/cupynumeric/item/write.h index 81be673ea3..f05994a798 100644 --- a/src/cupynumeric/item/write.h +++ b/src/cupynumeric/item/write.h @@ -22,7 +22,7 @@ namespace cupynumeric { class WriteTask : public CuPyNumericTask { public: - static constexpr auto TASK_ID = legate::LocalTaskID{CUPYNUMERIC_WRITE}; + static inline const auto TASK_CONFIG = legate::TaskConfig{legate::LocalTaskID{CUPYNUMERIC_WRITE}}; public: static void cpu_variant(legate::TaskContext context); diff --git a/src/cupynumeric/matrix/batched_cholesky.cc b/src/cupynumeric/matrix/batched_cholesky.cc index 2abc51e9fd..918d965a1e 100644 --- a/src/cupynumeric/matrix/batched_cholesky.cc +++ b/src/cupynumeric/matrix/batched_cholesky.cc @@ -78,10 +78,10 @@ struct BatchedTransposeImplBody { namespace // unnamed { -static void __attribute__((constructor)) register_tasks(void) -{ +const auto reg_ = []() -> char { BatchedCholeskyTask::register_variants(); -} + return 0; +}(); } // namespace } // namespace cupynumeric diff --git a/src/cupynumeric/matrix/batched_cholesky.h b/src/cupynumeric/matrix/batched_cholesky.h index 36192741c6..197d7b9f10 100644 --- a/src/cupynumeric/matrix/batched_cholesky.h +++ b/src/cupynumeric/matrix/batched_cholesky.h @@ -23,7 +23,8 @@ namespace cupynumeric { class BatchedCholeskyTask : public CuPyNumericTask { public: - static constexpr auto TASK_ID = legate::LocalTaskID{CUPYNUMERIC_BATCHED_CHOLESKY}; + static inline const auto TASK_CONFIG = + legate::TaskConfig{legate::LocalTaskID{CUPYNUMERIC_BATCHED_CHOLESKY}}; static constexpr auto GPU_VARIANT_OPTIONS = legate::VariantOptions{}.with_has_allocations(true); diff --git a/src/cupynumeric/matrix/contract.cc b/src/cupynumeric/matrix/contract.cc index bf58951757..e144908116 100644 --- a/src/cupynumeric/matrix/contract.cc +++ b/src/cupynumeric/matrix/contract.cc @@ -247,7 +247,10 @@ struct ContractImplBody { namespace // unnamed { -static void __attribute__((constructor)) register_tasks(void) { ContractTask::register_variants(); } +static const auto cupynumeric_reg_task_ = []() -> char { + ContractTask::register_variants(); + return 0; +}(); } // namespace } // namespace cupynumeric diff --git a/src/cupynumeric/matrix/contract.h b/src/cupynumeric/matrix/contract.h index 12a0ba8afe..86873c2610 100644 --- a/src/cupynumeric/matrix/contract.h +++ b/src/cupynumeric/matrix/contract.h @@ -31,7 +31,8 @@ struct ContractArgs { class ContractTask : public CuPyNumericTask { public: - static constexpr auto TASK_ID = legate::LocalTaskID{CUPYNUMERIC_CONTRACT}; + static inline const auto TASK_CONFIG = + legate::TaskConfig{legate::LocalTaskID{CUPYNUMERIC_CONTRACT}}; static constexpr auto CPU_VARIANT_OPTIONS = legate::VariantOptions{}.with_has_allocations(true); static constexpr auto GPU_VARIANT_OPTIONS = legate::VariantOptions{}.with_has_allocations(true); diff --git a/src/cupynumeric/matrix/diag.cc b/src/cupynumeric/matrix/diag.cc index fefff3710d..9f189aa94c 100644 --- a/src/cupynumeric/matrix/diag.cc +++ b/src/cupynumeric/matrix/diag.cc @@ -79,7 +79,10 @@ struct DiagImplBody { namespace // unnamed { -static void __attribute__((constructor)) register_tasks(void) { DiagTask::register_variants(); } +static const auto cupynumeric_reg_task_ = []() -> char { + DiagTask::register_variants(); + return 0; +}(); } // namespace } // namespace cupynumeric diff --git a/src/cupynumeric/matrix/diag.h b/src/cupynumeric/matrix/diag.h index f80e40f7b3..eda14a003d 100644 --- a/src/cupynumeric/matrix/diag.h +++ b/src/cupynumeric/matrix/diag.h @@ -29,7 +29,7 @@ struct DiagArgs { class DiagTask : public CuPyNumericTask { public: - static constexpr auto TASK_ID = legate::LocalTaskID{CUPYNUMERIC_DIAG}; + static inline const auto TASK_CONFIG = legate::TaskConfig{legate::LocalTaskID{CUPYNUMERIC_DIAG}}; public: static void cpu_variant(legate::TaskContext context); diff --git a/src/cupynumeric/matrix/dot.cc b/src/cupynumeric/matrix/dot.cc index 7d8c73079e..eaf00cf46c 100644 --- a/src/cupynumeric/matrix/dot.cc +++ b/src/cupynumeric/matrix/dot.cc @@ -57,7 +57,10 @@ struct DotImplBody { namespace // unnamed { -static void __attribute__((constructor)) register_tasks(void) { DotTask::register_variants(); } +static const auto cupynumeric_reg_task_ = []() -> char { + DotTask::register_variants(); + return 0; +}(); } // namespace } // namespace cupynumeric diff --git a/src/cupynumeric/matrix/dot.h b/src/cupynumeric/matrix/dot.h index 34c938ca02..e4cf99c11f 100644 --- a/src/cupynumeric/matrix/dot.h +++ b/src/cupynumeric/matrix/dot.h @@ -28,7 +28,7 @@ struct DotArgs { class DotTask : public CuPyNumericTask { public: - static constexpr auto TASK_ID = legate::LocalTaskID{CUPYNUMERIC_DOT}; + static inline const auto TASK_CONFIG = legate::TaskConfig{legate::LocalTaskID{CUPYNUMERIC_DOT}}; static constexpr auto GPU_VARIANT_OPTIONS = legate::VariantOptions{}.with_has_allocations(true); diff --git a/src/cupynumeric/matrix/geev.cc b/src/cupynumeric/matrix/geev.cc index 6974c6ef16..08ba4ba68b 100644 --- a/src/cupynumeric/matrix/geev.cc +++ b/src/cupynumeric/matrix/geev.cc @@ -34,7 +34,10 @@ using namespace legate; namespace // unnamed { -static void __attribute__((constructor)) register_tasks(void) { GeevTask::register_variants(); } +static const auto cupynumeric_reg_task_ = []() -> char { + GeevTask::register_variants(); + return 0; +}(); } // namespace } // namespace cupynumeric \ No newline at end of file diff --git a/src/cupynumeric/matrix/geev.h b/src/cupynumeric/matrix/geev.h index 6c1365841e..0e7f0ffb7b 100644 --- a/src/cupynumeric/matrix/geev.h +++ b/src/cupynumeric/matrix/geev.h @@ -22,7 +22,7 @@ namespace cupynumeric { class GeevTask : public CuPyNumericTask { public: - static constexpr auto TASK_ID = legate::LocalTaskID{CUPYNUMERIC_GEEV}; + static inline const auto TASK_CONFIG = legate::TaskConfig{legate::LocalTaskID{CUPYNUMERIC_GEEV}}; static const char* ERROR_MESSAGE; static constexpr auto CPU_VARIANT_OPTIONS = legate::VariantOptions{}.with_has_allocations(true); diff --git a/src/cupynumeric/matrix/gemm.cc b/src/cupynumeric/matrix/gemm.cc index bff311f441..a707d78c97 100644 --- a/src/cupynumeric/matrix/gemm.cc +++ b/src/cupynumeric/matrix/gemm.cc @@ -107,7 +107,10 @@ struct GemmImplBody { namespace // unnamed { -static void __attribute__((constructor)) register_tasks(void) { GemmTask::register_variants(); } +static const auto cupynumeric_reg_task_ = []() -> char { + GemmTask::register_variants(); + return 0; +}(); } // namespace } // namespace cupynumeric diff --git a/src/cupynumeric/matrix/gemm.h b/src/cupynumeric/matrix/gemm.h index d66164bb2c..30e6e2798b 100644 --- a/src/cupynumeric/matrix/gemm.h +++ b/src/cupynumeric/matrix/gemm.h @@ -22,7 +22,7 @@ namespace cupynumeric { class GemmTask : public CuPyNumericTask { public: - static constexpr auto TASK_ID = legate::LocalTaskID{CUPYNUMERIC_GEMM}; + static inline const auto TASK_CONFIG = legate::TaskConfig{legate::LocalTaskID{CUPYNUMERIC_GEMM}}; public: static void cpu_variant(legate::TaskContext context); diff --git a/src/cupynumeric/matrix/matmul.cc b/src/cupynumeric/matrix/matmul.cc index 5d70282d23..d8cc635825 100644 --- a/src/cupynumeric/matrix/matmul.cc +++ b/src/cupynumeric/matrix/matmul.cc @@ -37,7 +37,10 @@ using namespace legate; namespace // unnamed { -static void __attribute__((constructor)) register_tasks(void) { MatMulTask::register_variants(); } +static const auto cupynumeric_reg_task_ = []() -> char { + MatMulTask::register_variants(); + return 0; +}(); } // namespace } // namespace cupynumeric diff --git a/src/cupynumeric/matrix/matmul.h b/src/cupynumeric/matrix/matmul.h index d27e1b98f1..695515e8c3 100644 --- a/src/cupynumeric/matrix/matmul.h +++ b/src/cupynumeric/matrix/matmul.h @@ -28,7 +28,8 @@ struct MatMulArgs { class MatMulTask : public CuPyNumericTask { public: - static constexpr auto TASK_ID = legate::LocalTaskID{CUPYNUMERIC_MATMUL}; + static inline const auto TASK_CONFIG = + legate::TaskConfig{legate::LocalTaskID{CUPYNUMERIC_MATMUL}}; // Only the CPU implementation needs temporary allocations due to lack of float16 support static constexpr auto CPU_VARIANT_OPTIONS = legate::VariantOptions{}.with_has_allocations(true); diff --git a/src/cupynumeric/matrix/matvecmul.cc b/src/cupynumeric/matrix/matvecmul.cc index 8e7ede3e7c..a56ab8eb65 100644 --- a/src/cupynumeric/matrix/matvecmul.cc +++ b/src/cupynumeric/matrix/matvecmul.cc @@ -37,10 +37,10 @@ using namespace legate; namespace // unnamed { -static void __attribute__((constructor)) register_tasks(void) -{ +const auto reg_ = []() -> char { MatVecMulTask::register_variants(); -} + return 0; +}(); } // namespace } // namespace cupynumeric diff --git a/src/cupynumeric/matrix/matvecmul.h b/src/cupynumeric/matrix/matvecmul.h index 8cb1ed23b1..607070a200 100644 --- a/src/cupynumeric/matrix/matvecmul.h +++ b/src/cupynumeric/matrix/matvecmul.h @@ -28,7 +28,8 @@ struct MatVecMulArgs { class MatVecMulTask : public CuPyNumericTask { public: - static constexpr auto TASK_ID = legate::LocalTaskID{CUPYNUMERIC_MATVECMUL}; + static inline const auto TASK_CONFIG = + legate::TaskConfig{legate::LocalTaskID{CUPYNUMERIC_MATVECMUL}}; // Only the CPU implementation needs temporary allocations due to lack of float16 support static constexpr auto CPU_VARIANT_OPTIONS = legate::VariantOptions{}.with_has_allocations(true); diff --git a/src/cupynumeric/matrix/mp_potrf.cu b/src/cupynumeric/matrix/mp_potrf.cu index 365c2d73ec..980bcce4e0 100644 --- a/src/cupynumeric/matrix/mp_potrf.cu +++ b/src/cupynumeric/matrix/mp_potrf.cu @@ -140,7 +140,10 @@ struct MpPotrfImplBody { namespace // unnamed { -static void __attribute__((constructor)) register_tasks(void) { MpPotrfTask::register_variants(); } +static const auto cupynumeric_reg_task_ = []() -> char { + MpPotrfTask::register_variants(); + return 0; +}(); } // namespace } // namespace cupynumeric \ No newline at end of file diff --git a/src/cupynumeric/matrix/mp_potrf.h b/src/cupynumeric/matrix/mp_potrf.h index dce7e38767..2ccedcef79 100644 --- a/src/cupynumeric/matrix/mp_potrf.h +++ b/src/cupynumeric/matrix/mp_potrf.h @@ -22,7 +22,8 @@ namespace cupynumeric { class MpPotrfTask : public CuPyNumericTask { public: - static constexpr auto TASK_ID = legate::LocalTaskID{CUPYNUMERIC_MP_POTRF}; + static inline const auto TASK_CONFIG = + legate::TaskConfig{legate::LocalTaskID{CUPYNUMERIC_MP_POTRF}}; public: #if LEGATE_DEFINED(LEGATE_USE_CUDA) diff --git a/src/cupynumeric/matrix/mp_solve.cu b/src/cupynumeric/matrix/mp_solve.cu index 3ca290a20c..6c3ac2f8c7 100644 --- a/src/cupynumeric/matrix/mp_solve.cu +++ b/src/cupynumeric/matrix/mp_solve.cu @@ -241,7 +241,10 @@ struct MpSolveImplBody { namespace // unnamed { -static void __attribute__((constructor)) register_tasks(void) { MpSolveTask::register_variants(); } +static const auto cupynumeric_reg_task_ = []() -> char { + MpSolveTask::register_variants(); + return 0; +}(); } // namespace } // namespace cupynumeric \ No newline at end of file diff --git a/src/cupynumeric/matrix/mp_solve.h b/src/cupynumeric/matrix/mp_solve.h index 436c4c50be..d858682cb1 100644 --- a/src/cupynumeric/matrix/mp_solve.h +++ b/src/cupynumeric/matrix/mp_solve.h @@ -22,7 +22,8 @@ namespace cupynumeric { class MpSolveTask : public CuPyNumericTask { public: - static constexpr auto TASK_ID = legate::LocalTaskID{CUPYNUMERIC_MP_SOLVE}; + static inline const auto TASK_CONFIG = + legate::TaskConfig{legate::LocalTaskID{CUPYNUMERIC_MP_SOLVE}}; public: #if LEGATE_DEFINED(LEGATE_USE_CUDA) diff --git a/src/cupynumeric/matrix/potrf.cc b/src/cupynumeric/matrix/potrf.cc index ee9d7b9971..6b724ff76d 100644 --- a/src/cupynumeric/matrix/potrf.cc +++ b/src/cupynumeric/matrix/potrf.cc @@ -86,7 +86,10 @@ void PotrfImplBody::operator()(complex namespace // unnamed { -static void __attribute__((constructor)) register_tasks(void) { PotrfTask::register_variants(); } +static const auto cupynumeric_reg_task_ = []() -> char { + PotrfTask::register_variants(); + return 0; +}(); } // namespace } // namespace cupynumeric diff --git a/src/cupynumeric/matrix/potrf.h b/src/cupynumeric/matrix/potrf.h index 1c6430a12b..3a8891f6e5 100644 --- a/src/cupynumeric/matrix/potrf.h +++ b/src/cupynumeric/matrix/potrf.h @@ -22,7 +22,7 @@ namespace cupynumeric { class PotrfTask : public CuPyNumericTask { public: - static constexpr auto TASK_ID = legate::LocalTaskID{CUPYNUMERIC_POTRF}; + static inline const auto TASK_CONFIG = legate::TaskConfig{legate::LocalTaskID{CUPYNUMERIC_POTRF}}; static constexpr auto GPU_VARIANT_OPTIONS = legate::VariantOptions{}.with_has_allocations(true); diff --git a/src/cupynumeric/matrix/qr.cc b/src/cupynumeric/matrix/qr.cc index 111b932391..4c31141713 100644 --- a/src/cupynumeric/matrix/qr.cc +++ b/src/cupynumeric/matrix/qr.cc @@ -34,7 +34,10 @@ using namespace legate; namespace // unnamed { -static void __attribute__((constructor)) register_tasks(void) { QrTask::register_variants(); } +static const auto cupynumeric_reg_task_ = []() -> char { + QrTask::register_variants(); + return 0; +}(); } // namespace } // namespace cupynumeric diff --git a/src/cupynumeric/matrix/qr.h b/src/cupynumeric/matrix/qr.h index f06a3c5828..f43865e2f8 100644 --- a/src/cupynumeric/matrix/qr.h +++ b/src/cupynumeric/matrix/qr.h @@ -22,7 +22,7 @@ namespace cupynumeric { class QrTask : public CuPyNumericTask { public: - static constexpr auto TASK_ID = legate::LocalTaskID{CUPYNUMERIC_QR}; + static inline const auto TASK_CONFIG = legate::TaskConfig{legate::LocalTaskID{CUPYNUMERIC_QR}}; static const char* ERROR_MESSAGE; static constexpr auto CPU_VARIANT_OPTIONS = legate::VariantOptions{}.with_has_allocations(true); diff --git a/src/cupynumeric/matrix/solve.cc b/src/cupynumeric/matrix/solve.cc index 2bb8778fb1..28c8b7e03c 100644 --- a/src/cupynumeric/matrix/solve.cc +++ b/src/cupynumeric/matrix/solve.cc @@ -34,7 +34,10 @@ using namespace legate; namespace // unnamed { -static void __attribute__((constructor)) register_tasks(void) { SolveTask::register_variants(); } +static const auto cupynumeric_reg_task_ = []() -> char { + SolveTask::register_variants(); + return 0; +}(); } // namespace } // namespace cupynumeric diff --git a/src/cupynumeric/matrix/solve.h b/src/cupynumeric/matrix/solve.h index a1f8072a5c..8f6a76e50f 100644 --- a/src/cupynumeric/matrix/solve.h +++ b/src/cupynumeric/matrix/solve.h @@ -22,7 +22,7 @@ namespace cupynumeric { class SolveTask : public CuPyNumericTask { public: - static constexpr auto TASK_ID = legate::LocalTaskID{CUPYNUMERIC_SOLVE}; + static inline const auto TASK_CONFIG = legate::TaskConfig{legate::LocalTaskID{CUPYNUMERIC_SOLVE}}; static const char* ERROR_MESSAGE; static constexpr auto CPU_VARIANT_OPTIONS = legate::VariantOptions{}.with_has_allocations(true); diff --git a/src/cupynumeric/matrix/svd.cc b/src/cupynumeric/matrix/svd.cc index c735783fed..f87019d033 100644 --- a/src/cupynumeric/matrix/svd.cc +++ b/src/cupynumeric/matrix/svd.cc @@ -34,7 +34,10 @@ using namespace legate; namespace // unnamed { -static void __attribute__((constructor)) register_tasks(void) { SvdTask::register_variants(); } +static const auto cupynumeric_reg_task_ = []() -> char { + SvdTask::register_variants(); + return 0; +}(); } // namespace } // namespace cupynumeric \ No newline at end of file diff --git a/src/cupynumeric/matrix/svd.h b/src/cupynumeric/matrix/svd.h index 5144040623..758ef371cc 100644 --- a/src/cupynumeric/matrix/svd.h +++ b/src/cupynumeric/matrix/svd.h @@ -22,7 +22,7 @@ namespace cupynumeric { class SvdTask : public CuPyNumericTask { public: - static constexpr auto TASK_ID = legate::LocalTaskID{CUPYNUMERIC_SVD}; + static inline const auto TASK_CONFIG = legate::TaskConfig{legate::LocalTaskID{CUPYNUMERIC_SVD}}; static const char* ERROR_MESSAGE; static constexpr auto CPU_VARIANT_OPTIONS = legate::VariantOptions{}.with_has_allocations(true); diff --git a/src/cupynumeric/matrix/syrk.cc b/src/cupynumeric/matrix/syrk.cc index 3812538855..1f646d7fe0 100644 --- a/src/cupynumeric/matrix/syrk.cc +++ b/src/cupynumeric/matrix/syrk.cc @@ -85,7 +85,10 @@ struct SyrkImplBody { namespace // unnamed { -static void __attribute__((constructor)) register_tasks(void) { SyrkTask::register_variants(); } +static const auto cupynumeric_reg_task_ = []() -> char { + SyrkTask::register_variants(); + return 0; +}(); } // namespace } // namespace cupynumeric diff --git a/src/cupynumeric/matrix/syrk.h b/src/cupynumeric/matrix/syrk.h index 9b383fa3f5..5074c62d3c 100644 --- a/src/cupynumeric/matrix/syrk.h +++ b/src/cupynumeric/matrix/syrk.h @@ -22,7 +22,7 @@ namespace cupynumeric { class SyrkTask : public CuPyNumericTask { public: - static constexpr auto TASK_ID = legate::LocalTaskID{CUPYNUMERIC_SYRK}; + static inline const auto TASK_CONFIG = legate::TaskConfig{legate::LocalTaskID{CUPYNUMERIC_SYRK}}; public: static void cpu_variant(legate::TaskContext context); diff --git a/src/cupynumeric/matrix/tile.cc b/src/cupynumeric/matrix/tile.cc index 82fa7d542c..ec6d92bf21 100644 --- a/src/cupynumeric/matrix/tile.cc +++ b/src/cupynumeric/matrix/tile.cc @@ -45,7 +45,10 @@ struct TileImplBody { namespace // unnamed { -static void __attribute__((constructor)) register_tasks(void) { TileTask::register_variants(); } +static const auto cupynumeric_reg_task_ = []() -> char { + TileTask::register_variants(); + return 0; +}(); } // namespace } // namespace cupynumeric diff --git a/src/cupynumeric/matrix/tile.h b/src/cupynumeric/matrix/tile.h index db0ade32b8..266ecdfe2f 100644 --- a/src/cupynumeric/matrix/tile.h +++ b/src/cupynumeric/matrix/tile.h @@ -27,7 +27,7 @@ struct TileArgs { class TileTask : public CuPyNumericTask { public: - static constexpr auto TASK_ID = legate::LocalTaskID{CUPYNUMERIC_TILE}; + static inline const auto TASK_CONFIG = legate::TaskConfig{legate::LocalTaskID{CUPYNUMERIC_TILE}}; public: static void cpu_variant(legate::TaskContext context); diff --git a/src/cupynumeric/matrix/transpose.cc b/src/cupynumeric/matrix/transpose.cc index 8ebcc2f729..f38f6e5f87 100644 --- a/src/cupynumeric/matrix/transpose.cc +++ b/src/cupynumeric/matrix/transpose.cc @@ -59,10 +59,10 @@ struct TransposeImplBody { namespace // unnamed { -static void __attribute__((constructor)) register_tasks(void) -{ +const auto reg = []() -> char { TransposeTask::register_variants(); -} + return 0; +}(); } // namespace } // namespace cupynumeric diff --git a/src/cupynumeric/matrix/transpose.h b/src/cupynumeric/matrix/transpose.h index 26290c1259..172e5815aa 100644 --- a/src/cupynumeric/matrix/transpose.h +++ b/src/cupynumeric/matrix/transpose.h @@ -27,7 +27,8 @@ struct TransposeArgs { class TransposeTask : public CuPyNumericTask { public: - static constexpr auto TASK_ID = legate::LocalTaskID{CUPYNUMERIC_TRANSPOSE_COPY_2D}; + static inline const auto TASK_CONFIG = + legate::TaskConfig{legate::LocalTaskID{CUPYNUMERIC_TRANSPOSE_COPY_2D}}; public: static void cpu_variant(legate::TaskContext context); diff --git a/src/cupynumeric/matrix/trilu.cc b/src/cupynumeric/matrix/trilu.cc index 8a177040f1..9c41b45193 100644 --- a/src/cupynumeric/matrix/trilu.cc +++ b/src/cupynumeric/matrix/trilu.cc @@ -62,7 +62,10 @@ struct TriluImplBody { namespace // unnamed { -static void __attribute__((constructor)) register_tasks(void) { TriluTask::register_variants(); } +static const auto cupynumeric_reg_task_ = []() -> char { + TriluTask::register_variants(); + return 0; +}(); } // namespace } // namespace cupynumeric diff --git a/src/cupynumeric/matrix/trilu.h b/src/cupynumeric/matrix/trilu.h index 27ad3e443e..6364992157 100644 --- a/src/cupynumeric/matrix/trilu.h +++ b/src/cupynumeric/matrix/trilu.h @@ -29,7 +29,7 @@ struct TriluArgs { class TriluTask : public CuPyNumericTask { public: - static constexpr auto TASK_ID = legate::LocalTaskID{CUPYNUMERIC_TRILU}; + static inline const auto TASK_CONFIG = legate::TaskConfig{legate::LocalTaskID{CUPYNUMERIC_TRILU}}; public: static void cpu_variant(legate::TaskContext context); diff --git a/src/cupynumeric/matrix/trsm.cc b/src/cupynumeric/matrix/trsm.cc index 703b0c63ae..74cc26ed40 100644 --- a/src/cupynumeric/matrix/trsm.cc +++ b/src/cupynumeric/matrix/trsm.cc @@ -96,7 +96,10 @@ struct TrsmImplBody { namespace // unnamed { -static void __attribute__((constructor)) register_tasks(void) { TrsmTask::register_variants(); } +static const auto cupynumeric_reg_task_ = []() -> char { + TrsmTask::register_variants(); + return 0; +}(); } // namespace } // namespace cupynumeric diff --git a/src/cupynumeric/matrix/trsm.h b/src/cupynumeric/matrix/trsm.h index 2b299dd5e3..ca3fa55b77 100644 --- a/src/cupynumeric/matrix/trsm.h +++ b/src/cupynumeric/matrix/trsm.h @@ -22,7 +22,7 @@ namespace cupynumeric { class TrsmTask : public CuPyNumericTask { public: - static constexpr auto TASK_ID = legate::LocalTaskID{CUPYNUMERIC_TRSM}; + static inline const auto TASK_CONFIG = legate::TaskConfig{legate::LocalTaskID{CUPYNUMERIC_TRSM}}; public: static void cpu_variant(legate::TaskContext context); diff --git a/src/cupynumeric/ndarray.cc b/src/cupynumeric/ndarray.cc index d3d1c4cfd4..fc94dbf429 100644 --- a/src/cupynumeric/ndarray.cc +++ b/src/cupynumeric/ndarray.cc @@ -1882,7 +1882,7 @@ NDArray NDArray::_maybe_convert(const legate::Type& type) const void NDArray::_verify_mode_extent(const std::map& mode2extent, const std::vector& modes, - const std::vector& shape) const + const std::vector& shape) const { for (int32_t i = 0; i < modes.size(); i++) { assert(mode2extent.at(modes[i]) == shape[i]); diff --git a/src/cupynumeric/ndarray.h b/src/cupynumeric/ndarray.h index ac0c332f34..c7a24a0669 100644 --- a/src/cupynumeric/ndarray.h +++ b/src/cupynumeric/ndarray.h @@ -174,7 +174,7 @@ class NDArray { void dot_MM(const legate::LogicalStore& rhs1_store, const legate::LogicalStore& rhs2_store); void _verify_mode_extent(const std::map& mode2extent, const std::vector& modes, - const std::vector& shape) const; + const std::vector& shape) const; legate::LogicalStore _alphabetical_transpose(legate::LogicalStore store, const std::vector& modes) const; diff --git a/src/cupynumeric/nullary/arange.cc b/src/cupynumeric/nullary/arange.cc index 9acc578499..8773cb3639 100644 --- a/src/cupynumeric/nullary/arange.cc +++ b/src/cupynumeric/nullary/arange.cc @@ -41,7 +41,10 @@ struct ArangeImplBody { namespace // unnamed { -static void __attribute__((constructor)) register_tasks(void) { ArangeTask::register_variants(); } +static const auto cupynumeric_reg_task_ = []() -> char { + ArangeTask::register_variants(); + return 0; +}(); } // namespace } // namespace cupynumeric diff --git a/src/cupynumeric/nullary/arange.h b/src/cupynumeric/nullary/arange.h index 52f157c313..c3b993a157 100644 --- a/src/cupynumeric/nullary/arange.h +++ b/src/cupynumeric/nullary/arange.h @@ -28,7 +28,8 @@ struct ArangeArgs { class ArangeTask : public CuPyNumericTask { public: - static constexpr auto TASK_ID = legate::LocalTaskID{CUPYNUMERIC_ARANGE}; + static inline const auto TASK_CONFIG = + legate::TaskConfig{legate::LocalTaskID{CUPYNUMERIC_ARANGE}}; public: static void cpu_variant(legate::TaskContext context); diff --git a/src/cupynumeric/nullary/eye.cc b/src/cupynumeric/nullary/eye.cc index 42af36c94e..d1fdcd101d 100644 --- a/src/cupynumeric/nullary/eye.cc +++ b/src/cupynumeric/nullary/eye.cc @@ -40,7 +40,10 @@ struct EyeImplBody { namespace // unnamed { -static void __attribute__((constructor)) register_tasks(void) { EyeTask::register_variants(); } +static const auto cupynumeric_reg_task_ = []() -> char { + EyeTask::register_variants(); + return 0; +}(); } // namespace } // namespace cupynumeric diff --git a/src/cupynumeric/nullary/eye.h b/src/cupynumeric/nullary/eye.h index a50c051a97..921101510f 100644 --- a/src/cupynumeric/nullary/eye.h +++ b/src/cupynumeric/nullary/eye.h @@ -27,7 +27,7 @@ struct EyeArgs { class EyeTask : public CuPyNumericTask { public: - static constexpr auto TASK_ID = legate::LocalTaskID{CUPYNUMERIC_EYE}; + static inline const auto TASK_CONFIG = legate::TaskConfig{legate::LocalTaskID{CUPYNUMERIC_EYE}}; public: static void cpu_variant(legate::TaskContext context); diff --git a/src/cupynumeric/nullary/fill.cc b/src/cupynumeric/nullary/fill.cc index d252e0504a..56ee2b6985 100644 --- a/src/cupynumeric/nullary/fill.cc +++ b/src/cupynumeric/nullary/fill.cc @@ -52,7 +52,10 @@ struct FillImplBody { namespace // unnamed { -static void __attribute__((constructor)) register_tasks(void) { FillTask::register_variants(); } +static const auto cupynumeric_reg_task_ = []() -> char { + FillTask::register_variants(); + return 0; +}(); } // namespace } // namespace cupynumeric diff --git a/src/cupynumeric/nullary/fill.h b/src/cupynumeric/nullary/fill.h index 229cd63f27..bd18797cd5 100644 --- a/src/cupynumeric/nullary/fill.h +++ b/src/cupynumeric/nullary/fill.h @@ -27,7 +27,7 @@ struct FillArgs { class FillTask : public CuPyNumericTask { public: - static constexpr auto TASK_ID = legate::LocalTaskID{CUPYNUMERIC_FILL}; + static inline const auto TASK_CONFIG = legate::TaskConfig{legate::LocalTaskID{CUPYNUMERIC_FILL}}; public: static void cpu_variant(legate::TaskContext context); diff --git a/src/cupynumeric/nullary/window.cc b/src/cupynumeric/nullary/window.cc index a5490359fb..ed3d71575d 100644 --- a/src/cupynumeric/nullary/window.cc +++ b/src/cupynumeric/nullary/window.cc @@ -48,7 +48,10 @@ struct WindowImplBody { namespace // unnamed { -static void __attribute__((constructor)) register_tasks(void) { WindowTask::register_variants(); } +static const auto cupynumeric_reg_task_ = []() -> char { + WindowTask::register_variants(); + return 0; +}(); } // namespace } // namespace cupynumeric diff --git a/src/cupynumeric/nullary/window.h b/src/cupynumeric/nullary/window.h index 2a59549d22..aa0d7c28fe 100644 --- a/src/cupynumeric/nullary/window.h +++ b/src/cupynumeric/nullary/window.h @@ -22,7 +22,8 @@ namespace cupynumeric { class WindowTask : public CuPyNumericTask { public: - static constexpr auto TASK_ID = legate::LocalTaskID{CUPYNUMERIC_WINDOW}; + static inline const auto TASK_CONFIG = + legate::TaskConfig{legate::LocalTaskID{CUPYNUMERIC_WINDOW}}; public: static void cpu_variant(legate::TaskContext context); diff --git a/src/cupynumeric/operators.cc b/src/cupynumeric/operators.cc index 79bb3587be..c5a4fde9e5 100644 --- a/src/cupynumeric/operators.cc +++ b/src/cupynumeric/operators.cc @@ -623,7 +623,7 @@ NDArray dot(NDArray a, NDArray b) mode2extent[mode] = extent; } - std::vector c_shape; + std::vector c_shape; for (auto mode : c_modes) { auto search = mode2extent.find(mode); if (search != mode2extent.end()) { diff --git a/src/cupynumeric/random/bitgenerator.cc b/src/cupynumeric/random/bitgenerator.cc index b4c25282d3..5e927b7280 100644 --- a/src/cupynumeric/random/bitgenerator.cc +++ b/src/cupynumeric/random/bitgenerator.cc @@ -108,10 +108,10 @@ std::mutex BitGeneratorImplBody::lock_generators = {}; namespace // unnamed { -static void __attribute__((constructor)) register_tasks(void) -{ +const auto reg_ = []() -> char { BitGeneratorTask::register_variants(); -} + return 0; +}(); } // namespace diff --git a/src/cupynumeric/random/bitgenerator.h b/src/cupynumeric/random/bitgenerator.h index 71c92076fb..9ace4b0356 100644 --- a/src/cupynumeric/random/bitgenerator.h +++ b/src/cupynumeric/random/bitgenerator.h @@ -80,7 +80,8 @@ struct BitGeneratorArgs { class BitGeneratorTask : public CuPyNumericTask { public: - static constexpr auto TASK_ID = legate::LocalTaskID{CUPYNUMERIC_BITGENERATOR}; + static inline const auto TASK_CONFIG = + legate::TaskConfig{legate::LocalTaskID{CUPYNUMERIC_BITGENERATOR}}; public: static void cpu_variant(legate::TaskContext context); diff --git a/src/cupynumeric/random/rand.cc b/src/cupynumeric/random/rand.cc index d2e354d4e4..4dffe7d5cf 100644 --- a/src/cupynumeric/random/rand.cc +++ b/src/cupynumeric/random/rand.cc @@ -48,7 +48,10 @@ struct RandImplBody { namespace // unnamed { -static void __attribute__((constructor)) register_tasks(void) { RandTask::register_variants(); } +static const auto cupynumeric_reg_task_ = []() -> char { + RandTask::register_variants(); + return 0; +}(); } // namespace } // namespace cupynumeric diff --git a/src/cupynumeric/random/rand.h b/src/cupynumeric/random/rand.h index ccf2de91ef..f9d2d9ec17 100644 --- a/src/cupynumeric/random/rand.h +++ b/src/cupynumeric/random/rand.h @@ -31,7 +31,7 @@ struct RandArgs { class RandTask : public CuPyNumericTask { public: - static constexpr auto TASK_ID = legate::LocalTaskID{CUPYNUMERIC_RAND}; + static inline const auto TASK_CONFIG = legate::TaskConfig{legate::LocalTaskID{CUPYNUMERIC_RAND}}; public: static void cpu_variant(legate::TaskContext context); diff --git a/src/cupynumeric/scan/scan_global.cc b/src/cupynumeric/scan/scan_global.cc index 767bba1764..cc9a3c8419 100644 --- a/src/cupynumeric/scan/scan_global.cc +++ b/src/cupynumeric/scan/scan_global.cc @@ -74,10 +74,10 @@ struct ScanGlobalImplBody { namespace // unnamed { -static void __attribute__((constructor)) register_tasks(void) -{ +const auto reg_ = []() -> char { ScanGlobalTask::register_variants(); -} + return 0; +}(); } // namespace } // namespace cupynumeric diff --git a/src/cupynumeric/scan/scan_global.h b/src/cupynumeric/scan/scan_global.h index dcd10c2555..cd94729c7d 100644 --- a/src/cupynumeric/scan/scan_global.h +++ b/src/cupynumeric/scan/scan_global.h @@ -30,7 +30,8 @@ struct ScanGlobalArgs { class ScanGlobalTask : public CuPyNumericTask { public: - static constexpr auto TASK_ID = legate::LocalTaskID{CUPYNUMERIC_SCAN_GLOBAL}; + static inline const auto TASK_CONFIG = + legate::TaskConfig{legate::LocalTaskID{CUPYNUMERIC_SCAN_GLOBAL}}; public: static void cpu_variant(legate::TaskContext context); diff --git a/src/cupynumeric/scan/scan_local.cc b/src/cupynumeric/scan/scan_local.cc index 827536e20e..b7a31b4071 100644 --- a/src/cupynumeric/scan/scan_local.cc +++ b/src/cupynumeric/scan/scan_local.cc @@ -116,10 +116,10 @@ struct ScanLocalNanImplBody { namespace // unnamed { -static void __attribute__((constructor)) register_tasks(void) -{ +const auto reg_ = []() -> char { ScanLocalTask::register_variants(); -} + return 0; +}(); } // namespace } // namespace cupynumeric diff --git a/src/cupynumeric/scan/scan_local.h b/src/cupynumeric/scan/scan_local.h index 10c5e15bb7..c143877010 100644 --- a/src/cupynumeric/scan/scan_local.h +++ b/src/cupynumeric/scan/scan_local.h @@ -31,7 +31,8 @@ struct ScanLocalArgs { class ScanLocalTask : public CuPyNumericTask { public: - static constexpr auto TASK_ID = legate::LocalTaskID{CUPYNUMERIC_SCAN_LOCAL}; + static inline const auto TASK_CONFIG = + legate::TaskConfig{legate::LocalTaskID{CUPYNUMERIC_SCAN_LOCAL}}; static constexpr auto CPU_VARIANT_OPTIONS = legate::VariantOptions{}.with_has_allocations(true); static constexpr auto GPU_VARIANT_OPTIONS = legate::VariantOptions{}.with_has_allocations(true); diff --git a/src/cupynumeric/search/argwhere.cc b/src/cupynumeric/search/argwhere.cc index 8a71f04dff..93e616d3fa 100644 --- a/src/cupynumeric/search/argwhere.cc +++ b/src/cupynumeric/search/argwhere.cc @@ -62,7 +62,10 @@ struct ArgWhereImplBody { namespace // unnamed { -static void __attribute__((constructor)) register_tasks(void) { ArgWhereTask::register_variants(); } +static const auto cupynumeric_reg_task_ = []() -> char { + ArgWhereTask::register_variants(); + return 0; +}(); } // namespace } // namespace cupynumeric diff --git a/src/cupynumeric/search/argwhere.h b/src/cupynumeric/search/argwhere.h index 3a732281bf..4567860862 100644 --- a/src/cupynumeric/search/argwhere.h +++ b/src/cupynumeric/search/argwhere.h @@ -27,7 +27,8 @@ struct ArgWhereArgs { class ArgWhereTask : public CuPyNumericTask { public: - static constexpr auto TASK_ID = legate::LocalTaskID{CUPYNUMERIC_ARGWHERE}; + static inline const auto TASK_CONFIG = + legate::TaskConfig{legate::LocalTaskID{CUPYNUMERIC_ARGWHERE}}; static constexpr auto CPU_VARIANT_OPTIONS = legate::VariantOptions{}.with_has_allocations(true); static constexpr auto GPU_VARIANT_OPTIONS = legate::VariantOptions{}.with_has_allocations(true); diff --git a/src/cupynumeric/search/nonzero.cc b/src/cupynumeric/search/nonzero.cc index d9fef6d44c..3b7cc2d3f8 100644 --- a/src/cupynumeric/search/nonzero.cc +++ b/src/cupynumeric/search/nonzero.cc @@ -65,7 +65,10 @@ struct NonzeroImplBody { namespace // unnamed { -static void __attribute__((constructor)) register_tasks(void) { NonzeroTask::register_variants(); } +static const auto cupynumeric_reg_task_ = []() -> char { + NonzeroTask::register_variants(); + return 0; +}(); } // namespace } // namespace cupynumeric diff --git a/src/cupynumeric/search/nonzero.h b/src/cupynumeric/search/nonzero.h index f799ca4a34..f6d64307fa 100644 --- a/src/cupynumeric/search/nonzero.h +++ b/src/cupynumeric/search/nonzero.h @@ -27,7 +27,8 @@ struct NonzeroArgs { class NonzeroTask : public CuPyNumericTask { public: - static constexpr auto TASK_ID = legate::LocalTaskID{CUPYNUMERIC_NONZERO}; + static inline const auto TASK_CONFIG = + legate::TaskConfig{legate::LocalTaskID{CUPYNUMERIC_NONZERO}}; static constexpr auto CPU_VARIANT_OPTIONS = legate::VariantOptions{}.with_has_allocations(true); static constexpr auto GPU_VARIANT_OPTIONS = legate::VariantOptions{}.with_has_allocations(true); diff --git a/src/cupynumeric/set/unique.cc b/src/cupynumeric/set/unique.cc index d3b1a7f095..97bcd0810b 100644 --- a/src/cupynumeric/set/unique.cc +++ b/src/cupynumeric/set/unique.cc @@ -56,7 +56,10 @@ struct UniqueImplBody { namespace // unnamed { -static void __attribute__((constructor)) register_tasks(void) { UniqueTask::register_variants(); } +static const auto cupynumeric_reg_task_ = []() -> char { + UniqueTask::register_variants(); + return 0; +}(); } // namespace } // namespace cupynumeric diff --git a/src/cupynumeric/set/unique.h b/src/cupynumeric/set/unique.h index 7f87e6193f..9dca0f66a2 100644 --- a/src/cupynumeric/set/unique.h +++ b/src/cupynumeric/set/unique.h @@ -22,7 +22,8 @@ namespace cupynumeric { class UniqueTask : public CuPyNumericTask { public: - static constexpr auto TASK_ID = legate::LocalTaskID{CUPYNUMERIC_UNIQUE}; + static inline const auto TASK_CONFIG = + legate::TaskConfig{legate::LocalTaskID{CUPYNUMERIC_UNIQUE}}; static constexpr auto CPU_VARIANT_OPTIONS = legate::VariantOptions{}.with_has_allocations(true); static constexpr auto GPU_VARIANT_OPTIONS = diff --git a/src/cupynumeric/set/unique_reduce.cc b/src/cupynumeric/set/unique_reduce.cc index e6db524a47..56361a0983 100644 --- a/src/cupynumeric/set/unique_reduce.cc +++ b/src/cupynumeric/set/unique_reduce.cc @@ -26,10 +26,10 @@ namespace cupynumeric { namespace // unnamed { -static void __attribute__((constructor)) register_tasks(void) -{ +const auto reg_ = []() -> char { UniqueReduceTask::register_variants(); -} + return 0; +}(); } // namespace } // namespace cupynumeric diff --git a/src/cupynumeric/set/unique_reduce.h b/src/cupynumeric/set/unique_reduce.h index 0f03e7c7a3..10b192f722 100644 --- a/src/cupynumeric/set/unique_reduce.h +++ b/src/cupynumeric/set/unique_reduce.h @@ -22,7 +22,8 @@ namespace cupynumeric { class UniqueReduceTask : public CuPyNumericTask { public: - static constexpr auto TASK_ID = legate::LocalTaskID{CUPYNUMERIC_UNIQUE_REDUCE}; + static inline const auto TASK_CONFIG = + legate::TaskConfig{legate::LocalTaskID{CUPYNUMERIC_UNIQUE_REDUCE}}; static constexpr auto CPU_VARIANT_OPTIONS = legate::VariantOptions{}.with_has_allocations(true); static constexpr auto OMP_VARIANT_OPTIONS = legate::VariantOptions{}.with_has_allocations(true); diff --git a/src/cupynumeric/sort/searchsorted.cc b/src/cupynumeric/sort/searchsorted.cc index 89957f397f..1648f0c2b0 100644 --- a/src/cupynumeric/sort/searchsorted.cc +++ b/src/cupynumeric/sort/searchsorted.cc @@ -79,10 +79,10 @@ struct SearchSortedImplBody { namespace // unnamed { -static void __attribute__((constructor)) register_tasks(void) -{ +const auto reg_ = []() -> char { SearchSortedTask::register_variants(); -} + return 0; +}(); } // namespace } // namespace cupynumeric diff --git a/src/cupynumeric/sort/searchsorted.h b/src/cupynumeric/sort/searchsorted.h index e983f2deaf..a1b149eac8 100644 --- a/src/cupynumeric/sort/searchsorted.h +++ b/src/cupynumeric/sort/searchsorted.h @@ -31,7 +31,8 @@ struct SearchSortedArgs { class SearchSortedTask : public CuPyNumericTask { public: - static constexpr auto TASK_ID = legate::LocalTaskID{CUPYNUMERIC_SEARCHSORTED}; + static inline const auto TASK_CONFIG = + legate::TaskConfig{legate::LocalTaskID{CUPYNUMERIC_SEARCHSORTED}}; public: static void cpu_variant(legate::TaskContext context); diff --git a/src/cupynumeric/sort/sort.cc b/src/cupynumeric/sort/sort.cc index 660476b558..620356c33b 100644 --- a/src/cupynumeric/sort/sort.cc +++ b/src/cupynumeric/sort/sort.cc @@ -73,7 +73,10 @@ struct SortImplBody { namespace // unnamed { -static void __attribute__((constructor)) register_tasks(void) { SortTask::register_variants(); } +static const auto cupynumeric_reg_task_ = []() -> char { + SortTask::register_variants(); + return 0; +}(); } // namespace } // namespace cupynumeric diff --git a/src/cupynumeric/sort/sort.h b/src/cupynumeric/sort/sort.h index 04ccf04281..e719c553e2 100644 --- a/src/cupynumeric/sort/sort.h +++ b/src/cupynumeric/sort/sort.h @@ -94,7 +94,7 @@ struct modulusWithOffset : public thrust::binary_function { public: - static constexpr auto TASK_ID = legate::LocalTaskID{CUPYNUMERIC_SORT}; + static inline const auto TASK_CONFIG = legate::TaskConfig{legate::LocalTaskID{CUPYNUMERIC_SORT}}; static constexpr auto CPU_VARIANT_OPTIONS = legate::VariantOptions{}.with_concurrent(true).with_has_allocations(true); diff --git a/src/cupynumeric/stat/bincount.cc b/src/cupynumeric/stat/bincount.cc index 8a57525af7..0db2c105cc 100644 --- a/src/cupynumeric/stat/bincount.cc +++ b/src/cupynumeric/stat/bincount.cc @@ -58,7 +58,10 @@ struct BincountImplBody { namespace // unnamed { -static void __attribute__((constructor)) register_tasks(void) { BincountTask::register_variants(); } +static const auto cupynumeric_reg_task_ = []() -> char { + BincountTask::register_variants(); + return 0; +}(); } // namespace } // namespace cupynumeric diff --git a/src/cupynumeric/stat/bincount.h b/src/cupynumeric/stat/bincount.h index 188c5b11dd..f737a67857 100644 --- a/src/cupynumeric/stat/bincount.h +++ b/src/cupynumeric/stat/bincount.h @@ -29,7 +29,8 @@ struct BincountArgs { class BincountTask : public CuPyNumericTask { public: - static constexpr auto TASK_ID = legate::LocalTaskID{CUPYNUMERIC_BINCOUNT}; + static inline const auto TASK_CONFIG = + legate::TaskConfig{legate::LocalTaskID{CUPYNUMERIC_BINCOUNT}}; public: static void cpu_variant(legate::TaskContext context); diff --git a/src/cupynumeric/stat/histogram.cc b/src/cupynumeric/stat/histogram.cc index f8191b3d8a..0950e198ae 100644 --- a/src/cupynumeric/stat/histogram.cc +++ b/src/cupynumeric/stat/histogram.cc @@ -69,10 +69,10 @@ struct HistogramImplBody { namespace // unnamed { -static void __attribute__((constructor)) register_tasks(void) -{ +const auto reg_ = []() -> char { HistogramTask::register_variants(); -} + return 0; +}(); } // namespace } // namespace cupynumeric diff --git a/src/cupynumeric/stat/histogram.h b/src/cupynumeric/stat/histogram.h index a986b4e1a4..f086e351b5 100644 --- a/src/cupynumeric/stat/histogram.h +++ b/src/cupynumeric/stat/histogram.h @@ -29,7 +29,8 @@ struct HistogramArgs { class HistogramTask : public CuPyNumericTask { public: - static constexpr auto TASK_ID = legate::LocalTaskID{CUPYNUMERIC_HISTOGRAM}; + static inline const auto TASK_CONFIG = + legate::TaskConfig{legate::LocalTaskID{CUPYNUMERIC_HISTOGRAM}}; static constexpr auto CPU_VARIANT_OPTIONS = legate::VariantOptions{}.with_has_allocations(true); static constexpr auto OMP_VARIANT_OPTIONS = legate::VariantOptions{}.with_has_allocations(true); diff --git a/src/cupynumeric/ternary/where.cc b/src/cupynumeric/ternary/where.cc index f8c5cc1fc6..f74929c258 100644 --- a/src/cupynumeric/ternary/where.cc +++ b/src/cupynumeric/ternary/where.cc @@ -59,7 +59,10 @@ struct WhereImplBody { namespace // unnamed { -static void __attribute__((constructor)) register_tasks(void) { WhereTask::register_variants(); } +static const auto cupynumeric_reg_task_ = []() -> char { + WhereTask::register_variants(); + return 0; +}(); } // namespace } // namespace cupynumeric diff --git a/src/cupynumeric/ternary/where.h b/src/cupynumeric/ternary/where.h index 91bf04ead5..03a9dbf20c 100644 --- a/src/cupynumeric/ternary/where.h +++ b/src/cupynumeric/ternary/where.h @@ -29,7 +29,7 @@ struct WhereArgs { class WhereTask : public CuPyNumericTask { public: - static constexpr auto TASK_ID = legate::LocalTaskID{CUPYNUMERIC_WHERE}; + static inline const auto TASK_CONFIG = legate::TaskConfig{legate::LocalTaskID{CUPYNUMERIC_WHERE}}; public: static void cpu_variant(legate::TaskContext context); diff --git a/src/cupynumeric/transform/flip.cc b/src/cupynumeric/transform/flip.cc index e1bc799ee4..5b45cea010 100644 --- a/src/cupynumeric/transform/flip.cc +++ b/src/cupynumeric/transform/flip.cc @@ -49,7 +49,10 @@ struct FlipImplBody { namespace // unnamed { -static void __attribute__((constructor)) register_tasks(void) { FlipTask::register_variants(); } +static const auto cupynumeric_reg_task_ = []() -> char { + FlipTask::register_variants(); + return 0; +}(); } // namespace } // namespace cupynumeric diff --git a/src/cupynumeric/transform/flip.h b/src/cupynumeric/transform/flip.h index 25bfb1d96d..f9692128eb 100644 --- a/src/cupynumeric/transform/flip.h +++ b/src/cupynumeric/transform/flip.h @@ -28,7 +28,7 @@ struct FlipArgs { class FlipTask : public CuPyNumericTask { public: - static constexpr auto TASK_ID = legate::LocalTaskID{CUPYNUMERIC_FLIP}; + static inline const auto TASK_CONFIG = legate::TaskConfig{legate::LocalTaskID{CUPYNUMERIC_FLIP}}; static constexpr auto GPU_VARIANT_OPTIONS = legate::VariantOptions{}.with_has_allocations(true); diff --git a/src/cupynumeric/unary/convert.cc b/src/cupynumeric/unary/convert.cc index 7594d2f871..fb08696c64 100644 --- a/src/cupynumeric/unary/convert.cc +++ b/src/cupynumeric/unary/convert.cc @@ -57,7 +57,10 @@ struct ConvertImplBody { namespace // unnamed { -static void __attribute__((constructor)) register_tasks(void) { ConvertTask::register_variants(); } +static const auto cupynumeric_reg_task_ = []() -> char { + ConvertTask::register_variants(); + return 0; +}(); } // namespace } // namespace cupynumeric diff --git a/src/cupynumeric/unary/convert.h b/src/cupynumeric/unary/convert.h index 9d771dde9e..835b0d1cb7 100644 --- a/src/cupynumeric/unary/convert.h +++ b/src/cupynumeric/unary/convert.h @@ -29,7 +29,8 @@ struct ConvertArgs { class ConvertTask : public CuPyNumericTask { public: - static constexpr auto TASK_ID = legate::LocalTaskID{CUPYNUMERIC_CONVERT}; + static inline const auto TASK_CONFIG = + legate::TaskConfig{legate::LocalTaskID{CUPYNUMERIC_CONVERT}}; public: static void cpu_variant(legate::TaskContext context); diff --git a/src/cupynumeric/unary/scalar_unary_red.cc b/src/cupynumeric/unary/scalar_unary_red.cc index a0ae42a081..7167c30a91 100644 --- a/src/cupynumeric/unary/scalar_unary_red.cc +++ b/src/cupynumeric/unary/scalar_unary_red.cc @@ -26,10 +26,10 @@ namespace cupynumeric { namespace // unnamed { -static void __attribute__((constructor)) register_tasks(void) -{ +const auto reg_ = []() -> char { ScalarUnaryRedTask::register_variants(); -} + return 0; +}(); } // namespace } // namespace cupynumeric diff --git a/src/cupynumeric/unary/scalar_unary_red.h b/src/cupynumeric/unary/scalar_unary_red.h index 537423be88..20aa30eb2b 100644 --- a/src/cupynumeric/unary/scalar_unary_red.h +++ b/src/cupynumeric/unary/scalar_unary_red.h @@ -33,7 +33,8 @@ struct ScalarUnaryRedArgs { // Unary reduction task that produces scalar results class ScalarUnaryRedTask : public CuPyNumericTask { public: - static constexpr auto TASK_ID = legate::LocalTaskID{CUPYNUMERIC_SCALAR_UNARY_RED}; + static inline const auto TASK_CONFIG = + legate::TaskConfig{legate::LocalTaskID{CUPYNUMERIC_SCALAR_UNARY_RED}}; static constexpr auto GPU_VARIANT_OPTIONS = legate::VariantOptions{}.with_has_allocations(true); diff --git a/src/cupynumeric/unary/unary_op.cc b/src/cupynumeric/unary/unary_op.cc index f307801b63..0341e8e094 100644 --- a/src/cupynumeric/unary/unary_op.cc +++ b/src/cupynumeric/unary/unary_op.cc @@ -113,7 +113,10 @@ struct MultiOutUnaryOpImplBody { namespace // unnamed { -static void __attribute__((constructor)) register_tasks(void) { UnaryOpTask::register_variants(); } +static const auto cupynumeric_reg_task_ = []() -> char { + UnaryOpTask::register_variants(); + return 0; +}(); } // namespace } // namespace cupynumeric diff --git a/src/cupynumeric/unary/unary_op.h b/src/cupynumeric/unary/unary_op.h index 357062293f..89b40c77ed 100644 --- a/src/cupynumeric/unary/unary_op.h +++ b/src/cupynumeric/unary/unary_op.h @@ -37,7 +37,8 @@ struct MultiOutUnaryOpArgs { class UnaryOpTask : public CuPyNumericTask { public: - static constexpr auto TASK_ID = legate::LocalTaskID{CUPYNUMERIC_UNARY_OP}; + static inline const auto TASK_CONFIG = + legate::TaskConfig{legate::LocalTaskID{CUPYNUMERIC_UNARY_OP}}; public: static void cpu_variant(legate::TaskContext context); diff --git a/src/cupynumeric/unary/unary_red.cc b/src/cupynumeric/unary/unary_red.cc index 062c645f76..0d11ceeb5b 100644 --- a/src/cupynumeric/unary/unary_red.cc +++ b/src/cupynumeric/unary/unary_red.cc @@ -56,7 +56,10 @@ struct UnaryRedImplBody { namespace // unnamed { -static void __attribute__((constructor)) register_tasks(void) { UnaryRedTask::register_variants(); } +static const auto cupynumeric_reg_task_ = []() -> char { + UnaryRedTask::register_variants(); + return 0; +}(); } // namespace } // namespace cupynumeric diff --git a/src/cupynumeric/unary/unary_red.h b/src/cupynumeric/unary/unary_red.h index 9667253249..7aca22baa2 100644 --- a/src/cupynumeric/unary/unary_red.h +++ b/src/cupynumeric/unary/unary_red.h @@ -31,7 +31,8 @@ struct UnaryRedArgs { class UnaryRedTask : public CuPyNumericTask { public: - static constexpr auto TASK_ID = legate::LocalTaskID{CUPYNUMERIC_UNARY_RED}; + static inline const auto TASK_CONFIG = + legate::TaskConfig{legate::LocalTaskID{CUPYNUMERIC_UNARY_RED}}; public: static void cpu_variant(legate::TaskContext context); diff --git a/tests/cpp/integration/common_utils.h b/tests/cpp/integration/common_utils.h index 89663310d9..2078c4c556 100644 --- a/tests/cpp/integration/common_utils.h +++ b/tests/cpp/integration/common_utils.h @@ -74,7 +74,7 @@ NDArray mk_array(std::vector const& values, std::vector shape = {}) } template -void check_and_wrap(NDArray& a, const std::vector& values, std::vector& shape) +void check_and_wrap(NDArray& a, const std::vector& values, std::vector& shape) { if (shape.empty() && values.size() > 1) { shape.push_back(values.size()); @@ -89,7 +89,7 @@ void check_and_wrap(NDArray& a, const std::vector& values, std::vector -void check_array(NDArray a, const std::vector& values, std::vector shape = {}) +void check_array(NDArray a, const std::vector& values, std::vector shape = {}) { check_and_wrap(a, values, shape); if (a.size() == 0) { @@ -111,8 +111,8 @@ void check_array(NDArray a, const std::vector& values, std::vector sh template void check_array_near(NDArray a, const std::vector& values, - std::vector shape = {}, - double abs_error = 1.e-8) + std::vector shape = {}, + double abs_error = 1.e-8) { check_and_wrap(a, values, shape); if (a.size() == 0) { @@ -177,7 +177,7 @@ void debug_vector(const std::vector& vec) template std::vector mk_seq_vector(std::vector shape, T a = 1, T b = 0) { - size_t size = std::accumulate(shape.begin(), shape.end(), size_t(1), std::multiplies()); + size_t size = std::accumulate(shape.begin(), shape.end(), size_t(1), std::multiplies<>()); std::vector v(size); std::generate(v.begin(), v.end(), [a, x = b]() mutable { return x += a; }); return v; diff --git a/tests/cpp/integration/test_amax.cc b/tests/cpp/integration/test_amax.cc index ce483ccec2..3a50f66779 100644 --- a/tests/cpp/integration/test_amax.cc +++ b/tests/cpp/integration/test_amax.cc @@ -17,9 +17,9 @@ template void test_amax(const std::vector& in_array, - const std::vector& shape, + const std::vector& shape, const std::vector& expect_result, - const std::vector& expect_shape, + const std::vector& expect_shape, std::vector axis = {}, std::optional dtype = std::nullopt, std::optional out = std::nullopt, @@ -40,9 +40,9 @@ void test_amax(const std::vector& in_array, template void test_amax_each_axis(const std::vector& arr, - const std::vector& shape, + const std::vector& shape, std::map>& expect_results, - std::map>& expect_shapes, + std::map>& expect_shapes, bool keepdims = false, std::optional initial = std::nullopt) { @@ -61,41 +61,41 @@ void test_amax_basic() { typedef std::map> IntResult; typedef std::map> DoubleResult; - typedef std::map> ShapeResult; + typedef std::map> ShapeResult; // Test int type - dim=1 - std::vector arr1 = {-1, 4, 5, 2, 0}; - std::vector shape1 = {5}; - ShapeResult exp_shape1 = {{0, {}}}; - ShapeResult exp_shape1_k = {{0, {1}}}; - IntResult exp1 = {{0, {5}}}; + std::vector arr1 = {-1, 4, 5, 2, 0}; + std::vector shape1 = {5}; + ShapeResult exp_shape1 = {{0, {}}}; + ShapeResult exp_shape1_k = {{0, {1}}}; + IntResult exp1 = {{0, {5}}}; test_amax_each_axis(arr1, shape1, exp1, exp_shape1); test_amax_each_axis(arr1, shape1, exp1, exp_shape1_k, true); // Test int type - dim=2 - std::vector arr2 = {1, 0, 0, 5, 3, 2}; - std::vector shape2 = {3, 2}; - ShapeResult exp_shape2 = {{0, {2}}, {1, {3}}}; - ShapeResult exp_shape2_k = {{0, {1, 2}}, {1, {3, 1}}}; - IntResult exp2 = {{0, {3, 5}}, {1, {1, 5, 3}}}; + std::vector arr2 = {1, 0, 0, 5, 3, 2}; + std::vector shape2 = {3, 2}; + ShapeResult exp_shape2 = {{0, {2}}, {1, {3}}}; + ShapeResult exp_shape2_k = {{0, {1, 2}}, {1, {3, 1}}}; + IntResult exp2 = {{0, {3, 5}}, {1, {1, 5, 3}}}; test_amax_each_axis(arr2, shape2, exp2, exp_shape2); test_amax_each_axis(arr2, shape2, exp2, exp_shape2_k, true); // Test int type - dim=3 - std::vector arr3 = {0, 11, 2, 3, -4, 0, -6, 7}; - std::vector shape3 = {2, 2, 2}; - ShapeResult exp_shape3 = {{0, {2, 2}}, {1, {2, 2}}, {2, {2, 2}}}; - ShapeResult exp_shape3_k = {{0, {1, 2, 2}}, {1, {2, 1, 2}}, {2, {2, 2, 1}}}; - IntResult exp3 = {{0, {0, 11, 2, 7}}, {1, {2, 11, -4, 7}}, {2, {11, 3, 0, 7}}}; + std::vector arr3 = {0, 11, 2, 3, -4, 0, -6, 7}; + std::vector shape3 = {2, 2, 2}; + ShapeResult exp_shape3 = {{0, {2, 2}}, {1, {2, 2}}, {2, {2, 2}}}; + ShapeResult exp_shape3_k = {{0, {1, 2, 2}}, {1, {2, 1, 2}}, {2, {2, 2, 1}}}; + IntResult exp3 = {{0, {0, 11, 2, 7}}, {1, {2, 11, -4, 7}}, {2, {11, 3, 0, 7}}}; test_amax_each_axis(arr3, shape3, exp3, exp_shape3); test_amax_each_axis(arr3, shape3, exp3, exp_shape3_k, true); // Test float type - dim=3 - std::vector arr4 = {0.0, -0.99, 10.0, -5.0, 2.999, 1.51, -1.0, 2.99, 3.0}; - std::vector shape4 = {3, 1, 3}; - ShapeResult exp_shape4 = {{0, {1, 3}}, {1, {3, 3}}, {2, {3, 1}}}; - ShapeResult exp_shape4_k = {{0, {1, 1, 3}}, {1, {3, 1, 3}}, {2, {3, 1, 1}}}; - DoubleResult exp4 = {{0, {0.0, 2.999, 10.0}}, {1, arr4}, {2, {10.0, 2.999, 3.0}}}; + std::vector arr4 = {0.0, -0.99, 10.0, -5.0, 2.999, 1.51, -1.0, 2.99, 3.0}; + std::vector shape4 = {3, 1, 3}; + ShapeResult exp_shape4 = {{0, {1, 3}}, {1, {3, 3}}, {2, {3, 1}}}; + ShapeResult exp_shape4_k = {{0, {1, 1, 3}}, {1, {3, 1, 3}}, {2, {3, 1, 1}}}; + DoubleResult exp4 = {{0, {0.0, 2.999, 10.0}}, {1, arr4}, {2, {10.0, 2.999, 3.0}}}; test_amax_each_axis(arr4, shape4, exp4, exp_shape4); test_amax_each_axis(arr4, shape4, exp4, exp_shape4_k, true); } @@ -104,24 +104,24 @@ void test_amax_initial_input() { typedef std::map> IntResult; typedef std::map> DoubleResult; - typedef std::map> ShapeResult; + typedef std::map> ShapeResult; - std::vector arr1 = {0, 11, 2, 3, -4, 0, -6, 7}; - std::vector shape1 = {2, 2, 2}; - ShapeResult exp_shape1 = {{0, {2, 2}}, {1, {2, 2}}, {2, {2, 2}}}; - ShapeResult exp_shape1_k = {{0, {1, 2, 2}}, {1, {2, 1, 2}}, {2, {2, 2, 1}}}; + std::vector arr1 = {0, 11, 2, 3, -4, 0, -6, 7}; + std::vector shape1 = {2, 2, 2}; + ShapeResult exp_shape1 = {{0, {2, 2}}, {1, {2, 2}}, {2, {2, 2}}}; + ShapeResult exp_shape1_k = {{0, {1, 2, 2}}, {1, {2, 1, 2}}, {2, {2, 2, 1}}}; // use initial in each axis auto initial1 = legate::Scalar(6); IntResult exp1 = {{0, {6, 11, 6, 7}}, {1, {6, 11, 6, 7}}, {2, {11, 6, 6, 7}}}; test_amax_each_axis(arr1, shape1, exp1, exp_shape1, false, initial1); test_amax_each_axis(arr1, shape1, exp1, exp_shape1_k, true, initial1); - std::vector arr2 = {0.0, -0.99, 10.0, -5.0, 2.999, 1.51, -1.0, 2.99, 3.0}; - std::vector shape2 = {3, 3}; - ShapeResult exp_shape2 = {{0, {3}}, {1, {3}}}; - ShapeResult exp_shape2_k = {{0, {1, 3}}, {1, {3, 1}}}; - auto initial2 = legate::Scalar(2.9999); - DoubleResult exp2 = {{0, {2.9999, 2.9999, 10.0}}, {1, {10.0, 2.9999, 3.0}}}; + std::vector arr2 = {0.0, -0.99, 10.0, -5.0, 2.999, 1.51, -1.0, 2.99, 3.0}; + std::vector shape2 = {3, 3}; + ShapeResult exp_shape2 = {{0, {3}}, {1, {3}}}; + ShapeResult exp_shape2_k = {{0, {1, 3}}, {1, {3, 1}}}; + auto initial2 = legate::Scalar(2.9999); + DoubleResult exp2 = {{0, {2.9999, 2.9999, 10.0}}, {1, {10.0, 2.9999, 3.0}}}; test_amax_each_axis(arr2, shape2, exp2, exp_shape2, false, initial2); test_amax_each_axis(arr2, shape2, exp2, exp_shape2_k, true, initial2); } @@ -129,56 +129,56 @@ void test_amax_initial_input() void test_amax_dtype_input() { // int to float - std::vector arr1 = {-1, 4, 5, 2, 0}; - std::vector shape1 = {5}; - std::vector exp_shape1 = {}; - auto dtype1 = legate::float64(); - std::vector exp1 = {5.0}; + std::vector arr1 = {-1, 4, 5, 2, 0}; + std::vector shape1 = {5}; + std::vector exp_shape1 = {}; + auto dtype1 = legate::float64(); + std::vector exp1 = {5.0}; test_amax(arr1, shape1, exp1, exp_shape1, {}, dtype1); // float to int - std::vector arr2 = {0.0, -0.99, 10.1, -5.6, 2.999, 1.51}; - std::vector shape2 = {3, 2}; - std::vector exp_shape2 = {}; - auto dtype2 = legate::int32(); - std::vector exp2 = {10}; + std::vector arr2 = {0.0, -0.99, 10.1, -5.6, 2.999, 1.51}; + std::vector shape2 = {3, 2}; + std::vector exp_shape2 = {}; + auto dtype2 = legate::int32(); + std::vector exp2 = {10}; test_amax(arr2, shape2, exp2, exp_shape2, {}, dtype2); } void test_amax_axis_input() { - std::vector arr = {0.0, -0.99, 10.0, -5.0, 2.999, 1.51, -1.0, 2.99, 3.0}; - std::vector shape = {3, 1, 3}; + std::vector arr = {0.0, -0.99, 10.0, -5.0, 2.999, 1.51, -1.0, 2.99, 3.0}; + std::vector shape = {3, 1, 3}; - std::vector axis = {-1, 0, 1}; - std::vector exp_shape = {}; - std::vector exp = {10.0}; + std::vector axis = {-1, 0, 1}; + std::vector exp_shape = {}; + std::vector exp = {10.0}; test_amax(arr, shape, exp, exp_shape, axis); } void test_amax_out_input() { // Test out input with dim-1 and different datatype - std::vector arr = {-1, 4, 5, 2, 0, 3}; - std::vector shape1 = {6}; - std::vector exp_shape1 = {}; - auto df = std::nullopt; - auto out1 = cupynumeric::zeros(exp_shape1, legate::int32()); - auto out1_1 = cupynumeric::zeros(exp_shape1, legate::float64()); - std::vector exp1 = {5}; - std::vector exp1_1 = {5.0}; + std::vector arr = {-1, 4, 5, 2, 0, 3}; + std::vector shape1 = {6}; + std::vector exp_shape1 = {}; + auto df = std::nullopt; + auto out1 = cupynumeric::zeros(exp_shape1, legate::int32()); + auto out1_1 = cupynumeric::zeros(exp_shape1, legate::float64()); + std::vector exp1 = {5}; + std::vector exp1_1 = {5.0}; test_amax(arr, shape1, exp1, exp_shape1, {}, df, out1); test_amax(arr, shape1, exp1_1, exp_shape1, {}, df, out1_1); // Test out input with axis, keepdims and initial params - std::vector shape2 = {2, 3}; - std::vector exp_shape2 = {2}; - std::vector exp_shape2_k = {2, 1}; - auto out2 = cupynumeric::zeros(exp_shape2, legate::int32()); - auto out2_k = cupynumeric::zeros(exp_shape2_k, legate::int32()); - std::vector axis = {-1}; - auto ini = legate::Scalar(2); - std::vector exp2 = {5, 3}; + std::vector shape2 = {2, 3}; + std::vector exp_shape2 = {2}; + std::vector exp_shape2_k = {2, 1}; + auto out2 = cupynumeric::zeros(exp_shape2, legate::int32()); + auto out2_k = cupynumeric::zeros(exp_shape2_k, legate::int32()); + std::vector axis = {-1}; + auto ini = legate::Scalar(2); + std::vector exp2 = {5, 3}; test_amax(arr, shape2, exp2, exp_shape2, axis, df, out2); test_amax(arr, shape2, exp2, exp_shape2_k, axis, df, out2_k, true); @@ -191,39 +191,39 @@ void test_amax_max_dim() std::vector arr = {14, 10, 3, 12, 5, 13, 2, 4, 16, 8, 9, 7, 6, 11, 1, 15}; std::vector axis = {-1}; #if LEGATE_MAX_DIM >= 4 - std::vector shape_4d = {2, 2, 2, 2}; - std::vector exp_shape_4d = {2, 2, 2}; - std::vector exp_4d = {14, 12, 13, 4, 16, 9, 11, 15}; + std::vector shape_4d = {2, 2, 2, 2}; + std::vector exp_shape_4d = {2, 2, 2}; + std::vector exp_4d = {14, 12, 13, 4, 16, 9, 11, 15}; test_amax(arr, shape_4d, exp_4d, exp_shape_4d, axis); #endif #if LEGATE_MAX_DIM >= 5 - std::vector shape_5d = {1, 2, 2, 1, 4}; - std::vector exp_shape_5d = {1, 2, 2, 1}; - std::vector exp_5d = {14, 13, 16, 15}; + std::vector shape_5d = {1, 2, 2, 1, 4}; + std::vector exp_shape_5d = {1, 2, 2, 1}; + std::vector exp_5d = {14, 13, 16, 15}; test_amax(arr, shape_5d, exp_5d, exp_shape_5d, axis); #endif #if LEGATE_MAX_DIM >= 6 - std::vector shape_6d = {2, 1, 1, 2, 2, 2}; - std::vector exp_shape_6d = {2, 1, 1, 2, 2}; - std::vector exp_6d = {14, 12, 13, 4, 16, 9, 11, 15}; + std::vector shape_6d = {2, 1, 1, 2, 2, 2}; + std::vector exp_shape_6d = {2, 1, 1, 2, 2}; + std::vector exp_6d = {14, 12, 13, 4, 16, 9, 11, 15}; test_amax(arr, shape_6d, exp_6d, exp_shape_6d, axis); #endif #if LEGATE_MAX_DIM >= 7 - std::vector shape_7d = {2, 1, 1, 2, 1, 1, 4}; - std::vector exp_shape_7d = {2, 1, 1, 2, 1, 1}; - std::vector exp_7d = {14, 13, 16, 15}; + std::vector shape_7d = {2, 1, 1, 2, 1, 1, 4}; + std::vector exp_shape_7d = {2, 1, 1, 2, 1, 1}; + std::vector exp_7d = {14, 13, 16, 15}; test_amax(arr, shape_7d, exp_7d, exp_shape_7d, axis); #endif } void test_amax_large_array() { - const int32_t count = 100000; - std::vector shape = {count}; - std::vector exp_shape = {}; + const int32_t count = 100000; + std::vector shape = {count}; + std::vector exp_shape = {}; // Test int type for large array std::vector arr1(count); @@ -244,11 +244,11 @@ void test_amax_large_array() void test_amax_scalar_array() { - std::vector arr = {10}; - std::vector shape = {}; - std::vector exp = {10}; - auto out = cupynumeric::zeros(shape, legate::int32()); - auto df = std::nullopt; + std::vector arr = {10}; + std::vector shape = {}; + std::vector exp = {10}; + auto out = cupynumeric::zeros(shape, legate::int32()); + auto df = std::nullopt; test_amax(arr, shape, exp, shape); test_amax(arr, shape, exp, shape, {}, df, out); @@ -261,23 +261,23 @@ void test_amax_scalar_array() void test_amax_invalid_array() { // Test zero size array - std::vector arr1 = {}; - std::vector shape1 = {0}; - auto arr_emp = cupynumeric::mk_array(arr1, shape1); + std::vector arr1 = {}; + std::vector shape1 = {0}; + auto arr_emp = cupynumeric::mk_array(arr1, shape1); EXPECT_THROW(cupynumeric::amax(arr_emp), std::invalid_argument); // Test complex array (not supported now) std::vector> arr2 = {complex(0, 1), complex(1, 1)}; - std::vector shape2 = {2}; + std::vector shape2 = {2}; auto arr_comp = cupynumeric::mk_array>(arr2, shape2); EXPECT_THROW(cupynumeric::amax(arr_comp), std::runtime_error); } void test_amax_invalid_axis() { - std::vector arr = {1, 2, 3, 4, 5, 6}; - std::vector shape = {1, 3, 2}; - auto array = cupynumeric::mk_array(arr, shape); + std::vector arr = {1, 2, 3, 4, 5, 6}; + std::vector shape = {1, 3, 2}; + auto array = cupynumeric::mk_array(arr, shape); // Test out-of-bound std::vector axis1 = {-4, 3}; @@ -298,26 +298,26 @@ void test_amax_invalid_axis() void test_amax_invalid_shape() { - std::vector arr = {1, 2, 3, 4, 5, 6}; - std::vector shape = {1, 3, 2}; - auto array = cupynumeric::mk_array(arr, shape); - auto df = std::nullopt; + std::vector arr = {1, 2, 3, 4, 5, 6}; + std::vector shape = {1, 3, 2}; + auto array = cupynumeric::mk_array(arr, shape); + auto df = std::nullopt; - std::vector out_shape1 = {1}; - auto out1 = cupynumeric::zeros(out_shape1, legate::int32()); + std::vector out_shape1 = {1}; + auto out1 = cupynumeric::zeros(out_shape1, legate::int32()); EXPECT_THROW(cupynumeric::amax(array, {}, df, out1), std::invalid_argument); - std::vector out_shape2 = {2}; - std::vector axis2 = {1}; - auto out2 = cupynumeric::zeros(out_shape2, legate::int32()); + std::vector out_shape2 = {2}; + std::vector axis2 = {1}; + auto out2 = cupynumeric::zeros(out_shape2, legate::int32()); EXPECT_THROW(cupynumeric::amax(array, axis2, df, out2), std::invalid_argument); } void test_amax_invalid_dtype() { - std::vector arr = {1, 2, 3, 4, 5, 6}; - std::vector shape = {1, 3, 2}; - auto array = cupynumeric::mk_array(arr, shape); + std::vector arr = {1, 2, 3, 4, 5, 6}; + std::vector shape = {1, 3, 2}; + auto array = cupynumeric::mk_array(arr, shape); // Test invalid dtype auto dtype = legate::point_type(2); diff --git a/tests/cpp/integration/test_amin.cc b/tests/cpp/integration/test_amin.cc index eed3528dc5..22e135a472 100644 --- a/tests/cpp/integration/test_amin.cc +++ b/tests/cpp/integration/test_amin.cc @@ -17,9 +17,9 @@ template void test_amin(const std::vector& in_array, - const std::vector& shape, + const std::vector& shape, const std::vector& expect_result, - const std::vector& expect_shape, + const std::vector& expect_shape, std::vector axis = {}, std::optional dtype = std::nullopt, std::optional out = std::nullopt, @@ -40,9 +40,9 @@ void test_amin(const std::vector& in_array, template void test_amin_each_axis(const std::vector& arr, - const std::vector& shape, + const std::vector& shape, std::map>& expect_results, - std::map>& expect_shapes, + std::map>& expect_shapes, bool keepdims = false, std::optional initial = std::nullopt) { @@ -61,41 +61,41 @@ void test_amin_basic() { typedef std::map> IntResult; typedef std::map> DoubleResult; - typedef std::map> ShapeResult; + typedef std::map> ShapeResult; // Test int type - dim=1 - std::vector arr1 = {-1, 4, 5, 2, 0}; - std::vector shape1 = {5}; - ShapeResult exp_shape1 = {{0, {}}}; - ShapeResult exp_shape1_k = {{0, {1}}}; - IntResult exp1 = {{0, {-1}}}; + std::vector arr1 = {-1, 4, 5, 2, 0}; + std::vector shape1 = {5}; + ShapeResult exp_shape1 = {{0, {}}}; + ShapeResult exp_shape1_k = {{0, {1}}}; + IntResult exp1 = {{0, {-1}}}; test_amin_each_axis(arr1, shape1, exp1, exp_shape1); test_amin_each_axis(arr1, shape1, exp1, exp_shape1_k, true); // Test int type - dim=2 - std::vector arr2 = {1, 0, 0, 5, 3, 2}; - std::vector shape2 = {3, 2}; - ShapeResult exp_shape2 = {{0, {2}}, {1, {3}}}; - ShapeResult exp_shape2_k = {{0, {1, 2}}, {1, {3, 1}}}; - IntResult exp2 = {{0, {0, 0}}, {1, {0, 0, 2}}}; + std::vector arr2 = {1, 0, 0, 5, 3, 2}; + std::vector shape2 = {3, 2}; + ShapeResult exp_shape2 = {{0, {2}}, {1, {3}}}; + ShapeResult exp_shape2_k = {{0, {1, 2}}, {1, {3, 1}}}; + IntResult exp2 = {{0, {0, 0}}, {1, {0, 0, 2}}}; test_amin_each_axis(arr2, shape2, exp2, exp_shape2); test_amin_each_axis(arr2, shape2, exp2, exp_shape2_k, true); // Test int type - dim=3 - std::vector arr3 = {0, 11, 2, 3, -4, 0, -6, 7}; - std::vector shape3 = {2, 2, 2}; - ShapeResult exp_shape3 = {{0, {2, 2}}, {1, {2, 2}}, {2, {2, 2}}}; - ShapeResult exp_shape3_k = {{0, {1, 2, 2}}, {1, {2, 1, 2}}, {2, {2, 2, 1}}}; - IntResult exp3 = {{0, {-4, 0, -6, 3}}, {1, {0, 3, -6, 0}}, {2, {0, 2, -4, -6}}}; + std::vector arr3 = {0, 11, 2, 3, -4, 0, -6, 7}; + std::vector shape3 = {2, 2, 2}; + ShapeResult exp_shape3 = {{0, {2, 2}}, {1, {2, 2}}, {2, {2, 2}}}; + ShapeResult exp_shape3_k = {{0, {1, 2, 2}}, {1, {2, 1, 2}}, {2, {2, 2, 1}}}; + IntResult exp3 = {{0, {-4, 0, -6, 3}}, {1, {0, 3, -6, 0}}, {2, {0, 2, -4, -6}}}; test_amin_each_axis(arr3, shape3, exp3, exp_shape3); test_amin_each_axis(arr3, shape3, exp3, exp_shape3_k, true); // Test float type - dim=3 - std::vector arr4 = {0.0, -0.99, 10.0, -5.0, 2.999, 1.51, -1.0, 2.99, 3.0}; - std::vector shape4 = {3, 1, 3}; - ShapeResult exp_shape4 = {{0, {1, 3}}, {1, {3, 3}}, {2, {3, 1}}}; - ShapeResult exp_shape4_k = {{0, {1, 1, 3}}, {1, {3, 1, 3}}, {2, {3, 1, 1}}}; - DoubleResult exp4 = {{0, {-5.0, -0.99, 1.51}}, {1, arr4}, {2, {-0.99, -5.0, -1.0}}}; + std::vector arr4 = {0.0, -0.99, 10.0, -5.0, 2.999, 1.51, -1.0, 2.99, 3.0}; + std::vector shape4 = {3, 1, 3}; + ShapeResult exp_shape4 = {{0, {1, 3}}, {1, {3, 3}}, {2, {3, 1}}}; + ShapeResult exp_shape4_k = {{0, {1, 1, 3}}, {1, {3, 1, 3}}, {2, {3, 1, 1}}}; + DoubleResult exp4 = {{0, {-5.0, -0.99, 1.51}}, {1, arr4}, {2, {-0.99, -5.0, -1.0}}}; test_amin_each_axis(arr4, shape4, exp4, exp_shape4); test_amin_each_axis(arr4, shape4, exp4, exp_shape4_k, true); } @@ -104,24 +104,24 @@ void test_amin_initial_input() { typedef std::map> IntResult; typedef std::map> DoubleResult; - typedef std::map> ShapeResult; + typedef std::map> ShapeResult; - std::vector arr1 = {0, 11, 2, 3, -4, 0, -6, 7}; - std::vector shape1 = {2, 2, 2}; - ShapeResult exp_shape1 = {{0, {2, 2}}, {1, {2, 2}}, {2, {2, 2}}}; - ShapeResult exp_shape1_k = {{0, {1, 2, 2}}, {1, {2, 1, 2}}, {2, {2, 2, 1}}}; + std::vector arr1 = {0, 11, 2, 3, -4, 0, -6, 7}; + std::vector shape1 = {2, 2, 2}; + ShapeResult exp_shape1 = {{0, {2, 2}}, {1, {2, 2}}, {2, {2, 2}}}; + ShapeResult exp_shape1_k = {{0, {1, 2, 2}}, {1, {2, 1, 2}}, {2, {2, 2, 1}}}; // use initial in each axis auto initial1 = legate::Scalar(-1); IntResult exp1 = {{0, {-4, -1, -6, -1}}, {1, {-1, -1, -6, -1}}, {2, {-1, -1, -4, -6}}}; test_amin_each_axis(arr1, shape1, exp1, exp_shape1, false, initial1); test_amin_each_axis(arr1, shape1, exp1, exp_shape1_k, true, initial1); - std::vector arr2 = {0.0, -0.99, 10.0, -5.0, 2.999, 1.51, -1.0, 2.99, 3.0}; - std::vector shape2 = {3, 3}; - ShapeResult exp_shape2 = {{0, {3}}, {1, {3}}}; - ShapeResult exp_shape2_k = {{0, {1, 3}}, {1, {3, 1}}}; - auto initial2 = legate::Scalar(0.0); - DoubleResult exp2 = {{0, {-5.0, -0.99, 0.0}}, {1, {-0.99, -5.0, -1.0}}}; + std::vector arr2 = {0.0, -0.99, 10.0, -5.0, 2.999, 1.51, -1.0, 2.99, 3.0}; + std::vector shape2 = {3, 3}; + ShapeResult exp_shape2 = {{0, {3}}, {1, {3}}}; + ShapeResult exp_shape2_k = {{0, {1, 3}}, {1, {3, 1}}}; + auto initial2 = legate::Scalar(0.0); + DoubleResult exp2 = {{0, {-5.0, -0.99, 0.0}}, {1, {-0.99, -5.0, -1.0}}}; test_amin_each_axis(arr2, shape2, exp2, exp_shape2, false, initial2); test_amin_each_axis(arr2, shape2, exp2, exp_shape2_k, true, initial2); } @@ -129,56 +129,56 @@ void test_amin_initial_input() void test_amin_dtype_input() { // int to float - std::vector arr1 = {-1, 4, 5, 2, 0}; - std::vector shape1 = {5}; - std::vector exp_shape1 = {}; - auto dtype1 = legate::float64(); - std::vector exp1 = {-1.0}; + std::vector arr1 = {-1, 4, 5, 2, 0}; + std::vector shape1 = {5}; + std::vector exp_shape1 = {}; + auto dtype1 = legate::float64(); + std::vector exp1 = {-1.0}; test_amin(arr1, shape1, exp1, exp_shape1, {}, dtype1); // float to int - std::vector arr2 = {0.0, -0.99, 10.1, -5.6, 2.999, 1.51}; - std::vector shape2 = {3, 2}; - std::vector exp_shape2 = {}; - auto dtype2 = legate::int32(); - std::vector exp2 = {-5}; + std::vector arr2 = {0.0, -0.99, 10.1, -5.6, 2.999, 1.51}; + std::vector shape2 = {3, 2}; + std::vector exp_shape2 = {}; + auto dtype2 = legate::int32(); + std::vector exp2 = {-5}; test_amin(arr2, shape2, exp2, exp_shape2, {}, dtype2); } void test_amin_axis_input() { - std::vector arr = {0.0, -0.99, 10.0, -5.0, 2.999, 1.51, -1.0, 2.99, 3.0}; - std::vector shape = {3, 1, 3}; + std::vector arr = {0.0, -0.99, 10.0, -5.0, 2.999, 1.51, -1.0, 2.99, 3.0}; + std::vector shape = {3, 1, 3}; - std::vector axis = {-1, 0, 1}; - std::vector exp_shape = {}; - std::vector exp = {-5.0}; + std::vector axis = {-1, 0, 1}; + std::vector exp_shape = {}; + std::vector exp = {-5.0}; test_amin(arr, shape, exp, exp_shape, axis); } void test_amin_out_input() { // Test out input with dim-1 and different datatype - std::vector arr = {-1, 4, 5, 2, 0, 3}; - std::vector shape1 = {6}; - std::vector exp_shape1 = {}; - auto df = std::nullopt; - auto out1 = cupynumeric::zeros(exp_shape1, legate::int32()); - auto out1_1 = cupynumeric::zeros(exp_shape1, legate::float64()); - std::vector exp1 = {-1}; - std::vector exp1_1 = {-1.0}; + std::vector arr = {-1, 4, 5, 2, 0, 3}; + std::vector shape1 = {6}; + std::vector exp_shape1 = {}; + auto df = std::nullopt; + auto out1 = cupynumeric::zeros(exp_shape1, legate::int32()); + auto out1_1 = cupynumeric::zeros(exp_shape1, legate::float64()); + std::vector exp1 = {-1}; + std::vector exp1_1 = {-1.0}; test_amin(arr, shape1, exp1, exp_shape1, {}, df, out1); test_amin(arr, shape1, exp1_1, exp_shape1, {}, df, out1_1); // Test out input with axis, keepdims and initial params - std::vector shape2 = {2, 3}; - std::vector exp_shape2 = {2}; - std::vector exp_shape2_k = {2, 1}; - auto out2 = cupynumeric::zeros(exp_shape2, legate::int32()); - auto out2_k = cupynumeric::zeros(exp_shape2_k, legate::int32()); - std::vector axis = {-1}; - auto ini = legate::Scalar(2); - std::vector exp2 = {-1, 0}; + std::vector shape2 = {2, 3}; + std::vector exp_shape2 = {2}; + std::vector exp_shape2_k = {2, 1}; + auto out2 = cupynumeric::zeros(exp_shape2, legate::int32()); + auto out2_k = cupynumeric::zeros(exp_shape2_k, legate::int32()); + std::vector axis = {-1}; + auto ini = legate::Scalar(2); + std::vector exp2 = {-1, 0}; test_amin(arr, shape2, exp2, exp_shape2, axis, df, out2); test_amin(arr, shape2, exp2, exp_shape2_k, axis, df, out2_k, true); @@ -191,39 +191,39 @@ void test_amin_max_dim() std::vector arr = {14, 10, 3, 12, 5, 13, 2, 4, 16, 8, 9, 7, 6, 11, 1, 15}; std::vector axis = {-1}; #if LEGATE_MAX_DIM >= 4 - std::vector shape_4d = {2, 2, 2, 2}; - std::vector exp_shape_4d = {2, 2, 2}; - std::vector exp_4d = {10, 3, 5, 2, 8, 7, 6, 1}; + std::vector shape_4d = {2, 2, 2, 2}; + std::vector exp_shape_4d = {2, 2, 2}; + std::vector exp_4d = {10, 3, 5, 2, 8, 7, 6, 1}; test_amin(arr, shape_4d, exp_4d, exp_shape_4d, axis); #endif #if LEGATE_MAX_DIM >= 5 - std::vector shape_5d = {1, 2, 2, 1, 4}; - std::vector exp_shape_5d = {1, 2, 2, 1}; - std::vector exp_5d = {3, 2, 7, 1}; + std::vector shape_5d = {1, 2, 2, 1, 4}; + std::vector exp_shape_5d = {1, 2, 2, 1}; + std::vector exp_5d = {3, 2, 7, 1}; test_amin(arr, shape_5d, exp_5d, exp_shape_5d, axis); #endif #if LEGATE_MAX_DIM >= 6 - std::vector shape_6d = {2, 1, 1, 2, 2, 2}; - std::vector exp_shape_6d = {2, 1, 1, 2, 2}; - std::vector exp_6d = {10, 3, 5, 2, 8, 7, 6, 1}; + std::vector shape_6d = {2, 1, 1, 2, 2, 2}; + std::vector exp_shape_6d = {2, 1, 1, 2, 2}; + std::vector exp_6d = {10, 3, 5, 2, 8, 7, 6, 1}; test_amin(arr, shape_6d, exp_6d, exp_shape_6d, axis); #endif #if LEGATE_MAX_DIM >= 7 - std::vector shape_7d = {2, 1, 1, 2, 1, 1, 4}; - std::vector exp_shape_7d = {2, 1, 1, 2, 1, 1}; - std::vector exp_7d = {3, 2, 7, 1}; + std::vector shape_7d = {2, 1, 1, 2, 1, 1, 4}; + std::vector exp_shape_7d = {2, 1, 1, 2, 1, 1}; + std::vector exp_7d = {3, 2, 7, 1}; test_amin(arr, shape_7d, exp_7d, exp_shape_7d, axis); #endif } void test_amin_large_array() { - const int32_t count = 100000; - std::vector shape = {count}; - std::vector exp_shape = {}; + const int32_t count = 100000; + std::vector shape = {count}; + std::vector exp_shape = {}; // Test int type for large array std::vector arr1(count); @@ -244,11 +244,11 @@ void test_amin_large_array() void test_amin_scalar_array() { - std::vector arr = {10}; - std::vector shape = {}; - std::vector exp = {10}; - auto out = cupynumeric::zeros(shape, legate::int32()); - auto df = std::nullopt; + std::vector arr = {10}; + std::vector shape = {}; + std::vector exp = {10}; + auto out = cupynumeric::zeros(shape, legate::int32()); + auto df = std::nullopt; test_amin(arr, shape, exp, shape); test_amin(arr, shape, exp, shape, {}, df, out); @@ -261,23 +261,23 @@ void test_amin_scalar_array() void test_amin_invalid_array() { // Test zero size array - std::vector arr1 = {}; - std::vector shape1 = {0}; - auto arr_emp = cupynumeric::mk_array(arr1, shape1); + std::vector arr1 = {}; + std::vector shape1 = {0}; + auto arr_emp = cupynumeric::mk_array(arr1, shape1); EXPECT_THROW(cupynumeric::amin(arr_emp), std::invalid_argument); // Test complex array (not supported now) std::vector> arr2 = {complex(0, 1), complex(1, 1)}; - std::vector shape2 = {2}; + std::vector shape2 = {2}; auto arr_comp = cupynumeric::mk_array>(arr2, shape2); EXPECT_THROW(cupynumeric::amin(arr_comp), std::runtime_error); } void test_amin_invalid_axis() { - std::vector arr = {1, 2, 3, 4, 5, 6}; - std::vector shape = {1, 3, 2}; - auto array = cupynumeric::mk_array(arr, shape); + std::vector arr = {1, 2, 3, 4, 5, 6}; + std::vector shape = {1, 3, 2}; + auto array = cupynumeric::mk_array(arr, shape); // Test out-of-bound std::vector axis1 = {-4, 3}; @@ -298,26 +298,26 @@ void test_amin_invalid_axis() void test_amin_invalid_shape() { - std::vector arr = {1, 2, 3, 4, 5, 6}; - std::vector shape = {1, 3, 2}; - auto array = cupynumeric::mk_array(arr, shape); - auto df = std::nullopt; + std::vector arr = {1, 2, 3, 4, 5, 6}; + std::vector shape = {1, 3, 2}; + auto array = cupynumeric::mk_array(arr, shape); + auto df = std::nullopt; - std::vector out_shape1 = {1}; - auto out1 = cupynumeric::zeros(out_shape1, legate::int32()); + std::vector out_shape1 = {1}; + auto out1 = cupynumeric::zeros(out_shape1, legate::int32()); EXPECT_THROW(cupynumeric::amin(array, {}, df, out1), std::invalid_argument); - std::vector out_shape2 = {2}; - std::vector axis2 = {1}; - auto out2 = cupynumeric::zeros(out_shape2, legate::int32()); + std::vector out_shape2 = {2}; + std::vector axis2 = {1}; + auto out2 = cupynumeric::zeros(out_shape2, legate::int32()); EXPECT_THROW(cupynumeric::amin(array, axis2, df, out2), std::invalid_argument); } void test_amin_invalid_dtype() { - std::vector arr = {1, 2, 3, 4, 5, 6}; - std::vector shape = {1, 3, 2}; - auto array = cupynumeric::mk_array(arr, shape); + std::vector arr = {1, 2, 3, 4, 5, 6}; + std::vector shape = {1, 3, 2}; + auto array = cupynumeric::mk_array(arr, shape); // Test invalid dtype auto dtype = legate::point_type(2); diff --git a/tests/cpp/integration/test_dot.cc b/tests/cpp/integration/test_dot.cc index cc4a87cb03..8d09defca8 100644 --- a/tests/cpp/integration/test_dot.cc +++ b/tests/cpp/integration/test_dot.cc @@ -22,12 +22,12 @@ #include "cupynumeric.h" #include "common_utils.h" -std::vector calc_c_shape_scalar(const std::vector& a_shape, - const std::vector& b_shape) +std::vector calc_c_shape_scalar(const std::vector& a_shape, + const std::vector& b_shape) { assert(a_shape.size() == 0 || b_shape.size() == 0); - std::vector c_shape; + std::vector c_shape; if (a_shape.size() == 0 && b_shape.size() == 0) { c_shape = {}; } else { @@ -38,10 +38,10 @@ std::vector calc_c_shape_scalar(const std::vector& a_shape, template std::vector calc_result_scalar(const std::vector& vec_a, - const std::vector& a_shape, + const std::vector& a_shape, const std::vector& vec_b, - const std::vector& b_shape, - const std::vector& c_shape) + const std::vector& b_shape, + const std::vector& c_shape) { if (a_shape.size() == 0) { assert(vec_a.size() == 1); @@ -64,11 +64,11 @@ std::vector calc_result_scalar(const std::vector& vec_a, return vec_c; } -std::vector calc_c_shape_b_is_vector(const std::vector& a_shape, - const std::vector& b_shape) +std::vector calc_c_shape_b_is_vector(const std::vector& a_shape, + const std::vector& b_shape) { assert(a_shape[a_shape.size() - 1] == b_shape[b_shape.size() - 1]); - std::vector c_shape; + std::vector c_shape; int a_size_in_c = 1; for (int i = 0; i < a_shape.size() - 1; i++) { c_shape.push_back(a_shape[i]); @@ -79,10 +79,10 @@ std::vector calc_c_shape_b_is_vector(const std::vector& a_shape, template std::vector calc_result_b_is_vector(const std::vector& vec_a, - const std::vector& a_shape, + const std::vector& a_shape, const std::vector& vec_b, - const std::vector& b_shape, - const std::vector& c_shape) + const std::vector& b_shape, + const std::vector& c_shape) { int a_size_in_c = 1; for (int i = 0; i < a_shape.size() - 1; i++) { @@ -103,10 +103,10 @@ std::vector calc_result_b_is_vector(const std::vector& vec_a, return vec_c; } -std::vector calc_c_shape_contract(const std::vector& a_shape, - const std::vector& b_shape) +std::vector calc_c_shape_contract(const std::vector& a_shape, + const std::vector& b_shape) { - std::vector c_shape = {}; + std::vector c_shape = {}; for (int i = 0; i < a_shape.size() - 1; i++) { c_shape.push_back(a_shape[i]); } @@ -119,10 +119,10 @@ std::vector calc_c_shape_contract(const std::vector& a_shape, template std::vector calc_result_contract(const std::vector& vec_a, - const std::vector& a_shape, + const std::vector& a_shape, const std::vector& vec_b, - const std::vector& b_shape, - const std::vector& c_shape) + const std::vector& b_shape, + const std::vector& c_shape) { int a_size_in_c = 1, b_size_in_c = 1; for (int i = 0; i < a_shape.size() - 1; i++) { @@ -165,9 +165,9 @@ void verify_dot_output(cupynumeric::NDArray A, cupynumeric::NDArray B, cupynumer auto vec_a = cupynumeric::to_vector(A); auto vec_b = cupynumeric::to_vector(B); std::vector vec_c; - auto a_shape = A.shape(); - auto b_shape = B.shape(); - std::vector vec_c_shape = {}; + auto a_shape = A.shape(); + auto b_shape = B.shape(); + std::vector vec_c_shape = {}; if (A.dim() == 0 || B.dim() == 0) { vec_c_shape = calc_c_shape_scalar(a_shape, b_shape); @@ -188,7 +188,7 @@ void verify_dot_output(cupynumeric::NDArray A, cupynumeric::NDArray B, cupynumer } template -void test_contract_full(std::vector a_shape, std::vector b_shape) +void test_contract_full(std::vector a_shape, std::vector b_shape) { auto leg_type = legate::primitive_type(legate::type_code_of_v); if (leg_type == legate::float64()) { @@ -209,7 +209,7 @@ void test_contract_full(std::vector a_shape, std::vector b_shape } template -void test_contract_standard(std::vector a_shape, std::vector b_shape) +void test_contract_standard(std::vector a_shape, std::vector b_shape) { auto A = a_shape.size() == 0 @@ -221,8 +221,8 @@ void test_contract_standard(std::vector a_shape, std::vector b_s : cupynumeric::random(b_shape).as_type(legate::primitive_type(legate::type_code_of_v)); auto C = cupynumeric::dot(A, B); - auto leg_type = legate::primitive_type(legate::type_code_of_v); - std::vector vec_c_shape = {}; + auto leg_type = legate::primitive_type(legate::type_code_of_v); + std::vector vec_c_shape = {}; if (A.dim() == 0 || B.dim() == 0) { vec_c_shape = calc_c_shape_scalar(a_shape, b_shape); } else if (B.dim() == 1 && A.dim() >= 1) { @@ -349,4 +349,4 @@ TEST(Dot, AllDouble) { test_contract_full_all(); } TEST(Dot, AllComplex64) { test_contract_standard_all>(); } -TEST(Dot, AllComplex128) { test_contract_standard_all>(); } \ No newline at end of file +TEST(Dot, AllComplex128) { test_contract_standard_all>(); } diff --git a/tests/cpp/integration/test_repartition.cc b/tests/cpp/integration/test_repartition.cc index f144bcca5b..09d20e9adb 100644 --- a/tests/cpp/integration/test_repartition.cc +++ b/tests/cpp/integration/test_repartition.cc @@ -35,8 +35,8 @@ enum TaskIDs { template struct CheckRepartitionTask : public legate::LegateTask> { - static constexpr auto TASK_ID = - legate::LocalTaskID{CHECK_REPARTITION_TASK + I_ROW_MAJOR * 2 + O_ROW_MAJOR}; + static inline const auto TASK_CONFIG = + legate::TaskConfig{legate::LocalTaskID{CHECK_REPARTITION_TASK + I_ROW_MAJOR * 2 + O_ROW_MAJOR}}; static void gpu_variant(legate::TaskContext context); }; diff --git a/tests/cpp/integration/test_reshape.cc b/tests/cpp/integration/test_reshape.cc index c1ed9534a1..640c92c183 100644 --- a/tests/cpp/integration/test_reshape.cc +++ b/tests/cpp/integration/test_reshape.cc @@ -45,7 +45,7 @@ TEST_F(Reshape_TestSquare, test_shape) { for (auto shape : SQUARE_CASES) { auto a = arange(100).reshape({10, 10}); - check_array(reshape(a, shape), a_gt, as_type_vector(shape)); + check_array(reshape(a, shape), a_gt, as_type_vector(shape)); } { auto a = arange(100).reshape({10, 10}); @@ -61,7 +61,7 @@ TEST_F(Reshape_TestSquare, test_shape_mode) if (order == "F") { EXPECT_THROW(reshape(a, shape, order), std::invalid_argument); } else { - check_array(reshape(a, shape, order), a_gt, as_type_vector(shape)); + check_array(reshape(a, shape, order), a_gt, as_type_vector(shape)); } } { @@ -115,7 +115,7 @@ TEST_F(Reshape_TestRect, test_shape) { for (auto shape : RECT_CASES) { auto a = mk_array(a_gt, {5, 4, 10}); - check_array(reshape(a, shape), a_gt, as_type_vector(shape)); + check_array(reshape(a, shape), a_gt, as_type_vector(shape)); } { auto a = mk_array(a_gt, {5, 4, 10}); @@ -131,7 +131,7 @@ TEST_F(Reshape_TestRect, test_shape_mode) if (order == "F") { EXPECT_THROW(reshape(a, shape, order), std::invalid_argument); } else { - check_array(reshape(a, shape, order), a_gt, as_type_vector(shape)); + check_array(reshape(a, shape, order), a_gt, as_type_vector(shape)); } } { @@ -166,13 +166,13 @@ TEST(Reshape, test_reshape_empty_array) }; auto a = mk_array({}, {0, 1}); for (auto shape : shape_list) { - check_array(reshape(a, shape), {}, as_type_vector(shape)); + check_array(reshape(a, shape), {}, as_type_vector(shape)); } } TEST(Reshape, test_reshape_same_shape) { - std::vector shape{1, 2, 3}; + std::vector shape{1, 2, 3}; auto a_gt = mk_seq_vector(shape); auto a = mk_array(a_gt, shape); auto a_out = reshape(a, as_type_vector(shape)); diff --git a/tests/cpp/integration/test_squeeze.cc b/tests/cpp/integration/test_squeeze.cc index f2147b3bf7..1313afbdc3 100644 --- a/tests/cpp/integration/test_squeeze.cc +++ b/tests/cpp/integration/test_squeeze.cc @@ -20,13 +20,13 @@ using namespace cupynumeric; namespace { -typedef std::vector, std::vector>> VEC_SHAPE_AXES; +typedef std::vector, std::vector>> VEC_SHAPE_AXES; -std::vector squeeze_result( - const std::vector& shape, +std::vector squeeze_result( + const std::vector& shape, std::optional const>> axes = std::nullopt) { - std::vector result; + std::vector result; if (!axes.has_value()) { for (int i = 0; i < shape.size(); i++) { if (shape[i] != 1) { @@ -54,7 +54,7 @@ std::vector squeeze_result( } void test_squeeze( - const std::vector& shape, + const std::vector& shape, std::optional const>> axes = std::nullopt) { auto vec_a = mk_seq_vector(shape); @@ -64,8 +64,8 @@ void test_squeeze( check_array(x, vec_a, result_shape); } -static constexpr int32_t DIM = 5; -std::vector> SIZES = { +static constexpr int32_t DIM = 5; +std::vector> SIZES = { {}, { 0, @@ -170,7 +170,7 @@ TEST(Squeeze, AxesNegative) TEST(Squeeze, InvalidAxesNotEqualToOne) { - std::vector shape = {1, 2, 1}; + std::vector shape = {1, 2, 1}; std::vector> vec_axes = {{ 1, }, @@ -184,7 +184,7 @@ TEST(Squeeze, InvalidAxesNotEqualToOne) TEST(Squeeze, InvalidAxesOutOfBound) { - std::vector shape = {1, 2, 1}; + std::vector shape = {1, 2, 1}; std::vector> vec_axes = {{ 3, }, @@ -200,7 +200,7 @@ TEST(Squeeze, InvalidAxesOutOfBound) TEST(Squeeze, InvalidAxesDuplicate) { - std::vector shape = {1, 2, 1}; + std::vector shape = {1, 2, 1}; std::vector> vec_axes = {{0, -3}, {-1, 0, 2}}; auto vec_a = mk_seq_vector(shape); auto arr_a = mk_array(vec_a, shape); diff --git a/tests/cpp/integration/test_unique.cc b/tests/cpp/integration/test_unique.cc index baf3702a69..18cc112812 100644 --- a/tests/cpp/integration/test_unique.cc +++ b/tests/cpp/integration/test_unique.cc @@ -62,9 +62,9 @@ TEST(Unique, test_scalar) } template -std::vector mk_random_vector(std::vector shape, std::function gen) +std::vector mk_random_vector(std::vector shape, std::function gen) { - size_t size = std::accumulate(shape.begin(), shape.end(), size_t(1), std::multiplies()); + size_t size = std::accumulate(shape.begin(), shape.end(), size_t(1), std::multiplies()); std::vector v(size); std::generate(v.begin(), v.end(), gen); return v; @@ -75,7 +75,7 @@ static int randint(int low, int high) { return rand() % (high - low) + low; } TEST(Unique, test_ndim) { srand(111); - std::vector shape; + std::vector shape; size_t size = 1; for (int32_t ndim = 1; ndim <= LEGATE_MAX_DIM; ++ndim) { shape.emplace_back(4); diff --git a/tests/cpp/integration/test_where.cc b/tests/cpp/integration/test_where.cc index 52eea72c9a..b8684dd3bd 100644 --- a/tests/cpp/integration/test_where.cc +++ b/tests/cpp/integration/test_where.cc @@ -28,21 +28,21 @@ using namespace cupynumeric; template void test_where_basic(std::vector in_a, std::vector>& exp_vec, - std::vector in_shape) + std::vector in_shape) { auto A = mk_array(in_a, in_shape); auto B = where(A); assert(exp_vec.size() == B.size()); for (size_t i = 0; i < B.size(); i++) { - auto exp_arr = exp_vec[i]; - std::vector exp_shape = {exp_arr.size()}; + auto exp_arr = exp_vec[i]; + std::vector exp_shape = {exp_arr.size()}; check_array(B[i], exp_arr, exp_shape); } } template void test_where_full( - NDArray A, NDArray X, NDArray Y, std::vector exp_arr, std::vector exp_shape) + NDArray A, NDArray X, NDArray Y, std::vector exp_arr, std::vector exp_shape) { auto B = where(A, X, Y); check_array(B, exp_arr, exp_shape); @@ -51,7 +51,7 @@ void test_where_full( TEST(Where, Basic) { std::vector in_a = {-1, 54, 4, 4, 0, 45, 5, 58, 0, 9, 0, 4, 0, 0, 0, 5, 0, 1}; - std::vector> test_shapes = {{18}, {6, 3}, {3, 2, 3}}; + std::vector> test_shapes = {{18}, {6, 3}, {3, 2, 3}}; std::vector exp_vec1_1 = {0, 1, 2, 3, 5, 6, 7, 9, 11, 15, 17}; std::vector> exp_vec1 = {exp_vec1_1}; @@ -71,9 +71,9 @@ TEST(Where, Basic) TEST(Where, Condition) { - std::vector shape = {2, 2}; - auto X = mk_array({1, 2, 3, 4}, shape); - auto Y = mk_array({9, 8, 7, 6}, shape); + std::vector shape = {2, 2}; + auto X = mk_array({1, 2, 3, 4}, shape); + auto Y = mk_array({9, 8, 7, 6}, shape); auto A1 = mk_array({true, false, true, true}, shape); test_where_full(A1, X, Y, {1, 8, 3, 4}, shape); @@ -93,16 +93,16 @@ TEST(Where, Condition) TEST(Where, Type) { - std::vector shape = {2, 2}; - auto A = mk_array({true, false, true, true}, shape); - auto X_BOOL = mk_array({true, false, true, false}, shape); - auto X_INT = mk_array({1, 2, 3, 4}, shape); - auto X_FLOAT = mk_array({1, 2, 3, 4}, shape); - auto X_COMPLEX128 = mk_array>({1, 2, 3, 4}, shape); - auto Y_BOOL = mk_array({false, true, true, false}, shape); - auto Y_INT = mk_array({9, 8, 7, 6}, shape); - auto Y_FLOAT = mk_array({9, 8, 7, 6}, shape); - auto Y_COMPLEX128 = mk_array>({9, 8, 7, 6}, shape); + std::vector shape = {2, 2}; + auto A = mk_array({true, false, true, true}, shape); + auto X_BOOL = mk_array({true, false, true, false}, shape); + auto X_INT = mk_array({1, 2, 3, 4}, shape); + auto X_FLOAT = mk_array({1, 2, 3, 4}, shape); + auto X_COMPLEX128 = mk_array>({1, 2, 3, 4}, shape); + auto Y_BOOL = mk_array({false, true, true, false}, shape); + auto Y_INT = mk_array({9, 8, 7, 6}, shape); + auto Y_FLOAT = mk_array({9, 8, 7, 6}, shape); + auto Y_COMPLEX128 = mk_array>({9, 8, 7, 6}, shape); test_where_full(A, X_BOOL, Y_BOOL, {true, true, true, false}, shape); diff --git a/tests/cpp/integration/test_window.cc b/tests/cpp/integration/test_window.cc index 3ca0631702..456c53a072 100644 --- a/tests/cpp/integration/test_window.cc +++ b/tests/cpp/integration/test_window.cc @@ -24,14 +24,14 @@ namespace { struct windows_case { int64_t input; std::vector expected_values; - std::vector expected_shape; + std::vector expected_shape; }; struct kaiser_case { int64_t input; double beta_input; std::vector expected_values; - std::vector expected_shape; + std::vector expected_shape; }; class NormalInput : public ::testing::Test, public ::testing::WithParamInterface {}; @@ -193,4 +193,4 @@ TEST_P(KaiserTest, Basic) check_array_near(result, expected_values, expected_shape); } -} // namespace \ No newline at end of file +} // namespace From e7356d3edf33aa68b9cb910d12476cab481e98b0 Mon Sep 17 00:00:00 2001 From: Irina Demeshko Date: Tue, 11 Mar 2025 23:19:35 -0700 Subject: [PATCH 439/462] updating legate commit hash (#648) --- cmake/versions.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmake/versions.json b/cmake/versions.json index 9d97adfb36..ee6bc22fdf 100644 --- a/cmake/versions.json +++ b/cmake/versions.json @@ -10,7 +10,7 @@ "git_url" : "git@github.com:nv-legate/legate.internal.git", "git_shallow": false, "always_download": false, - "git_tag" : "37d52d7c7d0a6fa8c27224115334c97daf6f7cb7", + "git_tag" : "9f48a1b40d6028ac86e1d4e8a6ce2a7d5c84245f", "anaconda_label": "experimental" } } From f22ae8cd75c3b45fbb6c45acaad4fa54d9e68581 Mon Sep 17 00:00:00 2001 From: Sandeep Datta <128171450+sandeepd-nv@users.noreply.github.com> Date: Wed, 12 Mar 2025 21:35:48 +0530 Subject: [PATCH 440/462] Add separate docs workflow and push nightly docs to gh pages. (#611) --- .github/workflows/ci-gh-docs.yml | 46 +++++++ .github/workflows/ci-gh-nightly-release.yml | 22 ++++ .github/workflows/gh-build-and-test.yml | 21 +-- .github/workflows/gh-build-docs.yml | 122 ++++++++++++++++++ .../scripts/conda-dnld-utils | 2 +- continuous_integration/scripts/make-conda-env | 22 +++- continuous_integration/scripts/test | 17 ++- 7 files changed, 235 insertions(+), 17 deletions(-) create mode 100644 .github/workflows/ci-gh-docs.yml create mode 100644 .github/workflows/gh-build-docs.yml diff --git a/.github/workflows/ci-gh-docs.yml b/.github/workflows/ci-gh-docs.yml new file mode 100644 index 0000000000..349cf79e95 --- /dev/null +++ b/.github/workflows/ci-gh-docs.yml @@ -0,0 +1,46 @@ +--- +name: Docs + +concurrency: + group: ${{ startsWith(github.ref_name, 'main') && format('unique-{0}', github.run_id) || format('ci-build-docs-on-{0}-from-{1}', github.event_name, github.ref_name) }} + cancel-in-progress: true + +on: + push: + branches: + - "pull-request/[0-9]+" + - "branch-*" + - "main" + merge_group: + +jobs: + build-and-test: + name: Build documentation (${{ matrix.platform }}, ${{ matrix.target-device }}, ${{ matrix.build-mode }}, ucx enabled) + strategy: + fail-fast: false + matrix: + platform: + - linux + target-device: + - gpu + build-mode: + - release + uses: + ./.github/workflows/gh-build-docs.yml + with: + platform: ${{ matrix.platform }} + target-device: ${{ matrix.target-device }} + build-mode: ${{ matrix.build-mode }} + build-type: ci + upload-docs-to-gh-pages: false + secrets: inherit + + docs-pass: + if: always() + needs: + - build-and-test + runs-on: linux-amd64-cpu4 + steps: + - name: Check job results + if: contains(needs.*.result, 'failure') || contains(needs.*.result, 'cancelled') + run: exit 1 diff --git a/.github/workflows/ci-gh-nightly-release.yml b/.github/workflows/ci-gh-nightly-release.yml index 44a15644de..deecffaa5d 100644 --- a/.github/workflows/ci-gh-nightly-release.yml +++ b/.github/workflows/ci-gh-nightly-release.yml @@ -9,6 +9,7 @@ on: schedule: - cron: '0 23 * * *' # Nightly at 11:00 PM + jobs: build-and-test: strategy: @@ -39,3 +40,24 @@ jobs: refname: ${{ github.ref_name }} default-branch: ${{ github.event.repository.default_branch }} secrets: inherit + + build-nightly-docs: + name: Build Nightly documentation (${{ matrix.platform }}, ${{ matrix.target-device }}, ${{ matrix.build-mode }}, ucx enabled) + strategy: + fail-fast: false + matrix: + platform: + - linux + target-device: + - gpu + build-mode: + - release + uses: + ./.github/workflows/gh-build-docs.yml + with: + platform: ${{ matrix.platform }} + target-device: ${{ matrix.target-device }} + build-mode: ${{ matrix.build-mode }} + build-type: nightly + upload-docs-to-gh-pages: true + secrets: inherit diff --git a/.github/workflows/gh-build-and-test.yml b/.github/workflows/gh-build-and-test.yml index 9751b4a226..e4d0fbe3a5 100644 --- a/.github/workflows/gh-build-and-test.yml +++ b/.github/workflows/gh-build-and-test.yml @@ -53,13 +53,14 @@ jobs: needs: setup-build name: "Build (${{ inputs.platform }}, ${{ inputs.target-device }}, ${{ inputs.build-type }}, Python ${{ inputs.python-version }})" uses: - nv-legate/legate-gh-ci/.github/workflows/gh-build.yml@v1.26 + nv-legate/legate-gh-ci/.github/workflows/gh-build.yml@push_nightly_docs_to_gh_pages with: + build-has-tests: false build-mode: "" build-type: ${{ inputs.build-type }} client-repo: ${{ github.event.repository.name }} dependencies-file: "" - legate-gh-ci-tag: "v1.26" + legate-gh-ci-tag: "push_nightly_docs_to_gh_pages" network: "ucx" platform: ${{ inputs.platform }} python-version: ${{ inputs.python-version }} @@ -74,12 +75,13 @@ jobs: if: ${{ github.repository_owner == 'nv-legate' && contains(github.workflow, 'release') && inputs.upload-enabled == true }} name: Upload package to Server uses: - nv-legate/legate-gh-ci/.github/workflows/gh-upload.yml@v1.26 + nv-legate/legate-gh-ci/.github/workflows/gh-upload.yml@push_nightly_docs_to_gh_pages with: + build-has-tests: false build-mode: "" build-type: ${{ inputs.build-type }} client-repo: ${{ github.event.repository.name }} - legate-gh-ci-tag: "v1.26" + legate-gh-ci-tag: "push_nightly_docs_to_gh_pages" name: Upload package to Server network: "ucx" pkgSubString: "cupynumeric-" @@ -127,7 +129,6 @@ jobs: 'Eager execution test:test --use eager --debug:gpu' 'Eager execution test:test --use eager --debug:cpu' 'mypy:mypy:cpu' - 'Documentation:docs:cpu' 'Unit tests:unit:cpu' ) for RUNNER in "${RUNNERS[@]}"; do @@ -164,13 +165,14 @@ jobs: matrix: ${{fromJson(needs.setup-test.outputs.matrix)}} uses: - nv-legate/legate-gh-ci/.github/workflows/gh-test-within-container.yml@v1.26 + nv-legate/legate-gh-ci/.github/workflows/gh-test-within-container.yml@push_nightly_docs_to_gh_pages with: + build-has-tests: false build-mode: "" build-type: ${{ inputs.build-type }} client-repo: ${{ github.event.repository.name }} has-gpu: ${{ matrix.runner.type == 'gpu' }} - legate-gh-ci-tag: "v1.26" + legate-gh-ci-tag: "push_nightly_docs_to_gh_pages" name: ${{ matrix.test-config.name }} network: "ucx" platform: ${{ inputs.platform }} @@ -186,12 +188,13 @@ jobs: name: Update Test status on Server if: ${{ false }} uses: - nv-legate/legate-gh-ci/.github/workflows/gh-upload.yml@v1.26 + nv-legate/legate-gh-ci/.github/workflows/gh-upload.yml@push_nightly_docs_to_gh_pages with: + build-has-tests: false build-mode: "" build-type: ${{ inputs.build-type }} client-repo: ${{ github.event.repository.name }} - legate-gh-ci-tag: "v1.26" + legate-gh-ci-tag: "push_nightly_docs_to_gh_pages" name: UpdateTestStatus network: "ucx" pkgSubString: "cupynumeric-" diff --git a/.github/workflows/gh-build-docs.yml b/.github/workflows/gh-build-docs.yml new file mode 100644 index 0000000000..fbc1745b1e --- /dev/null +++ b/.github/workflows/gh-build-docs.yml @@ -0,0 +1,122 @@ +--- +on: + workflow_call: + inputs: + platform: + type: string + required: true + target-device: + type: string + required: true + build-mode: + type: string + required: true + build-type: + type: string + required: true + upload-docs-to-gh-pages: + type: boolean + required: false + default: false + +jobs: + build-cupynumeric: + if: ${{ github.repository_owner == 'nv-legate' }} + uses: + nv-legate/legate-gh-ci/.github/workflows/gh-build.yml@push_nightly_docs_to_gh_pages + with: + build-has-tests: false + client-repo: ${{ github.event.repository.name }} + target-device: ${{ inputs.target-device }} + runs-on: ${{ (inputs.platform == 'linux' && 'linux-amd64-cpu4') || (inputs.platform == 'mac' && 'macos-latest') }} + build-type: ${{ inputs.build-type }} + use-container: ${{ inputs.platform == 'linux' }} + platform: ${{ inputs.platform }} + dependencies-file: "" + legate-gh-ci-tag: "push_nightly_docs_to_gh_pages" + build-mode: ${{ inputs.build-mode }} + upload-enabled: false + network: "ucx" + secrets: inherit + + + build-docs: + needs: + - build-cupynumeric + name: Build cupynumeric docs (${{ inputs.platform }}, ${{ inputs.target-device }}) + + uses: + nv-legate/legate-gh-ci/.github/workflows/gh-test-within-container.yml@push_nightly_docs_to_gh_pages + with: + build-has-tests: false + build-mode: ${{ inputs.build-mode }} + build-type: ${{ inputs.build-type }} + output-build-type: docs + client-repo: ${{ github.event.repository.name }} + has-gpu: false + legate-gh-ci-tag: "push_nightly_docs_to_gh_pages" + name: Build documentation + network: "ucx" + platform: ${{ inputs.platform }} + python-version: ${{ inputs.python-version }} + runs-on: ${{ (inputs.platform == 'linux' && 'linux-amd64-gpu-l4-latest-1') || (inputs.platform == 'mac' && 'macos-latest') }} + target-device: ${{ inputs.target-device }} + test-options: docs + upload-enabled: false + secrets: inherit + + + upload-docs-to-gh-pages: + if: ${{ inputs.upload-docs-to-gh-pages }} + needs: + - build-docs + runs-on: ${{ (inputs.platform == 'linux' && 'linux-amd64-cpu4') || (inputs.platform == 'mac' && 'macos-latest') }} + steps: + - name: Set environment variables + shell: bash --noprofile --norc -xeuo pipefail {0} + run: | + echo "${{ needs.build-docs.outputs.output-artifact-name }}" + + ARTIFACTS_DIR=$(realpath "$(pwd)/../artifacts") + echo "ARTIFACTS_DIR=${ARTIFACTS_DIR}" >> $GITHUB_ENV + + mkdir -p "${ARTIFACTS_DIR}" + + - name: Download build artifacts + uses: actions/download-artifact@v4 + with: + name: ${{ needs.build-docs.outputs.output-artifact-name }} + path: ${{ env.ARTIFACTS_DIR }} + + - name: Display structure of downloaded artifacts + shell: bash --noprofile --norc -xeuo pipefail {0} + run: | + pwd + ls -lahR ${{ env.ARTIFACTS_DIR }} + + - name: Find index.html's parent folder + shell: bash --noprofile --norc -xeuo pipefail {0} + id: find_docs_dir + run: | + FILE_PATH="$( + find "${{ env.ARTIFACTS_DIR }}" -name "index.html" -printf '%d %p\n' \ + | sort -nk1 \ + | cut -d' ' -f2- \ + | head -n 1 + )" + if [ -z "${FILE_PATH}" ]; then + echo "index.html not found" >&2 + exit 1 + fi + PARENT_DIR=$(dirname "${FILE_PATH}") + echo "docs_dir=${PARENT_DIR}" >> "${GITHUB_OUTPUT}" + + - name: Checkout + uses: actions/checkout@v4 + + - name: Deploy + uses: JamesIves/github-pages-deploy-action@v4 + with: + folder: ${{ steps.find_docs_dir.outputs.docs_dir }} + token: ${{ secrets.NV_LEGATE_INTER_REPOS_ACCESS }} + repository-name: "nv-legate/cupynumeric" diff --git a/continuous_integration/scripts/conda-dnld-utils b/continuous_integration/scripts/conda-dnld-utils index 37ea9551fb..ddf1c6bbe5 100755 --- a/continuous_integration/scripts/conda-dnld-utils +++ b/continuous_integration/scripts/conda-dnld-utils @@ -12,7 +12,7 @@ generate_legate_version() { } verify_legate_version() { - mamba search legate=${LEGATE_VERSION} --channel https://conda.anaconda.org/${CONDA_CHANNEL}/label/${CONDA_LABEL} + legate-mamba-retry search legate=${LEGATE_VERSION} --channel https://conda.anaconda.org/${CONDA_CHANNEL}/label/${CONDA_LABEL} if [ $? -ne 0 ]; then echo "Error: conda search failed for legate." >&2; exit 1 fi diff --git a/continuous_integration/scripts/make-conda-env b/continuous_integration/scripts/make-conda-env index 286086417e..eacc3c5891 100755 --- a/continuous_integration/scripts/make-conda-env +++ b/continuous_integration/scripts/make-conda-env @@ -5,7 +5,26 @@ set -x . conda-utils make_release_env() { - mamba create -q -y -n "${CONDA_ENV}" -c conda-forge boa + legate-mamba-retry create -q -y -n "${CONDA_ENV}" -c conda-forge boa +} + +make_docs_env() { + set -xeuo pipefail + + export DEBIAN_FRONTEND=non-interactive + export CONDA_ENV=legate + + # Run package updates and install packages + apt-get update + apt-get install -y numactl make + + legate-mamba-retry create -yn "${CONDA_ENV}" pandoc doxygen + + . conda-utils; + activate_conda_env; + + # mamba install -y pandoc doxygen + pip install ipython jinja2 "markdown<3.4.0" myst-parser nbsphinx sphinx-copybutton "sphinx>=8" nvidia-sphinx-theme cffi } make_conda_env() { @@ -14,6 +33,7 @@ make_conda_env() { case "$1" in ci) make_release_env;; nightly) make_release_env;; + docs) make_docs_env;; *) return 1;; esac diff --git a/continuous_integration/scripts/test b/continuous_integration/scripts/test index 0da9fb67d2..686c34661b 100755 --- a/continuous_integration/scripts/test +++ b/continuous_integration/scripts/test @@ -13,29 +13,29 @@ setup_env() { apt-get update apt-get install -y numactl make - mamba search --override-channels -c "${ARTIFACTS_DIR}/conda-build/cupynumeric" --info cupynumeric + legate-mamba-retry search --override-channels -c "${ARTIFACTS_DIR}/conda-build/cupynumeric" --info cupynumeric # This requires strict channel priority to work (prioritize local channel) - mamba create -y -n legate -c "${ARTIFACTS_DIR}/conda-build/cupynumeric" -c https://conda.anaconda.org/${CONDA_CHANNEL}/label/${CONDA_LABEL} -c conda-forge legate cupynumeric + legate-mamba-retry create -y -n legate -c "${ARTIFACTS_DIR}/conda-build/cupynumeric" -c https://conda.anaconda.org/${CONDA_CHANNEL}/label/${CONDA_LABEL} -c conda-forge legate cupynumeric } setup_test_env() { - mamba install -y "clang-tools>=8" "clang>=8" colorama coverage mock pre-commit pytest-cov pytest-mock "pytest" types-docutils pynvml psutil + legate-mamba-retry install -y "clang-tools>=8" "clang>=8" colorama coverage mock pre-commit pytest-cov pytest-mock "pytest" types-docutils pynvml psutil pip install tifffile } setup_docs_env() { - mamba install -y pandoc doxygen + legate-mamba-retry install -y pandoc doxygen pip install ipython jinja2 "markdown<3.4.0" myst-parser nbsphinx sphinx-copybutton "sphinx>=8" nvidia-sphinx-theme cffi } setup_mypy_env() { - mamba install -y "mypy>=0.961" jinja2 nbsphinx sphinx-copybutton "sphinx>=4.4.0" types-docutils + legate-mamba-retry install -y "mypy>=0.961" jinja2 nbsphinx sphinx-copybutton "sphinx>=4.4.0" types-docutils } setup_unit_env() { - mamba install -y pytest pytest-mock mock cffi + legate-mamba-retry install -y pytest pytest-mock mock cffi } test_cupynumeric() { @@ -67,9 +67,14 @@ test_cupynumeric() { "docs") echo "Building docs..." shift; + setup_docs_env; cd docs/cupynumeric make clean html + # ls -lah . + echo Copying artifacts + cd build/html + cp -r . "${OUTPUT_ARTIFACTS_DIR}" ;; "unit") echo "Running Unit tests..." From 3f1e884256fc9273d5ad7a8f4bdbff910bdd833e Mon Sep 17 00:00:00 2001 From: Manolis Papadakis Date: Wed, 12 Mar 2025 09:35:56 -0700 Subject: [PATCH 441/462] Fixes #645 (#647) Also pet-peeve; rename all static-registration variables to align --- cupynumeric/_thunk/deferred.py | 9 +++++---- src/cupynumeric/binary/binary_red.cc | 2 +- src/cupynumeric/bits/unpackbits.cc | 2 +- src/cupynumeric/cudalibs.cu | 2 +- src/cupynumeric/index/advanced_indexing.cc | 2 +- src/cupynumeric/matrix/batched_cholesky.cc | 2 +- src/cupynumeric/matrix/matvecmul.cc | 2 +- src/cupynumeric/random/bitgenerator.cc | 2 +- src/cupynumeric/scan/scan_global.cc | 2 +- src/cupynumeric/scan/scan_local.cc | 2 +- src/cupynumeric/set/unique_reduce.cc | 2 +- src/cupynumeric/sort/searchsorted.cc | 2 +- src/cupynumeric/stat/histogram.cc | 2 +- src/cupynumeric/unary/scalar_unary_red.cc | 2 +- 14 files changed, 18 insertions(+), 17 deletions(-) diff --git a/cupynumeric/_thunk/deferred.py b/cupynumeric/_thunk/deferred.py index a2b6060665..98ebb7b0bc 100644 --- a/cupynumeric/_thunk/deferred.py +++ b/cupynumeric/_thunk/deferred.py @@ -3386,13 +3386,14 @@ def binary_reduction( args: tuple[Scalar, ...], ) -> None: lhs = self.base - rhs1 = src1.base - rhs2 = src2.base assert lhs.has_scalar_storage if broadcast is not None: - rhs1 = rhs1._broadcast(broadcast) - rhs2 = rhs2._broadcast(broadcast) + rhs1 = src1._broadcast(broadcast) + rhs2 = src2._broadcast(broadcast) + else: + rhs1 = src1.base + rhs2 = src2.base # Populate the Legate launcher if op == BinaryOpCode.NOT_EQUAL: diff --git a/src/cupynumeric/binary/binary_red.cc b/src/cupynumeric/binary/binary_red.cc index 9e2f0d0df0..89ad585ccf 100644 --- a/src/cupynumeric/binary/binary_red.cc +++ b/src/cupynumeric/binary/binary_red.cc @@ -66,7 +66,7 @@ struct BinaryRedImplBody { namespace // unnamed { -const auto reg_ = []() -> char { +const auto cupynumeric_reg_task_ = []() -> char { BinaryRedTask::register_variants(); return 0; }(); diff --git a/src/cupynumeric/bits/unpackbits.cc b/src/cupynumeric/bits/unpackbits.cc index 7454c1f56b..2be36a8287 100644 --- a/src/cupynumeric/bits/unpackbits.cc +++ b/src/cupynumeric/bits/unpackbits.cc @@ -45,7 +45,7 @@ struct UnpackbitsImplBody { namespace // unnamed { -const auto reg_ = []() -> char { +const auto cupynumeric_reg_task_ = []() -> char { UnpackbitsTask::register_variants(); return 0; }(); diff --git a/src/cupynumeric/cudalibs.cu b/src/cupynumeric/cudalibs.cu index d2b06cc221..d575c61491 100644 --- a/src/cupynumeric/cudalibs.cu +++ b/src/cupynumeric/cudalibs.cu @@ -566,7 +566,7 @@ class UnloadCUDALibsTask : public CuPyNumericTask { } }; -const auto reg_ = []() -> char { +const auto cupynumeric_reg_task_ = []() -> char { LoadCUDALibsTask::register_variants(); UnloadCUDALibsTask::register_variants(); return 0; diff --git a/src/cupynumeric/index/advanced_indexing.cc b/src/cupynumeric/index/advanced_indexing.cc index a186b06da4..ac590c620c 100644 --- a/src/cupynumeric/index/advanced_indexing.cc +++ b/src/cupynumeric/index/advanced_indexing.cc @@ -111,7 +111,7 @@ struct AdvancedIndexingImplBody { namespace // unnamed { -const auto reg_ = []() -> char { +const auto cupynumeric_reg_task_ = []() -> char { AdvancedIndexingTask::register_variants(); return 0; }(); diff --git a/src/cupynumeric/matrix/batched_cholesky.cc b/src/cupynumeric/matrix/batched_cholesky.cc index 918d965a1e..079fae80e4 100644 --- a/src/cupynumeric/matrix/batched_cholesky.cc +++ b/src/cupynumeric/matrix/batched_cholesky.cc @@ -78,7 +78,7 @@ struct BatchedTransposeImplBody { namespace // unnamed { -const auto reg_ = []() -> char { +const auto cupynumeric_reg_task_ = []() -> char { BatchedCholeskyTask::register_variants(); return 0; }(); diff --git a/src/cupynumeric/matrix/matvecmul.cc b/src/cupynumeric/matrix/matvecmul.cc index a56ab8eb65..29bd3c0acf 100644 --- a/src/cupynumeric/matrix/matvecmul.cc +++ b/src/cupynumeric/matrix/matvecmul.cc @@ -37,7 +37,7 @@ using namespace legate; namespace // unnamed { -const auto reg_ = []() -> char { +const auto cupynumeric_reg_task_ = []() -> char { MatVecMulTask::register_variants(); return 0; }(); diff --git a/src/cupynumeric/random/bitgenerator.cc b/src/cupynumeric/random/bitgenerator.cc index 5e927b7280..9d2dbab21a 100644 --- a/src/cupynumeric/random/bitgenerator.cc +++ b/src/cupynumeric/random/bitgenerator.cc @@ -108,7 +108,7 @@ std::mutex BitGeneratorImplBody::lock_generators = {}; namespace // unnamed { -const auto reg_ = []() -> char { +const auto cupynumeric_reg_task_ = []() -> char { BitGeneratorTask::register_variants(); return 0; }(); diff --git a/src/cupynumeric/scan/scan_global.cc b/src/cupynumeric/scan/scan_global.cc index cc9a3c8419..e80a3c86e8 100644 --- a/src/cupynumeric/scan/scan_global.cc +++ b/src/cupynumeric/scan/scan_global.cc @@ -74,7 +74,7 @@ struct ScanGlobalImplBody { namespace // unnamed { -const auto reg_ = []() -> char { +const auto cupynumeric_reg_task_ = []() -> char { ScanGlobalTask::register_variants(); return 0; }(); diff --git a/src/cupynumeric/scan/scan_local.cc b/src/cupynumeric/scan/scan_local.cc index b7a31b4071..b0663fe836 100644 --- a/src/cupynumeric/scan/scan_local.cc +++ b/src/cupynumeric/scan/scan_local.cc @@ -116,7 +116,7 @@ struct ScanLocalNanImplBody { namespace // unnamed { -const auto reg_ = []() -> char { +const auto cupynumeric_reg_task_ = []() -> char { ScanLocalTask::register_variants(); return 0; }(); diff --git a/src/cupynumeric/set/unique_reduce.cc b/src/cupynumeric/set/unique_reduce.cc index 56361a0983..9e75929d65 100644 --- a/src/cupynumeric/set/unique_reduce.cc +++ b/src/cupynumeric/set/unique_reduce.cc @@ -26,7 +26,7 @@ namespace cupynumeric { namespace // unnamed { -const auto reg_ = []() -> char { +const auto cupynumeric_reg_task_ = []() -> char { UniqueReduceTask::register_variants(); return 0; }(); diff --git a/src/cupynumeric/sort/searchsorted.cc b/src/cupynumeric/sort/searchsorted.cc index 1648f0c2b0..6d56046901 100644 --- a/src/cupynumeric/sort/searchsorted.cc +++ b/src/cupynumeric/sort/searchsorted.cc @@ -79,7 +79,7 @@ struct SearchSortedImplBody { namespace // unnamed { -const auto reg_ = []() -> char { +const auto cupynumeric_reg_task_ = []() -> char { SearchSortedTask::register_variants(); return 0; }(); diff --git a/src/cupynumeric/stat/histogram.cc b/src/cupynumeric/stat/histogram.cc index 0950e198ae..e76155ef71 100644 --- a/src/cupynumeric/stat/histogram.cc +++ b/src/cupynumeric/stat/histogram.cc @@ -69,7 +69,7 @@ struct HistogramImplBody { namespace // unnamed { -const auto reg_ = []() -> char { +const auto cupynumeric_reg_task_ = []() -> char { HistogramTask::register_variants(); return 0; }(); diff --git a/src/cupynumeric/unary/scalar_unary_red.cc b/src/cupynumeric/unary/scalar_unary_red.cc index 7167c30a91..02c9a86585 100644 --- a/src/cupynumeric/unary/scalar_unary_red.cc +++ b/src/cupynumeric/unary/scalar_unary_red.cc @@ -26,7 +26,7 @@ namespace cupynumeric { namespace // unnamed { -const auto reg_ = []() -> char { +const auto cupynumeric_reg_task_ = []() -> char { ScalarUnaryRedTask::register_variants(); return 0; }(); From ab44cbfb8d7e33656be135fddd17e4ca4499f292 Mon Sep 17 00:00:00 2001 From: Manolis Papadakis Date: Thu, 13 Mar 2025 09:35:00 -0700 Subject: [PATCH 442/462] Fix nvbug 5156643 (#649) --- cupynumeric/_array/array.py | 5 +- cupynumeric/_thunk/deferred.py | 12 +-- cupynumeric/linalg/linalg.py | 186 ++++++++++++++++++++++----------- cupynumeric/runtime.py | 10 +- 4 files changed, 139 insertions(+), 74 deletions(-) diff --git a/cupynumeric/_array/array.py b/cupynumeric/_array/array.py index 388147185f..b1dfc433fc 100644 --- a/cupynumeric/_array/array.py +++ b/cupynumeric/_array/array.py @@ -17,7 +17,7 @@ import operator import warnings from functools import reduce -from typing import TYPE_CHECKING, Any, Sequence, cast +from typing import TYPE_CHECKING, Any, Literal, Sequence, cast import legate.core.types as ty import numpy as np @@ -106,6 +106,7 @@ def __init__( order: OrderType | None = None, thunk: NumPyThunk | None = None, inputs: Any | None = None, + force_thunk: Literal["deferred"] | Literal["eager"] | None = None, writeable: bool = True, ) -> None: # `inputs` being a cuPyNumeric ndarray is definitely a bug @@ -138,7 +139,7 @@ def __init__( ] core_dtype = to_core_type(dtype) self._thunk = runtime.create_empty_thunk( - sanitized_shape, core_dtype, inputs + sanitized_shape, core_dtype, inputs, force_thunk ) else: self._thunk = thunk diff --git a/cupynumeric/_thunk/deferred.py b/cupynumeric/_thunk/deferred.py index 98ebb7b0bc..f8c6521271 100644 --- a/cupynumeric/_thunk/deferred.py +++ b/cupynumeric/_thunk/deferred.py @@ -3469,19 +3469,11 @@ def cholesky(self, src: Any) -> None: @auto_convert("ew", "ev") def eig(self, ew: Any, ev: Any) -> None: - if runtime.num_gpus > 0 and not runtime.cusolver_has_geev(): - lhs = runtime.to_eager_array(self) - lhs.eig(runtime.to_eager_array(ew), runtime.to_eager_array(ev)) - else: - eig_deferred(self, ew, ev) + eig_deferred(self, ew, ev) @auto_convert("ew") def eigvals(self, ew: Any) -> None: - if runtime.num_gpus > 0 and not runtime.cusolver_has_geev(): - lhs = runtime.to_eager_array(self) - lhs.eigvals(runtime.to_eager_array(ew)) - else: - eig_deferred(self, ew) + eig_deferred(self, ew) @auto_convert("q", "r") def qr(self, q: Any, r: Any) -> None: diff --git a/cupynumeric/linalg/linalg.py b/cupynumeric/linalg/linalg.py index d1cfd73e1e..39b04adc5c 100644 --- a/cupynumeric/linalg/linalg.py +++ b/cupynumeric/linalg/linalg.py @@ -14,11 +14,12 @@ # from __future__ import annotations -from typing import TYPE_CHECKING, Sequence, Any +from typing import TYPE_CHECKING, Any, Sequence import numpy as np from .._utils import is_np2 +from ..runtime import runtime if is_np2: from numpy.lib.array_utils import normalize_axis_index # type: ignore @@ -31,12 +32,13 @@ normalize_axis_tuple, ) +from legate.core import get_machine + from .._array.util import add_boilerplate, convert_to_cupynumeric_ndarray from .._module import dot, empty_like, eye, matmul, ndarray +from .._module.creation_shape import zeros, zeros_like from .._ufunc.math import add, sqrt as _sqrt from ._exception import LinAlgError -from .._module.creation_shape import zeros, zeros_like -from legate.core import get_machine, TaskTarget if TYPE_CHECKING: import numpy.typing as npt @@ -864,17 +866,29 @@ def _thunk_eig(a: ndarray) -> tuple[ndarray, ...]: else: raise TypeError("Eig input not supported (missing a conversion?)") - out_ew = ndarray( - shape=a.shape[:-1], - dtype=complex_dtype, - inputs=(a,), - ) - - out_ev = ndarray( - shape=a.shape, - dtype=complex_dtype, - inputs=(a,), - ) + if runtime.num_gpus > 0 and not runtime.cusolver_has_geev(): + a = ndarray(a.shape, a.dtype, thunk=runtime.to_eager_array(a._thunk)) + out_ew = ndarray( + shape=a.shape[:-1], + dtype=complex_dtype, + force_thunk="eager", + ) + out_ev = ndarray( + shape=a.shape, + dtype=complex_dtype, + force_thunk="eager", + ) + else: + out_ew = ndarray( + shape=a.shape[:-1], + dtype=complex_dtype, + inputs=(a,), + ) + out_ev = ndarray( + shape=a.shape, + dtype=complex_dtype, + inputs=(a,), + ) if a.shape[-1] > 0: a._thunk.eig(out_ew._thunk, out_ev._thunk) @@ -894,11 +908,19 @@ def _thunk_eigvals(a: ndarray) -> ndarray: else: raise TypeError("Eigvals input not supported (missing a conversion?)") - out_ew = ndarray( - shape=a.shape[:-1], - dtype=complex_dtype, - inputs=(a,), - ) + if runtime.num_gpus > 0 and not runtime.cusolver_has_geev(): + a = ndarray(a.shape, a.dtype, thunk=runtime.to_eager_array(a._thunk)) + out_ew = ndarray( + shape=a.shape[:-1], + dtype=complex_dtype, + force_thunk="eager", + ) + else: + out_ew = ndarray( + shape=a.shape[:-1], + dtype=complex_dtype, + inputs=(a,), + ) if a.shape[-1] > 0: a._thunk.eigvals(out_ew._thunk) @@ -995,9 +1017,7 @@ def _thunk_svd(a: ndarray, full_matrices: bool) -> tuple[ndarray, ...]: # helper function to construct rational Pade # numerator / denominator for expm(A): # -def make_uv(A: ndarray, - b: Any, - m: int) -> tuple[ndarray, ndarray]: +def make_uv(A: ndarray, b: Any, m: int) -> tuple[ndarray, ndarray]: # 1 + floor(m/2): # k = 1 + m // 2 @@ -1012,8 +1032,8 @@ def make_uv(A: ndarray, A2 = matmul(A, A) A2k = eye(n, dtype=A.dtype) for j in range(k): - U = U + b[2*j+1] * A2k - V = V + b[2*j] * A2k + U = U + b[2 * j + 1] * A2k + V = V + b[2 * j] * A2k A2k = matmul(A2k, A2) U = matmul(A, U) @@ -1025,6 +1045,7 @@ class ExpmConstants: """ Aggregates all the necessary expm(A) constants. """ + # Pade `b` coefficient generators # for both numerator `p(x)` and # denominator `q(x)` coefficients @@ -1034,19 +1055,45 @@ class ExpmConstants: # diagonal Pade implementation; # b_coeff = { - 3: np.array([120, 60, 12, 1], dtype = np.float64), - 5: np.array([30240, 15120, 3360, 420, 30, 1], dtype = np.float64), - 7: np.array([17297280, 8648640, 1995840, 277200, 25200, 1512, 56, 1], - dtype = np.float64), + 3: np.array([120, 60, 12, 1], dtype=np.float64), + 5: np.array([30240, 15120, 3360, 420, 30, 1], dtype=np.float64), + 7: np.array( + [17297280, 8648640, 1995840, 277200, 25200, 1512, 56, 1], + dtype=np.float64, + ), 9: np.array( - [17643225600, 8821612800, 2075673600, 302702400, 30270240, - 2162160, 110880, 3960, 90, 1], dtype = np.float64 + [ + 17643225600, + 8821612800, + 2075673600, + 302702400, + 30270240, + 2162160, + 110880, + 3960, + 90, + 1, + ], + dtype=np.float64, ), 13: np.array( - [64764752532480000, 32382376266240000, 7771770303897600, - 1187353796428800, 129060195264000, 10559470521600, - 670442572800, 33522128640, 1323241920, 40840800, - 960960, 16380, 182, 1], dtype = np.float64 + [ + 64764752532480000, + 32382376266240000, + 7771770303897600, + 1187353796428800, + 129060195264000, + 10559470521600, + 670442572800, + 33522128640, + 1323241920, + 40840800, + 960960, + 16380, + 182, + 1, + ], + dtype=np.float64, ), } @@ -1058,7 +1105,7 @@ class ExpmConstants: 5: 2.5e-1, 7: 9.5e-1, 9: 2.1, - 13:5.4, + 13: 5.4, } # Taylor-18 coefficients @@ -1095,14 +1142,13 @@ class ExpmConstants: def expm_impl(a: ndarray, output: ndarray) -> tuple[int, int]: - """ - Implements Pade rational aproximant of + """ + Implements Pade rational aproximant of Algorithm 10.20, p.246-247 in "Functions of Matrices - Theory and Computation", Nicholas J. Higham, SIAM 2008. """ - n = a.shape[0] lst_keys = list(ExpmConstants.theta.keys()) # maximum polynomial degree for [p(x)/q(x)]: @@ -1181,8 +1227,8 @@ def expm_impl(a: ndarray, output: ndarray) -> tuple[int, int]: def expm_expl(a: ndarray, output: ndarray) -> tuple[int, int]: - """ - Implements Taylor expansion, algorithm T_18 + """ + Implements Taylor expansion, algorithm T_18 in "Computing the Matrix Exponential with an Optimized Taylor Polynomial Approximation", Philipp Bader et. al., @@ -1190,12 +1236,12 @@ def expm_expl(a: ndarray, output: ndarray) -> tuple[int, int]: for given number of terms in the expansion. """ - tol_m = ExpmConstants.theta_m # may vary w/ degree, m, in future impls. + tol_m = ExpmConstants.theta_m # may vary w/ degree, m, in future impls. # L1 norm of matrix input: l1_norm_a = norm(a, 1) - requires_scaling = (l1_norm_a > tol_m) + requires_scaling = l1_norm_a > tol_m s = 0 A = a @@ -1212,18 +1258,38 @@ def expm_expl(a: ndarray, output: ndarray) -> tuple[int, int]: # A = a / sfactor - I = eye(A.shape[0], dtype=A.dtype) + EYE = eye(A.shape[0], dtype=A.dtype) A2 = matmul(A, A) A3 = matmul(A2, A) A6 = matmul(A3, A3) - B1 = ExpmConstants.a11*A + ExpmConstants.a21*A2 + ExpmConstants.a31*A3 - B2 = ExpmConstants.b11*A + ExpmConstants.b21*A2 + ExpmConstants.b31*A3 \ - + ExpmConstants.b61*A6 - B3 = ExpmConstants.b02*I + ExpmConstants.b12*A + ExpmConstants.b22*A2 \ - + ExpmConstants.b32*A3 + ExpmConstants.b62*A6 - B4 = ExpmConstants.b03*I + ExpmConstants.b13*A + ExpmConstants.b23*A2 \ - + ExpmConstants.b33*A3 + ExpmConstants.b63*A6 - B5 = ExpmConstants.b24*A2 + ExpmConstants.b34*A3 + ExpmConstants.b64*A6 + B1 = ( + ExpmConstants.a11 * A + ExpmConstants.a21 * A2 + ExpmConstants.a31 * A3 + ) + B2 = ( + ExpmConstants.b11 * A + + ExpmConstants.b21 * A2 + + ExpmConstants.b31 * A3 + + ExpmConstants.b61 * A6 + ) + B3 = ( + ExpmConstants.b02 * EYE + + ExpmConstants.b12 * A + + ExpmConstants.b22 * A2 + + ExpmConstants.b32 * A3 + + ExpmConstants.b62 * A6 + ) + B4 = ( + ExpmConstants.b03 * EYE + + ExpmConstants.b13 * A + + ExpmConstants.b23 * A2 + + ExpmConstants.b33 * A3 + + ExpmConstants.b63 * A6 + ) + B5 = ( + ExpmConstants.b24 * A2 + + ExpmConstants.b34 * A3 + + ExpmConstants.b64 * A6 + ) A9 = B4 + matmul(B1, B5) B39 = B3 + A9 @@ -1237,18 +1303,18 @@ def expm_expl(a: ndarray, output: ndarray) -> tuple[int, int]: for j in range(s): output[:] = matmul(output, output) - return (m, s) + return (m, s) @add_boilerplate("a") def expm(a: ndarray, method: str = "pade") -> ndarray: """ - Matrix exponential. + Matrix exponential. Returns exp(A) for each (M x M) slice into a multi-dimensional array, assumed to be of shape (..., M, M); - By default Pade (implicit) implementation is used. + By default Pade (implicit) implementation is used. However, explicit Taylor(deg = 18) implementation can be used, by supplying additional flag `use_explicit = True`. @@ -1262,15 +1328,15 @@ def expm(a: ndarray, method: str = "pade") -> ndarray: Returns ------- - exp(A): matrix exponential of input, or a matrix exponential + exp(A): matrix exponential of input, or a matrix exponential for each slice in the input. Notes ----- - Implicit Pade implementation is more stable but more computationally intensive than - explicit Taylor, which is less stable when matrix norm is big enough. - Also, Taylor can be slightly more performant for matrices of small - enough norms, but more memory consuming. + Implicit Pade implementation is more stable but more computationally + intensive than explicit Taylor, which is less stable when matrix norm is + big enough. Also, Taylor can be slightly more performant for matrices of + small enough norms, but more memory consuming. See Also -------- @@ -1292,7 +1358,7 @@ def expm(a: ndarray, method: str = "pade") -> ndarray: # run implicit (Pade) method by default: # if method == "pade": - expm_func = expm_impl + expm_func = expm_impl elif method == "taylor": expm_func = expm_expl else: diff --git a/cupynumeric/runtime.py b/cupynumeric/runtime.py index d2f6f65da2..ca7a32bf43 100644 --- a/cupynumeric/runtime.py +++ b/cupynumeric/runtime.py @@ -17,7 +17,7 @@ import math import warnings from functools import lru_cache, reduce -from typing import TYPE_CHECKING, Any, Sequence, TypeGuard +from typing import TYPE_CHECKING, Any, Literal, Sequence, TypeGuard import legate.core.types as ty import numpy as np @@ -480,10 +480,16 @@ def create_empty_thunk( shape: NdShape, dtype: ty.Type, inputs: Sequence[NumPyThunk] | None = None, + force_thunk: Literal["deferred"] | Literal["eager"] | None = None, ) -> NumPyThunk: from ._thunk.deferred import DeferredArray - if self.is_eager_shape(shape) and self.are_all_eager_inputs(inputs): + assert inputs is None or force_thunk is None + if force_thunk == "eager" or ( + force_thunk is None + and self.is_eager_shape(shape) + and self.are_all_eager_inputs(inputs) + ): return self.create_eager_thunk(shape, dtype.to_numpy_dtype()) store = legate_runtime.create_store( From 9dba1f3ee6c0046f61d83e53c86695675f41bd6f Mon Sep 17 00:00:00 2001 From: Manolis Papadakis Date: Thu, 13 Mar 2025 20:16:43 -0700 Subject: [PATCH 443/462] Documentation fixes (#651) --- README.md | 13 ++-- docs/cupynumeric/source/api/_bitgenerator.rst | 2 +- docs/cupynumeric/source/api/_generator.rst | 2 +- docs/cupynumeric/source/api/_ndarray.rst | 2 +- docs/cupynumeric/source/api/broadcast.rst | 2 +- docs/cupynumeric/source/api/fft.rst | 2 +- docs/cupynumeric/source/api/linalg.rst | 2 +- docs/cupynumeric/source/api/ndarray.rst | 2 +- docs/cupynumeric/source/api/random.rst | 2 +- .../cupynumeric/source/developer/building.rst | 69 ++++++++++++++----- docs/cupynumeric/source/faqs.rst | 2 +- docs/cupynumeric/source/index.rst | 12 ++-- docs/cupynumeric/source/oss-licenses.rst | 36 ++++++++++ 13 files changed, 115 insertions(+), 33 deletions(-) diff --git a/README.md b/README.md index 6b9b39325f..97428aacf7 100644 --- a/README.md +++ b/README.md @@ -19,10 +19,15 @@ limitations under the License. # cuPyNumeric -cuPyNumeric is a [Legate](https://github.com/nv-legate/legate.core) library -that aims to provide a distributed and accelerated drop-in replacement for the -[NumPy API](https://numpy.org/doc/stable/reference/) on top of the -[Legion](https://legion.stanford.edu) runtime. Using cuPyNumeric you can do things like run +cuPyNumeric is a library that aims to provide a distributed and accelerated +drop-in replacement for [NumPy](https://numpy.org/) built on top of the +[Legate](https://github.com/nv-legate/legate) framework. + +With cuPyNumeric you can write code productively in Python, using the familiar +NumPy API, and have your program scale with no code changes from single-CPU +computers to multi-node-multi-GPU clusters. + +For example, you can run [the final example of the Python CFD course](https://github.com/barbagroup/CFDPython/blob/master/lessons/15_Step_12.ipynb) completely unmodified on 2048 A100 GPUs in a [DGX SuperPOD](https://www.nvidia.com/en-us/data-center/dgx-superpod/) diff --git a/docs/cupynumeric/source/api/_bitgenerator.rst b/docs/cupynumeric/source/api/_bitgenerator.rst index e269e9872e..0ad24527d2 100644 --- a/docs/cupynumeric/source/api/_bitgenerator.rst +++ b/docs/cupynumeric/source/api/_bitgenerator.rst @@ -1,5 +1,5 @@ cupynumeric.random.BitGenerator -============================= +=============================== .. currentmodule:: cupynumeric.random diff --git a/docs/cupynumeric/source/api/_generator.rst b/docs/cupynumeric/source/api/_generator.rst index c734812327..5bffd5501b 100644 --- a/docs/cupynumeric/source/api/_generator.rst +++ b/docs/cupynumeric/source/api/_generator.rst @@ -1,5 +1,5 @@ cupynumeric.random.Generator -========================== +============================ .. currentmodule:: cupynumeric.random diff --git a/docs/cupynumeric/source/api/_ndarray.rst b/docs/cupynumeric/source/api/_ndarray.rst index ea6b57a328..5dfff107f7 100644 --- a/docs/cupynumeric/source/api/_ndarray.rst +++ b/docs/cupynumeric/source/api/_ndarray.rst @@ -1,5 +1,5 @@ cupynumeric.ndarray -================= +=================== .. currentmodule:: cupynumeric diff --git a/docs/cupynumeric/source/api/broadcast.rst b/docs/cupynumeric/source/api/broadcast.rst index 9e093e79ea..df9197044c 100644 --- a/docs/cupynumeric/source/api/broadcast.rst +++ b/docs/cupynumeric/source/api/broadcast.rst @@ -1,7 +1,7 @@ .. currentmodule:: cupynumeric cupynumeric.broadcast -=================== +===================== .. autoclass:: broadcast :members: \ No newline at end of file diff --git a/docs/cupynumeric/source/api/fft.rst b/docs/cupynumeric/source/api/fft.rst index 8a656a2538..6ffe039d9f 100644 --- a/docs/cupynumeric/source/api/fft.rst +++ b/docs/cupynumeric/source/api/fft.rst @@ -1,7 +1,7 @@ .. module:: cupynumeric.fft Discrete Fourier Transform (:mod:`cupynumeric.fft`) -================================================== +=================================================== Standard FFTs --------------- diff --git a/docs/cupynumeric/source/api/linalg.rst b/docs/cupynumeric/source/api/linalg.rst index 5b843c7e46..c3beaf9c61 100644 --- a/docs/cupynumeric/source/api/linalg.rst +++ b/docs/cupynumeric/source/api/linalg.rst @@ -1,7 +1,7 @@ .. module:: cupynumeric.linalg Linear algebra (:mod:`cupynumeric.linalg`) -======================================== +========================================== .. currentmodule:: cupynumeric diff --git a/docs/cupynumeric/source/api/ndarray.rst b/docs/cupynumeric/source/api/ndarray.rst index a9d17a648b..50bfd1a2a3 100644 --- a/docs/cupynumeric/source/api/ndarray.rst +++ b/docs/cupynumeric/source/api/ndarray.rst @@ -1,7 +1,7 @@ .. currentmodule:: cupynumeric The N-Dimensional array (:class:`cupynumeric.ndarray`) -==================================================== +====================================================== Constructing arrays ------------------- diff --git a/docs/cupynumeric/source/api/random.rst b/docs/cupynumeric/source/api/random.rst index 22036b5349..79a0f2adbd 100644 --- a/docs/cupynumeric/source/api/random.rst +++ b/docs/cupynumeric/source/api/random.rst @@ -1,7 +1,7 @@ .. module:: cupynumeric.random Random sampling (:mod:`cupynumeric.random`) -========================================= +=========================================== Random Generator ----------------- diff --git a/docs/cupynumeric/source/developer/building.rst b/docs/cupynumeric/source/developer/building.rst index 9c4f747650..25e61cc940 100644 --- a/docs/cupynumeric/source/developer/building.rst +++ b/docs/cupynumeric/source/developer/building.rst @@ -10,28 +10,66 @@ Users must have a working installation of the `Legate`_ library prior to installing cuPyNumeric. **Installing cuPyNumeric by itself will not automatically install Legate.** -As for other dependencies, the Dependencies section on the -`Legate build instructions`_ also covers cuPyNumeric, so no additional -packages are required. +See below for a list of cuPyNumeric's dependencies. The easiest way to set up a +build environment that includes all of cuPyNumeric dependencies is to use the +``scripts/generate-conda-envs.py`` script from the `Legate build instructions`_, +passing the ``--cupynumeric`` flag. -Once Legate is installed, you can simply invoke ``./install.py`` from the -cuPyNumeric top-level directory. The build will automatically pick up the +Once all dependencies are installed, you can simply invoke ``./install.py`` from +the cuPyNumeric top-level directory. The build will automatically pick up the configuration used when building Legate (e.g. the CUDA Toolkit directory). +Dependencies +------------ + +OpenBLAS +~~~~~~~~ + +Used for implementing linear algebra routines on CPUs. + +If you want to use a custom build of OpenBLAS, you will need to get a +Fortran compiler, e.g. by pulling ``fortran-compiler`` from conda-forge. + +If using a build of Legate that includes OpenMP support, then you need a build +of OpenBLAS configured with the following options: + +* ``USE_THREAD=1`` +* ``USE_OPENMP=1`` +* ``NUM_PARALLEL=32`` (or at least as many as the NUMA domains on the target + machine) -- The ``NUM_PARALLEL`` flag defines how many instances of OpenBLAS's + calculation API can run in parallel. Legate will typically instantiate a + separate OpenMP group per NUMA domain, and each group can launch independent + BLAS work. If ``NUM_PARALLEL`` is not high enough, some of this parallel work + will be serialized. + +TBLIS +~~~~~ + +Used for implementing tensor contraction routines on CPUs. + +This library will be automatically downloaded and built during cuPyNumeric +installation. + +cuPyNumeric requires a build of TBLIS configured as follows: + +.. code-block:: none + + --with-label-type=int32_t --with-length-type=int64_t --with-stride-type=int64_t + +and additionally ``--enable-thread-model=openmp`` if using a build of Legate +that includes OpenMP support. + Advanced topics --------------- Building through pip & cmake ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -cuPyNumeric uses the same cmake/scikit-build-based build workflow as Legate. -See the `Legate build instructions`_ for an overview. - -There are several examples in the ``scripts`` folder. We walk through the steps in -``build-with-legate-separately-no-install.sh`` here. - -We assume a pre-existing Legate build. For details on building Legate, -consult the `Legate repository`_. +cuPyNumeric uses a cmake/scikit-build-based build workflow. There are several +examples in the ``scripts`` directory, showing how to build different +configurations of cuPyNumeric. We walk through the steps in +``build-with-legate-separately-no-install.sh`` here. We assume a pre-existing +Legate build. First, the CMake build needs to be configured: @@ -66,6 +104,5 @@ complete workflow for building both Legate and cuPyNumeric. :width: 600 :alt: "notional diagram of cupynumeric build process" -.. _Legate: https://github.com/nv-legate/legate.core -.. _Legate build instructions: https://github.com/nv-legate/legate.core/blob/HEAD/BUILD.md -.. _Legate repository: https://github.com/nv-legate/legate.core +.. _Legate: https://github.com/nv-legate/legate +.. _Legate build instructions: https://docs.nvidia.com/legate/latest/BUILD.html#dependencies diff --git a/docs/cupynumeric/source/faqs.rst b/docs/cupynumeric/source/faqs.rst index e3acc8b6f7..7d542437f1 100644 --- a/docs/cupynumeric/source/faqs.rst +++ b/docs/cupynumeric/source/faqs.rst @@ -194,7 +194,7 @@ Where I can read more about cuPyNumeric? ---------------------------------------- Check out this `blog post `_ -or this `tutorial `_ +or this `tutorial `_ to learn more about cuPyNumeric. Questions? diff --git a/docs/cupynumeric/source/index.rst b/docs/cupynumeric/source/index.rst index b0e163d8e0..43ca3f8347 100644 --- a/docs/cupynumeric/source/index.rst +++ b/docs/cupynumeric/source/index.rst @@ -3,11 +3,14 @@ NVIDIA cuPyNumeric ================== +cuPyNumeric is a library that aims to provide a distributed and accelerated +drop-in replacement for `NumPy`_ built on top of the `Legate`_ framework. + With cuPyNumeric you can write code productively in Python, using the familiar -`NumPy API`_, and have your program scale with no code changes from single-CPU +NumPy API, and have your program scale with no code changes from single-CPU computers to multi-node-multi-GPU clusters. -For example, you can run the final example of the `Python CFD course`_ +For example, you can run `the final example of the Python CFD course`_ completely unmodified on 2048 A100 GPUs in a `DGX SuperPOD`_ and achieve good weak scaling. @@ -29,6 +32,7 @@ Indices and tables * :ref:`genindex` * :ref:`search` +.. _NumPy: https://numpy.org/ +.. _Legate: https://github.com/nv-legate/legate .. _DGX SuperPOD: https://www.nvidia.com/en-us/data-center/dgx-superpod/ -.. _Numpy API: https://numpy.org/doc/stable/reference/ -.. _Python CFD course: https://github.com/barbagroup/CFDPython/blob/master/lessons/15_Step_12.ipynb \ No newline at end of file +.. _the final example of the Python CFD course: https://github.com/barbagroup/CFDPython/blob/master/lessons/15_Step_12.ipynb \ No newline at end of file diff --git a/docs/cupynumeric/source/oss-licenses.rst b/docs/cupynumeric/source/oss-licenses.rst index a6a9b0226b..84c0d96456 100644 --- a/docs/cupynumeric/source/oss-licenses.rst +++ b/docs/cupynumeric/source/oss-licenses.rst @@ -5,6 +5,42 @@ Third-party notices =================== +NumPy +----- + +.. code-block:: none + + Copyright (c) 2005-2025, NumPy Developers. + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are + met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following + disclaimer in the documentation and/or other materials provided + with the distribution. + + * Neither the name of the NumPy Developers nor the names of any + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + TBLIS ----- From 4298c00a5a3b48f180e98ba42c0f8ff7e8176a56 Mon Sep 17 00:00:00 2001 From: Marcin Zalewski Date: Mon, 17 Mar 2025 22:42:21 -0700 Subject: [PATCH 444/462] Fix build has tests (#652) (#653) --- .github/workflows/gh-build-and-test.yml | 24 ++++++++++++------------ .github/workflows/gh-build-docs.yml | 8 ++++---- 2 files changed, 16 insertions(+), 16 deletions(-) diff --git a/.github/workflows/gh-build-and-test.yml b/.github/workflows/gh-build-and-test.yml index e4d0fbe3a5..1ec9cb651f 100644 --- a/.github/workflows/gh-build-and-test.yml +++ b/.github/workflows/gh-build-and-test.yml @@ -53,14 +53,14 @@ jobs: needs: setup-build name: "Build (${{ inputs.platform }}, ${{ inputs.target-device }}, ${{ inputs.build-type }}, Python ${{ inputs.python-version }})" uses: - nv-legate/legate-gh-ci/.github/workflows/gh-build.yml@push_nightly_docs_to_gh_pages + nv-legate/legate-gh-ci/.github/workflows/gh-build.yml@v1.28 with: - build-has-tests: false + build-has-tests: ${{ !inputs.upload-enabled }} build-mode: "" build-type: ${{ inputs.build-type }} client-repo: ${{ github.event.repository.name }} dependencies-file: "" - legate-gh-ci-tag: "push_nightly_docs_to_gh_pages" + legate-gh-ci-tag: "v1.28" network: "ucx" platform: ${{ inputs.platform }} python-version: ${{ inputs.python-version }} @@ -75,13 +75,13 @@ jobs: if: ${{ github.repository_owner == 'nv-legate' && contains(github.workflow, 'release') && inputs.upload-enabled == true }} name: Upload package to Server uses: - nv-legate/legate-gh-ci/.github/workflows/gh-upload.yml@push_nightly_docs_to_gh_pages + nv-legate/legate-gh-ci/.github/workflows/gh-upload.yml@v1.28 with: - build-has-tests: false + build-has-tests: ${{ !inputs.upload-enabled }} build-mode: "" build-type: ${{ inputs.build-type }} client-repo: ${{ github.event.repository.name }} - legate-gh-ci-tag: "push_nightly_docs_to_gh_pages" + legate-gh-ci-tag: "v1.28" name: Upload package to Server network: "ucx" pkgSubString: "cupynumeric-" @@ -165,14 +165,14 @@ jobs: matrix: ${{fromJson(needs.setup-test.outputs.matrix)}} uses: - nv-legate/legate-gh-ci/.github/workflows/gh-test-within-container.yml@push_nightly_docs_to_gh_pages + nv-legate/legate-gh-ci/.github/workflows/gh-test-within-container.yml@v1.28 with: - build-has-tests: false + build-has-tests: ${{ !inputs.upload-enabled }} build-mode: "" build-type: ${{ inputs.build-type }} client-repo: ${{ github.event.repository.name }} has-gpu: ${{ matrix.runner.type == 'gpu' }} - legate-gh-ci-tag: "push_nightly_docs_to_gh_pages" + legate-gh-ci-tag: "v1.28" name: ${{ matrix.test-config.name }} network: "ucx" platform: ${{ inputs.platform }} @@ -188,13 +188,13 @@ jobs: name: Update Test status on Server if: ${{ false }} uses: - nv-legate/legate-gh-ci/.github/workflows/gh-upload.yml@push_nightly_docs_to_gh_pages + nv-legate/legate-gh-ci/.github/workflows/gh-upload.yml@v1.28 with: - build-has-tests: false + build-has-tests: ${{ !inputs.upload-enabled }} build-mode: "" build-type: ${{ inputs.build-type }} client-repo: ${{ github.event.repository.name }} - legate-gh-ci-tag: "push_nightly_docs_to_gh_pages" + legate-gh-ci-tag: "v1.28" name: UpdateTestStatus network: "ucx" pkgSubString: "cupynumeric-" diff --git a/.github/workflows/gh-build-docs.yml b/.github/workflows/gh-build-docs.yml index fbc1745b1e..5a72a5dd6a 100644 --- a/.github/workflows/gh-build-docs.yml +++ b/.github/workflows/gh-build-docs.yml @@ -23,7 +23,7 @@ jobs: build-cupynumeric: if: ${{ github.repository_owner == 'nv-legate' }} uses: - nv-legate/legate-gh-ci/.github/workflows/gh-build.yml@push_nightly_docs_to_gh_pages + nv-legate/legate-gh-ci/.github/workflows/gh-build.yml@v1.28 with: build-has-tests: false client-repo: ${{ github.event.repository.name }} @@ -33,7 +33,7 @@ jobs: use-container: ${{ inputs.platform == 'linux' }} platform: ${{ inputs.platform }} dependencies-file: "" - legate-gh-ci-tag: "push_nightly_docs_to_gh_pages" + legate-gh-ci-tag: "v1.28" build-mode: ${{ inputs.build-mode }} upload-enabled: false network: "ucx" @@ -46,7 +46,7 @@ jobs: name: Build cupynumeric docs (${{ inputs.platform }}, ${{ inputs.target-device }}) uses: - nv-legate/legate-gh-ci/.github/workflows/gh-test-within-container.yml@push_nightly_docs_to_gh_pages + nv-legate/legate-gh-ci/.github/workflows/gh-test-within-container.yml@v1.28 with: build-has-tests: false build-mode: ${{ inputs.build-mode }} @@ -54,7 +54,7 @@ jobs: output-build-type: docs client-repo: ${{ github.event.repository.name }} has-gpu: false - legate-gh-ci-tag: "push_nightly_docs_to_gh_pages" + legate-gh-ci-tag: "v1.28" name: Build documentation network: "ucx" platform: ${{ inputs.platform }} From 7569d683b4420a6bc3fe9aa34e9c3856da5600b5 Mon Sep 17 00:00:00 2001 From: Sandeep Datta <128171450+sandeepd-nv@users.noreply.github.com> Date: Tue, 18 Mar 2025 15:33:03 +0530 Subject: [PATCH 445/462] CPP tests (#447) Ignoring known issues. --- .github/workflows/ci-gh-nightly-release.yml | 1 - .github/workflows/ci-gh.yml | 1 - .github/workflows/gh-build-and-test.yml | 52 ++++++++++++++------- .github/workflows/gh-build-docs.yml | 8 ++-- conda/conda-build/build.sh | 6 +++ conda/conda-build/meta.yaml | 11 +++++ continuous_integration/scripts/build | 13 ++++-- continuous_integration/scripts/test | 20 +++++++- src/cupynumeric/ndarray.cc | 7 ++- 9 files changed, 91 insertions(+), 28 deletions(-) diff --git a/.github/workflows/ci-gh-nightly-release.yml b/.github/workflows/ci-gh-nightly-release.yml index deecffaa5d..2ed4693ed3 100644 --- a/.github/workflows/ci-gh-nightly-release.yml +++ b/.github/workflows/ci-gh-nightly-release.yml @@ -36,7 +36,6 @@ jobs: python-version: ${{ matrix.python-version }} target-device: ${{ matrix.target-device }} upload-enabled: ${{ matrix.upload-enabled }} - waive-gpu-tests: ${{ github.workflow == 'Build Release package' && matrix.platform == 'linux-aarch64' }} refname: ${{ github.ref_name }} default-branch: ${{ github.event.repository.default_branch }} secrets: inherit diff --git a/.github/workflows/ci-gh.yml b/.github/workflows/ci-gh.yml index 8b233a6b72..4bb50dd233 100644 --- a/.github/workflows/ci-gh.yml +++ b/.github/workflows/ci-gh.yml @@ -38,7 +38,6 @@ jobs: python-version: ${{ matrix.python-version }} target-device: ${{ matrix.target-device }} upload-enabled: ${{ matrix.upload-enabled }} - waive-gpu-tests: ${{ github.workflow == 'Build Release package' && matrix.platform == 'linux-aarch64' }} refname: ${{ github.ref_name }} default-branch: ${{ github.event.repository.default_branch }} secrets: inherit diff --git a/.github/workflows/gh-build-and-test.yml b/.github/workflows/gh-build-and-test.yml index 1ec9cb651f..68cb304091 100644 --- a/.github/workflows/gh-build-and-test.yml +++ b/.github/workflows/gh-build-and-test.yml @@ -13,10 +13,6 @@ on: upload-enabled: type: boolean required: true - waive-gpu-tests: - required: true - type: boolean - description: Waive GPU tests based on specific configuration python-version: required: false type: string @@ -53,14 +49,14 @@ jobs: needs: setup-build name: "Build (${{ inputs.platform }}, ${{ inputs.target-device }}, ${{ inputs.build-type }}, Python ${{ inputs.python-version }})" uses: - nv-legate/legate-gh-ci/.github/workflows/gh-build.yml@v1.28 + nv-legate/legate-gh-ci/.github/workflows/gh-build.yml@cpp_tests with: build-has-tests: ${{ !inputs.upload-enabled }} build-mode: "" build-type: ${{ inputs.build-type }} client-repo: ${{ github.event.repository.name }} dependencies-file: "" - legate-gh-ci-tag: "v1.28" + legate-gh-ci-tag: "cpp_tests" network: "ucx" platform: ${{ inputs.platform }} python-version: ${{ inputs.python-version }} @@ -75,13 +71,13 @@ jobs: if: ${{ github.repository_owner == 'nv-legate' && contains(github.workflow, 'release') && inputs.upload-enabled == true }} name: Upload package to Server uses: - nv-legate/legate-gh-ci/.github/workflows/gh-upload.yml@v1.28 + nv-legate/legate-gh-ci/.github/workflows/gh-upload.yml@cpp_tests with: build-has-tests: ${{ !inputs.upload-enabled }} build-mode: "" build-type: ${{ inputs.build-type }} client-repo: ${{ github.event.repository.name }} - legate-gh-ci-tag: "v1.28" + legate-gh-ci-tag: "cpp_tests" name: Upload package to Server network: "ucx" pkgSubString: "cupynumeric-" @@ -107,19 +103,24 @@ jobs: - id: set-matrix run: | set -xeuo pipefail + MATRIX_JSON='{"include": [' + RUNNERS=( 'linux-amd64-gpu-v100-latest-1:gpu:gpu:linux' 'linux-amd64-2gpu:gpu:2gpu:linux' 'linux-amd64-cpu16:cpu:cpu:linux' 'linux-arm64-cpu16:cpu:cpu:linux-aarch64' 'linux-aarch64-2gpu:gpu:2gpu:linux-aarch64' 'linux-aarch64-2gpu:gpu:gpu:linux-aarch64' 'macos-latest:cpu:cpu:mac') + TEST_CONFIGS=( '1 CPU test:test --cpus 1 --debug:cpu' '1 CPU test:test --cpus 1 --debug:gpu' '2 CPU test:test --cpus 2 --debug:cpu' '2 CPU test:test --cpus 2 --debug:gpu' - # set the number of workers manually because nvidia runners report 6 gpus when onyl one is really available - # this workaround can be removed when the number of available gpus is reported correctly (when we run on VMs) + # Set the number of workers manually because nvidia runners report 6 + # gpus when only one is really available this workaround can be + # removed when the number of available gpus is reported correctly + # (when we run on VMs) 'GPU test:test --use cuda --gpus 1 -j 7 --debug:gpu' '2 GPU test:test --use cuda --gpus 2 --debug:2gpu' 'OpenMP test:test --use openmp --omps 1 --ompthreads 2 --debug:gpu' @@ -130,29 +131,48 @@ jobs: 'Eager execution test:test --use eager --debug:cpu' 'mypy:mypy:cpu' 'Unit tests:unit:cpu' + 'CPP tests:cpp:cpu' + # TODO: Uncomment the following lines once + # https://github.com/nv-legate/cupynumeric.internal/issues/654 has + # been fixed. + # 'CPP tests:cpp:gpu' + # 'CPP tests:cpp:2gpu' ) + for RUNNER in "${RUNNERS[@]}"; do IFS=':' read -ra RUNNER_INFO <<< "$RUNNER" RUNNER_NAME=${RUNNER_INFO[0]} RUNNER_TYPE=${RUNNER_INFO[1]} RUNNER_DEVICE=${RUNNER_INFO[2]} RUNNER_PLATFORM=${RUNNER_INFO[3]} + if [[ "$RUNNER_TYPE" == "${{ inputs.target-device }}" && "$RUNNER_PLATFORM" == "${{ inputs.platform }}" ]]; then + for TEST_CONFIG in "${TEST_CONFIGS[@]}"; do IFS=':' read -ra CONFIG_INFO <<< "$TEST_CONFIG" TEST_NAME=${CONFIG_INFO[0]} TEST_OPTIONS=${CONFIG_INFO[1]} TEST_TARGET_DEVICE=${CONFIG_INFO[2]} + + # Note: we don't have enough linux-aarch64 GPU runners to + # support per commit testing. This is why these tests are waived + # here. + WAIVE_TEST="${{ inputs.target-device == 'gpu' && inputs.build-type == 'ci' && inputs.platform == 'linux-aarch64' }}" + if [[ "$TEST_TARGET_DEVICE" == "$RUNNER_DEVICE" ]]; then - if ! [[ "$TEST_NAME" =~ "GPU" && "${{ inputs.waive-gpu-tests }}" == 'true' ]]; then + if [[ "${WAIVE_TEST}" == "false" ]]; then MATRIX_JSON+="{\"runner\": {\"name\": \"$RUNNER_NAME\", \"type\": \"$RUNNER_TYPE\", \"platform\": \"$RUNNER_PLATFORM\"}, \"test-config\": {\"name\": \"$TEST_NAME\", \"test-options\": \"$TEST_OPTIONS\"}}," fi fi done fi done - MATRIX_JSON=$(echo "$MATRIX_JSON" | sed 's/,$//') # Remove the trailing comma + + # Remove the trailing comma + MATRIX_JSON=$(echo "$MATRIX_JSON" | sed 's/,$//') + # Terminate JSON expression MATRIX_JSON+=']}' + echo "matrix=$MATRIX_JSON" >> $GITHUB_OUTPUT test: @@ -165,14 +185,14 @@ jobs: matrix: ${{fromJson(needs.setup-test.outputs.matrix)}} uses: - nv-legate/legate-gh-ci/.github/workflows/gh-test-within-container.yml@v1.28 + nv-legate/legate-gh-ci/.github/workflows/gh-test-within-container.yml@cpp_tests with: build-has-tests: ${{ !inputs.upload-enabled }} build-mode: "" build-type: ${{ inputs.build-type }} client-repo: ${{ github.event.repository.name }} has-gpu: ${{ matrix.runner.type == 'gpu' }} - legate-gh-ci-tag: "v1.28" + legate-gh-ci-tag: "cpp_tests" name: ${{ matrix.test-config.name }} network: "ucx" platform: ${{ inputs.platform }} @@ -188,13 +208,13 @@ jobs: name: Update Test status on Server if: ${{ false }} uses: - nv-legate/legate-gh-ci/.github/workflows/gh-upload.yml@v1.28 + nv-legate/legate-gh-ci/.github/workflows/gh-upload.yml@cpp_tests with: build-has-tests: ${{ !inputs.upload-enabled }} build-mode: "" build-type: ${{ inputs.build-type }} client-repo: ${{ github.event.repository.name }} - legate-gh-ci-tag: "v1.28" + legate-gh-ci-tag: "cpp_tests" name: UpdateTestStatus network: "ucx" pkgSubString: "cupynumeric-" diff --git a/.github/workflows/gh-build-docs.yml b/.github/workflows/gh-build-docs.yml index 5a72a5dd6a..835fed0e1d 100644 --- a/.github/workflows/gh-build-docs.yml +++ b/.github/workflows/gh-build-docs.yml @@ -23,7 +23,7 @@ jobs: build-cupynumeric: if: ${{ github.repository_owner == 'nv-legate' }} uses: - nv-legate/legate-gh-ci/.github/workflows/gh-build.yml@v1.28 + nv-legate/legate-gh-ci/.github/workflows/gh-build.yml@cpp_tests with: build-has-tests: false client-repo: ${{ github.event.repository.name }} @@ -33,7 +33,7 @@ jobs: use-container: ${{ inputs.platform == 'linux' }} platform: ${{ inputs.platform }} dependencies-file: "" - legate-gh-ci-tag: "v1.28" + legate-gh-ci-tag: "cpp_tests" build-mode: ${{ inputs.build-mode }} upload-enabled: false network: "ucx" @@ -46,7 +46,7 @@ jobs: name: Build cupynumeric docs (${{ inputs.platform }}, ${{ inputs.target-device }}) uses: - nv-legate/legate-gh-ci/.github/workflows/gh-test-within-container.yml@v1.28 + nv-legate/legate-gh-ci/.github/workflows/gh-test-within-container.yml@cpp_tests with: build-has-tests: false build-mode: ${{ inputs.build-mode }} @@ -54,7 +54,7 @@ jobs: output-build-type: docs client-repo: ${{ github.event.repository.name }} has-gpu: false - legate-gh-ci-tag: "v1.28" + legate-gh-ci-tag: "cpp_tests" name: Build documentation network: "ucx" platform: ${{ inputs.platform }} diff --git a/conda/conda-build/build.sh b/conda/conda-build/build.sh index 2a7b6589fc..4cbf5d1afb 100644 --- a/conda/conda-build/build.sh +++ b/conda/conda-build/build.sh @@ -29,6 +29,12 @@ else -Dcupynumeric_cuRAND_INCLUDE_DIR=$PREFIX/targets/x86_64-linux/include" fi +# We rely on an environment variable to determine if we need to build cpp tests +if [[ "$BUILD_TESTS" == "1" ]]; then + CMAKE_ARGS+=" +-Dcupynumeric_BUILD_TESTS=ON" +fi + export CMAKE_GENERATOR=Ninja export CUDAHOSTCXX=${CXX} export OPENSSL_DIR="$PREFIX" diff --git a/conda/conda-build/meta.yaml b/conda/conda-build/meta.yaml index 103727fdc9..a9ca0c3117 100644 --- a/conda/conda-build/meta.yaml +++ b/conda/conda-build/meta.yaml @@ -19,6 +19,14 @@ {# We need to have a default value for the initial pass over the recipe #} {% set upload_build_bool = false %} {% endif %} +{% if build_tests == "true" %} + {% set build_tests_bool = true %} +{% elif build_tests == "false" %} + {% set build_tests_bool = false %} +{% else %} + {# We need to have a default value for the initial pass over the recipe #} + {% set build_tests_bool = false %} +{% endif %} ## The placeholder version is strictly for making two-pass conda build process. ## It should not be used for any other purpose, and this is not a default version. {% set placeholder_version = '0.0.0.dev' %} @@ -83,6 +91,9 @@ build: - SCCACHE_S3_KEY_PREFIX - AWS_ACCESS_KEY_ID - AWS_SECRET_ACCESS_KEY +{% if build_tests_bool %} + - BUILD_TESTS=1 +{% endif %} {% if not gpu_enabled_bool %} - CPU_ONLY=1 # The CPU-only packages having more track_features than the GPU builds helps diff --git a/continuous_integration/scripts/build b/continuous_integration/scripts/build index b5f21379da..a5b30af5f5 100755 --- a/continuous_integration/scripts/build +++ b/continuous_integration/scripts/build @@ -2,7 +2,7 @@ set -x build_release_product() { - set -xeo pipefail; + set -xeuo pipefail; echo "RUNNING build_release_product" @@ -28,15 +28,22 @@ build_release_product() { UPLOAD_BUILD=true [ "${UPLOAD_ENABLED:-}" = "OFF" ] && UPLOAD_BUILD=false - variantOpts=$(printf "{\"gpu_enabled\": [$GPU_ENABLED], \"upload_build\": [$UPLOAD_BUILD], \"python\": [$PYTHON_VERSION]}") + variantOpts=$(printf "{\"gpu_enabled\": [$GPU_ENABLED], \"build_tests\": [$BUILD_TESTS], \"upload_build\": [$UPLOAD_BUILD], \"python\": [$PYTHON_VERSION]}") conda_build_args+=(--variants "$variantOpts") # https://github.com/nv-legate/cupynumeric.internal/pull/351#issuecomment-2286922486 export CONDA_OVERRIDE_CUDA="${CUDA_VERSION}" + # Use the new .conda format. conda config --set conda_build.pkg_format 2 - conda mambabuild "${conda_build_args[@]}" "${REPO_DIR}/conda/conda-build"; + + set +u; + + # For whatever reason, the default buffering of conda/mamba is not sufficient, and + # leads to garbled output in CI (mixing conda output and whatever build.sh prints). So + # we need to force unbuffered output. + stdbuf -o0 -e0 conda mambabuild "${conda_build_args[@]}" "${REPO_DIR}/conda/conda-build"; copy_release_artifacts } diff --git a/continuous_integration/scripts/test b/continuous_integration/scripts/test index 686c34661b..7b3550c153 100755 --- a/continuous_integration/scripts/test +++ b/continuous_integration/scripts/test @@ -38,6 +38,14 @@ setup_unit_env() { legate-mamba-retry install -y pytest pytest-mock mock cffi } +run_legate_issue() { + if command -v "legate-issue" &> /dev/null; then + legate-issue + else + echo "WARNING: legate-issue not found." + fi +} + test_cupynumeric() { set -xeo pipefail @@ -56,19 +64,21 @@ test_cupynumeric() { echo "Executing tests..." shift; setup_test_env; + run_legate_issue; ./test.py -vv --timeout 300 "$@" ;; "mypy") echo "Installing and executing mypy..." shift; setup_mypy_env; + run_legate_issue; mypy cupynumeric ;; "docs") echo "Building docs..." shift; - setup_docs_env; + run_legate_issue; cd docs/cupynumeric make clean html # ls -lah . @@ -80,8 +90,16 @@ test_cupynumeric() { echo "Running Unit tests..." shift; setup_unit_env; + run_legate_issue; LEGATE_AUTO_CONFIG=0 pytest tests/unit ;; + "cpp") + echo "Running CPP tests..." + shift; + run_legate_issue; + export LD_LIBRARY_PATH=${CONDA_PREFIX}/lib/legate/deps:${LD_LIBRARY_PATH:-} + REALM_BACKTRACE=1 LEGATE_TEST=1 LEGATE_LOG_MAPPING=1 ${CONDA_PREFIX}/bin/cpp_tests + ;; *) echo "Invalid command: $1" return 1 diff --git a/src/cupynumeric/ndarray.cc b/src/cupynumeric/ndarray.cc index fc94dbf429..6629832a69 100644 --- a/src/cupynumeric/ndarray.cc +++ b/src/cupynumeric/ndarray.cc @@ -703,10 +703,13 @@ NDArray NDArray::unique() auto part_in = task.declare_partition(); task.add_output(result.store_, part_out); task.add_input(store_, part_in); - task.add_communicator("nccl"); - if (!has_gpus) { + + if (has_gpus) { + task.add_communicator("nccl"); + } else { task.add_constraint(legate::broadcast(part_in, legate::from_range(0, dim()))); } + runtime->submit(std::move(task)); return result; } From a29a2cf93aa8a8c2cec14dbe02283e1a363eb536 Mon Sep 17 00:00:00 2001 From: "Marcus D. Hanwell" Date: Tue, 18 Mar 2025 13:16:35 -0400 Subject: [PATCH 446/462] Disable ARM64 GPU testing for wheels (#655) This is due to limited availability of ARM64 GPU runners for the project. --- .github/workflows/wheels-test.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/wheels-test.yml b/.github/workflows/wheels-test.yml index 50c1799644..6634498cf7 100644 --- a/.github/workflows/wheels-test.yml +++ b/.github/workflows/wheels-test.yml @@ -65,10 +65,10 @@ jobs: - { ARCH: 'amd64', PY_VER: '3.10', TARGET_DEV: 'gpu', GPU: 'l4', LINUX_VER: 'ubuntu22.04' } - { ARCH: 'amd64', PY_VER: '3.11', TARGET_DEV: 'gpu', GPU: 'l4', LINUX_VER: 'ubuntu22.04' } - { ARCH: 'amd64', PY_VER: '3.12', TARGET_DEV: 'gpu', GPU: 'l4', LINUX_VER: 'ubuntu24.04' } - # arm64 - - { ARCH: 'arm64', PY_VER: '3.10', TARGET_DEV: 'gpu', GPU: 'a100', LINUX_VER: 'ubuntu22.04' } - - { ARCH: 'arm64', PY_VER: '3.11', TARGET_DEV: 'gpu', GPU: 'a100', LINUX_VER: 'ubuntu22.04' } - - { ARCH: 'arm64', PY_VER: '3.12', TARGET_DEV: 'gpu', GPU: 'a100', LINUX_VER: 'ubuntu24.04' } + # arm64 - disabled due to ARM GPU runner availability + # - { ARCH: 'arm64', PY_VER: '3.10', TARGET_DEV: 'gpu', GPU: 'a100', LINUX_VER: 'ubuntu22.04' } + # - { ARCH: 'arm64', PY_VER: '3.11', TARGET_DEV: 'gpu', GPU: 'a100', LINUX_VER: 'ubuntu22.04' } + # - { ARCH: 'arm64', PY_VER: '3.12', TARGET_DEV: 'gpu', GPU: 'a100', LINUX_VER: 'ubuntu24.04' } " MATRIX="$( From bdceb14a5d3a14844af8ab033504e162d2f43c9f Mon Sep 17 00:00:00 2001 From: Sandeep Datta <128171450+sandeepd-nv@users.noreply.github.com> Date: Wed, 19 Mar 2025 21:52:38 +0530 Subject: [PATCH 447/462] Upload docs only on main (#657) Also ... - Use legate-gh-ci@v1.29. - Use a cpu16 machine to build cupynumeric even when building docs. --- .github/workflows/gh-build-and-test.yml | 16 ++++++++-------- .github/workflows/gh-build-docs.yml | 12 ++++++------ 2 files changed, 14 insertions(+), 14 deletions(-) diff --git a/.github/workflows/gh-build-and-test.yml b/.github/workflows/gh-build-and-test.yml index 68cb304091..50ce55778c 100644 --- a/.github/workflows/gh-build-and-test.yml +++ b/.github/workflows/gh-build-and-test.yml @@ -49,14 +49,14 @@ jobs: needs: setup-build name: "Build (${{ inputs.platform }}, ${{ inputs.target-device }}, ${{ inputs.build-type }}, Python ${{ inputs.python-version }})" uses: - nv-legate/legate-gh-ci/.github/workflows/gh-build.yml@cpp_tests + nv-legate/legate-gh-ci/.github/workflows/gh-build.yml@v1.29 with: build-has-tests: ${{ !inputs.upload-enabled }} build-mode: "" build-type: ${{ inputs.build-type }} client-repo: ${{ github.event.repository.name }} dependencies-file: "" - legate-gh-ci-tag: "cpp_tests" + legate-gh-ci-tag: "v1.29" network: "ucx" platform: ${{ inputs.platform }} python-version: ${{ inputs.python-version }} @@ -71,13 +71,13 @@ jobs: if: ${{ github.repository_owner == 'nv-legate' && contains(github.workflow, 'release') && inputs.upload-enabled == true }} name: Upload package to Server uses: - nv-legate/legate-gh-ci/.github/workflows/gh-upload.yml@cpp_tests + nv-legate/legate-gh-ci/.github/workflows/gh-upload.yml@v1.29 with: build-has-tests: ${{ !inputs.upload-enabled }} build-mode: "" build-type: ${{ inputs.build-type }} client-repo: ${{ github.event.repository.name }} - legate-gh-ci-tag: "cpp_tests" + legate-gh-ci-tag: "v1.29" name: Upload package to Server network: "ucx" pkgSubString: "cupynumeric-" @@ -185,14 +185,14 @@ jobs: matrix: ${{fromJson(needs.setup-test.outputs.matrix)}} uses: - nv-legate/legate-gh-ci/.github/workflows/gh-test-within-container.yml@cpp_tests + nv-legate/legate-gh-ci/.github/workflows/gh-test-within-container.yml@v1.29 with: build-has-tests: ${{ !inputs.upload-enabled }} build-mode: "" build-type: ${{ inputs.build-type }} client-repo: ${{ github.event.repository.name }} has-gpu: ${{ matrix.runner.type == 'gpu' }} - legate-gh-ci-tag: "cpp_tests" + legate-gh-ci-tag: "v1.29" name: ${{ matrix.test-config.name }} network: "ucx" platform: ${{ inputs.platform }} @@ -208,13 +208,13 @@ jobs: name: Update Test status on Server if: ${{ false }} uses: - nv-legate/legate-gh-ci/.github/workflows/gh-upload.yml@cpp_tests + nv-legate/legate-gh-ci/.github/workflows/gh-upload.yml@v1.29 with: build-has-tests: ${{ !inputs.upload-enabled }} build-mode: "" build-type: ${{ inputs.build-type }} client-repo: ${{ github.event.repository.name }} - legate-gh-ci-tag: "cpp_tests" + legate-gh-ci-tag: "v1.29" name: UpdateTestStatus network: "ucx" pkgSubString: "cupynumeric-" diff --git a/.github/workflows/gh-build-docs.yml b/.github/workflows/gh-build-docs.yml index 835fed0e1d..57dd3bf54e 100644 --- a/.github/workflows/gh-build-docs.yml +++ b/.github/workflows/gh-build-docs.yml @@ -23,17 +23,17 @@ jobs: build-cupynumeric: if: ${{ github.repository_owner == 'nv-legate' }} uses: - nv-legate/legate-gh-ci/.github/workflows/gh-build.yml@cpp_tests + nv-legate/legate-gh-ci/.github/workflows/gh-build.yml@v1.29 with: build-has-tests: false client-repo: ${{ github.event.repository.name }} target-device: ${{ inputs.target-device }} - runs-on: ${{ (inputs.platform == 'linux' && 'linux-amd64-cpu4') || (inputs.platform == 'mac' && 'macos-latest') }} + runs-on: ${{ (inputs.platform == 'linux' && 'linux-amd64-cpu16') || (inputs.platform == 'mac' && 'macos-latest') }} build-type: ${{ inputs.build-type }} use-container: ${{ inputs.platform == 'linux' }} platform: ${{ inputs.platform }} dependencies-file: "" - legate-gh-ci-tag: "cpp_tests" + legate-gh-ci-tag: "v1.29" build-mode: ${{ inputs.build-mode }} upload-enabled: false network: "ucx" @@ -46,7 +46,7 @@ jobs: name: Build cupynumeric docs (${{ inputs.platform }}, ${{ inputs.target-device }}) uses: - nv-legate/legate-gh-ci/.github/workflows/gh-test-within-container.yml@cpp_tests + nv-legate/legate-gh-ci/.github/workflows/gh-test-within-container.yml@v1.29 with: build-has-tests: false build-mode: ${{ inputs.build-mode }} @@ -54,7 +54,7 @@ jobs: output-build-type: docs client-repo: ${{ github.event.repository.name }} has-gpu: false - legate-gh-ci-tag: "cpp_tests" + legate-gh-ci-tag: "v1.29" name: Build documentation network: "ucx" platform: ${{ inputs.platform }} @@ -67,7 +67,7 @@ jobs: upload-docs-to-gh-pages: - if: ${{ inputs.upload-docs-to-gh-pages }} + if: ${{ inputs.upload-docs-to-gh-pages && github.ref_name == 'main' }} needs: - build-docs runs-on: ${{ (inputs.platform == 'linux' && 'linux-amd64-cpu4') || (inputs.platform == 'mac' && 'macos-latest') }} From 06244e4252da99cf7522a34be7271cea4c01320d Mon Sep 17 00:00:00 2001 From: Manolis Papadakis Date: Wed, 19 Mar 2025 15:59:17 -0700 Subject: [PATCH 448/462] Update for removal of LEGATE_NEED_* envvars (#650) * Update for removal of LEGATE_NEED_* envvars * Update to latest legate * add channel for UCC 1.4.0 * one more ucc140 * typo --------- Co-authored-by: Marcin Zalewski --- cmake/versions.json | 2 +- continuous_integration/scripts/build | 1 + continuous_integration/scripts/test | 2 +- tests/integration/test_convolve.py | 5 ++--- tests/integration/test_nd_convolve.py | 4 ---- 5 files changed, 5 insertions(+), 9 deletions(-) diff --git a/cmake/versions.json b/cmake/versions.json index ee6bc22fdf..a8b44699a5 100644 --- a/cmake/versions.json +++ b/cmake/versions.json @@ -10,7 +10,7 @@ "git_url" : "git@github.com:nv-legate/legate.internal.git", "git_shallow": false, "always_download": false, - "git_tag" : "9f48a1b40d6028ac86e1d4e8a6ce2a7d5c84245f", + "git_tag" : "fe71160b63291c1d073090ad2cb7a11c618d958a", "anaconda_label": "experimental" } } diff --git a/continuous_integration/scripts/build b/continuous_integration/scripts/build index a5b30af5f5..5c8d92ad9b 100755 --- a/continuous_integration/scripts/build +++ b/continuous_integration/scripts/build @@ -11,6 +11,7 @@ build_release_product() { local conda_build_args=(); # The channel sequence below needs to be preserved conda_build_args+=(-c https://conda.anaconda.org/${CONDA_CHANNEL}/label/${CONDA_LABEL}); + conda_build_args+=(-c legate/label/ucc140); conda_build_args+=(-c conda-forge); conda_build_args+=(--override-channels); conda_build_args+=(--croot /tmp/conda-build/cupynumeric); diff --git a/continuous_integration/scripts/test b/continuous_integration/scripts/test index 7b3550c153..0bdb65d914 100755 --- a/continuous_integration/scripts/test +++ b/continuous_integration/scripts/test @@ -16,7 +16,7 @@ setup_env() { legate-mamba-retry search --override-channels -c "${ARTIFACTS_DIR}/conda-build/cupynumeric" --info cupynumeric # This requires strict channel priority to work (prioritize local channel) - legate-mamba-retry create -y -n legate -c "${ARTIFACTS_DIR}/conda-build/cupynumeric" -c https://conda.anaconda.org/${CONDA_CHANNEL}/label/${CONDA_LABEL} -c conda-forge legate cupynumeric + legate-mamba-retry create -y -n legate -c "${ARTIFACTS_DIR}/conda-build/cupynumeric" -c https://conda.anaconda.org/${CONDA_CHANNEL}/label/${CONDA_LABEL} -c legate/label/ucc140 -c conda-forge legate cupynumeric } setup_test_env() { diff --git a/tests/integration/test_convolve.py b/tests/integration/test_convolve.py index be1dbaf83b..5410781425 100644 --- a/tests/integration/test_convolve.py +++ b/tests/integration/test_convolve.py @@ -13,16 +13,15 @@ # limitations under the License. # -import os - import numpy as np import pytest import scipy.signal as sig from utils.comparisons import allclose import cupynumeric as num +from cupynumeric.runtime import runtime -CUDA_TEST = os.environ.get("LEGATE_NEED_CUDA") == "1" +CUDA_TEST = runtime.num_gpus > 0 SHAPES = [(100,), (10, 10), (10, 10, 10), (32, 2, 32)] FILTER_SHAPES = [(5,), (3, 5), (3, 5, 3), (32, 1, 32)] diff --git a/tests/integration/test_nd_convolve.py b/tests/integration/test_nd_convolve.py index 6685c45394..cde3feff23 100644 --- a/tests/integration/test_nd_convolve.py +++ b/tests/integration/test_nd_convolve.py @@ -13,15 +13,11 @@ # limitations under the License. # -import os - import pytest from utils.comparisons import allclose import cupynumeric as num -CUDA_TEST = os.environ.get("LEGATE_NEED_CUDA") == "1" - def test_interpolation_x(): import scipy.signal as signal From 3e88ba2bae52d7ba74b73367317b035404856447 Mon Sep 17 00:00:00 2001 From: "Marcus D. Hanwell" Date: Thu, 20 Mar 2025 14:30:07 -0400 Subject: [PATCH 449/462] Remove debug and don't cache the wheel (#660) This switches to using artifact upload/download to pass the wheel from build to test jobs. It removes some debug and pins the action being used for inter-repository artifact download to the v9 release SHA. --- .github/workflows/wheels-build.yml | 17 ++--------------- .github/workflows/wheels-test.yml | 18 ++++++------------ 2 files changed, 8 insertions(+), 27 deletions(-) diff --git a/.github/workflows/wheels-build.yml b/.github/workflows/wheels-build.yml index 06abb34d73..e8768f4179 100644 --- a/.github/workflows/wheels-build.yml +++ b/.github/workflows/wheels-build.yml @@ -98,12 +98,6 @@ jobs: run: | sha=$(echo ${{github.sha}} | head -c 10) echo "sha=$sha" >> $GITHUB_OUTPUT - - name: Dump GitHub context - env: - GITHUB_CONTEXT: ${{ toJson(github) }} - run: echo "$GITHUB_CONTEXT" - - name: Check it worked... - run: echo "${{steps.get-sha.outputs.sha}}" - if: github.repository_owner == 'nv-legate' name: Get AWS credentials for sccache bucket uses: aws-actions/configure-aws-credentials@v4 @@ -123,7 +117,8 @@ jobs: id: cache env: BUILD_NAME: ${{ matrix.ARCH }}-${{ matrix.TARGET_DEV }}-cuda${{ inputs.cuda_ver }}-py${{ matrix.PY_VER }} - uses: dawidd6/action-download-artifact@v8 + # Pin to the v9 release SHA + uses: dawidd6/action-download-artifact@07ab29fd4a977ae4d2b275087cf67563dfdf0295 with: github_token: ${{ secrets.NV_LEGATE_INTER_REPOS_ACCESS_RO }} repo: nv-legate/legate.internal @@ -145,11 +140,3 @@ jobs: with: name: cupynumeric-wheel-${{ env.BUILD_NAME }}-g${{ env.BUILD_SHA }} path: final-dist/*.whl - - name: Cache the wheel - env: - BUILD_SHA: ${{ steps.get-sha.outputs.sha }} - BUILD_NAME: ${{ matrix.ARCH }}-${{ matrix.TARGET_DEV }}-cuda${{ inputs.cuda_ver }}-py${{ matrix.PY_VER }} - uses: actions/cache@v4 - with: - path: final-dist/*.whl - key: wheel-${{ env.BUILD_NAME }}-g${{ env.BUILD_SHA }} diff --git a/.github/workflows/wheels-test.yml b/.github/workflows/wheels-test.yml index 6634498cf7..28ef42d328 100644 --- a/.github/workflows/wheels-test.yml +++ b/.github/workflows/wheels-test.yml @@ -96,12 +96,6 @@ jobs: run: | sha=$(echo ${{github.sha}} | head -c 10) echo "sha=$sha" >> $GITHUB_OUTPUT - - name: Dump GitHub context - env: - GITHUB_CONTEXT: ${{ toJson(github) }} - run: echo "$GITHUB_CONTEXT" - - name: Check it worked... - run: echo "${{steps.get-sha.outputs.sha}}" - uses: actions/checkout@v4 with: repository: ${{ inputs.repo }} @@ -121,7 +115,8 @@ jobs: id: cache env: BUILD_NAME: ${{ matrix.ARCH }}-${{ matrix.TARGET_DEV }}-cuda12.5.1-py${{ matrix.PY_VER }} - uses: dawidd6/action-download-artifact@v8 + # Pin to the v9 release SHA + uses: dawidd6/action-download-artifact@07ab29fd4a977ae4d2b275087cf67563dfdf0295 with: github_token: ${{ secrets.NV_LEGATE_INTER_REPOS_ACCESS_RO }} repo: nv-legate/legate.internal @@ -130,14 +125,13 @@ jobs: name: "legate-wheel-${{ env.BUILD_NAME }}" check_artifacts: true path: wheel - - name: Restore the cached wheel - id: cached-wheel + - name: Download the wheel from the build job env: BUILD_SHA: ${{ steps.get-sha.outputs.sha }} BUILD_NAME: ${{ matrix.ARCH }}-${{ matrix.TARGET_DEV }}-cuda12.5.1-py${{ matrix.PY_VER }} - uses: actions/cache@v4 + uses: actions/download-artifact@v4 with: - path: final-dist/*.whl - key: wheel-${{ env.BUILD_NAME }}-g${{ env.BUILD_SHA }} + path: final-dist + name: cupynumeric-wheel-${{ env.BUILD_NAME }}-g${{ env.BUILD_SHA }} - name: Run tests run: ${{ inputs.script }} From c07bc2e502d14f5d8aebc241c4936b6d26c319d8 Mon Sep 17 00:00:00 2001 From: "Marcus D. Hanwell" Date: Mon, 24 Mar 2025 11:57:52 -0400 Subject: [PATCH 450/462] Migrate to a `gh` CLI based artifact download (#661) Add some utility scripts to use the official `gh` CLI to look up the run ID and download artifacts. This enables us to migrate away from the third-party action and offers a base of capabilities as we split the conda packages and pip wheels. This is inspired by and shamelessly takes from https://github.com/rapidsai/gha-tools/, specifically `rapids-download-from-github` and `rapids-github-run-id` adapting them for these projects. --- .github/workflows/wheels-build.yml | 18 +++------- .github/workflows/wheels-test.yml | 16 +++------ .../scripts/tools/legate-gh-download-artifact | 34 +++++++++++++++++++ .../scripts/tools/legate-gh-run-id | 30 ++++++++++++++++ 4 files changed, 73 insertions(+), 25 deletions(-) create mode 100755 continuous_integration/scripts/tools/legate-gh-download-artifact create mode 100755 continuous_integration/scripts/tools/legate-gh-run-id diff --git a/.github/workflows/wheels-build.yml b/.github/workflows/wheels-build.yml index e8768f4179..fa91eebee0 100644 --- a/.github/workflows/wheels-build.yml +++ b/.github/workflows/wheels-build.yml @@ -112,21 +112,13 @@ jobs: fetch-depth: 0 - name: Add default paths to the env run: | - echo $(pwd)/continuous_integration/scripts/tools >> "${GITHUB_PATH}" - - name: Get the legate wheel - id: cache + echo "$(pwd)"/continuous_integration/scripts/tools >> "${GITHUB_PATH}" + - name: Download the legate wheel env: BUILD_NAME: ${{ matrix.ARCH }}-${{ matrix.TARGET_DEV }}-cuda${{ inputs.cuda_ver }}-py${{ matrix.PY_VER }} - # Pin to the v9 release SHA - uses: dawidd6/action-download-artifact@07ab29fd4a977ae4d2b275087cf67563dfdf0295 - with: - github_token: ${{ secrets.NV_LEGATE_INTER_REPOS_ACCESS_RO }} - repo: nv-legate/legate.internal - workflow: pr.yml - commit: ${{ inputs.legate-sha }} - name: "legate-wheel-${{ env.BUILD_NAME }}" - check_artifacts: true - path: wheel + GH_TOKEN: ${{ secrets.NV_LEGATE_INTER_REPOS_ACCESS_RO }} + run: | + legate-gh-download-artifact ${{ inputs.legate-sha }} "legate-wheel-${{ env.BUILD_NAME }}" "wheel" - name: Wheel build run: ${{ inputs.script }} env: diff --git a/.github/workflows/wheels-test.yml b/.github/workflows/wheels-test.yml index 28ef42d328..a0db1b5145 100644 --- a/.github/workflows/wheels-test.yml +++ b/.github/workflows/wheels-test.yml @@ -111,20 +111,12 @@ jobs: continue-on-error: true # Skip the cache on RDS Lab nodes if: ${{ matrix.GPU != 'v100' && matrix.GPU != 'a100' }} - - name: Get the legate wheel - id: cache + - name: Download the legate wheel env: BUILD_NAME: ${{ matrix.ARCH }}-${{ matrix.TARGET_DEV }}-cuda12.5.1-py${{ matrix.PY_VER }} - # Pin to the v9 release SHA - uses: dawidd6/action-download-artifact@07ab29fd4a977ae4d2b275087cf67563dfdf0295 - with: - github_token: ${{ secrets.NV_LEGATE_INTER_REPOS_ACCESS_RO }} - repo: nv-legate/legate.internal - workflow: pr.yml - commit: ${{ inputs.legate-sha }} - name: "legate-wheel-${{ env.BUILD_NAME }}" - check_artifacts: true - path: wheel + GH_TOKEN: ${{ secrets.NV_LEGATE_INTER_REPOS_ACCESS_RO }} + run: | + legate-gh-download-artifact ${{ inputs.legate-sha }} "legate-wheel-${{ env.BUILD_NAME }}" "wheel" - name: Download the wheel from the build job env: BUILD_SHA: ${{ steps.get-sha.outputs.sha }} diff --git a/continuous_integration/scripts/tools/legate-gh-download-artifact b/continuous_integration/scripts/tools/legate-gh-download-artifact new file mode 100755 index 0000000000..5accd59edd --- /dev/null +++ b/continuous_integration/scripts/tools/legate-gh-download-artifact @@ -0,0 +1,34 @@ +#!/usr/bin/env bash + +# SPDX-FileCopyrightText: Copyright (c) 2025-2025 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# SPDX-License-Identifier: Apache-2.0 + +# A utility script adapted from https://github.com/rapidsai/gha-tools/blob/main/tools/rapids-download-from-github +# Given a git SHA, artifact name and output path grab the artifact from the run. + +set -euo pipefail + +# Default values for the environment variables. +LEGATE_REPO_NAME=${LEGATE_REPO_NAME:-"nv-legate/legate.internal"} + +# Check if the script was called with exactly 1 argument +if [[ ${#} -ne 3 ]]; then + echo "Error: This script requires exactly 3 arguments (the git SHA, the artifact name, and the output path)." + echo "You provided ${#} arguments." + echo "Usage: ${0} git-sha artifact-name output-path" + exit 1 +fi + +# Poppulate our variables from the arguments. +run_id=$(legate-gh-run-id "${1}") +artifact_name="${2}" +output_path="${3}" + +echo "Downloading and decompressing artifact ${artifact_name} from run ${run_id} to ${output_path}" + +gh run download "${run_id}" \ + --repo "${LEGATE_REPO_NAME}" \ + --name "${artifact_name}" \ + --dir "${output_path}" + +echo -n "${output_path}" diff --git a/continuous_integration/scripts/tools/legate-gh-run-id b/continuous_integration/scripts/tools/legate-gh-run-id new file mode 100755 index 0000000000..339a674296 --- /dev/null +++ b/continuous_integration/scripts/tools/legate-gh-run-id @@ -0,0 +1,30 @@ +#!/usr/bin/env bash + +# SPDX-FileCopyrightText: Copyright (c) 2025-2025 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# SPDX-License-Identifier: Apache-2.0 + +# A utility script adapted from https://github.com/rapidsai/gha-tools/blob/main/tools/rapids-github-run-id +# This gets the GitHub run ID for the specified workflow and commit SHA. + +set -euo pipefail + +# Default values for the environment variables. +LEGATE_WORKFLOW_NAME=${LEGATE_WORKFLOW_NAME:-"pr"} +LEGATE_REF_NAME=${LEGATE_REF_NAME:-"main"} +LEGATE_REPO_NAME=${LEGATE_REPO_NAME:-"nv-legate/legate.internal"} + +# Check if the script was called with exactly 1 argument +if [[ ${#} -ne 1 ]]; then + echo "Error: This script requires exactly 1 argument (the git SHA). You provided ${#}" + echo "Usage: ${0} git-sha" + exit 1 +fi + +gh_run_id=$(gh run list \ + --repo "${LEGATE_REPO_NAME}" \ + --branch "${LEGATE_REF_NAME}" \ + --workflow "${LEGATE_WORKFLOW_NAME}" \ + --commit "${1}" \ + --json databaseId --jq '.[0] | .databaseId') + +echo -n "${gh_run_id}" From 1f53931f4101a66d07d12935aff0c692940a109a Mon Sep 17 00:00:00 2001 From: "Marcus D. Hanwell" Date: Mon, 24 Mar 2025 16:22:18 -0400 Subject: [PATCH 451/462] Update licenses that were missed - wheels (#664) --- .../scripts/build_wheel_linux.bash | 2 +- .../scripts/test_wheel_linux.bash | 2 +- .../build/python/cupynumeric/CMakeLists.txt | 20 +++++++++++-------- .../build/python/cupynumeric/pyproject.toml | 4 ++-- 4 files changed, 16 insertions(+), 12 deletions(-) diff --git a/continuous_integration/scripts/build_wheel_linux.bash b/continuous_integration/scripts/build_wheel_linux.bash index 24a7ce5b0d..a4de07fe3b 100755 --- a/continuous_integration/scripts/build_wheel_linux.bash +++ b/continuous_integration/scripts/build_wheel_linux.bash @@ -1,7 +1,7 @@ #!/usr/bin/env bash # SPDX-FileCopyrightText: Copyright (c) 2025-2025 NVIDIA CORPORATION & AFFILIATES. All rights reserved. -# SPDX-License-Identifier: LicenseRef-NvidiaProprietary +# SPDX-License-Identifier: Apache-2.0 # # NVIDIA CORPORATION, its affiliates and licensors retain all intellectual # property and proprietary rights in and to this material, related diff --git a/continuous_integration/scripts/test_wheel_linux.bash b/continuous_integration/scripts/test_wheel_linux.bash index 0a1ddda58f..4414d96003 100755 --- a/continuous_integration/scripts/test_wheel_linux.bash +++ b/continuous_integration/scripts/test_wheel_linux.bash @@ -1,7 +1,7 @@ #!/usr/bin/env bash # SPDX-FileCopyrightText: Copyright (c) 2025-2025 NVIDIA CORPORATION & AFFILIATES. All rights reserved. -# SPDX-License-Identifier: LicenseRef-NvidiaProprietary +# SPDX-License-Identifier: Apache-2.0 # # NVIDIA CORPORATION, its affiliates and licensors retain all intellectual # property and proprietary rights in and to this material, related diff --git a/scripts/build/python/cupynumeric/CMakeLists.txt b/scripts/build/python/cupynumeric/CMakeLists.txt index ceb4fdc79e..f0fa381c3a 100644 --- a/scripts/build/python/cupynumeric/CMakeLists.txt +++ b/scripts/build/python/cupynumeric/CMakeLists.txt @@ -1,13 +1,17 @@ #============================================================================= -# SPDX-FileCopyrightText: Copyright (c) 2022-2025 NVIDIA CORPORATION & AFFILIATES. All rights reserved. -# SPDX-License-Identifier: LicenseRef-NvidiaProprietary +# Copyright 2024 NVIDIA Corporation # -# NVIDIA CORPORATION, its affiliates and licensors retain all intellectual -# property and proprietary rights in and to this material, related -# documentation and any modifications thereto. Any use, reproduction, -# disclosure or distribution of this material and related documentation -# without an express license agreement from NVIDIA CORPORATION or -# its affiliates is strictly prohibited. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. #============================================================================= cmake_minimum_required(VERSION 3.26.4) diff --git a/scripts/build/python/cupynumeric/pyproject.toml b/scripts/build/python/cupynumeric/pyproject.toml index 423fbd251b..653789bc0f 100644 --- a/scripts/build/python/cupynumeric/pyproject.toml +++ b/scripts/build/python/cupynumeric/pyproject.toml @@ -20,13 +20,13 @@ python-requires = ">=3.10" [project] name = "nvidia-cupynumeric" authors = [{name = "NVIDIA Corporation"}] -license = {text = "Proprietary"} +license = {text = "Apache-2.0"} description = "cupynumeric - drop in replacement for numpy" classifiers = [ "Intended Audience :: Developers", "Topic :: Database", "Topic :: Scientific/Engineering", - "License :: Proprietary :: Nvidia Proprietary", + "License :: OSI Approved :: Apache Software License", "Programming Language :: Python", "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", From cc093019b931ef29d08b69a48a1003e190dd8900 Mon Sep 17 00:00:00 2001 From: "Marcus D. Hanwell" Date: Tue, 25 Mar 2025 10:52:14 -0400 Subject: [PATCH 452/462] Use a clean version before the build starts (#667) The version file is ironically causing the version string to be incorrect. Issue #666 documents what should be done to fix this correctly. In the short term I need a clean release version for the upcoming initial wheels release hence overriding it. Longer term we should override in CI anyway on the release branch to switch to a post release versioning scheme in release branches. --- continuous_integration/scripts/build_wheel_linux.bash | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/continuous_integration/scripts/build_wheel_linux.bash b/continuous_integration/scripts/build_wheel_linux.bash index a4de07fe3b..95d7303b67 100755 --- a/continuous_integration/scripts/build_wheel_linux.bash +++ b/continuous_integration/scripts/build_wheel_linux.bash @@ -61,6 +61,12 @@ sitepkgs=$(python -c 'import site; print(site.getsitepackages()[0], end="")') ln -fs "${sitepkgs}"/cutensor/lib/libcutensor.so.2 "${sitepkgs}"/cutensor/lib/libcutensor.so ln -fs "${sitepkgs}"/cutensor/lib/libcutensorMg.so.2 "${sitepkgs}"/cutensor/lib/libcutensorMg.so +# TODO(cryos): https://github.com/nv-legate/cupynumeric.internal/issues/666 +# This is a very hackish way to generate the version for now. +scm_version=$(python -m setuptools_scm -c "${CUPYNUMERIC_DIR}"/scripts/build/python/cupynumeric/pyproject.toml) +export SETUPTOOLS_SCM_PRETEND_VERSION="${scm_version}" +echo "Building wheels with version '${scm_version}'" + # build with '--no-build-isolation', for better sccache hit rate # 0 really means "add --no-build-isolation" (ref: https://github.com/pypa/pip/issues/5735) export PIP_NO_BUILD_ISOLATION=0 From a55045d64032825cb540dd8b4dedb9d46209b767 Mon Sep 17 00:00:00 2001 From: Sandeep Datta <128171450+sandeepd-nv@users.noreply.github.com> Date: Wed, 26 Mar 2025 16:36:25 +0530 Subject: [PATCH 453/462] Nightly push to external repo (#659) Push source code to nv-legate/cupynumeric as part of the nightly CI run. Tracking issue: LLRDO-335. --- .github/workflows/ci-gh-nightly-release.yml | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/.github/workflows/ci-gh-nightly-release.yml b/.github/workflows/ci-gh-nightly-release.yml index 2ed4693ed3..46b887687c 100644 --- a/.github/workflows/ci-gh-nightly-release.yml +++ b/.github/workflows/ci-gh-nightly-release.yml @@ -9,7 +9,6 @@ on: schedule: - cron: '0 23 * * *' # Nightly at 11:00 PM - jobs: build-and-test: strategy: @@ -60,3 +59,13 @@ jobs: build-type: nightly upload-docs-to-gh-pages: true secrets: inherit + + push_code: + name: Nightly source release + uses: + nv-legate/legate-gh-ci/.github/workflows/gh-push-code.yml@nightly_push_to_external_repo + with: + runs-on: linux-amd64-cpu4 + source-repo: "${{ github.repository_owner }}/cupynumeric.internal" + dest-repo: "${{ github.repository_owner }}/cupynumeric" + secrets: inherit From 73f64bb83130a93b51271e39d3053917d1097b99 Mon Sep 17 00:00:00 2001 From: "Marcus D. Hanwell" Date: Tue, 1 Apr 2025 11:27:41 -0400 Subject: [PATCH 454/462] Get the Legate SHA from `versions.cmake` (#669) Get the Legate SHA from the `cmake/versions.cmake` file for the wheels, this is the same place that the conda-based CI/build gets the SHA from creating a single source of truth. Also bump the legate wheel dependency to `25.5.*` for `main`. --- .github/workflows/pr.yml | 27 +++++++++++++------ .../build/python/cupynumeric/pyproject.toml | 2 +- 2 files changed, 20 insertions(+), 9 deletions(-) diff --git a/.github/workflows/pr.yml b/.github/workflows/pr.yml index 22ef63c648..3fd2c7f62e 100644 --- a/.github/workflows/pr.yml +++ b/.github/workflows/pr.yml @@ -13,23 +13,34 @@ concurrency: defaults: run: - shell: bash + shell: bash -eou pipefail {0} jobs: + legate-sha: + runs-on: linux-amd64-cpu4 + outputs: + LEGATE_SHA: ${{ steps.legate-sha.outputs.sha }} + steps: + - name: Checkout repository + uses: actions/checkout@v4 + with: + fetch-depth: 0 + - name: Get the Legate SHA + id: legate-sha + run: | + sha=$(jq .packages.legate.git_tag cmake/versions.json) + echo "sha=$sha" >> $GITHUB_OUTPUT wheels-build: + needs: legate-sha secrets: inherit uses: ./.github/workflows/wheels-build.yml with: build-type: pull-request - # TODO(cryos): https://github.com/nv-legate/legate.internal/issues/1893 - # Remove this once we have uploads to PyPi. - legate-sha: 37d52d7c7d0a6fa8c27224115334c97daf6f7cb7 + legate-sha: ${{ needs.legate-sha.outputs.LEGATE_SHA }} wheels-test: - needs: wheels-build + needs: [wheels-build, legate-sha] secrets: inherit uses: ./.github/workflows/wheels-test.yml with: build-type: pull-request - # TODO(cryos): https://github.com/nv-legate/legate.internal/issues/1893 - # Remove this once we have uploads to PyPi. - legate-sha: 37d52d7c7d0a6fa8c27224115334c97daf6f7cb7 + legate-sha: ${{ needs.legate-sha.outputs.LEGATE_SHA }} diff --git a/scripts/build/python/cupynumeric/pyproject.toml b/scripts/build/python/cupynumeric/pyproject.toml index 653789bc0f..ae17766497 100644 --- a/scripts/build/python/cupynumeric/pyproject.toml +++ b/scripts/build/python/cupynumeric/pyproject.toml @@ -36,7 +36,7 @@ dependencies = [ "numpy!=2.1.0", "cffi", "opt_einsum", - "legate==25.3.*,>=0.0.0a0", + "legate==25.5.*,>=0.0.0a0", "cutensor-cu12", "nvidia-cublas-cu12", "nvidia-cufft-cu12", From 7fc9dc99dad70130d61681bcdb408cbdd27ed1a6 Mon Sep 17 00:00:00 2001 From: "Marcus D. Hanwell" Date: Tue, 1 Apr 2025 11:28:31 -0400 Subject: [PATCH 455/462] Move the conda build string version info to end (#671) The git SHA and build number should be at the end of the build string, the build number is especially important in assisting the solver in resolving the latest version that should be installed. --- conda/conda-build/meta.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/conda/conda-build/meta.yaml b/conda/conda-build/meta.yaml index a9ca0c3117..2ab9b6b2e9 100644 --- a/conda/conda-build/meta.yaml +++ b/conda/conda-build/meta.yaml @@ -78,10 +78,10 @@ build: {% set upload_tag='' if upload_build_bool else '_with_tests' %} {% if use_local_path is not defined %} # use git hash - string: "cuda{{ cuda_major }}_py{{ py_version }}_{{ GIT_DESCRIBE_HASH }}_{{ PKG_BUILDNUM }}{{ cpu_gpu_tag }}{{ upload_tag }}" + string: "cuda{{ cuda_major }}_py{{ py_version }}{{ cpu_gpu_tag }}{{ upload_tag }}_{{ GIT_DESCRIBE_HASH }}_{{ PKG_BUILDNUM }}" {% else %} # do not use git hash - string: "cuda{{ cuda_major }}_py{{ py_version }}_{{ PKG_BUILDNUM }}{{ cpu_gpu_tag }}{{ upload_tag }}" + string: "cuda{{ cuda_major }}_py{{ py_version }}{{ cpu_gpu_tag }}{{ upload_tag }}_{{ PKG_BUILDNUM }}" {% endif %} script_env: - SCCACHE_BUCKET From 45492a8cca92c5a69cdeedf9bffe4bf87c677b33 Mon Sep 17 00:00:00 2001 From: Marcin Zalewski Date: Thu, 3 Apr 2025 00:08:00 -0700 Subject: [PATCH 456/462] Update runners to NVKS (#588) --- .github/workflows/gh-build-and-test.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/gh-build-and-test.yml b/.github/workflows/gh-build-and-test.yml index 50ce55778c..06bd77b2ff 100644 --- a/.github/workflows/gh-build-and-test.yml +++ b/.github/workflows/gh-build-and-test.yml @@ -107,7 +107,7 @@ jobs: MATRIX_JSON='{"include": [' RUNNERS=( - 'linux-amd64-gpu-v100-latest-1:gpu:gpu:linux' 'linux-amd64-2gpu:gpu:2gpu:linux' + 'linux-amd64-gpu-l4-latest-1:gpu:gpu:linux' 'linux-amd64-2gpu:gpu:2gpu:linux' 'linux-amd64-cpu16:cpu:cpu:linux' 'linux-arm64-cpu16:cpu:cpu:linux-aarch64' 'linux-aarch64-2gpu:gpu:2gpu:linux-aarch64' 'linux-aarch64-2gpu:gpu:gpu:linux-aarch64' 'macos-latest:cpu:cpu:mac') @@ -121,7 +121,7 @@ jobs: # gpus when only one is really available this workaround can be # removed when the number of available gpus is reported correctly # (when we run on VMs) - 'GPU test:test --use cuda --gpus 1 -j 7 --debug:gpu' + 'GPU test:test --use cuda --gpus 1 --debug:gpu' '2 GPU test:test --use cuda --gpus 2 --debug:2gpu' 'OpenMP test:test --use openmp --omps 1 --ompthreads 2 --debug:gpu' 'OpenMP test:test --use openmp --omps 1 --ompthreads 2 --debug:cpu' From 70e91d57b4a7948be4b1c1a57603904ffa3dadd4 Mon Sep 17 00:00:00 2001 From: "Marcus D. Hanwell" Date: Thu, 3 Apr 2025 09:20:20 -0400 Subject: [PATCH 457/462] Update CODEOWNERS to use the GitHub team (#675) --- .github/CODEOWNERS | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index df699eb401..b310312985 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -1,4 +1,4 @@ # Code Ownership -.github @marcinz @m3vaz @sandeepd-nv @mag1cp1n @cryos -continuous_integration @marcinz @m3vaz @sandeepd-nv @mag1cp1n @cryos -conda @marcinz @m3vaz @sandeepd-nv @mag1cp1n @cryos +.github @nv-legate/devops-reviewers +continuous_integration @nv-legate/devops-reviewers +conda @nv-legate/devops-reviewers From c95afb3989a099869537b53aafd9d315e9d83398 Mon Sep 17 00:00:00 2001 From: "Marcus D. Hanwell" Date: Thu, 3 Apr 2025 11:14:01 -0400 Subject: [PATCH 458/462] Enable sccache in for CI builds (#674) Enable `sccache` for the wheels and conda builds of the project. This yielded ball park improvements of: - 16-19 minutes down to 3-5 minutes for wheel builds - 28 minutes down to 7 minutes for conda GPU builds - 10-11 minutes down to 4 minutes for conda CPU builds No observed regressions in the tests. --- conda/conda-build/meta.yaml | 6 ++++++ continuous_integration/scripts/build | 7 +++++++ .../scripts/build_wheel_linux.bash | 7 ++++++- .../scripts/tools/legate-configure-sccache | 20 +++++++++++++++++++ 4 files changed, 39 insertions(+), 1 deletion(-) create mode 100755 continuous_integration/scripts/tools/legate-configure-sccache diff --git a/conda/conda-build/meta.yaml b/conda/conda-build/meta.yaml index 2ab9b6b2e9..d29e2a1279 100644 --- a/conda/conda-build/meta.yaml +++ b/conda/conda-build/meta.yaml @@ -89,8 +89,14 @@ build: - SCCACHE_IDLE_TIMEOUT - SCCACHE_S3_KEY_PREFIX - SCCACHE_S3_KEY_PREFIX + - SCCACHE_S3_USE_SSL + - SCCACHE_S3_NO_CREDENTIALS - AWS_ACCESS_KEY_ID - AWS_SECRET_ACCESS_KEY + - AWS_SESSION_TOKEN + - CMAKE_C_COMPILER_LAUNCHER + - CMAKE_CUDA_COMPILER_LAUNCHER + - CMAKE_CXX_COMPILER_LAUNCHER {% if build_tests_bool %} - BUILD_TESTS=1 {% endif %} diff --git a/continuous_integration/scripts/build b/continuous_integration/scripts/build index 5c8d92ad9b..8287fc517a 100755 --- a/continuous_integration/scripts/build +++ b/continuous_integration/scripts/build @@ -39,6 +39,11 @@ build_release_product() { # Use the new .conda format. conda config --set conda_build.pkg_format 2 + # Set up the SCCACHE environment variables + export CI=true + source "${REPO_DIR}/continuous_integration/scripts/tools/legate-configure-sccache" + sccache --zero-stats + set +u; # For whatever reason, the default buffering of conda/mamba is not sufficient, and @@ -46,6 +51,8 @@ build_release_product() { # we need to force unbuffered output. stdbuf -o0 -e0 conda mambabuild "${conda_build_args[@]}" "${REPO_DIR}/conda/conda-build"; + sccache --show-adv-stats + copy_release_artifacts } diff --git a/continuous_integration/scripts/build_wheel_linux.bash b/continuous_integration/scripts/build_wheel_linux.bash index 95d7303b67..fdd14e668e 100755 --- a/continuous_integration/scripts/build_wheel_linux.bash +++ b/continuous_integration/scripts/build_wheel_linux.bash @@ -20,7 +20,8 @@ ls -lah ls -lh wheel -export PARALLEL_LEVEL=${PARALLEL_LEVEL:-$(nproc --all --ignore=2)} +# Configure and enable sccache for the build. +source legate-configure-sccache export CMAKE_BUILD_PARALLEL_LEVEL=${PARALLEL_LEVEL} if [[ "${CI:-false}" == "true" ]]; then @@ -102,6 +103,8 @@ ls -lah echo "Building wheel..." cd "${package_dir}" +sccache --zero-stats + python -m pip wheel \ -w "${CUPYNUMERIC_DIR}"/dist \ -v \ @@ -109,6 +112,8 @@ python -m pip wheel \ --disable-pip-version-check \ . +sccache --show-adv-stats + echo "Show dist contents" pwd ls -lh "${CUPYNUMERIC_DIR}"/dist diff --git a/continuous_integration/scripts/tools/legate-configure-sccache b/continuous_integration/scripts/tools/legate-configure-sccache new file mode 100755 index 0000000000..bd7a5e0be5 --- /dev/null +++ b/continuous_integration/scripts/tools/legate-configure-sccache @@ -0,0 +1,20 @@ +#!/usr/bin/env bash +# A utility script that configures sccache environment variables + +export CMAKE_CUDA_COMPILER_LAUNCHER=sccache +export CMAKE_CXX_COMPILER_LAUNCHER=sccache +export CMAKE_C_COMPILER_LAUNCHER=sccache +export RUSTC_WRAPPER=sccache +export PARALLEL_LEVEL=${PARALLEL_LEVEL:-$(nproc --all --ignore=2)} +export SCCACHE_BUCKET=rapids-sccache-east +export SCCACHE_IDLE_TIMEOUT=32768 +export SCCACHE_REGION=us-east-2 +export SCCACHE_S3_KEY_PREFIX=legate-cunumeric-dev +export SCCACHE_S3_NO_CREDENTIALS=false +export SCCACHE_S3_USE_SSL=true + +if [[ "${CI:-false}" == "false" ]]; then + # Configure sccache for read-only mode since no credentials + # are available in local builds. + export SCCACHE_S3_NO_CREDENTIALS=true +fi From 0a1f3dc9a4379133ea19400c1501f04f65b95ef3 Mon Sep 17 00:00:00 2001 From: Marcin Zalewski Date: Thu, 3 Apr 2025 11:00:10 -0700 Subject: [PATCH 459/462] Wheels docs (#670) --- docs/cupynumeric/source/installation.rst | 79 +++++++++++++++++------- 1 file changed, 56 insertions(+), 23 deletions(-) diff --git a/docs/cupynumeric/source/installation.rst b/docs/cupynumeric/source/installation.rst index d5e97c844d..1f1e88dafc 100644 --- a/docs/cupynumeric/source/installation.rst +++ b/docs/cupynumeric/source/installation.rst @@ -1,8 +1,8 @@ Installation ============ -Default conda install ---------------------- +Installing Conda Packages +------------------------- cuPyNumeric supports the `same platforms as Legate `_. @@ -10,41 +10,74 @@ cuPyNumeric supports the cuPyNumeric is available from `conda `_ on the `legate channel `_. -Please make sure you have at least conda version 24.1 installed, then create -a new environment containing cuPyNumeric: -.. code-block:: sh +.. note:: + conda version >= 24.1 required - conda create -n myenv -c conda-forge -c legate cupynumeric +.. code-block:: bash -or install it into an existing environment: + # with a new environment + $ conda create -n myenv -c conda-forge -c legate cupynumeric -.. code-block:: sh + # =========== OR =========== # + + # into an existing environment + $ conda install -c conda-forge -c legate cupynumeric + +Installing PyPI Packages +------------------------ + +cuPyNumeric is also available from `PyPI +`_. To install, run the following +command: + +.. code-block:: bash + + # into existing environment + $ pip install nvidia-cupynumeric - conda install -c conda-forge -c legate cupynumeric + # =========== OR =========== # -Packages with GPU support are available, and will be chosen automatically by -``conda install`` on systems with GPUs. + # into new environment + $ python -m venv myenv + $ source myenv/bin/activate + $ pip install nvidia-cupynumeric -In an environment without GPUs available, ``conda install`` will by default -choose a CPU-only package. To install a version with GPU support in such an -environment, use environment variable ``CONDA_OVERRIDE_CUDA``: +This will install the latest version of cuPyNumeric and the corresponding +version of `Legate `_. + +The cuPyNumeric package on PyPI is multi-node and multi-rank capable. Please +check `Legate `_ documentation to find more +details about running on multiple nodes. + +Verify your Installation +------------------------ + +You can verify the installation by running one of the +`examples `_. + +For instance: .. code-block:: sh - CONDA_OVERRIDE_CUDA="12.2" \ - conda install -c conda-forge -c legate cupynumeric + $ legate examples/black_scholes.py + Running black scholes on 10K options... + Elapsed Time: 129.017 ms -Once installed, you can verify the installation by running one of the examples -from the -`cuPyNumeric repository `_, -for instance: +Conda and GPU / CPU Variants +---------------------------- + +``conda`` automatically installs the right variant for the system: +* CPU variant if no NVIDIA GPU is detected +* GPU variant if an NVIDIA GPU is detected + +To override this behavior and force install a version with GPU support, use the +following (with the desired CUDA version): .. code-block:: sh - $ legate examples/black_scholes.py - Running black scholes on 10K options... - Elapsed Time: 129.017 ms + $ CONDA_OVERRIDE_CUDA="12.2" conda install -c conda-forge -c legate cupynumeric + Building from source --------------------- From 37150449710d0d22f67c7923bf1025b787a99f2f Mon Sep 17 00:00:00 2001 From: Bryan Van de Ven Date: Fri, 4 Apr 2025 12:41:20 -0400 Subject: [PATCH 460/462] add special case to handle matmul ufunc (#679) * add special case to handle matmul ufunc * Update cupynumeric/_array/array.py Co-authored-by: Manolis Papadakis --------- Co-authored-by: Manolis Papadakis --- cupynumeric/_array/array.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/cupynumeric/_array/array.py b/cupynumeric/_array/array.py index b1dfc433fc..48e7e33bd4 100644 --- a/cupynumeric/_array/array.py +++ b/cupynumeric/_array/array.py @@ -266,6 +266,11 @@ def __array_ufunc__( except NotImplementedError: what = f"the requested combination of arguments to {what}" + # special case for @ matmul + if what == "matmul.__call__": + from .._module import matmul + return matmul(*inputs, **kwargs) + # We cannot handle this ufunc call, so we will fall back to NumPy. warnings.warn( FALLBACK_WARNING.format(what=what), From a26f8fce0b87a968c4af9da639ae0fad798dcba2 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 7 Apr 2025 20:03:46 +0000 Subject: [PATCH 461/462] [pre-commit.ci] pre-commit autoupdate MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/pre-commit/mirrors-mypy: v1.5.1 → v1.15.0](https://github.com/pre-commit/mirrors-mypy/compare/v1.5.1...v1.15.0) - [github.com/PyCQA/isort: 5.12.0 → 6.0.1](https://github.com/PyCQA/isort/compare/5.12.0...6.0.1) - [github.com/psf/black: 23.9.1 → 25.1.0](https://github.com/psf/black/compare/23.9.1...25.1.0) - [github.com/PyCQA/flake8: 6.1.0 → 7.2.0](https://github.com/PyCQA/flake8/compare/6.1.0...7.2.0) - [github.com/pre-commit/mirrors-clang-format: v16.0.6 → v20.1.0](https://github.com/pre-commit/mirrors-clang-format/compare/v16.0.6...v20.1.0) --- .pre-commit-config.yaml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index ab351fc46f..db16bb5d34 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,26 +1,26 @@ repos: - repo: https://github.com/pre-commit/mirrors-mypy - rev: 'v1.5.1' + rev: 'v1.15.0' hooks: - id: mypy language: system pass_filenames: false args: ['cupynumeric'] - repo: https://github.com/PyCQA/isort - rev: 5.12.0 + rev: 6.0.1 hooks: - id: isort - repo: https://github.com/psf/black - rev: 23.9.1 + rev: 25.1.0 hooks: - id: black args: ["--target-version", "py310"] - repo: https://github.com/PyCQA/flake8 - rev: 6.1.0 + rev: 7.2.0 hooks: - id: flake8 - repo: https://github.com/pre-commit/mirrors-clang-format - rev: 'v16.0.6' # Use the sha / tag you want to point at + rev: 'v20.1.0' # Use the sha / tag you want to point at hooks: - id: clang-format files: \.(cu|cuh|h|cc|inl)$ From 2f2a4d3cb6b6d4a471b5dec323f4a774cbb0dd79 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 7 Apr 2025 20:04:19 +0000 Subject: [PATCH 462/462] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- cupynumeric/__init__.py | 1 + cupynumeric/_array/array.py | 5 +- cupynumeric/_module/ssc_searching.py | 8 +- cupynumeric/_sphinxext/_comparison_util.py | 2 +- cupynumeric/_thunk/deferred.py | 10 +- cupynumeric/_thunk/eager.py | 40 +-- cupynumeric/_thunk/thunk.py | 278 ++++++------------ cupynumeric/_ufunc/ufunc.py | 8 +- cupynumeric/_utils/coverage.py | 3 +- cupynumeric/config.py | 14 +- cupynumeric/patch.py | 2 +- cupynumeric/random/_bitgenerator.py | 3 +- examples/benchmark.py | 3 +- .../reduction/scalar_reduction.cuh | 6 +- src/cupynumeric/ndarray.cc | 6 +- src/cupynumeric/random/philox.h | 2 +- src/cupynumeric/random/rand_util.h | 2 +- src/cupynumeric/stat/bincount_template.inl | 8 +- src/cupynumeric/unary/unary_red_template.inl | 2 +- tests/cpp/integration/test_where.cc | 10 +- tests/integration/test_expm_sh.py | 92 +++--- tests/integration/test_expm_sh_cpx_qr.py | 81 ++--- tests/integration/test_scan.py | 6 +- tests/todo/lstm_batch.py | 4 +- 24 files changed, 258 insertions(+), 338 deletions(-) diff --git a/cupynumeric/__init__.py b/cupynumeric/__init__.py index 54bfcf3dcd..082217fb12 100644 --- a/cupynumeric/__init__.py +++ b/cupynumeric/__init__.py @@ -48,6 +48,7 @@ def _fixup_version() -> str: return v from . import _version + if hasattr(_version, "get_versions"): return _version.get_versions()["version"] # type: ignore [no-untyped-call] if hasattr(_version, "__version__"): diff --git a/cupynumeric/_array/array.py b/cupynumeric/_array/array.py index 48e7e33bd4..a985f3cbe4 100644 --- a/cupynumeric/_array/array.py +++ b/cupynumeric/_array/array.py @@ -269,6 +269,7 @@ def __array_ufunc__( # special case for @ matmul if what == "matmul.__call__": from .._module import matmul + return matmul(*inputs, **kwargs) # We cannot handle this ufunc call, so we will fall back to NumPy. @@ -2291,9 +2292,7 @@ def _diag_helper( res_dtype = ( dtype if dtype is not None - else out.dtype - if out is not None - else a.dtype + else out.dtype if out is not None else a.dtype ) a = a._maybe_convert(res_dtype, (a,)) if out is not None and out.shape != out_shape: diff --git a/cupynumeric/_module/ssc_searching.py b/cupynumeric/_module/ssc_searching.py index c64261eb2b..bcdba11cee 100644 --- a/cupynumeric/_module/ssc_searching.py +++ b/cupynumeric/_module/ssc_searching.py @@ -197,8 +197,9 @@ def flatnonzero(a: ndarray) -> ndarray: @overload -def where(a: npt.ArrayLike | ndarray, x: None, y: None) -> tuple[ndarray, ...]: - ... +def where( + a: npt.ArrayLike | ndarray, x: None, y: None +) -> tuple[ndarray, ...]: ... @overload @@ -206,8 +207,7 @@ def where( a: npt.ArrayLike | ndarray, x: npt.ArrayLike | ndarray, y: npt.ArrayLike | ndarray, -) -> ndarray: - ... +) -> ndarray: ... @add_boilerplate("a", "x", "y") # type: ignore [misc] diff --git a/cupynumeric/_sphinxext/_comparison_util.py b/cupynumeric/_sphinxext/_comparison_util.py index 5992fac72b..a7168cee47 100644 --- a/cupynumeric/_sphinxext/_comparison_util.py +++ b/cupynumeric/_sphinxext/_comparison_util.py @@ -25,7 +25,7 @@ from ._comparison_config import SectionConfig YES = "\u2713" -NO = "\u274C" +NO = "\u274c" @dataclass(frozen=True) diff --git a/cupynumeric/_thunk/deferred.py b/cupynumeric/_thunk/deferred.py index f8c6521271..58349cd57d 100644 --- a/cupynumeric/_thunk/deferred.py +++ b/cupynumeric/_thunk/deferred.py @@ -143,9 +143,11 @@ def decorator(func: Callable[P, R]) -> Callable[P, R]: def wrapper(*args: Any, **kwargs: Any) -> R: # Convert relevant arguments to DeferredArrays args = tuple( - runtime.to_deferred_array(arg, read_only=True) - if idx in indices and arg is not None - else arg + ( + runtime.to_deferred_array(arg, read_only=True) + if idx in indices and arg is not None + else arg + ) for (idx, arg) in enumerate(args) ) for k, v in kwargs.items(): @@ -1590,7 +1592,7 @@ def rounding_divide( # TODO: better heuristics def choose_2d_color_shape( - shape: tuple[int, int] + shape: tuple[int, int], ) -> tuple[int, int]: # 1M elements, we should probably even go larger MIN_MATRIX_SIZE = 1 << 20 diff --git a/cupynumeric/_thunk/eager.py b/cupynumeric/_thunk/eager.py index ae2551f99f..4eb86df694 100644 --- a/cupynumeric/_thunk/eager.py +++ b/cupynumeric/_thunk/eager.py @@ -1467,17 +1467,21 @@ def unary_op( func( rhs.array, out=self.array, - where=where - if not isinstance(where, EagerArray) - else where.array, + where=( + where + if not isinstance(where, EagerArray) + else where.array + ), ) else: func( rhs.array, out=(self.array, *(out.array for out in multiout)), - where=where - if not isinstance(where, EagerArray) - else where.array, + where=( + where + if not isinstance(where, EagerArray) + else where.array + ), ) elif op == UnaryOpCode.CLIP: np.clip( @@ -1549,9 +1553,9 @@ def unary_reduction( out=self.array, axis=orig_axis, keepdims=keepdims, - where=where - if not isinstance(where, EagerArray) - else where.array, + where=( + where if not isinstance(where, EagerArray) else where.array + ), **kws, ) elif op == UnaryRedCode.SUM_SQUARES: @@ -1560,9 +1564,9 @@ def unary_reduction( squared, out=self.array, axis=orig_axis, - where=where - if not isinstance(where, EagerArray) - else where.array, + where=( + where if not isinstance(where, EagerArray) else where.array + ), keepdims=keepdims, ) elif op == UnaryRedCode.VARIANCE: @@ -1572,9 +1576,9 @@ def unary_reduction( np.sum( squares, axis=orig_axis, - where=where - if not isinstance(where, EagerArray) - else where.array, + where=( + where if not isinstance(where, EagerArray) else where.array + ), keepdims=keepdims, out=self.array, ) @@ -1619,9 +1623,9 @@ def binary_op( rhs1.array, rhs2.array, out=self.array, - where=where - if not isinstance(where, EagerArray) - else where.array, + where=( + where if not isinstance(where, EagerArray) else where.array + ), ) def binary_reduction( diff --git a/cupynumeric/_thunk/thunk.py b/cupynumeric/_thunk/thunk.py index 132168dfab..06619d7dc1 100644 --- a/cupynumeric/_thunk/thunk.py +++ b/cupynumeric/_thunk/thunk.py @@ -74,24 +74,19 @@ def size(self) -> int: # Abstract methods @abstractproperty - def shape(self) -> NdShape: - ... + def shape(self) -> NdShape: ... @abstractmethod - def __numpy_array__(self) -> npt.NDArray[Any]: - ... + def __numpy_array__(self) -> npt.NDArray[Any]: ... @abstractmethod - def imag(self) -> NumPyThunk: - ... + def imag(self) -> NumPyThunk: ... @abstractmethod - def real(self) -> NumPyThunk: - ... + def real(self) -> NumPyThunk: ... @abstractmethod - def conj(self) -> NumPyThunk: - ... + def conj(self) -> NumPyThunk: ... @abstractmethod def convolve( @@ -100,8 +95,7 @@ def convolve( filter: Any, mode: ConvolveMode, method: ConvolveMethod, - ) -> None: - ... + ) -> None: ... @abstractmethod def fft( @@ -110,43 +104,34 @@ def fft( axes: Sequence[int], kind: FFTType, direction: FFTDirection, - ) -> None: - ... + ) -> None: ... @abstractmethod - def copy(self, rhs: Any, deep: bool) -> None: - ... + def copy(self, rhs: Any, deep: bool) -> None: ... @abstractmethod def repeat( self, repeats: Any, axis: int, scalar_repeats: bool - ) -> NumPyThunk: - ... + ) -> NumPyThunk: ... @property @abstractmethod - def scalar(self) -> bool: - ... + def scalar(self) -> bool: ... @abstractmethod - def get_item(self, key: Any) -> NumPyThunk: - ... + def get_item(self, key: Any) -> NumPyThunk: ... @abstractmethod - def set_item(self, key: Any, value: Any) -> None: - ... + def set_item(self, key: Any, value: Any) -> None: ... @abstractmethod - def reshape(self, newshape: NdShape, order: OrderType) -> NumPyThunk: - ... + def reshape(self, newshape: NdShape, order: OrderType) -> NumPyThunk: ... @abstractmethod - def squeeze(self, axis: int | tuple[int, ...] | None) -> NumPyThunk: - ... + def squeeze(self, axis: int | tuple[int, ...] | None) -> NumPyThunk: ... @abstractmethod - def swapaxes(self, axis1: int, axis2: int) -> NumPyThunk: - ... + def swapaxes(self, axis1: int, axis2: int) -> NumPyThunk: ... @abstractmethod def convert( @@ -155,20 +140,16 @@ def convert( warn: bool = True, nan_op: ConvertCode = ConvertCode.NOOP, temporary: bool = False, - ) -> None: - ... + ) -> None: ... @abstractmethod - def fill(self, value: Any) -> None: - ... + def fill(self, value: Any) -> None: ... @abstractmethod - def transpose(self, axes: tuple[int, ...] | list[int]) -> NumPyThunk: - ... + def transpose(self, axes: tuple[int, ...] | list[int]) -> NumPyThunk: ... @abstractmethod - def flip(self, rhs: Any, axes: int | tuple[int, ...] | None) -> None: - ... + def flip(self, rhs: Any, axes: int | tuple[int, ...] | None) -> None: ... @abstractmethod def contract( @@ -179,12 +160,10 @@ def contract( rhs2_thunk: Any, rhs2_modes: list[str], mode2extent: dict[str, int], - ) -> None: - ... + ) -> None: ... @abstractmethod - def choose(self, rhs: Any, *args: Any) -> None: - ... + def choose(self, rhs: Any, *args: Any) -> None: ... @abstractmethod def select( @@ -192,46 +171,38 @@ def select( condlist: Iterable[Any], choicelist: Iterable[Any], default: npt.NDArray[Any], - ) -> None: - ... + ) -> None: ... @abstractmethod def _diag_helper( self, rhs: Any, offset: int, naxes: int, extract: bool, trace: bool - ) -> None: - ... + ) -> None: ... @abstractmethod - def put(self, indices: Any, values: Any, check_bounds: bool) -> None: - ... + def put(self, indices: Any, values: Any, check_bounds: bool) -> None: ... @abstractmethod - def putmask(self, mask: Any, values: Any) -> None: - ... + def putmask(self, mask: Any, values: Any) -> None: ... @abstractmethod - def eye(self, k: int) -> None: - ... + def eye(self, k: int) -> None: ... @abstractmethod - def arange(self, start: float, stop: float, step: float) -> None: - ... + def arange(self, start: float, stop: float, step: float) -> None: ... @abstractmethod - def tile(self, rhs: Any, reps: Any | Sequence[int]) -> None: - ... + def tile(self, rhs: Any, reps: Any | Sequence[int]) -> None: ... @abstractmethod - def trilu(self, rhs: Any, k: int, lower: bool) -> None: - ... + def trilu(self, rhs: Any, k: int, lower: bool) -> None: ... @abstractmethod - def bincount(self, rhs: Any, weights: NumPyThunk | None = None) -> None: - ... + def bincount( + self, rhs: Any, weights: NumPyThunk | None = None + ) -> None: ... @abstractmethod - def nonzero(self) -> tuple[NumPyThunk, ...]: - ... + def nonzero(self) -> tuple[NumPyThunk, ...]: ... @abstractmethod def bitgenerator_random_raw( @@ -240,8 +211,7 @@ def bitgenerator_random_raw( generatorType: BitGeneratorType, seed: int | None, flags: int, - ) -> None: - ... + ) -> None: ... @abstractmethod def bitgenerator_integers( @@ -252,8 +222,7 @@ def bitgenerator_integers( flags: int, low: int, high: int, - ) -> None: - ... + ) -> None: ... @abstractmethod def bitgenerator_uniform( @@ -264,8 +233,7 @@ def bitgenerator_uniform( flags: int, low: float, high: float, - ) -> None: - ... + ) -> None: ... @abstractmethod def bitgenerator_lognormal( @@ -276,8 +244,7 @@ def bitgenerator_lognormal( flags: int, mean: float, sigma: float, - ) -> None: - ... + ) -> None: ... @abstractmethod def bitgenerator_normal( @@ -288,8 +255,7 @@ def bitgenerator_normal( flags: int, mean: float, sigma: float, - ) -> None: - ... + ) -> None: ... @abstractmethod def bitgenerator_poisson( @@ -299,8 +265,7 @@ def bitgenerator_poisson( seed: int | None, flags: int, lam: float, - ) -> None: - ... + ) -> None: ... @abstractmethod def bitgenerator_exponential( @@ -310,8 +275,7 @@ def bitgenerator_exponential( seed: int | None, flags: int, scale: float, - ) -> None: - ... + ) -> None: ... @abstractmethod def bitgenerator_gumbel( @@ -322,8 +286,7 @@ def bitgenerator_gumbel( flags: int, mu: float, beta: float, - ) -> None: - ... + ) -> None: ... @abstractmethod def bitgenerator_laplace( @@ -334,8 +297,7 @@ def bitgenerator_laplace( flags: int, mu: float, beta: float, - ) -> None: - ... + ) -> None: ... @abstractmethod def bitgenerator_logistic( @@ -346,8 +308,7 @@ def bitgenerator_logistic( flags: int, mu: float, beta: float, - ) -> None: - ... + ) -> None: ... @abstractmethod def bitgenerator_pareto( @@ -357,8 +318,7 @@ def bitgenerator_pareto( seed: int | None, flags: int, alpha: float, - ) -> None: - ... + ) -> None: ... @abstractmethod def bitgenerator_power( @@ -368,8 +328,7 @@ def bitgenerator_power( seed: int | None, flags: int, alpha: float, - ) -> None: - ... + ) -> None: ... @abstractmethod def bitgenerator_rayleigh( @@ -379,8 +338,7 @@ def bitgenerator_rayleigh( seed: int | None, flags: int, sigma: float, - ) -> None: - ... + ) -> None: ... @abstractmethod def bitgenerator_cauchy( @@ -391,8 +349,7 @@ def bitgenerator_cauchy( flags: int, x0: float, gamma: float, - ) -> None: - ... + ) -> None: ... @abstractmethod def bitgenerator_triangular( @@ -404,8 +361,7 @@ def bitgenerator_triangular( a: float, b: float, c: float, - ) -> None: - ... + ) -> None: ... @abstractmethod def bitgenerator_weibull( @@ -416,8 +372,7 @@ def bitgenerator_weibull( flags: int, lam: float, k: float, - ) -> None: - ... + ) -> None: ... @abstractmethod def bitgenerator_bytes( @@ -426,8 +381,7 @@ def bitgenerator_bytes( generatorType: BitGeneratorType, seed: int | None, flags: int, - ) -> None: - ... + ) -> None: ... @abstractmethod def bitgenerator_beta( @@ -438,8 +392,7 @@ def bitgenerator_beta( flags: int, a: float, b: float, - ) -> None: - ... + ) -> None: ... @abstractmethod def bitgenerator_f( @@ -450,8 +403,7 @@ def bitgenerator_f( flags: int, dfnum: float, dfden: float, - ) -> None: - ... + ) -> None: ... @abstractmethod def bitgenerator_logseries( @@ -461,8 +413,7 @@ def bitgenerator_logseries( seed: int | None, flags: int, p: float, - ) -> None: - ... + ) -> None: ... @abstractmethod def bitgenerator_noncentral_f( @@ -474,8 +425,7 @@ def bitgenerator_noncentral_f( dfnum: float, dfden: float, nonc: float, - ) -> None: - ... + ) -> None: ... @abstractmethod def bitgenerator_chisquare( @@ -486,8 +436,7 @@ def bitgenerator_chisquare( flags: int, df: float, nonc: float, - ) -> None: - ... + ) -> None: ... @abstractmethod def bitgenerator_gamma( @@ -498,8 +447,7 @@ def bitgenerator_gamma( flags: int, k: float, theta: float, - ) -> None: - ... + ) -> None: ... @abstractmethod def bitgenerator_standard_t( @@ -509,8 +457,7 @@ def bitgenerator_standard_t( seed: int | None, flags: int, df: float, - ) -> None: - ... + ) -> None: ... @abstractmethod def bitgenerator_hypergeometric( @@ -522,8 +469,7 @@ def bitgenerator_hypergeometric( ngood: int, nbad: int, nsample: int, - ) -> None: - ... + ) -> None: ... @abstractmethod def bitgenerator_vonmises( @@ -534,8 +480,7 @@ def bitgenerator_vonmises( flags: int, mu: float, kappa: float, - ) -> None: - ... + ) -> None: ... @abstractmethod def bitgenerator_zipf( @@ -545,8 +490,7 @@ def bitgenerator_zipf( seed: int | None, flags: int, alpha: float, - ) -> None: - ... + ) -> None: ... @abstractmethod def bitgenerator_geometric( @@ -556,8 +500,7 @@ def bitgenerator_geometric( seed: int | None, flags: int, p: float, - ) -> None: - ... + ) -> None: ... @abstractmethod def bitgenerator_wald( @@ -568,8 +511,7 @@ def bitgenerator_wald( flags: int, mean: float, scale: float, - ) -> None: - ... + ) -> None: ... @abstractmethod def bitgenerator_binomial( @@ -580,8 +522,7 @@ def bitgenerator_binomial( flags: int, ntrials: int, p: float, - ) -> None: - ... + ) -> None: ... @abstractmethod def bitgenerator_negative_binomial( @@ -592,12 +533,10 @@ def bitgenerator_negative_binomial( flags: int, ntrials: int, p: float, - ) -> None: - ... + ) -> None: ... @abstractmethod - def random_uniform(self) -> None: - ... + def random_uniform(self) -> None: ... @abstractmethod def partition( @@ -608,24 +547,22 @@ def partition( axis: int | None = -1, kind: SelectKind = "introselect", order: str | list[str] | None = None, - ) -> None: - ... + ) -> None: ... @abstractmethod - def random_normal(self) -> None: - ... + def random_normal(self) -> None: ... @abstractmethod def random_integer( self, low: int | npt.NDArray[Any], high: int | npt.NDArray[Any], - ) -> None: - ... + ) -> None: ... @abstractmethod - def searchsorted(self, rhs: Any, v: Any, side: SortSide = "left") -> None: - ... + def searchsorted( + self, rhs: Any, v: Any, side: SortSide = "left" + ) -> None: ... @abstractmethod def sort( @@ -635,8 +572,7 @@ def sort( axis: int | None = -1, kind: SortType = "quicksort", order: str | list[str] | None = None, - ) -> None: - ... + ) -> None: ... @abstractmethod def unary_op( @@ -646,8 +582,7 @@ def unary_op( where: Any, args: tuple[Scalar, ...] = (), multiout: Any | None = None, - ) -> None: - ... + ) -> None: ... @abstractmethod def unary_reduction( @@ -660,14 +595,12 @@ def unary_reduction( keepdims: bool, args: tuple[Scalar, ...], initial: Any, - ) -> None: - ... + ) -> None: ... @abstractmethod def isclose( self, rhs1: Any, rhs2: Any, rtol: float, atol: float, equal_nan: bool - ) -> None: - ... + ) -> None: ... @abstractmethod def binary_op( @@ -677,8 +610,7 @@ def binary_op( rhs2: Any, where: Any, args: tuple[Scalar, ...], - ) -> None: - ... + ) -> None: ... @abstractmethod def binary_reduction( @@ -688,44 +620,34 @@ def binary_reduction( rhs2: Any, broadcast: NdShape | None, args: tuple[Scalar, ...], - ) -> None: - ... + ) -> None: ... @abstractmethod - def broadcast_to(self, shape: NdShape) -> NumPyThunk: - ... + def broadcast_to(self, shape: NdShape) -> NumPyThunk: ... @abstractmethod - def argwhere(self) -> NumPyThunk: - ... + def argwhere(self) -> NumPyThunk: ... @abstractmethod - def where(self, rhs1: Any, rhs2: Any, rhs3: Any) -> None: - ... + def where(self, rhs1: Any, rhs2: Any, rhs3: Any) -> None: ... @abstractmethod - def cholesky(self, src: Any) -> None: - ... + def cholesky(self, src: Any) -> None: ... @abstractmethod - def eig(self, ew: Any, ev: Any) -> None: - ... + def eig(self, ew: Any, ev: Any) -> None: ... @abstractmethod - def eigvals(self, ew: Any) -> None: - ... + def eigvals(self, ew: Any) -> None: ... @abstractmethod - def qr(self, q: Any, r: Any) -> None: - ... + def qr(self, q: Any, r: Any) -> None: ... @abstractmethod - def solve(self, a: Any, b: Any) -> None: - ... + def solve(self, a: Any, b: Any) -> None: ... @abstractmethod - def svd(self, u: Any, s: Any, vh: Any) -> None: - ... + def svd(self, u: Any, s: Any, vh: Any) -> None: ... @abstractmethod def scan( @@ -735,39 +657,35 @@ def scan( axis: int, dtype: npt.DTypeLike | None, nan_to_identity: bool, - ) -> None: - ... + ) -> None: ... @abstractmethod - def unique(self) -> NumPyThunk: - ... + def unique(self) -> NumPyThunk: ... @abstractmethod - def create_window(self, op_code: WindowOpCode, M: Any, *args: Any) -> None: - ... + def create_window( + self, op_code: WindowOpCode, M: Any, *args: Any + ) -> None: ... @abstractmethod - def packbits(self, src: Any, axis: int | None, bitorder: BitOrder) -> None: - ... + def packbits( + self, src: Any, axis: int | None, bitorder: BitOrder + ) -> None: ... @abstractmethod def unpackbits( self, src: Any, axis: int | None, bitorder: BitOrder - ) -> None: - ... + ) -> None: ... @abstractmethod - def _wrap(self, src: Any, new_len: int) -> None: - ... + def _wrap(self, src: Any, new_len: int) -> None: ... @abstractmethod - def histogram(self, src: Any, bins: Any, weights: Any) -> None: - ... + def histogram(self, src: Any, bins: Any, weights: Any) -> None: ... @abstractmethod def stencil_hint( self, low_offsets: tuple[int, ...], high_offsets: tuple[int, ...], - ) -> None: - ... + ) -> None: ... diff --git a/cupynumeric/_ufunc/ufunc.py b/cupynumeric/_ufunc/ufunc.py index 2eca1ccee2..6eb42a3221 100644 --- a/cupynumeric/_ufunc/ufunc.py +++ b/cupynumeric/_ufunc/ufunc.py @@ -676,9 +676,11 @@ def _resolve_dtype( else: to_dtypes = tuple(arr.dtype for arr in arrs) key = tuple( - arr.dtype.char - if type(orig) not in (int, float, complex) - else type(orig) + ( + arr.dtype.char + if type(orig) not in (int, float, complex) + else type(orig) + ) for orig, arr in zip(orig_args, arrs) ) # When all inputs are scalars, cannot use weak logic below. diff --git a/cupynumeric/_utils/coverage.py b/cupynumeric/_utils/coverage.py index ca2b4cfe83..0a05f82360 100644 --- a/cupynumeric/_utils/coverage.py +++ b/cupynumeric/_utils/coverage.py @@ -57,8 +57,7 @@ def filter_namespace( class AnyCallable(Protocol): - def __call__(self, *args: Any, **kwargs: Any) -> Any: - ... + def __call__(self, *args: Any, **kwargs: Any) -> Any: ... @dataclass(frozen=True) diff --git a/cupynumeric/config.py b/cupynumeric/config.py index b931dead9f..c7a351d8f5 100644 --- a/cupynumeric/config.py +++ b/cupynumeric/config.py @@ -285,20 +285,18 @@ class _CupynumericSharedLib: CUPYNUMERIC_ZIP: int @abstractmethod - def cupynumeric_has_cusolvermp(self) -> bool: - ... + def cupynumeric_has_cusolvermp(self) -> bool: ... @abstractmethod - def cupynumeric_cusolver_has_geev(self) -> bool: - ... + def cupynumeric_cusolver_has_geev(self) -> bool: ... @abstractmethod - def cupynumeric_max_eager_volume(self) -> int: - ... + def cupynumeric_max_eager_volume(self) -> int: ... @abstractmethod - def cupynumeric_register_reduction_ops(self, code: int) -> _ReductionOpIds: - ... + def cupynumeric_register_reduction_ops( + self, code: int + ) -> _ReductionOpIds: ... def dlopen_no_autoclose(ffi: Any, lib_path: str) -> Any: diff --git a/cupynumeric/patch.py b/cupynumeric/patch.py index b92a7f7e32..569499fc29 100644 --- a/cupynumeric/patch.py +++ b/cupynumeric/patch.py @@ -12,7 +12,7 @@ # See the License for the specific language governing permissions and # limitations under the License. # -""" This module may be imported in order to globably replace NumPy with +"""This module may be imported in order to globably replace NumPy with cuPyNumeric. In order to function properly, this module must be imported early (ideally diff --git a/cupynumeric/random/_bitgenerator.py b/cupynumeric/random/_bitgenerator.py index 55ecbea8eb..c4f62691b1 100644 --- a/cupynumeric/random/_bitgenerator.py +++ b/cupynumeric/random/_bitgenerator.py @@ -68,8 +68,7 @@ def __init__( ) @abstractproperty - def generatorType(self) -> BitGeneratorType: - ... + def generatorType(self) -> BitGeneratorType: ... def __del__(self) -> None: if self.handle != 0: diff --git a/examples/benchmark.py b/examples/benchmark.py index 98ae1249dd..29a7f4a451 100644 --- a/examples/benchmark.py +++ b/examples/benchmark.py @@ -21,8 +21,7 @@ class Timer(Protocol): - def start(self): - ... + def start(self): ... def stop(self): """ diff --git a/src/cupynumeric/execution_policy/reduction/scalar_reduction.cuh b/src/cupynumeric/execution_policy/reduction/scalar_reduction.cuh index 004c058a76..20a506944e 100644 --- a/src/cupynumeric/execution_policy/reduction/scalar_reduction.cuh +++ b/src/cupynumeric/execution_policy/reduction/scalar_reduction.cuh @@ -49,10 +49,8 @@ static __global__ void __launch_bounds__(1, 1) copy_kernel(Buffer result, RedAcc template struct ScalarReductionPolicy { template - void __attribute__((visibility("hidden"))) operator()(size_t volume, - AccessorRD& out, - const LHS& identity, - Kernel&& kernel) + void __attribute__((visibility("hidden"))) operator()( + size_t volume, AccessorRD & out, const LHS & identity, Kernel && kernel) { if (0 == volume) { return; diff --git a/src/cupynumeric/ndarray.cc b/src/cupynumeric/ndarray.cc index 6629832a69..10f0d017a3 100644 --- a/src/cupynumeric/ndarray.cc +++ b/src/cupynumeric/ndarray.cc @@ -1715,8 +1715,7 @@ NDArray NDArray::reshape(std::vector newshape) // case 1: zero size if (size() == 0) { if (1 == num_unknowns) { - std::replace_if( - newshape.begin(), newshape.end(), [](auto x) { return x < 0; }, 0); + std::replace_if(newshape.begin(), newshape.end(), [](auto x) { return x < 0; }, 0); } auto out_size = vec_prod(newshape); if (out_size != 0) { @@ -1738,8 +1737,7 @@ NDArray NDArray::reshape(std::vector newshape) if (unknown_extent * known_volume != size()) { throw std::invalid_argument("cannot reshape, size mismatch"); } - std::replace_if( - newshape.begin(), newshape.end(), [](auto x) { return x < 0; }, unknown_extent); + std::replace_if(newshape.begin(), newshape.end(), [](auto x) { return x < 0; }, unknown_extent); auto in_shape = shape(); auto out_shape = vec_convert(newshape); diff --git a/src/cupynumeric/random/philox.h b/src/cupynumeric/random/philox.h index 46eb7f6ae3..5666d6def7 100644 --- a/src/cupynumeric/random/philox.h +++ b/src/cupynumeric/random/philox.h @@ -118,7 +118,7 @@ class Philox_2x32 { // This syntax is only supported on >= c++17 const float scale = 0x1.p-32; // 2^-32 #else - const float scale = 0.00000000023283064365386962890625; + const float scale = 0.00000000023283064365386962890625; #endif return (bits * scale); } diff --git a/src/cupynumeric/random/rand_util.h b/src/cupynumeric/random/rand_util.h index 95968d6b3c..b5ff655a7b 100644 --- a/src/cupynumeric/random/rand_util.h +++ b/src/cupynumeric/random/rand_util.h @@ -20,7 +20,7 @@ #include "cupynumeric/random/philox.h" #define HI_BITS(x) (static_cast((x) >> 32)) -#define LO_BITS(x) (static_cast((x)&0x00000000FFFFFFFF)) +#define LO_BITS(x) (static_cast((x) & 0x00000000FFFFFFFF)) namespace cupynumeric { diff --git a/src/cupynumeric/stat/bincount_template.inl b/src/cupynumeric/stat/bincount_template.inl index 8606446fe5..66bc9c723c 100644 --- a/src/cupynumeric/stat/bincount_template.inl +++ b/src/cupynumeric/stat/bincount_template.inl @@ -42,12 +42,12 @@ struct BincountImpl { auto rhs = args.rhs.read_accessor(rect); if (args.has_weights) { auto weights = args.weights.read_accessor(rect); - auto lhs = - args.lhs.reduce_accessor, KIND != VariantKind::GPU, 1>(lhs_rect); + auto lhs = args.lhs.reduce_accessor < SumReduction, KIND != VariantKind::GPU, + 1 > (lhs_rect); BincountImplBody()(lhs, rhs, weights, rect, lhs_rect); } else { - auto lhs = - args.lhs.reduce_accessor, KIND != VariantKind::GPU, 1>(lhs_rect); + auto lhs = args.lhs.reduce_accessor < SumReduction, KIND != VariantKind::GPU, + 1 > (lhs_rect); BincountImplBody()(lhs, rhs, rect, lhs_rect); } } diff --git a/src/cupynumeric/unary/unary_red_template.inl b/src/cupynumeric/unary/unary_red_template.inl index 1d268ca6c1..975c716c3b 100644 --- a/src/cupynumeric/unary/unary_red_template.inl +++ b/src/cupynumeric/unary/unary_red_template.inl @@ -50,7 +50,7 @@ struct UnaryRedImpl { auto rhs = args.rhs.read_accessor(rect); - auto lhs = args.lhs.reduce_accessor(rect); + auto lhs = args.lhs.reduce_accessor < typename OP::OP, KIND != VariantKind::GPU, DIM > (rect); AccessorRO where; if constexpr (HAS_WHERE) { diff --git a/tests/cpp/integration/test_where.cc b/tests/cpp/integration/test_where.cc index b8684dd3bd..642959884b 100644 --- a/tests/cpp/integration/test_where.cc +++ b/tests/cpp/integration/test_where.cc @@ -168,26 +168,26 @@ TEST(Where, BroadcastShape) TEST(Where, EmptyAndScalar) { auto A = mk_array({true}, - { + { 1, }); auto A_SCALAR = mk_array({false}, {}); auto A_EMPTY = mk_array({}, - { + { 0, }); auto X = mk_array({10}, - { + { 1, }); auto Y = mk_array({20}, - { + { 1, }); auto X_SCALAR = mk_array({10}, {}); auto Y_SCALAR = mk_array({20}, {}); auto EMPTY = mk_array({}, - { + { 0, }); diff --git a/tests/integration/test_expm_sh.py b/tests/integration/test_expm_sh.py index d369e5e573..6a41eeb6b6 100644 --- a/tests/integration/test_expm_sh.py +++ b/tests/integration/test_expm_sh.py @@ -15,10 +15,10 @@ import numpy as np import pytest +import scipy as sp from utils.comparisons import allclose import cupynumeric as num -import scipy as sp SIZES = (4, 10) @@ -36,55 +36,63 @@ np.dtype(np.complex128): 1e-8, } -def make_skew_hermitian(n: int, - min_v: float = 0.0, - max_v: float = 100.0) -> np.ndarray: - num_off_d = int(n*(n-1)/2) + +def make_skew_hermitian( + n: int, min_v: float = 0.0, max_v: float = 100.0 +) -> np.ndarray: + num_off_d = int(n * (n - 1) / 2) np.random.seed(1729) - r_array = np.array([ np.random.uniform(min_v, max_v) - for k in range(num_off_d)], dtype=np.dtype('float64')) + r_array = np.array( + [np.random.uniform(min_v, max_v) for k in range(num_off_d)], + dtype=np.dtype("float64"), + ) - i_array = np.array([ np.random.uniform(min_v, max_v) - for k in range(num_off_d)], dtype=np.dtype('float64')) + i_array = np.array( + [np.random.uniform(min_v, max_v) for k in range(num_off_d)], + dtype=np.dtype("float64"), + ) - d_array = np.array([ np.random.uniform(min_v, max_v) - for k in range(n)], dtype=np.dtype('float64')) - - mat = np.zeros((n, n), dtype=np.dtype('complex64')) + d_array = np.array( + [np.random.uniform(min_v, max_v) for k in range(n)], + dtype=np.dtype("float64"), + ) + + mat = np.zeros((n, n), dtype=np.dtype("complex64")) arr_index = 0 for col in range(1, n): for row in range(0, col): - mat[row, col] = r_array[arr_index] + i_array[arr_index]*1.j + mat[row, col] = r_array[arr_index] + i_array[arr_index] * 1.0j mat[col, row] = -np.conjugate(mat[row, col]) arr_index = arr_index + 1 c_1 = col - 1 - mat[c_1, c_1] = d_array[c_1]*1.j + mat[c_1, c_1] = d_array[c_1] * 1.0j - mat[n-1][n-1] = d_array[n-1]*1.j + mat[n - 1][n - 1] = d_array[n - 1] * 1.0j return mat - - + + def check_skew_hermitian(A: np.ndarray) -> bool: assert A.ndim == 2 n = A.shape[0] assert n == A.shape[1] - num_half_off_d = int(n*(n-1)/2) + num_half_off_d = int(n * (n - 1) / 2) - arr_off_d = np.array([A[i, j] + np.conjugate(A[j, i]) for i in range(n) - for j in range(i)], - dtype=np.dtype('complex64')) + arr_off_d = np.array( + [A[i, j] + np.conjugate(A[j, i]) for i in range(n) for j in range(i)], + dtype=np.dtype("complex64"), + ) - check_arr = np.zeros((num_half_off_d, ), dtype=np.dtype('complex64')) + check_arr = np.zeros((num_half_off_d,), dtype=np.dtype("complex64")) assert arr_off_d.size == num_half_off_d - - assert allclose(arr_off_d, check_arr, atol=ATOL[A.dtype], - check_dtype=False + + assert allclose( + arr_off_d, check_arr, atol=ATOL[A.dtype], check_dtype=False ) assert np.all([np.real(A[k, k]) for k in range(n)] == np.zeros(n)) @@ -92,22 +100,18 @@ def check_skew_hermitian(A: np.ndarray) -> bool: @pytest.mark.parametrize("n", SIZES) -@pytest.mark.parametrize( - "min_v", (0.0, ) -) -@pytest.mark.parametrize( - "max_v", (10.0,) -) +@pytest.mark.parametrize("min_v", (0.0,)) +@pytest.mark.parametrize("max_v", (10.0,)) def test_expm_rnd_sh_tensor_pade(n, min_v, max_v): m = 3 - a = np.zeros(shape=(m,n,n), dtype=np.complex64) + a = np.zeros(shape=(m, n, n), dtype=np.complex64) for idx in np.ndindex(a.shape[:-2]): a[idx] = make_skew_hermitian(n, min_v, max_v) # more info for debug purposes: # (out_num, m, s) = num.linalg.expm_impl(a) # - out_num = num.linalg.expm(a, method='pade') + out_num = num.linalg.expm(a, method="pade") out_s = sp.linalg.expm(a) rtol = RTOL[out_num.dtype] @@ -132,8 +136,8 @@ def test_expm_rnd_sh_tensor_pade(n, min_v, max_v): # # conversion to string shows more decimals... # - print("external ||exp(A)|| = %s\n"%(str(norm_exp_s))) - print("Cupynumeric ||exp(A)|| = %s\n"%(str(norm_exp_num))) + print("external ||exp(A)|| = %s\n" % (str(norm_exp_s))) + print("Cupynumeric ||exp(A)|| = %s\n" % (str(norm_exp_num))) assert np.abs(1.0 - norm_exp_num) <= np.abs(1.0 - norm_exp_s) @@ -141,22 +145,18 @@ def test_expm_rnd_sh_tensor_pade(n, min_v, max_v): @pytest.mark.parametrize("n", SIZES) -@pytest.mark.parametrize( - "min_v", (0.0, ) -) -@pytest.mark.parametrize( - "max_v", (10.0,) -) +@pytest.mark.parametrize("min_v", (0.0,)) +@pytest.mark.parametrize("max_v", (10.0,)) def test_expm_rnd_sh_tensor_taylor(n, min_v, max_v): m = 3 - a = np.zeros(shape=(m,n,n), dtype=np.complex64) + a = np.zeros(shape=(m, n, n), dtype=np.complex64) for idx in np.ndindex(a.shape[:-2]): a[idx] = make_skew_hermitian(n, min_v, max_v) # more info for debug purposes: # (out_num, m, s) = num.linalg.expm_impl(a) # - out_num = num.linalg.expm(a, method='taylor') + out_num = num.linalg.expm(a, method="taylor") out_s = sp.linalg.expm(a) rtol = RTOL[out_num.dtype] @@ -181,8 +181,8 @@ def test_expm_rnd_sh_tensor_taylor(n, min_v, max_v): # # conversion to string shows more decimals... # - print("external ||exp(A)|| = %s\n"%(str(norm_exp_s))) - print("Cupynumeric ||exp(A)|| = %s\n"%(str(norm_exp_num))) + print("external ||exp(A)|| = %s\n" % (str(norm_exp_s))) + print("Cupynumeric ||exp(A)|| = %s\n" % (str(norm_exp_num))) assert np.abs(1.0 - norm_exp_num) <= np.abs(1.0 - norm_exp_s) diff --git a/tests/integration/test_expm_sh_cpx_qr.py b/tests/integration/test_expm_sh_cpx_qr.py index 3f595dcffd..934277d242 100644 --- a/tests/integration/test_expm_sh_cpx_qr.py +++ b/tests/integration/test_expm_sh_cpx_qr.py @@ -14,13 +14,14 @@ # import numpy as np + # import cupy as cp # import cupyx.scipy.linalg as cpxl import pytest +import scipy as sp from utils.comparisons import allclose import cupynumeric as num -import scipy as sp SIZES = (4, 10, 50) @@ -38,55 +39,63 @@ np.dtype(np.complex128): 1e-6, } -def make_skew_hermitian(n: int, - min_v: float = 0.0, - max_v: float = 100.0) -> np.ndarray: - num_off_d = int(n*(n-1)/2) + +def make_skew_hermitian( + n: int, min_v: float = 0.0, max_v: float = 100.0 +) -> np.ndarray: + num_off_d = int(n * (n - 1) / 2) np.random.seed(1729) - r_array = np.array([ np.random.uniform(min_v, max_v) - for k in range(num_off_d)], dtype=np.dtype('float64')) + r_array = np.array( + [np.random.uniform(min_v, max_v) for k in range(num_off_d)], + dtype=np.dtype("float64"), + ) + + i_array = np.array( + [np.random.uniform(min_v, max_v) for k in range(num_off_d)], + dtype=np.dtype("float64"), + ) - i_array = np.array([ np.random.uniform(min_v, max_v) - for k in range(num_off_d)], dtype=np.dtype('float64')) + d_array = np.array( + [np.random.uniform(min_v, max_v) for k in range(n)], + dtype=np.dtype("float64"), + ) - d_array = np.array([ np.random.uniform(min_v, max_v) - for k in range(n)], dtype=np.dtype('float64')) - - mat = np.zeros((n, n), dtype=np.dtype('complex64')) + mat = np.zeros((n, n), dtype=np.dtype("complex64")) arr_index = 0 for col in range(1, n): for row in range(0, col): - mat[row, col] = r_array[arr_index] + i_array[arr_index]*1.j + mat[row, col] = r_array[arr_index] + i_array[arr_index] * 1.0j mat[col, row] = -np.conjugate(mat[row, col]) arr_index = arr_index + 1 c_1 = col - 1 - mat[c_1, c_1] = d_array[c_1]*1.j + mat[c_1, c_1] = d_array[c_1] * 1.0j - mat[n-1][n-1] = d_array[n-1]*1.j + mat[n - 1][n - 1] = d_array[n - 1] * 1.0j return mat - - + + def check_skew_hermitian(A: np.ndarray) -> bool: assert A.ndim == 2 n = A.shape[0] assert n == A.shape[1] - num_half_off_d = int(n*(n-1)/2) + num_half_off_d = int(n * (n - 1) / 2) - arr_off_d = np.array([A[i, j] + np.conjugate(A[j, i]) for i in range(n) - for j in range(i)], - dtype=np.dtype('complex64')) + arr_off_d = np.array( + [A[i, j] + np.conjugate(A[j, i]) for i in range(n) for j in range(i)], + dtype=np.dtype("complex64"), + ) - check_arr = np.zeros((num_half_off_d, ), dtype=np.dtype('complex64')) + check_arr = np.zeros((num_half_off_d,), dtype=np.dtype("complex64")) assert arr_off_d.size == num_half_off_d - - assert allclose(arr_off_d, check_arr, atol=ATOL[A.dtype], - check_dtype=False + + assert allclose( + arr_off_d, check_arr, atol=ATOL[A.dtype], check_dtype=False ) assert np.all([np.real(A[k, k]) for k in range(n)] == np.zeros(n)) @@ -94,12 +103,8 @@ def check_skew_hermitian(A: np.ndarray) -> bool: @pytest.mark.parametrize("n", SIZES) -@pytest.mark.parametrize( - "min_v", (0.0, )#10.0) -) -@pytest.mark.parametrize( - "max_v", (2.0,) #100.0) -) +@pytest.mark.parametrize("min_v", (0.0,)) # 10.0) +@pytest.mark.parametrize("max_v", (2.0,)) # 100.0) def test_expm_rnd_skew_h(n, min_v, max_v): a = make_skew_hermitian(n, min_v, max_v) check_skew_hermitian(a) @@ -122,9 +127,9 @@ def test_expm_rnd_skew_h(n, min_v, max_v): if n > 1024: atol *= 20.0 - print("\nexternal solver: %s\n"%(str(out_s))) - print("CuPyNumeric: %s\n"%(str(out_num))) - + print("\nexternal solver: %s\n" % (str(out_s))) + print("CuPyNumeric: %s\n" % (str(out_num))) + tol_satisfied = allclose( out_num, out_s, rtol=rtol, atol=atol, check_dtype=False ) @@ -137,10 +142,10 @@ def test_expm_rnd_skew_h(n, min_v, max_v): # # conversion to string shows more decimals... # - print("external ||exp(A)|| = %s\n"%(str(norm_exp_s))) - print("Cupynumeric ||exp(A)|| = %s\n"%(str(norm_exp_num))) + print("external ||exp(A)|| = %s\n" % (str(norm_exp_s))) + print("Cupynumeric ||exp(A)|| = %s\n" % (str(norm_exp_num))) assert np.abs(1.0 - norm_exp_num) <= np.abs(1.0 - norm_exp_s) - + (_, R) = np.linalg.qr(a) min_abs_diag = np.min([np.abs(R[k, k]) for k in range(a.shape[0])]) if min_abs_diag.item() < atol: diff --git a/tests/integration/test_scan.py b/tests/integration/test_scan.py index 95bd5ca13e..a91208037b 100644 --- a/tests/integration/test_scan.py +++ b/tests/integration/test_scan.py @@ -43,9 +43,9 @@ def _gen_array(n0, shape, dt, axis, outtype): A[(1,) * len(shape)] = np.nan elif n0 == "second_half": # second from last element along all axes is a NAN - A[ - tuple(map(lambda i, j: i - j, A.shape, (2,) * len(A.shape))) - ] = np.nan + A[tuple(map(lambda i, j: i - j, A.shape, (2,) * len(A.shape)))] = ( + np.nan + ) if outtype is None: B = None C = None diff --git a/tests/todo/lstm_batch.py b/tests/todo/lstm_batch.py index 6e352857b2..2d72321d33 100644 --- a/tests/todo/lstm_batch.py +++ b/tests/todo/lstm_batch.py @@ -137,9 +137,7 @@ def backward(dHout_in, cache, dcn=None, dhn=None): tanhCt = Ct[t] dIFOGf[t, :, 2 * d : 3 * d] = tanhCt * dHout[t] # backprop tanh non-linearity first then continue backprop - dC[t] += (1 - tanhCt**2) * ( - IFOGf[t, :, 2 * d : 3 * d] * dHout[t] - ) + dC[t] += (1 - tanhCt**2) * (IFOGf[t, :, 2 * d : 3 * d] * dHout[t]) if t > 0: dIFOGf[t, :, d : 2 * d] = C[t - 1] * dC[t] dC[t - 1] += IFOGf[t, :, d : 2 * d] * dC[t]