Skip to content

Commit 305638c

Browse files
mergennachinclaude
andauthored
Refactor examples/kev into examples/systemone/kev (pytorch#23341)
## Summary - Splits the generic System One decision-inference C++ interface (question/answer types, the `SystemOne` base class) out of `examples/kev` into `examples/systemone/api.h`, in the `executorch::systemone` namespace, so other models can implement the same interface. - Moves everything Kev-specific (`kev.h`/`kev.cpp`, `main.cpp`, `benchmark.cpp`, `export.py`, `model.py`, `CMakeLists.txt`) into `examples/systemone/kev/`, which now implements that interface for the Kev model. - Adds `examples/systemone/README.md` describing the generic interface and pointing at `kev/` as the one model implementing it so far. ## Test plan - [x] `examples/systemone/api.h` syntax-checks standalone with clang (`-fsyntax-only`) - [x] `cmake -S examples/systemone/kev -B ...` configures successfully with the updated `EXECUTORCH_ROOT` path - [x] `cmake --build ... --target kev_runner` compiles hundreds of ExecuTorch/XNNPACK sources before hitting a pre-existing, unrelated build failure in ExecuTorch's own core runtime (torch/c10 version mismatch in the local environment) -- no file touched by this change is implicated - [ ] Full build/run of `kev_runner`/`kev_benchmark` against an exported `.pte` (blocked locally by the environment issue above; paths and namespaces reviewed manually) Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
1 parent 9dedab4 commit 305638c

10 files changed

Lines changed: 81 additions & 37 deletions

File tree

‎examples/systemone/README.md‎

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
# System One: native structured decision inference
2+
3+
System One models make fast, structured decisions — a Choice among named
4+
options, a Noul (yes/no), or a Score over ordered levels — from a shared text
5+
snapshot, returning probabilities and a confidence value instead of generated
6+
text. This directory provides a model-agnostic C++ interface for embedding
7+
them, following
8+
[TypeSafe's SDK operation](https://docs.typesafe.ai/sdk/python/api/clients/sync)
9+
and [typed SDK](https://docs.typesafe.ai/sdk/python/api/types/questions).
10+
11+
[api.h](api.h) defines the question and answer types (`Choice`, `Noul`,
12+
`Score`, `ChoiceAnswer`, `NoulAnswer`, `ScoreAnswer`) and the virtual
13+
`SystemOne::system_one(state, questions)` interface in the
14+
`executorch::systemone` namespace. It is shared across models; it has no
15+
dependency on any one model's tokenizer, export format, or runtime.
16+
17+
Model-specific implementations live in their own subdirectory, each
18+
borrowing or embedding an ExecuTorch `Module` and providing a concrete
19+
`SystemOne` subclass:
20+
21+
- [kev/](kev/) — [Kev](https://huggingface.co/jaredpalmer/kev-0.8b), a System
22+
One model, using XNNPACK on CPU or MLX on Apple GPUs.
23+
24+
See a model's subdirectory for export, build, and benchmarking instructions.
Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,8 @@
1616
#include <variant>
1717
#include <vector>
1818

19-
namespace kev {
19+
namespace executorch {
20+
namespace systemone {
2021

2122
struct Choice {
2223
std::string instructions;
@@ -70,4 +71,5 @@ class SystemOne {
7071
const Questions& questions) = 0;
7172
};
7273

73-
} // namespace kev
74+
} // namespace systemone
75+
} // namespace executorch
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ project(kev LANGUAGES C CXX)
2424

2525
set(CMAKE_CXX_STANDARD 20)
2626
set(CMAKE_CXX_STANDARD_REQUIRED ON)
27-
get_filename_component(EXECUTORCH_ROOT "../.." ABSOLUTE)
27+
get_filename_component(EXECUTORCH_ROOT "../../.." ABSOLUTE)
2828

2929
option(BUILD_SHARED_LIBS "" OFF)
3030
option(EXECUTORCH_BUILD_EXECUTOR_RUNNER "" OFF)
Lines changed: 27 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
# Native Kev decision inference
22

3-
Embed [Kev](https://huggingface.co/jaredpalmer/kev-0.8b) in a C++ application
4-
with ExecuTorch's `Module` API, using XNNPACK on CPU or MLX on Apple GPUs.
3+
Embed [Kev](https://huggingface.co/jaredpalmer/kev-0.8b), a System One model,
4+
in a C++ application with ExecuTorch's `Module` API, using XNNPACK on CPU or
5+
MLX on Apple GPUs.
56
Prefill a shared text once, then ask batches of questions from that snapshot.
67
Inference uses the native binary, model, and tokenizer; Python is needed for
78
export.
@@ -27,10 +28,10 @@ hf download jaredpalmer/kev-0.8b \
2728
--revision 54f4f8777356cd5bbbb6c6919c657f26e6f2f6d8 \
2829
--local-dir kev-checkpoint
2930

30-
PYTHONPATH="$HOME/kev:$PYTHONPATH" python examples/kev/export.py \
31+
PYTHONPATH="$HOME/kev:$PYTHONPATH" python examples/systemone/kev/export.py \
3132
--checkpoint kev-checkpoint --backend xnnpack --dtype fp32 --output kev-cpu
3233

33-
PYTHONPATH="$HOME/kev:$PYTHONPATH" python examples/kev/export.py \
34+
PYTHONPATH="$HOME/kev:$PYTHONPATH" python examples/systemone/kev/export.py \
3435
--checkpoint kev-checkpoint --backend mlx --dtype bf16 --output kev-mlx
3536
```
3637

@@ -44,7 +45,7 @@ The default limits are 384 prefix tokens and 1,024 tokens for the prefix plus
4445
one question. Set larger export bounds for longer documents:
4546

4647
```bash
47-
PYTHONPATH="$HOME/kev:$PYTHONPATH" python examples/kev/export.py \
48+
PYTHONPATH="$HOME/kev:$PYTHONPATH" python examples/systemone/kev/export.py \
4849
--checkpoint kev-checkpoint --backend mlx --dtype bf16 --output kev-mlx-long \
4950
--max-prefix 2048 --max-context 4096
5051
```
@@ -63,7 +64,7 @@ returns the fitted temperature as a float; `get_checkpoint_id` returns
6364
From the ExecuTorch root, with submodules initialized:
6465

6566
```bash
66-
cmake -S examples/kev -B cmake-out/kev-cpu \
67+
cmake -S examples/systemone/kev -B cmake-out/kev-cpu \
6768
-DCMAKE_BUILD_TYPE=Release -DPYTHON_EXECUTABLE="$(command -v python)"
6869
cmake --build cmake-out/kev-cpu --target kev_runner --parallel 8
6970

@@ -73,7 +74,7 @@ cmake-out/kev-cpu/kev_runner kev-cpu/model.pte kev-cpu/tokenizer.json
7374
For MLX, use macOS 14+ with Xcode's Metal compiler installed:
7475

7576
```bash
76-
cmake -S examples/kev -B cmake-out/kev-mlx \
77+
cmake -S examples/systemone/kev -B cmake-out/kev-mlx \
7778
-DCMAKE_BUILD_TYPE=Release -DPYTHON_EXECUTABLE="$(command -v python)" \
7879
-DEXECUTORCH_BUILD_MLX=ON
7980
cmake --build cmake-out/kev-mlx --target kev_runner --parallel 8
@@ -134,19 +135,22 @@ of the other medians. FP32 is faster on this CPU; all exports are unquantized.
134135
To reproduce the XNNPACK BF16 column with the CPU benchmark:
135136

136137
```bash
137-
PYTHONPATH="$HOME/kev:$PYTHONPATH" python examples/kev/export.py \
138+
PYTHONPATH="$HOME/kev:$PYTHONPATH" python examples/systemone/kev/export.py \
138139
--checkpoint kev-checkpoint --backend xnnpack --dtype bf16 --output kev-cpu-bf16
139140

140141
cmake-out/kev-cpu/kev_benchmark kev-cpu-bf16/model.pte kev-cpu-bf16/tokenizer.json
141142
```
142143

143144
## C++ API
144145

145-
[api.h](api.h) defines the question and answer types and the virtual
146-
`SystemOne::system_one(state, questions)` interface, following
147-
[TypeSafe's SDK operation](https://docs.typesafe.ai/sdk/python/api/clients/sync).
148-
It returns ExecuTorch's `Result<Answers>`. [kev.h](kev.h) provides `Kev`,
149-
which implements this interface and borrows a `Module` and tokenizer.
146+
[../api.h](../api.h) is generic across System One models. It defines the
147+
question and answer types and the virtual `SystemOne::system_one(state,
148+
questions)` interface, following
149+
[TypeSafe's SDK operation](https://docs.typesafe.ai/sdk/python/api/clients/sync),
150+
in the `executorch::systemone` namespace. It returns ExecuTorch's
151+
`Result<Answers>`. [kev.h](kev.h) provides `Kev` in `executorch::systemone::kev`,
152+
which implements this interface for the Kev model and borrows a `Module` and
153+
tokenizer.
150154

151155
For explicit prefix reuse, `Kev` also provides `prefill(state)` and
152156
`evaluate(prefix, questions)`. `Prefix` owns its snapshot and must be used with
@@ -164,21 +168,22 @@ The question and answer types follow [TypeSafe's typed SDK](https://docs.typesaf
164168
and are not sent to the model.
165169

166170
```cpp
167-
kev::Questions questions{
168-
{"department", kev::Choice{"Which team should handle this?",
171+
using namespace executorch::systemone;
172+
Questions questions{
173+
{"department", Choice{"Which team should handle this?",
169174
{{"billing", "Invoices and refunds"}, {"technical", "Bugs and outages"}}}},
170-
{"refund", kev::Noul{"Is a refund requested?",
171-
{{kev::NoulOutcome::False, "No refund requested"},
172-
{kev::NoulOutcome::True, "Explicitly asks for a refund"}}}},
173-
{"urgency", kev::Score{"How urgent is this?",
175+
{"refund", Noul{"Is a refund requested?",
176+
{{NoulOutcome::False, "No refund requested"},
177+
{NoulOutcome::True, "Explicitly asks for a refund"}}}},
178+
{"urgency", Score{"How urgent is this?",
174179
{"Can wait", "This week", "Today"}}}};
175180
```
176181
177182
With a loaded module and tokenizer, call through the interface:
178183
179184
```cpp
180-
kev::Kev model(module, tokenizer);
181-
kev::SystemOne& api = model;
185+
executorch::systemone::kev::Kev model(module, tokenizer);
186+
executorch::systemone::SystemOne& api = model;
182187
auto answers = api.system_one(state, questions);
183188
```
184189

@@ -195,7 +200,7 @@ if (!answers.ok()) {
195200
return 1;
196201
}
197202
auto followup = model.evaluate(*prefix, {
198-
{"duplicate", kev::Noul{"Was the customer charged more than once?", {}}}});
203+
{"duplicate", Noul{"Was the customer charged more than once?", {}}}});
199204
if (!followup.ok()) {
200205
return 1;
201206
}
Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -16,9 +16,10 @@
1616
#include "kev.h"
1717

1818
using executorch::extension::Module;
19-
using kev::Choice;
20-
using kev::Noul;
21-
using kev::Questions;
19+
using executorch::systemone::Choice;
20+
using executorch::systemone::Noul;
21+
using executorch::systemone::Questions;
22+
using executorch::systemone::kev::Kev;
2223

2324
int main(int argc, char** argv) {
2425
if (argc < 3 || argc > 4) {
@@ -31,7 +32,7 @@ int main(int argc, char** argv) {
3132
std::cerr << "Cannot load tokenizer.json\n";
3233
return 1;
3334
}
34-
kev::Kev model(module, tokenizer);
35+
Kev model(module, tokenizer);
3536
const std::string state = argc == 4
3637
? argv[3]
3738
: "I was charged twice for invoice 4411. Please refund the duplicate charge.";
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@
1818
#include <executorch/extension/tensor/tensor.h>
1919
#include <pytorch/tokenizers/tokenizer.h>
2020

21+
namespace executorch {
22+
namespace systemone {
2123
namespace kev {
2224

2325
using executorch::aten::ScalarType;
@@ -356,3 +358,5 @@ Result<Answers> Kev::evaluate(
356358
}
357359

358360
} // namespace kev
361+
} // namespace systemone
362+
} // namespace executorch
Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,12 +15,14 @@
1515
#include <cstdint>
1616
#include <string>
1717

18-
#include "api.h"
18+
#include "executorch/examples/systemone/api.h"
1919

2020
namespace tokenizers {
2121
class Tokenizer;
2222
}
2323

24+
namespace executorch {
25+
namespace systemone {
2426
namespace kev {
2527

2628
class Kev;
@@ -70,3 +72,5 @@ class Kev final : public SystemOne {
7072
};
7173

7274
} // namespace kev
75+
} // namespace systemone
76+
} // namespace executorch
Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -12,9 +12,10 @@
1212
#include "kev.h"
1313

1414
using executorch::extension::Module;
15-
using kev::Choice;
16-
using kev::Noul;
17-
using kev::Questions;
15+
using executorch::systemone::Choice;
16+
using executorch::systemone::Noul;
17+
using executorch::systemone::Questions;
18+
using executorch::systemone::kev::Kev;
1819

1920
int main(int argc, char** argv) {
2021
if (argc < 3 || argc > 4) {
@@ -41,24 +42,27 @@ int main(int argc, char** argv) {
4142
Noul{"Does the customer explicitly ask for a refund?", {}}},
4243
{"duplicate_charge",
4344
Noul{"Was the customer charged more than once?", {}}}};
44-
kev::Kev model(module, tokenizer);
45+
Kev model(module, tokenizer);
4546
auto answers = model.system_one(state, questions);
4647
if (!answers.ok()) {
4748
std::cerr << executorch::runtime::to_string(answers.error()) << '\n';
4849
return 1;
4950
}
5051
for (const auto& [id, answer] : *answers) {
5152
std::cout << id << ": ";
52-
if (const auto* choice = std::get_if<kev::ChoiceAnswer>(&answer)) {
53+
if (const auto* choice =
54+
std::get_if<executorch::systemone::ChoiceAnswer>(&answer)) {
5355
std::cout << choice->choice << " (confidence " << choice->confidence
5456
<< ")\n";
5557
for (const auto& [name, probability] : choice->probabilities) {
5658
std::cout << " " << name << ": " << probability << '\n';
5759
}
58-
} else if (const auto* noul = std::get_if<kev::NoulAnswer>(&answer)) {
60+
} else if (
61+
const auto* noul =
62+
std::get_if<executorch::systemone::NoulAnswer>(&answer)) {
5963
std::cout << noul->noul << '\n';
6064
} else {
61-
const auto& score = std::get<kev::ScoreAnswer>(answer);
65+
const auto& score = std::get<executorch::systemone::ScoreAnswer>(answer);
6266
std::cout << score.score << " (confidence " << score.confidence << ")\n";
6367
for (size_t level = 0; level < score.probabilities.size(); ++level) {
6468
std::cout << " " << level << " (" << score.legend.at(level)

0 commit comments

Comments
 (0)