open-genie-server 1.0.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (89) hide show
  1. open_genie_server-1.0.0/LICENSE +21 -0
  2. open_genie_server-1.0.0/MANIFEST.in +20 -0
  3. open_genie_server-1.0.0/PKG-INFO +291 -0
  4. open_genie_server-1.0.0/README.ja.md +244 -0
  5. open_genie_server-1.0.0/README.md +249 -0
  6. open_genie_server-1.0.0/SECURITY.ja.md +61 -0
  7. open_genie_server-1.0.0/SECURITY.md +68 -0
  8. open_genie_server-1.0.0/docs/API.ja.md +396 -0
  9. open_genie_server-1.0.0/docs/API.md +400 -0
  10. open_genie_server-1.0.0/docs/CHANGELOG.md +71 -0
  11. open_genie_server-1.0.0/docs/MANUAL.ja.md +1379 -0
  12. open_genie_server-1.0.0/docs/MANUAL.md +1419 -0
  13. open_genie_server-1.0.0/docs/PLATFORM_NOTES.ja.md +102 -0
  14. open_genie_server-1.0.0/docs/PLATFORM_NOTES.md +107 -0
  15. open_genie_server-1.0.0/docs/QAIRT_VERSIONS.ja.md +372 -0
  16. open_genie_server-1.0.0/docs/QAIRT_VERSIONS.md +401 -0
  17. open_genie_server-1.0.0/examples/bfcl/README.ja.md +301 -0
  18. open_genie_server-1.0.0/examples/bfcl/README.md +315 -0
  19. open_genie_server-1.0.0/examples/bfcl/bfcl_analyze.py +61 -0
  20. open_genie_server-1.0.0/examples/bfcl/bfcl_format_cost.py +132 -0
  21. open_genie_server-1.0.0/examples/bfcl/bfcl_marker_cost.py +103 -0
  22. open_genie_server-1.0.0/examples/bfcl/gemma4_fc_handler.py +303 -0
  23. open_genie_server-1.0.0/examples/bfcl/gemma4_handler.py +90 -0
  24. open_genie_server-1.0.0/examples/bfcl/install_gemma4_handler.sh +86 -0
  25. open_genie_server-1.0.0/examples/bfcl/prompt_lengths.py +52 -0
  26. open_genie_server-1.0.0/examples/bfcl/run_bfcl.sh +130 -0
  27. open_genie_server-1.0.0/examples/bfcl/subset_ids.py +69 -0
  28. open_genie_server-1.0.0/examples/config/env_config.android.sample.json +16 -0
  29. open_genie_server-1.0.0/examples/config/env_config.dual-nsp.sample.json +28 -0
  30. open_genie_server-1.0.0/examples/config/env_config.single.sample.json +18 -0
  31. open_genie_server-1.0.0/examples/config/env_config.text-vlm.sample.json +36 -0
  32. open_genie_server-1.0.0/examples/config/env_config.vlm.sample.json +22 -0
  33. open_genie_server-1.0.0/examples/grammar/README.ja.md +25 -0
  34. open_genie_server-1.0.0/examples/grammar/README.md +29 -0
  35. open_genie_server-1.0.0/examples/grammar/genie_config.json +69 -0
  36. open_genie_server-1.0.0/examples/grammar/grammar_schema.txt +15 -0
  37. open_genie_server-1.0.0/examples/lm_eval/README.ja.md +90 -0
  38. open_genie_server-1.0.0/examples/lm_eval/README.md +95 -0
  39. open_genie_server-1.0.0/examples/lm_eval/compare_runs.py +47 -0
  40. open_genie_server-1.0.0/examples/lm_eval/run_lm_eval.sh +57 -0
  41. open_genie_server-1.0.0/examples/vlm/README.ja.md +106 -0
  42. open_genie_server-1.0.0/examples/vlm/README.md +107 -0
  43. open_genie_server-1.0.0/genie-server.py +36 -0
  44. open_genie_server-1.0.0/pyproject.toml +83 -0
  45. open_genie_server-1.0.0/requirements.txt +17 -0
  46. open_genie_server-1.0.0/setup.cfg +4 -0
  47. open_genie_server-1.0.0/src/genie_server/__init__.py +24 -0
  48. open_genie_server-1.0.0/src/genie_server/app.py +1220 -0
  49. open_genie_server-1.0.0/src/genie_server/asgi.py +24 -0
  50. open_genie_server-1.0.0/src/genie_server/bootstrap.py +58 -0
  51. open_genie_server-1.0.0/src/genie_server/capi.py +558 -0
  52. open_genie_server-1.0.0/src/genie_server/cli.py +85 -0
  53. open_genie_server-1.0.0/src/genie_server/config.py +442 -0
  54. open_genie_server-1.0.0/src/genie_server/engine.py +313 -0
  55. open_genie_server-1.0.0/src/genie_server/genie_node.py +265 -0
  56. open_genie_server-1.0.0/src/genie_server/logprobs.py +175 -0
  57. open_genie_server-1.0.0/src/genie_server/prefix_cache.py +97 -0
  58. open_genie_server-1.0.0/src/genie_server/protocol.py +247 -0
  59. open_genie_server-1.0.0/src/genie_server/slots.py +474 -0
  60. open_genie_server-1.0.0/src/genie_server/templates.py +242 -0
  61. open_genie_server-1.0.0/src/genie_server/tool_formats.py +373 -0
  62. open_genie_server-1.0.0/src/genie_server/tools.py +412 -0
  63. open_genie_server-1.0.0/src/genie_server/vlm.py +400 -0
  64. open_genie_server-1.0.0/src/genie_server/vlm_specs.py +221 -0
  65. open_genie_server-1.0.0/src/open_genie_server.egg-info/PKG-INFO +291 -0
  66. open_genie_server-1.0.0/src/open_genie_server.egg-info/SOURCES.txt +87 -0
  67. open_genie_server-1.0.0/src/open_genie_server.egg-info/dependency_links.txt +1 -0
  68. open_genie_server-1.0.0/src/open_genie_server.egg-info/entry_points.txt +2 -0
  69. open_genie_server-1.0.0/src/open_genie_server.egg-info/requires.txt +17 -0
  70. open_genie_server-1.0.0/src/open_genie_server.egg-info/top_level.txt +1 -0
  71. open_genie_server-1.0.0/tests/conftest.py +67 -0
  72. open_genie_server-1.0.0/tests/fake_genie.py +253 -0
  73. open_genie_server-1.0.0/tests/integration/README.ja.md +79 -0
  74. open_genie_server-1.0.0/tests/integration/README.md +92 -0
  75. open_genie_server-1.0.0/tests/integration/fake_server.py +37 -0
  76. open_genie_server-1.0.0/tests/integration/measure_parallelism.py +104 -0
  77. open_genie_server-1.0.0/tests/integration/measure_perf_policy.py +146 -0
  78. open_genie_server-1.0.0/tests/integration/measure_ttft_tps.py +110 -0
  79. open_genie_server-1.0.0/tests/integration/run_integration_tests.py +1314 -0
  80. open_genie_server-1.0.0/tests/integration/setup_grammar_models.py +94 -0
  81. open_genie_server-1.0.0/tests/integration/slot_names.py +35 -0
  82. open_genie_server-1.0.0/tests/integration/test_config.sample.json +54 -0
  83. open_genie_server-1.0.0/tests/test_api.py +1061 -0
  84. open_genie_server-1.0.0/tests/test_disconnect.py +242 -0
  85. open_genie_server-1.0.0/tests/test_docs.py +189 -0
  86. open_genie_server-1.0.0/tests/test_grammar.py +279 -0
  87. open_genie_server-1.0.0/tests/test_packaging.py +110 -0
  88. open_genie_server-1.0.0/tests/test_tool_formats.py +176 -0
  89. open_genie_server-1.0.0/tests/test_units.py +1464 -0
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Tadayuki OKADA
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,20 @@
1
+ # What an sdist gets beyond the package itself. The wheel is unaffected —
2
+ # it ships src/genie_server/ and nothing else.
3
+ #
4
+ # README.md links into docs/ and examples/, and the launcher is how the device
5
+ # starts the server, so a source tarball without them is not the source.
6
+ include LICENSE
7
+ include README.md
8
+ include README.ja.md
9
+ include SECURITY.md
10
+ include SECURITY.ja.md
11
+ include requirements.txt
12
+ include genie-server.py
13
+ recursive-include docs *.md
14
+ recursive-include examples *.md *.json *.py *.sh *.txt
15
+ recursive-include tests *.py *.md *.json
16
+
17
+ # Never ship someone else's board, or bytecode.
18
+ exclude tests/integration/test_config.json
19
+ global-exclude *.pyc
20
+ global-exclude __pycache__
@@ -0,0 +1,291 @@
1
+ Metadata-Version: 2.4
2
+ Name: open-genie-server
3
+ Version: 1.0.0
4
+ Summary: OpenAI-compatible REST API server for Qualcomm Genie (libGenie.so) on the Hexagon NPU
5
+ Author: Tadayuki OKADA
6
+ Project-URL: Homepage, https://github.com/TadayukiOkada/open-genie-server
7
+ Project-URL: Documentation, https://github.com/TadayukiOkada/open-genie-server/blob/master/docs/MANUAL.md
8
+ Project-URL: Changelog, https://github.com/TadayukiOkada/open-genie-server/blob/master/docs/CHANGELOG.md
9
+ Keywords: qualcomm,genie,qairt,hexagon,npu,openai,llm
10
+ Classifier: Development Status :: 4 - Beta
11
+ Classifier: License :: OSI Approved :: MIT License
12
+ Classifier: Environment :: Console
13
+ Classifier: Framework :: FastAPI
14
+ Classifier: Intended Audience :: Developers
15
+ Classifier: Operating System :: POSIX :: Linux
16
+ Classifier: Operating System :: Android
17
+ Classifier: Programming Language :: Python :: 3
18
+ Classifier: Programming Language :: Python :: 3.10
19
+ Classifier: Programming Language :: Python :: 3.11
20
+ Classifier: Programming Language :: Python :: 3.12
21
+ Classifier: Programming Language :: Python :: 3.13
22
+ Classifier: Programming Language :: Python :: 3.14
23
+ Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
24
+ Requires-Python: >=3.10
25
+ Description-Content-Type: text/markdown
26
+ License-File: LICENSE
27
+ Requires-Dist: fastapi
28
+ Requires-Dist: uvicorn
29
+ Requires-Dist: tokenizers
30
+ Provides-Extra: logprobs
31
+ Requires-Dist: numpy; extra == "logprobs"
32
+ Provides-Extra: vlm
33
+ Requires-Dist: numpy; extra == "vlm"
34
+ Requires-Dist: pillow; extra == "vlm"
35
+ Provides-Extra: test
36
+ Requires-Dist: pytest; extra == "test"
37
+ Requires-Dist: httpx; extra == "test"
38
+ Requires-Dist: requests; extra == "test"
39
+ Requires-Dist: jsonschema; extra == "test"
40
+ Requires-Dist: numpy; extra == "test"
41
+ Dynamic: license-file
42
+
43
+ # open-genie-server
44
+
45
+ *English | [日本語](https://github.com/TadayukiOkada/open-genie-server/blob/master/README.ja.md)*
46
+
47
+ A single-process FastAPI server that exposes the Qualcomm Genie C API (`libGenie.so`) as an OpenAI-compatible REST API. It lets you drive LLMs running on a [Hexagon NPU](https://github.com/TadayukiOkada/open-genie-server/blob/master/docs/MANUAL.md#glossary) — Qualcomm's name for the accelerator this server talks to through the QNN HTP backend — from ordinary OpenAI-compatible HTTP clients — `lm_eval`, `curl`, the OpenAI SDK, [Open WebUI](https://github.com/open-webui/open-webui), and so on. The implementation lives in the `genie_server` package (`src/genie_server/`); `genie-server.py` is the launcher.
48
+
49
+ > [!IMPORTANT]
50
+ > This repository contains only the open-genie-server source itself. To run it you also need the **QAIRT SDK** from Qualcomm (a toolchain distributed under Qualcomm's proprietary license, containing `libGenie.so`) and a model compiled for a Hexagon NPU (a model directory containing `genie_config.json`). Neither the SDK nor any models are included in this repository — obtain them separately from [Qualcomm AI Hub](https://aihub.qualcomm.com/) or similar.
51
+
52
+ See [MANUAL.md](https://github.com/TadayukiOkada/open-genie-server/blob/master/docs/MANUAL.md) for configuration and behaviour, [API.md](https://github.com/TadayukiOkada/open-genie-server/blob/master/docs/API.md) for the endpoint reference, and [Platform Notes](https://github.com/TadayukiOkada/open-genie-server/blob/master/docs/PLATFORM_NOTES.md) for what every measured number here assumes about the device it was measured on.
53
+
54
+ ## What this is for
55
+
56
+ **A bench instrument for the Genie C API and for quantized model bundles — not a production inference server.** Everything else follows from that, in this order:
57
+
58
+ 1. **Reach as much of the Genie C API as the API allows.** Not just chat: SDK-side profiling counters, performance policies, prompt scoring through the custom-sampler hook, LoRA, prefix-cache snapshots, and the composable `GenieNode`/`GeniePipeline` path behind VLM slots are all reachable over HTTP, because the point is to exercise them.
59
+ 2. **Make standard benchmarks easy to point at a device.** `lm_eval` works against an unmodified install, generation and loglikelihood tasks alike, and [examples/bfcl](https://github.com/TadayukiOkada/open-genie-server/tree/master/examples/bfcl) drives the Berkeley Function Calling Leaderboard the same way. A benchmark that needs the server patched is a benchmark you will not run.
60
+ 3. **Do not hide the SDK's or the model's problems by default.** A defect you cannot see is one you will ship. So the stock-library slot wedge is neither detected nor papered over; grammar's leaked terminal token is reported rather than stripped; a response names the model actually loaded instead of echoing the string the client sent; and the prefix cache fills only on an explicit warmup, so it cannot quietly improve a TTFT measurement. Workarounds exist, but they are switches you turn on knowing what they conceal — `TOOL_CALL_RECOVERY`, which reassembles a tool call whose marker the model mangled, is off until you ask for it.
61
+ 4. **Stay compatible with the OpenAI API, and with what other inference servers do — where that does not conflict with 3.** Where the two disagree, this server reports what happened. The `model` field above is the worked example: OpenAI and vLLM echo the request, and this server does not, because a benchmark that ran against a hot-swapped model should say so.
62
+
63
+ **Backward compatibility is not one of these.** Nothing gets broken for the sake of it, and a change to an existing response shape or default is called out under Breaking in the [CHANGELOG](https://github.com/TadayukiOkada/open-genie-server/blob/master/docs/CHANGELOG.md). But this server is a view onto the Genie C API, and when that API moves, this follows — keeping an old shape alive to spare an existing caller would make the instrument lie about what the SDK now does. The same goes for our own defaults: a measurement showing that one of them hides something is reason enough to change it. If you need a surface that holds still, pin a version.
64
+
65
+ **What it is not.** There is no authentication, no rate limiting and no multi-process scaling, `POST /v1/models/switch` will open any path the process can read, and one text slot serializes its requests behind a single `GenieDialog` handle. Run it on a bench network you control ([SECURITY.md](https://github.com/TadayukiOkada/open-genie-server/blob/master/SECURITY.md) says what that means, and what is worth reporting). If you need a production serving stack on Hexagon, this is the wrong starting point — but it will tell you, in detail, what your bundle and your SDK actually do.
66
+
67
+ ## Features
68
+
69
+ - `/v1/completions` / `/v1/chat/completions` — OpenAI-compatible text/chat completion (streaming supported; also registered without the `/v1` prefix)
70
+ - **Function calling (`tools`)** — Hermes-format tool calling for Qwen3-class models: `<tool_call>` output is parsed into OpenAI `message.tool_calls` / `finish_reason: "tool_calls"`, held back correctly during streaming
71
+ - Works out of the box with `lm_eval` (`local-completions` / `local-chat-completions`; token-id prompts are decoded server-side)
72
+ - **Logprobs** via the SDK's custom-sampler hook: per-token `logprobs`/`top_logprobs` for generated tokens (a few ms/token overhead, zero when unused), and **prompt scoring** (`echo`+`logprobs` teacher forcing) that makes lm_eval loglikelihood tasks (hellaswag, arc, mmlu, ...) work — gated behind `POST /v1/server/prompt_logprobs` since it runs at decode speed
73
+ - Open WebUI-friendly: parts-array `content` flattening, `GET /health`, CORS, streaming `usage` chunks (`stream_options.include_usage`)
74
+ - Prefix KV cache for system prompts (namespaced per model/LoRA)
75
+ - LoRA adapter hot-swapping via `GenieDialog_applyLora` etc. — apply, strength, release and read-back, verified on hardware
76
+ - Model hot-swapping via `/v1/models/switch` (by default the old model is freed before the new one loads, which is the order that switches reliably; a failed load then leaves the slot empty. `"unload_first": false` keeps the old model as a fallback by holding both at once — see the caveat below before using it)
77
+ - `Genie_PerformancePolicy_t` switching (e.g. pin to `burst` for benchmarking)
78
+ - Non-blocking status monitoring, including context occupancy (KV cache usage)
79
+ - **SDK-side profiling** — `GENIE_PROFILE` exposes Genie's own TTFT / prefill / decode KPIs on `GET /v1/server/profile`, without touching the OpenAI response shapes
80
+ - **Multi text-slot support** — `TEXT_SLOTS` lets you assign an independent `GenieDialog` handle (its own lock, optionally its own model) to each Hexagon NSP core you can use (cdsp0/cdsp1; see the [Glossary](https://github.com/TadayukiOkada/open-genie-server/blob/master/docs/MANUAL.md#glossary) if HTP/NSP/cDSP/NPU are unfamiliar). Requests to different slots do overlap, but the measured gain on our bench was **~1.3×, not 2×** (see [Multi Text Slots](https://github.com/TadayukiOkada/open-genie-server/blob/master/docs/MANUAL.md#multi-text-slots)). **How many cores you may use is licensed per SKU, not implied by the part number** — see [Platform Notes](https://github.com/TadayukiOkada/open-genie-server/blob/master/docs/PLATFORM_NOTES.md)
81
+ - **Grammar-constrained decoding** — constrain output with JSON Schema/regex/EBNF (XGrammar backend, fixed per model/slot)
82
+ - **VLM (multimodal) support** — image-input models such as Qwen3-VL that use the `GenieNode`/`GeniePipeline` composable pipeline API can be added via `VLM_SLOTS`, entirely in parallel with `TEXT_SLOTS` (see [examples/vlm](https://github.com/TadayukiOkada/open-genie-server/tree/master/examples/vlm))
83
+ - Offline test suite (`tests/`, pytest + a fake SDK) — the whole HTTP/engine/template stack runs without an NPU
84
+
85
+ ## Requirements
86
+
87
+ - Python 3.10+ (uses `int | None`-style type syntax)
88
+ - The QAIRT SDK (`libGenie.so` and its dependencies) and a model that runs on the Hexagon NPU (see the note above)
89
+
90
+ ```bash
91
+ pip install .[logprobs,vlm] # everything
92
+ pip install . # server only: fastapi, uvicorn, tokenizers
93
+ ```
94
+
95
+ The distribution is named `open-genie-server`; the package you import is
96
+ `genie_server`. `pip install -r requirements.txt` still works and is the same
97
+ as the first line above.
98
+
99
+ | | in | why |
100
+ |---|---|---|
101
+ | `fastapi`, `uvicorn` | core | the server |
102
+ | `tokenizers` | core | accurate token counts. Without it, counts come from `text.split()`, so a 55-token Japanese paragraph counts as 1 — and that feeds the context check and the default `max_tokens`, not just the reported usage. See [Token counting](https://github.com/TadayukiOkada/open-genie-server/blob/master/docs/MANUAL.md#token-counting) |
103
+ | `numpy` | `[logprobs]`, `[vlm]` | **logprobs and prompt scoring**, and VLM. Without it those requests are rejected with HTTP 400 |
104
+ | `pillow` | `[vlm]` | image input |
105
+ | `pytest`, `httpx`, `requests`, `jsonschema` | `[test]` | the offline suite |
106
+
107
+ Installing the package also gives you a `genie-server` command; the
108
+ repository-root `genie-server.py` launcher does the same thing and needs no
109
+ install. On Android three of these publish no wheel — see
110
+ [Running on Android](https://github.com/TadayukiOkada/open-genie-server/blob/master/docs/MANUAL.md#running-on-android).
111
+
112
+ > [!WARNING]
113
+ > **Check which QAIRT version you are pointing at before deploying.** Which SDK
114
+ > defects you inherit depends on that version, and **every 2.49.x we have tested
115
+ > carries three of them in one place: what `GenieDialog_reset()` fails to put
116
+ > back.** A server resets between requests to keep them independent, so that is
117
+ > the path under every request you serve.
118
+ >
119
+ > You would see one oversized request wedging a slot for good; a long request
120
+ > failing on an empty context because the one before it was shorter; or, on a
121
+ > bundle built for speculative decoding, every answer after the first reset
122
+ > coming back fluent and wrong. **All three report success.**
123
+ >
124
+ > **[QAIRT Version Issues](https://github.com/TadayukiOkada/open-genie-server/blob/master/docs/QAIRT_VERSIONS.md)** has the per-version
125
+ > matrix, [a check you can run against your own SDK](https://github.com/TadayukiOkada/open-genie-server/blob/master/docs/QAIRT_VERSIONS.md#checking-your-own-sdk),
126
+ > and how to choose a library. A version not listed there is one we have not
127
+ > tested, not one we know to be clean.
128
+
129
+ ## Quick start
130
+
131
+ > [!NOTE]
132
+ > The examples in this section reach the device at `192.168.1.2:8080`. That
133
+ > address is not special to this server — it is the default the SA8255P's LV GVM
134
+ > comes up with, and it is what our own bench uses, so it appears throughout the
135
+ > docs and in `tests/integration/test_config.sample.json`. Replace it with your
136
+ > device's address (or `localhost` if you are running the server and the client
137
+ > on the same machine).
138
+
139
+ 1. Place an `env_config.json` in the server's startup (current) directory.
140
+
141
+ ```json
142
+ {
143
+ "QAIRT_SDK_ROOT": "/path/to/qairt-dir",
144
+ "HEXAGON_VERSION": "v73",
145
+ "MODELS_BASE_DIR": "/path/to/models",
146
+ "PREFIX_CACHE_DIR": "/path/to/prefix_cache",
147
+ "TEXT_SLOTS": [{"model_root": "model-dir"}]
148
+ }
149
+ ```
150
+
151
+ `model_root` points at the directory containing `genie_config.json`. That is the only key a slot needs; `name` and `device_id` default.
152
+
153
+ A relative `model_root` resolves under `MODELS_BASE_DIR`, so the example above loads `/path/to/models/model-dir`. Absolute paths also work — see [Where model paths resolve](https://github.com/TadayukiOkada/open-genie-server/blob/master/docs/MANUAL.md#where-model-paths-resolve).
154
+
155
+ On a SoC with multiple NSP cores, add an entry per core to keep a model resident on each (see [MANUAL.md](https://github.com/TadayukiOkada/open-genie-server/blob/master/docs/MANUAL.md#multi-text-slots) for details, including the ordering rule — a second slot does not always fit):
156
+
157
+ ```json
158
+ {
159
+ "QAIRT_SDK_ROOT": "/path/to/qairt-dir",
160
+ "HEXAGON_VERSION": "v73",
161
+ "MODELS_BASE_DIR": "/path/to/models",
162
+ "PREFIX_CACHE_DIR": "/path/to/prefix_cache",
163
+ "TEXT_SLOTS": [
164
+ {"name": "tool_call", "device_id": 0, "model_root": "model-fast"},
165
+ {"name": "chat", "device_id": 1, "model_root": "model-general"}
166
+ ]
167
+ }
168
+ ```
169
+
170
+ 2. Start the server.
171
+
172
+ ```bash
173
+ python3 genie-server.py # flags: --config/--host/--port
174
+ # or, if the package is installed
175
+ genie-server # same flags
176
+ # or under uvicorn directly
177
+ uvicorn genie_server.asgi:app --host 0.0.0.0 --port 8080 --workers 1
178
+ ```
179
+
180
+ 3. Sanity-check it:
181
+
182
+ ```bash
183
+ curl http://192.168.1.2:8080/v1/models
184
+
185
+ curl http://192.168.1.2:8080/v1/chat/completions \
186
+ -H "Content-Type: application/json" \
187
+ -d '{"model":"genie-local","messages":[{"role":"user","content":"Hello"}]}'
188
+ ```
189
+
190
+ See [examples/grammar](https://github.com/TadayukiOkada/open-genie-server/tree/master/examples/grammar) for a grammar-constrained decoding config example, [examples/vlm](https://github.com/TadayukiOkada/open-genie-server/tree/master/examples/vlm) for VLM (image input) setup and testing steps, and [examples/lm_eval](https://github.com/TadayukiOkada/open-genie-server/tree/master/examples/lm_eval) for running `lm_eval` against a board (including how to compare the result with the unquantized model).
191
+
192
+ ## Using it with lm_eval
193
+
194
+ ```bash
195
+ lm_eval --model local-chat-completions \
196
+ --model_args model=genie-local,base_url=http://192.168.1.2:8080/v1,\
197
+ tokenizer_backend=huggingface,tokenizer=<hf_model>,max_tokens=512,num_concurrent=1 \
198
+ --tasks mmlu_generative --apply_chat_template --batch_size 1
199
+ ```
200
+
201
+ See [API.md](https://github.com/TadayukiOkada/open-genie-server/blob/master/docs/API.md) for the endpoint reference and [MANUAL.md](https://github.com/TadayukiOkada/open-genie-server/blob/master/docs/MANUAL.md) for configuration.
202
+
203
+ ## Directory layout
204
+
205
+ ```
206
+ genie-server.py — launcher (CLI flags: --config/--host/--port)
207
+ src/genie_server/ — the server implementation, one module per concern
208
+ pyproject.toml — packaging: dependencies, extras, the genie-server command
209
+ requirements.txt — kept as a pointer to pyproject.toml's dependencies
210
+ SECURITY.md — what is deliberately absent, and what to report (.ja.md)
211
+ LICENSE
212
+
213
+ tests/ — offline test suite (pytest; fake SDK, no NPU needed)
214
+ tests/integration/ — host-side runner against a live device (MD/JSON reports)
215
+
216
+ docs/MANUAL.md — configuration, behaviour, and the why behind it (.ja.md)
217
+ docs/API.md — every endpoint, grouped by purpose (.ja.md)
218
+ docs/PLATFORM_NOTES.md— what the measured numbers assume about a device (.ja.md)
219
+ docs/QAIRT_VERSIONS.md— the SDK defects, per QAIRT version (.ja.md)
220
+ docs/CHANGELOG.md — release notes
221
+
222
+ examples/config/ — env_config.json samples (single-slot / dual-NSP / VLM)
223
+ examples/grammar/ — grammar-constrained decoding
224
+ examples/vlm/ — VLM (multimodal) setup and testing steps
225
+ examples/lm_eval/ — lm_eval, and comparing against the unquantized model
226
+ examples/bfcl/ — the Berkeley Function Calling Leaderboard
227
+ ```
228
+
229
+ The module-by-module breakdown of `src/genie_server/` is in
230
+ [MANUAL.md § Architecture Overview](https://github.com/TadayukiOkada/open-genie-server/blob/master/docs/MANUAL.md#architecture-overview) rather than repeated
231
+ here.
232
+
233
+ Run the offline tests with:
234
+
235
+ ```bash
236
+ pip install -e .[logprobs,vlm,test]
237
+ python3 -m pytest tests/
238
+ ```
239
+
240
+ The `[test]` dependencies are not optional for a green run: without `requests`
241
+ and `jsonschema` eight grammar tests fail with `ModuleNotFoundError`, and
242
+ without `numpy` eight logprobs tests do. The same suite runs on every push and
243
+ pull request against Python 3.10 and 3.12
244
+ (`.github/workflows/offline-tests.yml`).
245
+
246
+ To exercise a real device end-to-end from the host PC (with a Markdown/JSON report and server-death detection), see [tests/integration/](https://github.com/TadayukiOkada/open-genie-server/tree/master/tests/integration).
247
+
248
+ ## Known limitations
249
+
250
+ - **This server does not guard against the stock-library reset defects** described at the top of this page — it neither detects nor recovers from them. Avoiding them is a deployment choice: see [QAIRT Version Issues](https://github.com/TadayukiOkada/open-genie-server/blob/master/docs/QAIRT_VERSIONS.md).
251
+ - **A bundle built for speculative decoding (`"dialog": {"type": "ssd-q1"}`) needs a patched library**, and cannot use LoRA without one. See [D5](https://github.com/TadayukiOkada/open-genie-server/blob/master/docs/QAIRT_VERSIONS.md#d5--reset-corrupts-a-speculative-decoding-dialog) for why, and for the one-line change to the bundle that avoids it.
252
+ - One text slot = one `GenieDialog` handle; requests to a slot are serialized by that slot's own lock (with `TEXT_SLOTS` unset there's only a single slot, so every request is serialized, same as before).
253
+ - `n > 1` (multiple completions per request) is not supported; it is rejected with a `400`.
254
+ - The Llama2/Mistral template folds the system prompt into `[INST]`, so it's not eligible for prefix KV caching.
255
+ - `POST /v1/models/switch` frees the old model before loading the new one by default, so a failed load leaves that slot with no model until a later switch succeeds; every endpoint that touches it returns `503` in the meantime.
256
+ - `"unload_first": false` avoids that by holding both models on the slot's HTP device while the new one loads — but **on the SA8255P board that overlap is not dependable**. Over 36 measured swaps the outcome did not follow from which models were involved: one pair succeeded 6/6 in one run and failed 8/8 in another, and a run of six flipped from failing to succeeding halfway through. What decides it is device state the host cannot observe. Use it only where the device has memory to spare and the swaps your deployment actually performs have been tested there, repeatedly and from a cold start.
257
+ - VLM slots are single-turn only (no conversation history), and don't support LoRA, prefix KV cache, grammar constraints, or hot-swapping. See [MANUAL.md](https://github.com/TadayukiOkada/open-genie-server/blob/master/docs/MANUAL.md#vlm-multimodal-support) for details.
258
+
259
+ See [MANUAL.md's Limitations section](https://github.com/TadayukiOkada/open-genie-server/blob/master/docs/MANUAL.md#limitations) for the rest of the known limitations.
260
+
261
+ ## Acknowledgements
262
+
263
+ Built with [Claude Code](https://claude.com/claude-code). The first commit here
264
+ is dated 2026-08-19, and in the eleven days since, this went from a single
265
+ 2,700-line script to a packaged server with 311 offline tests, a hardware
266
+ integration suite, and manuals in two languages.
267
+
268
+ The code was never the slow part. What took the time was reading the QAIRT
269
+ SDK's reference sources closely enough to tell an SDK defect from a bug of our
270
+ own, reproducing each one on the board until it was certain which it was, and
271
+ then writing down what had been measured rather than what had been assumed —
272
+ several of the findings in these documents reverse an earlier conclusion that
273
+ looked obvious at the time. Doing that at this pace, as one engineer, would not
274
+ have been possible without it.
275
+
276
+ ## License
277
+
278
+ [MIT](https://github.com/TadayukiOkada/open-genie-server/blob/master/LICENSE)
279
+
280
+ This repository's license applies only to the open-genie-server source code itself. The Qualcomm QAIRT SDK, `libGenie.so`, and any Hexagon NPU models are not covered — they remain subject to Qualcomm's and each model's own distributor's license terms.
281
+
282
+ **MIT was a choice, not a default.** A bench instrument is the kind of project
283
+ people expect to find under a copyleft licence, so it is worth saying why this
284
+ one is not. Two reasons, both specific to what this is. It loads a proprietary
285
+ `libGenie.so` through ctypes at run time, and copyleft would put a
286
+ combined-work question in front of anyone shipping a board image that carries
287
+ both — a normal way to ship on this hardware, and not a question worth handing
288
+ to someone's legal team before they can measure a model. And the useful thing
289
+ to do with this code is take it apart: lift `logprobs.py` into your own
290
+ evaluation harness, fork it for the one endpoint your board needs. A licence
291
+ that taxes that is working against the point.
@@ -0,0 +1,244 @@
1
+ # open-genie-server
2
+
3
+ *[English](./README.md) | 日本語*
4
+
5
+ Qualcomm Genie C API (`libGenie.so`) を OpenAI互換のREST APIとして公開する、単一プロセスのFastAPIサーバです。[Hexagon NPU](./docs/MANUAL.ja.md#用語集)(本サーバが QNN HTP バックエンド経由で使うアクセラレータの、Qualcomm での呼び名)上で動くLLMを、`lm_eval`・`curl`・OpenAI SDK・[Open WebUI](https://github.com/open-webui/open-webui)等、一般的なOpenAI互換HTTPクライアントから叩けるようにします。実装本体は `genie_server` パッケージ(`src/genie_server/`)にあり、`genie-server.py` はランチャーです。
6
+
7
+ > [!IMPORTANT]
8
+ > このリポジトリは open-genie-server 自体のソースのみを含みます。動作させるには別途、Qualcomm から **QAIRT SDK**(`libGenie.so` を含む、Qualcomm の proprietary licenseで配布されるツールチェーン)と、Hexagon NPU向けにコンパイル済みのモデル一式(`genie_config.json` を含むモデルディレクトリ)を用意する必要があります。SDK・モデルはこのリポジトリには含まれておらず、[Qualcomm AI Hub](https://aihub.qualcomm.com/) 等から別途取得してください。
9
+
10
+ 設定と挙動は [MANUAL.ja.md](./docs/MANUAL.ja.md)、エンドポイントのリファレンスは [API.ja.md](./docs/API.ja.md)、そして**本ドキュメントの実測値がデバイスについて何を前提にしているか**は [プラットフォーム別の注意点](./docs/PLATFORM_NOTES.ja.md) を参照してください。
11
+
12
+ ## このプロジェクトの目的
13
+
14
+ **Genie C API と量子化済みモデルバンドルを検証するための計測器であって、本番用の推論サーバではありません。** 他のすべてはここから、この優先順位で導かれます:
15
+
16
+ 1. **Genie C API を、APIが許す限り広く使えるようにする。** チャットだけではありません。SDK側のプロファイリングカウンタ、パフォーマンスポリシー、カスタムサンプラーフック経由のプロンプトスコアリング、LoRA、prefixキャッシュのスナップショット、VLMスロットの背後にある `GenieNode`/`GeniePipeline` — これらすべてにHTTPから手が届きます。**触れるようにすること自体が目的**だからです。
17
+ 2. **標準的なベンチマークを、そのままデバイスに向けられるようにする。** `lm_eval` は無改造で通ります(生成タスクもloglikelihoodタスクも)。[examples/bfcl](./examples/bfcl) は Berkeley Function Calling Leaderboard を同じやり方で回します。**サーバに手を入れないと動かないベンチマークは、結局回さない**ものです。
18
+ 3. **SDKやモデルの問題を、既定では隠さない。** 見えない欠陥は、そのまま出荷される欠陥です。よって素のライブラリによるスロット恒久故障は検知も回避もしません。grammar が漏らす終端トークンは、取り除かずに報告します。応答が名乗るのは、クライアントが送ってきた文字列ではなく**実際にロードされているモデル**です。prefixキャッシュは明示的なウォームアップでしか埋まらないので、TTFTの計測を黙って改善してしまうことがありません。回避策はありますが、**何を隠すかを承知のうえで自分で入れるスイッチ**として用意してあります — マーカーが化けたツール呼び出しを組み立て直す `TOOL_CALL_RECOVERY` は、**自分で ON にするまで効きません**。
19
+ 4. **OpenAI API および他の主要な推論サーバの挙動との互換性を保つ — 3と衝突しない範囲で。** 両者が食い違うときは、実際に起きたことを報告する側を採ります。上に挙げた `model` フィールドがその実例です。OpenAI も vLLM もリクエストをそのまま返しますが、本サーバは返しません。**ホットスワップされたモデルで走ったベンチマークは、そう名乗るべき**だからです。
20
+
21
+ **後方互換性はこの中に入りません。** 意味もなく壊すことはしませんし、既存の応答形式や既定値を変える変更は [CHANGELOG](./docs/CHANGELOG.md) の Breaking に明記します。ただし本サーバは **Genie C API を覗くための窓**であり、**その API が動けば、こちらも動きます** — 既存の呼び出し側を守るためだけに古い形を残すことは、SDK が今どうなっているかについて**計測器に嘘をつかせる**ことになります。**自分たちの既定値についても同じ**です。ある既定値が何かを隠していると実測で分かれば、それだけで変更の理由になります。**動かない表面が必要なら、バージョンを固定してください。**
22
+
23
+ **目指していないもの。** 認証もレート制限もマルチプロセスのスケーリングもありません。`POST /v1/models/switch` はプロセスが読めるあらゆるパスを開きます。1つのテキストスロットは、単一の `GenieDialog` ハンドルの後ろでリクエストを直列化します。**自分で管理しているベンチ用ネットワークで動かしてください**([SECURITY.ja.md](./SECURITY.ja.md) に、それが何を意味するかと、報告してほしいことを書いてあります)。Hexagon上に本番のサービング基盤が要るなら、ここは出発点として間違っています — ただし、**あなたのバンドルとSDKが実際に何をしているかは、詳細に教えてくれます**。
24
+
25
+ ## 特徴
26
+
27
+ - `/v1/completions`・`/v1/chat/completions` — OpenAI互換のテキスト/チャット補完(ストリーミング対応。`/v1` なしのパスにも登録)
28
+ - **Function calling(`tools`)対応** — Qwen3系モデル向けのHermes形式ツール呼び出し。出力の `<tool_call>` をOpenAIの `message.tool_calls` / `finish_reason: "tool_calls"` に変換し、ストリーミング中は正しくホールドバック
29
+ - `lm_eval`(`local-completions` / `local-chat-completions`)にそのまま対応。トークンID形式のプロンプトもサーバ側でデコード
30
+ - **Logprobs対応**(SDKのカスタムサンプラーフック経由): 生成トークンの`logprobs`/`top_logprobs`(トークンあたり数msのオーバーヘッド、未使用時はゼロ)に加え、**プロンプトスコアリング**(`echo`+`logprobs`のteacher forcing)でlm_evalのloglikelihoodタスク(hellaswag, arc, mmlu等)も実行可能 — デコード速度で走るため`POST /v1/server/prompt_logprobs`によるゲート付き
31
+ - Open WebUIフレンドリー — parts配列形式 `content` のフラット化、`GET /health`、CORS、ストリーミング `usage` チャンク(`stream_options.include_usage`)
32
+ - システムプロンプトのprefix KVキャッシュ(モデル/LoRAごとにnamespace化)
33
+ - `GenieDialog_applyLora` 等によるLoRAアダプタのホットスワップ — 適用・強度・解放・読み戻しとも**実機で確認済み**
34
+ - `/v1/models/switch` によるモデルのホットスワップ(既定では旧モデルを解放してから新モデルをロードします。この順序が確実に切り替わりますが、ロードに失敗するとスロットは空になります。`"unload_first": false` は新旧を同時に載せることで旧モデルをフォールバックとして残せます — 使う前に下記の注意を参照)
35
+ - `Genie_PerformancePolicy_t` の切り替え(ベンチマーク時に `burst` 固定など)
36
+ - Context occupancy(KVキャッシュ占有量)を含む非ブロッキングなステータス監視
37
+ - **SDK側プロファイリング** — `GENIE_PROFILE` で Genie 自身が計測した TTFT/プレフィル/デコードのKPIを `GET /v1/server/profile` から取得([プロファイリング](./docs/MANUAL.ja.md#プロファイリングsdk側のkpi)参照)。OpenAIのレスポンス形状は一切変更しない
38
+ - **マルチテキストスロット対応** — `TEXT_SLOTS` 設定で、**使える**Hexagon NSPコア(cdsp0/cdsp1。HTP/NSP/cDSP/NPU の関係は[用語集](./docs/MANUAL.ja.md#用語集)参照)それぞれに独立した `GenieDialog` ハンドル(別ロック・別モデル可)を割り当て。別スロット宛のリクエストは実際に重なって進みますが、当方のベンチでの実測向上は **2倍ではなく約1.3倍**でした([マルチテキストスロット](./docs/MANUAL.ja.md#マルチテキストスロット)参照)。**何本使えるかは品番ではなくSKUのライセンスで決まります** — [プラットフォーム別の注意点](./docs/PLATFORM_NOTES.ja.md)参照
39
+ - **Grammar制約デコーディング** — JSON Schema/正規表現/EBNFで出力を制約(XGrammarバックエンド、モデル/スロット単位の固定設定)
40
+ - **VLM(マルチモーダル)対応** — Qwen3-VL等、`GenieNode`/`GeniePipeline` composable pipeline APIを使う画像入力モデルを、`VLM_SLOTS`設定で `TEXT_SLOTS` と完全に並列に追加可能([examples/vlm](./examples/vlm/README.ja.md)参照)
41
+ - オフラインテストスイート(`tests/`、pytest + fake SDK)— HTTP/エンジン/テンプレートの全スタックがNPUなしで動作確認可能
42
+
43
+ ## 必要要件
44
+
45
+ - Python 3.10+(`int | None` 等の型構文を使用)
46
+ - QAIRT SDK(`libGenie.so` とその依存ライブラリ)、Hexagon NPU上で動作するモデル一式(上記の注意参照)
47
+
48
+ ```bash
49
+ pip install .[logprobs,vlm] # 全部入り
50
+ pip install . # サーバ本体のみ: fastapi, uvicorn, tokenizers
51
+ ```
52
+
53
+ 配布名は `open-genie-server`、import するパッケージ名は `genie_server` です。
54
+ `pip install -r requirements.txt` も従来どおり使えます(上の1行目と同じ内容)。
55
+
56
+ | | 区分 | 用途 |
57
+ |---|---|---|
58
+ | `fastapi`, `uvicorn` | 本体 | サーバ |
59
+ | `tokenizers` | 本体 | 正確なトークン数カウント。無いと `text.split()` で数えるため**実際55トークンの日本語の段落が「1」になり**、usage だけでなくコンテキストチェックと既定 `max_tokens` にも効きます。[トークン数のカウント](./docs/MANUAL.ja.md#トークン数のカウント)を参照 |
60
+ | `numpy` | `[logprobs]`, `[vlm]` | **logprobsとプロンプトスコアリング**、およびVLM。無いとこれらのリクエストは HTTP 400 で拒否されます |
61
+ | `pillow` | `[vlm]` | 画像入力 |
62
+ | `pytest`, `httpx`, `requests`, `jsonschema` | `[test]` | オフラインテストスイート |
63
+
64
+ インストールすると `genie-server` コマンドが使えるようになります。リポジトリ直下の
65
+ `genie-server.py` ランチャーも同じもので、こちらはインストール不要です。
66
+ Android ではこのうち3つに wheel がありません —
67
+ [Androidで動かす](./docs/MANUAL.ja.md#androidで動かす)を参照してください。
68
+
69
+ > [!WARNING]
70
+ > **デプロイ前に、どのQAIRTバージョンを指しているか確認してください。**
71
+ > どのSDK欠陥を抱えることになるかはそのバージョンで決まります。そして
72
+ > **当方が検証した 2.49.x はいずれも、3つの欠陥を同じ一箇所に抱えています —
73
+ > `GenieDialog_reset()` が元に戻しそこねるもの**です。サーバはリクエストを独立させるために
74
+ > 毎回リセットするので、そこは**あなたが処理するすべてのリクエストの下を通る経路**です。
75
+ >
76
+ > 現れ方は、**過大なリクエスト1回でスロットが恒久的に壊れる**、
77
+ > **直前が短かったせいで長いリクエストが空のコンテキストで失敗する**、
78
+ > **投機デコード向けのバンドルで、最初のリセット以降の応答が流暢なまま間違って返る** —
79
+ > のいずれかです。**3つとも「成功」を報告します。**
80
+ >
81
+ > バージョン別の一覧、[手元のSDKで試せる確認手順](./docs/QAIRT_VERSIONS.ja.md#手元の-sdk-を確認する)、
82
+ > ライブラリの選び方は **[QAIRT バージョン別の問題点](./docs/QAIRT_VERSIONS.ja.md)** に
83
+ > まとめてあります。**そこに載っていないバージョンは
84
+ > 「問題が無いと分かっている」のではなく「未検証」**です。
85
+
86
+ ## クイックスタート
87
+
88
+ > [!NOTE]
89
+ > この節の例は `192.168.1.2:8080` でデバイスに接続しています。これは本サーバ固有の
90
+ > 値ではなく、**SA8255P の LV GVM が既定で起動してくるアドレス**です。当方の検証環境も
91
+ > これを使っているため、ドキュメント全体と
92
+ > `tests/integration/test_config.sample.json` に同じ値が出てきます。
93
+ > ご自身のデバイスのアドレスに置き換えてください(サーバとクライアントが同じマシン上
94
+ > なら `localhost` で構いません)。
95
+
96
+ 1. `env_config.json` をサーバ起動ディレクトリ(カレントディレクトリ)に用意します。
97
+
98
+ ```json
99
+ {
100
+ "QAIRT_SDK_ROOT": "/path/to/qairt-dir",
101
+ "HEXAGON_VERSION": "v73",
102
+ "MODELS_BASE_DIR": "/path/to/models",
103
+ "PREFIX_CACHE_DIR": "/path/to/prefix_cache",
104
+ "TEXT_SLOTS": [{"model_root": "model-dir"}]
105
+ }
106
+ ```
107
+
108
+ `model_root` は `genie_config.json` を含むディレクトリを指します。スロットに必須なのはこのキーだけで、`name` と `device_id` は既定値が使われます。
109
+
110
+ 相対パスの `model_root` は `MODELS_BASE_DIR` 配下として解決されるので、上の例は `/path/to/models/model-dir` をロードします。絶対パスも使えます — [モデルパスの解決ルール](./docs/MANUAL.ja.md#モデルパスの解決ルール)を参照。
111
+
112
+ 複数NSPコアを積んだSoCでは、コアごとにエントリを足すとそれぞれにモデルを常駐させられます(詳細と、2本目が必ず載るとは限らないという順序の制約は [MANUAL.ja.md](./docs/MANUAL.ja.md#マルチテキストスロット)):
113
+
114
+ ```json
115
+ {
116
+ "QAIRT_SDK_ROOT": "/path/to/qairt-dir",
117
+ "HEXAGON_VERSION": "v73",
118
+ "MODELS_BASE_DIR": "/path/to/models",
119
+ "PREFIX_CACHE_DIR": "/path/to/prefix_cache",
120
+ "TEXT_SLOTS": [
121
+ {"name": "tool_call", "device_id": 0, "model_root": "model-fast"},
122
+ {"name": "chat", "device_id": 1, "model_root": "model-general"}
123
+ ]
124
+ }
125
+ ```
126
+
127
+ 2. サーバを起動します。
128
+
129
+ ```bash
130
+ python3 genie-server.py # フラグ: --config/--host/--port
131
+ # または、パッケージをインストール済みなら
132
+ genie-server # フラグは同じ
133
+ # または uvicorn を直接
134
+ uvicorn genie_server.asgi:app --host 0.0.0.0 --port 8080 --workers 1
135
+ ```
136
+
137
+ 3. 動作確認:
138
+
139
+ ```bash
140
+ curl http://192.168.1.2:8080/v1/models
141
+
142
+ curl http://192.168.1.2:8080/v1/chat/completions \
143
+ -H "Content-Type: application/json" \
144
+ -d '{"model":"genie-local","messages":[{"role":"user","content":"こんにちは"}]}'
145
+ ```
146
+
147
+ Grammar制約デコーディングの設定例は [examples/grammar](./examples/grammar/README.ja.md)、VLM(画像入力)の設定・テスト手順は [examples/vlm](./examples/vlm/README.ja.md)、`lm_eval` の実行手順(量子化前モデルとの比較方法を含む)は [examples/lm_eval](./examples/lm_eval/README.ja.md) を参照してください。
148
+
149
+ ## lm_eval での利用
150
+
151
+ ```bash
152
+ lm_eval --model local-chat-completions \
153
+ --model_args model=genie-local,base_url=http://192.168.1.2:8080/v1,\
154
+ tokenizer_backend=huggingface,tokenizer=<hf_model>,max_tokens=512,num_concurrent=1 \
155
+ --tasks mmlu_generative --apply_chat_template --batch_size 1
156
+ ```
157
+
158
+ 設定と挙動の詳細は [MANUAL.ja.md](./docs/MANUAL.ja.md)、エンドポイントのリファレンスは [API.ja.md](./docs/API.ja.md) を参照してください。
159
+
160
+ ## ディレクトリ構成
161
+
162
+ ```
163
+ genie-server.py — ランチャー(CLIフラグ: --config/--host/--port)
164
+ src/genie_server/ — サーバ実装本体。関心ごとに1モジュール
165
+ pyproject.toml — パッケージング設定: 依存・extras・genie-server コマンド
166
+ requirements.txt — pyproject.toml の依存を指すだけのファイル(互換用)
167
+ SECURITY.md — 意図的に持っていないもの/報告してほしいもの(日本語版あり)
168
+ LICENSE
169
+
170
+ tests/ — オフラインテストスイート(pytest。fake SDKでNPU不要)
171
+ tests/integration/ — 実機に対するホスト側テストランナー(MD/JSONレポート出力)
172
+
173
+ docs/MANUAL.ja.md — 設定・挙動と、その理由(英語版は MANUAL.md)
174
+ docs/API.ja.md — 全エンドポイントを用途別にまとめたもの(英語版は API.md)
175
+ docs/PLATFORM_NOTES.ja.md — 実測値がデバイスについて前提にしていること(英語版あり)
176
+ docs/QAIRT_VERSIONS.ja.md — QAIRT バージョン別のSDK欠陥(英語版あり)
177
+ docs/CHANGELOG.md — リリースノート
178
+
179
+ examples/config/ — env_config.json サンプル(シングルスロット/デュアルNSP/VLM)
180
+ examples/grammar/ — Grammar制約デコーディング
181
+ examples/vlm/ — VLM(マルチモーダル)の設定・テスト手順
182
+ examples/lm_eval/ — lm_eval の実行と、量子化前モデルとの比較
183
+ examples/bfcl/ — Berkeley Function Calling Leaderboard
184
+ ```
185
+
186
+ `src/genie_server/` のモジュールごとの内訳は、ここで繰り返さず
187
+ [MANUAL.ja.md § アーキテクチャ概要](./docs/MANUAL.ja.md#アーキテクチャ概要) にまとめてあります。
188
+
189
+ オフラインテストの実行:
190
+
191
+ ```bash
192
+ pip install -e .[logprobs,vlm,test]
193
+ python3 -m pytest tests/
194
+ ```
195
+
196
+ `[test]` の依存は「任意」ではありません。`requests` と `jsonschema` が無いと
197
+ grammar のテストが8件 `ModuleNotFoundError` で落ち、`numpy` が無いと
198
+ logprobs のテストが8件落ちます。同じスイートは push と
199
+ pull request のたびに Python 3.10 / 3.12 で実行されます
200
+ (`.github/workflows/offline-tests.yml`)。
201
+
202
+ ホストPCから実機をエンドツーエンドで検査する統合テスト(Markdown/JSONレポート、サーバ停止検出付き)は [tests/integration/](./tests/integration/README.ja.md) を参照してください。
203
+
204
+ ## 既知の制約
205
+
206
+ - **本サーバは、このページ冒頭で説明した素のライブラリでのリセット系欠陥を防ぎません** — 検知も復旧もしません。回避はデプロイ側の選択です — [QAIRT バージョン別の問題点](./docs/QAIRT_VERSIONS.ja.md)を参照。
207
+ - **投機デコード向けにビルドされたバンドル(`"dialog": {"type": "ssd-q1"}`)にはパッチ版ライブラリが要り、それ無しでは LoRA も使えません。** 理由と、バンドル側の1行の書き換えで回避する方法は [D5](./docs/QAIRT_VERSIONS.ja.md#d5--リセットが投機デコードのダイアログを壊す) を参照。
208
+ - 1テキストスロット = 1 `GenieDialog` ハンドルで、スロット内のリクエストはそのスロット自身のロックで直列化されます(`TEXT_SLOTS` 未設定時は単一スロットのみで、従来通り全リクエストが直列化されます)。
209
+ - `n > 1`(1リクエストでの複数補完同時生成)は非対応で、`400` で拒否されます。
210
+ - Llama2/Mistralテンプレートはシステムプロンプトを `[INST]` に埋め込むため、prefix KVキャッシュの対象外。
211
+ - `POST /v1/models/switch` は既定で旧モデルを解放してから新モデルをロードするため、ロードに失敗するとそのスロットは次のswitchが成功するまでモデル未ロードのままになります。その間、そのスロットに触れる全エンドポイントは `503` を返します。
212
+ - `"unload_first": false` にすると新旧を同時にHTPデバイスへ載せてこれを避けられますが、**SA8255Pボードではこの同時常駐は当てになりません**。36回のスワップを実測した結果、成否は**どのモデルの組み合わせかでは決まりませんでした** — 同じ組み合わせがあるときは6回中6回成功し、別のときは8回中8回失敗し、6回の連続実行の途中で失敗から成功に転じたこともあります。決めているのはホストからは観測できないデバイス側の状態です。**デバイスのメモリに十分な余裕があり、かつ実運用で行うスワップをコールドスタートからの反復も含めてテスト済みの場合にだけ**使ってください。
213
+ - VLMスロットはシングルターンのみ(会話履歴の保持なし)、LoRA・Prefix KVキャッシュ・grammar制約・ホットスワップ非対応。詳細は [MANUAL.ja.md](./docs/MANUAL.ja.md#vlmマルチモーダル対応) を参照。
214
+
215
+ 他の詳細な制約事項は [MANUAL.ja.md の制約事項セクション](./docs/MANUAL.ja.md#制約事項) を参照してください。
216
+
217
+ ## 謝辞
218
+
219
+ 本プロジェクトは [Claude Code](https://claude.com/claude-code) を使って開発しました。
220
+ 最初のコミットは 2026-08-19 で、そこからの11日間で、2,700行の単一スクリプトから、
221
+ オフラインテスト311件・実機統合テスト・英日2言語のマニュアルを備えたパッケージに
222
+ なりました。
223
+
224
+ **時間がかかったのはコードではありません。** QAIRT SDK のリファレンス実装を
225
+ 「SDK の欠陥」と「こちらのバグ」を切り分けられる程度まで読み込むこと、その一つひとつを
226
+ 実機で再現させてどちらなのかを確定させること、そして**推測ではなく実測を書き残す**こと —
227
+ 本ドキュメントの記述にも、当時は自明に見えた結論を後から覆したものがいくつもあります。
228
+ これを**一人でこの速度でやることは、Claude Code なしでは不可能でした。**
229
+
230
+ ## License
231
+
232
+ [MIT](./LICENSE)
233
+
234
+ 本リポジトリのライセンスはopen-genie-server自体のソースコードにのみ適用されます。Qualcomm QAIRT SDK・`libGenie.so`・Hexagon NPU向けモデルは対象外で、それぞれQualcommおよび各モデルの配布元のライセンス条件に従います。
235
+
236
+ **MIT は既定値ではなく選択です。** この種の計測器はコピーレフトで公開されていることが
237
+ 多いので、そうしなかった理由を書いておきます。理由は2つあり、どちらもこのプロジェクト
238
+ 固有のものです。ひとつは、本サーバが proprietary な `libGenie.so` を ctypes で
239
+ **実行時にロードする**こと。コピーレフトにすると、**このサーバと SDK を同梱した
240
+ ボードイメージを配布する**人に結合著作物の解釈問題を負わせることになります。これは
241
+ このハードウェアでは普通の配布形態であり、モデルを1つ測る前に法務レビューを挟ませるだけの
242
+ 価値はありません。もうひとつは、**このコードの正しい使い方が「分解すること」**だという点です。
243
+ `logprobs.py` を自分の評価ハーネスに持っていく、自分のボードに必要なエンドポイント1つの
244
+ ために fork する — そこに課税するライセンスは、このプロジェクトの目的に逆行します。