원클릭으로
dev-qairt-runtime
Reference for developing against the QAIRT/QNN runtime — graph execution, KV cache, tensor I/O, multi-shard wiring
Codex 또는 Claude로 설치 이 Prompt를 복사해 Codex, Claude 또는 다른 어시스턴트에 붙여 넣으면 Skill 페이지를 검토하고 설치를 진행할 수 있습니다.
메뉴
Reference for developing against the QAIRT/QNN runtime — graph execution, KV cache, tensor I/O, multi-shard wiring
Codex 또는 Claude로 설치 이 Prompt를 복사해 Codex, Claude 또는 다른 어시스턴트에 붙여 넣으면 Skill 페이지를 검토하고 설치를 진행할 수 있습니다.
SOC 직업 분류 기준
| name | dev-qairt-runtime |
| description | Reference for developing against the QAIRT/QNN runtime — graph execution, KV cache, tensor I/O, multi-shard wiring |
| user-invocable | true |
Use this skill when writing or modifying runtime code that interacts with QNN graphs, KV cache, or the inference loop.
Each compiled context binary (.bin) contains one or more graphs. A graph is a single forward pass for a fixed (sequence_length, context_length) configuration.
context_binary.bin
├── prompt_ar128_cl512_1_of_2 (prefill, 128 tokens, CL=512, shard 1)
├── prompt_ar128_cl512_2_of_2 (prefill, 128 tokens, CL=512, shard 2)
├── token_ar1_cl512_1_of_2 (decode, 1 token, CL=512, shard 1)
└── token_ar1_cl512_2_of_2 (decode, 1 token, CL=512, shard 2)
LLMModel::onInitialized parses graph names with regex
(?:[A-Za-z]+_)?ar(\d+)_cl(\d+)_(\d+)_of_(\d+), accepting both prefixed
(prompt_/token_) and unprefixed forms. Phase is inferred from ar
(largest AR ⇒ prefill, smallest ⇒ decode). The CL set, AR set, and
seq_len_prefill / seq_len_decode on LLMSpec are all populated from
the loaded QNN graph names — nothing to set on the spec.
<phase> — prompt (prefill) or token (decode), optionalar — autoregressive length (e.g. 128 or 1)cl — context lengthshard / total — shard index and count// Type-safe write (handles float32, float16, ufixed8/16, int32 conversion)
graph.write("input_tensor_name", float_ptr);
// Direct buffer access (zero-copy, caller handles dtype)
float* ptr = static_cast<float*>(graph.inputPtr("tensor_name"));
graph.read("output_tensor_name", float_output_ptr);
float* ptr = static_cast<float*>(graph.outputPtr("tensor_name"));
auto input_specs = graph.inputSpecs(); // vector<TensorSpec>
auto output_specs = graph.outputSpecs(); // vector<TensorSpec>
// TensorSpec has: name, dtype, shape, quantization params
Large models are split across shards. Each shard processes a subset of layers. Hidden states are passed between shards via connections.
Shard 1: input_ids → hidden_state_mid
Shard 2: hidden_state_mid → logits
The LLMSpec.shards field defines this wiring:
.shards = {
ShardSpec{"input_ids", "hidden_state_mid"},
ShardSpec{"hidden_state_mid", "logits"},
}
The runtime automatically routes shard N's output tensor to shard N+1's input tensor.
[num_layers, num_kv_heads, context_length, head_dim]
Pre-allocated at max CL. Unused positions are zero-padded and masked via the attention mask.
.state_blocks = {
makeKVOnlyStateBlock({
std::nullopt, // shard 0: no KV (embedding-only)
LayerRange{0, 15}, // shard 1: layers 0-15
LayerRange{16, 31}, // shard 2: layers 16-31
}),
}
.kv_key_in_pattern = "past_key_{}_in" // {} = layer index
.kv_value_in_pattern = "past_value_{}_in"
.kv_key_out_pattern = "past_key_{}_out"
.kv_value_out_pattern = "past_value_{}_out"
When a model has multiple CL variants (e.g. 512, 1024, 2048, 4096):
The mask must handle three concerns simultaneously:
For prefill with padding:
Prompt: [A, B, C, PAD, PAD, ...] (padded to 128)
Mask row for token C (position 2): [1, 1, 1, 0, 0, ..., 0]
↑attend ↑pad ↑unfilled KV
1. Tokenize prompt → token_ids
2. For each 128-token chunk:
a. Prepare inputs (embeddings/token_ids, RoPE, attention mask)
b. Execute prefill graph for each shard sequentially
c. Update KV cache position counter
3. Decode loop (until EOS or max_length):
a. Prepare inputs for single token
b. Execute decode graph for each shard
c. Sample next token from logits
d. Increment position
e. Promote CL if needed
Multiple graph variants (different AR lengths, different CLs) share the same weight tensors in memory. Only activation buffers and KV cache differ. This is managed automatically by QNN when graphs are loaded from the same context binary.
Prefill (ARN) and decode (AR1) graphs share input/output buffers since they never execute concurrently. Writing to one graph's input buffer modifies the other's. This is intentional and saves memory.
| Export type | Tensor dtype | Notes |
|---|---|---|
| Genie w4 | float16 | Weights quantized, activations in fp16 |
| Genie w8 | float32 | Standard precision |
| AI Hub | varies | Check TensorSpec.dtype at runtime |
Graph::write(float*) and Graph::read(float*) handle conversion transparently. You always work with float32 on the CPU side.
InputProvider logic efficient--verbose flag on example executables for timing breakdownAdd a new LLM model to the geniex runtime (creates spec header, example executable, CMakeLists)
Convert xtensor prototype code to production QNN direct-buffer operations (zero-copy)
Write C++ tensor logic using xtensor (NumPy-like API) for prototyping before QNN conversion