Skip to content

feat: unify HTP power-mode control across qairt and llama_cpp - #1447

Draft
Mengsheng Wu (mengshengwu) wants to merge 8 commits into
mainfrom
feat/unified-power-mode
Draft

feat: unify HTP power-mode control across qairt and llama_cpp#1447
Mengsheng Wu (mengshengwu) wants to merge 8 commits into
mainfrom
feat/unified-power-mode

Conversation

@mengshengwu

Copy link
Copy Markdown
Contributor

Summary

  • Adds one libgeniex alias table (geniex_resolve_power_mode) for HTP DCVS/HMX power/clock-management, shared by the qairt and llama_cpp plugins instead of each having its own knob (or none).
  • Surfaces it as --power-mode on geniex infer, geniex run, geniex serve (with a per-request JSON override), and geniex-bench.
  • qairt: sets ModelConfig::perf_profile; warns when a bundle's own htp_backend_ext_config.json overrides the requested mode.
  • llama_cpp: carried by two patches on top of an unmerged internal PR (ggml/llama.cpp#340) that adds the DCVS/HMX power-mode table to ggml-hexagon. This PR stays draft until that upstream PR merges and the third-party/llama.cpp pin can be bumped past it — at that point llama-hexagon-power-mode.patch should be deleted; llama-hexagon-power-mode-setter.patch is a permanent GenieX addition.
  • Empty / default resolves to burst on both plugins, matching each plugin's pre-existing behavior (qairt already defaulted to BURST; llama_cpp's HTP corners were hardcoded to max).

Test plan

Verified on a Snapdragon X Elite device (Windows ARM64), signed local HTP build:

  • geniex-bench --plugin llama_cpp --device npu --power-mode {burst,sustained_high_performance,power_saver} — each logs ggml-hex: HTP0 power mode: <mode>; power_saver prefill tok/s is measurably lower than burst.
  • No --power-mode produces byte-identical logging/perf to --power-mode burst on llama_cpp and qairt.
  • geniex-bench --plugin qairt --device npu --power-mode balanced against a bundle whose htp_backend_ext_config.json pins perf_profile to burst — the plugin logs a warning and the bundle wins (both LLM and VLM vision-encoder configs).
  • geniex-bench --plugin qairt --vlm --power-mode power_saver (VLM path) applies the mode to both the LLM and vision-encoder configs.
  • --power-mode turbo (invalid alias) is rejected fast on both plugins, before any model load.
  • --device cpu --power-mode balanced on llama_cpp logs a warning and ignores the mode instead of failing.
  • pytest tests -m "api or (llama_cpp and device_cpu)" — 35/35 pass.
  • go mod tidy — no diff.

Not independently exercised here (pre-existing behavior, unrelated to this change): geniex run's HTTP passthrough of power_mode, and the keep-alive cache-key change for power_mode in geniex serve — both follow the exact pattern already used for compute/spec_type.

geniex_resolve_power_mode maps one alias table (low_power_saver..burst,
default) to a shared geniex_PowerMode enum, mirroring how
geniex_resolve_device owns the compute-unit aliases. Both plugins will
call it to normalize --power-mode instead of each defining its own
table.

llama_cpp support rides on an unmerged internal PR
(ggml/llama.cpp#340): two patches carry it — one mirrors that PR,
the other is a permanent GenieX addition exposing a setter proc so
the plugin can change the mode per model load instead of only once
at process start via an env var.

Signed-off-by: Mengsheng Wu <mengshengwu@outlook.com>
llama_cpp: htp_session gains set_power_mode, which calls the setter
proc then releases idle HTP sessions so the next load's reacquire
picks up the new mode. A session already in use by another loaded
model keeps its old mode until released -- logged, not silently
dropped.

qairt: sets ModelConfig::perf_profile from the resolved mode on both
the LLM and VLM (vision encoder) configs. Model::initialize seeds
perf_profile from the caller but then lets a bundle's
htp_backend_ext_config.json overwrite it, so the plugin probes the
bundle with the same parseHtpConfig and warns when it disagrees with
--power-mode, rather than silently losing the caller's choice.

Signed-off-by: Mengsheng Wu <mengshengwu@outlook.com>
Adds the power_mode field to ModelConfig across all three FFI
surfaces, plus a Go ResolvePowerMode wrapper for CLI-side fail-fast
validation before touching model load (Python and Android pass the
string straight through; each plugin validates it on create).

Signed-off-by: Mengsheng Wu <mengshengwu@outlook.com>
infer / run share the --power-mode flag on llmFlags; serve adds it as
a per-server default (--power-mode / GENIEX_POWER_MODE) overridable
per request via the power_mode JSON field, added to ModelParam so the
keep-alive cache reloads the model instead of silently reusing one
loaded under a different mode.

Signed-off-by: Mengsheng Wu <mengshengwu@outlook.com>
Threads through to geniex_ModelConfig via the existing
fill_model_config, and is recorded in the per-cell JSON params block
when set; bumps schema_version to 5.

Signed-off-by: Mengsheng Wu <mengshengwu@outlook.com>
Adds a Power mode section to notes/run.md mirroring the existing
Compute-unit aliases section (alias table, default-is-burst,
llama_cpp's unmerged-PR caveat, qairt's bundle-override precedence),
plus the --power-mode row in the CLI and geniex-bench references.

Signed-off-by: Mengsheng Wu <mengshengwu@outlook.com>
geniex-qairt-plugin#55 fixes Model::initialize to let a caller-supplied
perf_profile win over the bundle's htp_backend_ext_config.json, instead
of the bundle silently overwriting it. Pulls in 18ee1bfc..277c45c3.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Signed-off-by: Mengsheng Wu <mengshengwu@outlook.com>
apply_power_mode() used to probe the bundle's htp_backend_ext_config.json
and warn when it disagreed with the caller's --power-mode, because the
bundle silently won. geniex-qairt-plugin#55 fixed the precedence itself
(caller > bundle > default), so the probe is dead code; drop it and
update the docs to describe the corrected precedence.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Signed-off-by: Mengsheng Wu <mengshengwu@outlook.com>
@mengshengwu

Copy link
Copy Markdown
Contributor Author

Rebased onto latest `main` and bumped `geniex-qairt` to geniex-qairt-plugin#55, which fixes precedence so a caller-supplied `perf_profile` now wins over a bundle's `htp_backend_ext_config.json`. Dropped the now-dead bundle-override warning workaround accordingly.

On-device (Snapdragon X Elite), `qualcomm/Qwen3-4B` (bundle pins `perf_profile=burst`):

`--power-mode` Applied `perf_profile` Notes
(default) 5 (burst) matches bundle, no regression
`balanced` 1 (balanced) correctly overrides bundle's burst
`power_saver` 8 (power_saver) correctly overrides bundle's burst
`turbo` (invalid) fails fast with the valid alias list

No override warning fires anymore — the caller's request actually takes effect now.

`llama_cpp`, `unsloth/Qwen3-0.6B-GGUF` on NPU: `--power-mode sustained_high_performance` → `ggml-hex: HTP0 power mode: sustained_high_performance`, confirmed applied before session creation.

`go mod tidy`: clean. SDK + CLI build green.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants