Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .codecov.yml
Original file line number Diff line number Diff line change
Expand Up @@ -25,4 +25,5 @@ ignore:
- "*/examples/*"
- "*/python/unittest/*"
- "*/ark/unittest/*"
- "*/ark/ops/ops_test_common.*"
- "*/ark/include/kernels/*"
3 changes: 2 additions & 1 deletion .github/workflows/ut.yml
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ jobs:
if: github.event_name != 'schedule'
run: |
cd build
lcov --capture --directory . --output-file cpp_coverage.info
lcov --ignore-errors negative --capture --directory . --output-file cpp_coverage.info
lcov --ignore-errors unused --remove cpp_coverage.info \
'/usr/*' \
'/tmp/*' \
Expand All @@ -86,6 +86,7 @@ jobs:
'*/examples/*' \
'*/python/*' \
'*/ark/unittest/unittest_utils.cpp' \
'*/ark/ops/ops_test_common.*' \
--output-file cpp_coverage.info
lcov --list cpp_coverage.info

Expand Down
2 changes: 2 additions & 0 deletions ark/gpu/gpu.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,8 @@ ARK_GPU_DEFINE_CONSTANT_ALIAS(gpuPointerAttributeSyncMemops,
HIP_POINTER_ATTRIBUTE_SYNC_MEMOPS);

// runtime API
ARK_GPU_DEFINE_FUNC_ALIAS(gpuGetDeviceCount, cudaGetDeviceCount,
hipGetDeviceCount);
ARK_GPU_DEFINE_FUNC_ALIAS(gpuGetErrorString, cudaGetErrorString,
hipGetErrorString);
ARK_GPU_DEFINE_FUNC_ALIAS(gpuGetLastError, cudaGetLastError, hipGetLastError);
Expand Down
6 changes: 6 additions & 0 deletions ark/ops/ops_all_reduce.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,12 @@ Tensor Model::all_reduce(Tensor input, int gpu_id, int gpu_num, Tensor output,
}
if (output.is_null()) {
output = this->copy(input);
} else if (output.ref()->buffer()->id() == input.ref()->buffer()->id()) {
// In-place: copy input so the ring loop does not mutate send data.
// TODO: This catches the common case (output IS input). Sub-buffer
// offset aliasing or aliasing through different buffer objects backed
// by the same allocation is not currently detected.
input = this->copy(input);
}
Tensor prev_recv = NullTensor;
Tensor cumulate = output;
Expand Down
57 changes: 57 additions & 0 deletions ark/ops/ops_all_reduce_test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
#include "model/model_buffer.hpp"
#include "model/model_node.hpp"
#include "model/model_op.hpp"
#include "model/model_tensor.hpp"
#include "ops_test_common.hpp"

