hexagon: support for multi-NPU devices (IQ9, IQ10) and fully asynchronous backend (#26501)
* hexagon: use non-host bufs by default and make the backend fully async * hex-hb: remove optional hostbuf support and fix async copy * hex-unary: relax supported unary check * hex-bufs: use same get_alignment for host bufs * snapdragon: bump android_platform to 34 * hex-rows: super hacky get/set rows for q8_0 * hex-get-rows: fix q8_0 * hex-get-rows: supprot for f16 and cleanup for q8_0 * hex-get-rows: generic macros and specialized thread funcs * hex-get-rows: add DMA pipeline, vtcm_layout and kernel params * hex-set-rows: fix q8_0 support, add dma and tracing * hex-tests: override nmse threshold for HTP of Q8_0 quants * hex-fa: add support for Q8_0 with inplace dequantizers * hex-get-rows: simplify type dispatch * hex-rows: simplify GET/SET_ROWS DMA pipeline * hex-async: add events, set/get-tensor-async and rest of the async api support * hex-repack: use slice instead of expert in repack functions * hex-cpy: update event/async-cpy logging * hex-set-rows: optimize smaller tensors * hex-geglu: fix perf regression with larger tensors * hex-get-rows: add missing header * hex-set-rows: add missing header * hex-bufs: ressurect GGML_HEXAGON_HOSTBUF but disable it by default * hexagon: do not reject ops with non-heaxon buffers * hex-get-rows: apply >=32 restriction only for q8_0 * hex-res: bump vtcm acquire timeout to 10 seconds * hex-bufs: add support for cloning buffers between sessions to speed up tensor copies * hex-async: rework event recording and batch flushing and integrate with meta backend * hex-bufs: improved handling of repacked tensors * hex-repack: handle get_tensor_2d offsets * hex-dev: add support for devices with multiple NPUs * hex-sync: add support for sync tokens to synchronize npu devices for async splits * hex-mmap: cleanup mmap calls and add a retry for robustness * hex-sync: add failsafe if sync wait gets stuck * hex-sync: use sync_seq to check for completed events * hex-sync: rotate tokens for extra robustness * hex-devs: add supprot for legacy device names for now * hex-bufs: add support for auto-cloning buffers from diff sessions * hex-fusion: simplify and optimize htp-opnode fusion handling * hex-sync: override opnode name so that it shows up in the profiles * hex-trace: update scripts to handle multiple devices * hex-sync: bump the size of the opbatch queue and number of sync tokens * hex-cpy-sync: do not explicitly flush opbatches in cpy_tensor_async and add support for cpy-dma * hex-sync: add graph-flush threshold to avoid single op batches * hex-sync: add sync_peer so that we can flush peers we depend on during cross-device ops * hex-bufs: introduce tensor->extra and shadow_bufs for repacking * hex-l2: flush tiny tensors inline * hex-sync: use explicit l2flush for sync tokens * hex-extra: track weight flags via tensor extra * hex-fence: rename sync to fence * hex-repack: proper handling of set-tensor-2d in the shadow_buf * hex-trace: remove obsolete opstage mask that we used for profiling * hex-env: remove obsolete use_hmx variable * hexagon: new unified run.py and build.py and updated docs * snapdragon: update run script to auto-escapt test-backend-op -p argument * hex-scripts: fix trailing spaces * hex-scripts: fix flake8 warnings * snapdragon: cleanup dst lib/bin dirs before copying new build * hex-ops: add support for allreduce * hex-ar: improved allreduce with dma pipeline * hex-ar: align macros * hex-ar: consistent use of fence_seq * hex-ar: add AR_SELECT env var to select ALLREDUCE kernel or fallback * hex-ar: add proper synchronize handling for ALLREDUCE * hex-opbatch: looks like we now just rely on backend.synchronise to flush the batches, no need to flush them by threshold * hex-ar: bump block size to improve dma efficiency * hex-ar: fused ALLREDUCE+ADD * hex-ar: cleaner fence buffer management * hex-ar: futher allreduce tweaking to remove race conditions * hex-ar: add simple solver and remove non-dma kernels * hex-ar: add row-broadcast to fuse with bias ADD * hex-fence: pass seq numbers via op_params * hex-ar: allow for both entry/exit seq for completing entry wait * hex-ar: align macros * hex-ar: do not refetch broadcast row * hex-fusion: move all fusion into opbatch::add_op for consistency with ALLREDUCE and things * hex-fusion: fix incorrect MUL_MAT reordering * hex-mm: make fused 2x and 3x matmuls more generic * hex-fusion: move tensor fusion tagging to graph_compute * hexagon: make sure to copy tensor->extra by value * hex-get-rows: fix offset calc with row-chunking * hex-repack: get_tensor_2d fixes for non-zero offsets * snapdragon: make profile/trace scripts more robust and donot mix stdout/stderr by default * hex-devices: use legacy device nameing by default to ease the transition * hex-devices: hardcode CDSP domain IDs for current devices for now * hex-optrace: improve multi-NPU timestamp alignment and overall handling of cycle values * hex-optrace: more robust handling of the fence events
This commit is contained in:
@@ -1,49 +0,0 @@
|
||||
#!/bin/sh
|
||||
#
|
||||
|
||||
# Basedir on device
|
||||
basedir=/data/local/tmp/llama.cpp
|
||||
|
||||
branch=.
|
||||
[ "$B" != "" ] && branch=$B
|
||||
|
||||
adbserial=
|
||||
[ "$S" != "" ] && adbserial="-s $S"
|
||||
|
||||
adbhost=
|
||||
[ "$H" != "" ] && adbhost="-H $H"
|
||||
|
||||
model="Llama-3.2-3B-Instruct-Q4_0.gguf"
|
||||
[ "$M" != "" ] && model="$M"
|
||||
|
||||
device="HTP0"
|
||||
[ "$D" != "" ] && device="$D"
|
||||
|
||||
verbose=
|
||||
[ "$V" != "" ] && verbose="GGML_HEXAGON_VERBOSE=$V" cli_opts="$cli_opts -v"
|
||||
|
||||
profile=
|
||||
[ "$PROF" != "" ] && profile="GGML_HEXAGON_PROFILE=$PROF" cli_opts="$cli_opts -v"
|
||||
|
||||
opmask=
|
||||
[ "$OPSTAGE" != "" ] && opmask="GGML_HEXAGON_OPSTAGE=$OPSTAGE"
|
||||
|
||||
nhvx=
|
||||
[ "$NHVX" != "" ] && nhvx="GGML_HEXAGON_NHVX=$NHVX"
|
||||
|
||||
ndev=
|
||||
[ "$NDEV" != "" ] && ndev="GGML_HEXAGON_NDEV=$NDEV"
|
||||
|
||||
hb=
|
||||
[ "$HB" != "" ] && hb="GGML_HEXAGON_HOSTBUF=$HB"
|
||||
|
||||
set -x
|
||||
|
||||
adb $adbserial $adbhost shell " \
|
||||
cd $basedir; \
|
||||
LD_LIBRARY_PATH=$basedir/$branch/lib \
|
||||
ADSP_LIBRARY_PATH=$basedir/$branch/lib \
|
||||
$ndev $nhvx $opmask $verbose $profile $hb ./$branch/bin/llama-bench --device $device --load-mode none -m $basedir/../gguf/$model \
|
||||
--poll 1000 -t 6 --cpu-mask 0xfc --cpu-strict 1 \
|
||||
--ubatch-size 1024 -fa 1 -ngl 99 $cli_opts $@ \
|
||||
"
|
||||
@@ -1,78 +0,0 @@
|
||||
#!/bin/sh
|
||||
#
|
||||
|
||||
# Basedir on device
|
||||
basedir=/data/local/tmp/llama.cpp
|
||||
|
||||
cli_opts=
|
||||
|
||||
branch=.
|
||||
[ "$B" != "" ] && branch=$B
|
||||
|
||||
adbserial=
|
||||
[ "$S" != "" ] && adbserial="-s $S"
|
||||
|
||||
adbhost=
|
||||
[ "$H" != "" ] && adbhost="-H $H"
|
||||
|
||||
model="Llama-3.2-3B-Instruct-Q4_0.gguf"
|
||||
[ "$M" != "" ] && model="$M"
|
||||
|
||||
device="HTP0"
|
||||
[ "$D" != "" ] && device="$D"
|
||||
|
||||
verbose=
|
||||
[ "$V" != "" ] && verbose="GGML_HEXAGON_VERBOSE=$V" cli_opts="$cli_opts -v"
|
||||
|
||||
sched=
|
||||
[ "$SCHED" != "" ] && sched="GGML_SCHED_DEBUG=2" cli_opts="$cli_opts -v"
|
||||
|
||||
profile=
|
||||
[ "$PROF" != "" ] && profile="GGML_HEXAGON_PROFILE=$PROF" cli_opts="$cli_opts -v"
|
||||
|
||||
opmask=
|
||||
[ "$OPSTAGE" != "" ] && opmask="GGML_HEXAGON_OPSTAGE=$OPSTAGE"
|
||||
|
||||
nhvx=
|
||||
[ "$NHVX" != "" ] && nhvx="GGML_HEXAGON_NHVX=$NHVX"
|
||||
|
||||
hmx=
|
||||
[ "$HMX" != "" ] && hmx="GGML_HEXAGON_USE_HMX=$HMX"
|
||||
|
||||
ndev=
|
||||
[ "$NDEV" != "" ] && ndev="GGML_HEXAGON_NDEV=$NDEV"
|
||||
|
||||
hb=
|
||||
[ "$HB" != "" ] && hb="GGML_HEXAGON_HOSTBUF=$HB"
|
||||
|
||||
opbatch=
|
||||
[ "$OB" != "" ] && opbatch="GGML_HEXAGON_OPBATCH=$OB"
|
||||
|
||||
opqueue=
|
||||
[ "$OQ" != "" ] && opqueue="GGML_HEXAGON_OPQUEUE=$OQ"
|
||||
|
||||
opflt=
|
||||
[ "$OF" != "" ] && opflt="GGML_HEXAGON_OPFILTER=$OF"
|
||||
|
||||
vmem=
|
||||
[ "$VM" != "" ] && opflt="GGML_HEXAGON_VMEM=$VM"
|
||||
|
||||
mbuf=
|
||||
[ "$MB" != "" ] && opflt="GGML_HEXAGON_MBUF=$MB"
|
||||
vmem=
|
||||
[ "$VM" != "" ] && vmem="GGML_HEXAGON_VMEM=$VM"
|
||||
|
||||
mbuf=
|
||||
[ "$MB" != "" ] && mbuf="GGML_HEXAGON_MBUF=$MB"
|
||||
set -x
|
||||
|
||||
adb $adbserial $adbhost shell " \
|
||||
cd $basedir; ulimit -c unlimited; \
|
||||
LD_LIBRARY_PATH=$basedir/$branch/lib \
|
||||
ADSP_LIBRARY_PATH=$basedir/$branch/lib \
|
||||
$verbose $sched $opmask $profile $nhvx $hmx $ndev $hb $opbatch $opqueue $opflt $vmem $mbuf \
|
||||
./$branch/bin/llama-cli --load-mode none -m $basedir/../gguf/$model \
|
||||
--poll 1000 -t 6 --cpu-mask 0xfc --cpu-strict 1 \
|
||||
--ctx-size 8192 --ubatch-size 1024 -fa on \
|
||||
-ngl 99 --device $device $cli_opts $@ \
|
||||
"
|
||||
@@ -1,86 +0,0 @@
|
||||
#!/bin/sh
|
||||
#
|
||||
|
||||
# Basedir on device
|
||||
basedir=/data/local/tmp/llama.cpp
|
||||
|
||||
cli_opts=
|
||||
|
||||
branch=.
|
||||
[ "$B" != "" ] && branch=$B
|
||||
|
||||
adbserial=
|
||||
[ "$S" != "" ] && adbserial="-s $S"
|
||||
|
||||
adbhost=
|
||||
[ "$H" != "" ] && adbhost="-H $H"
|
||||
|
||||
model="Llama-3.2-3B-Instruct-Q4_0.gguf"
|
||||
[ "$M" != "" ] && model="$M"
|
||||
|
||||
device="HTP0"
|
||||
[ "$D" != "" ] && device="$D"
|
||||
|
||||
verbose=
|
||||
[ "$V" != "" ] && verbose="GGML_HEXAGON_VERBOSE=$V" cli_opts="$cli_opts -v"
|
||||
|
||||
sched=
|
||||
[ "$SCHED" != "" ] && sched="GGML_SCHED_DEBUG=2" cli_opts="$cli_opts -v"
|
||||
|
||||
profile=
|
||||
[ "$PROF" != "" ] && profile="GGML_HEXAGON_PROFILE=$PROF" cli_opts="$cli_opts -v"
|
||||
|
||||
opmask=
|
||||
[ "$OPSTAGE" != "" ] && opmask="GGML_HEXAGON_OPSTAGE=$OPSTAGE"
|
||||
|
||||
nhvx=
|
||||
[ "$NHVX" != "" ] && nhvx="GGML_HEXAGON_NHVX=$NHVX"
|
||||
|
||||
hmx=
|
||||
[ "$HMX" != "" ] && hmx="GGML_HEXAGON_USE_HMX=$HMX"
|
||||
|
||||
ndev=
|
||||
[ "$NDEV" != "" ] && ndev="GGML_HEXAGON_NDEV=$NDEV"
|
||||
|
||||
hb=
|
||||
[ "$HB" != "" ] && hb="GGML_HEXAGON_HOSTBUF=$HB"
|
||||
|
||||
opbatch=
|
||||
[ "$OB" != "" ] && opbatch="GGML_HEXAGON_OPBATCH=$OB"
|
||||
|
||||
opqueue=
|
||||
[ "$OQ" != "" ] && opqueue="GGML_HEXAGON_OPQUEUE=$OQ"
|
||||
|
||||
oppoll=
|
||||
[ "$OP" != "" ] && oppoll="GGML_HEXAGON_OPPOLL=$OP"
|
||||
|
||||
opflt=
|
||||
[ "$OF" != "" ] && opflt="GGML_HEXAGON_OPFILTER=$OF"
|
||||
|
||||
opfuse=
|
||||
[ "$OC" != "" ] && opfuse="GGML_HEXAGON_OPFUSION=$OC"
|
||||
|
||||
vmem=
|
||||
[ "$VM" != "" ] && vmem="GGML_HEXAGON_VMEM=$VM"
|
||||
|
||||
mbuf=
|
||||
[ "$MB" != "" ] && mbuf="GGML_HEXAGON_MBUF=$MB"
|
||||
|
||||
mmsel=
|
||||
[ "$MM" != "" ] && mmsel="GGML_HEXAGON_MM_SELECT=$MM"
|
||||
|
||||
fasel=
|
||||
[ "$FA" != "" ] && fasel="GGML_HEXAGON_FA_SELECT=$FA"
|
||||
|
||||
set -x
|
||||
|
||||
adb $adbserial $adbhost shell " \
|
||||
cd $basedir; ulimit -c unlimited; \
|
||||
LD_LIBRARY_PATH=$basedir/$branch/lib \
|
||||
ADSP_LIBRARY_PATH=$basedir/$branch/lib \
|
||||
$verbose $sched $opmask $profile $nhvx $hmx $ndev $hb $opbatch $opqueue $oppoll $opflt $opfuse $vmem $mbuf $mmsel $fasel \
|
||||
./$branch/bin/llama-completion --load-mode none -m $basedir/../gguf/$model \
|
||||
--poll 1000 -t 6 --cpu-mask 0xfc --cpu-strict 1 \
|
||||
--ctx-size 8192 --ubatch-size 1024 -fa on \
|
||||
-ngl 99 --device $device $cli_opts $@ \
|
||||
"
|
||||
@@ -1,71 +0,0 @@
|
||||
#!/bin/sh
|
||||
#
|
||||
|
||||
# Basedir on device
|
||||
basedir=/data/local/tmp/llama.cpp
|
||||
|
||||
cli_opts=
|
||||
|
||||
branch=.
|
||||
[ "$B" != "" ] && branch=$B
|
||||
|
||||
adbserial=
|
||||
[ "$S" != "" ] && adbserial="-s $S"
|
||||
|
||||
adbhost=
|
||||
[ "$H" != "" ] && adbhost="-H $H"
|
||||
|
||||
model="gemma-3-4b-it-Q4_0.gguf"
|
||||
[ "$M" != "" ] && model="$M"
|
||||
|
||||
mmproj="mmproj-F16.gguf"
|
||||
[ "$MMPROJ" != "" ] && mmproj="$MMPROJ"
|
||||
|
||||
image=
|
||||
[ "$IMG" != "" ] && image="$IMG"
|
||||
|
||||
device="HTP0"
|
||||
[ "$D" != "" ] && device="$D"
|
||||
|
||||
verbose=
|
||||
[ "$V" != "" ] && verbose="GGML_HEXAGON_VERBOSE=$V"
|
||||
|
||||
experimental="GGML_HEXAGON_EXPERIMENTAL=1"
|
||||
[ "$E" != "" ] && experimental="GGML_HEXAGON_EXPERIMENTAL=$E"
|
||||
|
||||
sched=
|
||||
[ "$SCHED" != "" ] && sched="GGML_SCHED_DEBUG=2" cli_opts="$cli_opts -v"
|
||||
|
||||
profile=
|
||||
[ "$PROF" != "" ] && profile="GGML_HEXAGON_PROFILE=$PROF"
|
||||
|
||||
opmask=
|
||||
[ "$OPSTAGE" != "" ] && opmask="GGML_HEXAGON_OPSTAGE=$OPSTAGE"
|
||||
|
||||
nhvx=
|
||||
[ "$NHVX" != "" ] && nhvx="GGML_HEXAGON_NHVX=$NHVX"
|
||||
|
||||
hmx=
|
||||
[ "$HMX" != "" ] && hmx="GGML_HEXAGON_USE_HMX=$HMX"
|
||||
|
||||
ndev=
|
||||
[ "$NDEV" != "" ] && ndev="GGML_HEXAGON_NDEV=$NDEV"
|
||||
|
||||
# MTMD backend device for vision model (defaults to CPU if not set)
|
||||
mtmd_backend=
|
||||
[ "$MTMD_DEVICE" != "" ] && mtmd_backend="MTMD_BACKEND_DEVICE=$MTMD_DEVICE"
|
||||
|
||||
set -x
|
||||
|
||||
adb $adbserial $adbhost shell " \
|
||||
cd $basedir; ulimit -c unlimited; \
|
||||
LD_LIBRARY_PATH=$basedir/$branch/lib \
|
||||
ADSP_LIBRARY_PATH=$basedir/$branch/lib \
|
||||
$verbose $experimental $sched $opmask $profile $hmx $nhvx $ndev $mtmd_backend \
|
||||
./$branch/bin/llama-mtmd-cli --load-mode none -m $basedir/../gguf/$model \
|
||||
--mmproj $basedir/../gguf/$mmproj \
|
||||
--image $basedir/../gguf/$image \
|
||||
--poll 1000 -t 6 --cpu-mask 0xfc --cpu-strict 1 \
|
||||
--ctx-size 8192 --ubatch-size 1024 -fa on \
|
||||
-ngl 99 --device $device -v $cli_opts $@ \
|
||||
"
|
||||
@@ -1,72 +0,0 @@
|
||||
#!/bin/sh
|
||||
#
|
||||
|
||||
# Basedir on device
|
||||
basedir=/data/local/tmp/llama.cpp
|
||||
|
||||
cli_opts=
|
||||
|
||||
branch=.
|
||||
[ "$B" != "" ] && branch=$B
|
||||
|
||||
adbserial=
|
||||
[ "$S" != "" ] && adbserial="-s $S"
|
||||
|
||||
adbhost=
|
||||
[ "$H" != "" ] && adbhost="-H $H"
|
||||
|
||||
device="HTP0"
|
||||
[ "$D" != "" ] && device="$D"
|
||||
|
||||
verbose=
|
||||
[ "$V" != "" ] && verbose="GGML_HEXAGON_VERBOSE=$V"
|
||||
|
||||
sched=
|
||||
[ "$SCHED" != "" ] && sched="GGML_SCHED_DEBUG=2" cli_opts="$cli_opts -v"
|
||||
|
||||
profile=
|
||||
[ "$PROF" != "" ] && profile="GGML_HEXAGON_PROFILE=$PROF"
|
||||
|
||||
opmask=
|
||||
[ "$OPSTAGE" != "" ] && opmask="GGML_HEXAGON_OPSTAGE=$OPSTAGE"
|
||||
|
||||
nhvx=
|
||||
[ "$NHVX" != "" ] && nhvx="GGML_HEXAGON_NHVX=$NHVX"
|
||||
|
||||
hmx=
|
||||
[ "$HMX" != "" ] && hmx="GGML_HEXAGON_USE_HMX=$HMX"
|
||||
|
||||
ndev=
|
||||
[ "$NDEV" != "" ] && ndev="GGML_HEXAGON_NDEV=$NDEV"
|
||||
|
||||
hb=
|
||||
[ "$HB" != "" ] && hb="GGML_HEXAGON_HOSTBUF=$HB"
|
||||
|
||||
opbatch=
|
||||
[ "$OB" != "" ] && opbatch="GGML_HEXAGON_OPBATCH=$OB"
|
||||
|
||||
opqueue=
|
||||
[ "$OQ" != "" ] && opqueue="GGML_HEXAGON_OPQUEUE=$OQ"
|
||||
|
||||
oppoll=
|
||||
[ "$OP" != "" ] && oppoll="GGML_HEXAGON_OPPOLL=$OP"
|
||||
|
||||
opfuse=
|
||||
[ "$OC" != "" ] && opfuse="GGML_HEXAGON_OPFUSION=$OC"
|
||||
|
||||
mmsel=
|
||||
[ "$MM" != "" ] && mmsel="GGML_HEXAGON_MM_SELECT=$MM"
|
||||
|
||||
fasel=
|
||||
[ "$FA" != "" ] && fasel="GGML_HEXAGON_FA_SELECT=$FA"
|
||||
|
||||
set -x
|
||||
|
||||
tool=$1; shift
|
||||
|
||||
adb $adbserial $adbhost shell " \
|
||||
cd $basedir; ulimit -c unlimited; \
|
||||
LD_LIBRARY_PATH=$basedir/$branch/lib \
|
||||
ADSP_LIBRARY_PATH=$basedir/$branch/lib \
|
||||
$verbose $sched $opmask $profile $nhvx $hmx $ndev $hb $opbatch $opqueue $oppoll $opfuse $mmsel $fasel ./$branch/bin/$tool $@ \
|
||||
"
|
||||
Executable
+260
@@ -0,0 +1,260 @@
|
||||
#!/usr/bin/env python3
|
||||
#
|
||||
# Build llama.cpp for Snapdragon (via Docker or natively) and push to device.
|
||||
#
|
||||
|
||||
import sys
|
||||
import os
|
||||
import argparse
|
||||
import subprocess
|
||||
import platform
|
||||
import shutil
|
||||
import logging
|
||||
|
||||
logger = logging.getLogger("build")
|
||||
|
||||
|
||||
def parse_target(target_str):
|
||||
if not target_str:
|
||||
return None, None
|
||||
if target_str.startswith("adb") or target_str.startswith("android"):
|
||||
parts = target_str.split(":", 1)
|
||||
serial = parts[1] if len(parts) > 1 else None
|
||||
return "android", serial
|
||||
elif target_str.startswith("lnx") or target_str.startswith("linux") or target_str.startswith("ubuntu"):
|
||||
parts = target_str.split(":", 1)
|
||||
host = parts[1] if len(parts) > 1 else None
|
||||
return "linux", host
|
||||
elif target_str in ("wos", "windows"):
|
||||
return "windows", None
|
||||
else:
|
||||
return None, None
|
||||
|
||||
|
||||
def get_uid_gid():
|
||||
if platform.system() != "Windows":
|
||||
return [f"{os.getuid()}:{os.getgid()}"]
|
||||
return []
|
||||
|
||||
|
||||
def main():
|
||||
logging.basicConfig(level=logging.INFO, format='%(message)s')
|
||||
parser = argparse.ArgumentParser(
|
||||
description="Build llama.cpp for Snapdragon using cross-compilation docker containers or natively."
|
||||
)
|
||||
parser.add_argument("--target", default="android", help="Compilation target and deployment definition (e.g. android[:serial]/adb[:serial], linux:[user@]host/lnx:[user@]host/ubuntu:[user@]host, windows/wos) (default: android)")
|
||||
parser.add_argument("--build-dir", help="Build directory name (defaults to build-TARGET[-dbg], e.g. build-android)")
|
||||
parser.add_argument("--install-dir", help="Install directory name (defaults to pkg-TARGET[-dbg], e.g. pkg-android)")
|
||||
parser.add_argument("--jobs", "-j", type=int, help="Number of build jobs (defaults to CPU thread count)")
|
||||
parser.add_argument("--no-docker", action="store_true", help="Build natively on the host instead of in a docker container")
|
||||
parser.add_argument("--preset", help="Override the CMake preset to use")
|
||||
parser.add_argument("--debug", action="store_true", help="Build in debug mode (uses -debug presets instead of -release)")
|
||||
|
||||
# Push options
|
||||
parser.add_argument("--push", action="store_true", help="Push built package to the target device via ADB or SSH/SCP")
|
||||
parser.add_argument("--target-dir", help="Target directory on the device (default: /data/local/tmp/llama.cpp for Android, ~/llama.cpp for Linux)")
|
||||
|
||||
# Toolchain options
|
||||
parser.add_argument("--toolchain-version", default="v0.7", help="Docker toolchain image version/tag (default: v0.7)")
|
||||
parser.add_argument("--toolchain-url", default="ghcr.io/snapdragon-toolchain", help="Docker toolchain registry URL/namespace (default: ghcr.io/snapdragon-toolchain)")
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
target_type, target_val = parse_target(args.target)
|
||||
if not target_type:
|
||||
logger.error(f"Error: Invalid target format '{args.target}'. Must be android[:serial]/adb[:serial], linux:[user@]host/lnx:[user@]host/ubuntu:[user@]host, or windows/wos.")
|
||||
sys.exit(1)
|
||||
|
||||
# Determine preset and check if it's debug
|
||||
preset = args.preset
|
||||
if preset:
|
||||
is_debug = args.debug or ("debug" in preset.lower())
|
||||
else:
|
||||
is_debug = args.debug
|
||||
config_type = "debug" if is_debug else "release"
|
||||
if args.no_docker:
|
||||
if target_type == "windows" or platform.system() == "Windows":
|
||||
preset = f"arm64-windows-snapdragon-{config_type}"
|
||||
elif target_type == "linux":
|
||||
preset = f"arm64-linux-snapdragon-{config_type}"
|
||||
else:
|
||||
preset = f"arm64-android-snapdragon-{config_type}"
|
||||
else:
|
||||
preset = f"arm64-linux-snapdragon-{config_type}" if target_type == "linux" else f"arm64-android-snapdragon-{config_type}"
|
||||
|
||||
target_prefix = args.target.split(":", 1)[0]
|
||||
suffix = "-dbg" if is_debug else ""
|
||||
|
||||
build_dir = args.build_dir
|
||||
if not build_dir:
|
||||
build_dir = f"build-{target_prefix}{suffix}"
|
||||
|
||||
install_dir = args.install_dir
|
||||
if not install_dir:
|
||||
install_dir = f"pkg-{target_prefix}{suffix}"
|
||||
|
||||
repo_root = os.path.abspath(os.path.join(os.path.dirname(__file__), "..", ".."))
|
||||
|
||||
# Ensure CMakeUserPresets.json is in the workspace root, update if docs version is newer
|
||||
preset_src = os.path.join(repo_root, "docs", "backend", "snapdragon", "CMakeUserPresets.json")
|
||||
preset_dst = os.path.join(repo_root, "CMakeUserPresets.json")
|
||||
if os.path.exists(preset_src):
|
||||
should_copy = False
|
||||
if not os.path.exists(preset_dst):
|
||||
should_copy = True
|
||||
else:
|
||||
# Check modification times
|
||||
src_mtime = os.path.getmtime(preset_src)
|
||||
dst_mtime = os.path.getmtime(preset_dst)
|
||||
if src_mtime > dst_mtime:
|
||||
preset_bak = preset_dst + ".bak"
|
||||
logger.info(f"Docs CMakeUserPresets.json is newer. Backing up existing {preset_dst} to {preset_bak}")
|
||||
shutil.copy2(preset_dst, preset_bak)
|
||||
should_copy = True
|
||||
|
||||
if should_copy:
|
||||
logger.info(f"Copying CMakeUserPresets.json from {preset_src} to {preset_dst}")
|
||||
shutil.copy2(preset_src, preset_dst)
|
||||
else:
|
||||
logger.warning("Warning: CMakeUserPresets.json not found in docs/backend/snapdragon/.")
|
||||
|
||||
jobs = args.jobs if args.jobs else os.cpu_count() or 4
|
||||
|
||||
if target_type == "windows":
|
||||
logger.info("Windows target selected. Forcing native compilation...")
|
||||
args.no_docker = True
|
||||
if platform.system() != "Windows":
|
||||
logger.warning("Warning: Windows compilation is intended to run on Windows arm64 hosts.")
|
||||
|
||||
if args.no_docker:
|
||||
# Native/local host build
|
||||
logger.info("Running native/local CMake build...")
|
||||
install_prefix = os.path.join(repo_root, install_dir, "llama.cpp")
|
||||
|
||||
# Configure
|
||||
configure_cmd = ["cmake", f"--preset={preset}", "-B", build_dir]
|
||||
logger.info(f"+ {' '.join(configure_cmd)}")
|
||||
res = subprocess.run(configure_cmd, cwd=repo_root)
|
||||
if res.returncode != 0:
|
||||
logger.error("CMake configuration failed.")
|
||||
sys.exit(res.returncode)
|
||||
|
||||
# Build
|
||||
build_cmd = ["cmake", "--build", build_dir, "-j", str(jobs)]
|
||||
logger.info(f"+ {' '.join(build_cmd)}")
|
||||
res = subprocess.run(build_cmd, cwd=repo_root)
|
||||
if res.returncode != 0:
|
||||
logger.error("CMake build failed.")
|
||||
sys.exit(res.returncode)
|
||||
|
||||
# Install
|
||||
install_cmd = ["cmake", "--install", build_dir, "--prefix", install_prefix]
|
||||
logger.info(f"+ {' '.join(install_cmd)}")
|
||||
res = subprocess.run(install_cmd, cwd=repo_root)
|
||||
if res.returncode != 0:
|
||||
logger.error("CMake install failed.")
|
||||
sys.exit(res.returncode)
|
||||
else:
|
||||
# Docker-based build
|
||||
logger.info("Running Docker-based cross-compilation build...")
|
||||
image_name = "arm64-linux" if target_type == "linux" else "arm64-android"
|
||||
image = f"{args.toolchain_url}/{image_name}:{args.toolchain_version}"
|
||||
|
||||
install_prefix_container = f"/workspace/{install_dir}/llama.cpp"
|
||||
|
||||
build_sh_cmd = (
|
||||
f"cmake --preset {preset} -B /workspace/{build_dir} && "
|
||||
f"cmake --build /workspace/{build_dir} -j {jobs} && "
|
||||
f"cmake --install /workspace/{build_dir} --prefix {install_prefix_container}"
|
||||
)
|
||||
|
||||
docker_cmd = [
|
||||
"docker", "run", "--rm",
|
||||
"--volume", f"{repo_root}:/workspace",
|
||||
"--workdir", "/workspace",
|
||||
"--platform", "linux/amd64"
|
||||
]
|
||||
uid_gid = get_uid_gid()
|
||||
if uid_gid:
|
||||
docker_cmd += ["-u", uid_gid[0]]
|
||||
|
||||
docker_cmd += [image, "bash", "-c", build_sh_cmd]
|
||||
|
||||
logger.info(f"+ {' '.join(docker_cmd)}")
|
||||
res = subprocess.run(docker_cmd, cwd=repo_root)
|
||||
if res.returncode != 0:
|
||||
logger.error("Docker-based build failed.")
|
||||
sys.exit(res.returncode)
|
||||
|
||||
logger.info("\nBuild and installation completed successfully!")
|
||||
|
||||
# Push/deploy if requested
|
||||
if args.push:
|
||||
src_path = os.path.join(repo_root, install_dir, "llama.cpp")
|
||||
if not os.path.exists(src_path):
|
||||
logger.error(f"Error: installation directory {src_path} does not exist. Cannot deploy.")
|
||||
sys.exit(1)
|
||||
|
||||
# Resolve target directory on device
|
||||
target_dir = args.target_dir
|
||||
if not target_dir:
|
||||
target_dir = "/data/local/tmp/llama.cpp" if target_type == "android" else "~/llama.cpp"
|
||||
target_dir = target_dir.rstrip("/")
|
||||
|
||||
sub_items = [item for item in os.listdir(src_path) if not item.startswith(".")]
|
||||
|
||||
if target_type == "android":
|
||||
logger.info("\nPushing built artifacts to Android device via ADB...")
|
||||
adb_cmd = ["adb"]
|
||||
if target_val: # serial
|
||||
adb_cmd += ["-s", target_val]
|
||||
|
||||
# Clean stale package files on device
|
||||
if sub_items:
|
||||
clean_paths = " ".join(f"{target_dir}/{item}" for item in sub_items)
|
||||
clean_cmd = adb_cmd + ["shell", f"rm -rf {clean_paths}"]
|
||||
logger.info(f"+ {' '.join(clean_cmd)}")
|
||||
subprocess.run(clean_cmd)
|
||||
|
||||
# Android destination directory is target_dir
|
||||
push_cmd = adb_cmd + ["push", os.path.join(src_path, "."), target_dir]
|
||||
logger.info(f"+ {' '.join(push_cmd)}")
|
||||
res = subprocess.run(push_cmd)
|
||||
if res.returncode != 0:
|
||||
logger.error("ADB push failed.")
|
||||
sys.exit(res.returncode)
|
||||
logger.info("ADB push completed successfully!")
|
||||
|
||||
elif target_type == "linux":
|
||||
ssh_host = target_val
|
||||
if not ssh_host:
|
||||
logger.error("Error: SSH host not specified in target (e.g. use linux:user@host, lnx:user@host, or ubuntu:user@host). Cannot deploy.")
|
||||
sys.exit(1)
|
||||
logger.info(f"\nDeploying built artifacts to Linux device {ssh_host} via SSH/SCP...")
|
||||
|
||||
# Clean stale package files on remote host
|
||||
if sub_items:
|
||||
clean_paths = " ".join(f"{target_dir}/{item}" for item in sub_items)
|
||||
clean_cmd = ["ssh", ssh_host, f"rm -rf {clean_paths}"]
|
||||
logger.info(f"+ {' '.join(clean_cmd)}")
|
||||
subprocess.run(clean_cmd)
|
||||
|
||||
# Deploy to target_dir
|
||||
deploy_cmd = ["scp", "-r", os.path.join(src_path, "."), f"{ssh_host}:{target_dir}"]
|
||||
logger.info(f"+ {' '.join(deploy_cmd)}")
|
||||
res = subprocess.run(deploy_cmd)
|
||||
if res.returncode != 0:
|
||||
logger.error("SSH/SCP deploy failed.")
|
||||
sys.exit(res.returncode)
|
||||
logger.info("SSH/SCP deploy completed successfully!")
|
||||
|
||||
elif target_type == "windows":
|
||||
logger.info("\nPush for Windows on Snapdragon (windows) target is currently a stub.")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
try:
|
||||
main()
|
||||
except KeyboardInterrupt:
|
||||
logger.info("\nInterrupted by user.")
|
||||
sys.exit(130)
|
||||
@@ -34,6 +34,26 @@ trace_pattern = re.compile(
|
||||
r"trace-evt\s+(?P<event>[A-Z_0-9\-]+):\s+thread\s+(?P<thread>\d+)\s+info\s+(?P<info>\d+)\s+(?P<state>start|stop)\s+(?P<cycles>\d+)"
|
||||
)
|
||||
|
||||
device_pattern = re.compile(r"\b(HTP\d+(?::\d+)?)\s+(?:profile-op|trace-evt)\b")
|
||||
|
||||
|
||||
def extract_device(line):
|
||||
m = device_pattern.search(line)
|
||||
if m:
|
||||
return m.group(1)
|
||||
return "HTP0"
|
||||
|
||||
|
||||
def device_matches(record_device, target_device):
|
||||
targets = [t.strip() for t in target_device.split(',')]
|
||||
for target in targets:
|
||||
if record_device == target:
|
||||
return True
|
||||
if record_device.startswith(target + ":"):
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
logger = logging.getLogger("ggml-hexagon-profile")
|
||||
|
||||
|
||||
@@ -72,7 +92,7 @@ class CycleUnwrapper:
|
||||
return raw + self.high_part
|
||||
|
||||
|
||||
def parse_log(file_path, pmu_index=None):
|
||||
def parse_log(file_path, pmu_index=None, limit=None, device_filter=None, op_filter_re=None):
|
||||
try:
|
||||
if file_path != "-":
|
||||
f = open(file_path, 'r', encoding='utf-8', errors='ignore')
|
||||
@@ -85,13 +105,22 @@ def parse_log(file_path, pmu_index=None):
|
||||
all_ops: List[Dict[str, Any]] = []
|
||||
all_traces: List[Dict[str, Any]] = []
|
||||
current_op: Optional[Dict[str, Any]] = None
|
||||
ops_count_per_device = {}
|
||||
if device_filter is not None:
|
||||
for target in device_filter.split(','):
|
||||
ops_count_per_device[target.strip()] = 0
|
||||
limit_reached = False
|
||||
|
||||
timestamp_pattern = re.compile(r"^(?P<min>\d+)\.(?P<sec>\d+)\.(?P<ms>\d+)\.(?P<us>\d+)\s+[A-Z]\s+")
|
||||
unwrapper = None
|
||||
trace_unwrapper = None
|
||||
timestamp_pattern = re.compile(r"(?P<min>\d+)\.(?P<sec>\d+)\.(?P<ms>\d+)\.(?P<us>\d+)\s+[A-Z]\s+")
|
||||
unwrappers = {}
|
||||
last_batch_start = {}
|
||||
trace_unwrappers = {}
|
||||
|
||||
for line in f:
|
||||
ts_match = timestamp_pattern.match(line)
|
||||
if "profile-op" not in line and "trace-evt" not in line:
|
||||
continue
|
||||
|
||||
ts_match = timestamp_pattern.search(line)
|
||||
abs_usec = 0
|
||||
if ts_match:
|
||||
abs_usec = (
|
||||
@@ -100,8 +129,11 @@ def parse_log(file_path, pmu_index=None):
|
||||
+ int(ts_match.group('us'))
|
||||
)
|
||||
|
||||
if "|" in line and "profile-op" in line:
|
||||
parts = [p.strip() for p in line.split("|")]
|
||||
device = extract_device(line)
|
||||
|
||||
idx = line.find("profile-op")
|
||||
if idx != -1 and "|" in line[idx:]:
|
||||
parts = [p.strip() for p in line[idx:].split("|")]
|
||||
prefix = parts[0]
|
||||
prefix_match = re.search(r"profile-op\s+(?P<op_name>[A-Z_0-9+]+)", prefix)
|
||||
if not prefix_match:
|
||||
@@ -145,7 +177,6 @@ def parse_log(file_path, pmu_index=None):
|
||||
except (ValueError, IndexError):
|
||||
pmu_val = None
|
||||
|
||||
evt_val = None
|
||||
evt_val = None
|
||||
if types.startswith("evt-cnt "):
|
||||
try:
|
||||
@@ -158,14 +189,18 @@ def parse_log(file_path, pmu_index=None):
|
||||
if op_name == "OPBATCH":
|
||||
if cycles_start_raw:
|
||||
unwrapped_cycles_start = int(cycles_start_raw)
|
||||
unwrapper = CycleUnwrapper(unwrapped_cycles_start)
|
||||
trace_unwrapper = CycleUnwrapper(unwrapped_cycles_start)
|
||||
unwrappers[device] = CycleUnwrapper(unwrapped_cycles_start)
|
||||
last_batch_start[device] = unwrapped_cycles_start
|
||||
for k in list(trace_unwrappers.keys()):
|
||||
if k[0] == device:
|
||||
del trace_unwrappers[k]
|
||||
else:
|
||||
if cycles_start_raw and unwrapper is not None:
|
||||
unwrapped_cycles_start = unwrapper.unwrap(int(cycles_start_raw))
|
||||
if cycles_start_raw:
|
||||
device_unwrapper = unwrappers.get(device)
|
||||
if device_unwrapper is not None:
|
||||
unwrapped_cycles_start = device_unwrapper.unwrap(int(cycles_start_raw))
|
||||
|
||||
idx = line.find("profile-op ")
|
||||
op_text = line[idx + 11:].strip() if idx != -1 else line.strip()
|
||||
op_text = re.sub(r"^profile-op\s+", "", line[idx:]).strip() if idx != -1 else line.strip()
|
||||
|
||||
current_op = {
|
||||
'name': op_name,
|
||||
@@ -180,24 +215,58 @@ def parse_log(file_path, pmu_index=None):
|
||||
'pmu_val': pmu_val,
|
||||
'evt_val': evt_val,
|
||||
'abs_usec': abs_usec,
|
||||
'trace_events': []
|
||||
'trace_events': [],
|
||||
'device': device
|
||||
}
|
||||
all_ops.append(current_op)
|
||||
|
||||
# Check if matching early exit criteria
|
||||
matched = False
|
||||
matched_target = None
|
||||
if device_filter is not None:
|
||||
targets = [t.strip() for t in device_filter.split(',')]
|
||||
for target in targets:
|
||||
if device == target or device.startswith(target + ":"):
|
||||
matched = True
|
||||
matched_target = target
|
||||
break
|
||||
else:
|
||||
matched = True
|
||||
matched_target = device
|
||||
|
||||
if op_filter_re is not None and not op_filter_re.search(op_text):
|
||||
matched = False
|
||||
|
||||
if matched:
|
||||
if matched_target not in ops_count_per_device:
|
||||
ops_count_per_device[matched_target] = 0
|
||||
ops_count_per_device[matched_target] += 1
|
||||
|
||||
if limit is not None and len(ops_count_per_device) > 0 and all(count >= limit for count in ops_count_per_device.values()):
|
||||
limit_reached = True
|
||||
|
||||
if limit_reached and op_name == "OPBATCH":
|
||||
break
|
||||
continue
|
||||
|
||||
trace_match = trace_pattern.search(line)
|
||||
if trace_match:
|
||||
thread = int(trace_match.group('thread'))
|
||||
raw_cyc = int(trace_match.group('cycles'))
|
||||
unwrapped_cyc = None
|
||||
if trace_unwrapper is not None:
|
||||
unwrapped_cyc = trace_unwrapper.unwrap(raw_cyc)
|
||||
th_key = (device, thread)
|
||||
if th_key not in trace_unwrappers:
|
||||
batch_start = last_batch_start.get(device)
|
||||
trace_unwrappers[th_key] = CycleUnwrapper(batch_start)
|
||||
unwrapped_cyc = trace_unwrappers[th_key].unwrap(raw_cyc)
|
||||
all_traces.append({
|
||||
'thread': int(trace_match.group('thread')),
|
||||
'thread': thread,
|
||||
'event': trace_match.group('event'),
|
||||
'info': int(trace_match.group('info')),
|
||||
'cycles': raw_cyc,
|
||||
'unwrapped_cycles': unwrapped_cyc,
|
||||
'state': trace_match.group('state')
|
||||
'state': trace_match.group('state'),
|
||||
'device': device
|
||||
})
|
||||
|
||||
f.close()
|
||||
@@ -207,39 +276,45 @@ def parse_log(file_path, pmu_index=None):
|
||||
op['start_cycles'] = op['unwrapped_cycles_start']
|
||||
op['end_cycles'] = op['start_cycles'] + op['cycles'] if op['start_cycles'] is not None else None
|
||||
|
||||
# Filter ops with valid start_cycles
|
||||
valid_ops = [op for op in all_ops if op['start_cycles'] is not None and op['end_cycles'] is not None]
|
||||
# Group ops by device
|
||||
valid_ops_by_dev = defaultdict(list)
|
||||
for op in all_ops:
|
||||
if op['start_cycles'] is not None and op['end_cycles'] is not None:
|
||||
valid_ops_by_dev[op['device']].append(op)
|
||||
|
||||
# Separate OPBATCH ops from other ops
|
||||
opbatch_ops = [op for op in valid_ops if op['name'] == "OPBATCH"]
|
||||
other_ops = [op for op in valid_ops if op['name'] != "OPBATCH"]
|
||||
|
||||
# Sort them by start_cycles to enable binary search
|
||||
opbatch_ops.sort(key=lambda op: op['start_cycles'])
|
||||
other_ops.sort(key=lambda op: op['start_cycles'])
|
||||
|
||||
opbatch_starts = [op['start_cycles'] for op in opbatch_ops]
|
||||
other_starts = [op['start_cycles'] for op in other_ops]
|
||||
|
||||
# Map trace events to any operator whose cycles contain them
|
||||
# Group trace events by device
|
||||
traces_by_dev = defaultdict(list)
|
||||
for e in all_traces:
|
||||
cyc = e['unwrapped_cycles']
|
||||
if cyc is None:
|
||||
continue
|
||||
if e['unwrapped_cycles'] is not None:
|
||||
traces_by_dev[e['device']].append(e)
|
||||
|
||||
# Map to OPBATCH
|
||||
idx = bisect.bisect_right(opbatch_starts, cyc) - 1
|
||||
if idx >= 0:
|
||||
op = opbatch_ops[idx]
|
||||
if op['start_cycles'] <= cyc <= op['end_cycles']:
|
||||
op['trace_events'].append(e)
|
||||
for device, dev_ops in valid_ops_by_dev.items():
|
||||
opbatch_ops = [op for op in dev_ops if op['name'] == "OPBATCH"]
|
||||
other_ops = [op for op in dev_ops if op['name'] != "OPBATCH"]
|
||||
|
||||
# Map to other ops
|
||||
idx = bisect.bisect_right(other_starts, cyc) - 1
|
||||
if idx >= 0:
|
||||
op = other_ops[idx]
|
||||
if op['start_cycles'] <= cyc <= op['end_cycles']:
|
||||
op['trace_events'].append(e)
|
||||
opbatch_ops.sort(key=lambda op: op['start_cycles'])
|
||||
other_ops.sort(key=lambda op: op['start_cycles'])
|
||||
|
||||
opbatch_starts = [op['start_cycles'] for op in opbatch_ops]
|
||||
other_starts = [op['start_cycles'] for op in other_ops]
|
||||
|
||||
dev_traces = traces_by_dev.get(device, [])
|
||||
for e in dev_traces:
|
||||
cyc = e['unwrapped_cycles']
|
||||
|
||||
# Map to OPBATCH
|
||||
idx = bisect.bisect_right(opbatch_starts, cyc) - 1
|
||||
if idx >= 0:
|
||||
op = opbatch_ops[idx]
|
||||
if op['start_cycles'] <= cyc <= op['end_cycles']:
|
||||
op['trace_events'].append(e)
|
||||
|
||||
# Map to other ops
|
||||
idx = bisect.bisect_right(other_starts, cyc) - 1
|
||||
if idx >= 0:
|
||||
op = other_ops[idx]
|
||||
if op['start_cycles'] <= cyc <= op['end_cycles']:
|
||||
op['trace_events'].append(e)
|
||||
|
||||
return all_ops
|
||||
|
||||
@@ -563,6 +638,7 @@ def main():
|
||||
parser.add_argument("--timeline", type=str, nargs='?', const='summary', choices=["summary", "bubbles"],
|
||||
help="Output ASCII art event summary or thread idle bubble analysis (default: summary)")
|
||||
parser.add_argument("--filter", type=str, help="Regex filter matching against the original profile-op line")
|
||||
parser.add_argument("--device", type=str, help="Device to filter by (e.g. HTP0, HTP0:0) or 'split' to generate separate reports per device")
|
||||
|
||||
group = parser.add_mutually_exclusive_group()
|
||||
group.add_argument("--head", type=int, help="Limit to first N ops")
|
||||
@@ -586,29 +662,84 @@ def main():
|
||||
logger.warning(f"Invalid width format '{w}'")
|
||||
|
||||
final_pmu_name = (args.pmu_name or f"#{args.pmu_index}") if args.pmu_index is not None else None
|
||||
ops = parse_log(args.logfile, pmu_index=args.pmu_index)
|
||||
|
||||
op_filter_re = None
|
||||
if args.filter:
|
||||
try:
|
||||
filter_re = re.compile(args.filter)
|
||||
op_filter_re = re.compile(args.filter)
|
||||
except re.error as e:
|
||||
logger.error(f"Invalid regex filter: {e}")
|
||||
sys.exit(1)
|
||||
ops = [op for op in ops if filter_re.search(op['op_text'])]
|
||||
|
||||
if args.head is not None:
|
||||
ops = ops[:args.head]
|
||||
elif args.tail is not None:
|
||||
ops = ops[-args.tail:]
|
||||
limit = args.head if args.head is not None else None
|
||||
device_filter = args.device if (args.device and args.device != "split") else None
|
||||
ops = parse_log(args.logfile, pmu_index=args.pmu_index, limit=limit, device_filter=device_filter, op_filter_re=op_filter_re)
|
||||
|
||||
if args.timeline:
|
||||
for op in ops:
|
||||
if args.timeline == "summary":
|
||||
print_ascii_summary(op['name'], op['dims'], op['types'], op['usec'], op['cycles'], op['trace_events'])
|
||||
elif args.timeline == "bubbles":
|
||||
print_bubbles_timeline(op)
|
||||
if args.device and args.device != "split":
|
||||
ops = [op for op in ops if device_matches(op['device'], args.device)]
|
||||
|
||||
if args.device == "split":
|
||||
unique_devices = sorted(list(set(op['device'] for op in ops)))
|
||||
for dev in unique_devices:
|
||||
dev_ops = [op for op in ops if device_matches(op['device'], dev)]
|
||||
|
||||
if args.filter:
|
||||
try:
|
||||
filter_re = re.compile(args.filter)
|
||||
except re.error as e:
|
||||
logger.error(f"Invalid regex filter: {e}")
|
||||
sys.exit(1)
|
||||
dev_ops = [op for op in dev_ops if filter_re.search(op['op_text'])]
|
||||
|
||||
if args.head is not None:
|
||||
dev_ops = dev_ops[:args.head]
|
||||
elif args.tail is not None:
|
||||
dev_ops = dev_ops[-args.tail:]
|
||||
|
||||
logger.info("\n=========================================")
|
||||
logger.info(f" Device: {dev}")
|
||||
logger.info("=========================================")
|
||||
|
||||
if args.timeline:
|
||||
for op in dev_ops:
|
||||
if args.timeline == "summary":
|
||||
print_ascii_summary(op['name'], op['dims'], op['types'], op['usec'], op['cycles'], op['trace_events'])
|
||||
elif args.timeline == "bubbles":
|
||||
print_bubbles_timeline(op)
|
||||
else:
|
||||
generate_report(dev_ops, args.top, overrides, args.sort, pmu_name=final_pmu_name)
|
||||
else:
|
||||
generate_report(ops, args.top, overrides, args.sort, pmu_name=final_pmu_name)
|
||||
if args.filter:
|
||||
try:
|
||||
filter_re = re.compile(args.filter)
|
||||
except re.error as e:
|
||||
logger.error(f"Invalid regex filter: {e}")
|
||||
sys.exit(1)
|
||||
ops = [op for op in ops if filter_re.search(op['op_text'])]
|
||||
|
||||
if args.head is not None or args.tail is not None:
|
||||
ops_by_dev = defaultdict(list)
|
||||
for op in ops:
|
||||
ops_by_dev[op['device']].append(op)
|
||||
|
||||
filtered_ops = []
|
||||
for dev in sorted(ops_by_dev.keys()):
|
||||
dev_ops = ops_by_dev[dev]
|
||||
if args.head is not None:
|
||||
dev_ops = dev_ops[:args.head]
|
||||
elif args.tail is not None:
|
||||
dev_ops = dev_ops[-args.tail:]
|
||||
filtered_ops.extend(dev_ops)
|
||||
ops = filtered_ops
|
||||
|
||||
if args.timeline:
|
||||
for op in ops:
|
||||
if args.timeline == "summary":
|
||||
print_ascii_summary(op['name'], op['dims'], op['types'], op['usec'], op['cycles'], op['trace_events'])
|
||||
elif args.timeline == "bubbles":
|
||||
print_bubbles_timeline(op)
|
||||
else:
|
||||
generate_report(ops, args.top, overrides, args.sort, pmu_name=final_pmu_name)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
@@ -20,6 +20,31 @@ trace_pattern = re.compile(
|
||||
r"trace-evt\s+(?P<event>[A-Z_0-9\-]+):\s+thread\s+(?P<thread>\d+)\s+info\s+(?P<info>\d+)\s+(?P<state>start|stop)\s+(?P<cycles>\d+)"
|
||||
)
|
||||
|
||||
device_pattern = re.compile(r"\b(HTP\d+(?::\d+)?)\s+(?:profile-op|trace-evt)\b")
|
||||
|
||||
|
||||
def extract_device(line):
|
||||
m = device_pattern.search(line)
|
||||
if m:
|
||||
return m.group(1)
|
||||
return "HTP0"
|
||||
|
||||
|
||||
def device_matches(record_device, target_device):
|
||||
targets = [t.strip() for t in target_device.split(',')]
|
||||
for target in targets:
|
||||
if record_device == target:
|
||||
return True
|
||||
if record_device.startswith(target + ":"):
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
def get_split_output_path(base_path, device_name):
|
||||
safe_device = device_name.replace(':', '_')
|
||||
root, ext = os.path.splitext(base_path)
|
||||
return f"{root}-{safe_device}{ext}"
|
||||
|
||||
|
||||
def normalize_event_name(evt_type, info=0):
|
||||
if evt_type == "HVX_COMP":
|
||||
@@ -54,7 +79,79 @@ class CycleUnwrapper:
|
||||
return raw + self.high_part
|
||||
|
||||
|
||||
def parse_log(file_path):
|
||||
class DeviceTimeMapper:
|
||||
def __init__(self, dev, ops):
|
||||
self.dev = dev
|
||||
self.batches = []
|
||||
for op in ops:
|
||||
if op.get('device') == dev and op.get('name') == 'OPBATCH' and op.get('unwrapped_cycles_start') is not None:
|
||||
cycles = op.get('cycles', 0)
|
||||
usec = op.get('usec', 0)
|
||||
start_cyc = op['unwrapped_cycles_start']
|
||||
freq = (cycles / usec) if usec > 0 and cycles > 0 else 1000.0
|
||||
if freq <= 0:
|
||||
freq = 1000.0
|
||||
self.batches.append({
|
||||
'start_cycles': start_cyc,
|
||||
'cycles': cycles,
|
||||
'end_cycles': start_cyc + cycles,
|
||||
'usec': usec,
|
||||
'dur_ns': usec * 1000,
|
||||
'freq_mhz': freq,
|
||||
})
|
||||
|
||||
self.batches.sort(key=lambda b: b['start_cycles'])
|
||||
|
||||
for i, b in enumerate(self.batches):
|
||||
if i == 0:
|
||||
b['start_time_ns'] = 0
|
||||
else:
|
||||
prev = self.batches[i - 1]
|
||||
idle_cyc = max(0, b['start_cycles'] - prev['end_cycles'])
|
||||
idle_ns = int(round((idle_cyc / prev['freq_mhz']) * 1000))
|
||||
b['start_time_ns'] = prev['start_time_ns'] + prev['dur_ns'] + idle_ns
|
||||
|
||||
self.batch_starts = [b['start_cycles'] for b in self.batches]
|
||||
|
||||
valid_starts = [op['unwrapped_cycles_start'] for op in ops if op.get('device') == dev and op.get('unwrapped_cycles_start') is not None]
|
||||
self.min_cyc = min(valid_starts) if valid_starts else 0
|
||||
if self.batches:
|
||||
self.default_freq = self.batches[0]['freq_mhz']
|
||||
else:
|
||||
freqs = [op['cycles'] / op['usec'] for op in ops if op.get('device') == dev and op.get('usec', 0) > 0 and op.get('cycles', 0) > 0]
|
||||
self.default_freq = statistics.mean(freqs) if freqs else 1000.0
|
||||
|
||||
def get_batch(self, cyc):
|
||||
if not self.batches:
|
||||
return None
|
||||
idx = bisect.bisect_right(self.batch_starts, cyc) - 1
|
||||
if idx >= 0:
|
||||
return self.batches[idx]
|
||||
return self.batches[0]
|
||||
|
||||
def get_freq(self, cyc=None):
|
||||
if cyc is not None:
|
||||
b = self.get_batch(cyc)
|
||||
if b is not None:
|
||||
return b['freq_mhz']
|
||||
return self.default_freq
|
||||
|
||||
def cycle_to_ns(self, cyc):
|
||||
if cyc is None:
|
||||
return 0
|
||||
b = self.get_batch(cyc)
|
||||
if b is not None:
|
||||
return b['start_time_ns'] + int(round(((cyc - b['start_cycles']) / b['freq_mhz']) * 1000))
|
||||
return int(round(((cyc - self.min_cyc) / self.default_freq) * 1000))
|
||||
|
||||
def dur_cycles_to_ns(self, cyc_start, cyc_dur):
|
||||
if cyc_dur is None:
|
||||
return 0
|
||||
freq = self.get_freq(cyc_start)
|
||||
return int(round((cyc_dur / freq) * 1000))
|
||||
|
||||
|
||||
def parse_log(file_path, limit=None, device_filter=None, op_filter_re=None):
|
||||
try:
|
||||
if file_path != "-":
|
||||
f = open(file_path, 'r', encoding='utf-8', errors='ignore')
|
||||
@@ -67,14 +164,25 @@ def parse_log(file_path):
|
||||
all_ops: List[Dict[str, Any]] = []
|
||||
all_traces: List[Dict[str, Any]] = []
|
||||
current_op: Optional[Dict[str, Any]] = None
|
||||
unwrapper = None
|
||||
trace_unwrapper = None
|
||||
ops_count_per_device = {}
|
||||
if device_filter is not None:
|
||||
for target in device_filter.split(','):
|
||||
ops_count_per_device[target.strip()] = 0
|
||||
limit_reached = False
|
||||
unwrappers = {}
|
||||
last_batch_start = {}
|
||||
trace_unwrappers = {}
|
||||
line_idx = 0
|
||||
|
||||
for line in f:
|
||||
line_idx += 1
|
||||
if "|" in line and "profile-op" in line:
|
||||
parts = [p.strip() for p in line.split("|")]
|
||||
if "profile-op" not in line and "trace-evt" not in line:
|
||||
continue
|
||||
device = extract_device(line)
|
||||
|
||||
idx = line.find("profile-op")
|
||||
if idx != -1 and "|" in line[idx:]:
|
||||
parts = [p.strip() for p in line[idx:].split("|")]
|
||||
prefix = parts[0]
|
||||
prefix_match = re.search(r"profile-op\s+(?P<op_name>[A-Z_0-9+]+)", prefix)
|
||||
if not prefix_match:
|
||||
@@ -115,14 +223,18 @@ def parse_log(file_path):
|
||||
if op_name == "OPBATCH":
|
||||
if cycles_start_raw:
|
||||
unwrapped_cycles_start = int(cycles_start_raw)
|
||||
unwrapper = CycleUnwrapper(unwrapped_cycles_start)
|
||||
trace_unwrapper = CycleUnwrapper(unwrapped_cycles_start)
|
||||
unwrappers[device] = CycleUnwrapper(unwrapped_cycles_start)
|
||||
last_batch_start[device] = unwrapped_cycles_start
|
||||
for k in list(trace_unwrappers.keys()):
|
||||
if k[0] == device:
|
||||
del trace_unwrappers[k]
|
||||
else:
|
||||
if cycles_start_raw and unwrapper is not None:
|
||||
unwrapped_cycles_start = unwrapper.unwrap(int(cycles_start_raw))
|
||||
if cycles_start_raw:
|
||||
device_unwrapper = unwrappers.get(device)
|
||||
if device_unwrapper is not None:
|
||||
unwrapped_cycles_start = device_unwrapper.unwrap(int(cycles_start_raw))
|
||||
|
||||
idx = line.find("profile-op ")
|
||||
op_text = line[idx + 11:].strip() if idx != -1 else line.strip()
|
||||
op_text = re.sub(r"^profile-op\s+", "", line[idx:]).strip() if idx != -1 else line.strip()
|
||||
|
||||
evt_str = None
|
||||
if types.startswith("evt-cnt "):
|
||||
@@ -142,24 +254,59 @@ def parse_log(file_path):
|
||||
'cycles_start': int(cycles_start_raw) if cycles_start_raw else None,
|
||||
'unwrapped_cycles_start': unwrapped_cycles_start,
|
||||
'trace_events': [],
|
||||
'line_num': line_idx
|
||||
'line_num': line_idx,
|
||||
'device': device
|
||||
}
|
||||
all_ops.append(current_op)
|
||||
|
||||
# Check if matching early exit criteria
|
||||
matched = False
|
||||
matched_target = None
|
||||
if device_filter is not None:
|
||||
targets = [t.strip() for t in device_filter.split(',')]
|
||||
for target in targets:
|
||||
if device == target or device.startswith(target + ":"):
|
||||
matched = True
|
||||
matched_target = target
|
||||
break
|
||||
else:
|
||||
matched = True
|
||||
matched_target = device
|
||||
|
||||
if op_filter_re is not None and not op_filter_re.search(op_text):
|
||||
matched = False
|
||||
|
||||
if matched:
|
||||
if matched_target not in ops_count_per_device:
|
||||
ops_count_per_device[matched_target] = 0
|
||||
ops_count_per_device[matched_target] += 1
|
||||
|
||||
if limit is not None and len(ops_count_per_device) > 0 and all(count >= limit for count in ops_count_per_device.values()):
|
||||
limit_reached = True
|
||||
|
||||
if limit_reached and op_name == "OPBATCH":
|
||||
break
|
||||
continue
|
||||
|
||||
trace_match = trace_pattern.search(line)
|
||||
if trace_match:
|
||||
thread = int(trace_match.group('thread'))
|
||||
raw_cyc = int(trace_match.group('cycles'))
|
||||
unwrapped_cyc = None
|
||||
if trace_unwrapper is not None:
|
||||
unwrapped_cyc = trace_unwrapper.unwrap(raw_cyc)
|
||||
th_key = (device, thread)
|
||||
if th_key not in trace_unwrappers:
|
||||
batch_start = last_batch_start.get(device)
|
||||
trace_unwrappers[th_key] = CycleUnwrapper(batch_start)
|
||||
unwrapped_cyc = trace_unwrappers[th_key].unwrap(raw_cyc)
|
||||
all_traces.append({
|
||||
'thread': int(trace_match.group('thread')),
|
||||
'thread': thread,
|
||||
'event': trace_match.group('event'),
|
||||
'info': int(trace_match.group('info')),
|
||||
'cycles': raw_cyc,
|
||||
'unwrapped_cycles': unwrapped_cyc,
|
||||
'state': trace_match.group('state')
|
||||
'state': trace_match.group('state'),
|
||||
'line_num': line_idx,
|
||||
'device': device
|
||||
})
|
||||
|
||||
f.close()
|
||||
@@ -274,27 +421,24 @@ def generate_perfetto_trace(filtered_ops, trace_events, output_path):
|
||||
logger.warning("No operators found after filtering.")
|
||||
return
|
||||
|
||||
# Compute average frequency
|
||||
frequencies = []
|
||||
for op in filtered_ops:
|
||||
if op['usec'] > 0 and op['cycles'] > 0:
|
||||
frequencies.append(op['cycles'] / op['usec'])
|
||||
avg_freq_mhz = statistics.mean(frequencies) if frequencies else 1000.0
|
||||
if avg_freq_mhz <= 0:
|
||||
avg_freq_mhz = 1000.0
|
||||
|
||||
# Assign start and end cycles to each operator
|
||||
for op in filtered_ops:
|
||||
op['start_cycles'] = op['unwrapped_cycles_start']
|
||||
op['end_cycles'] = op['start_cycles'] + op['cycles']
|
||||
op['end_cycles'] = op['start_cycles'] + op['cycles'] if op['start_cycles'] is not None else None
|
||||
|
||||
global_min_cyc = min(op['start_cycles'] for op in filtered_ops if op['start_cycles'] is not None)
|
||||
# Get list of unique devices present in the operations
|
||||
unique_devices = sorted(list(set(op['device'] for op in filtered_ops)))
|
||||
device_to_idx = {dev: idx for idx, dev in enumerate(unique_devices)}
|
||||
time_mappers = {dev: DeviceTimeMapper(dev, filtered_ops) for dev in unique_devices}
|
||||
|
||||
# Process events
|
||||
completed_events = []
|
||||
if trace_events:
|
||||
trace_events = sorted(trace_events, key=lambda e: e['unwrapped_cycles'])
|
||||
one_usec_cycles = max(avg_freq_mhz, 1.0)
|
||||
|
||||
one_usec_cycles = {}
|
||||
for dev in unique_devices:
|
||||
one_usec_cycles[dev] = max(time_mappers[dev].get_freq(), 1.0)
|
||||
|
||||
active_starts = {}
|
||||
for e in trace_events:
|
||||
@@ -303,31 +447,36 @@ def generate_perfetto_trace(filtered_ops, trace_events, output_path):
|
||||
info = e['info']
|
||||
state = e['state']
|
||||
cyc = e['unwrapped_cycles']
|
||||
dev = e['device']
|
||||
|
||||
key = (t, evt, info)
|
||||
key = (dev, t, evt, info)
|
||||
if state == 'start':
|
||||
# Handle missing stop (start followed by another start)
|
||||
if key in active_starts:
|
||||
prev_start = active_starts[key]
|
||||
prev_e = active_starts[key]
|
||||
completed_events.append({
|
||||
'thread': t,
|
||||
'event': evt,
|
||||
'info': info,
|
||||
'start_cyc': prev_start,
|
||||
'end_cyc': prev_start + one_usec_cycles,
|
||||
'start_cyc': prev_e['unwrapped_cycles'],
|
||||
'end_cyc': prev_e['unwrapped_cycles'] + one_usec_cycles.get(dev, 1000.0),
|
||||
'line_num': prev_e.get('line_num'),
|
||||
'missing_stop': True,
|
||||
'device': dev
|
||||
})
|
||||
active_starts[key] = cyc
|
||||
active_starts[key] = e
|
||||
elif state == 'stop':
|
||||
if key in active_starts:
|
||||
start_cyc = active_starts[key]
|
||||
prev_e = active_starts[key]
|
||||
del active_starts[key]
|
||||
completed_events.append({
|
||||
'thread': t,
|
||||
'event': evt,
|
||||
'info': info,
|
||||
'start_cyc': start_cyc,
|
||||
'start_cyc': prev_e['unwrapped_cycles'],
|
||||
'end_cyc': cyc,
|
||||
'line_num': prev_e.get('line_num'),
|
||||
'device': dev
|
||||
})
|
||||
else:
|
||||
# Handle missing start (stop without start)
|
||||
@@ -335,31 +484,36 @@ def generate_perfetto_trace(filtered_ops, trace_events, output_path):
|
||||
'thread': t,
|
||||
'event': evt,
|
||||
'info': info,
|
||||
'start_cyc': cyc - one_usec_cycles,
|
||||
'start_cyc': cyc - one_usec_cycles.get(dev, 1000.0),
|
||||
'end_cyc': cyc,
|
||||
'line_num': e.get('line_num'),
|
||||
'missing_start': True,
|
||||
'device': dev
|
||||
})
|
||||
|
||||
# Clear remaining unmatched starts
|
||||
for key, start_cyc in active_starts.items():
|
||||
t, evt, info = key
|
||||
for key, prev_e in active_starts.items():
|
||||
dev, t, evt, info = key
|
||||
completed_events.append({
|
||||
'thread': t,
|
||||
'event': evt,
|
||||
'info': info,
|
||||
'start_cyc': start_cyc,
|
||||
'end_cyc': start_cyc + one_usec_cycles,
|
||||
'start_cyc': prev_e['unwrapped_cycles'],
|
||||
'end_cyc': prev_e['unwrapped_cycles'] + one_usec_cycles.get(dev, 1000.0),
|
||||
'line_num': prev_e.get('line_num'),
|
||||
'missing_stop': True,
|
||||
'device': dev
|
||||
})
|
||||
|
||||
completed_events.sort(key=lambda e: e['start_cyc'])
|
||||
|
||||
# Convert event times to microseconds and apply clamp rounded to 1ns resolution (3 decimals)
|
||||
# Convert event times to nanoseconds using per-device / per-batch time mapper
|
||||
for e in completed_events:
|
||||
start_us = (e['start_cyc'] - global_min_cyc) / avg_freq_mhz
|
||||
dur_us = (e['end_cyc'] - e['start_cyc']) / avg_freq_mhz
|
||||
e['ts_ns'] = int(round(start_us * 1000))
|
||||
e['dur_ns'] = int(round(max(dur_us, 0.1) * 1000))
|
||||
dev = e['device']
|
||||
tm = time_mappers[dev]
|
||||
e['ts_ns'] = tm.cycle_to_ns(e['start_cyc'])
|
||||
dur_ns = tm.dur_cycles_to_ns(e['start_cyc'], e['end_cyc'] - e['start_cyc'])
|
||||
e['dur_ns'] = max(dur_ns, 100)
|
||||
|
||||
# Allocate slots (sub-tracks) to prevent overlaps on same virtual track
|
||||
active_slots = defaultdict(list)
|
||||
@@ -368,14 +522,15 @@ def generate_perfetto_trace(filtered_ops, trace_events, output_path):
|
||||
evt = e['event']
|
||||
ts = e['ts_ns']
|
||||
dur = e['dur_ns']
|
||||
dev = e['device']
|
||||
|
||||
norm_evt = normalize_event_name(evt, e['info'])
|
||||
if norm_evt == "DMA":
|
||||
track_key = (t, "DMA")
|
||||
track_key = (dev, t, "DMA")
|
||||
elif t == 10:
|
||||
track_key = (t, "HMX")
|
||||
track_key = (dev, t, "HMX")
|
||||
else:
|
||||
track_key = (t, "HVX")
|
||||
track_key = (dev, t, "HVX")
|
||||
|
||||
slots = active_slots[track_key]
|
||||
allocated_slot = -1
|
||||
@@ -395,6 +550,7 @@ def generate_perfetto_trace(filtered_ops, trace_events, output_path):
|
||||
t = e['thread']
|
||||
evt = e['event']
|
||||
slot = e['slot']
|
||||
dev = e['device']
|
||||
|
||||
norm_evt = normalize_event_name(evt, e['info'])
|
||||
if norm_evt == "DMA":
|
||||
@@ -408,56 +564,69 @@ def generate_perfetto_trace(filtered_ops, trace_events, output_path):
|
||||
evt_id = 2
|
||||
|
||||
t_sort = 1 if t == 10 else t + 2
|
||||
dev_idx = device_to_idx[dev]
|
||||
|
||||
# Unique UUID for each sub-track
|
||||
if t == 10:
|
||||
uuid = 20 # HMX thread track UUID
|
||||
uuid = dev_idx * 10000000 + 20 # HMX thread track UUID
|
||||
else:
|
||||
uuid = int(t_sort * 1000000 + evt_id * 1000 + slot)
|
||||
uuid = int(dev_idx * 10000000 + t_sort * 1000000 + evt_id * 1000 + slot)
|
||||
e['uuid'] = uuid
|
||||
used_tracks[uuid] = (t, track_evt, slot)
|
||||
used_tracks[uuid] = (dev, t, track_evt, slot)
|
||||
|
||||
with open(output_path, "wb") as f:
|
||||
# Define Process with EXPLICIT child sorting
|
||||
proc_desc = make_process_descriptor(1, "HTP NPU")
|
||||
proc_packet = make_trace_packet(0, track_descriptor=make_track_descriptor(1, process=proc_desc, child_ordering=3))
|
||||
write_trace_packet_to_file(f, proc_packet)
|
||||
for dev in unique_devices:
|
||||
dev_idx = device_to_idx[dev]
|
||||
pid = dev_idx + 1
|
||||
proc_uuid = dev_idx * 10000000 + 1
|
||||
|
||||
# Define Operators Track (UUID = 2) as a thread track at rank 1, tid 8
|
||||
op_thread_desc = make_thread_descriptor(1, 8, "Ops", sort_index=1)
|
||||
op_packet = make_trace_packet(0, track_descriptor=make_track_descriptor(2, parent_uuid=1, thread=op_thread_desc))
|
||||
write_trace_packet_to_file(f, op_packet)
|
||||
# Define Process with EXPLICIT child sorting
|
||||
proc_name = dev
|
||||
proc_desc = make_process_descriptor(pid, proc_name)
|
||||
proc_packet = make_trace_packet(0, track_descriptor=make_track_descriptor(proc_uuid, process=proc_desc, child_ordering=3))
|
||||
write_trace_packet_to_file(f, proc_packet)
|
||||
|
||||
# Define HMX Thread Track (UUID = 20) at rank 2, tid 9
|
||||
hmx_thread_desc = make_thread_descriptor(1, 9, "HMX", sort_index=2)
|
||||
hmx_packet = make_trace_packet(0, track_descriptor=make_track_descriptor(20, parent_uuid=1, thread=hmx_thread_desc))
|
||||
write_trace_packet_to_file(f, hmx_packet)
|
||||
# Define Operators Track as a thread track
|
||||
op_track_uuid = dev_idx * 10000000 + 2
|
||||
op_tid = pid * 100 + 8
|
||||
op_thread_desc = make_thread_descriptor(pid, op_tid, "Ops", sort_index=1)
|
||||
op_packet = make_trace_packet(0, track_descriptor=make_track_descriptor(op_track_uuid, parent_uuid=proc_uuid, thread=op_thread_desc))
|
||||
write_trace_packet_to_file(f, op_packet)
|
||||
|
||||
# Define Thread Tracks (T0, T1, ..., T9)
|
||||
unique_threads = sorted(list(set(t for (t, _, _) in used_tracks.values() if t != 10)))
|
||||
for t in unique_threads:
|
||||
thread_uuid = 10 + t
|
||||
thread_name = f"T{t}"
|
||||
# Sort order starts from index 3 (T0 -> 3, T1 -> 4, etc.)
|
||||
sort_index = 3 + t
|
||||
tid = 10 + t
|
||||
thread_desc = make_thread_descriptor(1, tid, thread_name, sort_index=sort_index)
|
||||
thread_packet = make_trace_packet(0, track_descriptor=make_track_descriptor(
|
||||
thread_uuid,
|
||||
parent_uuid=1,
|
||||
thread=thread_desc,
|
||||
sibling_order_rank=sort_index,
|
||||
child_ordering=3 # Explicit child sorting for sub-tracks
|
||||
))
|
||||
write_trace_packet_to_file(f, thread_packet)
|
||||
# Define HMX Thread Track at rank 2
|
||||
hmx_track_uuid = dev_idx * 10000000 + 20
|
||||
hmx_tid = pid * 100 + 9
|
||||
hmx_thread_desc = make_thread_descriptor(pid, hmx_tid, "HMX", sort_index=2)
|
||||
hmx_packet = make_trace_packet(0, track_descriptor=make_track_descriptor(hmx_track_uuid, parent_uuid=proc_uuid, thread=hmx_thread_desc))
|
||||
write_trace_packet_to_file(f, hmx_packet)
|
||||
|
||||
# Define Thread Tracks (T0, T1, ..., T9) for this device
|
||||
dev_used_tracks = {uuid: val for uuid, val in used_tracks.items() if val[0] == dev}
|
||||
unique_threads = sorted(list(set(t for (_, t, _, _) in dev_used_tracks.values() if t != 10)))
|
||||
for t in unique_threads:
|
||||
thread_uuid = dev_idx * 10000000 + 10 + t
|
||||
thread_name = f"T{t}"
|
||||
sort_index = 3 + t
|
||||
tid = pid * 100 + 10 + t
|
||||
thread_desc = make_thread_descriptor(pid, tid, thread_name, sort_index=sort_index)
|
||||
thread_packet = make_trace_packet(0, track_descriptor=make_track_descriptor(
|
||||
thread_uuid,
|
||||
parent_uuid=proc_uuid,
|
||||
thread=thread_desc,
|
||||
sibling_order_rank=sort_index,
|
||||
child_ordering=3 # Explicit child sorting for sub-tracks
|
||||
))
|
||||
write_trace_packet_to_file(f, thread_packet)
|
||||
|
||||
# Define Track descriptors for sub-tracks parented to thread tracks
|
||||
for uuid in sorted(used_tracks.keys()):
|
||||
if uuid == 20:
|
||||
dev, t, evt, slot = used_tracks[uuid]
|
||||
dev_idx = device_to_idx[dev]
|
||||
if t == 10:
|
||||
continue
|
||||
t, evt, slot = used_tracks[uuid]
|
||||
name = f"T{t} {evt}"
|
||||
rank = 0 if evt == "HVX" else 1
|
||||
parent_thread_uuid = 10 + t
|
||||
parent_thread_uuid = dev_idx * 10000000 + 10 + t
|
||||
# Sibling merge behavior: 1 (SIBLING_MERGE_BEHAVIOR_BY_TRACK_NAME)
|
||||
track_desc = make_track_descriptor(
|
||||
uuid=uuid,
|
||||
@@ -470,15 +639,18 @@ def generate_perfetto_trace(filtered_ops, trace_events, output_path):
|
||||
write_trace_packet_to_file(f, track_packet)
|
||||
|
||||
# Emit Operators
|
||||
last_op_end_ns = 0
|
||||
last_op_end_ns = defaultdict(int)
|
||||
for op in filtered_ops:
|
||||
op_start_ns = int(round(((op['start_cycles'] - global_min_cyc) / avg_freq_mhz) * 1000))
|
||||
op_dur_ns = int(round((op['cycles'] / avg_freq_mhz) * 1000))
|
||||
dev = op['device']
|
||||
dev_idx = device_to_idx[dev]
|
||||
tm = time_mappers[dev]
|
||||
op_start_ns = tm.cycle_to_ns(op['start_cycles'])
|
||||
op_dur_ns = tm.dur_cycles_to_ns(op['start_cycles'], op['cycles'])
|
||||
if op['name'] != "OPBATCH":
|
||||
if op_start_ns < last_op_end_ns:
|
||||
op_start_ns = last_op_end_ns
|
||||
if op_start_ns < last_op_end_ns[dev]:
|
||||
op_start_ns = last_op_end_ns[dev]
|
||||
clamped_dur = max(op_dur_ns, 100) # Clamp to 100ns (0.1us)
|
||||
last_op_end_ns = op_start_ns + clamped_dur
|
||||
last_op_end_ns[dev] = op_start_ns + clamped_dur
|
||||
else:
|
||||
clamped_dur = max(op_dur_ns, 100)
|
||||
|
||||
@@ -495,24 +667,41 @@ def generate_perfetto_trace(filtered_ops, trace_events, output_path):
|
||||
if 'evt' in op and op['evt']:
|
||||
debug_annots.append(make_debug_annotation("evt", string_val=op['evt']))
|
||||
|
||||
op_track_uuid = dev_idx * 10000000 + 2
|
||||
|
||||
# Slice Begin
|
||||
evt_begin = make_track_event(1, 2, name=f"{op['name']} ({op['dims']})", category="operator", debug_annotations=debug_annots)
|
||||
evt_begin = make_track_event(1, op_track_uuid, name=f"{op['name']} ({op['dims']})", category="operator", debug_annotations=debug_annots)
|
||||
packet_begin = make_trace_packet(op_start_ns, track_event=evt_begin)
|
||||
write_trace_packet_to_file(f, packet_begin)
|
||||
|
||||
# Slice End
|
||||
evt_end = make_track_event(2, 2)
|
||||
evt_end = make_track_event(2, op_track_uuid)
|
||||
packet_end = make_trace_packet(op_start_ns + clamped_dur, track_event=evt_end)
|
||||
write_trace_packet_to_file(f, packet_end)
|
||||
|
||||
# Emit Thread Trace Events
|
||||
for e in completed_events:
|
||||
norm_name = normalize_event_name(e['event'], e['info'])
|
||||
name = f"DMA {e['info']}" if norm_name == "DMA" else norm_name
|
||||
if norm_name == "DMA":
|
||||
name = f"DMA {e['info']}"
|
||||
elif norm_name == "FENCE":
|
||||
name = f"FENCE {e['info']}" if e.get('info') is not None and e['info'] != 0 else "FENCE"
|
||||
else:
|
||||
name = norm_name
|
||||
|
||||
if e.get('missing_start') or e.get('missing_stop'):
|
||||
name += "!"
|
||||
|
||||
debug_annots = []
|
||||
if 'line_num' in e and e['line_num'] is not None:
|
||||
debug_annots.append(make_debug_annotation("line", int_val=e['line_num']))
|
||||
if norm_name == "FENCE" and e.get('info') is not None:
|
||||
debug_annots.append(make_debug_annotation("seq", int_val=e['info']))
|
||||
elif norm_name == "DMA" and e.get('info') is not None:
|
||||
debug_annots.append(make_debug_annotation("channel", int_val=e['info']))
|
||||
elif e.get('info') is not None and e['info'] != 0:
|
||||
debug_annots.append(make_debug_annotation("info", int_val=e['info']))
|
||||
|
||||
if e.get('missing_start'):
|
||||
debug_annots.append(make_debug_annotation("missing_start", string_val="true"))
|
||||
if e.get('missing_stop'):
|
||||
@@ -536,6 +725,7 @@ def main():
|
||||
parser.add_argument("logfile", help="Path to hex-log profile file")
|
||||
parser.add_argument("-o", "--output", default="optrace.perfetto-trace", help="Output trace file path (default: optrace.perfetto-trace)")
|
||||
parser.add_argument("--filter", type=str, help="Regex filter matching against the original profile-op line")
|
||||
parser.add_argument("--device", type=str, help="Device to filter by (e.g. HTP0, HTP0:0) or 'split' to generate separate files per device")
|
||||
|
||||
group = parser.add_mutually_exclusive_group()
|
||||
group.add_argument("--head", type=int, help="Limit to first N ops")
|
||||
@@ -544,7 +734,21 @@ def main():
|
||||
args = parser.parse_args()
|
||||
logging.basicConfig(level=logging.INFO, format='%(message)s')
|
||||
|
||||
ops, traces = parse_log(args.logfile)
|
||||
op_filter_re = None
|
||||
if args.filter:
|
||||
try:
|
||||
op_filter_re = re.compile(args.filter)
|
||||
except re.error as e:
|
||||
logger.error(f"Invalid regex filter: {e}")
|
||||
sys.exit(1)
|
||||
|
||||
limit = args.head if args.head is not None else None
|
||||
device_filter = args.device if (args.device and args.device != "split") else None
|
||||
ops, traces = parse_log(args.logfile, limit=limit, device_filter=device_filter, op_filter_re=op_filter_re)
|
||||
|
||||
if args.device and args.device != "split":
|
||||
ops = [op for op in ops if device_matches(op['device'], args.device)]
|
||||
traces = [t for t in traces if device_matches(t['device'], args.device)]
|
||||
|
||||
if args.filter:
|
||||
try:
|
||||
@@ -554,35 +758,60 @@ def main():
|
||||
sys.exit(1)
|
||||
ops = [op for op in ops if filter_re.search(op['op_text'])]
|
||||
|
||||
if args.head is not None:
|
||||
ops = ops[:args.head]
|
||||
elif args.tail is not None:
|
||||
ops = ops[-args.tail:]
|
||||
if args.head is not None or args.tail is not None:
|
||||
ops_by_dev = defaultdict(list)
|
||||
for op in ops:
|
||||
ops_by_dev[op['device']].append(op)
|
||||
|
||||
filtered_ops = []
|
||||
for dev in sorted(ops_by_dev.keys()):
|
||||
dev_ops = ops_by_dev[dev]
|
||||
if args.head is not None:
|
||||
dev_ops = dev_ops[:args.head]
|
||||
elif args.tail is not None:
|
||||
dev_ops = dev_ops[-args.tail:]
|
||||
filtered_ops.extend(dev_ops)
|
||||
ops = filtered_ops
|
||||
|
||||
if args.filter or args.head is not None or args.tail is not None:
|
||||
valid_ranges = []
|
||||
# Group valid ranges by device
|
||||
valid_ranges_by_dev = defaultdict(list)
|
||||
for op in ops:
|
||||
start_cyc = op['unwrapped_cycles_start']
|
||||
end_cyc = start_cyc + op['cycles'] if start_cyc is not None else None
|
||||
if start_cyc is not None and end_cyc is not None:
|
||||
valid_ranges.append((start_cyc, end_cyc))
|
||||
valid_ranges_by_dev[op['device']].append((start_cyc, end_cyc))
|
||||
|
||||
valid_ranges.sort(key=lambda r: r[0])
|
||||
range_starts = [r[0] for r in valid_ranges]
|
||||
for dev in valid_ranges_by_dev:
|
||||
valid_ranges_by_dev[dev].sort(key=lambda r: r[0])
|
||||
|
||||
range_starts_by_dev = {dev: [r[0] for r in ranges] for dev, ranges in valid_ranges_by_dev.items()}
|
||||
|
||||
filtered_traces = []
|
||||
for e in traces:
|
||||
cyc = e['unwrapped_cycles']
|
||||
if cyc is None:
|
||||
continue
|
||||
dev = e['device']
|
||||
range_starts = range_starts_by_dev.get(dev)
|
||||
if not range_starts:
|
||||
continue
|
||||
idx = bisect.bisect_right(range_starts, cyc) - 1
|
||||
if idx >= 0:
|
||||
start, end = valid_ranges[idx]
|
||||
start, end = valid_ranges_by_dev[dev][idx]
|
||||
if start <= cyc <= end:
|
||||
filtered_traces.append(e)
|
||||
traces = filtered_traces
|
||||
|
||||
generate_perfetto_trace(ops, traces, args.output)
|
||||
if args.device == "split":
|
||||
unique_devices = sorted(list(set(op['device'] for op in ops)))
|
||||
for dev in unique_devices:
|
||||
dev_ops = [op for op in ops if device_matches(op['device'], dev)]
|
||||
dev_traces = [t for t in traces if device_matches(t['device'], dev)]
|
||||
out_path = get_split_output_path(args.output, dev)
|
||||
generate_perfetto_trace(dev_ops, dev_traces, out_path)
|
||||
else:
|
||||
generate_perfetto_trace(ops, traces, args.output)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
Executable
+405
@@ -0,0 +1,405 @@
|
||||
#!/usr/bin/env python3
|
||||
#
|
||||
# Run llama.cpp tools on Snapdragon devices (natively, via ADB, or SSH).
|
||||
#
|
||||
|
||||
import sys
|
||||
import os
|
||||
import argparse
|
||||
import subprocess
|
||||
import platform
|
||||
import shlex
|
||||
import logging
|
||||
|
||||
logger = logging.getLogger("run")
|
||||
|
||||
|
||||
def parse_target(target_str):
|
||||
if not target_str:
|
||||
return None, None
|
||||
if target_str.startswith("adb") or target_str.startswith("android"):
|
||||
parts = target_str.split(":", 1)
|
||||
serial = parts[1] if len(parts) > 1 else None
|
||||
return "android", serial
|
||||
elif target_str.startswith("lnx") or target_str.startswith("linux") or target_str.startswith("ubuntu"):
|
||||
parts = target_str.split(":", 1)
|
||||
host = parts[1] if len(parts) > 1 else None
|
||||
return "linux", host
|
||||
elif target_str in ("wos", "windows"):
|
||||
return "windows", None
|
||||
else:
|
||||
return None, None
|
||||
|
||||
|
||||
def shlex_join(args_list):
|
||||
if hasattr(shlex, 'join'):
|
||||
return shlex.join(args_list)
|
||||
import pipes
|
||||
return " ".join(pipes.quote(x) for x in args_list)
|
||||
|
||||
|
||||
def main():
|
||||
logging.basicConfig(level=logging.INFO, format='%(message)s')
|
||||
# Split arguments at '--'
|
||||
if '--' in sys.argv:
|
||||
idx = sys.argv.index('--')
|
||||
run_args = sys.argv[1:idx]
|
||||
cmd_args = sys.argv[idx + 1:]
|
||||
else:
|
||||
run_args = sys.argv[1:]
|
||||
cmd_args = []
|
||||
|
||||
parser = argparse.ArgumentParser(
|
||||
description="Unified runner for llama.cpp tools on Snapdragon (natively, via ADB, or via SSH)."
|
||||
)
|
||||
parser.add_argument("--target", help="Execution target (e.g. android[:serial]/adb[:serial], linux:[user@]host/lnx:[user@]host/ubuntu:[user@]host, windows/wos) (default: local run)")
|
||||
parser.add_argument("--target-dir", help="Target directory on the device (default: /data/local/tmp/llama.cpp for Android, ~/llama.cpp for Linux)")
|
||||
parser.add_argument("--install-dir", help="Install directory name (defaults to pkg-TARGET or pkg-TARGET-dbg prefix based on target)")
|
||||
parser.add_argument("--debug", action="store_true", help="Use debug build (defaults to pkg-TARGET-dbg folder)")
|
||||
parser.add_argument("--devices", "--device", "-d", help="Select execution devices (split into NPU and OpenCL GPUs automatically, default: HTP0)")
|
||||
parser.add_argument("--verbose", help="Verbose level (enables both Hexagon and OpenCL kernel cache debugging)")
|
||||
parser.add_argument("--profile", help="Profiling flag (enables Hexagon profiling and OpenCL autotuning)")
|
||||
parser.add_argument("--sched-debug", action="store_true", help="Enable GGML/llama.cpp scheduler debug output (GGML_SCHED_DEBUG=2)")
|
||||
parser.add_argument("--mtmd-device", help="Specify the backend device ID for Multi-Threaded Multi-Device setup (MTMD_BACKEND_DEVICE)")
|
||||
|
||||
# Hexagon specific parameters
|
||||
parser.add_argument("--hex-verbose", help="Enable verbose logging (GGML_HEXAGON_VERBOSE)")
|
||||
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-hostbuf", help="Enable host buffers (GGML_HEXAGON_HOSTBUF)")
|
||||
parser.add_argument("--hex-opbatch", help="Maximum number of operations to batch into a single HTP execution (GGML_HEXAGON_OPBATCH)")
|
||||
parser.add_argument("--hex-opqueue", help="Size of the asynchronous NPU operation queue (GGML_HEXAGON_OPQUEUE)")
|
||||
parser.add_argument("--hex-oppoll", default="1", help="Enable (1) or Disable (0) polling for NPU opbatch completion (GGML_HEXAGON_OPPOLL) (default: 1)")
|
||||
parser.add_argument("--hex-opfilter", help="Regex pattern to filter/select which operators are offloaded to NPU (GGML_HEXAGON_OPFILTER)")
|
||||
parser.add_argument("--hex-opfusion", help="NPU graph node fusion optimization level (0: disabled, 1: enabled) (GGML_HEXAGON_OPFUSION)")
|
||||
parser.add_argument("--hex-vmem", help="Maximum NPU VMEM size limit in MB to allocate (GGML_HEXAGON_VMEM)")
|
||||
parser.add_argument("--hex-mbuf", help="Maximum host buffer size limit in MB to allocate (GGML_HEXAGON_MBUF)")
|
||||
parser.add_argument("--hex-mm-select", help="Select MUL_MAT and MUL_MAT_ID kernel (GGML_HEXAGON_MM_SELECT) 3:HMX,2:HVX-tiled,1:HVX-flat,0:disable")
|
||||
parser.add_argument("--hex-fa-select", help="Select Flash Attention kernel (GGML_HEXAGON_FA_SELECT) 2:HMX,1:HVX,0:disable")
|
||||
parser.add_argument("--hex-ar-select", help="Select All-Reduce kernel (GGML_HEXAGON_AR_SELECT) 1:enable,0:disable")
|
||||
parser.add_argument("--hex-etm", help="Enable Embedded Trace Macrocell hardware tracing / trace logging (GGML_HEXAGON_ETM)")
|
||||
parser.add_argument("--hex-arch", help="Target Hexagon NPU architecture version override (v73, v75, v79, v81, etc.) (GGML_HEXAGON_ARCH)")
|
||||
parser.add_argument("--hex-optrace", help="Trace buffer size in number of records (GGML_HEXAGON_OPTRACE)")
|
||||
|
||||
# OpenCL specific parameters
|
||||
parser.add_argument("--cl-platform", help="Select OpenCL platform name/regex (e.g. Qualified Qualcomm OpenCL platform) (GGML_OPENCL_PLATFORM)")
|
||||
parser.add_argument("--cl-device", help="Select OpenCL device name/regex (e.g. Adreno GPU) (GGML_OPENCL_DEVICE)")
|
||||
parser.add_argument("--cl-opfilter", help="Regex pattern to filter/select which operators are offloaded to OpenCL (GGML_OPENCL_OPFILTER)")
|
||||
parser.add_argument("--cl-disable-fusion", action="store_true", help="Disable OpenCL kernel fusion optimizations (GGML_OPENCL_DISABLE_FUSION)")
|
||||
parser.add_argument("--cl-cache-dir", help="Directory path to store compiled OpenCL program binaries (GGML_OPENCL_KERNEL_CACHE_DIR)")
|
||||
parser.add_argument("--cl-cache-debug", help="Enable verbose debugging logs for the kernel caching system (GGML_OPENCL_KERNEL_CACHE_DEBUG)")
|
||||
parser.add_argument("--cl-fa-tune", action="store_true", help="Enable automatic Flash Attention kernel autotuning (GGML_OPENCL_FA_TUNE)")
|
||||
parser.add_argument("--cl-adreno-xmem", action="store_true", help="Enforce matmul using texture/image (xmem) memory paths on Adreno GPUs (GGML_OPENCL_ADRENO_XMEM_GEMM)")
|
||||
parser.add_argument("--cl-adreno-large-buffer", action="store_true", help="Allow allocating larger buffer sizes on Adreno GPUs (GGML_OPENCL_ADRENO_USE_LARGE_BUFFER)")
|
||||
|
||||
args = parser.parse_args(run_args)
|
||||
|
||||
if not cmd_args:
|
||||
parser.print_help()
|
||||
logger.error("\nError: No command specified after '--'")
|
||||
sys.exit(1)
|
||||
|
||||
target_type = None
|
||||
target_val = None
|
||||
target_prefix = None
|
||||
if args.target:
|
||||
target_type, target_val = parse_target(args.target)
|
||||
if not target_type:
|
||||
logger.error(f"Error: Invalid target format '{args.target}'. Must be android[:serial]/adb[:serial], linux:[user@]host/lnx:[user@]host/ubuntu:[user@]host, or windows/wos.")
|
||||
sys.exit(1)
|
||||
target_prefix = args.target.split(":", 1)[0]
|
||||
|
||||
# Resolve install directory
|
||||
install_dir = args.install_dir
|
||||
if not install_dir:
|
||||
if target_prefix:
|
||||
suffix = "-dbg" if args.debug else ""
|
||||
install_dir = f"pkg-{target_prefix}{suffix}"
|
||||
else:
|
||||
# Smart branch folder detection for local run if default is not set
|
||||
prefixes = ("wos", "windows", "lnx", "linux", "ubuntu", "adb", "android")
|
||||
suffixes = ("-dbg", "") if args.debug else ("", "-dbg")
|
||||
found = False
|
||||
for suffix in suffixes:
|
||||
for prefix in prefixes:
|
||||
test_path = f"./pkg-{prefix}{suffix}/llama.cpp"
|
||||
if os.path.exists(test_path):
|
||||
install_dir = f"pkg-{prefix}{suffix}"
|
||||
found = True
|
||||
break
|
||||
if found:
|
||||
break
|
||||
if not install_dir:
|
||||
install_dir = "pkg-android" # Fallback default
|
||||
|
||||
# Host side package path
|
||||
package_path = os.path.join(install_dir, "llama.cpp")
|
||||
|
||||
# Environment variables to map
|
||||
env_vars = {}
|
||||
|
||||
def set_env(env_name, opt_val):
|
||||
if opt_val is not None:
|
||||
env_vars[env_name] = str(opt_val)
|
||||
elif env_name in os.environ:
|
||||
env_vars[env_name] = os.environ[env_name]
|
||||
|
||||
# Resolve and filter devices (HTP vs OpenCL)
|
||||
devices_val = args.devices if args.devices is not None else "HTP0"
|
||||
if devices_val.isdigit():
|
||||
hex_devices = devices_val
|
||||
cl_device = ""
|
||||
else:
|
||||
parts = [p.strip() for p in devices_val.split(",")]
|
||||
# Any device containing "htp" is Hexagon, rest is OpenCL
|
||||
hex_parts = [p for p in parts if "htp" in p.lower()]
|
||||
cl_parts = [p for p in parts if "htp" not in p.lower()]
|
||||
hex_devices = ",".join(hex_parts)
|
||||
cl_device = ",".join(cl_parts)
|
||||
|
||||
# Set Hexagon devices
|
||||
if hex_devices:
|
||||
env_vars["GGML_HEXAGON_DEVICES"] = hex_devices
|
||||
elif "GGML_HEXAGON_DEVICES" in os.environ:
|
||||
env_vars["GGML_HEXAGON_DEVICES"] = os.environ["GGML_HEXAGON_DEVICES"]
|
||||
|
||||
# Set OpenCL device (unless overridden by --cl-device)
|
||||
final_cl_device = args.cl_device if args.cl_device is not None else cl_device
|
||||
if final_cl_device:
|
||||
env_vars["GGML_OPENCL_DEVICE"] = final_cl_device
|
||||
elif "GGML_OPENCL_DEVICE" in os.environ:
|
||||
env_vars["GGML_OPENCL_DEVICE"] = os.environ["GGML_OPENCL_DEVICE"]
|
||||
|
||||
# Map shared & backend-specific parameters with correct overrides
|
||||
|
||||
# Verbose logging mapping
|
||||
hex_verbose_val = args.hex_verbose if args.hex_verbose is not None else args.verbose
|
||||
set_env("GGML_HEXAGON_VERBOSE", hex_verbose_val)
|
||||
|
||||
cl_cache_debug_val = args.cl_cache_debug if args.cl_cache_debug is not None else args.verbose
|
||||
set_env("GGML_OPENCL_KERNEL_CACHE_DEBUG", cl_cache_debug_val)
|
||||
|
||||
# Profiling mapping
|
||||
hex_profile_val = args.hex_profile if args.hex_profile is not None else args.profile
|
||||
set_env("GGML_HEXAGON_PROFILE", hex_profile_val)
|
||||
|
||||
if args.cl_fa_tune or args.profile is not None:
|
||||
env_vars["GGML_OPENCL_FA_TUNE"] = "1"
|
||||
elif "GGML_OPENCL_FA_TUNE" in os.environ:
|
||||
env_vars["GGML_OPENCL_FA_TUNE"] = os.environ["GGML_OPENCL_FA_TUNE"]
|
||||
|
||||
# Other Hexagon environment variables
|
||||
set_env("GGML_HEXAGON_NHVX", args.hex_nhvx)
|
||||
set_env("GGML_HEXAGON_NHMX", args.hex_nhmx)
|
||||
set_env("GGML_HEXAGON_HOSTBUF", args.hex_hostbuf)
|
||||
set_env("GGML_HEXAGON_OPBATCH", args.hex_opbatch)
|
||||
set_env("GGML_HEXAGON_OPQUEUE", args.hex_opqueue)
|
||||
set_env("GGML_HEXAGON_OPPOLL", args.hex_oppoll)
|
||||
set_env("GGML_HEXAGON_OPFILTER", args.hex_opfilter)
|
||||
set_env("GGML_HEXAGON_OPFUSION", args.hex_opfusion)
|
||||
set_env("GGML_HEXAGON_VMEM", args.hex_vmem)
|
||||
set_env("GGML_HEXAGON_MBUF", args.hex_mbuf)
|
||||
set_env("GGML_HEXAGON_MM_SELECT", args.hex_mm_select)
|
||||
set_env("GGML_HEXAGON_FA_SELECT", args.hex_fa_select)
|
||||
set_env("GGML_HEXAGON_AR_SELECT", args.hex_ar_select)
|
||||
set_env("GGML_HEXAGON_ETM", args.hex_etm)
|
||||
set_env("GGML_HEXAGON_ARCH", args.hex_arch)
|
||||
set_env("GGML_HEXAGON_OPTRACE", args.hex_optrace)
|
||||
set_env("MTMD_BACKEND_DEVICE", args.mtmd_device)
|
||||
|
||||
# OpenCL environment variables
|
||||
set_env("GGML_OPENCL_PLATFORM", args.cl_platform)
|
||||
set_env("GGML_OPENCL_OPFILTER", args.cl_opfilter)
|
||||
set_env("GGML_OPENCL_KERNEL_CACHE_DIR", args.cl_cache_dir)
|
||||
|
||||
if args.cl_disable_fusion:
|
||||
env_vars["GGML_OPENCL_DISABLE_FUSION"] = "1"
|
||||
elif "GGML_OPENCL_DISABLE_FUSION" in os.environ:
|
||||
env_vars["GGML_OPENCL_DISABLE_FUSION"] = os.environ["GGML_OPENCL_DISABLE_FUSION"]
|
||||
|
||||
if args.cl_adreno_xmem:
|
||||
env_vars["GGML_OPENCL_ADRENO_XMEM_GEMM"] = "1"
|
||||
elif "GGML_OPENCL_ADRENO_XMEM_GEMM" in os.environ:
|
||||
env_vars["GGML_OPENCL_ADRENO_XMEM_GEMM"] = os.environ["GGML_OPENCL_ADRENO_XMEM_GEMM"]
|
||||
|
||||
if args.cl_adreno_large_buffer:
|
||||
env_vars["GGML_OPENCL_ADRENO_USE_LARGE_BUFFER"] = "1"
|
||||
elif "GGML_OPENCL_ADRENO_USE_LARGE_BUFFER" in os.environ:
|
||||
env_vars["GGML_OPENCL_ADRENO_USE_LARGE_BUFFER"] = os.environ["GGML_OPENCL_ADRENO_USE_LARGE_BUFFER"]
|
||||
|
||||
if args.sched_debug:
|
||||
env_vars["GGML_SCHED_DEBUG"] = "2"
|
||||
|
||||
# Resolve executable path
|
||||
executable = cmd_args[0]
|
||||
known_binaries = ["llama-cli", "llama-bench", "llama-completion", "llama-mtmd-cli", "test-backend-ops"]
|
||||
if executable in known_binaries:
|
||||
if target_type in ("android", "linux"):
|
||||
resolved_exec = f"./bin/{executable}"
|
||||
else:
|
||||
if platform.system() == "Windows":
|
||||
resolved_exec = os.path.normpath(os.path.join(package_path, "bin", f"{executable}.exe"))
|
||||
else:
|
||||
resolved_exec = os.path.normpath(os.path.join(package_path, "bin", executable))
|
||||
cmd_args[0] = resolved_exec
|
||||
|
||||
# Infer device string to pass to the tool
|
||||
basename = os.path.basename(executable)
|
||||
if basename.endswith(".exe"):
|
||||
basename = basename[:-4]
|
||||
|
||||
device_val = None
|
||||
if basename == "test-backend-ops":
|
||||
for i in range(len(cmd_args)):
|
||||
if cmd_args[i] in ("-p", "--params") and i + 1 < len(cmd_args):
|
||||
val = cmd_args[i + 1]
|
||||
new_val = ""
|
||||
for j, char in enumerate(val):
|
||||
if char in ('[', ']'):
|
||||
if j > 0 and val[j - 1] == '\\':
|
||||
new_val += char
|
||||
else:
|
||||
new_val += '\\' + char
|
||||
else:
|
||||
new_val += char
|
||||
cmd_args[i + 1] = new_val
|
||||
|
||||
has_b = any(arg == "-b" for arg in cmd_args)
|
||||
if not has_b:
|
||||
if args.devices:
|
||||
if args.devices.isdigit():
|
||||
n = int(args.devices)
|
||||
device_val = ",".join(f"HTP{i}" for i in range(n))
|
||||
else:
|
||||
device_val = args.devices
|
||||
elif "D" in os.environ:
|
||||
device_val = os.environ["D"]
|
||||
elif "DEVICE" in os.environ:
|
||||
device_val = os.environ["DEVICE"]
|
||||
else:
|
||||
device_val = "HTP0"
|
||||
if device_val:
|
||||
cmd_args += ["-b", device_val]
|
||||
else:
|
||||
has_device = any(arg.startswith("--device") for arg in cmd_args)
|
||||
if not has_device:
|
||||
if args.devices:
|
||||
if args.devices.isdigit():
|
||||
n = int(args.devices)
|
||||
device_val = ",".join(f"HTP{i}" for i in range(n))
|
||||
else:
|
||||
device_val = args.devices
|
||||
elif "D" in os.environ:
|
||||
device_val = os.environ["D"]
|
||||
elif "DEVICE" in os.environ:
|
||||
device_val = os.environ["DEVICE"]
|
||||
else:
|
||||
device_val = "HTP0"
|
||||
if device_val:
|
||||
cmd_args += ["--device", device_val]
|
||||
|
||||
# Automatically add -v to known llama tools if sched-debug, verbose, or profile are set
|
||||
verbose_trigger = (
|
||||
args.sched_debug
|
||||
or args.verbose is not None
|
||||
or args.profile is not None
|
||||
or args.hex_verbose is not None
|
||||
or args.hex_profile is not None
|
||||
or args.hex_optrace is not None
|
||||
)
|
||||
if verbose_trigger and basename in ("llama-cli", "llama-completion", "llama-bench", "llama-server", "llama-mtmd-cli"):
|
||||
if "-v" not in cmd_args and "--verbose" not in cmd_args:
|
||||
cmd_args.append("-v")
|
||||
|
||||
# Inject defaults for llama-cli, llama-completion, and llama-server if not overridden by the user
|
||||
if basename in ("llama-cli", "llama-completion", "llama-server"):
|
||||
if "-ngl" not in cmd_args and "--n-gpu-layers" not in cmd_args:
|
||||
cmd_args += ["-ngl", "99"]
|
||||
if "--ubatch-size" not in cmd_args and "-ub" not in cmd_args:
|
||||
cmd_args += ["--ubatch-size", "1024"]
|
||||
if "-fa" not in cmd_args and "--flash-attn" not in cmd_args:
|
||||
cmd_args += ["-fa", "on"]
|
||||
|
||||
if basename in ("llama-cli", "llama-completion", "llama-server", "llama-bench"):
|
||||
if "-t" not in cmd_args and "--threads" not in cmd_args:
|
||||
cmd_args += ["-t", "6"]
|
||||
|
||||
# Resolve target directory on device
|
||||
target_dir = args.target_dir
|
||||
if not target_dir:
|
||||
target_dir = "/data/local/tmp/llama.cpp" if target_type == "android" else "~/llama.cpp"
|
||||
|
||||
if target_type == "android":
|
||||
# Run via ADB
|
||||
adb_base = ["adb"]
|
||||
if target_val: # serial
|
||||
adb_base += ["-s", target_val]
|
||||
|
||||
env_parts = [
|
||||
"LD_LIBRARY_PATH=./lib",
|
||||
"ADSP_LIBRARY_PATH=./lib"
|
||||
]
|
||||
for k, v in env_vars.items():
|
||||
env_parts.append(f"{k}={v}")
|
||||
env_str = " ".join(env_parts)
|
||||
|
||||
cmd_str = shlex_join(cmd_args)
|
||||
adb_shell_cmd = f"cd {target_dir} && ulimit -c unlimited && {env_str} {cmd_str}"
|
||||
full_cmd = adb_base + ["shell", adb_shell_cmd]
|
||||
|
||||
logger.info(f"+ {' '.join(full_cmd)}")
|
||||
res = subprocess.run(full_cmd)
|
||||
sys.exit(res.returncode)
|
||||
|
||||
elif target_type == "linux":
|
||||
ssh_host = target_val
|
||||
if not ssh_host:
|
||||
logger.error("Error: SSH host not specified in target (e.g. use linux:user@host, lnx:user@host, or ubuntu:user@host). Cannot execute.")
|
||||
sys.exit(1)
|
||||
|
||||
# Linux remote run via SSH
|
||||
env_parts = [
|
||||
"LD_LIBRARY_PATH=./lib",
|
||||
"ADSP_LIBRARY_PATH=./lib"
|
||||
]
|
||||
for k, v in env_vars.items():
|
||||
env_parts.append(f"{k}={v}")
|
||||
env_str = " ".join(env_parts)
|
||||
|
||||
cmd_str = shlex_join(cmd_args)
|
||||
ssh_shell_cmd = f"cd {target_dir} && ulimit -c unlimited && {env_str} {cmd_str}"
|
||||
full_cmd = ["ssh", ssh_host, ssh_shell_cmd]
|
||||
|
||||
logger.info(f"+ {' '.join(full_cmd)}")
|
||||
res = subprocess.run(full_cmd)
|
||||
sys.exit(res.returncode)
|
||||
|
||||
elif target_type == "windows":
|
||||
logger.info("Windows target execution is currently a stub.")
|
||||
sys.exit(0)
|
||||
|
||||
else:
|
||||
# Run locally
|
||||
local_env = os.environ.copy()
|
||||
lib_dir = os.path.normpath(os.path.join(package_path, "lib"))
|
||||
local_env["ADSP_LIBRARY_PATH"] = lib_dir
|
||||
if platform.system() == "Windows":
|
||||
local_env["PATH"] = lib_dir + os.path.pathsep + local_env.get("PATH", "")
|
||||
else:
|
||||
local_env["LD_LIBRARY_PATH"] = lib_dir + os.path.pathsep + local_env.get("LD_LIBRARY_PATH", "")
|
||||
|
||||
for k, v in env_vars.items():
|
||||
local_env[k] = v
|
||||
|
||||
logger.info(f"+ {shlex_join(cmd_args)}")
|
||||
res = subprocess.run(cmd_args, env=local_env)
|
||||
sys.exit(res.returncode)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
try:
|
||||
main()
|
||||
except KeyboardInterrupt:
|
||||
logger.info("\nInterrupted by user.")
|
||||
sys.exit(130)
|
||||
@@ -1,48 +0,0 @@
|
||||
|
||||
#!/usr/bin/env pwsh
|
||||
|
||||
# Basedir on device
|
||||
$basedir=".\pkg-snapdragon"
|
||||
|
||||
$cli_opts=$args
|
||||
|
||||
$model="Llama-3.2-3B-Instruct-Q4_0.gguf"
|
||||
if ($null -ne $env:M) {
|
||||
$model=$env:M
|
||||
}
|
||||
|
||||
$device="HTP0"
|
||||
if ($null -ne $env:D) {
|
||||
$device=$env:D
|
||||
}
|
||||
|
||||
if ($null -ne $env:V) {
|
||||
$env:GGML_HEXAGON_VERBOSE=$env:V
|
||||
}
|
||||
|
||||
if ($null -ne $env:PROF) {
|
||||
$env:GGML_HEXAGON_PROFILE=$env:PROF
|
||||
}
|
||||
|
||||
if ($null -ne $env:OPSTAGE) {
|
||||
$env:GGML_HEXAGON_OPSTAGE=$env:OPSTAGE
|
||||
}
|
||||
|
||||
if ($null -ne $env:NHVX) {
|
||||
$env:GGML_HEXAGON_NHVX=$env:NHVX
|
||||
}
|
||||
|
||||
if ($null -ne $env:NDEV) {
|
||||
$env:GGML_HEXAGON_NDEV=$env:NDEV
|
||||
}
|
||||
|
||||
if ($null -ne $env:HB) {
|
||||
$env:GGML_HEXAGON_HOSTBUF=$env:HB
|
||||
}
|
||||
|
||||
$env:ADSP_LIBRARY_PATH="$basedir\lib"
|
||||
|
||||
& "$basedir\bin\llama-bench.exe" `
|
||||
--load-mode none -m $basedir\..\..\gguf\$model `
|
||||
--poll 1000 -t 6 --cpu-mask 0xfc --cpu-strict 1 `
|
||||
--ubatch-size 1024 -ngl 99 --device $device $cli_opts
|
||||
@@ -1,53 +0,0 @@
|
||||
|
||||
#!/usr/bin/env pwsh
|
||||
|
||||
# Basedir on device
|
||||
$basedir=".\pkg-snapdragon"
|
||||
|
||||
$cli_opts=$args
|
||||
|
||||
$model="Llama-3.2-3B-Instruct-Q4_0.gguf"
|
||||
if ($null -ne $env:M) {
|
||||
$model=$env:M
|
||||
}
|
||||
|
||||
$device="HTP0"
|
||||
if ($null -ne $env:D) {
|
||||
$device=$env:D
|
||||
}
|
||||
|
||||
if ($null -ne $env:V) {
|
||||
$env:GGML_HEXAGON_VERBOSE=$env:V
|
||||
}
|
||||
|
||||
if ($null -ne $env:SCHED) {
|
||||
$env:GGML_SCHED_DEBUG=$env:SCHED; $cli_opts="$cli_opts -v"
|
||||
}
|
||||
|
||||
if ($null -ne $env:PROF) {
|
||||
$env:GGML_HEXAGON_PROFILE=$env:PROF
|
||||
}
|
||||
|
||||
if ($null -ne $env:OPSTAGE) {
|
||||
$env:GGML_HEXAGON_OPSTAGE=$env:OPSTAGE
|
||||
}
|
||||
|
||||
if ($null -ne $env:NHVX) {
|
||||
$env:GGML_HEXAGON_NHVX=$env:NHVX
|
||||
}
|
||||
|
||||
if ($null -ne $env:NDEV) {
|
||||
$env:GGML_HEXAGON_NDEV=$env:NDEV
|
||||
}
|
||||
|
||||
if ($null -ne $env:HB) {
|
||||
$env:GGML_HEXAGON_HOSTBUF=$env:HB
|
||||
}
|
||||
|
||||
$env:ADSP_LIBRARY_PATH="$basedir\lib"
|
||||
|
||||
& "$basedir\bin\llama-cli.exe" `
|
||||
--load-mode none -m $basedir\..\..\gguf\$model `
|
||||
--poll 1000 -t 6 --cpu-mask 0xfc --cpu-strict 1 `
|
||||
--ctx-size 8192 --ubatch-size 1024 -fa on `
|
||||
-ngl 99 --device $device $cli_opts
|
||||
@@ -1,53 +0,0 @@
|
||||
|
||||
#!/usr/bin/env pwsh
|
||||
|
||||
# Basedir on device
|
||||
$basedir=".\pkg-snapdragon"
|
||||
|
||||
$cli_opts=$args
|
||||
|
||||
$model="Llama-3.2-3B-Instruct-Q4_0.gguf"
|
||||
if ($null -ne $env:M) {
|
||||
$model=$env:M
|
||||
}
|
||||
|
||||
$device="HTP0"
|
||||
if ($null -ne $env:D) {
|
||||
$device=$env:D
|
||||
}
|
||||
|
||||
if ($null -ne $env:V) {
|
||||
$env:GGML_HEXAGON_VERBOSE=$env:V
|
||||
}
|
||||
|
||||
if ($null -ne $env:SCHED) {
|
||||
$env:GGML_SCHED_DEBUG=$env:SCHED; $cli_opts="$cli_opts -v"
|
||||
}
|
||||
|
||||
if ($null -ne $env:PROF) {
|
||||
$env:GGML_HEXAGON_PROFILE=$env:PROF
|
||||
}
|
||||
|
||||
if ($null -ne $env:OPSTAGE) {
|
||||
$env:GGML_HEXAGON_OPSTAGE=$env:OPSTAGE
|
||||
}
|
||||
|
||||
if ($null -ne $env:NHVX) {
|
||||
$env:GGML_HEXAGON_NHVX=$env:NHVX
|
||||
}
|
||||
|
||||
if ($null -ne $env:NDEV) {
|
||||
$env:GGML_HEXAGON_NDEV=$env:NDEV
|
||||
}
|
||||
|
||||
if ($null -ne $env:HB) {
|
||||
$env:GGML_HEXAGON_HOSTBUF=$env:HB
|
||||
}
|
||||
|
||||
$env:ADSP_LIBRARY_PATH="$basedir\lib"
|
||||
|
||||
& "$basedir\bin\llama-completion.exe" `
|
||||
--load-mode none -m $basedir\..\..\gguf\$model `
|
||||
--poll 1000 -t 6 --cpu-mask 0xfc --cpu-strict 1 `
|
||||
--ctx-size 8192 --ubatch-size 1024 -fa on `
|
||||
-ngl 99 -no-cnv --device $device $cli_opts
|
||||
@@ -1,68 +0,0 @@
|
||||
#!/usr/bin/env pwsh
|
||||
|
||||
# Basedir on device
|
||||
$basedir=".\pkg-snapdragon"
|
||||
|
||||
$cli_opts=$args
|
||||
|
||||
$model="gemma-3-4b-it-Q4_0.gguf"
|
||||
if ($null -ne $env:M) {
|
||||
$model=$env:M
|
||||
}
|
||||
|
||||
$mmproj="mmproj-F16.gguf"
|
||||
if ($null -ne $env:MMPROJ) {
|
||||
$mmproj=$env:MMPROJ
|
||||
}
|
||||
|
||||
$image=""
|
||||
if ($null -ne $env:IMG) {
|
||||
$image=$env:IMG
|
||||
}
|
||||
|
||||
$device="HTP0"
|
||||
if ($null -ne $env:D) {
|
||||
$device=$env:D
|
||||
}
|
||||
|
||||
if ($null -ne $env:V) {
|
||||
$env:GGML_HEXAGON_VERBOSE=$env:V
|
||||
}
|
||||
|
||||
if ($null -ne $env:SCHED) {
|
||||
$env:GGML_SCHED_DEBUG=$env:SCHED; $cli_opts="$cli_opts -v"
|
||||
}
|
||||
|
||||
if ($null -ne $env:PROF) {
|
||||
$env:GGML_HEXAGON_PROFILE=$env:PROF
|
||||
}
|
||||
|
||||
if ($null -ne $env:OPSTAGE) {
|
||||
$env:GGML_HEXAGON_OPSTAGE=$env:OPSTAGE
|
||||
}
|
||||
|
||||
if ($null -ne $env:NHVX) {
|
||||
$env:GGML_HEXAGON_NHVX=$env:NHVX
|
||||
}
|
||||
|
||||
if ($null -ne $env:NDEV) {
|
||||
$env:GGML_HEXAGON_NDEV=$env:NDEV
|
||||
}
|
||||
|
||||
if ($null -ne $env:HB) {
|
||||
$env:GGML_HEXAGON_HOSTBUF=$env:HB
|
||||
}
|
||||
|
||||
if ($null -ne $env:MTMD_DEVICE) {
|
||||
$env:MTMD_BACKEND_DEVICE=$env:MTMD_DEVICE
|
||||
}
|
||||
|
||||
$env:ADSP_LIBRARY_PATH="$basedir\lib"
|
||||
|
||||
& "$basedir\bin\llama-mtmd-cli.exe" `
|
||||
--load-mode none -m $basedir\..\..\gguf\$model `
|
||||
--mmproj $basedir\..\..\gguf\$mmproj `
|
||||
--image $basedir\..\..\gguf\$image `
|
||||
--poll 1000 -t 6 --cpu-mask 0xfc --cpu-strict 1 `
|
||||
--ctx-size 8192 --ubatch-size 1024 -fa on `
|
||||
-ngl 99 --device $device -v $cli_opts
|
||||
@@ -1,56 +0,0 @@
|
||||
|
||||
#!/usr/bin/env pwsh
|
||||
|
||||
# Basedir on device
|
||||
$basedir=".\pkg-snapdragon"
|
||||
|
||||
if ($args.Count -eq 0) {
|
||||
Write-Host "No arguments provided.Expected the tool and argument to run."
|
||||
exit -1
|
||||
}
|
||||
|
||||
$tool=$args[0]
|
||||
$cli_opts=@()
|
||||
|
||||
if ($args.Count -gt 1) {
|
||||
$cli_opts=$args[1..($args.Count - 1)]
|
||||
$remainingArgs = $args[1..($args.Count - 1)]
|
||||
}
|
||||
|
||||
$device="HTP0"
|
||||
if ($null -ne $env:D) {
|
||||
$device=$env:D
|
||||
}
|
||||
|
||||
if ($null -ne $env:V) {
|
||||
$env:GGML_HEXAGON_VERBOSE=$env:V
|
||||
}
|
||||
|
||||
if ($null -ne $env:SCHED) {
|
||||
$env:GGML_SCHED_DEBUG=$env:SCHED; $cli_opts="$cli_opts -v"
|
||||
}
|
||||
|
||||
if ($null -ne $env:PROF) {
|
||||
$env:GGML_HEXAGON_PROFILE=$env:PROF
|
||||
}
|
||||
|
||||
if ($null -ne $env:OPSTAGE) {
|
||||
$env:GGML_HEXAGON_OPSTAGE=$env:OPSTAGE
|
||||
}
|
||||
|
||||
if ($null -ne $env:NHVX) {
|
||||
$env:GGML_HEXAGON_NHVX=$env:NHVX
|
||||
}
|
||||
|
||||
if ($null -ne $env:NDEV) {
|
||||
$env:GGML_HEXAGON_NDEV=$env:NDEV
|
||||
}
|
||||
|
||||
if ($null -ne $env:HB) {
|
||||
$env:GGML_HEXAGON_HOSTBUF=$env:HB
|
||||
}
|
||||
|
||||
$env:ADSP_LIBRARY_PATH="$basedir\lib"
|
||||
|
||||
& "$basedir\bin\$tool" `
|
||||
$cli_opts
|
||||
@@ -1,105 +0,0 @@
|
||||
# Requires Run as Administrator is NOT strictly necessary for User-scope env vars,
|
||||
# but recommended for creating directories in C:\ root if permissions are restricted.
|
||||
|
||||
$ErrorActionPreference = "Stop"
|
||||
|
||||
# --- Configuration ---
|
||||
$BaseDir = "C:\Qualcomm"
|
||||
|
||||
# SDK 1: Hexagon
|
||||
$HexagonUrl = "https://github.com/snapdragon-toolchain/hexagon-sdk/releases/download/v6.6.0.0/hexagon-sdk-v6.6.0.0-arm64-wos.tar.xz"
|
||||
$HexagonParent = Join-Path $BaseDir "Hexagon_SDK"
|
||||
$HexagonSdkVersion = "6.6.0.0"
|
||||
$HexagonToolsVersion = "19.0.07"
|
||||
$HexagonSdkTarget = Join-Path $HexagonParent $HexagonSdkVersion
|
||||
$HexagonToolsTarget = Join-Path $HexagonSdkTarget "\tools\HEXAGON_Tools\$HexagonToolsVersion"
|
||||
|
||||
# SDK 2: OpenCL
|
||||
$OpenCLUrl = "https://github.com/snapdragon-toolchain/opencl-sdk/releases/download/v2.3.2/adreno-opencl-sdk-v2.3.2-arm64-wos.tar.xz"
|
||||
$OpenCLParent = Join-Path $BaseDir "OpenCL_SDK"
|
||||
$OpenCLVersion = "2.3.2"
|
||||
$OpenCLTarget = Join-Path $OpenCLParent $OpenCLVersion
|
||||
|
||||
# --- Helper Function ---
|
||||
function Install-QualcommSDK {
|
||||
param (
|
||||
[string]$Url,
|
||||
[string]$ParentDir,
|
||||
[string]$TargetDir,
|
||||
[string]$Name
|
||||
)
|
||||
|
||||
# 1. Create Parent Directory
|
||||
if (-not (Test-Path -Path $ParentDir)) {
|
||||
Write-Host "Creating directory: $ParentDir" -ForegroundColor Cyan
|
||||
New-Item -Path $ParentDir -ItemType Directory -Force | Out-Null
|
||||
}
|
||||
|
||||
# 2. Check for Specific Version Directory
|
||||
if (Test-Path -Path $TargetDir) {
|
||||
Write-Host "$Name ($TargetDir) already exists. Skipping download." -ForegroundColor Green
|
||||
}
|
||||
else {
|
||||
Write-Host "$Name not found. preparing to download..." -ForegroundColor Yellow
|
||||
|
||||
# Create the target directory to extract into
|
||||
New-Item -Path $TargetDir -ItemType Directory -Force | Out-Null
|
||||
|
||||
# Define temporary archive path
|
||||
$TempFile = Join-Path $ParentDir "temp_sdk.tar.xz"
|
||||
|
||||
try {
|
||||
# Download
|
||||
Write-Host "Downloading from: $Url"
|
||||
Invoke-WebRequest -Uri $Url -OutFile $TempFile
|
||||
|
||||
# Untar
|
||||
# Note: We assume Windows includes tar.exe (Win 10 build 17063+)
|
||||
Write-Host "Extracting archive to $TargetDir..."
|
||||
|
||||
# We use -C to extract contents INTO the target directory created above
|
||||
tar -xJvf $TempFile -C $TargetDir\..
|
||||
|
||||
Write-Host "Extraction complete." -ForegroundColor Green
|
||||
}
|
||||
catch {
|
||||
Write-Error "Failed to download or extract $Name. Error: $_"
|
||||
# Cleanup target dir if failed so script tries again next time
|
||||
Remove-Item -Path $TargetDir -Recurse -Force -ErrorAction SilentlyContinue
|
||||
}
|
||||
finally {
|
||||
# Cleanup Archive
|
||||
if (Test-Path $TempFile) { Remove-Item $TempFile -Force }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# --- Execution ---
|
||||
|
||||
# 1. Ensure Base C:\Qualcomm exists
|
||||
if (-not (Test-Path $BaseDir)) {
|
||||
New-Item -Path $BaseDir -ItemType Directory -Force | Out-Null
|
||||
}
|
||||
|
||||
# 2. Run Install Logic
|
||||
Install-QualcommSDK -Url $HexagonUrl -ParentDir $HexagonParent -TargetDir $HexagonSdkTarget -Name "Hexagon SDK"
|
||||
Install-QualcommSDK -Url $OpenCLUrl -ParentDir $OpenCLParent -TargetDir $OpenCLTarget -Name "OpenCL SDK"
|
||||
|
||||
# --- Environment Variables ---
|
||||
|
||||
Write-Host "`nSetting Environment Variables..." -ForegroundColor Cyan
|
||||
|
||||
# Set OPENCL_SDK_ROOT
|
||||
[System.Environment]::SetEnvironmentVariable('OPENCL_SDK_ROOT', $OpenCLTarget, [System.EnvironmentVariableTarget]::User)
|
||||
$env:OPENCL_SDK_ROOT = $OpenCLTarget # Set for current session as well
|
||||
Write-Host "OPENCL_SDK_ROOT set to: $OpenCLTarget"
|
||||
|
||||
# Set HEXAGON_SDK_ROOT
|
||||
[System.Environment]::SetEnvironmentVariable('HEXAGON_SDK_ROOT', $HexagonSdkTarget, [System.EnvironmentVariableTarget]::User)
|
||||
$env:HEXAGON_SDK_ROOT = $HexagonSdkTarget # Set for current session as well
|
||||
Write-Host "HEXAGON_SDK_ROOT set to: $HexagonSdkTarget"
|
||||
|
||||
# Set HEXAGON_SDK_ROOT
|
||||
[System.Environment]::SetEnvironmentVariable('HEXAGON_TOOLS_ROOT', $HexagonToolsTarget, [System.EnvironmentVariableTarget]::User)
|
||||
$env:HEXAGON_TOOLS_ROOT = $HexagonToolsTarget # Set for current session as well
|
||||
Write-Host "HEXAGON_TOOLS_ROOT set to: $HexagonToolsTarget"
|
||||
Reference in New Issue
Block a user