From ec0bfac1794ad722221dd68d84760f8980355fb2 Mon Sep 17 00:00:00 2001 From: Pranav Dhinakar Date: Tue, 13 Oct 2026 07:47:55 -0700 Subject: [PATCH] hexagon: add configurable HTP DCVS/HMX power mode (#30423) * hexagon: add configurable HTP DCVS/HMX power mode * hexagon: address PR review feedback (drop CLI flag, align QAIRT power modes, comment/log cleanup) * hexagon: add --hex-power-mode flag to run.py Hooks GGML_HEXAGON_POWER_MODE into the unified run.py runner, following the existing --hex-nhvx pattern. The env var must be set before the process touches the Hexagon backend registry (see comment in ggml_hexagon_init), so this is exposed as a runner flag rather than a common/arg.cpp CLI option. * hexagon: keep HTP awake for DCVS power profiles * Apply suggestion from @max-krasnyansky --------- Co-authored-by: Max Krasnyansky --- docs/backend/snapdragon/README.md | 25 ++++++ ggml/src/ggml-hexagon/ggml-hexagon.cpp | 47 ++++++++++- ggml/src/ggml-hexagon/htp/htp-ops.h | 14 ++++ ggml/src/ggml-hexagon/htp/htp_iface.idl | 2 +- ggml/src/ggml-hexagon/htp/main.c | 105 +++++++++++++++++++++--- scripts/snapdragon/run.py | 2 + 6 files changed, 180 insertions(+), 15 deletions(-) diff --git a/docs/backend/snapdragon/README.md b/docs/backend/snapdragon/README.md index ca79ca885..b8347a224 100644 --- a/docs/backend/snapdragon/README.md +++ b/docs/backend/snapdragon/README.md @@ -330,6 +330,31 @@ on 4 physical NPUs, or `--devices 'HTP0[0-1:0],HTP1[0-1:1]'` on 2 physical NPUs - `GGML_HEXAGON_DMA64=0` (default: enabled on v81+) Disables 64-bit DMA for model weights. Set to `1` to enable it explicitly on a supported architecture. +- `GGML_HEXAGON_POWER_MODE=default` + Controls the DCVS/HMX power mode requested from the HTP DSP for the session. Modes are listed + from lowest to highest power. All modes except `burst` leave DCVS enabled and keep the HTP awake. + + - `low_power_saver` + - `power_saver` + - `high_power_saver` + - `low_balanced` + - `balanced` + - `high_performance` + - `sustained_high_performance` + - `burst` (maximum clocks, disables DCVS; may thermal-throttle on sustained workloads) + - `default` (currently aliases to `burst`) + + Unknown values are logged as a warning and fall back to `burst`. + + This must be set in the environment before the process initializes the backend registry, so + there is no `llama-*` CLI flag for it. The `run.py` runner exposes it as `--hex-power-mode`. + + Examples: + + `GGML_HEXAGON_POWER_MODE=balanced llama-completion ...` + `./scripts/snapdragon/run.py --hex-power-mode sustained_high_performance ...` + + - `GGML_HEXAGON_VERBOSE=1` Enables verbose logging of Ops from the backend. Example output: diff --git a/ggml/src/ggml-hexagon/ggml-hexagon.cpp b/ggml/src/ggml-hexagon/ggml-hexagon.cpp index 1c1c92da2..08c15ff78 100644 --- a/ggml/src/ggml-hexagon/ggml-hexagon.cpp +++ b/ggml/src/ggml-hexagon/ggml-hexagon.cpp @@ -108,6 +108,41 @@ static int opt_verbose = 0; static int opt_profile = 0; // profiling mode (0-disabled, 1-basic, 2-pmu) static bool opt_hostbuf = false; static bool opt_dma64 = false; +static int opt_power_mode = HTP_POWER_MODE_BURST; // DCVS/HMX power profile requested at session start + +static const std::pair htp_power_mode_names[] = { + { "low_power_saver", HTP_POWER_MODE_LOW_POWER_SAVER }, + { "power_saver", HTP_POWER_MODE_POWER_SAVER }, + { "high_power_saver", HTP_POWER_MODE_HIGH_POWER_SAVER }, + { "low_balanced", HTP_POWER_MODE_LOW_BALANCED }, + { "balanced", HTP_POWER_MODE_BALANCED }, + { "high_performance", HTP_POWER_MODE_HIGH_PERFORMANCE }, + { "sustained_high_performance", HTP_POWER_MODE_SUSTAINED_HIGH_PERFORMANCE }, + { "burst", HTP_POWER_MODE_BURST }, + { "default", HTP_POWER_MODE_BURST }, // flip this to change the default mode +}; + +static int htp_power_mode_from_str(const char * s) { + for (auto & e : htp_power_mode_names) { +#if defined(_MSC_VER) + if (_stricmp(s, e.first) == 0) { +#else + if (strcasecmp(s, e.first) == 0) { +#endif + return e.second; + } + } + return -1; +} + +static const char * htp_power_mode_to_str(int mode) { + for (auto & e : htp_power_mode_names) { + if (e.second == mode) { + return e.first; + } + } + return "unknown"; +} static size_t opt_mbuf_dyn = 512ul * 1024 * 1024; // max dynamic (compute) buffer size static size_t opt_mbuf_static = 1ul * 1024 * 1024 * 1024; // max static (weight/KV) buffer size @@ -4956,12 +4991,13 @@ void ggml_hexagon_session::allocate(const ggml_hexagon_device_config & config) n this->op_batch = new ggml_hexagon_opbatch(this, opt_opbatch, this->max_vmem); // Start dspqueue/opbatch processing - err = htp_iface_start(this->handle, this->session_id, this->queue_id, this->n_threads, opt_nhmx, this->max_vmem); + err = htp_iface_start(this->handle, this->session_id, this->queue_id, this->n_threads, opt_nhmx, this->max_vmem, (uint32_t) opt_power_mode); if (err != 0) { GGML_LOG_ERROR("ggml-hex: %s failed to start session: 0x%08x\n", this->c_name(), (unsigned) err); throw std::runtime_error("ggml-hex: iface start failed (see log for details)"); } this->valid_iface = true; + GGML_LOG_INFO("ggml-hex: %s power mode: %s\n", this->c_name(), htp_power_mode_to_str(opt_power_mode)); if (opt_profile) { htp_iface_pmu_conf pmu_conf{}; @@ -9959,6 +9995,7 @@ static void ggml_hexagon_init(ggml_backend_reg * reg) { const char * str_optrace = getenv("GGML_HEXAGON_OPTRACE"); const char * str_hostbuf = getenv("GGML_HEXAGON_HOSTBUF"); const char * str_dma64 = getenv("GGML_HEXAGON_DMA64"); + const char * str_power_mode = getenv("GGML_HEXAGON_POWER_MODE"); // Init Arch first since it affects other defaults if (!str_arch) { @@ -9999,6 +10036,14 @@ static void ggml_hexagon_init(ggml_backend_reg * reg) { opt_opfusion = str_opfusion ? atoi(str_opfusion) : opt_opfusion; opt_profile = str_profile ? atoi(str_profile) : 0; opt_etm = str_etm ? atoi(str_etm) : 0; + if (str_power_mode) { + int m = htp_power_mode_from_str(str_power_mode); + if (m < 0) { + GGML_LOG_WARN("ggml-hex: unknown GGML_HEXAGON_POWER_MODE '%s', falling back to 'burst'\n", str_power_mode); + } else { + opt_power_mode = m; + } + } opt_nhvx = str_nhvx ? strtoul(str_nhvx, NULL, 0) : opt_nhvx; opt_nhmx = str_nhmx ? atoi(str_nhmx) : opt_nhmx; opt_mm_select = str_mm_select ? atoi(str_mm_select) : opt_mm_select; diff --git a/ggml/src/ggml-hexagon/htp/htp-ops.h b/ggml/src/ggml-hexagon/htp/htp-ops.h index 5405c83b6..70d56e181 100644 --- a/ggml/src/ggml-hexagon/htp/htp-ops.h +++ b/ggml/src/ggml-hexagon/htp/htp-ops.h @@ -197,6 +197,20 @@ enum htp_profiler_mode { HTP_PROF_TRACE = 3, }; +// DCVS/HMX power profile requested for the session. +enum htp_power_mode { + HTP_POWER_MODE_LOW_POWER_SAVER = 0, + HTP_POWER_MODE_POWER_SAVER = 1, + HTP_POWER_MODE_HIGH_POWER_SAVER = 2, + HTP_POWER_MODE_LOW_BALANCED = 3, + HTP_POWER_MODE_BALANCED = 4, + HTP_POWER_MODE_HIGH_PERFORMANCE = 5, + HTP_POWER_MODE_SUSTAINED_HIGH_PERFORMANCE = 6, + HTP_POWER_MODE_BURST = 7, + + HTP_POWER_MODE_COUNT +}; + enum htp_trace_event_id { HTP_TRACE_EVT_DMA = 0, HTP_TRACE_EVT_L2FLUSH = 1, diff --git a/ggml/src/ggml-hexagon/htp/htp_iface.idl b/ggml/src/ggml-hexagon/htp/htp_iface.idl index b46e25296..303dbfb0a 100644 --- a/ggml/src/ggml-hexagon/htp/htp_iface.idl +++ b/ggml/src/ggml-hexagon/htp/htp_iface.idl @@ -11,7 +11,7 @@ struct htp_iface_pmu_conf { }; interface htp_iface : remote_handle64 { - AEEResult start(in uint32 sess_id, in uint64 dsp_queue_id, in uint32 n_hvx, in uint32 n_hmx, in uint64 max_vmem); + AEEResult start(in uint32 sess_id, in uint64 dsp_queue_id, in uint32 n_hvx, in uint32 n_hmx, in uint64 max_vmem, in uint32 power_mode); AEEResult stop(); AEEResult mmap(in uint32 fd, in uint64 size); AEEResult munmap(in uint32 fd); diff --git a/ggml/src/ggml-hexagon/htp/main.c b/ggml/src/ggml-hexagon/htp/main.c index aff1c6c0f..df7c4a5bd 100644 --- a/ggml/src/ggml-hexagon/htp/main.c +++ b/ggml/src/ggml-hexagon/htp/main.c @@ -363,12 +363,91 @@ static void htp_main_thread(void * context); static void htp_packet_callback(dspqueue_t queue, int error, void * context); static void htp_error_callback(dspqueue_t queue, int error, void * context); -AEEResult htp_iface_start(remote_handle64 handle, uint32_t sess_id, uint64_t dsp_queue_id, uint32_t n_hvx, uint32_t n_hmx, uint64_t max_vmem) { +// Non-BURST modes leave DCVS enabled and keep the HTP awake. +// BURST pins all corners to MAX and disables DCVS. +// Corner constants are from HAP_power.h (HAP_dcvs_voltage_corner_t / HAP_dcvs_exp_vcorner_t). +struct htp_power_profile { + boolean dcvs_enable; + uint32_t bus_min_corner; + uint32_t bus_max_corner; + uint32_t bus_target_corner; + uint32_t core_min_corner; + uint32_t core_max_corner; + uint32_t core_target_corner; + boolean sleep_disable; + uint32_t hmx_min_corner; // only used when __HVX_ARCH__ >= 75 + uint32_t hmx_max_corner; + uint32_t hmx_target_corner; + uint32_t hmx_perf_mode; +}; + +#define HTP_POWER_PROFILE_ENTRY(min_c, max_c, hmx_c) \ + { \ + .dcvs_enable = TRUE, \ + .bus_min_corner = (min_c), \ + .bus_max_corner = (max_c), \ + .bus_target_corner = (max_c), \ + .core_min_corner = (min_c), \ + .core_max_corner = (max_c), \ + .core_target_corner = (max_c), \ + .sleep_disable = TRUE, \ + .hmx_min_corner = (hmx_c), \ + .hmx_max_corner = (hmx_c), \ + .hmx_target_corner = (hmx_c), \ + .hmx_perf_mode = HAP_CLK_PERF_HIGH, \ + } + +static const struct htp_power_profile htp_power_profiles[HTP_POWER_MODE_COUNT] = { + [HTP_POWER_MODE_LOW_POWER_SAVER] = + HTP_POWER_PROFILE_ENTRY(HAP_DCVS_VCORNER_SVS2, HAP_DCVS_VCORNER_SVS2, HAP_DCVS_EXP_VCORNER_MIN_SVS), + [HTP_POWER_MODE_POWER_SAVER] = + HTP_POWER_PROFILE_ENTRY(HAP_DCVS_VCORNER_SVS, HAP_DCVS_VCORNER_SVS_PLUS, HAP_DCVS_EXP_VCORNER_SVS), + [HTP_POWER_MODE_HIGH_POWER_SAVER] = + HTP_POWER_PROFILE_ENTRY(HAP_DCVS_VCORNER_SVS_PLUS, HAP_DCVS_VCORNER_NOM, HAP_DCVS_EXP_VCORNER_SVS_L2), + [HTP_POWER_MODE_LOW_BALANCED] = + HTP_POWER_PROFILE_ENTRY(HAP_DCVS_VCORNER_NOM, HAP_DCVS_VCORNER_NOM_PLUS, HAP_DCVS_EXP_VCORNER_NOM), + [HTP_POWER_MODE_BALANCED] = + HTP_POWER_PROFILE_ENTRY(HAP_DCVS_VCORNER_NOM_PLUS, HAP_DCVS_VCORNER_TURBO, HAP_DCVS_EXP_VCORNER_NOM_L1), + [HTP_POWER_MODE_HIGH_PERFORMANCE] = + HTP_POWER_PROFILE_ENTRY(HAP_DCVS_VCORNER_TURBO, HAP_DCVS_VCORNER_TURBO_PLUS, HAP_DCVS_EXP_VCORNER_TUR_L2), + [HTP_POWER_MODE_SUSTAINED_HIGH_PERFORMANCE] = + HTP_POWER_PROFILE_ENTRY(HAP_DCVS_VCORNER_NOM_PLUS, HAP_DCVS_VCORNER_TURBO_PLUS, HAP_DCVS_EXP_VCORNER_TUR_L2), + [HTP_POWER_MODE_BURST] = { + .dcvs_enable = FALSE, + .bus_min_corner = HAP_DCVS_VCORNER_MAX, + .bus_max_corner = HAP_DCVS_VCORNER_MAX, + .bus_target_corner = HAP_DCVS_VCORNER_MAX, + .core_min_corner = HAP_DCVS_VCORNER_MAX, + .core_max_corner = HAP_DCVS_VCORNER_MAX, + .core_target_corner = HAP_DCVS_VCORNER_MAX, + .sleep_disable = TRUE, + .hmx_min_corner = HAP_DCVS_EXP_VCORNER_MAX, + .hmx_max_corner = HAP_DCVS_EXP_VCORNER_MAX, + .hmx_target_corner = HAP_DCVS_EXP_VCORNER_MAX, + .hmx_perf_mode = HAP_CLK_PERF_HIGH, + }, +}; + +#undef HTP_POWER_PROFILE_ENTRY + +AEEResult htp_iface_start(remote_handle64 handle, uint32_t sess_id, uint64_t dsp_queue_id, uint32_t n_hvx, uint32_t n_hmx, uint64_t max_vmem, uint32_t power_mode) { struct htp_handle * h = (struct htp_handle *) handle; if (!h) { return AEE_EBADPARM; } + if (power_mode >= HTP_POWER_MODE_COUNT) { + FARF(ERROR, "invalid power-mode %u, falling back to burst", (unsigned) power_mode); + power_mode = HTP_POWER_MODE_BURST; + } + const struct htp_power_profile * pwr = &htp_power_profiles[power_mode]; + FARF(ALWAYS, "power-mode %u : dcvs-enable %d bus[%u,%u,%u] core[%u,%u,%u] sleep-disable %d hmx[%u,%u,%u]", + (unsigned) power_mode, (int) pwr->dcvs_enable, + (unsigned) pwr->bus_min_corner, (unsigned) pwr->bus_max_corner, (unsigned) pwr->bus_target_corner, + (unsigned) pwr->core_min_corner, (unsigned) pwr->core_max_corner, (unsigned) pwr->core_target_corner, + (int) pwr->sleep_disable, + (unsigned) pwr->hmx_min_corner, (unsigned) pwr->hmx_max_corner, (unsigned) pwr->hmx_target_corner); + if (h->ctx) { FARF(ERROR, "Queue already open"); return AEE_EITEMBUSY; @@ -499,17 +578,17 @@ AEEResult htp_iface_start(remote_handle64 handle, uint32_t sess_id, uint64_t dsp request.type = HAP_power_set_DCVS_v3; request.dcvs_v3.set_dcvs_enable = TRUE; - request.dcvs_v3.dcvs_enable = FALSE; + request.dcvs_v3.dcvs_enable = pwr->dcvs_enable; request.dcvs_v3.set_bus_params = TRUE; - request.dcvs_v3.bus_params.min_corner = HAP_DCVS_VCORNER_MAX; - request.dcvs_v3.bus_params.max_corner = HAP_DCVS_VCORNER_MAX; - request.dcvs_v3.bus_params.target_corner = HAP_DCVS_VCORNER_MAX; + request.dcvs_v3.bus_params.min_corner = pwr->bus_min_corner; + request.dcvs_v3.bus_params.max_corner = pwr->bus_max_corner; + request.dcvs_v3.bus_params.target_corner = pwr->bus_target_corner; request.dcvs_v3.set_core_params = TRUE; - request.dcvs_v3.core_params.min_corner = HAP_DCVS_VCORNER_MAX; - request.dcvs_v3.core_params.max_corner = HAP_DCVS_VCORNER_MAX; - request.dcvs_v3.core_params.target_corner = HAP_DCVS_VCORNER_MAX; + request.dcvs_v3.core_params.min_corner = pwr->core_min_corner; + request.dcvs_v3.core_params.max_corner = pwr->core_max_corner; + request.dcvs_v3.core_params.target_corner = pwr->core_target_corner; request.dcvs_v3.set_sleep_disable = TRUE; - request.dcvs_v3.sleep_disable = TRUE; + request.dcvs_v3.sleep_disable = pwr->sleep_disable; #if (__HEXAGON_ARCH__ >= 79) HAP_set_dcvs_v3_protected_bus_corners(&request, 1); @@ -537,10 +616,10 @@ AEEResult htp_iface_start(remote_handle64 handle, uint32_t sess_id, uint64_t dsp request.hmx_v2.set_power = TRUE; request.hmx_v2.power_up = TRUE; request.hmx_v2.set_clock = TRUE; - request.hmx_v2.target_corner = HAP_DCVS_EXP_VCORNER_MAX; - request.hmx_v2.min_corner = HAP_DCVS_EXP_VCORNER_MAX; - request.hmx_v2.max_corner = HAP_DCVS_EXP_VCORNER_MAX; - request.hmx_v2.perf_mode = HAP_CLK_PERF_HIGH; + request.hmx_v2.target_corner = pwr->hmx_target_corner; + request.hmx_v2.min_corner = pwr->hmx_min_corner; + request.hmx_v2.max_corner = pwr->hmx_max_corner; + request.hmx_v2.perf_mode = pwr->hmx_perf_mode; FARF(ALWAYS, "Setting HMX clock\n"); err = HAP_power_set((void *) ctx, &request); if (err != AEE_SUCCESS) { diff --git a/scripts/snapdragon/run.py b/scripts/snapdragon/run.py index 382879f7b..dec4b996f 100755 --- a/scripts/snapdragon/run.py +++ b/scripts/snapdragon/run.py @@ -159,6 +159,7 @@ def main(): parser.add_argument("--hex-profile", help="Enable NPU/Hexagon profiling and performance metrics print (GGML_HEXAGON_PROFILE)") parser.add_argument("--hex-nhvx", help="Number of HVX units to use (GGML_HEXAGON_NHVX)") parser.add_argument("--hex-nhmx", help="Number of HMX units to use. 0 disables HMX power-up (GGML_HEXAGON_NHMX)") + parser.add_argument("--hex-power-mode", help="HTP DCVS/HMX power mode: low_power_saver, power_saver, high_power_saver, low_balanced, balanced, high_performance, sustained_high_performance, burst (GGML_HEXAGON_POWER_MODE)") parser.add_argument("--hex-hostbuf", help="Enable host buffers (GGML_HEXAGON_HOSTBUF)") parser.add_argument("--hex-dma64", nargs="?", const="1", help="Enable (1) or disable (0) 64-bit DMA for model weights (GGML_HEXAGON_DMA64)") parser.add_argument("--hex-opbatch", help="Maximum number of operations to batch into a single HTP execution (GGML_HEXAGON_OPBATCH)") @@ -302,6 +303,7 @@ def main(): # Other Hexagon environment variables set_env("GGML_HEXAGON_NHVX", args.hex_nhvx) set_env("GGML_HEXAGON_NHMX", args.hex_nhmx) + set_env("GGML_HEXAGON_POWER_MODE", args.hex_power_mode) set_env("GGML_HEXAGON_HOSTBUF", args.hex_hostbuf) set_env("GGML_HEXAGON_DMA64", args.hex_dma64) set_env("GGML_HEXAGON_OPBATCH", args.hex_opbatch)