Skip to content

Add Halidoscope for interactive trace and profiling visualization. - #9356

Open
parkerziegler wants to merge 68 commits into
mainfrom
parkerziegler/halidoscope
Open

parkerziegler wants to merge 68 commits into
mainfrom
parkerziegler/halidoscope

Conversation

@parkerziegler

@parkerziegler parkerziegler commented Aug 17, 2026

Copy link
Copy Markdown

This PR introduces Halidoscope, a new tool for interactively visualizing Halide traces and performance information captured by the Halide profiler.

Trace View Profile View
A screenshot of the Trace view in the Halidoscope UI. A screenshot of the Profile view in the Halidoscope UI.

Motivation

Understanding the performance characteristics of a Halide program (and, specifically, its schedule) can be tricky for experts and downright daunting for newer Halide developers. Today, the primary developer tooling we have to aid folks in understanding their Halide programs is HalideTraceViz. While HalideTraceViz provides good visual intuition for how a schedule executes, it doesn't provide much insight into how effectively a given schedule balances trade-offs in cache locality, redundant recomputation, and parallelism. Halidoscope attempts to fill this gap by:

  • Interactively visualizing key metrics (called "render modes") like per-Func Store / Load Frequency, Redundant Stores, Reuse Distance, and Thread Coverage
  • Integrating directly with the Halide profiler
  • Allowing users to see myriad useful bits of information, such as pipeline dataflow, NaNs / Infs, and real-time liveness information (e.g., Func buffer liveness, active producer-consumer relationships)

High-Level API

Halidoscope ships as both a GUI and CLI that can be invoked through a new Pipeline::halidoscope member function or directly from the command line.

Calling Halidoscope via Pipeline::halidoscope

Halide developers can launch Halidoscope via a call to Pipeline::halidoscope (either in C++ or via the Python bindings) like so:

// Normal algorithm definition and scheduling code.

// Create the pipeline.
Pipeline pipeline(output);

// Explicitly passing desired output buffer dimensions for allocation,
// which happen to match the input.
std::vector<int32_t> sizes = {input.width(), input.height(), input.channels()};

// Call .halidoscope!
 pipeline.halidoscope(sizes);

Under the hood, the Pipeline::halidoscope member function will:

  1. Serialize the pipeline (to obtain a fresh copy)
  2. Deserialize it and execute it once with tracing enabled
  3. Deserialize it a second time and execute it with profiling enabled

Users can specify a HalidoscopeOptions struct to control how Halidoscope executes with the following fields.

Field Type Description
halidoscope_path std::optional<std::string> The path to the Halidoscope binary on disk. By default, the call will look for Halidoscope on the user's $PATH and error if not found.
halidoscope_output_dir std::optional<std::string> (Optional.) A path to a non-volatile directory for storing Halidoscope-generated trace binaries and profiler output. By default, Halidoscope will write recorded .hltrace and profile JSON files to a temporary directory that is destroyed on process exit.
halidoscope_profile_runs std::optional<int> The number of profiling runs for the Halide profiler to execute on the pipeline. Defaults to 1. Users can opt out of profiling altogether by specifying 0.

Calling Halidoscope from the command line

Users can also call launch Halidoscope directly from the command line, pointing it at a pre-recorded Halide trace binary and (optionally) a profile JSON file.

halidoscope --trace <path/to/recorded.hltrace> [--profile <path/to/recorded-profile.json>]

The Halidoscope CLI also comes with several commands (documented in the README.md and printable via halidoscope --help) that can provide useful, high-level information on a trace (for both humans and agents).

  • halidoscope list — List the Funcs in a trace, along with their dimensionality.
  • halidoscope stats — Print statistics (minimum/maximum coordinates, minimum/maximum value, maximum store/load counts, and thread count) for one or all Funcs in a trace.
  • halidoscope dot — Generate a Graphviz DOT representation of the pipeline's dataflow graph.
  • halidoscope snapshot — Snapshot a Func's values at a given packet index for a given render mode, writing the underlying data to a JSON file.

Stack and High-Level Architecture

Halidoscope is a completely standalone Tauri application in tools/halidoscope — it has no dependency on the Halide runtime.

Warning

The only slight exception to this is our use of bindgen to derive Rust bindings for halide_trace_packet_t, which ensures that our Rust packet parser always reads the correct offsets for Halide's packet format. bindgen is run at build-time against a user's local version of the Halide source. However, note that there is no actual runtime FFI between Halidoscope's Rust parser and the Halide runtime or C ABI.

Backend

The Halidoscope backend is implemented in Rust and contains the following main modules:

  • trace.rs — Responsible for parsing a .hltrace file and accumulating trace-level statistics and metadata.
  • render.rs — Computation supporting Halidoscope's render modes, where trace data is converted to RGBA Vec<u8> buffers (for display) and binary payloads for histogram data and NaN / Inf overlays
  • commands.rs — Sets up the Tauri IPC API for communication with the frontend.
  • cli.rs — Handles calls to the Halidoscope CLI.

Communication is done entirely over IPC using Tauri commands.

Frontend

The frontend is implemented in TypeScript and React, using Jotai for state management and Tailwind for styling and CSS management. We use Vite as our frontend build system. This is about as standard a frontend stack as you'll find in modern web dev.

By default, Tauri uses a system's native WebView for rendering, which allows us to avoid bundling a full browser engine with the Halidoscope binary. Today, the Halidoscope binary weighs in at 10.5 MB uncompressed and 3.7 MB gzipped.

Breaking changes

No changes here are breaking; all changes are purely additive. Still, it is likely worth reviewing the additions to the Pipeline API a bit more carefully to confirm.

Checklist

  • Tests added or updated (not required for docs, CI config, or typo fixes)
  • Documentation updated (if public API changed)
  • Python bindings updated (if public API changed)
  • Benchmarks are included here if the change is intended to affect performance.
  • Commits include AI attribution where applicable (see Code of Conduct)

Loading
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants