Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,11 @@ build*/
site/
bench-results/

# ResNet inference example: locally generated dataset + model artifacts
# (built offline by applications/resnet50_inference/tools/, see the tutorial)
/data/
/models/

# tune_system.py default output
pcie_schematic.png

Expand Down
11 changes: 10 additions & 1 deletion AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ CMake options (full table in `docs/getting-started.md`):
- `DAQIRI_ENGINE` — space-separated list of optional engines to compile. Valid values: `dpdk` (raw Ethernet) and `ibverbs` (RDMA/RoCE). Linux sockets (UDP/TCP) are always built in, so there is no `socket` value. Default is `"dpdk ibverbs"`.
- `DAQIRI_BUILD_PYTHON` — builds `pybind11` bindings from `python/`.
- `DAQIRI_BUILD_EXAMPLES` — builds the benchmark executables (default `ON`).
- `DAQIRI_BUILD_APPLICATIONS` — builds the end-to-end example applications under `applications/` (default `OFF`; requires TensorRT, e.g. the `BASE_IMAGE=torch` container). Currently builds `applications/resnet50_inference/` (DAQIRI → TensorRT ResNet inference).
- `DAQIRI_ENABLE_OTEL_METRICS` — enables OpenTelemetry metrics instrumentation (default `OFF`).
- `DAQIRI_REORDER_GPU_PROFILE` — enable CUDA event timing in the DPDK reorder kernels (off by default).
- `DAQIRI_ENABLE_S3` — enable AWS SDK-backed asynchronous raw packet writes to S3 (off by default).
Expand Down Expand Up @@ -55,6 +56,14 @@ Configs named `raw_rx_*` are RX-only — they initialize the RX path and wait fo

When determining throughput for a benchmark use the `mlnx_perf` utility in the background to view transmit and receive rates. Using application run time with packet counts is usually not accurate enough due to startup inconsistencies.

### Example applications (`applications/`, opt-in)

Beyond the benchmarks, end-to-end **example applications** live under `applications/` and build only with `-DDAQIRI_BUILD_APPLICATIONS=ON` (off by default; they pull in heavier optional dependencies). They are downstream `daqiri::daqiri` consumers — they reuse the bench scaffolding (`examples/raw_bench_common.*`, `bench_pipeline.cu`) but are not part of the benchmark table above.

| Executable | Source | Typical config |
|---|---|---|
| `daqiri_resnet50_inference` | `applications/resnet50_inference/` | `configs/resnet50_wire_loopback.yaml` (example: real CIFAR-10 images → per-class feature stats), `configs/resnet50_bench_spark.yaml` (synthetic throughput sweep). DAQIRI raw/GPUDirect RX → GPU sequence-number reorder → ResNet feature extraction via **TensorRT** (FP16) → per-class mean-feature stats. Requires TensorRT (build the container with `BASE_IMAGE=torch`). Accepts `--seconds N --dataset <pcap> --images-per-batch N --model resnet18|34|50|101|152`. Data prep + sweep tools in `applications/resnet50_inference/tools/`. See `docs/tutorials/daqiri-resnet-inference.md`. |

## Formatting

