Skip to content

Commit f5f595e

Browse files
committed
Add gflags-free Cadence ISS executor runner for OSS CI
Add cadence_executor_runner, a cross-compile-friendly ExecuTorch runner for Cadence Xtensa cores targeting the Instruction Set Simulator (xt-run). The upstream executor_runner cannot cross-compile to Xtensa because gflags pulls in mkdir(2), absent from Xtensa newlib; this uses plain argv parsing like the Arm and NXP backends. It loads a .pte via xt-run semi-hosting, runs the first method with all-ones inputs, and prints outputs. EXECUTORCH_BUILD_CADENCE_RUNNER builds the runner, linking cadence_ops_lib transitively (no --whole-archive, which would double-run static kernel registration). -lidma is linked only for Vision/Fusion-G3 cores, whose ops reference iDMA and whose LSPs ship libidma; HiFi4 does not and its LSP lacks it. Also register op_quantized_depthwise_conv1d_{ncl,nlc}.cpp in the HiFi4 operators CMakeLists: codegen references these kernels, so omitting the sources broke the cross-compile link.
1 parent 193574d commit f5f595e

3 files changed

Lines changed: 235 additions & 0 deletions

File tree

backends/cadence/CMakeLists.txt

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,3 +97,38 @@ else()
9797
endif()
9898

