node.hpp
Go to the documentation of this file.
1 /*
2  * Copyright (c) 2023-2024, NVIDIA CORPORATION.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 #pragma once
17 
21 
22 #include <iostream>
23 #include <type_traits>
24 
25 namespace ML {
26 namespace experimental {
27 namespace fil {
28 
29 namespace detail {
30 
31 /*
32  * Return the byte size to which a node with the given types should be aligned
33  */
34 template <typename threshold_t, typename index_t, typename metadata_storage_t, typename offset_t>
35 auto constexpr get_node_alignment()
36 {
37  auto total = index_type(std::max(sizeof(threshold_t), sizeof(index_t)) +
38  sizeof(metadata_storage_t) + sizeof(offset_t));
39  auto result = index_type{8};
40  if (total > result) { result = index_type{16}; }
41  if (total > result) { result = index_type{32}; }
42  if (total > result) { result = index_type{64}; }
43  if (total > result) { result = index_type{128}; }
44  if (total > result) { result = index_type{256}; }
45  if (total > result) { result = total; }
46  return result;
47 }
48 
49 } // namespace detail
50 
51 /* @brief A single node in a forest model
52  *
53  * Note that this implementation includes NO error checking for poorly-chosen
54  * template types. If the types are not large enough to hold the required data,
55  * an incorrect node will be silently constructed. Error checking occurs
56  * instead at the time of construction of the entire forest.
57  *
58  * @tparam layout_v The layout for nodes within the forest
59  *
60  * @tparam threshold_t The type used as a threshold for evaluating a non-leaf
61  * node or (when possible) the output of a leaf node. For non-categorical
62  * nodes, if an input value is less than this threshold, the node evaluates to
63  * true. For leaf nodes, output values will only be stored as this type if it
64  * matches the leaf output type expected by the forest. Typically, this type is
65  * either float or double.
66  *
67  * @tparam index_t The type used as an index to the output data for leaf nodes,
68  * or to the categorical set for a categorical non-leaf node. This type should
69  * be large enough to index the entire array of output data or categorical sets
70  * stored in the forest. Typically, this type is either uint32_t or uint64_t.
71  * Smaller types offer no benefit, since this value is stored in a union with
72  * threshold_t, which is at least 32 bits.
73  *
74  * @tparam metadata_storage_t An unsigned integral type used for a bit-wise
75  * representation of metadata about this node. The first three bits encode
76  * whether or not this is a leaf node, whether or not we should default to the
77  * more distant child in case of missing values, and whether or not this node
78  * is categorical. The remaining bits are used to encode the feature index for
79  * this node. Thus, uint8_t may be used for 2**(8 - 3) = 32 or fewer features,
80  * uint16_t for 2**(16 - 3) = 8192 or fewer, and uint32_t for 536870912 or
81  * fewer features.
82  *
83  * @tparam offset_t An integral type used to indicate the offset from
84  * this node to its most distant child. This type must be large enough to store
85  * the largest such offset in the forest model.
86  */
87 template <tree_layout layout_v,
88  typename threshold_t,
89  typename index_t,
90  typename metadata_storage_t,
91  typename offset_t>
92 struct alignas(
94  detail::get_node_alignment<threshold_t, index_t, metadata_storage_t, offset_t>()) node {
95  // @brief An alias for layout_v
96  auto constexpr static const layout = layout_v;
97  // @brief An alias for threshold_t
98  using threshold_type = threshold_t;
99  // @brief An alias for index_t
100  using index_type = index_t;
101  /* @brief A union to hold either a threshold value or index
102  *
103  * All nodes will need EITHER a threshold value, an output value, OR an index
104  * to data elsewhere that wil be used either for evaluating the node (e.g. an
105  * index to a categorical set) or creating an output (e.g. an index to vector
106  * leaf output). This union allows us to store either of these values without
107  * using additional space for the unused value.
108  */
109  union value_type {
110  threshold_t value;
111  index_t index;
112  };
114  using metadata_storage_type = metadata_storage_t;
116  using offset_type = offset_t;
117 
118  // TODO(wphicks): Add custom type to ensure given child offset is at least
119  // one
120 #pragma GCC diagnostic push
121 #pragma GCC diagnostic ignored "-Wnarrowing"
122  HOST DEVICE constexpr node(threshold_type value = threshold_type{},
123  bool is_leaf_node = true,
124  bool default_to_distant_child = false,
125  bool is_categorical_node = false,
127  offset_type distant_child_offset = offset_type{})
128  : aligned_data{
129  .inner_data = {
130  {.value = value},
131  distant_child_offset,
132  construct_metadata(is_leaf_node, default_to_distant_child, is_categorical_node, feature)}}
133  {
134  }
135 
136  HOST DEVICE constexpr node(index_type index,
137  bool is_leaf_node = true,
138  bool default_to_distant_child = false,
139  bool is_categorical_node = false,
141  offset_type distant_child_offset = offset_type{})
142  : aligned_data{
143  .inner_data = {
144  {.index = index},
145  distant_child_offset,
146  construct_metadata(is_leaf_node, default_to_distant_child, is_categorical_node, feature)}}
147  {
148  }
149 #pragma GCC diagnostic pop
150 
151  /* The index of the feature for this node */
152  HOST DEVICE auto constexpr feature_index() const
153  {
154  return aligned_data.inner_data.metadata & FEATURE_MASK;
155  }
156  /* Whether or not this node is a leaf node */
157  HOST DEVICE auto constexpr is_leaf() const
158  {
159  return !bool(aligned_data.inner_data.distant_offset);
160  }
161  /* Whether or not to default to distant child in case of missing values */
162  HOST DEVICE auto constexpr default_distant() const
163  {
164  return bool(aligned_data.inner_data.metadata & DEFAULT_DISTANT_MASK);
165  }
166  /* Whether or not this node is a categorical node */
167  HOST DEVICE auto constexpr is_categorical() const
168  {
169  return bool(aligned_data.inner_data.metadata & CATEGORICAL_MASK);
170  }
171  /* The offset to the child of this node if it evaluates to given condition */
172  HOST DEVICE auto constexpr child_offset(bool condition) const
173  {
174  if constexpr (layout == tree_layout::depth_first) {
175  return offset_type{1} + condition * (aligned_data.inner_data.distant_offset - offset_type{1});
176  } else if constexpr (layout == tree_layout::breadth_first) {
177  return condition * offset_type{1} + (aligned_data.inner_data.distant_offset - offset_type{1});
178  } else {
179  static_assert(layout == tree_layout::depth_first);
180  }
181  }
182  /* The threshold value for this node */
183  HOST DEVICE auto constexpr threshold() const
184  {
185  return aligned_data.inner_data.stored_value.value;
186  }
187 
188  /* The index value for this node */
189  HOST DEVICE auto const& index() const { return aligned_data.inner_data.stored_value.index; }
190  /* The output value for this node
191  *
192  * @tparam output_t The expected output type for this node.
193  */
194  template <bool has_vector_leaves>
195  HOST DEVICE auto constexpr output() const
196  {
197  if constexpr (has_vector_leaves) {
198  return aligned_data.inner_data.stored_value.index;
199  } else {
200  return aligned_data.inner_data.stored_value.value;
201  }
202  }
203 
204  private:
205  /* Define all bit masks required to extract information from the stored
206  * metadata. The first bit tells us whether or not this is a leaf node, the
207  * second tells us whether or not we should default to the distant child in
208  * the case of a missing value, and the third tells us whether or not this is
209  * a categorical node. The remaining bits indicate the index of the feature
210  * for this node */
211  auto constexpr static const LEAF_BIT =
213  auto constexpr static const LEAF_MASK = metadata_storage_type(1 << LEAF_BIT);
214  auto constexpr static const DEFAULT_DISTANT_BIT = metadata_storage_type(LEAF_BIT - 1);
215  auto constexpr static const DEFAULT_DISTANT_MASK =
216  metadata_storage_type(1 << DEFAULT_DISTANT_BIT);
217  auto constexpr static const CATEGORICAL_BIT = metadata_storage_type(DEFAULT_DISTANT_BIT - 1);
218  auto constexpr static const CATEGORICAL_MASK = metadata_storage_type(1 << CATEGORICAL_BIT);
219  auto constexpr static const FEATURE_MASK =
220  metadata_storage_type(~(LEAF_MASK | DEFAULT_DISTANT_MASK | CATEGORICAL_MASK));
221 
222  // Helper function for bit packing with the above masks
223  auto static constexpr construct_metadata(bool is_leaf_node = true,
224  bool default_to_distant_child = false,
225  bool is_categorical_node = false,
227  {
228  return metadata_storage_type(
229  (is_leaf_node << LEAF_BIT) + (default_to_distant_child << DEFAULT_DISTANT_BIT) +
230  (is_categorical_node << CATEGORICAL_BIT) + (feature & FEATURE_MASK));
231  }
232 
233  auto static constexpr const byte_size =
234  detail::get_node_alignment<threshold_t, index_t, metadata_storage_t, offset_t>();
235 
236  struct inner_data_type {
237  value_type stored_value;
238  // TODO (wphicks): It may be possible to store both of the following together
239  // to save bytes
240  offset_type distant_offset;
241  metadata_storage_type metadata;
242  };
243  union aligned_data_type {
244  inner_data_type inner_data;
245  char spacer_data[byte_size];
246  };
247 
248  aligned_data_type aligned_data;
249 };
250 
251 } // namespace fil
252 } // namespace experimental
} // namespace ML
#define DEVICE
Definition: gpu_support.hpp:35
#define HOST
Definition: gpu_support.hpp:34
math_t max(math_t a, math_t b)
Definition: learning_rate.h:27
constexpr auto get_node_alignment()
Definition: node.hpp:35
tree_layout
Definition: tree_layout.hpp:20
uint32_t index_type
Definition: index_type.hpp:21
Definition: dbscan.hpp:30
Definition: node.hpp:93
HOST DEVICE constexpr auto default_distant() const
Definition: node.hpp:161
HOST DEVICE constexpr auto threshold() const
Definition: node.hpp:182
HOST DEVICE constexpr auto is_categorical() const
Definition: node.hpp:166
HOST DEVICE constexpr auto child_offset(bool condition) const
Definition: node.hpp:171
HOST DEVICE constexpr auto output() const
Definition: node.hpp:194
threshold_t threshold_type
Definition: node.hpp:97
metadata_storage_t metadata_storage_type
An alias for metadata_storage_t.
Definition: node.hpp:113
HOST DEVICE constexpr auto is_leaf() const
Definition: node.hpp:156
HOST DEVICE auto const & index() const
Definition: node.hpp:188
HOST DEVICE constexpr auto feature_index() const
Definition: node.hpp:151
HOST constexpr DEVICE node(threshold_type value=threshold_type{}, bool is_leaf_node=true, bool default_to_distant_child=false, bool is_categorical_node=false, metadata_storage_type feature=metadata_storage_type{}, offset_type distant_child_offset=offset_type{})
Definition: node.hpp:121
index_t index_type
Definition: node.hpp:99
constexpr static auto const layout
Definition: node.hpp:95
offset_t offset_type
An alias for offset_t.
Definition: node.hpp:115
threshold_t value
Definition: node.hpp:109
index_t index
Definition: node.hpp:110