Tutorial 22 - Bundles On The GPU
Bundles are not exactly data themselves, they are a representation of a collection of attributes whose composition is determined at runtime. As such, they will always live on the CPU. However the attributes they are encapsulating have the same flexibility as other attributes to live on the CPU, GPU, or have their location decided at runtime.
For that reason it’s convenient to use the same “cpu”, “cuda”, and “any” memory types for the bundle attributes, with a slightly different interpretation.
cpu all attributes in the bundle will be on the CPU
gpu all attributes in the bundle will be on the GPU
any either some attributes in the bundle are on the CPU and some are on the GPU, or that decision will be made at runtime
For example if you had a bundle of attributes consisting of a large array of points and a boolean that controls the type of operation you will perform on them it makes sense to leave the boolean on the CPU and move the points to the GPU for more efficient processing.
OgnTutorialCpuGpuBundles.ogn
The ogn file shows the implementation of a node named “omni.graph.tutorials.CpuGpuBundles” with an input bundle on the CPU, an input bundle on the GPU, and an output bundle whose memory location is decided at runtime by a boolean.
1{
2 "CpuGpuBundles": {
3 "version": 1,
4 "categories": "tutorials",
5 "description": [
6 "This is a tutorial node. It exercises functionality for accessing data in bundles that",
7 "are on the GPU as well as bundles whose CPU/GPU location is decided at runtime. The",
8 "compute looks for bundled attributes named 'points' and, if they are found, computes",
9 "their dot products. If the bundle on the output contains an integer array type named",
10 "'dotProducts' then the results are placed there, otherwise a new attribute of that name and",
11 "type is created on the output bundle to hold the results.",
12 "This node is identical to OgnTutorialCpuGpuBundlesPy.ogn, except it is implemented in C++."
13 ],
14 "tags": ["tutorial", "bundle", "gpu"],
15 "tokens": ["points", "dotProducts"],
16 "uiName": "Tutorial Node: CPU/GPU Bundles",
17 "inputs": {
18 "cpuBundle": {
19 "type": "bundle",
20 "description": "Input bundle whose data always lives on the CPU",
21 "uiName": "CPU Input Bundle"
22 },
23 "gpuBundle": {
24 "type": "bundle",
25 "memoryType": "cuda",
26 "description": "Input bundle whose data always lives on the GPU",
27 "uiName": "GPU Input Bundle"
28 },
29 "gpu": {
30 "type": "bool",
31 "description": "If true then copy gpuBundle onto the output, otherwise copy cpuBundle",
32 "uiName": "Results To GPU"
33 }
34 },
35 "outputs": {
36 "cpuGpuBundle": {
37 "type": "bundle",
38 "memoryType": "any",
39 "description": [
40 "This is the bundle with the merged data. If the 'gpu' attribute is set to true then this",
41 "bundle's contents will be entirely on the GPU, otherwise they will be on the CPU."
42 ],
43 "uiName": "Constructed Bundle"
44 }
45 }
46 }
47}
OgnTutorialCpuGpuBundles.cpp
The cpp file contains the implementation of the compute method. It creates a merged bundle in either the CPU or GPU based on the input boolean and runs an algorithm on the output location.
1// SPDX-FileCopyrightText: Copyright (c) 2021-2024 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
2// SPDX-License-Identifier: LicenseRef-NvidiaProprietary
3//
4// NVIDIA CORPORATION, its affiliates and licensors retain all intellectual
5// property and proprietary rights in and to this material, related
6// documentation and any modifications thereto. Any use, reproduction,
7// disclosure or distribution of this material and related documentation
8// without an express license agreement from NVIDIA CORPORATION or
9// its affiliates is strictly prohibited.
10#include <OgnTutorialCpuGpuBundlesDatabase.h>
11extern "C" void cpuGpuDotProductCPU(float const (*p1)[3], float const (*p2)[3], float*, size_t);
12extern "C" void cpuGpuDotProductGPU(float const (**p1)[3], float const (**p2)[3], float**, size_t);
13
14namespace omni
15{
16namespace graph
17{
18namespace tutorials
19{
20
21class OgnTutorialCpuGpuBundles
22{
23public:
24 static bool compute(OgnTutorialCpuGpuBundlesDatabase& db)
25 {
26 const auto& gpu = db.inputs.gpu();
27 // Bundles are merely abstract representations of a collection of attributes so you do not have to do anything
28 // different when they are marked for GPU, or ANY memory location.
29 const auto& cpuBundle = db.inputs.cpuBundle();
30 const auto& gpuBundle = db.inputs.gpuBundle();
31 auto& outputBundle = db.outputs.cpuGpuBundle();
32
33 // Assign the correct destination bundle to the output based on the gpu flag
34 if (gpu)
35 {
36 outputBundle = gpuBundle;
37 }
38 else
39 {
40 outputBundle = cpuBundle;
41 }
42
43 // Get the attribute references. They're the same whether the bundles are on the CPU or GPU
44 const auto pointsCpuAttribute = cpuBundle.attributeByName(db.tokens.points);
45 const auto pointsGpuAttribute = gpuBundle.attributeByName(db.tokens.points);
46 auto dotProductAttribute = outputBundle.attributeByName(db.tokens.dotProducts);
47 if (!dotProductAttribute.isValid())
48 {
49 dotProductAttribute = outputBundle.addAttribute(db.tokens.dotProducts, Type(BaseDataType::eFloat, 1, 1));
50 }
51
52 // Find the bundle contents to be processed
53 if (gpu)
54 {
55 const auto points1 = pointsCpuAttribute.getGpu<float[][3]>();
56 const auto points2 = pointsGpuAttribute.get<float[][3]>();
57 auto dotProducts = dotProductAttribute.getGpu<float[]>();
58 if (!points1)
59 {
60 db.logWarning("Skipping compute - No valid float[3][] attribute named '%s' on the CPU bundle",
61 db.tokenToString(db.tokens.points));
62 return false;
63 }
64 if (!points2)
65 {
66 db.logWarning("Skipping compute - No valid float[3][] attribute named '%s' on the GPU bundle",
67 db.tokenToString(db.tokens.points));
68 return false;
69 }
70 if (points1.size() != points2.size())
71 {
72 db.logWarning("Skipping compute - Point arrays are different sizes (%zu and %zu)", points1.size(),
73 points2.size());
74 return false;
75 }
76 dotProducts.resize(points1.size());
77 if (!dotProducts)
78 {
79 db.logWarning("Skipping compute - No valid float[] attribute named '%s' on the output bundle",
80 db.tokenToString(db.tokens.dotProducts));
81 return false;
82 }
83 cpuGpuDotProductGPU(points1(), points2(), dotProducts(), points1.size());
84 }
85 else
86 {
87 const auto points1 = pointsCpuAttribute.get<float[][3]>();
88 const auto points2 = pointsGpuAttribute.getCpu<float[][3]>();
89 auto dotProducts = dotProductAttribute.getCpu<float[]>();
90 if (!points1)
91 {
92 db.logWarning("Skipping compute - No valid float[3][] attribute named '%s' on the CPU bundle",
93 db.tokenToString(db.tokens.points));
94 return false;
95 }
96 if (!points2)
97 {
98 db.logWarning("Skipping compute - No valid float[3][] attribute named '%s' on the GPU bundle",
99 db.tokenToString(db.tokens.points));
100 return false;
101 }
102 if (points1.size() != points2.size())
103 {
104 db.logWarning("Skipping compute - Point arrays are different sizes (%zu and %zu)", points1.size(),
105 points2.size());
106 return false;
107 }
108 dotProducts.resize(points1.size());
109 if (!dotProducts)
110 {
111 db.logWarning("Skipping compute - No valid dot product attribute on the output bundle");
112 return false;
113 }
114 cpuGpuDotProductCPU(points1->data(), points2->data(), dotProducts->data(), points1.size());
115 }
116 return true;
117 }
118};
119
120REGISTER_OGN_NODE()
121
122} // namespace tutorials
123} // namespace graph
124} // namespace omni
OgnTutorialCpuGpuBundlesPy.py
The py file contains the same algorithm as the C++ node, with the node implementation language being different.
1"""
2Implementation of the Python node accessing attributes whose memory location is determined at runtime.
3"""
4
5import numpy as np
6import omni.graph.core as og
7
8# Types to check on bundled attributes
9FLOAT_ARRAY_TYPE = og.Type(og.BaseDataType.FLOAT, array_depth=1)
10FLOAT3_ARRAY_TYPE = og.Type(og.BaseDataType.FLOAT, tuple_count=3, array_depth=1, role=og.AttributeRole.POSITION)
11
12
13class OgnTutorialCpuGpuBundlesPy:
14 """Exercise bundle members through a Python OmniGraph node"""
15
16 @staticmethod
17 def compute(db) -> bool:
18 """Implements the same algorithm as the C++ node OgnTutorialCpuGpuBundles.cpp.
19
20 It follows the same code pattern for easier comparison, though in practice you would probably code Python
21 nodes differently from C++ nodes to take advantage of the strengths of each language.
22 """
23 if db.inputs.gpu:
24 # Invalid data yields no compute
25 if not db.inputs.gpuBundle.valid:
26 return True
27 db.outputs.cpuGpuBundle = db.inputs.gpuBundle
28 else:
29 if not db.inputs.cpuBundle.valid:
30 return True
31 db.outputs.cpuGpuBundle = db.inputs.cpuBundle
32
33 # Find and verify the attributes containing the points
34 cpu_points = db.inputs.cpuBundle.attribute_by_name(db.tokens.points)
35 if cpu_points.type != FLOAT3_ARRAY_TYPE:
36 db.log_warning(
37 f"Skipping compute - No valid float[3][] attribute named '{db.tokens.points}' on the CPU bundle"
38 )
39 return False
40 gpu_points = db.inputs.gpuBundle.attribute_by_name(db.tokens.points)
41 if gpu_points.type != FLOAT3_ARRAY_TYPE:
42 db.log_warning(
43 f"Skipping compute - No valid float[3][] attribute named '{db.tokens.points}' on the GPU bundle"
44 )
45 return False
46
47 # If the attribute is not already on the output bundle then add it
48 dot_product = db.outputs.cpuGpuBundle.attribute_by_name(db.tokens.dotProducts)
49 if dot_product is None:
50 dot_product = db.outputs.cpuGpuBundle.insert((og.Type(og.BaseDataType.FLOAT, array_depth=1), "dotProducts"))
51 elif dot_product.type != FLOAT_ARRAY_TYPE:
52 # Python types do not use a cast to find out if they are the correct type so explicitly check it instead
53 db.log_warning(
54 f"Skipping compute - No valid float[] attribute named '{db.tokens.dotProducts}' on the output bundle"
55 )
56 return False
57
58 # Set the size to what is required for the dot product calculation
59 dot_product.size = cpu_points.size
60
61 # Use the correct data access based on whether the output is supposed to be on the GPU or not
62 if db.inputs.gpu:
63 # The second line is how the values would be extracted if Python supported GPU data extraction.
64 # When it does this tutorial will be updated
65 dot_product.cpu_value = np.einsum("ij,ij->i", cpu_points.value, gpu_points.cpu_value)
66 # dot_product.gpu_value = np.einsum("ij,ij->i", cpu_points.gpu_value, gpu_points.value)
67 else:
68 dot_product.cpu_value = np.einsum("ij,ij->i", cpu_points.value, gpu_points.cpu_value)
69
70 return True