9999
add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/${TARGET_DIR}/operators)
100+
101+
# Cadence executor_runner: cross-compiled ExecuTorch runner for the Xtensa ISS
102+
# (xt-run / xt-run --turbo). Self-contained, gflags-free argv parser, reads .pte
103+
# via xt-run semi-hosting.
104+
#
105+
# Usage: cmake ... -DEXECUTORCH_BUILD_CADENCE_RUNNER=ON xt-run --turbo
106+
# cmake-out/backends/cadence/cadence_executor_runner \ --model_path=add.pte
107+
if(EXECUTORCH_BUILD_CADENCE_RUNNER)
108+
add_executable(cadence_executor_runner cadence_executor_runner.cpp)
109+
target_compile_definitions(
110+
cadence_executor_runner PRIVATE ET_ENABLE_ENUM_STRINGS=0
111+
)
112+
target_include_directories(
113+
cadence_executor_runner
114+
PRIVATE ${_common_include_directories} ${CMAKE_BINARY_DIR}
115+
${CMAKE_BINARY_DIR}/include
116+
)
117+
# Mirror the upstream executor_runner cadence link list (top-level
118+
# CMakeLists.txt: list(APPEND _executor_runner_libs cadence_ops_lib)). Do NOT
119+
# add --whole-archive: cadence_ops_lib is also pulled transitively, and
120+
# forcing a second copy double-runs its static kernel-registration
121+
# initializers and asserts at runtime.
122+
target_link_libraries(
123+
cadence_executor_runner PRIVATE executorch extension_evalue_util
124+
extension_runner_util cadence_ops_lib
125+
)
126+
# Vision and Fusion-G3 ops (e.g. op_softmax) reference iDMA scheduling symbols
127+
# and those cores ship libidma in their LSP. HiFi4 and generic cores do not
128+
# use iDMA and their LSPs may not provide libidma, so only link it for the
129+
# cores that need it.
130+
if(EXECUTORCH_VISION_OPT OR EXECUTORCH_FUSION_G3_OPT)
131+
target_link_options(cadence_executor_runner PRIVATE -lidma)
132+
endif()
133+
target_link_options(cadence_executor_runner PRIVATE -static -lm)
134+
endif()
Lines changed: 198 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,198 @@
1+
/*
2+
* Copyright (c) Meta Platforms, Inc. and affiliates.
3+
* All rights reserved.
4+
*
5+
* This source code is licensed under the BSD-style license found in the
6+
* LICENSE file in the root directory of this source tree.
7+
*/
8+
9+
/**
10+
* @file
11+
*
12+
* ExecuTorch runner for Cadence Xtensa cores, intended to run on the
13+
* Xtensa Instruction Set Simulator (xt-run / xt-run --turbo).
14+
*
15+
* Reads a .pte from the host filesystem via xt-run semi-hosting,
16+
* executes the first method with all-ones inputs (via
17+
* prepare_input_tensors), and prints the outputs.
18+
*
19+
* Argument parsing is plain argv inspection — gflags pulls in
20+
* mkdir(2), which Xtensa newlib does not declare, breaking
21+
* cross-compile. Mirrors the same approach Arm and NXP take in their
22+
* embedded runners.
23+
*
24+
* Usage:
25+
* xt-run --turbo cadence_executor_runner --model_path=add.pte
26+
* xt-run --mem_model --summary cadence_executor_runner --model_path=add.pte
27+
*/
28+
29+
#include <cstdio>
30+
#include <cstdlib>
31+
#include <cstring>
32+
#include <memory>
33+
#include <string>
34+
// patternlint-disable executorch-cpp-nostdinc
35+
#include <vector>
36+
37+
#include <executorch/extension/data_loader/buffer_data_loader.h>
38+
#include <executorch/extension/runner_util/inputs.h>
39+
#include <executorch/runtime/executor/method.h>
40+
#include <executorch/runtime/executor/program.h>
41+
#include <executorch/runtime/platform/log.h>
42+
#include <executorch/runtime/platform/runtime.h>
43+
44+
using executorch::runtime::Error;
45+
using executorch::runtime::Result;
46+
47+
namespace {
48+
49+
// 18 KB has historically been enough for the cadence "hello world"
50+
// models (add, simple MLP). Bump if you hit MemoryAllocator overflow
51+
// at load_method time.
52+
constexpr std::size_t kMethodAllocatorBytes = 18 * 1024U;
53+
uint8_t method_allocator_pool[kMethodAllocatorBytes];
54+
55+
const char* parse_model_path(int argc, char** argv) {
56+
constexpr char kFlag[] = "--model_path=";
57+
constexpr std::size_t kFlagLen = sizeof(kFlag) - 1;
58+
for (int i = 1; i < argc; ++i) {
59+
if (std::strncmp(argv[i], kFlag, kFlagLen) == 0) {
60+
// Static so the returned pointer stays valid after parse returns.
61+
static std::string path{argv[i] + kFlagLen};
62+
return path.c_str();
63+
}
64+
}
65+
return "model.pte";
66+
}
67+
68+
bool slurp(const char* path, std::vector<uint8_t>* out) {
69+
FILE* f = std::fopen(path, "rb");
70+
if (!f) {
71+
ET_LOG(Error, "fopen('%s') failed", path);
72+
return false;
73+
}
74+
std::fseek(f, 0, SEEK_END);
75+
long sz = std::ftell(f);
76+
std::fseek(f, 0, SEEK_SET);
77+
if (sz <= 0) {
78+
ET_LOG(Error, "model file '%s' is empty or stat failed", path);
79+
std::fclose(f);
80+
return false;
81+
}
82+
out->resize(static_cast<std::size_t>(sz));
83+
std::size_t n = std::fread(out->data(), 1, sz, f);
84+
std::fclose(f);
85+
if (static_cast<long>(n) != sz) {
86+
ET_LOG(Error, "fread short on '%s': %zu/%ld", path, n, sz);
87+
return false;
88+
}
89+
ET_LOG(Info, "Loaded %ld bytes from %s", sz, path);
90+
return true;
91+
}
92+
93+
} // namespace
94+
95+
int main(int argc, char** argv) {
96+
executorch::runtime::runtime_init();
97+
98+
std::vector<uint8_t> model;
99+
const char* path = parse_model_path(argc, argv);
100+
if (!slurp(path, &model)) {
101+
return 1;
102+
}
103+
104+
auto loader =
105+
executorch::extension::BufferDataLoader(model.data(), model.size());
106+
107+
Result<executorch::runtime::Program> program =
108+
executorch::runtime::Program::load(&loader);
109+
if (!program.ok()) {
110+
ET_LOG(Error, "Program::load failed: 0x%" PRIx32, program.error());
111+
return 1;
112+
}
113+
ET_LOG(Info, "Model buffer loaded, has %u methods", program->num_methods());
114+
115+
const char* method_name = nullptr;
116+
{
117+
const auto method_name_result = program->get_method_name(0);
118+
ET_CHECK_MSG(method_name_result.ok(), "Program has no methods");
119+
method_name = *method_name_result;
120+
}
121+
ET_LOG(Info, "Running method %s", method_name);
122+
123+
Result<executorch::runtime::MethodMeta> method_meta =
124+
program->method_meta(method_name);
125+
if (!method_meta.ok()) {
126+
ET_LOG(
127+
Error,
128+
"method_meta('%s') failed: 0x%x",
129+
method_name,
130+
(unsigned int)method_meta.error());
131+
return 1;
132+
}
133+
134+
executorch::runtime::MemoryAllocator method_allocator(
135+
sizeof(method_allocator_pool), method_allocator_pool);
136+
137+
std::vector<std::unique_ptr<uint8_t[]>> planned_buffers;
138+
std::vector<executorch::runtime::Span<uint8_t>> planned_spans;
139+
const std::size_t num_planned = method_meta->num_memory_planned_buffers();
140+
for (std::size_t id = 0; id < num_planned; ++id) {
141+
const std::size_t buffer_size = static_cast<std::size_t>(
142+
method_meta->memory_planned_buffer_size(id).get());
143+
ET_LOG(Info, "Setting up planned buffer %zu, size %zu", id, buffer_size);
144+
planned_buffers.push_back(std::make_unique<uint8_t[]>(buffer_size));
145+
planned_spans.push_back({planned_buffers.back().get(), buffer_size});
146+
}
147+
executorch::runtime::HierarchicalAllocator planned_memory(
148+
{planned_spans.data(), planned_spans.size()});
149+
150+
executorch::runtime::MemoryManager memory_manager(
151+
&method_allocator, &planned_memory);
152+
153+
Result<executorch::runtime::Method> method =
154+
program->load_method(method_name, &memory_manager);
155+
if (!method.ok()) {
156+
ET_LOG(
157+
Error,
158+
"load_method('%s') failed: 0x%" PRIx32,
159+
method_name,
160+
method.error());
161+
return 1;
162+
}
163+
ET_LOG(Info, "Method loaded.");
164+
165+
auto cleanup = executorch::extension::prepare_input_tensors(*method);
166+
if (!cleanup.ok()) {
167+
ET_LOG(
168+
Error,
169+
"prepare_input_tensors failed: 0x%x",
170+
(unsigned int)cleanup.error());
171+
return 1;
172+
}
173+
ET_LOG(Info, "Starting model execution...");
174+
175+
Error status = method->execute();
176+
if (status != Error::Ok) {
177+
ET_LOG(Error, "execute() failed for '%s': 0x%" PRIx32, method_name, status);
178+
return 1;
179+
}
180+
ET_LOG(Info, "Model executed successfully.");
181+
182+
std::vector<executorch::runtime::EValue> outputs(method->outputs_size());
183+
method->get_outputs(outputs.data(), outputs.size());
184+
for (std::size_t i = 0; i < outputs.size(); ++i) {
185+
if (!outputs[i].isTensor()) {
186+
ET_LOG(Info, "output[%zu]: non-tensor", i);
187+
continue;
188+
}
189+
const auto& t = outputs[i].toTensor();
190+
const float* p = t.const_data_ptr<float>();
191+
const std::size_t n = t.numel() < 20 ? t.numel() : 20;
192+
ET_LOG(Info, "First %zu elements of output %zu:", n, i);
193+
for (std::size_t j = 0; j < n; ++j) {
194+
ET_LOG(Info, " %f", p[j]);
195+
}
196+
}
197+
return 0;
198+
}

backends/cadence/hifi/operators/CMakeLists.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -134,6 +134,8 @@ add_library(
134134
"op_quantized_conv2d_nchw_out.cpp"
135135
"op_quantized_conv1d_ncl.cpp"
136136
"op_quantized_conv1d_nlc.cpp"
137+
"op_quantized_depthwise_conv1d_ncl.cpp"
138+
"op_quantized_depthwise_conv1d_nlc.cpp"
137139
"op_quantized_conv2d_nchw_asym8sxsym8s_asym8s_per_tensor_out.cpp"
138140
"op_quantized_conv2d_nchw_asym8uxsym8u_asym8u_per_tensor_out.cpp"
139141
"op_quantized_conv2d_nchw_depthwise_asym8sxsym8s_asym8s_per_tensor_out.cpp"

0 commit comments

Comments
 (0)