Add a new model
Add a new model called $ARGUMENTS (or ask the user for the model name if not provided).
Steps
Create model directory: models/<name>/
Usually no header. A plain decoder-only model needs none: call
geniex::auto_llm::makeModel / makePipeline from
core/include/pipeline/auto_llm.h. Nothing else is needed: it is served
automatically as makeLLMPipeline's fallback in models/dispatch.h.
prepend_bos is the only per-family knob -- keyed off config.json's
architectures[0], not model_id -- and only Qwen3 needs it true today
(a leading BOS its chat template does not emit; Llama-3, Qwen2.5, Falcon3
and Phi do not).
Create <name>.h only if the family overrides runtime behaviour (a custom
LLMModel subclass, extra InputProviders).
Create <name>_example.cpp — example executable:
- Parse command-line arguments
- Configure
QnnRuntimeConfig (backend paths)
- Configure
ModelConfig (model binary paths, tokenizer)
- Initialize model with
model.initialize(runtime_cfg, model_cfg)
- Run inference loop with
model.generate()
Create CMakeLists.txt:
add_executable(<name> <name>_example.cpp)
target_link_libraries(<name> PRIVATE geniex_core geniex-proc)
set_target_properties(<name> PROPERTIES RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin)
Update root CMakeLists.txt:
- Add
add_subdirectory(${CMAKE_SOURCE_DIR}/models/<name>)
- Add include dir to
geniex_core target
Verify build: cmake --build build --config Release --target <name> -j32
LLMSpec structure
LLMSpec uses two key fields for shard layout:
.shards — vector of ShardSpec{in_state_name, out_state_name}, one per shard
.state_blocks — vector of StateBlockSpec. Use makeKVOnlyStateBlock(...) with per-shard LayerRange{begin, end} or std::nullopt for shards with no KV cache
Example (3-shard model with embedding shard + 2 KV shards):
.shards = {
{"input_ids", "_model_model_embed_tokens_Gather_output_0"},
{"_model_model_embed_tokens_Gather_output_0", "_model_model_layers_7_Add_1_output_0"},
{"_model_model_layers_7_Add_1_output_0", "logits"},
},
.state_blocks = {
makeKVOnlyStateBlock({std::nullopt, LayerRange{0, 7}, LayerRange{8, 15}}),
},
Choosing InputProvider
| Provider |
When to use |
TokenIdInputProvider |
Genie/AI Hub exports (on-device embedding, shard 0 takes input_ids) |
EmbeddingInputProvider |
Custom exports with CPU-side embedding table (needs model_cfg.embedding_path) |
RoPEInputProvider |
Standard RoPE, no scaling (Qwen3, Falcon3, etc.) |
LongRoPEInputProvider |
Long-rope with dynamic scaling + per-dimension ext_factors (Phi3.5) |
PartialRoPEInputProvider |
Partial-dimension RoPE with rope_fraction and scale |
Llama3RoPEInputProvider |
Llama 3 frequency-dependent scaling (factor=32 for 3.2, factor=8 for 3.1) |
Common pitfalls
- Tensor names: metadata.yaml uses ONNX-style slashes (
/model/model/...) but QNN graphs may use underscores (_model_model_...). Verify at runtime via graph.inputSpecs()/graph.outputSpecs().
- Graph name patterns:
LLMModel::onInitialized auto-detects both prefixed (prompt_arN_clM_S_of_T, token_arN_clM_S_of_T) and unprefixed (arN_clM_S_of_T) graph names via regex; nothing to set on LLMSpec.
- Tensor dtypes: Some exports use float16, others float32 or quantized.
Graph::write(float*) / Graph::read(float*) handle conversion.
- Linker: Example executables must link
geniex-proc explicitly (PRIVATE linkage in geniex_core doesn't propagate).
- HTP version: Bundled runtime is QAIRT v2.45.0.260326. Verify model compile version from shard
.json buildId field.
1---2name: add-model3description: Add a new LLM model to the geniex runtime (creates spec header, example executable, CMakeLists)4---56# Add a new model78Add a new model called `$ARGUMENTS` (or ask the user for the model name if not provided).910## Steps11121. **Create model directory**: `models/<name>/`13142. **Usually no header.** A plain decoder-only model needs none: call15 `geniex::auto_llm::makeModel` / `makePipeline` from16 `core/include/pipeline/auto_llm.h`. Nothing else is needed: it is served17 automatically as `makeLLMPipeline`'s fallback in `models/dispatch.h`.18 `prepend_bos` is the only per-family knob -- keyed off config.json's19 `architectures[0]`, not `model_id` -- and only Qwen3 needs it true today20 (a leading BOS its chat template does not emit; Llama-3, Qwen2.5, Falcon321 and Phi do not).2223 Create `<name>.h` only if the family overrides runtime behaviour (a custom24 `LLMModel` subclass, extra InputProviders).25263. **Create `<name>_example.cpp`** — example executable:27 - Parse command-line arguments28 - Configure `QnnRuntimeConfig` (backend paths)29 - Configure `ModelConfig` (model binary paths, tokenizer)30 - Initialize model with `model.initialize(runtime_cfg, model_cfg)`31 - Run inference loop with `model.generate()`32334. **Create `CMakeLists.txt`**:34 ```cmake35 add_executable(<name> <name>_example.cpp)36 target_link_libraries(<name> PRIVATE geniex_core geniex-proc)37 set_target_properties(<name> PROPERTIES RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin)38 ```39405. **Update root `CMakeLists.txt`**:41 - Add `add_subdirectory(${CMAKE_SOURCE_DIR}/models/<name>)`42 - Add include dir to `geniex_core` target43446. **Verify build**: `cmake --build build --config Release --target <name> -j32`4546## LLMSpec structure4748`LLMSpec` uses two key fields for shard layout:4950- **`.shards`** — vector of `ShardSpec{in_state_name, out_state_name}`, one per shard51- **`.state_blocks`** — vector of `StateBlockSpec`. Use `makeKVOnlyStateBlock(...)` with per-shard `LayerRange{begin, end}` or `std::nullopt` for shards with no KV cache5253Example (3-shard model with embedding shard + 2 KV shards):54```cpp55.shards = {56 {"input_ids", "_model_model_embed_tokens_Gather_output_0"},57 {"_model_model_embed_tokens_Gather_output_0", "_model_model_layers_7_Add_1_output_0"},58 {"_model_model_layers_7_Add_1_output_0", "logits"},59},60.state_blocks = {61 makeKVOnlyStateBlock({std::nullopt, LayerRange{0, 7}, LayerRange{8, 15}}),62},63```6465## Choosing InputProvider6667| Provider | When to use |68|----------|-------------|69| `TokenIdInputProvider` | Genie/AI Hub exports (on-device embedding, shard 0 takes `input_ids`) |70| `EmbeddingInputProvider` | Custom exports with CPU-side embedding table (needs `model_cfg.embedding_path`) |71| `RoPEInputProvider` | Standard RoPE, no scaling (Qwen3, Falcon3, etc.) |72| `LongRoPEInputProvider` | Long-rope with dynamic scaling + per-dimension `ext_factors` (Phi3.5) |73| `PartialRoPEInputProvider` | Partial-dimension RoPE with `rope_fraction` and `scale` |74| `Llama3RoPEInputProvider` | Llama 3 frequency-dependent scaling (factor=32 for 3.2, factor=8 for 3.1) |7576## Common pitfalls7778- **Tensor names**: metadata.yaml uses ONNX-style slashes (`/model/model/...`) but QNN graphs may use underscores (`_model_model_...`). Verify at runtime via `graph.inputSpecs()`/`graph.outputSpecs()`.79- **Graph name patterns**: `LLMModel::onInitialized` auto-detects both prefixed (`prompt_arN_clM_S_of_T`, `token_arN_clM_S_of_T`) and unprefixed (`arN_clM_S_of_T`) graph names via regex; nothing to set on `LLMSpec`.80- **Tensor dtypes**: Some exports use float16, others float32 or quantized. `Graph::write(float*)` / `Graph::read(float*)` handle conversion.81- **Linker**: Example executables must link `geniex-proc` explicitly (PRIVATE linkage in geniex_core doesn't propagate).82- **HTP version**: Bundled runtime is QAIRT v2.45.0.260326. Verify model compile version from shard `.json` `buildId` field.