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