Skip to content

Commit 5e93494

Browse files
JakeStevensfacebook-github-bot
authored andcommitted
Add ETDump tracing to CpuBackend (pytorch#23926)
Summary: `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 c8220d9 commit 5e93494

23 files changed

Lines changed: 573 additions & 106 deletions

File tree

‎.claude/faq.md‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ Error codes defined in `runtime/core/error.h`.
77
|------|------|--------------|
88
| 0x10 | InvalidArgument | Input shape mismatch - inputs don't match export shapes. Use dynamic shapes if needed. |
99
| 0x14 | OperatorMissing | Selective build missing operator. Regenerate `et_operator_library` from current model. |
10-
| 0x20 | NotFound | Missing backend. Link with `--whole-archive`: `-Wl,--whole-archive libxnnpack_backend.a -Wl,--no-whole-archive` |
10+
| 0x20 | NotFound | Missing backend. Link with `--whole-archive`: `-Wl,--whole-archive libxnnpack_backend.a -Wl,--no-whole-archive libxnnpack_profiler.a` |
1111

1212
## Export Issues
1313

‎.claude/skills/building/SKILL.md‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -202,7 +202,7 @@ Most commonly needed flags (full list: `CMakeLists.txt`):
202202
| Artifact | Location |
203203
|----------|----------|
204204
| Core runtime | `cmake-out/lib/libexecutorch.a` |
205-
| XNNPACK backend | `cmake-out/lib/libxnnpack_backend.a` |
205+
| XNNPACK backend | `cmake-out/lib/libxnnpack_backend.a` and `cmake-out/lib/libxnnpack_profiler.a` |
206206
| executor_runner | `cmake-out/executor_runner` (Ninja/Make) or `cmake-out/Release/executor_runner` (Xcode) |
207207
| Model runners | `cmake-out/examples/models/<model>/<runner>` |
208208

‎backends/cpu/CMakeLists.txt‎

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -35,8 +35,15 @@ 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
3939
)
40+
if(EXECUTORCH_ENABLE_EVENT_TRACER)
41+
# Traced builds include XNNPACK's internal runtime header from its source
42+
# root.
43+
target_include_directories(
44+
cpu_xnnpack PRIVATE $<TARGET_PROPERTY:XNNPACK,SOURCE_DIR>
45+
)
46+
endif()
4047
if(EXECUTORCH_BUILD_SHARED)
4148
target_link_libraries(cpu_xnnpack PRIVATE xnnpack_shared)
4249
else()
@@ -106,8 +113,13 @@ add_library(
106113
)
107114
target_link_libraries(
108115
cpu_backend PRIVATE native_program extension_threadpool
109-
optimized_native_cpu_ops_lib
116+
optimized_native_cpu_ops_lib xnnpack_profiler
110117
)
118+
if(EXECUTORCH_ENABLE_EVENT_TRACER)
119+
target_include_directories(
120+
cpu_backend PRIVATE $<TARGET_PROPERTY:XNNPACK,SOURCE_DIR>
121+
)
122+
endif()
111123
if(EXECUTORCH_BUILD_SHARED)
112124
set_target_properties(
113125
cpu_backend PROPERTIES OUTPUT_NAME executorch_backend_cpu

‎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: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,9 @@ 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+
/// Empty unless ET_EVENT_TRACER_ENABLED is defined.
23+
std::string trace_name;
2124
};
2225

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

‎backends/cpu/runtime/KernelProvider.h‎

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,9 @@ 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 with a
69+
/// profiling level above kProfileMethodOnly. The plan records one delegate
70+
/// event per step at every level when set during execution.
6871
runtime::EventTracer* event_tracer = nullptr;
6972
};
7073

@@ -122,6 +125,10 @@ class Executable {
122125
virtual runtime::Error reshape() = 0;
123126
virtual runtime::Error bind(const Buffer& scratch = {}) = 0;
124127
virtual runtime::Error run(const ExecutionContext& context) = 0;
128+
/// Callers of run must call this afterwards, even when run fails. The plan
129+
/// calls it after closing the step's trace event, so internal events are
130+
/// logged without inflating it.
131+
virtual void submit_trace() {}
125132
};
126133

127134
class KernelImplementation {

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

Lines changed: 92 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,67 @@ class XNNExecutable final : public Executable {
381420
return Error::Ok;
382421
}
383422

384-
Error run(const ExecutionContext&) override {
423+
Error run(const ExecutionContext& context) override {
424+
// Closes a session left open by a caller that skipped submit_trace.
425+
submit_trace();
385426
ET_CHECK_OR_RETURN_ERROR(
386427
bound_, InvalidState, "XNNPACK requires a successful bind");
387428
// Shared workspace growth can relocate another region's internal values.
388429
CPU_XNN_CHECK(
389430
xnn_setup_runtime_v2(runtime_, external_.size(), external_.data()));
431+
tracing_ = profiling_ &&
432+
profiling_ok(profiler_.start(context.event_tracer), "start");
390433
CPU_XNN_CHECK(xnn_invoke_runtime(runtime_));
391434
return Error::Ok;
392435
}
393436

437+
void submit_trace() override {
438+
if (tracing_) {
439+
tracing_ = false;
440+
profiling_ok(profiler_.end(), "end");
441+
}
442+
}
443+
394444
private:
395445
friend const XNNOp* find_op(std::string_view target);
396446

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

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

0 commit comments

Comments
 (0)