/home/runner/work/HiCR/HiCR/include/hicr/backends/hwloc/computeResource.hpp Source File

HiCR: /home/runner/work/HiCR/HiCR/include/hicr/backends/hwloc/computeResource.hpp Source File
HiCR
computeResource.hpp
Go to the documentation of this file.
1/*
2 * Copyright 2025 Huawei Technologies Co., Ltd.
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
24#pragma once
25
26#include <unordered_set>
27#include <hwloc.h>
29#include <hicr/core/definitions.hpp>
32
33namespace HiCR::backend::hwloc
34{
35
41{
42 public:
43
47 using logicalProcessorId_t = unsigned int;
48
52 using physicalProcessorId_t = unsigned int;
53
57 using numaAffinity_t = unsigned int;
58
64 ComputeResource(hwloc_topology_t topology, const logicalProcessorId_t logicalProcessorId)
65 : HiCR::ComputeResource(),
66 _logicalProcessorId(logicalProcessorId),
67 _physicalProcessorId(detectPhysicalProcessorId(topology, logicalProcessorId)),
68 _numaAffinity(detectCoreNUMAffinity(topology, logicalProcessorId)),
69 _caches(detectCpuCaches(topology, logicalProcessorId)){};
70
78 ComputeResource(const logicalProcessorId_t logicalProcessorId,
79 const physicalProcessorId_t physicalProcessorId,
80 const numaAffinity_t numaAffinity,
81 std::unordered_set<std::shared_ptr<backend::hwloc::Cache>> caches)
82 : HiCR::ComputeResource(),
83 _logicalProcessorId(logicalProcessorId),
84 _physicalProcessorId(physicalProcessorId),
85 _numaAffinity(numaAffinity),
86 _caches(std::move(caches)){};
87 ~ComputeResource() override = default;
88
92 ComputeResource() = default;
93
94 __INLINE__ std::string getType() const override { return "Processing Unit"; }
95
101 __INLINE__ logicalProcessorId_t getProcessorId() const { return _logicalProcessorId; }
102
109 __INLINE__ physicalProcessorId_t getPhysicalProcessorId() const { return _physicalProcessorId; }
110
119 ComputeResource(const nlohmann::json &input) { deserialize(input); }
120
129 __INLINE__ static void detectThreadPUs(hwloc_topology_t topology, hwloc_obj_t obj, int depth, std::vector<logicalProcessorId_t> &threadPUs)
130 {
131 if (obj->arity == 0) threadPUs.push_back(obj->logical_index);
132 for (unsigned int i = 0; i < obj->arity; i++) detectThreadPUs(topology, obj->children[i], depth + 1, threadPUs);
133 }
134
142 __INLINE__ static physicalProcessorId_t detectPhysicalProcessorId(hwloc_topology_t topology, const logicalProcessorId_t logicalProcessorId)
143 {
144 hwloc_obj_t obj = hwloc_get_obj_by_type(topology, HWLOC_OBJ_PU, logicalProcessorId);
145 if (!obj) HICR_THROW_RUNTIME("Attempting to access a compute resource that does not exist (%lu) in this backend", logicalProcessorId);
146
147 // Acquire the parent core object
148 // There is an asumption here that a HWLOC_OBJ_PU type always has a parent of type HWLOC_OBJ_CORE,
149 // which is consistent with current HWloc, but maybe reconsider it.
150 obj = obj->parent;
151 if (obj->type != HWLOC_OBJ_CORE) HICR_THROW_RUNTIME("Unexpected hwloc object type while trying to access Core/CPU (%lu)", logicalProcessorId);
152
153 return obj->logical_index;
154 }
155
163 __INLINE__ static numaAffinity_t detectCoreNUMAffinity(hwloc_topology_t topology, const logicalProcessorId_t logicalProcessorId)
164 {
165 // Sanitize input? So far we only call it internally so assume ID given is safe?
166 hwloc_obj_t obj = hwloc_get_obj_by_type(topology, HWLOC_OBJ_PU, logicalProcessorId);
167
168 if (!obj) HICR_THROW_RUNTIME("Attempting to access a compute resource that does not exist (%lu) in this backend", logicalProcessorId);
169
170 size_t ret = 0;
171
172 // obj is a leaf/PU; get to its parents to discover the hwloc memory space it belongs to
173 hwloc_obj_t ancestor = obj->parent;
174 hwloc_obj_t nodeNUMA = nullptr;
175 bool found = false;
176
177 // iterate over parents until we find a memory node
178 while (ancestor && !ancestor->memory_arity) ancestor = ancestor->parent;
179
180 // iterate over potential sibling nodes (the likely behavior though is to run only once)
181 for (size_t memChild = 0; memChild < ancestor->memory_arity; memChild++)
182 {
183 if (memChild == 0)
184 nodeNUMA = ancestor->memory_first_child;
185 else if (nodeNUMA)
186 nodeNUMA = nodeNUMA->next_sibling;
187
188 if (hwloc_obj_type_is_memory(nodeNUMA->type) && hwloc_bitmap_isset(obj->nodeset, nodeNUMA->os_index))
189 {
190 found = true;
191 ret = nodeNUMA->logical_index;
192 break;
193 }
194 }
195
196 if (!found) HICR_THROW_RUNTIME("NUMA Domain not detected for compute resource (%lu)", logicalProcessorId);
197
198 return ret;
199 }
200
215 __INLINE__ static std::unordered_set<std::shared_ptr<backend::hwloc::Cache>> detectCpuCaches(hwloc_topology_t topology, const logicalProcessorId_t logicalProcessorId)
216 {
217 // Sanitize input? So far we only call it internally so assume ID given is safe?
218 hwloc_obj_t obj = hwloc_get_obj_by_type(topology, HWLOC_OBJ_PU, logicalProcessorId);
219
220 if (!obj) HICR_THROW_RUNTIME("Attempting to access a compute resource that does not exist (%lu) in this backend", logicalProcessorId);
221
222 std::unordered_set<std::shared_ptr<backend::hwloc::Cache>> ret;
223
224 // Start from 1 level above our leaf/PU
225 hwloc_obj_t cache = obj->parent;
226 while (cache)
227 {
229 std::string type;
230
231 // Check if the current object is a cache-type object
232 if (cache->type == HWLOC_OBJ_L1CACHE || cache->type == HWLOC_OBJ_L2CACHE || cache->type == HWLOC_OBJ_L3CACHE || cache->type == HWLOC_OBJ_L4CACHE ||
233 cache->type == HWLOC_OBJ_L5CACHE || cache->type == HWLOC_OBJ_L1ICACHE || cache->type == HWLOC_OBJ_L2ICACHE || cache->type == HWLOC_OBJ_L3ICACHE)
234 {
235 // In case it is a cache, deduce the level from the types HWloc supports
236 switch (cache->type)
237 {
238 case HWLOC_OBJ_L1CACHE:
239 case HWLOC_OBJ_L1ICACHE: level = Cache::cacheLevel_t::L1; break;
240 case HWLOC_OBJ_L2CACHE:
241 case HWLOC_OBJ_L2ICACHE: level = Cache::cacheLevel_t::L2; break;
242 case HWLOC_OBJ_L3CACHE:
243 case HWLOC_OBJ_L3ICACHE: level = Cache::cacheLevel_t::L3; break;
244 case HWLOC_OBJ_L4CACHE: level = Cache::cacheLevel_t::L4; break;
245 case HWLOC_OBJ_L5CACHE: level = Cache::cacheLevel_t::L5; break;
246 // We never expect to get here; this is for compiler warning suppresion
247 default: HICR_THROW_RUNTIME("Unsupported Cache level detected (%lu)", cache->type);
248 }
249
250 // Storage for cache type
251 std::string type = "Unknown";
252
253 // Discover the type: Instruction, Data or Unified
254 switch (cache->attr->cache.type)
255 {
256 case HWLOC_OBJ_CACHE_UNIFIED: type = "Unified"; break;
257 case HWLOC_OBJ_CACHE_INSTRUCTION: type = "Instruction"; break;
258 case HWLOC_OBJ_CACHE_DATA: type = "Data"; break;
259 }
260
261 // Storage for more cache information
262 const bool shared = cache->arity > 1;
263 const auto size = cache->attr->cache.size;
264 const auto lineSize = cache->attr->cache.linesize;
265
266 // Insert element to our return container
267 ret.insert(std::make_shared<backend::hwloc::Cache>(level, type, size, lineSize, shared));
268 }
269
270 // Repeat the search 1 level above
271 cache = cache->parent;
272 }
273
274 return ret;
275 }
276
284 __INLINE__ static numaAffinity_t getCpuNumaAffinity(hwloc_topology_t topology, const logicalProcessorId_t logicalProcessorId)
285 {
286 // Sanitize input? So far we only call it internally so assume ID given is safe?
287 hwloc_obj_t obj = hwloc_get_obj_by_type(topology, HWLOC_OBJ_PU, logicalProcessorId);
288
289 if (!obj) HICR_THROW_RUNTIME("Attempting to access a compute resource that does not exist (%lu) in this backend", logicalProcessorId);
290
291 numaAffinity_t ret = 0;
292
293 // obj is a leaf/PU; get to its parents to discover the hwloc memory space it belongs to
294 hwloc_obj_t ancestor = obj->parent;
295 hwloc_obj_t nodeNUMA = nullptr;
296 bool found = false;
297
298 // iterate over parents until we find a memory node
299 while (ancestor && !ancestor->memory_arity) ancestor = ancestor->parent;
300
301 // iterate over potential sibling nodes (the likely behavior though is to run only once)
302 for (size_t memChild = 0; memChild < ancestor->memory_arity; memChild++)
303 {
304 if (memChild == 0)
305 nodeNUMA = ancestor->memory_first_child;
306 else if (nodeNUMA)
307 nodeNUMA = nodeNUMA->next_sibling;
308
309 if (hwloc_obj_type_is_memory(nodeNUMA->type) && hwloc_bitmap_isset(obj->nodeset, nodeNUMA->os_index))
310 {
311 found = true;
312 ret = (numaAffinity_t)nodeNUMA->logical_index;
313 break;
314 }
315 }
316
317 if (found == false) HICR_THROW_RUNTIME("NUMA Domain not detected for compute resource (%lu)", logicalProcessorId);
318
319 return ret;
320 }
321
322 protected:
323
324 __INLINE__ void serializeImpl(nlohmann::json &output) const override
325 {
326 // Writing core's information into the serialized object
327 output["Logical Processor Id"] = _logicalProcessorId;
328 output["Physical Processor Id"] = _physicalProcessorId;
329 output["NUMA Affinity"] = _numaAffinity;
330
331 // Writing Cache information
332 std::string cachesKey = "Caches";
333 output[cachesKey] = std::vector<nlohmann::json>();
334 for (const auto &cache : _caches) output[cachesKey] += cache->serialize();
335 }
336
337 __INLINE__ void deserializeImpl(const nlohmann::json &input) override
338 {
339 std::string key = "Logical Processor Id";
340 if (input.contains(key) == false) HICR_THROW_LOGIC("The serialized object contains no '%s' key", key.c_str());
341 if (input[key].is_number() == false) HICR_THROW_LOGIC("The '%s' entry is not a number", key.c_str());
342 _logicalProcessorId = input[key].get<logicalProcessorId_t>();
343
344 key = "Physical Processor Id";
345 if (input.contains(key) == false) HICR_THROW_LOGIC("The serialized object contains no '%s' key", key.c_str());
346 if (input[key].is_number() == false) HICR_THROW_LOGIC("The '%s' entry is not a number", key.c_str());
347 _physicalProcessorId = input[key].get<physicalProcessorId_t>();
348
349 key = "NUMA Affinity";
350 if (input.contains(key) == false) HICR_THROW_LOGIC("The serialized object contains no '%s' key", key.c_str());
351 if (input[key].is_number() == false) HICR_THROW_LOGIC("The '%s' entry is not a number", key.c_str());
352 _numaAffinity = input[key].get<numaAffinity_t>();
353
354 key = "Caches";
355 if (input.contains(key) == false) HICR_THROW_LOGIC("The serialized object contains no '%s' key", key.c_str());
356 if (input[key].is_array() == false) HICR_THROW_LOGIC("The '%s' entry is not an array", key.c_str());
357
358 _caches.clear();
359 for (const auto &c : input[key])
360 {
361 // Deserializing cache
362 auto cache = std::make_shared<backend::hwloc::Cache>(c);
363
364 // Adding it to the list
365 _caches.insert(cache);
366 }
367 }
368
369 private:
370
374 logicalProcessorId_t _logicalProcessorId{};
375
381 physicalProcessorId_t _physicalProcessorId{};
382
386 numaAffinity_t _numaAffinity{};
387
392 std::unordered_set<std::shared_ptr<backend::hwloc::Cache>> _caches;
393};
394
395} // namespace HiCR::backend::hwloc
Defines the Cache class for interacting with the host (CPUs) device type.
Definition computeResource.hpp:39
__INLINE__ void deserialize(const nlohmann::json &input)
Definition computeResource.hpp:79
cacheLevel_t
Definition cache.hpp:46
@ L1
Cache Level L1.
Definition cache.hpp:48
@ L3
Cache Level L3.
Definition cache.hpp:54
@ L2
Cache Level L2.
Definition cache.hpp:51
@ L4
Cache Level L4.
Definition cache.hpp:57
@ L5
Cache Level L5.
Definition cache.hpp:60
Definition computeResource.hpp:41
__INLINE__ void serializeImpl(nlohmann::json &output) const override
Definition computeResource.hpp:324
__INLINE__ void deserializeImpl(const nlohmann::json &input) override
Definition computeResource.hpp:337
ComputeResource(hwloc_topology_t topology, const logicalProcessorId_t logicalProcessorId)
Definition computeResource.hpp:64
unsigned int logicalProcessorId_t
Definition computeResource.hpp:47
static __INLINE__ physicalProcessorId_t detectPhysicalProcessorId(hwloc_topology_t topology, const logicalProcessorId_t logicalProcessorId)
Definition computeResource.hpp:142
static __INLINE__ std::unordered_set< std::shared_ptr< backend::hwloc::Cache > > detectCpuCaches(hwloc_topology_t topology, const logicalProcessorId_t logicalProcessorId)
Definition computeResource.hpp:215
unsigned int numaAffinity_t
Definition computeResource.hpp:57
unsigned int physicalProcessorId_t
Definition computeResource.hpp:52
ComputeResource(const logicalProcessorId_t logicalProcessorId, const physicalProcessorId_t physicalProcessorId, const numaAffinity_t numaAffinity, std::unordered_set< std::shared_ptr< backend::hwloc::Cache > > caches)
Definition computeResource.hpp:78
static __INLINE__ numaAffinity_t detectCoreNUMAffinity(hwloc_topology_t topology, const logicalProcessorId_t logicalProcessorId)
Definition computeResource.hpp:163
__INLINE__ logicalProcessorId_t getProcessorId() const
Definition computeResource.hpp:101
__INLINE__ physicalProcessorId_t getPhysicalProcessorId() const
Definition computeResource.hpp:109
static __INLINE__ numaAffinity_t getCpuNumaAffinity(hwloc_topology_t topology, const logicalProcessorId_t logicalProcessorId)
Definition computeResource.hpp:284
ComputeResource(const nlohmann::json &input)
Definition computeResource.hpp:119
static __INLINE__ void detectThreadPUs(hwloc_topology_t topology, hwloc_obj_t obj, int depth, std::vector< logicalProcessorId_t > &threadPUs)
Definition computeResource.hpp:129
__INLINE__ std::string getType() const override
Definition computeResource.hpp:94
Provides a base definition for a HiCR ComputeResource class.
Provides a failure model and corresponding exception classes.
#define HICR_THROW_RUNTIME(...)
Definition exceptions.hpp:74
#define HICR_THROW_LOGIC(...)
Definition exceptions.hpp:67