`clang-format` is required for contributions (CONTRIBUTING.md):
Expand Down Expand Up @@ -112,7 +121,7 @@ The web docs live in `docs/` and are built with [MkDocs Material](https://squidf
- `docs/concepts.md` — terminology glossary (stream types and endpoint URI schemes, GPUDirect, packet/burst/segment, flow/queue, memory region, zero-copy ownership, RX reorder). Meant to be opened in parallel with the rest of the docs.
- `docs/api-reference/index.md` — API guide (6-step application lifecycle, configuration-first model)
- `docs/api-reference/configuration.md`, `docs/api-reference/cpp.md`, `docs/api-reference/python.md` — YAML schema, C++ API, and Python bindings docs
- `docs/tutorials/` — tutorial walkthroughs (system config, config-file walkthrough, Holoscan integration)
- `docs/tutorials/` — tutorial walkthroughs (system config, config-file walkthrough, Holoscan integration, ResNet inference)
- `docs/benchmarks/` — benchmark guide pages, surfaced as a top-level "Benchmarking" nav section in `mkdocs.yml` and the landing page (`docs/index.md`):
- `docs/benchmarks/benchmarks.md` — overview and engine-selection decision tree
- `docs/benchmarks/socket_benchmarking.md` — "Socket and RDMA Benchmarking" (TCP/UDP and RoCE/RDMA)
Expand Down
5 changes: 5 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ endif()

option(DAQIRI_BUILD_PYTHON "Build Python bindings" OFF)
option(DAQIRI_BUILD_EXAMPLES "Build standalone examples" ON)
option(DAQIRI_BUILD_APPLICATIONS "Build example applications (requires TensorRT)" OFF)
option(DAQIRI_ENABLE_OTEL_METRICS "Enable OpenTelemetry metrics instrumentation" OFF)
set(DAQIRI_ENGINE "dpdk ibverbs" CACHE STRING "Optional engine implementations to build: dpdk, ibverbs (Linux sockets are always built in)")

Expand All @@ -53,6 +54,10 @@ if(DAQIRI_BUILD_EXAMPLES)
add_subdirectory(examples)
endif()

if(DAQIRI_BUILD_APPLICATIONS)
add_subdirectory(applications)
endif()

install(
EXPORT daqiriTargets
NAMESPACE daqiri::
Expand Down
19 changes: 19 additions & 0 deletions applications/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
# SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
# SPDX-License-Identifier: Apache-2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

# End-to-end example applications that build ON TOP of the DAQIRI library
# (downstream consumers). Gated by DAQIRI_BUILD_APPLICATIONS in the top-level
# CMakeLists because they pull in heavier optional dependencies (TensorRT).
add_subdirectory(resnet50_inference)
118 changes: 118 additions & 0 deletions applications/resnet50_inference/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
# SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
# SPDX-License-Identifier: Apache-2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

cmake_minimum_required(VERSION 3.20)

# Mirror the examples/ CUDA-arch auto-detect so Spark (sm_120/121), IGX (sm_89),
# and A100/H100 (sm_80/90) all build without manual flags.
if(NOT DEFINED CMAKE_CUDA_ARCHITECTURES)
set(CMAKE_CUDA_ARCHITECTURES 80 90)
set(DAQIRI_APP_USING_DEFAULT_CUDA_ARCHITECTURES ON)
endif()

project(daqiri_resnet50_inference LANGUAGES CXX CUDA)

find_package(CUDAToolkit REQUIRED)
find_package(Threads REQUIRED)

if(DAQIRI_APP_USING_DEFAULT_CUDA_ARCHITECTURES AND
CMAKE_CUDA_COMPILER_VERSION VERSION_GREATER_EQUAL 13.0)
list(APPEND CMAKE_CUDA_ARCHITECTURES 121)
endif()
set(DAQIRI_APP_CUDA_ARCHITECTURES "${CMAKE_CUDA_ARCHITECTURES}")

# --- TensorRT discovery (cross-platform). NGC PyTorch (BASE_IMAGE=torch) ships
# TensorRT with headers under /usr/include and libs under
# /usr/lib/{x86_64,aarch64}-linux-gnu. The CMake-find machinery for TRT is
# inconsistent across versions, so locate the header + libs manually. If TRT is
# absent we warn and skip the target rather than failing the whole build.
find_path(TRT_INCLUDE_DIR NvInfer.h
HINTS /usr/include /usr/include/x86_64-linux-gnu /usr/include/aarch64-linux-gnu)
find_library(TRT_NVINFER_LIB nvinfer
HINTS /usr/lib/aarch64-linux-gnu /usr/lib/x86_64-linux-gnu /usr/lib)
find_library(TRT_NVONNXPARSER_LIB nvonnxparser
HINTS /usr/lib/aarch64-linux-gnu /usr/lib/x86_64-linux-gnu /usr/lib)
if(NOT TRT_INCLUDE_DIR OR NOT TRT_NVINFER_LIB OR NOT TRT_NVONNXPARSER_LIB)
message(WARNING
"TensorRT not found (NvInfer.h / libnvinfer / libnvonnxparser); skipping "
"resnet50_inference application. Build the container with BASE_IMAGE=torch "
"to get TensorRT. INCLUDE=${TRT_INCLUDE_DIR} NVINFER=${TRT_NVINFER_LIB} "
"NVONNX=${TRT_NVONNXPARSER_LIB}")
return()
endif()
message(STATUS "TensorRT include: ${TRT_INCLUDE_DIR}")
message(STATUS "TensorRT libs: ${TRT_NVINFER_LIB} ${TRT_NVONNXPARSER_LIB}")

# yaml-cpp target: set by the top-level/src CMake when built in-tree; mirror the
# examples/ resolution block for a standalone application build.
if(NOT DAQIRI_YAML_TARGET)
find_package(yaml-cpp QUIET)
if(TARGET yaml-cpp::yaml-cpp)
set(DAQIRI_YAML_TARGET yaml-cpp::yaml-cpp)
elseif(TARGET yaml-cpp)
set(DAQIRI_YAML_TARGET yaml-cpp)
else()
message(FATAL_ERROR "yaml-cpp not found; install yaml-cpp-dev")
endif()
endif()

set(DAQIRI_EXAMPLES_DIR "${CMAKE_SOURCE_DIR}/examples")

# Reuse the bench scaffolding: config parsers, thread affinity, UDP header
# builders, wait_for_stop, and the ReorderPipeline. raw_bench_common.cpp's
# rx_count_worker references GpuWorkload + ReorderPipeline, so we also compile
# bench_workload.cu + bench_pipeline.cu and link the math libs (same as the
# socket bench).
add_executable(daqiri_resnet50_inference
main.cpp
app_config.cpp
pcap_replayer.cpp
feature_sink.cpp
inference_pipeline.cu
trt_runner.cu
${DAQIRI_EXAMPLES_DIR}/raw_bench_common.cpp
${DAQIRI_EXAMPLES_DIR}/raw_bench_common_cuda.cu
${DAQIRI_EXAMPLES_DIR}/bench_workload.cu
${DAQIRI_EXAMPLES_DIR}/bench_pipeline.cu)

target_compile_features(daqiri_resnet50_inference PRIVATE cxx_std_17)
target_include_directories(daqiri_resnet50_inference PRIVATE
${CMAKE_CURRENT_SOURCE_DIR}
${DAQIRI_EXAMPLES_DIR}
${CMAKE_SOURCE_DIR}
${TRT_INCLUDE_DIR})
target_link_libraries(daqiri_resnet50_inference PRIVATE
daqiri::daqiri
${DAQIRI_YAML_TARGET}
CUDA::cudart
CUDA::cufft
CUDA::cublas
Threads::Threads
${TRT_NVINFER_LIB}
${TRT_NVONNXPARSER_LIB})
set_target_properties(daqiri_resnet50_inference PROPERTIES
CUDA_ARCHITECTURES "${DAQIRI_APP_CUDA_ARCHITECTURES}"
BUILD_RPATH "$ORIGIN/../../src;$ORIGIN/../../src/third_party/yaml-cpp")

set(DAQIRI_RESNET_CONFIGS
configs/resnet50_wire_loopback.yaml
configs/resnet50_bench_spark.yaml)
foreach(cfg IN LISTS DAQIRI_RESNET_CONFIGS)
configure_file(${CMAKE_CURRENT_SOURCE_DIR}/${cfg}
${CMAKE_CURRENT_BINARY_DIR}/${cfg} COPYONLY)
endforeach()

install(TARGETS daqiri_resnet50_inference RUNTIME DESTINATION bin)
install(FILES ${DAQIRI_RESNET_CONFIGS} DESTINATION bin/resnet50_inference)
139 changes: 139 additions & 0 deletions applications/resnet50_inference/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
<!--
SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES.
All rights reserved. SPDX-License-Identifier: Apache-2.0
-->

# DAQIRI → TensorRT ResNet inference example

End-to-end demo wiring DAQIRI packet ingestion into a GPU inference pipeline
(GitHub issue #73):

```
received packets (raw / DPDK GPUDirect)
→ GPU sequence-number reorder (image reassembly)
→ ResNet feature extraction (TensorRT, FP16)
→ per-class mean-feature stats (example mode)
```

Packets DMA straight into GPU memory, a CUDA kernel reorders each image's
packets by sequence number into a contiguous FP32 NCHW tensor, TensorRT runs a
ResNet feature extractor on a batch of those tensors, and the feature vectors
are summarized per class — all without a host bounce on the data path.

The example runs over the **DGX Spark physical p0→p1 cabled loopback** (the same
two-physical-port DPDK loopback the performance report uses), so it exercises the
real NIC RX path rather than a software shortcut. The build, CMake target, and
CUDA-arch handling are platform-agnostic (x86_64 + aarch64 TensorRT discovery),
so the same code builds on IGX and RTX Pro servers.

## Prerequisites

Build the project container with TensorRT (and torch / torchvision for the data
prep), which the `torch` base image provides:

```bash
BASE_IMAGE=torch BASE_TARGET=dpdk DAQIRI_ENGINE="dpdk ibverbs" scripts/build-container.sh
```

## Build

Enable the applications tree (off by default; it requires TensorRT):

```bash
cmake -S . -B build -DCMAKE_BUILD_TYPE=Release -DBUILD_SHARED_LIBS=ON \
-DDAQIRI_ENGINE="dpdk ibverbs" -DDAQIRI_BUILD_APPLICATIONS=ON
cmake --build build -j
cmake --install build --prefix /opt/daqiri
```

If TensorRT is not found the application is skipped with a CMake warning (the
rest of the build is unaffected).

## Prepare the model and dataset (offline, one-time)

```bash
# 1. Export a ResNet feature extractor (final FC stripped) to ONNX.
python3 applications/resnet50_inference/tools/export_resnet_onnx.py \
--model resnet50 --output models/resnet50_features.onnx --check

# 2. Packetize CIFAR-10 into a replayable pcap (+ a labels sidecar).
python3 applications/resnet50_inference/tools/prepare_cifar10_pcap.py \
--num-images 256 --out data/cifar10_resnet.pcap
```

The TensorRT engine itself is built and cached on the first run
(`models/resnet50_features.fp16.engine`) and loaded from cache afterwards.

## Run (example mode — real images + per-class stats)

Bring the wire loopback's network namespaces **down** (they hide the ports from
DPDK) and export the RX port MAC, exactly as for the raw/DPDK bench:

```bash
./scripts/setup_spark_wire_loopback_netns.sh down
export ETH_DST_ADDR=$(cat /sys/class/net/<rx-iface>/address) # RX port (p1)
```

Fill the `<tx-pcie-addr>` / `<rx-pcie-addr>` placeholders in
`configs/resnet50_wire_loopback.yaml` (TX = p0, RX = p1), then:

```bash
./build/applications/resnet50_inference/daqiri_resnet50_inference \
./build/applications/resnet50_inference/configs/resnet50_wire_loopback.yaml \
--dataset data/cifar10_resnet.pcap --seconds 10
```

Expected output: the engine builds + caches on the first run (loads from cache
on the next), a couple of sample feature vectors print, and at shutdown a
per-class mean-feature summary prints. Confirm nonzero RX with `mlnx_perf`.

Example mode replays the dataset **once** by default (use `--loop` to repeat):
the whole dataset is buffered in the RX ring, so even though ResNet inference is
slower than line rate no packets drop and the in-order, drop-free image
reassembly (and label mapping) stays correct. Keep
`num_images * packets_per_image < num_bufs` (the default 256 × 84 = 21 504 fits
the 51 200-buffer ring).

## Run (benchmark mode — synthetic, throughput sweep)

```bash
export ETH_DST_ADDR=$(cat /sys/class/net/<rx-iface>/address)
BUILD_DIR=./build ./applications/resnet50_inference/tools/run_resnet_bench.sh
```

Sweeps ResNet-18/34/50/101/152 (FP16) and writes `resnet-bench.csv` (img/s per
model). No dataset is needed; the TX synthesizes frames so this measures the
full reorder + inference receive-path throughput.

## How it works

See the tutorial `docs/tutorials/daqiri-resnet-inference.md` for the CUDA-event
buffer logic (how bursts are ingested and freed without stalling the GPU) and a
flow diagram. The short version:

```
p0 --cable--> p1 -> get_rx_burst -> [reorder-scatter into image buffer (stream s)]
-> [D2D image -> NCHW batch + input_ready] -> [TrtRunner enqueueV3(s) + release_evt]
-> [D2H + d2h_event] -> FeatureSink -> per-class stats / counts
|
cudaStreamSynchronize(s) BEFORE free_all_packets_and_burst_rx
```

One CUDA stream serializes reorder → batch-copy → inference, so no explicit sync
is needed between them; the only barrier is a `cudaStreamSynchronize` before each
burst is freed, because the reorder kernel reads the burst's device pointers.

## Files

| File | Role |
|---|---|
| `main.cpp` | arg parse, `daqiri_init`, spawn TX + RX threads |
| `app_config.{h,cpp}` | YAML parse + derived reorder geometry |
| `pcap_replayer.{h,cpp}` | pcap reader, dst-MAC patch, TX worker, synthetic frames |
| `trt_runner.{hpp,cu}` | TRT engine build/cache + FP16 dynamic-batch inference |
| `inference_pipeline.{h,cu}` | RX loop: reorder → NCHW batch → TRT |
| `feature_sink.{h,cpp}` | per-class mean-feature stats / throughput |
| `tools/export_resnet_onnx.py` | torchvision ResNet → ONNX (feature extractor) |
| `tools/prepare_cifar10_pcap.py` | CIFAR-10 → preprocessed framed pcap + labels |
| `tools/run_resnet_bench.sh` | model-size throughput sweep → CSV |
| `configs/*.yaml` | wire-loopback example + benchmark configs |
Loading
Loading