自带模型 (BYOM)
Holoscan 平台针对执行 AI 推理工作流进行了优化。本节介绍用户如何轻松修改 bring_your_own_model
示例来创建自己的 AI 应用程序。
在本示例中,我们将介绍
使用
FormatConverterOp
、InferenceOp
、SegmentationPostprocessorOp
算子将 AI 推理添加到工作流中。如何修改本示例中的现有代码以创建超声分割应用程序,从而可视化脊柱侧弯分割模型的结果。
示例源代码和运行说明可以在 GitHub 上的 examples 目录中找到,或者在 NGC 容器和 Debian 软件包中的 /opt/nvidia/holoscan/examples
下找到,以及它们的可执行文件。
下图显示了 byom.py 示例中使用的算子和工作流。

图 10 BYOM 推理工作流
示例代码已包含创建上述管道所需的管道,其中视频由 VideoStreamReplayer
加载并传递到两个分支。第一个分支直接进入 Holoviz
以显示原始视频。此工作流中的第二个分支通过 AI 推理,可用于生成叠加层,例如边界框、分割掩码或文本,以添加其他信息。
第二个分支有三个我们尚未遇到的算子。
格式转换器:输入视频流经过预处理阶段,将张量转换为适当的形状/格式,然后再馈送到 AI 模型中。此处用于将图像的数据类型从
uint8
转换为float32
,并调整大小以匹配模型的期望。推理:此算子使用提供的模型对输入视频流执行 AI 推理。它支持对多个输入视频流和模型进行推理。
分割后处理器:此后处理阶段获取推理的输出,可以是最终的 softmax 层(多类)或 sigmoid(2 类),并发出一个包含最高概率类别索引的
uint8
值的张量。分割后处理器的输出随后被馈送到 Holoviz 可视化器以创建叠加层。
要跟随本示例,您可以运行以下命令下载超声数据集
./scripts/download_ngc_data --url nvidia/clara-holoscan/holoscan_ultrasound_sample_data:20220608
您还可以根据您自己的输入视频和模型调整算子参数,并将您的视频和模型转换为 Holoscan 可以理解的格式,从而使用您自己的数据集进行操作。
输入视频
视频流重放器支持读取编码为 GXF 实体的视频文件。这些文件与超声数据集一起提供,作为 ultrasound_256x256.gxf_entities
和 ultrasound_256x256.gxf_index
文件。
要使用您自己的视频数据,您可以使用 convert_video_to_gxf_entities.py
脚本(安装在 /opt/nvidia/holoscan/bin
中或 在 GitHub 上)来编码您的视频。请注意 - 使用此脚本 - 生成的 GXF 张量文件中的元数据将指示应在读取时将数据复制到 GPU。
输入模型
目前,Holoscan 中的推理算子能够加载 ONNX 模型,或为将运行模型的 GPU 架构构建的 TensorRT 引擎文件。TensorRT 引擎在应用程序运行时从 ONNX 自动生成。
如果您要将模型从 PyTorch 转换为 ONNX,则您的输入很可能是 NCHW 格式,并且需要转换为 NHWC 格式。我们提供了一个名为 graph_surgeon.py
的示例转换脚本,安装在 /opt/nvidia/holoscan/bin
中或在 GitHub 上 提供。您可能需要根据需要修改尺寸,然后再修改模型。
为了更好地理解您的模型,以及此步骤是否必要,可以使用 netron.app 等网站。
在修改应用程序之前,让我们看一下现有代码,以更好地了解其工作原理。
import os
from argparse import ArgumentParser
from holoscan.core import Application
from holoscan.operators import (
FormatConverterOp,
HolovizOp,
InferenceOp,
SegmentationPostprocessorOp,
VideoStreamReplayerOp,
)
from holoscan.resources import UnboundedAllocator
class BYOMApp(Application):
def __init__(self, data):
"""Initialize the application
Parameters
----------
data : Location to the data
"""
super().__init__()
# set name
self.name = "BYOM App"
if data == "none":
data = os.environ.get("HOLOSCAN_INPUT_PATH", "../data")
self.sample_data_path = data
self.model_path = os.path.join(os.path.dirname(__file__), "../model")
self.model_path_map = {
"byom_model": os.path.join(self.model_path, "identity_model.onnx"),
}
self.video_dir = os.path.join(self.sample_data_path, "racerx")
if not os.path.exists(self.video_dir):
raise ValueError(f"Could not find video data:{self.video_dir=}")
内置的
FormatConvertOp
、InferenceOp
和SegmentationPostprocessorOp
算子在第7
、9
和10
行导入。这 3 个算子分别构成了我们 AI 管道的预处理、推理和后处理阶段。UnboundedAllocator
资源在第13
行导入。我们的应用程序的算子使用它进行内存分配。identity
模型的路径在第35-38
行定义。此模型将其输入张量传递给其输出,并充当本示例的占位符。视频文件的目录在第
40
行定义。
接下来,我们看一下应用程序 YAML 文件中定义的算子及其参数。
def compose(self):
host_allocator = UnboundedAllocator(self, name="host_allocator")
source = VideoStreamReplayerOp(
self, name="replayer", directory=self.video_dir, **self.kwargs("replayer")
)
preprocessor = FormatConverterOp(
self, name="preprocessor", pool=host_allocator, **self.kwargs("preprocessor")
)
inference = InferenceOp(
self,
name="inference",
allocator=host_allocator,
model_path_map=self.model_path_map,
**self.kwargs("inference"),
)
postprocessor = SegmentationPostprocessorOp(
self, name="postprocessor", allocator=host_allocator, **self.kwargs("postprocessor")
)
viz = HolovizOp(self, name="viz", **self.kwargs("viz"))
UnboundedAllocator
资源类的实例被创建(第2
行),并由后续算子用于内存分配。此分配器根据需要在主机上动态分配内存。对于延迟成为问题的应用程序,可以使用支持内存池的分配器,例如BlockMemoryPool
或RMMAllocator
。预处理器算子(第
8
行)负责将来自源视频的输入视频转换为 AI 模型可以使用的格式。推理算子(第
12
行)将来自预处理器的输出馈送到 AI 模型以执行推理。后处理器算子(第
20
行)对来自推理算子的输出进行后处理,然后将其向下游传递到可视化器。在这里,分割后处理器检查模型输出的概率,以确定哪个类别最有可能,并发出此类别索引。然后,Holoviz
算子使用它来创建分割掩码叠加层。
%YAML 1.2
replayer: # VideoStreamReplayer
basename: "racerx"
frame_rate: 0 # as specified in timestamps
repeat: true # default: false
realtime: true # default: true
count: 0 # default: 0 (no frame count restriction)
preprocessor: # FormatConverter
out_tensor_name: source_video
out_dtype: "float32"
resize_width: 512
resize_height: 512
inference: # Inference
backend: "trt"
pre_processor_map:
"byom_model": ["source_video"]
inference_map:
"byom_model": ["output"]
postprocessor: # SegmentationPostprocessor
in_tensor_name: output
# network_output_type: None
data_format: nchw
viz: # Holoviz
width: 854
height: 480
color_lut: [
[0.65, 0.81, 0.89, 0.1],
]
预处理器将张量转换为
float32
值(第11
行),并确保图像调整大小为 512x512(第12-13
行)。pre_processor_map
参数(第17-18
行)将模型名称映射到输入张量名称。此处,“source_video”与预处理器的输出张量名称匹配(第10
行)。inference_map
参数将模型名称映射到输出张量名称。此处,“output”与后处理器的输入张量名称匹配(第23
行)。有关InferenceOp
参数的更多详细信息,请参阅 自定义推理算子 或参考 推理。network_output_type
参数在第24
行被注释掉,以提醒我们自己,第二个分支目前没有生成任何有趣的东西。如果未指定,则此参数对于SegmentationPostprocessorOp
默认为 “softmax”。在第
30-32
行定义的颜色查找表在此处用于创建分割掩码叠加层。表中的每个条目的值都是介于 0.0 和 1.0 之间的 RGBA 值。对于 alpha 值,0.0 是完全透明,1.0 是完全不透明。
最后,我们定义应用程序和工作流。
# Define the workflow
self.add_flow(source, viz, {("output", "receivers")})
self.add_flow(source, preprocessor, {("output", "source_video")})
self.add_flow(preprocessor, inference, {("tensor", "receivers")})
self.add_flow(inference, postprocessor, {("transmitter", "in_tensor")})
self.add_flow(postprocessor, viz, {("out_tensor", "receivers")})
def main(config_file, data):
app = BYOMApp(data=data)
# if the --config command line argument was provided, it will override this config_file
app.config(config_file)
app.run()
if __name__ == "__main__":
# Parse args
parser = ArgumentParser(description="BYOM demo application.")
parser.add_argument(
"-d",
"--data",
default="none",
help=("Set the data path"),
)
args = parser.parse_args()
config_file = os.path.join(os.path.dirname(__file__), "byom.yaml")
main(config_file=config_file, data=args.data)
第
2
行的add_flow()
定义了第一个分支以显示原始视频。第
3-6
行的add_flow()
命令定义了第二个分支以显示分割掩码叠加层。
要创建超声分割应用程序,我们需要更换输入视频和模型以使用超声文件,并调整参数以确保输入视频正确调整大小以符合模型的期望。
我们将需要修改 Python 和 YAML 文件,以将我们的应用程序更改为超声分割应用程序。
class BYOMApp(Application):
def __init__(self, data):
"""Initialize the application
Parameters
----------
data : Location to the data
"""
super().__init__()
# set name
self.name = "BYOM App"
if data == "none":
data = os.environ.get("HOLOSCAN_INPUT_PATH", "../data")
self.sample_data_path = data
self.model_path = os.path.join(self.sample_data_path, "ultrasound_segmentation", "model")
self.model_path_map = {
"byom_model": os.path.join(self.model_path, "us_unet_256x256_nhwc.onnx"),
}
self.video_dir = os.path.join(self.sample_data_path, "ultrasound_segmentation", "video")
if not os.path.exists(self.video_dir):
raise ValueError(f"Could not find video data:{self.video_dir=}")
更新
self.model_path_map
以指向超声分割模型(第20-23
行)。更新
self.video_dir
以指向超声视频文件的目录(第25
行)。
replayer: # VideoStreamReplayer
basename: "ultrasound_256x256"
frame_rate: 0 # as specified in timestamps
repeat: true # default: false
realtime: true # default: true
count: 0 # default: 0 (no frame count restriction)
preprocessor: # FormatConverter
out_tensor_name: source_video
out_dtype: "float32"
resize_width: 256
resize_height: 256
inference: # Inference
backend: "trt"
pre_processor_map:
"byom_model": ["source_video"]
inference_map:
"byom_model": ["output"]
postprocessor: # SegmentationPostprocessor
in_tensor_name: output
network_output_type: softmax
data_format: nchw
viz: # Holoviz
width: 854
height: 480
color_lut: [
[0.65, 0.81, 0.89, 0.1],
[0.2, 0.63, 0.17, 0.7]
]
将
basename
更新为超声视频文件的基本名称(第2
行)。AI 模型期望图像的宽度和高度为 256x256,更新预处理器的参数以将输入调整大小为 256x256(第
11-12
行)。AI 模型的最终输出层是 softmax,因此我们将其指示给后处理器(第
23
行)。由于此模型在两个类别之间进行预测,因此我们需要在 Holoviz 的颜色查找表中添加另一个条目(第
31
行)。请注意,第一个颜色条目的 alpha 值为0.1
(第30
行),因此背景类别的掩码可能不可见。我们刚刚添加的第二个条目是绿色,alpha 值为0.7
,因此它将很容易看到。
以上更改足以将 BYOM 示例更新为超声分割应用程序。
一般来说,在部署您自己的 AI 模型时,您需要考虑第二个分支中的算子。本示例使用了一个非常典型的 AI 工作流
输入:这可以是磁盘上的视频、来自捕获设备的输入流或其他数据流。
预处理:您可能需要预处理输入流,以将张量转换为 AI 模型期望的形状和格式(例如,转换数据类型和调整大小)。
推理:您的模型需要采用 ONNX 或 TensorRT 格式。
后处理:一个后处理模型输出的算子,使其成为可以被下游算子轻松使用的格式。
输出:后处理后的流可以显示或被其他下游算子使用。
Holoscan SDK 附带了许多 内置算子,您可以使用它们来配置自己的工作流。如果需要,您可以编写自己的自定义算子,或访问 Holohub 以获取其他实现和算子的想法。
按照上述指示修改应用程序后,运行该应用程序应显示带有分割掩码叠加层的超声视频,类似于下图。

