Skip to navigation

FAQ

View as Markdown

Quick answers to the questions we hear most often about Holoscan SDK 5.x — from developers arriving fresh and from developers arriving from 4.x, where many familiar names (Application, schedulers, Conditions, GXF) no longer exist. Each answer is deliberately short and links to the page that owns the full story.

If you are migrating from 4.x, start with Migrating from 4.x; if you are new to the SDK, start with Core concepts.

Naming and concepts

Why is the method called contract()?

Because it declares a temporal contract between your operator and the runtime: what the runtime must satisfy before it invokes compute(). A contract names the trigger (what causes an activation, such as OnEach{camera} or OnClock{.period = 33ms}), the input selection (which samples participate, such as LatestWithin{.max_age = 5ms}), and the missing-data policy (what happens when an input is absent or stale, such as ReuseLast or Skip). It is a contract in the everyday sense — both sides agree on terms up front, the compiler checks them, and the runtime enforces them — which is what lets compute() stay entirely oblivious to timing. See Temporal contracts.

Why errors-as-values instead of exceptions?

Fallible runtime APIs return holoscan::expected<T, Error> instead of throwing, for four reasons: failure is visible in the signature (you cannot forget to handle it — the results are [[nodiscard]]); returning an error is an ordinary value return with deterministic cost, whereas stack unwinding is expensive and hard to bound on real-time paths; a code-only Error (say, kNotReady on a hot polling path) allocates nothing; and value-channel code works even in builds with exceptions disabled. Exceptions still exist at genuine boundaries — graph authoring throws on programming errors, by design. Branch on ErrorCode, and use HOLOSCAN_TRY to propagate concisely. See Errors as values.

Where did the schedulers go?

There is no user-selectable scheduler in 5.x — no Greedy, Multi-Thread, or Event-Based Scheduler to choose between. Instead, each operator’s contract() declares when it should run, compile() lowers those declarations into an execution plan, and the runtime’s dispatcher executes that plan. Scheduling intent moved from a global runtime knob into a per-operator, statically checkable declaration. See Core concepts and Temporal contracts.

Why is there no Application class?

5.x separates the three phases that Application used to bundle. You compose a holoscan::Graph (add operators with graph.op<T>(...), wire them with add_flow), compile it with holoscan::compile(graph) into an ExecutionPlan, and run the plan with holoscan::run_async(plan), which returns a RunSession you control. Each phase has its own failure style: authoring throws, compile() returns a plan you check with plan.ok(), and a run fails closed. Because the phases are separate values, you can compile once and run many times, inspect a rejected plan’s diagnostics, or hold multiple sessions. See Composing a graph and Running and stopping.

What replaced Conditions (CountCondition, PeriodicCondition, …)?

Triggers and selectors inside contract(). A 4.x PeriodicCondition becomes OnClock{.period = ...}; message-availability conditions become the default OnAnyInput{} or an explicit OnEach{port}; alignment of multiple inputs becomes OnAligned{...}. Unlike Conditions, these are typed value objects that the compiler validates as part of the plan rather than runtime components attached to an operator. See Temporal contracts.

What are the “three declarations” of an operator?

setup() declares structure (ports and parameters), contract() declares time (trigger, selection, missing-data policy), and compute() does the work — nothing else. Keeping the three concerns in three methods is what makes operators testable and reusable across graphs with different timing. See Operator anatomy.

Practical

Why does my run fail with a clock-binding diagnostic?

Ordinary input- and clock-triggered pipelines must select a default clock. If it is absent, compile() rejects the plan with TEMPORAL_CLOCK_UNDECLARED. Declare and select it before compiling:

graph.set_default_clock(graph.add_clock<holoscan::RealtimeClock>("clock"));

Python can use hs.Graph("pipeline", default_clock=hs.RealtimeClock). Readiness-only sources may be clock-free. RUNTIME_CLOCK_BINDING_UNAVAILABLE instead means an admitted plan’s required clock could not be bound at startup. See Time and clocks.

Why am I getting kResourceExhausted?

Because 5.x is bounded by construction: tensor pools are preallocated, queues have declared depths, and per-compute emit counts are finite. When a bounded resource has no free capacity, the SDK does not silently allocate more — it returns Error{kResourceExhausted} so you can react (drop, retry, or treat it as a real sizing bug). The usual causes are a pool sized too small for the pipeline’s in-flight depth, or a slot held longer than intended (for example, a tensor loan that was never committed). See Understanding failures and Payload type rules.

Why can’t I use int as a port type?

Because int (like size_t and long) is a platform-dependent alias, and a port type is a wire contract that must mean the same thing everywhere. The in-process scalar catalog is a closed set of fixed-width types: bool, uint8_t through int64_t, float, and double. Write holoscan::Input<std::int32_t>, not holoscan::Input<int>. Structured types go through schema payloads, and tensors through Tensor ports — not through ad-hoc structs. See Payload type rules.

How do I synchronize CUDA streams between operators?

You don’t — not on the hot path. Stream ordering is automatic: committing a tensor records its readiness, and a Tensor you receive is already ordered on your compute stream, so you launch kernels immediately.

Never call cudaStreamSynchronize (or cudaDeviceSynchronize) on the hot path. It stalls the pipeline and defeats the SDK’s asynchronous ordering. If you need CPU access to tensor data, request host access explicitly — that is the sanctioned, visible synchronization point.

