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