template <typename T, int NumGpus>
Expand All @@ -27,6 +28,7 @@ template <int NumGpus>
void test_all_reduce_internal(ark::DimType nelem) {
for (int gpu_id = 0; gpu_id < NumGpus; ++gpu_id) {
ark::unittest::spawn_process([gpu_id, nelem]() {
UNITTEST_SKIP(ark::unittest::get_gpu_count() < NumGpus);
// Each GPU's data is equal to its GPU ID + 1.
ark::Model m(gpu_id, NumGpus);
ark::Tensor ones = m.tensor({nelem}, ark::FP16);
Expand Down Expand Up @@ -118,6 +120,7 @@ template <int NumGpus>
void test_all_reduce_packet_internal(ark::DimType nelem) {
for (int gpu_id = 0; gpu_id < NumGpus; ++gpu_id) {
ark::unittest::spawn_process([gpu_id, nelem]() {
UNITTEST_SKIP(ark::unittest::get_gpu_count() < NumGpus);
// Each GPU's data is equal to its GPU ID + 1.
ark::Model m(gpu_id, NumGpus);
ark::Tensor ones = m.tensor({nelem}, ark::FP16);
Expand Down Expand Up @@ -224,6 +227,7 @@ void test_all_reduce_sm_internal(ark::DimType nelem) {
};
for (int gpu_id = 0; gpu_id < NumGpus; ++gpu_id) {
ark::unittest::spawn_process([gpu_id, nelem, config_rule]() {
UNITTEST_SKIP(ark::unittest::get_gpu_count() < NumGpus);
// Each GPU's data is equal to its GPU ID + 1.
ark::Model m(gpu_id, NumGpus);
ark::Tensor ones = m.tensor({nelem}, ark::FP16);
Expand All @@ -244,6 +248,56 @@ void test_all_reduce_sm_internal(ark::DimType nelem) {
ark::unittest::wait_all_processes();
}

template <int NumGpus>
void test_all_reduce_inplace_internal(ark::DimType nelem) {
for (int gpu_id = 0; gpu_id < NumGpus; ++gpu_id) {
ark::unittest::spawn_process([gpu_id, nelem]() {
UNITTEST_SKIP(ark::unittest::get_gpu_count() < NumGpus);
// Each GPU's data is equal to its GPU ID + 1.
ark::Model m(gpu_id, NumGpus);
ark::Tensor ones = m.tensor({nelem}, ark::FP16);
ark::Tensor data = m.mul(ones, float(gpu_id + 1));
// In-place: pass the same tensor as both input and output.
ark::Tensor output = m.all_reduce(data, gpu_id, NumGpus, data);

// Verify the output is truly in-place (same buffer as input).
UNITTEST_EQ(output.ref()->buffer()->id(),
data.ref()->buffer()->id());

std::vector<ark::half_t> ones_vec(ones.shape().nelems(),
ark::half_t(1.0f));
auto result = ark::op_test(
"all_reduce_inplace", m, {ones}, {output},
baseline_all_reduce<ark::half_t, NumGpus>, {ones_vec.data()});
UNITTEST_LOG(result);
UNITTEST_EQ(result.max_diff[0], 0.0f);
return ark::unittest::SUCCESS;
});
}
ark::unittest::wait_all_processes();
}

// The corruption was most visible with gpu_num >= 3 (multiple ring
// iterations); this 2-GPU case validates the in-place copy path still
// works for the simpler ring.
ark::unittest::State test_all_reduce_inplace_2gpus() {
test_all_reduce_inplace_internal<2>(64);
test_all_reduce_inplace_internal<2>(8192);
return ark::unittest::SUCCESS;
}

ark::unittest::State test_all_reduce_inplace_3gpus() {
test_all_reduce_inplace_internal<3>(64);
test_all_reduce_inplace_internal<3>(8192);
return ark::unittest::SUCCESS;
}

ark::unittest::State test_all_reduce_inplace_4gpus() {
test_all_reduce_inplace_internal<4>(64);
test_all_reduce_inplace_internal<4>(8192);
return ark::unittest::SUCCESS;
}

ark::unittest::State test_all_reduce_4gpus() {
test_all_reduce_internal<4>(64);
test_all_reduce_internal<4>(8192);
Expand Down Expand Up @@ -287,5 +341,8 @@ int main() {
UNITTEST(test_all_reduce_packet_8gpus);
UNITTEST(test_all_reduce_sm_4gpus);
UNITTEST(test_all_reduce_sm_8gpus);
UNITTEST(test_all_reduce_inplace_2gpus);
UNITTEST(test_all_reduce_inplace_3gpus);
UNITTEST(test_all_reduce_inplace_4gpus);
return 0;
}
18 changes: 12 additions & 6 deletions ark/ops/ops_test_common.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@
#include "ark/planner.hpp"
#include "ark/random.hpp"
#include "cpu_timer.h"
#include "env.h"
#include "gpu/gpu_logging.hpp"
#include "logging.hpp"
#include "model/model_data_type.hpp"
Expand Down Expand Up @@ -40,11 +39,7 @@ OpsTestResult op_test(const std::string &test_name_prefix, const Model &model,
const std::vector<void *> &inputs_data,
const std::vector<Planner::ConfigRule> &config_rules,
bool print_on_error) {
const char *env = std::getenv("ARK_UT_CORRECTNESS");
if (!env || std::string(env) != "1") {
// Zero values let callers pass without GPU work.
LOG(INFO, "[SKIP] %s: ARK_UT_CORRECTNESS not set",
test_name_prefix.c_str());
auto make_skipped = [&]() {
OpsTestResult skipped;
skipped.test_name = test_name_prefix;
skipped.iter = 0;
Expand All @@ -56,6 +51,17 @@ OpsTestResult op_test(const std::string &test_name_prefix, const Model &model,
skipped.num_wrong.resize(n, 0);
skipped.num_total.resize(n, 0);
return skipped;
};
const char *env = std::getenv("ARK_UT_CORRECTNESS");
if (!env || std::string(env) != "1") {
// Zero values let callers pass without GPU work.
LOG(INFO, "[SKIP] %s: ARK_UT_CORRECTNESS not set",
test_name_prefix.c_str());
return make_skipped();
}
if (ark::unittest::get_gpu_count() < 1) {
LOG(INFO, "[SKIP] %s: no GPU available", test_name_prefix.c_str());
return make_skipped();
}
DefaultExecutor exe(model, -1, nullptr, config_rules);

Expand Down
9 changes: 9 additions & 0 deletions ark/unittest/unittest_utils.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
#include <vector>

#include "file_io.h"
#include "gpu/gpu.hpp"
#include "logging.hpp"

// Grep SIGALRM and exit.
Expand All @@ -22,6 +23,14 @@ static void sigalrm_timeout_handler(int) {
namespace ark {
namespace unittest {

int get_gpu_count() {
int count = 0;
if (gpuGetDeviceCount(&count) != gpuSuccess) {
return 0;
}
return count;
}

// Temporal unittest states.
struct TempStates {
std::vector<int> pids;
Expand Down
14 changes: 14 additions & 0 deletions ark/unittest/unittest_utils.h
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,9 @@ namespace unittest {

typedef enum { SUCCESS = 0, FAILURE, UNEXPECTED } State;

// Return the number of visible GPUs (calls gpuGetDeviceCount).
int get_gpu_count();

void exit(State s, const std::string &errmsg);
void fexit(const std::string &errmsg = "");
void uexit(const std::string &errmsg = "");
Expand Down Expand Up @@ -240,4 +243,15 @@ std::string get_kernel_code(const std::string &name);
// Log a message.
#define UNITTEST_LOG(...) LOG(ark::INFO, __VA_ARGS__)

// Skip the current test (exit with SUCCESS) if @p cond is true.
// WARNING: calls std::exit(), which skips local destructors.
// Only safe inside spawn_process() child lambdas.
#define UNITTEST_SKIP(cond) \
do { \
if (cond) { \
LOG(ark::INFO, "unittest skip: " #cond); \
std::exit(ark::unittest::SUCCESS); \
} \
} while (0)

#endif // ARK_UNITTEST_UNITTEST_UTILS_H_
Loading