See Tensors 101 and Tensors in depth.

How do I stop a running pipeline cleanly?

run_async(plan) returns a RunSession; call session.request_stop() and then session.wait(). The session’s termination() tells you how the run ended (for example RunTermination::kStopped for a requested stop). There is no deadlock-based shutdown idiom as in 4.x — stopping is an explicit, first-class request. See Running and stopping and Restart control.

How does an operator finish the whole run successfully?

Inside compute(), call context.request_completion(). The session reports RunTermination::kRequestedCompleted, distinct from natural finite kCompleted, and blocking run() returns zero. The request is immediate: it closes new callback admission for the whole session, but does not guarantee EOS, source closure, downstream drain, or queued delivery. Never retain ExecutionContext; it is valid only for the callback.

There is no kRequestedStopped because kStopped already denotes the explicit stop category; HSDK 5 has no separate natural-stop success state. It also has no operator-local deactivate/readiness-removal API or deadlock-as-success rule. Count-bounded EOS and drain semantics are separate from this request. See Running and stopping.

Why does compile() return a rejected plan instead of throwing?

A rejected graph is an expected outcome, not an exceptional one — so compile() reports it as a value. Check plan.ok() and, when it is false, read plan.diagnostics(): each diagnostic names what was rejected (an invalid trigger/selector combination, over-subscribed queue capacity, a type mismatch across a flow) before anything runs. Failing at compile time, with a full list of problems, beats failing one bug at a time at runtime. See Compile and check.

Can I use the channel client API inside my graph?

No.

One graph, one vocabulary. Inside a graph, operators communicate through typed ports (Input<T>/Output<T>) wired with add_flow() — the runtime materializes the channels for you. The ChannelRuntime client API exists only for standalone processes outside any graph. Never mix the two: using ChannelRuntime inside an operator bypasses the compiled plan and its guarantees.

See Interprocess channels and Standalone channel clients.

Scope: languages, platforms, operators

Is Python supported?

Yes. Holoscan SDK 5.x provides a Python 3.12+ application-authoring API for defining Python operators, composing them with registered C++ operators, compiling inspectable plans, and controlling run sessions. It includes reusable graphs, graph policy, graph-owned YAML/JSON configuration, scalar and inline-schema external topics, Tensor interop, typed exceptions, registry proxies, and typing stubs.

Python operators subclass holoscan.Operator and declare typed ports, a temporal contract, and a compute() callback. The current surface does not expose C++ managed-signal ownership. Python applications import registered operator providers and manage process signals themselves. Start with the Python guide and use the Python migration guide rather than inferring compatibility from 4.x packages.

Which platforms are supported?

Linux on x86_64 and arm64 with NVIDIA GPUs. The authoritative, per-release platform and CUDA matrix lives on SDK installation — check that page rather than assuming a 4.x-era matrix carries over.

Where are the domain operators — inference, visualization, video I/O?

In Holoscan Modules. 5.x separates the core runtime — the graph/compile/run machinery, temporal contracts, payloads, tensors, and channels — from domain operators, which ship as separately selectable modules on top of it. This release carries three core modules: Inference (InferenceOp), Vision (nine CV-CUDA and NPP operators), and Visualization (HolovizOp). For other capabilities, author your own operators against the core API or look for a community module on HoloHub.

What is the difference between a Holoscan Core Module and a Holoscan External Module?

Both are libraries that extend the SDK with operators, subgraphs, and/or schema. Core modules live in the SDK repository, are released on the SDK cadence as separate, optional Debian packages and Python wheels, and are supported through the SDK release process. External modules live in a separate project, are published by the community or by hardware partners on their own cadence, and are supported by their maintainers. See Core modules and ecosystem modules.

How do I publish my own module?

Scaffold it from the Holoscan CLI module template (holoscan create my-module), follow the HoloHub Create a Holoscan module tutorial, and list it in the HoloHub Modules catalog. If you want to propose a module for the SDK itself, read the core module development guide first. See Build your own module.

Is there anything like distributed applications / fragments?

Cross-process pipelines are built from interprocess channels: processes exchange typed payloads over named external topics rather than partitioning one application object into fragments. See External topics and Interprocess channels.

Migration

I have a 4.x application. Where do I start?

Read Migrating from 4.x first — it maps each 4.x concept (Application, Fragment, Conditions, schedulers, make_operator) to its 5.x counterpart and walks a real port. Then work through the getting-started sequence from Core concepts: the mental model (compose → compile → run, errors as values, contracts) is worth absorbing before you translate code line by line.

Do my 4.x operators or GXF extensions work in 5.x?

No. 5.x is a new runtime, not an API revision — there is no GXF underneath, no entity/codelet layer, and no compatibility shim for 4.x operator classes. Operators are re-authored around the three declarations (setup() / contract() / compute()), which is usually a simplification: scheduling Conditions become contract triggers, and receive/emit boilerplate becomes typed Input<T>/Output<T> calls returning expected. See Migrating from 4.x and Operator anatomy.

Where should I ask a question this page doesn’t answer?

Check the Glossary for unfamiliar terms, the Understanding failures page for any diagnostic the runtime printed, and the Holoscan by example tour for working code to compare against.