From 0037509be2cf9616351d8ccf156e39e2ea9f58f6 Mon Sep 17 00:00:00 2001 From: hualxie Date: Tue, 21 Jul 2026 10:32:50 +0800 Subject: [PATCH 01/11] remove require_utilization; we should fix --- tests/e2e/test_perf_e2e.py | 32 ++++++++++---------------------- 1 file changed, 10 insertions(+), 22 deletions(-) diff --git a/tests/e2e/test_perf_e2e.py b/tests/e2e/test_perf_e2e.py index 758ae690a..d2c6cdd69 100644 --- a/tests/e2e/test_perf_e2e.py +++ b/tests/e2e/test_perf_e2e.py @@ -77,9 +77,7 @@ def _require_npu() -> None: pytest.skip("No NPU detected via PDH") -def _assert_hw_monitor_section( - data: dict, device_kind: str, *, require_utilization: bool = True -) -> None: +def _assert_hw_monitor_section(data: dict, device_kind: str) -> None: """Assert the ``hw_monitor`` section is present and well-formed. Checks the section emitted by HWMonitor when --monitor is passed: @@ -95,8 +93,7 @@ def _assert_hw_monitor_section( else: assert hw["device_kind"] == device_kind assert hw["adapter_luid"] is not None - if require_utilization: - assert hw[device_kind]["mean_pct"] > 0 + assert hw[device_kind]["mean_pct"] > 0 def _build_perf_args( @@ -153,7 +150,6 @@ def _assert_monitor_result( device: str, device_kind: str | None = None, ep: str | None = None, - require_utilization: bool = True, ) -> None: """Assert a monitored perf run produced the expected device + hw_monitor data. @@ -161,7 +157,7 @@ def _assert_monitor_result( measured, and delegates the hw_monitor checks to :func:`_assert_hw_monitor_section`. ``device_kind`` defaults to ``device`` when not given (only differs for cases like VitisAI where ``--device`` and - the monitored hardware diverge). ``require_utilization`` is forwarded. + the monitored hardware diverge). """ if device_kind is None: device_kind = device @@ -169,7 +165,7 @@ def _assert_monitor_result( assert data["latency_ms"]["mean"] > 0 if ep is not None: assert data["benchmark_info"]["ep"] == ep - _assert_hw_monitor_section(data, device_kind, require_utilization=require_utilization) + _assert_hw_monitor_section(data, device_kind) # =========================================================================== @@ -424,8 +420,7 @@ def test_benchmark_gpu_monitor(self, tmp_path: Path, model_arg: str): assert output_file.exists(), f"Output file not created: {output_file}" data = json.loads(output_file.read_text()) - # Tiny synthetic fixture: below PDH utilization-publish floor. - _assert_monitor_result(data, device="gpu", require_utilization=False) + _assert_monitor_result(data, device="gpu") def test_benchmark_npu_monitor(self, tmp_path: Path, model_arg: str): """Benchmark on NPU with --monitor. @@ -450,8 +445,7 @@ def test_benchmark_npu_monitor(self, tmp_path: Path, model_arg: str): assert output_file.exists(), f"Output file not created: {output_file}" data = json.loads(output_file.read_text()) - # Tiny synthetic fixture: below PDH utilization-publish floor. - _assert_monitor_result(data, device="npu", require_utilization=False) + _assert_monitor_result(data, device="npu") def test_benchmark_auto(self, tmp_path: Path, model_arg: str): """Benchmark with --device auto. @@ -551,8 +545,7 @@ def test_benchmark_ep_device_gpu(self, ep: str, tmp_path: Path, model_arg: str): assert output_file.exists() data = json.loads(output_file.read_text()) - # Tiny synthetic fixture: below PDH utilization-publish floor. - _assert_monitor_result(data, device="gpu", ep=EP_ALIASES[ep], require_utilization=False) + _assert_monitor_result(data, device="gpu", ep=EP_ALIASES[ep]) @pytest.mark.parametrize("ep", NPU_EPS) def test_benchmark_ep_device_npu(self, ep: str, tmp_path: Path, model_arg: str): @@ -578,8 +571,7 @@ def test_benchmark_ep_device_npu(self, ep: str, tmp_path: Path, model_arg: str): assert output_file.exists() data = json.loads(output_file.read_text()) - # Tiny synthetic fixture: below PDH utilization-publish floor. - _assert_monitor_result(data, device="npu", ep=EP_ALIASES[ep], require_utilization=False) + _assert_monitor_result(data, device="npu", ep=EP_ALIASES[ep]) # =========================================================================== @@ -647,9 +639,7 @@ def test_benchmark_ep_gpu(self, ep: str, tmp_path: Path, model_arg: str): assert output_file.exists() data = json.loads(output_file.read_text()) assert data["benchmark_info"]["ep"] == EP_ALIASES[ep] - # Not all EPs bump PDH GPU-engine counters (OpenVINO routes via its own - # compute path); validate structure only, not utilization magnitude. - _assert_monitor_result(data, device="gpu", require_utilization=False) + _assert_monitor_result(data, device="gpu") @pytest.mark.parametrize("ep", NPU_EPS) def test_benchmark_ep_npu(self, ep: str, tmp_path: Path, model_arg: str): @@ -673,9 +663,7 @@ def test_benchmark_ep_npu(self, ep: str, tmp_path: Path, model_arg: str): assert output_file.exists() data = json.loads(output_file.read_text()) assert data["benchmark_info"]["ep"] == EP_ALIASES[ep] - # Not all EPs bump PDH NPU-engine counters reliably for short runs; - # validate structure only, not utilization magnitude. - _assert_monitor_result(data, device="npu", require_utilization=False) + _assert_monitor_result(data, device="npu") # =========================================================================== From 7c3ed2372901195441b2d6b0b221cb0f8ab41476 Mon Sep 17 00:00:00 2001 From: hualxie Date: Tue, 21 Jul 2026 10:46:44 +0800 Subject: [PATCH 02/11] test(e2e): cover perf input options and op tracing --- tests/e2e/test_perf_e2e.py | 111 +++++++++++++++++++++++++++++++++++++ 1 file changed, 111 insertions(+) diff --git a/tests/e2e/test_perf_e2e.py b/tests/e2e/test_perf_e2e.py index d2c6cdd69..6a99f46a6 100644 --- a/tests/e2e/test_perf_e2e.py +++ b/tests/e2e/test_perf_e2e.py @@ -35,6 +35,7 @@ import sys from pathlib import Path +import numpy as np import onnx import pytest from click.testing import CliRunner @@ -107,6 +108,9 @@ def _build_perf_args( memory: bool | None = None, verbose: bool = False, no_skip_build: bool = False, + batch_size: int | None = None, + input_data: Path | None = None, + op_tracing: str | None = None, ) -> list[str]: """Build the argv list passed to the perf CLI. @@ -141,6 +145,12 @@ def _build_perf_args( args.append("--verbose") if no_skip_build: args.append("--no-skip-build") + if batch_size is not None: + args += ["--batch-size", str(batch_size)] + if input_data is not None: + args += ["--input-data", str(input_data)] + if op_tracing is not None: + args += ["--op-tracing", op_tracing] return args @@ -586,6 +596,107 @@ class TestPerfONNXDirect(_PerfBenchmarkSuite): def model_arg(self, onnx_model_path: Path) -> str: return str(onnx_model_path) + def test_batch_size_cpu(self, tmp_path: Path, onnx_model_path: Path): + """--batch-size applies to a model with a dynamic leading dimension.""" + model = onnx.load(onnx_model_path) + for value_info in (*model.graph.input, *model.graph.output): + dimensions = value_info.type.tensor_type.shape.dim + if dimensions: + dimensions[0].ClearField("dim_value") + dimensions[0].dim_param = "batch" + + dynamic_model_path = tmp_path / "dynamic_batch.onnx" + onnx.save(model, dynamic_model_path) + output_file = tmp_path / "perf_batch_size_cpu.json" + + result = CliRunner().invoke( + perf, + _build_perf_args( + model_arg=str(dynamic_model_path), + output_file=output_file, + device="cpu", + batch_size=4, + memory=False, + ), + obj={}, + catch_exceptions=False, + ) + + assert result.exit_code == 0, f"perf failed (exit {result.exit_code}):\n{result.output}" + data = json.loads(output_file.read_text()) + assert data["benchmark_info"]["batch_size"] == 4 + assert data["benchmark_info"]["effective_batch_size"] == 4 + assert all(shape[0] is None for shape in data["model_info"]["input_shapes"]) + assert data["throughput"]["samples_per_sec"] > 0 + + def test_input_data_cpu(self, tmp_path: Path, onnx_model_path: Path): + """--input-data benchmarks named tensors from an NPZ archive on CPU.""" + model = onnx.load(onnx_model_path) + initializer_names = {initializer.name for initializer in model.graph.initializer} + arrays = {} + for value_info in model.graph.input: + if value_info.name in initializer_names: + continue + tensor_type = value_info.type.tensor_type + shape = [dimension.dim_value or 1 for dimension in tensor_type.shape.dim] + dtype = onnx.helper.tensor_dtype_to_np_dtype(tensor_type.elem_type) + arrays[value_info.name] = np.ones(shape, dtype=dtype) + + input_file = tmp_path / "inputs.npz" + np.savez(input_file, **arrays) + output_file = tmp_path / "perf_input_data_cpu.json" + + result = CliRunner().invoke( + perf, + _build_perf_args( + model_arg=str(onnx_model_path), + output_file=output_file, + device="cpu", + input_data=input_file, + memory=False, + ), + obj={}, + catch_exceptions=False, + ) + + assert result.exit_code == 0, f"perf failed (exit {result.exit_code}):\n{result.output}" + data = json.loads(output_file.read_text()) + assert data["benchmark_info"]["device"] == "cpu" + effective_batch = next(iter(arrays.values())).shape[0] + assert data["benchmark_info"]["effective_batch_size"] == effective_batch + assert data["latency_ms"]["mean"] > 0 + + def test_op_tracing_basic_qnn_npu(self, tmp_path: Path, onnx_model_path: Path): + """--op-tracing basic produces a QNN NPU operator trace.""" + require_ep("qnn") + _require_npu() + output_file = tmp_path / "perf_op_tracing_qnn_npu.json" + trace_output = tmp_path / "perf_op_tracing_qnn_npu_op_trace.json" + + result = CliRunner().invoke( + perf, + _build_perf_args( + model_arg=str(onnx_model_path), + output_file=output_file, + device="npu", + ep="qnn", + op_tracing="basic", + memory=False, + ), + obj={}, + catch_exceptions=False, + ) + + assert result.exit_code == 0, f"perf failed (exit {result.exit_code}):\n{result.output}" + assert output_file.exists() + assert trace_output.exists() + trace = json.loads(trace_output.read_text()) + assert trace["metadata"]["device"] == "npu" + assert trace["metadata"]["ep"] == EP_ALIASES["qnn"] + assert trace["metadata"]["tracing_level"] == "basic" + assert trace["metadata"]["num_samples"] == 3 + assert trace["operators"] + class TestPerfHuggingFace: """Benchmark a HuggingFace model by loading it via the perf command.""" From 02337e3db3244f48d2c4abd85f95b02a8803ef2c Mon Sep 17 00:00:00 2001 From: hualxie Date: Tue, 21 Jul 2026 13:50:55 +0800 Subject: [PATCH 03/11] add iterations_overwrite --- tests/e2e/test_perf_e2e.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/tests/e2e/test_perf_e2e.py b/tests/e2e/test_perf_e2e.py index 6a99f46a6..8513265cf 100644 --- a/tests/e2e/test_perf_e2e.py +++ b/tests/e2e/test_perf_e2e.py @@ -111,6 +111,7 @@ def _build_perf_args( batch_size: int | None = None, input_data: Path | None = None, op_tracing: str | None = None, + iterations_overwrite: int | None = None ) -> list[str]: """Build the argv list passed to the perf CLI. @@ -118,7 +119,7 @@ def _build_perf_args( enough samples to observe utilization) and 3 otherwise (kept tiny for e2e speed). Warmup is always 1. """ - iterations = 300 if monitor else 3 + iterations = iterations_overwrite if iterations_overwrite is not None else (300 if monitor else 3) args: list[str] = [ "-m", model_arg, @@ -421,7 +422,7 @@ def test_benchmark_gpu_monitor(self, tmp_path: Path, model_arg: str): result = runner.invoke( perf, _build_perf_args( - model_arg=model_arg, output_file=output_file, device="gpu", monitor=True + model_arg=model_arg, output_file=output_file, device="gpu", monitor=True, iterations_overwrite=1000 ), obj={}, catch_exceptions=False, @@ -546,7 +547,7 @@ def test_benchmark_ep_device_gpu(self, ep: str, tmp_path: Path, model_arg: str): result = runner.invoke( perf, _build_perf_args( - model_arg=model_arg, output_file=output_file, device="gpu", ep=ep, monitor=True + model_arg=model_arg, output_file=output_file, device="gpu", ep=ep, monitor=True, iterations_overwrite=1000 ), obj={}, catch_exceptions=False, @@ -740,7 +741,7 @@ def test_benchmark_ep_gpu(self, ep: str, tmp_path: Path, model_arg: str): result = runner.invoke( perf, _build_perf_args( - model_arg=model_arg, output_file=output_file, device="gpu", ep=ep, monitor=True + model_arg=model_arg, output_file=output_file, device="gpu", ep=ep, monitor=True, iterations_overwrite=1000 ), obj={}, catch_exceptions=False, From e270d3f6e6438f83f2f23087279bebce573870c3 Mon Sep 17 00:00:00 2001 From: hualxie Date: Tue, 21 Jul 2026 14:50:37 +0800 Subject: [PATCH 04/11] add model --- tests/assets/model.onnx | Bin 0 -> 63972 bytes tests/assets/model.onnx.data | 1 + 2 files changed, 1 insertion(+) create mode 100644 tests/assets/model.onnx create mode 100644 tests/assets/model.onnx.data diff --git a/tests/assets/model.onnx b/tests/assets/model.onnx new file mode 100644 index 0000000000000000000000000000000000000000..ffbd13c5591abd20949fdb7693df422717aabc09 GIT binary patch literal 63972 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 C*59%K literal 0 HcmV?d00001 diff --git a/tests/assets/model.onnx.data b/tests/assets/model.onnx.data new file mode 100644 index 000000000..049f5063a --- /dev/null +++ b/tests/assets/model.onnx.data @@ -0,0 +1 @@ +zyKrZHG\kAؤp[}fz4Rc^{KV`5}UWxw|sgRefUR;ythcdiif}fYkbybJpXqlolzmfk_\yx:P4lugy9c`wgmfJR0erxD^`UGs^EhZ|XjoԖzTGsxhsz{pv}dŧOnaaC]`q]WC*~Pxljwo]KxfqiI1LqNLag\shwwAZ_O>~mgasi?wbDtAxCHpqgo{W{ngjFM]x^[ygPo\.tzXrvsqwt:kf|qxckiu|U}rr?_buyzY9Iv~fyY^Z`TPw]dgWzjOwPsEZo\p]Zgwsp@hrdteF傁B=xq\]}{`jYL]\lT|leޠa||%o}>5r~VvP`s`PyatϑG[gR~kh\3RaC*|b|VYE^8Zr~~"ytoplpsOxO~~YsPpvmZP\I~<};pvxuIRtzposSpzrch_zlq!oovho9qhm;wblxMn7mifpD{|]p\iQalt}xï[uwohHxjR\^(\bw,}HwojjK}|Pqߞd-kVabEƠϛh}tulo`LQ{}Qf{}iaUms:nCShpG=va7nL{sdwq\sefJuh{vp}фw~KSphU@w~ts~lWn{~IuyLLfks/qPobG|st_~||uJ,T?=dHm°^nftݨexRxHTm>zwyu`c{thqt]\fTY@LTpO'e\Hpyjgnx_q|bħoddƢp|fd~PVj~yMTgV~{q{RwrXxzxPtpwAzg_sNs]уWMIfJmRj`i\fSnk~kzitxMzlprm_dOzqZu]7eurpW&~ȑmvoDelsqJxvy|rJ`u7hS]xny_mNexhSHX]FiMxikjl]7FNc|ClQmAryrnIxQVdJvXi@JBydĺπ{tyߏt^\9Ktw?DY:nMG“jv?ƁV_uWnd%deweۉanWKyQ_h@:~ٍ~ShuIZUAnP}WrRyTffbwU[/tԓhs beZmqhzy}p5s?}XG4^Jo~ykqtvrtqB^imvaT=jYdW_nv7XzaaҬkxsZ{v[vKH`hx]h~>bmMp*y_leuygkBtf2{mgg:|qrv}rPvm6R^ms>k~mvnQzLH1ORj6n8fmX_xo{>Ӏ[tuZnkhk\h{hS+|z.HeH=q3]mSYjkjidc|_~uopx{mg@bscwҎUVH`UfOEV}bnrigAgd^PwyH`etj7{OzaxnkVgi_Z~uRK}^h7fTo_RyN^VNūݙSXcgN\tpS_jrȫT{]^wvmsݪWzaowujoGsMsuT`TjXYha[l}|qabvKXn~iK9jRnuxxrpoxe}o=j_lyXvQ7we>WXSuJ{cg~}}}:TP_xj*qStswnQqe}1wJɰbMto=g4Jy^ltQys\zy|ELIGcSRk{c̀$Yyz[lhKW{qC|dU{~eo(lgkgbSrī^^pnKYfSiG_,iWw^ie?usVc)⏄{x}8,cTgxfu{s\mk^r[l~olymzFi^tt{ObnhSIvylA^{PNJu;jk4n4H^S\rXvovqaZiUZAh}zolcgkCctSogՁbmʋslMC}XCVp1Z+bqju\oNSwchdGju\{pDFd}``in[On֡sw†rqxwz{ǧ:`KsvEWzoZNxiuvarRi`vScp\oqnğJtaQGCtJß|mF~xqyWa~ff4u]uedzVY^bk{nTOYrzQLJzXPa^qo]YOspm~OIQifL}Oi`0q`xh`T[|kbl}xsykfqoFb_lG}S|зqfixylxbvRi^bDr}ajj|rN`*y|gmlknQgRWEixBfslitraGSlXnpZeawi AJ6kVosU/tm^v{Nj4~`ޖZOêuYv1ucQG{^/L~jwqXwjssex|e5Y/kȥb7VUx}gHa|h|b40NBzpXq_s^~~Quqe$ro=αn[O]Tg`|ZvmaReeioL'\dyer{T>uwp^ge[ijfszVq{CqLskx`pJw}wSpJVOhobPKCu~Tlq{urQwkficc[{MkxbRdenHpweoJ'exsuyXFZhuyBJcU:xŖqiltƓXgXawDfpsE~xoYdmjM]nRKe[w4}.ًJSw]anto}déjEljxrT-O{\rBc{Z_|g{ pzPX|]va4NlM@N?f^JWZs~n\{Ke~YlC4{?}cw^a^MjZqj4_mbWea`|PpS:h{ZyQqm$؛^Jcc~mbixe|V]mzS>"fƒnsFyK{rTamkbpiukiE{dguwiňWGTbwqXaezWL|XSqQJxUC$pEqQG_wCkvW}Lm]l:PYwJ|ъW[\YdD]~Axp~j9yAId\N}`y=z@fcnn~RdxlyUmp}lx@HzoAm`gv@hm;cx_zaY?EG~bgfdnxi:aoAqOX6yZ~yrsalP\>'|Cis}xR[X@tp~vybpqbEo{@e[I]hzoui}Mf_zef{[NRodnvb_>Ԛhmc|NGtPbkdmzLst`ewjGKgqTt5Ba*x\zx{b@~s~NqKY`tANwЀI{MS_[gYG?qH|q٥^N}^ߜFTxZgmM~rqifZde^nQzthSVj{^R`nqlzOVřftZ6PWƕ^`|>l11R`r/wffs}2Ul`Zl8eV\}eyWCowi|od~cłb^FwRfvxOD;~RxrtNh~mZpuM{~@pvZgze}ktO}XRd9uXxtbAhd\h+Q{lOIVZFskCq>ubV­_YmHis`~ZU}W|c}|rokTFT-nmq{TfxHq}Hdr~^噏dpkomHln`MQwt`tXuOa|}h0F~iz_W6vkbu0iOqmfb_jtTz{ʇuaHzNzfvUO\ogromlb~pgxuah[t}vp&p@uwyTkt?iysfi~zpqŕtye`MZ`NH\`gu4UIxHX~yjRvpdxJaTqcvPTT?VlLjztkpbvv[ cXRoivrhXS`Yٷor|^wn{v}k`e1~ke\OqsNZu}trzRxejkt@z\wm>h~^{ws%q̟xr|U~zy@a~VUJ[zrbApbup{d`zyB]|:ky`{_CkĎaCUEuէHItn[LS|yx|uz^ZacNG{mi{a|zt`j`|iYG^Y{Zg(adPy_zmS@ZxT][ieueIY̜zdNjPW[`FA{mzMmwxiJNeo`|G~dmvinxo_LyŎk"y`T&JimBwr{Mt`hcsTzivIfpy~f,jvn`~`c|`zRM԰p0N^j4tvX]Ȅ}^Q;f`r[emtff|`6qsfrmso.L~svrrFq[tfwdfvk{fm3^~p|]Pmtqmtgy|qcflItdH&|{q~adz_qr|V}GZX+vZdu|okijkęr?ISYVzbf`j{WEiZeq9@vzҎ{kjwLwh{s>XwtqdN_uJzqu_im}nh¤}JRtO{en^txu|x<^vi_zvLv`cp^lk_)Osl{ǟ{i||]\]]KnlT`PY]qAa{]oxa~I}]vv_qmugtqTMJfd}l}se{D{LTLgICFlfdgɥxntM}~i`fmyNTdhjZh]kJtcwzlcvf7]l}VqbU^pk9zlis[^yih}oyЙIWb2yoOb~W}x{vPjqy{l3e_}zb4qx~zec>EGZmrqt]^coy9vmc;[CkU5{^Śf8ocj_kdUi~ĝCV^lhrɌؓRi\vAsm|=}xxY[IUwhqT:Bj6_|0}'Z_|?ԜukRq3w`hZM,w]}hF/#vjc`}膓eLFiJhntyRRJS>l6x˃[]w}}Zt^{e-m:rrk)~xfvc{{QlqfxN_OrTi}kQky_x~iv˄jjH{f-zv-y҉C䉾mRr]pAOxV5X:3RoE@lbwGN[BPrV{kPExlL`bp7t}ဤfN0~%ffyibR`[us_Ir>H[{Y4zêvsmMoixǨF~S+Zu%gSlthNjuqĵXmfaW{oBUXQmC5ĿV׉~i\rp֌mZrohAiwYMqRy$ypVRaPG9X\TŪ_hiрa1MQώ|ɷ|F;Tk^OoYicEMSwmIed Date: Tue, 21 Jul 2026 14:56:19 +0800 Subject: [PATCH 05/11] test(e2e): use real ONNX asset model for NPU perf tests The perf ONNX suite shared one generated float MatMul across CPU/GPU/NPU tests, which is not representative of NPU (QNN/VitisAI) execution. Add an npu_model_arg fixture that defaults to model_arg, override it in TestPerfONNXDirect to point at tests/assets/model.onnx, and route the NPU tests (memory, monitor, ep-device, op-tracing) through it. CPU/GPU tests keep the lightweight generated model. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- tests/e2e/test_perf_e2e.py | 67 ++++++++++++++++++++++++++++++-------- 1 file changed, 54 insertions(+), 13 deletions(-) diff --git a/tests/e2e/test_perf_e2e.py b/tests/e2e/test_perf_e2e.py index 8513265cf..a81e45d91 100644 --- a/tests/e2e/test_perf_e2e.py +++ b/tests/e2e/test_perf_e2e.py @@ -57,6 +57,11 @@ GPU_EPS = ("dml", "nv_tensorrt_rtx", "migraphx", "openvino", "qnn") NON_CPU_EPS = ("qnn", "vitisai", "dml", "nv_tensorrt_rtx", "migraphx") +# Real image-classification ONNX model shipped in the repo +# (``pixel_values`` [1, 224, 224, 3] -> ``logits`` [1, 3]). Used for NPU +# tests, where a bare float MatMul is not representative of NPU execution. +ASSETS_ONNX_MODEL = Path(__file__).resolve().parent.parent / "assets" / "model.onnx" + def _require_gpu() -> None: """Skip the current test unless a GPU is discoverable via PDH.""" @@ -119,7 +124,9 @@ def _build_perf_args( enough samples to observe utilization) and 3 otherwise (kept tiny for e2e speed). Warmup is always 1. """ - iterations = iterations_overwrite if iterations_overwrite is not None else (300 if monitor else 3) + iterations = ( + iterations_overwrite if iterations_overwrite is not None else (300 if monitor else 3) + ) args: list[str] = [ "-m", model_arg, @@ -191,6 +198,17 @@ class _PerfBenchmarkSuite: def model_arg(self) -> str: raise NotImplementedError("Subclasses must override model_arg fixture") + @pytest.fixture + def npu_model_arg(self, model_arg: str) -> str: + """Model source used by the NPU tests. + + Defaults to ``model_arg`` so subclasses share a single model. + Subclasses backed by a real ONNX file override this to point at a + model that actually executes on the NPU (QNN / VitisAI), where a + bare float MatMul is not representative. + """ + return model_arg + def test_benchmark_cpu(self, tmp_path: Path, model_arg: str): """Benchmark on CPU with minimal iterations. @@ -347,7 +365,7 @@ def test_benchmark_cpu_no_memory(self, tmp_path: Path, model_arg: str): # Console output should NOT contain Memory section assert "Memory:" not in result.output - def test_benchmark_npu_memory(self, tmp_path: Path, model_arg: str): + def test_benchmark_npu_memory(self, tmp_path: Path, npu_model_arg: str): """Benchmark on NPU with --memory produces VRAM fields. Verifies VRAM local/shared fields are present in JSON output. @@ -359,7 +377,7 @@ def test_benchmark_npu_memory(self, tmp_path: Path, model_arg: str): result = runner.invoke( perf, _build_perf_args( - model_arg=model_arg, output_file=output_file, device="npu", memory=True + model_arg=npu_model_arg, output_file=output_file, device="npu", memory=True ), obj={}, catch_exceptions=False, @@ -422,7 +440,11 @@ def test_benchmark_gpu_monitor(self, tmp_path: Path, model_arg: str): result = runner.invoke( perf, _build_perf_args( - model_arg=model_arg, output_file=output_file, device="gpu", monitor=True, iterations_overwrite=1000 + model_arg=model_arg, + output_file=output_file, + device="gpu", + monitor=True, + iterations_overwrite=1000, ), obj={}, catch_exceptions=False, @@ -432,8 +454,7 @@ def test_benchmark_gpu_monitor(self, tmp_path: Path, model_arg: str): assert output_file.exists(), f"Output file not created: {output_file}" data = json.loads(output_file.read_text()) _assert_monitor_result(data, device="gpu") - - def test_benchmark_npu_monitor(self, tmp_path: Path, model_arg: str): + def test_benchmark_npu_monitor(self, tmp_path: Path, npu_model_arg: str): """Benchmark on NPU with --monitor. Requires a real NPU discoverable via PDH. Verifies the JSON output @@ -447,7 +468,7 @@ def test_benchmark_npu_monitor(self, tmp_path: Path, model_arg: str): result = runner.invoke( perf, _build_perf_args( - model_arg=model_arg, output_file=output_file, device="npu", monitor=True + model_arg=npu_model_arg, output_file=output_file, device="npu", monitor=True ), obj={}, catch_exceptions=False, @@ -547,7 +568,12 @@ def test_benchmark_ep_device_gpu(self, ep: str, tmp_path: Path, model_arg: str): result = runner.invoke( perf, _build_perf_args( - model_arg=model_arg, output_file=output_file, device="gpu", ep=ep, monitor=True, iterations_overwrite=1000 + model_arg=model_arg, + output_file=output_file, + device="gpu", + ep=ep, + monitor=True, + iterations_overwrite=1000, ), obj={}, catch_exceptions=False, @@ -559,7 +585,7 @@ def test_benchmark_ep_device_gpu(self, ep: str, tmp_path: Path, model_arg: str): _assert_monitor_result(data, device="gpu", ep=EP_ALIASES[ep]) @pytest.mark.parametrize("ep", NPU_EPS) - def test_benchmark_ep_device_npu(self, ep: str, tmp_path: Path, model_arg: str): + def test_benchmark_ep_device_npu(self, ep: str, tmp_path: Path, npu_model_arg: str): """Benchmark with --ep and --device npu. Skipped if the specified EP or a NPU is unavailable on the host. @@ -573,7 +599,7 @@ def test_benchmark_ep_device_npu(self, ep: str, tmp_path: Path, model_arg: str): result = runner.invoke( perf, _build_perf_args( - model_arg=model_arg, output_file=output_file, device="npu", ep=ep, monitor=True + model_arg=npu_model_arg, output_file=output_file, device="npu", ep=ep, monitor=True ), obj={}, catch_exceptions=False, @@ -597,6 +623,16 @@ class TestPerfONNXDirect(_PerfBenchmarkSuite): def model_arg(self, onnx_model_path: Path) -> str: return str(onnx_model_path) + @pytest.fixture + def npu_model_arg(self) -> str: + """NPU tests run against a real image-classification ONNX model. + + A bare float MatMul does not exercise a representative NPU + (QNN / VitisAI) execution path, so the NPU tests use the model + shipped under ``tests/assets/``. + """ + return str(ASSETS_ONNX_MODEL) + def test_batch_size_cpu(self, tmp_path: Path, onnx_model_path: Path): """--batch-size applies to a model with a dynamic leading dimension.""" model = onnx.load(onnx_model_path) @@ -667,7 +703,7 @@ def test_input_data_cpu(self, tmp_path: Path, onnx_model_path: Path): assert data["benchmark_info"]["effective_batch_size"] == effective_batch assert data["latency_ms"]["mean"] > 0 - def test_op_tracing_basic_qnn_npu(self, tmp_path: Path, onnx_model_path: Path): + def test_op_tracing_basic_qnn_npu(self, tmp_path: Path, npu_model_arg: str): """--op-tracing basic produces a QNN NPU operator trace.""" require_ep("qnn") _require_npu() @@ -677,7 +713,7 @@ def test_op_tracing_basic_qnn_npu(self, tmp_path: Path, onnx_model_path: Path): result = CliRunner().invoke( perf, _build_perf_args( - model_arg=str(onnx_model_path), + model_arg=npu_model_arg, output_file=output_file, device="npu", ep="qnn", @@ -741,7 +777,12 @@ def test_benchmark_ep_gpu(self, ep: str, tmp_path: Path, model_arg: str): result = runner.invoke( perf, _build_perf_args( - model_arg=model_arg, output_file=output_file, device="gpu", ep=ep, monitor=True, iterations_overwrite=1000 + model_arg=model_arg, + output_file=output_file, + device="gpu", + ep=ep, + monitor=True, + iterations_overwrite=1000, ), obj={}, catch_exceptions=False, From 32706ea261e93ef195a7700e16ef13fea700951f Mon Sep 17 00:00:00 2001 From: xieofxie Date: Wed, 22 Jul 2026 11:22:55 +0800 Subject: [PATCH 06/11] Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- tests/e2e/test_perf_e2e.py | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/e2e/test_perf_e2e.py b/tests/e2e/test_perf_e2e.py index a81e45d91..214ae6031 100644 --- a/tests/e2e/test_perf_e2e.py +++ b/tests/e2e/test_perf_e2e.py @@ -454,6 +454,7 @@ def test_benchmark_gpu_monitor(self, tmp_path: Path, model_arg: str): assert output_file.exists(), f"Output file not created: {output_file}" data = json.loads(output_file.read_text()) _assert_monitor_result(data, device="gpu") + def test_benchmark_npu_monitor(self, tmp_path: Path, npu_model_arg: str): """Benchmark on NPU with --monitor. From 4321ce5e680e3f65e6352144e20f9390b075ed98 Mon Sep 17 00:00:00 2001 From: Hualiang Xie Date: Wed, 22 Jul 2026 11:26:38 +0800 Subject: [PATCH 07/11] test(e2e): match npu_model_arg override signature to base fixture Restore the model_arg parameter on TestPerfONNXDirect.npu_model_arg so it matches the _PerfBenchmarkSuite base fixture signature, resolving the CodeQL signature-mismatch alert. --- tests/e2e/test_perf_e2e.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/tests/e2e/test_perf_e2e.py b/tests/e2e/test_perf_e2e.py index 214ae6031..b5fe74919 100644 --- a/tests/e2e/test_perf_e2e.py +++ b/tests/e2e/test_perf_e2e.py @@ -116,7 +116,7 @@ def _build_perf_args( batch_size: int | None = None, input_data: Path | None = None, op_tracing: str | None = None, - iterations_overwrite: int | None = None + iterations_overwrite: int | None = None, ) -> list[str]: """Build the argv list passed to the perf CLI. @@ -444,7 +444,7 @@ def test_benchmark_gpu_monitor(self, tmp_path: Path, model_arg: str): output_file=output_file, device="gpu", monitor=True, - iterations_overwrite=1000, + iterations_overwrite=1500, ), obj={}, catch_exceptions=False, @@ -574,7 +574,7 @@ def test_benchmark_ep_device_gpu(self, ep: str, tmp_path: Path, model_arg: str): device="gpu", ep=ep, monitor=True, - iterations_overwrite=1000, + iterations_overwrite=1500, ), obj={}, catch_exceptions=False, @@ -625,12 +625,12 @@ def model_arg(self, onnx_model_path: Path) -> str: return str(onnx_model_path) @pytest.fixture - def npu_model_arg(self) -> str: + def npu_model_arg(self, model_arg: str) -> str: """NPU tests run against a real image-classification ONNX model. A bare float MatMul does not exercise a representative NPU (QNN / VitisAI) execution path, so the NPU tests use the model - shipped under ``tests/assets/``. + shipped under ``tests/assets/`` instead of ``model_arg``. """ return str(ASSETS_ONNX_MODEL) @@ -783,7 +783,7 @@ def test_benchmark_ep_gpu(self, ep: str, tmp_path: Path, model_arg: str): device="gpu", ep=ep, monitor=True, - iterations_overwrite=1000, + iterations_overwrite=1500, ), obj={}, catch_exceptions=False, From 7786711010231444d298eeb48f3c8e3f9d53ee5c Mon Sep 17 00:00:00 2001 From: hualxie Date: Wed, 22 Jul 2026 14:08:14 +0800 Subject: [PATCH 08/11] add fp32 model --- tests/assets/resnet.md | 7 +++++++ tests/assets/resnet_fp32/model.onnx | Bin 0 -> 16863 bytes tests/assets/resnet_fp32/model.onnx.data | Bin 0 -> 63492 bytes tests/assets/{ => resnet_w8a8}/model.onnx | Bin tests/assets/{ => resnet_w8a8}/model.onnx.data | 0 5 files changed, 7 insertions(+) create mode 100644 tests/assets/resnet.md create mode 100644 tests/assets/resnet_fp32/model.onnx create mode 100644 tests/assets/resnet_fp32/model.onnx.data rename tests/assets/{ => resnet_w8a8}/model.onnx (100%) rename tests/assets/{ => resnet_w8a8}/model.onnx.data (100%) diff --git a/tests/assets/resnet.md b/tests/assets/resnet.md new file mode 100644 index 000000000..79484877a --- /dev/null +++ b/tests/assets/resnet.md @@ -0,0 +1,7 @@ +# resnet_w8a8 + +Generated by using hf-tiny-model-private/tiny-random-ResNetForImageClassification in FTK's AMD npu Microsoft/resnet-50 recipe. + +# resnet_fp32 + +The output model of the first part of the above process. diff --git a/tests/assets/resnet_fp32/model.onnx b/tests/assets/resnet_fp32/model.onnx new file mode 100644 index 0000000000000000000000000000000000000000..a33b44f9d85bdb7a432248144193df30e7d9471e GIT binary patch literal 16863 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 literal 0 HcmV?d00001 diff --git a/tests/assets/resnet_fp32/model.onnx.data b/tests/assets/resnet_fp32/model.onnx.data new file mode 100644 index 0000000000000000000000000000000000000000..68e97b030d85cbec8f251107a56da233b5d65d49 GIT binary patch literal 63492 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 Date: Wed, 22 Jul 2026 14:12:15 +0800 Subject: [PATCH 09/11] add ASSETS_GPU_ONNX_MODEL --- tests/e2e/test_perf_e2e.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/tests/e2e/test_perf_e2e.py b/tests/e2e/test_perf_e2e.py index b5fe74919..6f284b12f 100644 --- a/tests/e2e/test_perf_e2e.py +++ b/tests/e2e/test_perf_e2e.py @@ -60,7 +60,8 @@ # Real image-classification ONNX model shipped in the repo # (``pixel_values`` [1, 224, 224, 3] -> ``logits`` [1, 3]). Used for NPU # tests, where a bare float MatMul is not representative of NPU execution. -ASSETS_ONNX_MODEL = Path(__file__).resolve().parent.parent / "assets" / "model.onnx" +ASSETS_NPU_ONNX_MODEL = Path(__file__).resolve().parent.parent / "assets" / "resnet_w8a8" / "model.onnx" +ASSETS_GPU_ONNX_MODEL = Path(__file__).resolve().parent.parent / "assets" / "resnet_fp32" / "model.onnx" def _require_gpu() -> None: @@ -632,7 +633,7 @@ def npu_model_arg(self, model_arg: str) -> str: (QNN / VitisAI) execution path, so the NPU tests use the model shipped under ``tests/assets/`` instead of ``model_arg``. """ - return str(ASSETS_ONNX_MODEL) + return str(ASSETS_NPU_ONNX_MODEL) def test_batch_size_cpu(self, tmp_path: Path, onnx_model_path: Path): """--batch-size applies to a model with a dynamic leading dimension.""" From 2d858d056d5f91c299542fe83b97324a95bef0ed Mon Sep 17 00:00:00 2001 From: hualxie Date: Wed, 22 Jul 2026 14:40:15 +0800 Subject: [PATCH 10/11] implement --- tests/e2e/test_perf_e2e.py | 47 ++++++++++++++++++++++++++++---------- 1 file changed, 35 insertions(+), 12 deletions(-) diff --git a/tests/e2e/test_perf_e2e.py b/tests/e2e/test_perf_e2e.py index 6f284b12f..e2fb3a6a1 100644 --- a/tests/e2e/test_perf_e2e.py +++ b/tests/e2e/test_perf_e2e.py @@ -57,11 +57,13 @@ GPU_EPS = ("dml", "nv_tensorrt_rtx", "migraphx", "openvino", "qnn") NON_CPU_EPS = ("qnn", "vitisai", "dml", "nv_tensorrt_rtx", "migraphx") -# Real image-classification ONNX model shipped in the repo -# (``pixel_values`` [1, 224, 224, 3] -> ``logits`` [1, 3]). Used for NPU -# tests, where a bare float MatMul is not representative of NPU execution. -ASSETS_NPU_ONNX_MODEL = Path(__file__).resolve().parent.parent / "assets" / "resnet_w8a8" / "model.onnx" -ASSETS_GPU_ONNX_MODEL = Path(__file__).resolve().parent.parent / "assets" / "resnet_fp32" / "model.onnx" +# Real image-classification ONNX models shipped in the repo, used by the +# NPU / GPU tests where a bare float MatMul is not representative of +# accelerator execution. NPU uses a quantized ResNet (w8a8, NHWC); GPU uses +# the fp32 ResNet (NCHW). Both classify to ``logits`` [1, 3]. +_ASSETS_DIR = Path(__file__).resolve().parent.parent / "assets" +ASSETS_NPU_ONNX_MODEL = _ASSETS_DIR / "resnet_w8a8" / "model.onnx" +ASSETS_GPU_ONNX_MODEL = _ASSETS_DIR / "resnet_fp32" / "model.onnx" def _require_gpu() -> None: @@ -210,6 +212,17 @@ def npu_model_arg(self, model_arg: str) -> str: """ return model_arg + @pytest.fixture + def gpu_model_arg(self, model_arg: str) -> str: + """Model source used by the GPU tests. + + Defaults to ``model_arg`` so subclasses share a single model. + Subclasses backed by a real ONNX file override this to point at a + model that actually executes on the GPU, where a bare float MatMul + is not representative. + """ + return model_arg + def test_benchmark_cpu(self, tmp_path: Path, model_arg: str): """Benchmark on CPU with minimal iterations. @@ -427,7 +440,7 @@ def test_benchmark_cpu_monitor(self, tmp_path: Path, model_arg: str): data = json.loads(output_file.read_text()) _assert_monitor_result(data, device="cpu") - def test_benchmark_gpu_monitor(self, tmp_path: Path, model_arg: str): + def test_benchmark_gpu_monitor(self, tmp_path: Path, gpu_model_arg: str): """Benchmark on GPU with --monitor. Requires a real GPU discoverable via PDH. Verifies the JSON output @@ -441,11 +454,11 @@ def test_benchmark_gpu_monitor(self, tmp_path: Path, model_arg: str): result = runner.invoke( perf, _build_perf_args( - model_arg=model_arg, + model_arg=gpu_model_arg, output_file=output_file, device="gpu", monitor=True, - iterations_overwrite=1500, + iterations_overwrite=1000, ), obj={}, catch_exceptions=False, @@ -556,7 +569,7 @@ def test_benchmark_ep_device_cpu(self, ep: str, tmp_path: Path, model_arg: str): _assert_monitor_result(data, device="cpu", ep=EP_ALIASES[ep]) @pytest.mark.parametrize("ep", GPU_EPS) - def test_benchmark_ep_device_gpu(self, ep: str, tmp_path: Path, model_arg: str): + def test_benchmark_ep_device_gpu(self, ep: str, tmp_path: Path, gpu_model_arg: str): """Benchmark with --ep and --device gpu. Skipped if the specified EP or a GPU is unavailable on the host. @@ -570,12 +583,12 @@ def test_benchmark_ep_device_gpu(self, ep: str, tmp_path: Path, model_arg: str): result = runner.invoke( perf, _build_perf_args( - model_arg=model_arg, + model_arg=gpu_model_arg, output_file=output_file, device="gpu", ep=ep, monitor=True, - iterations_overwrite=1500, + iterations_overwrite=1000, ), obj={}, catch_exceptions=False, @@ -635,6 +648,16 @@ def npu_model_arg(self, model_arg: str) -> str: """ return str(ASSETS_NPU_ONNX_MODEL) + @pytest.fixture + def gpu_model_arg(self, model_arg: str) -> str: + """GPU tests run against a real image-classification ONNX model. + + A bare float MatMul does not exercise a representative GPU + execution path, so the GPU tests use the model shipped under + ``tests/assets/`` instead of ``model_arg``. + """ + return str(ASSETS_GPU_ONNX_MODEL) + def test_batch_size_cpu(self, tmp_path: Path, onnx_model_path: Path): """--batch-size applies to a model with a dynamic leading dimension.""" model = onnx.load(onnx_model_path) @@ -784,7 +807,7 @@ def test_benchmark_ep_gpu(self, ep: str, tmp_path: Path, model_arg: str): device="gpu", ep=ep, monitor=True, - iterations_overwrite=1500, + iterations_overwrite=1000, ), obj={}, catch_exceptions=False, From 989598b28c3942c44825adda3ef2491b4f392829 Mon Sep 17 00:00:00 2001 From: hualxie Date: Wed, 22 Jul 2026 15:15:47 +0800 Subject: [PATCH 11/11] add back require_utilization --- tests/e2e/test_perf_e2e.py | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/tests/e2e/test_perf_e2e.py b/tests/e2e/test_perf_e2e.py index e2fb3a6a1..4862e0893 100644 --- a/tests/e2e/test_perf_e2e.py +++ b/tests/e2e/test_perf_e2e.py @@ -86,7 +86,9 @@ def _require_npu() -> None: pytest.skip("No NPU detected via PDH") -def _assert_hw_monitor_section(data: dict, device_kind: str) -> None: +def _assert_hw_monitor_section( + data: dict, device_kind: str, *, require_utilization: bool = True + ) -> None: """Assert the ``hw_monitor`` section is present and well-formed. Checks the section emitted by HWMonitor when --monitor is passed: @@ -102,7 +104,11 @@ def _assert_hw_monitor_section(data: dict, device_kind: str) -> None: else: assert hw["device_kind"] == device_kind assert hw["adapter_luid"] is not None - assert hw[device_kind]["mean_pct"] > 0 + if require_utilization: + assert hw[device_kind]["mean_pct"] > 0 + else: + # Just a valid data + assert hw[device_kind]["mean_pct"] >= 0 def _build_perf_args( @@ -171,6 +177,7 @@ def _assert_monitor_result( device: str, device_kind: str | None = None, ep: str | None = None, + require_utilization: bool = True, ) -> None: """Assert a monitored perf run produced the expected device + hw_monitor data. @@ -178,7 +185,7 @@ def _assert_monitor_result( measured, and delegates the hw_monitor checks to :func:`_assert_hw_monitor_section`. ``device_kind`` defaults to ``device`` when not given (only differs for cases like VitisAI where ``--device`` and - the monitored hardware diverge). + the monitored hardware diverge). ``require_utilization`` is forwarded. """ if device_kind is None: device_kind = device @@ -186,7 +193,7 @@ def _assert_monitor_result( assert data["latency_ms"]["mean"] > 0 if ep is not None: assert data["benchmark_info"]["ep"] == ep - _assert_hw_monitor_section(data, device_kind) + _assert_hw_monitor_section(data, device_kind, require_utilization=require_utilization) # =========================================================================== @@ -597,7 +604,8 @@ def test_benchmark_ep_device_gpu(self, ep: str, tmp_path: Path, gpu_model_arg: s assert output_file.exists() data = json.loads(output_file.read_text()) - _assert_monitor_result(data, device="gpu", ep=EP_ALIASES[ep]) + # openvino gpu could not emit valid pdh counter + _assert_monitor_result(data, device="gpu", ep=EP_ALIASES[ep], require_utilization=ep != "openvino") @pytest.mark.parametrize("ep", NPU_EPS) def test_benchmark_ep_device_npu(self, ep: str, tmp_path: Path, npu_model_arg: str):