Skip to content

Commit b2acf70

Browse files
JakeStevensmeta-codesync[bot]
authored andcommitted
Add ETDump tracing to CpuBackend (#23926)
Summary: Pull Request resolved: #23926 `CPUPlan` records one ETDump delegate event per step, named `provider/implementation:first_node[+N]`, so every provider is traced without provider changes. The XNNPACK provider enables XNNPACK runtime profiling when a tracer is attached and logs `<operator> #<n>` events, such as `Transpose (ND, X32) #3`, inside its step, with each operator's input and output shapes as JSON delegate metadata. Shapes come from XNNPACK's internal runtime (`src/xnnpack/subgraph.h`). That header dependency and the code that reads it are only compiled when `ET_EVENT_TRACER_ENABLED` is defined. `runner` writes an ETDump when `ET_CPU_ETDUMP` is set. CMake builds `XNNProfiler` as a separate `xnnpack_profiler` library that `xnnpack_backend` and the CPU provider targets link, matching the Buck split, and adds XNNPACK's source root to the CPU targets' include path for the traced build. The Apple XNNPACK framework packs the new archive. Authored with Claude Code. Differential Revision: D125525223
1 parent 42fd7f5 commit b2acf70

18 files changed

Lines changed: 444 additions & 41 deletions

File tree

‎backends/cpu/CMakeLists.txt‎

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,12 @@ if(EXECUTORCH_BUILD_NATIVE_CPU)
3535
target_link_libraries(
3636
cpu_xnnpack
3737
PUBLIC cpu_runtime
38-
PRIVATE extension_threadpool
38+
PRIVATE extension_threadpool xnnpack_profiler
39+
)
40+
# Traced builds include XNNPACK's internal runtime header from its source
41+
# root.
42+
target_include_directories(
43+
cpu_xnnpack PRIVATE $<TARGET_PROPERTY:XNNPACK,SOURCE_DIR>
3944
)
4045
if(EXECUTORCH_BUILD_SHARED)
4146
target_link_libraries(cpu_xnnpack PRIVATE xnnpack_shared)
@@ -106,7 +111,10 @@ add_library(
106111
)
107112
target_link_libraries(
108113
cpu_backend PRIVATE native_program extension_threadpool
109-
optimized_native_cpu_ops_lib
114+
optimized_native_cpu_ops_lib xnnpack_profiler
115+
)
116+
target_include_directories(
117+
cpu_backend PRIVATE $<TARGET_PROPERTY:XNNPACK,SOURCE_DIR>
110118
)
111119
if(EXECUTORCH_BUILD_SHARED)
112120
set_target_properties(

‎backends/cpu/runtime/CPUBackend.cpp‎

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -93,7 +93,10 @@ class CPUExecution {
9393
return error;
9494
}
9595
plan_ = std::make_unique<CPUPlan>(
96-
graph, buffers_, std::move(configuration), current_execution_context());
96+
graph,
97+
buffers_,
98+
std::move(configuration),
99+
current_execution_context(context.event_tracer()));
97100
error = plan_->select();
98101
if (error != Error::Ok) {
99102
return error;

‎backends/cpu/runtime/CPUPlan.cpp‎

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
// LICENSE file in the root directory of this source tree.
66

77
#include <executorch/backends/cpu/runtime/CPUPlan.h>
8+
#include <executorch/runtime/core/event_tracer_hooks_delegate.h>
89

910
#include <algorithm>
1011
#include <cstring>
@@ -174,7 +175,13 @@ Error CPUPlan::execute(const ExecutionContext& context) {
174175
}
175176
}
176177
for (auto& step : steps_) {
178+
const auto entry = event_tracer_start_profiling_delegate(
179+
context.event_tracer,
180+
step.trace_name.c_str(),
181+
kUnsetDelegateDebugIntId);
177182
const auto error = step.executable->run(context);
183+
event_tracer_end_profiling_delegate(context.event_tracer, entry);
184+
step.executable->submit_trace();
178185
if (error != Error::Ok) {
179186
return error;
180187
}
@@ -344,7 +351,7 @@ Error CPUPlan::select() {
344351
// cycle.
345352
if (steps_.empty() || steps_.back().implementation != selected ||
346353
!selected->accepts_regions() || !connected) {
347-
steps_.push_back({selected_provider, selected, {}, nullptr, {}});
354+
steps_.push_back({selected_provider, selected, {}, nullptr, {}, {}});
348355
}
349356
steps_.back().region.nodes.push_back(id);
350357
node_regions.at(id) = steps_.size() - 1;
@@ -382,6 +389,14 @@ Error CPUPlan::select() {
382389
step.region.outputs.assign(outputs.begin(), outputs.end());
383390
std::sort(step.region.inputs.begin(), step.region.inputs.end());
384391
std::sort(step.region.outputs.begin(), step.region.outputs.end());
392+
#ifdef ET_EVENT_TRACER_ENABLED
393+
step.trace_name = std::string(step.provider->name()) + "/" +
394+
std::string(step.implementation->name()) + ":" +
395+
graph.node(step.region.nodes.front()).name;
396+
if (step.region.nodes.size() > 1) {
397+
step.trace_name += "+" + std::to_string(step.region.nodes.size() - 1);
398+
}
399+
#endif
385400
}
386401
requirements_.resize(graph.values.size());
387402
for (size_t id = 0; id < graph.values.size(); ++id) {

‎backends/cpu/runtime/CPUPlan.h‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@ struct PlanStep {
1818
KernelRegion region;
1919
std::unique_ptr<Executable> executable;
2020
StorageRequirements storage;
21+
/// ETDump delegate event name: provider/implementation:first_node[+N].
22+
std::string trace_name;
2123
};
2224

2325
/// One static prepared plan. Graph, bindings and allocator outlive the plan.

‎backends/cpu/runtime/KernelProvider.h‎

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,8 @@ runtime::Result<Buffer> allocate_buffer(
6565
struct ExecutionContext {
6666
size_t threads = 1;
6767
pthreadpool* threadpool = nullptr;
68+
/// Providers enable internal profiling when set during preparation. The plan
69+
/// records one delegate event per step when set during execution.
6870
runtime::EventTracer* event_tracer = nullptr;
6971
};
7072

@@ -122,6 +124,9 @@ class Executable {
122124
virtual runtime::Error reshape() = 0;
123125
virtual runtime::Error bind(const Buffer& scratch = {}) = 0;
124126
virtual runtime::Error run(const ExecutionContext& context) = 0;
127+
/// Called after the plan closes the step's trace event, so internal events
128+
/// are logged without inflating it. Called even when run fails.
129+
virtual void submit_trace() {}
125130
};
126131

127132
class KernelImplementation {

‎backends/cpu/runtime/providers/xnnpack/XNNPACKProvider.cpp‎

Lines changed: 90 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,22 +6,29 @@
66

77
#include <executorch/backends/cpu/runtime/op_helpers/Convolution.h>
88
#include <executorch/backends/cpu/runtime/providers/xnnpack/XNNPACKProvider.h>
9+
#include <executorch/backends/xnnpack/runtime/profiling/XNNProfiler.h>
910
#include <executorch/extension/memory_allocator/malloc_memory_allocator.h>
1011
#include <xnnpack.h>
12+
#ifdef ET_EVENT_TRACER_ENABLED
13+
#include "src/xnnpack/subgraph.h" // @manual
14+
#endif
1115

1216
#include <algorithm>
1317
#include <array>
1418
#include <cmath>
1519
#include <limits>
1620
#include <map>
1721
#include <numeric>
22+
#include <string>
1823
#include <string_view>
24+
#include <unordered_map>
1925
#include <unordered_set>
2026
#include <utility>
2127

2228
namespace executorch::backends::cpu {
2329
namespace {
2430
using namespace executorch::runtime;
31+
using executorch::backends::xnnpack::delegate::profiling::XNNProfiler;
2532

2633
Error check(xnn_status status, const char* operation) {
2734
if (status == xnn_status_success) {
@@ -41,6 +48,17 @@ Error check(xnn_status status, const char* operation) {
4148
} \
4249
} while (false)
4350

51+
bool profiling_ok(Error error, const char* operation) {
52+
if (error != Error::Ok) {
53+
ET_LOG(
54+
Error,
55+
"CPU XNNPACK profiling %s failed: %u",
56+
operation,
57+
static_cast<unsigned int>(error));
58+
}
59+
return error == Error::Ok;
60+
}
61+
4462
bool constant(const ptn::Value& value) {
4563
return value.role == ptn::ValueRole::Parameter ||
4664
value.role == ptn::ValueRole::ConstantTensor ||
@@ -244,6 +262,8 @@ class XNNState {
244262
return std::exchange(packing_source_bytes_, 0);
245263
}
246264
std::map<std::pair<ptn::ValueId, bool>, Buffer> converted_weights;
265+
/// Numbers ETDump operator names uniquely across the provider's regions.
266+
std::unordered_map<std::string, uint32_t> operator_counts;
247267
xnn_workspace_t workspace = nullptr;
248268
xnn_weights_cache_t cache = nullptr;
249269

@@ -334,13 +354,27 @@ class XNNExecutable final : public Executable {
334354
return error;
335355
}
336356
}
357+
uint32_t runtime_flags = 0;
358+
#ifdef ET_EVENT_TRACER_ENABLED
359+
auto* tracer = context_.execution.event_tracer;
360+
if (tracer &&
361+
tracer->event_tracer_profiling_level() >
362+
EventTracerProfilingLevel::kProfileMethodOnly) {
363+
runtime_flags |= XNN_FLAG_BASIC_PROFILING;
364+
}
365+
#endif
337366
CPU_XNN_CHECK(xnn_create_runtime_v4(
338367
subgraph_,
339368
state_.cache,
340369
state_.workspace,
341370
context_.execution.threadpool,
342-
0,
371+
runtime_flags,
343372
&runtime_));
373+
if (runtime_flags & XNN_FLAG_BASIC_PROFILING) {
374+
profiling_ = profiling_ok(
375+
profiler_.initialize(runtime_, &state_.operator_counts),
376+
"initialization");
377+
}
344378
return Error::Ok;
345379
}
346380

@@ -359,6 +393,11 @@ class XNNExecutable final : public Executable {
359393
"XNNPACK inferred a different semantic shape for %s",
360394
context_.graph.value(binding.id).name.c_str());
361395
}
396+
#ifdef ET_EVENT_TRACER_ENABLED
397+
if (profiling_) {
398+
profiling_ok(describe_operators(), "metadata");
399+
}
400+
#endif
362401
return Error::Ok;
363402
}
364403

@@ -381,19 +420,65 @@ class XNNExecutable final : public Executable {
381420
return Error::Ok;
382421
}
383422

384-
Error run(const ExecutionContext&) override {
423+
Error run(const ExecutionContext& context) override {
385424
ET_CHECK_OR_RETURN_ERROR(
386425
bound_, InvalidState, "XNNPACK requires a successful bind");
387426
// Shared workspace growth can relocate another region's internal values.
388427
CPU_XNN_CHECK(
389428
xnn_setup_runtime_v2(runtime_, external_.size(), external_.data()));
429+
tracing_ = profiling_ &&
430+
profiling_ok(profiler_.start(context.event_tracer), "start");
390431
CPU_XNN_CHECK(xnn_invoke_runtime(runtime_));
391432
return Error::Ok;
392433
}
393434

435+
void submit_trace() override {
436+
if (tracing_) {
437+
tracing_ = false;
438+
profiling_ok(profiler_.end(), "end");
439+
}
440+
}
441+
394442
private:
395443
friend const XNNOp* find_op(std::string_view target);
396444

445+
#ifdef ET_EVENT_TRACER_ENABLED
446+
// Reads XNNPACK's internal runtime, which the public API does not expose.
447+
// xnn_get_runtime_profiling_info reports names and timings for opdata with a
448+
// primary operator, in opdata order; this must apply the same filter.
449+
Error describe_operators() {
450+
std::vector<std::string> shapes;
451+
for (size_t index = 0; index < runtime_->num_ops; ++index) {
452+
const auto& op = runtime_->opdata[index];
453+
if (op.operator_objects[0] != nullptr) {
454+
shapes.push_back(
455+
"{\"inputs\":" + value_shapes(op.inputs, op.num_inputs) +
456+
",\"outputs\":" + value_shapes(op.outputs, op.num_outputs) + "}");
457+
}
458+
}
459+
return profiler_.set_operator_metadata(std::move(shapes));
460+
}
461+
462+
std::string value_shapes(const uint32_t* ids, uint32_t count) const {
463+
std::string text = "[";
464+
for (uint32_t index = 0; index < count; ++index) {
465+
text += index ? "," : "";
466+
if (ids == nullptr || ids[index] == XNN_INVALID_VALUE_ID ||
467+
ids[index] >= runtime_->num_values) {
468+
text += "null";
469+
continue;
470+
}
471+
const auto& shape = runtime_->values[ids[index]].shape;
472+
text += "[";
473+
for (size_t dim = 0; dim < shape.num_dims; ++dim) {
474+
text += (dim ? "," : "") + std::to_string(shape.dim[dim]);
475+
}
476+
text += "]";
477+
}
478+
return text + "]";
479+
}
480+
#endif
481+
397482
std::vector<size_t> dimensions(ptn::ValueId id) const {
398483
const auto& sizes = context_.graph.value(id).tensor_meta().sizes;
399484
return {sizes.begin(), sizes.end()};
@@ -1122,6 +1207,9 @@ class XNNExecutable final : public Executable {
11221207
bool bound_ = false;
11231208
std::vector<Binding> bindings_;
11241209
std::vector<xnn_external_value> external_;
1210+
XNNProfiler profiler_;
1211+
bool profiling_ = false;
1212+
bool tracing_ = false;
11251213
};
11261214

11271215
const XNNOp* find_op(std::string_view target) {

0 commit comments

Comments
 (0)