Skip to content

Tracing events do not match the advertised structure #8917

@alexreinking

Description

@alexreinking

In HalideRuntime.h, we document the meaning of a trace event's parent as follows:

* halide_trace returns a unique ID which will be passed to future
* events that "belong" to the earlier event as the parent id. The
* ownership hierarchy looks like:
*
* begin_pipeline
* +--trace_tag (if any)
* +--trace_tag (if any)
* ...
* +--begin_realization
* | +--produce
* | | +--load/store
* | | +--end_produce
* | +--consume
* | | +--load
* | | +--end_consume
* | +--end_realization
* +--end_pipeline

From this, we see that loads may be owned by either produce events or consume events, and that these are further tied to a specific realization. Every trace packet also contains a reference to a func name1, as shown here:

/** Get the func name, assuming this packet is laid out in memory
* as it was written. It comes after the value. */
HALIDE_ALWAYS_INLINE const char *func() const {
return (const char *)value() + type.lanes * type.bytes();
}
HALIDE_ALWAYS_INLINE char *func() {
return (char *)value() + type.lanes * type.bytes();
}

Clearly, for a load packet, this is the name of the func loaded from.

So one is left to assume that the parent PRODUCE node of a load would be tied to the func whose realization is doing the loading, right? Unfortunately, no. The lowering in Tracing.cpp uses the load target's name both for writing out the name in the packet directly, as seen here:

Halide/src/Tracing.cpp

Lines 141 to 149 in 5423753

TraceEventBuilder builder;
builder.func = op->name;
builder.value = {value_var};
builder.coordinates = op->args;
builder.type = op->type;
builder.event = halide_trace_load;
builder.parent_id = trace_parent;
builder.value_index = op->value_index;
Expr trace = builder.build();

and also for determining the parent ID, which always points to a CONSUME node, as seen here:

Halide/src/Tracing.cpp

Lines 112 to 123 in 5423753

if (op->call_type == Call::Halide) {
auto it = env.find(op->name);
internal_assert(it != env.end()) << op->name << " not in environment\n";
Function f = it->second;
internal_assert(!f.can_be_inlined() || !f.schedule().compute_level().is_inlined());
trace_it = trace_all_loads || f.is_tracing_loads();
trace_parent = Variable::make(Int(32), op->name + ".trace_id");
if (trace_it) {
add_trace_tags(op->name, f.get_trace_tags());
touch(funcs_touched, op->name, op->value_index, op->type);
}

Even more vexingly, loads to ImageParams have the pipeline itself as the parent, which is not even permitted under the documentation.

Halide/src/Tracing.cpp

Lines 124 to 135 in 5423753

} else if (op->call_type == Call::Image) {
// op->param is defined when we're loading from an ImageParam, and undefined
// when we're loading from an inlined Buffer.
trace_it = trace_all_loads || (op->param.defined() && op->param.is_tracing_loads());
trace_parent = Variable::make(Int(32), "pipeline.trace_id");
if (trace_it) {
if (op->param.defined()) {
add_trace_tags(op->name, op->param.get_trace_tags());
}
touch(images_touched, op->name, op->value_index, op->type);
}
}

In all cases, op refers to a Call node in the IR.

Footnotes

  1. Because a func might be realized multiple times in parallel, I believe the name alone is insufficient to determine a true load target, unless there is some additional invariant we can use to disambiguate.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions