1 /*!
2  * Copyright 2018 XGBoost contributors
3  */
4 
5 #include <gtest/gtest.h>
6 #include <thrust/equal.h>
7 #include <thrust/iterator/counting_iterator.h>
8 
9 #include "../../../src/common/device_helpers.cuh"
10 #include <xgboost/host_device_vector.h>
11 
12 namespace xgboost {
13 namespace common {
14 
SetDevice(int device)15 void SetDevice(int device) {
16   int n_devices;
17   dh::safe_cuda(cudaGetDeviceCount(&n_devices));
18   device %= n_devices;
19   dh::safe_cuda(cudaSetDevice(device));
20 }
21 
22 struct HostDeviceVectorSetDeviceHandler {
23   template <typename Functor>
HostDeviceVectorSetDeviceHandlerxgboost::common::HostDeviceVectorSetDeviceHandler24   explicit HostDeviceVectorSetDeviceHandler(Functor f) {
25     SetCudaSetDeviceHandler(f);
26   }
27 
~HostDeviceVectorSetDeviceHandlerxgboost::common::HostDeviceVectorSetDeviceHandler28   ~HostDeviceVectorSetDeviceHandler() {
29     SetCudaSetDeviceHandler(nullptr);
30   }
31 };
32 
InitHostDeviceVector(size_t n,int device,HostDeviceVector<int> * v)33 void InitHostDeviceVector(size_t n, int device, HostDeviceVector<int> *v) {
34   // create the vector
35   v->SetDevice(device);
36   v->Resize(n);
37 
38   ASSERT_EQ(v->Size(), n);
39   ASSERT_EQ(v->DeviceIdx(), device);
40   // ensure that the device have read-write access
41   ASSERT_TRUE(v->DeviceCanRead());
42   ASSERT_TRUE(v->DeviceCanWrite());
43   // ensure that the host has no access
44   ASSERT_FALSE(v->HostCanRead());
45   ASSERT_FALSE(v->HostCanWrite());
46 
47   // fill in the data on the host
48   std::vector<int>& data_h = v->HostVector();
49   // ensure that the host has full access, while the device have none
50   ASSERT_TRUE(v->HostCanRead());
51   ASSERT_TRUE(v->HostCanWrite());
52   ASSERT_FALSE(v->DeviceCanRead());
53   ASSERT_FALSE(v->DeviceCanWrite());
54   ASSERT_EQ(data_h.size(), n);
55   std::copy_n(thrust::make_counting_iterator(0), n, data_h.begin());
56 }
57 
PlusOne(HostDeviceVector<int> * v)58 void PlusOne(HostDeviceVector<int> *v) {
59   int device = v->DeviceIdx();
60   SetDevice(device);
61   thrust::transform(dh::tcbegin(*v), dh::tcend(*v), dh::tbegin(*v),
62                     [=]__device__(unsigned int a){ return a + 1; });
63   ASSERT_TRUE(v->DeviceCanWrite());
64 }
65 
CheckDevice(HostDeviceVector<int> * v,size_t size,unsigned int first,GPUAccess access)66 void CheckDevice(HostDeviceVector<int>* v,
67                  size_t size,
68                  unsigned int first,
69                  GPUAccess access) {
70   ASSERT_EQ(v->Size(), size);
71   SetDevice(v->DeviceIdx());
72 
73   ASSERT_TRUE(thrust::equal(dh::tcbegin(*v), dh::tcend(*v),
74                             thrust::make_counting_iterator(first)));
75   ASSERT_TRUE(v->DeviceCanRead());
76   // ensure that the device has at most the access specified by access
77   ASSERT_EQ(v->DeviceCanWrite(), access == GPUAccess::kWrite);
78   ASSERT_EQ(v->HostCanRead(), access == GPUAccess::kRead);
79   ASSERT_FALSE(v->HostCanWrite());
80 
81   ASSERT_TRUE(thrust::equal(dh::tbegin(*v), dh::tend(*v),
82                             thrust::make_counting_iterator(first)));
83   ASSERT_TRUE(v->DeviceCanRead());
84   ASSERT_TRUE(v->DeviceCanWrite());
85   ASSERT_FALSE(v->HostCanRead());
86   ASSERT_FALSE(v->HostCanWrite());
87 }
88 
CheckHost(HostDeviceVector<int> * v,GPUAccess access)89 void CheckHost(HostDeviceVector<int> *v, GPUAccess access) {
90   const std::vector<int>& data_h = access == GPUAccess::kNone ?
91     v->HostVector() : v->ConstHostVector();
92   for (size_t i = 0; i < v->Size(); ++i) {
93     ASSERT_EQ(data_h.at(i), i + 1);
94   }
95   ASSERT_TRUE(v->HostCanRead());
96   ASSERT_EQ(v->HostCanWrite(), access == GPUAccess::kNone);
97   ASSERT_EQ(v->DeviceCanRead(), access == GPUAccess::kRead);
98   // the devices should have no write access
99   ASSERT_FALSE(v->DeviceCanWrite());
100 }
101 
TestHostDeviceVector(size_t n,int device)102 void TestHostDeviceVector(size_t n, int device) {
103   HostDeviceVectorSetDeviceHandler hdvec_dev_hndlr(SetDevice);
104   HostDeviceVector<int> v;
105   InitHostDeviceVector(n, device, &v);
106   CheckDevice(&v, n, 0, GPUAccess::kRead);
107   PlusOne(&v);
108   CheckDevice(&v, n, 1, GPUAccess::kWrite);
109   CheckHost(&v, GPUAccess::kRead);
110   CheckHost(&v, GPUAccess::kNone);
111 }
112 
TEST(HostDeviceVector,Basic)113 TEST(HostDeviceVector, Basic) {
114   size_t n = 1001;
115   int device = 0;
116   TestHostDeviceVector(n, device);
117 }
118 
TEST(HostDeviceVector,Copy)119 TEST(HostDeviceVector, Copy) {
120   size_t n = 1001;
121   int device = 0;
122   HostDeviceVectorSetDeviceHandler hdvec_dev_hndlr(SetDevice);
123 
124   HostDeviceVector<int> v;
125   {
126     // a separate scope to ensure that v1 is gone before further checks
127     HostDeviceVector<int> v1;
128     InitHostDeviceVector(n, device, &v1);
129     v.Resize(v1.Size());
130     v.Copy(v1);
131   }
132   CheckDevice(&v, n, 0, GPUAccess::kRead);
133   PlusOne(&v);
134   CheckDevice(&v, n, 1, GPUAccess::kWrite);
135   CheckHost(&v, GPUAccess::kRead);
136   CheckHost(&v, GPUAccess::kNone);
137 }
138 
TEST(HostDeviceVector,SetDevice)139 TEST(HostDeviceVector, SetDevice) {
140   std::vector<int> h_vec (2345);
141   for (size_t i = 0; i < h_vec.size(); ++i) {
142     h_vec[i] = i;
143   }
144   HostDeviceVector<int> vec (h_vec);
145   auto device = 0;
146 
147   vec.SetDevice(device);
148   ASSERT_EQ(vec.Size(), h_vec.size());
149   auto span = vec.DeviceSpan();  // sync to device
150 
151   vec.SetDevice(-1);  // pull back to cpu.
152   ASSERT_EQ(vec.Size(), h_vec.size());
153   ASSERT_EQ(vec.DeviceIdx(), -1);
154 
155   auto h_vec_1 = vec.HostVector();
156   ASSERT_TRUE(std::equal(h_vec_1.cbegin(), h_vec_1.cend(), h_vec.cbegin()));
157 }
158 
TEST(HostDeviceVector,Span)159 TEST(HostDeviceVector, Span) {
160   HostDeviceVector<float> vec {1.0f, 2.0f, 3.0f, 4.0f};
161   vec.SetDevice(0);
162   auto span = vec.DeviceSpan();
163   ASSERT_EQ(vec.Size(), span.size());
164   ASSERT_EQ(vec.DevicePointer(), span.data());
165   auto const_span = vec.ConstDeviceSpan();
166   ASSERT_EQ(vec.Size(), const_span.size());
167   ASSERT_EQ(vec.ConstDevicePointer(), const_span.data());
168 
169   auto h_span = vec.ConstHostSpan();
170   ASSERT_TRUE(vec.HostCanRead());
171   ASSERT_FALSE(vec.HostCanWrite());
172   ASSERT_EQ(h_span.size(), vec.Size());
173   ASSERT_EQ(h_span.data(), vec.ConstHostPointer());
174 
175   h_span = vec.HostSpan();
176   ASSERT_TRUE(vec.HostCanWrite());
177 }
178 
TEST(HostDeviceVector,Empty)179 TEST(HostDeviceVector, Empty) {
180   HostDeviceVector<float> vec {1.0f, 2.0f, 3.0f, 4.0f};
181   HostDeviceVector<float> another { std::move(vec) };
182   ASSERT_FALSE(another.Empty());
183   ASSERT_TRUE(vec.Empty());
184 }
185 
TEST(HostDeviceVector,MGPU_Basic)186 TEST(HostDeviceVector, MGPU_Basic) {  // NOLINT
187   if (AllVisibleGPUs() < 2) {
188     LOG(WARNING) << "Not testing in multi-gpu environment.";
189     return;
190   }
191 
192   size_t n = 1001;
193   int device = 1;
194   TestHostDeviceVector(n, device);
195 }
196 }  // namespace common
197 }  // namespace xgboost
198