Replies: 3 comments
|
Hi, thanks for the post. A few references might be helpful to your work. For Isaac Sim performance optimizations, please check out the Isaac Sim performance optimization handbook. |
|
Thanks @PeterL-NV , Yes I've gone through those two pages multiple times in order to optimize as much as I can. How consumption is wired Sensors are authored first, then runtime wrappers are created exactly once and retained. Capture is driven by two callbacks registered on the Kit main loop:
The camera path (simplified) # One TiledCameraSensor shared by the two 640x400 cameras
self._buffers = [wp.empty((n_cams, h, w, 4), dtype=wp.uint8, device="cuda")
for _ in range(2)] # double buffered
def get_image(self, sensor, timestamp_ns):
if self._last_timestamp_ns != timestamp_ns:
batch, _ = self._sensor.get_data("rgba", tiled=False,
out=self._buffers[self._next_buffer])
self._last_timestamp_ns = timestamp_ns
self._last_batch = batch.numpy() # <-- single D2H for the whole tile
self._next_buffer = (self._next_buffer + 1) % len(self._buffers)
return self._last_batch[self._indices[sensor.sensor_id]]The tiled product is fetched once per render (camera render tick) and sliced per logical camera, so the device to host is one ~2 MB transfer per frame, not one per camera. But it is a blocking .numpy() executed inline in the NEW_FRAME callback, which I believe makes the double buffering ineffective, the copy is consumed before the next get_data() is ever issued. The lidar path (simplified) raw, _ = self._sensor.get_data("generic-model-output")
host_raw = raw.numpy() # <-- D2H, on the main thread
gmo = parse_generic_model_output_data(host_raw)
xyz = np.column_stack((gmo.x, gmo.y, gmo.z)).astype(np.float32, copy=False)
valid = np.isfinite(xyz).all(axis=1)
xyz = xyz[valid]with de-duplication on (gmo.frameId, gmo.timestampNs) so a repeated GMO buffer is dropped rather than re-published. That's why I used app.sensors.nv.lidar.outputBufferOnGPU=true (1.224 → 1.529 RTF) : the buffer staying on device is worth ~4.9 ms/frame even though I then immediately pull it to host anyway. Downstream Committed samples go into a per-sensor latest-value stream, subscribers serialize and push them over a socket from the same interpreter's asyncio loop. Downstream consumers do not need main-thread ordering they only need the sample. Hope it helps to understand better the architecture of my codebase, thank you in advance 🙏 |
|
I am closing this, feel free to contribute/re-open if you have other optimization ideas! |
Uh oh!
There was an error while loading. Please reload this page.
I'm building a simulation application with Isaac Sim 6.0.1. I have reduced the issue to a small, repeatable sensor workload and would appreciate advice on where to look next.
The scene is deliberately light: 30 prims, a ground environment, and one articulated vehicle. The simulated sensor rig is:
TiledCameraSensorrender product, at 640x400 and 30 Hz.The physics rate is 200 Hz to support the IMU and rendering is 30 Hz. The application loop is uncapped and headless. My target is to run faster than real time the current result is normally around 1.3–1.5x RTF.
What seems counterintuitive is that neither aggregate CPU nor GPU utilization appears saturated during the run. I understand that aggregate utilization is not proof of spare capacity: the Kit main loop has ordering constraints, and a single serial wait can determine the frame time. I am trying to determine whether I have reached an expected synchronization boundary for this workload or whether I am overlooking a configuration or architectural change.
Hardware and runtime environment
performanceno swap was in use during the benchmark.Repeatable benchmark results
Each normal run uses 120 warm-up frames followed by 600 measured application frames. The 200 Hz runs advance 18 simulated seconds with 3,600 physics steps. Results below are from the same machine and same scene values labelled
liveinclude my normal sensor-consumption path.lean-base-kit-fixedbaselinesensor-profile-200hzsensor-profile-200hz-fixedsensor-profile-live-200hzsensor-profile-live-detailed-200hzlive-baseline-200hzlive-tasking-4-200hzcarb.tasking.plugin.threadCount=4, TBB max=4live-tasking-12-200hzcarb.tasking.plugin.threadCount=12, TBB max=12live-physics-4-200hzpersistent.physics.numThreads=4live-physics-12-200hzpersistent.physics.numThreads=12live-gather-render-results-off-200hzgatherRenderResults=falselive-lidar-host-buffer-200hzheadless-no-uiheadless-no-ui-finalcpu-profile-200hzThere is normal run-to-run variation, particularly in the multi-sample runs (roughly 1–4% RTF standard deviation), so I do not interpret the small differences between the CPU-thread experiments as meaningful. The useful conclusions seem to be:
Sensor consumption architecture
I author the RTX sensors first, then create the experimental runtime wrappers (
TiledCameraSensor, lidar runtime sensor, and IMU sensor). The significant throughput drop happens when the runtime wrapper is created andget_data()begins to be called authoring the sensor alone is much cheaper.The relevant camera paths appear to reshape GPU data and use Warp copies/kernels. In simplified form, the tiled path performs a Warp copy or a tiled-image reshape kernel:
while the single-camera path can reshape a view and optionally copy into an output buffer:
My Python code is loaded through Kit's interactive Python environment. An async main method builds the scene, subscribes to new-frame and physics-step events, calls
get_data()at each sensor's desired rate, and hands data to downstream processing that does not need to remain on the main thread.I know the render/physics/Kit frame itself cannot safely be moved to a Python subprocess. My narrower question is whether there is a supported way to decouple consumption of already-produced GPU sensor buffers from the Kit main loop without introducing a forced synchronization or host copy. A subprocess is not necessarily the right mechanism it may make the data-transfer problem worse.
Relevant Kit settings
This is the performance-relevant portion of the experience file. I removed unrelated application settings.
A few environment details
The log notes that multi-tick mode is enabled without Motion BVH, DLSS is scaling the small tiled render product up to its minimum input size (weird knowing that it has been deactivated in the kit file), the GPU is operating at PCIe x8 despite x16 capability, and IOMMU is enabled. I will test these independently, but they are secondary context rather than the central question.
Questions
CameraSensor/TiledCameraSensorand RTX-lidar data when downstream work can run asynchronously? In particular, can the GPU work returned byget_data()be handed to another process without blocking the Kit frame and sharing data throughout cuda shared memory for instance exempting the main thread from extracting data to cpu?I would especially value advice from anyone who has profiled a comparable headless multi-rate RTX sensor setup, or guidance on a lower-overhead profiling approach than the CPU profiler used above.
Thank you in advance 🙏
Here is a frame from the profiler:

All reactions