图 11 超声分割
如果您在未修改的情况下运行 byom.py 应用程序,并且正在使用 debian 安装,您可能会遇到以下错误消息
[error] Error in Inference Manager ... TRT Inference: failed to build TRT engine file.
在这种情况下,修改模型目录的写入权限应该会有所帮助(谨慎使用)
sudo chmod a+w /opt/nvidia/holoscan/examples/bring_your_own_model/model
内置的 InferenceOp
算子提供了 推理 的功能。此算子有一个 receivers
端口,可以连接到任意数量的上游端口以实现多 AI 推理,以及一个 transmitter
端口以将结果发送到下游。以下是算子的一些参数的描述以及如何使用它们的一般指南。
backend
:如果输入模型为tensorrt engine file
格式,请选择trt
作为后端。如果输入模型为onnx
格式,请选择trt
或onnx
作为后端。allocator
:可以传递给此算子以指定如何分配输出张量。model_path_map
:包含字典键,其中包含引用每个模型的唯一字符串。这些值设置为磁盘上模型文件的路径。所有模型都必须是onnx
或tensorrt engine file
格式。如果 TensorRT 引擎文件不存在,Holoscan 推理模块将执行onnx
到tensorrt
模型转换。pre_processor_map
:此字典应包含与model_path_map
相同的键,映射到每个模型的输出张量名称。inference_map
:此字典应包含与model_path_map
相同的键,映射到每个模型的输出张量名称。enable_fp16
:布尔变量,指示是否应使用半精度来加速推理。默认值为 False,并使用单精度(32 位 fp)值。input_on_cuda
:指示输入张量是在设备上还是在主机上。output_on_cuda
:指示输出张量是在设备上还是在主机上。transmit_on_cuda
:如果为 True,则表示来自推理的数据传输将在设备上进行,否则表示来自推理的数据传输将在主机上进行。
颜色通道顺序
了解您的模型期望的通道顺序非常重要。这可能由训练数据、训练时执行的预训练转换或应用程序中使用的预期推理格式指示。
例如,如果您的推理数据是 RGB,但您的模型期望 BGR,您将需要在 yaml 文件中的 segmentation_preprocessor 中添加以下内容:out_channel_order: [2,1,0]
。
规范化您的数据
同样,流式数据的默认缩放比例为 [0,1]
,但取决于您的模型是如何训练的,您可能期望 [0,255]
。
对于上述情况,您将在 YAML 文件中的 segmentation_preprocessor 中添加以下内容
scale_min: 0.0
scale_max: 255.0
网络输出类型
模型通常具有不同的输出类型,例如 Sigmoid
、Softmax
或其他类型,您可能需要检查模型的最后几层以确定哪种类型适用于您的情况。
正如我们上面的超声分割示例一样,我们在 YAML 文件中添加了以下内容:network_output_type: softmax