Skip to content

Commit 5f25b92

Browse files
committed
Update readme and cli experience, remove previous edits on existing examples
1 parent 6517525 commit 5f25b92

10 files changed

Lines changed: 111 additions & 466 deletions

File tree

backends/qualcomm/debugger/README.md

Lines changed: 19 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -90,28 +90,38 @@ Note: Files ending with `.bin ` do not support graph visualization in qairt_visu
9090
For more details, visit the [QAIRT Visualizer](https://pypi.org/project/qairt-visualizer/).
9191

9292

93-
# Observatory
93+
# Observatory
94+
9495
A new, review-focused Observatory implementation is available under:
9596

9697
`backends/qualcomm/debugger/observatory`
9798

98-
Use observaotry.cli to invoke ordinary aot script. Use `--lens_recipe=accuracy` to enable accuracy Lenses.
99+
Use the Observatory CLI to wrap any Qualcomm AOT export script. Use `--lens_recipe=accuracy`
100+
to enable accuracy lenses.
99101

100102
```bash
101-
python -m executorch.backends.qualcomm.debugger.observatory.cli \
102-
--output-html obs_report.html \
103-
--lens_recipe=accuracy \
104-
{original script and args}
103+
python -m executorch.backends.qualcomm.debugger.observatory \
104+
--output-html obs_report.html \
105+
--lens_recipe=accuracy \
106+
{original script and args}
105107
```
106-
For example
108+
109+
For example:
107110

108111
```bash
109-
python -m executorch.backends.qualcomm.debugger.observatory.cli \
112+
python -m executorch.backends.qualcomm.debugger.observatory \
110113
--output-html obs_report.html \
111114
--lens_recipe=accuracy \
112-
examples/qualcomm/oss_scripts/mobilevit_v2.py --backend htp --model SM8650 -d ./imagenet-mini-val/ -b build-android/ --compile_only
115+
examples/qualcomm/oss_scripts/mobilevit_v2.py \
116+
--backend htp --model SM8650 -d ./imagenet-mini-val/ -b build-android/ --compile_only
113117
```
114118

119+
> **Note**: Qualcomm example scripts (e.g. `oss_scripts/roberta.py`) use only absolute imports
120+
> and are run as plain scripts. The Observatory CLI auto-selects `runpy.run_path` for these since
121+
> their directories do not contain `__init__.py`.
122+
123+
See `backends/qualcomm/debugger/observatory/README.md` for full documentation.
124+
115125
# ExecuTorch QNN Intermediate Output Debugger
116126

117127
ExecuTorch QNN Intermediate Output Debugger is a tool that helps users debug intermediate output accuracy by comparing CPU outputs with QNN outputs. This tool offers a variety of output formats and flexibility for users to define their own metrics when debugging.

backends/xnnpack/debugger/observatory/cli.py

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@
2525

2626

2727
def main():
28-
from devtools.observatory.cli import (
28+
from executorch.devtools.observatory.cli import (
2929
make_collect_parser,
3030
make_visualize_parser,
3131
run_observatory,
@@ -49,8 +49,8 @@ def main():
4949
)
5050
args = parser.parse_args(sys.argv[1:])
5151

52-
from devtools.observatory.observatory import Observatory
53-
from devtools.observatory.lenses.pipeline_graph_collector import (
52+
from executorch.devtools.observatory.observatory import Observatory
53+
from executorch.devtools.observatory.lenses.pipeline_graph_collector import (
5454
PipelineGraphCollectorLens,
5555
)
5656
from .lenses.xnnpack_patches import install_xnnpack_patches
@@ -60,11 +60,11 @@ def main():
6060
Observatory.register_lens(PipelineGraphCollectorLens)
6161

6262
if args.lens_recipe == "accuracy":
63-
from devtools.observatory.lenses.accuracy import AccuracyLens
63+
from executorch.devtools.observatory.lenses.accuracy import AccuracyLens
6464

6565
Observatory.register_lens(AccuracyLens)
6666

67-
from devtools.observatory.lenses.per_layer_accuracy import (
67+
from executorch.devtools.observatory.lenses.per_layer_accuracy import (
6868
PerLayerAccuracyLens,
6969
)
7070

devtools/fx_viewer/exporter.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -389,8 +389,9 @@ def _sweep_min_gap(nids):
389389
# Phase 1: chain detection (real + dummy members)
390390
chains = cls._detect_chains(el or [], nodes)
391391

392+
FAIR_RUNS=5
392393
# Phase 2: iterative spine cohesion + pure-A overlap fix
393-
for i in range(cls._SPINE_COHESION_ITER + 2):
394+
for i in range(cls._SPINE_COHESION_ITER + FAIR_RUNS):
394395

395396
# delta for each node
396397
# We record the relative weight (chain length) and their base delta
@@ -399,7 +400,7 @@ def _sweep_min_gap(nids):
399400
if not ch:
400401
continue
401402
mean_x = sum(x[v] for v in ch) / len(ch)
402-
# emphasize end point
403+
# emphasize end point (disable for last FAIR_RUNS iters)
403404
if i < cls._SPINE_COHESION_ITER:
404405
# we use common start and end node of chain to attract chain close together
405406
mean_x = (mean_x + x[ch[0]] + x[ch[-1]]) / 3.0

devtools/observatory/README.md

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,10 @@ python -m executorch.backends.xnnpack.debugger.observatory \
5959
--model_name=mv2 --delegate --quantize --output_dir /tmp/mv2
6060
```
6161

62+
> **XNNPack note**: `aot_compiler.py` uses relative imports so it must run as a Python module.
63+
> The CLI auto-detects this from `__init__.py` presence. You can also pass the dotted module
64+
> name directly: `examples.xnnpack.aot_compiler`
65+
6266
This produces:
6367
- `/tmp/obs/report.html` (interactive report)
6468
- `/tmp/obs/report.json` (raw data, path auto-derived from HTML path)
@@ -232,7 +236,6 @@ return AnalysisResult(per_record_data={"step_1": record_analysis})
232236
| [USAGE.md](USAGE.md) | CLI usage guide, workflow examples, demo script modes |
233237
| [lenses/LENSES.md](lenses/LENSES.md) | Built-in lens details, accuracy lens internals, custom lens patterns |
234238
| [REFERENCE.md](REFERENCE.md) | Contract tables, API reference, JS callbacks, performance notes |
235-
| [lenses/DEBUG_HANDLE_SYNC_ANALYSIS.md](lenses/DEBUG_HANDLE_SYNC_ANALYSIS.md) | Technical analysis of debug_handle consistency across pipeline stages |
236239

237240
## Tests
238241

devtools/observatory/USAGE.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,11 @@ python -m executorch.backends.xnnpack.debugger.observatory \
3232
--model_name=mv2 --delegate --quantize --output_dir /tmp/mv2
3333
```
3434

35+
> **XNNPack note**: `examples/xnnpack/aot_compiler.py` uses relative imports (`from . import ...`).
36+
> The CLI auto-detects this: when a `.py` path is given and its directory contains `__init__.py`,
37+
> it runs via `runpy.run_module` instead of `runpy.run_path`. You can also pass a dotted module
38+
> name directly (e.g. `examples.xnnpack.aot_compiler`) to force module mode explicitly.
39+
3540
## 2. Convert JSON to HTML (Visualize Mode)
3641

3742
Use the `visualize` subcommand to convert an existing JSON file to HTML without

devtools/observatory/cli.py

Lines changed: 69 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,41 @@ def run_visualize(input_json: str, output_html: str) -> None:
8383
)
8484

8585

86+
def _resolve_run_mode(script: str) -> tuple[str, str, str | None]:
87+
"""Return (mode, target, pkg_root).
88+
89+
mode='module': target is a dotted module name; pkg_root is prepended to sys.path.
90+
mode='script': target is an absolute file path; pkg_root is None.
91+
92+
Detection rules (in order):
93+
1. No '.py' suffix and no path separator → explicit dotted module name → 'module'
94+
2. File path whose directory has __init__.py → walk up to find package root → 'module'
95+
3. Everything else → 'script'
96+
"""
97+
if not script.endswith(".py") and os.sep not in script and "/" not in script:
98+
return "module", script, None
99+
100+
abs_path = os.path.abspath(script)
101+
script_dir = os.path.dirname(abs_path)
102+
103+
if not os.path.isfile(os.path.join(script_dir, "__init__.py")):
104+
return "script", abs_path, None
105+
106+
parts = [os.path.splitext(os.path.basename(abs_path))[0]]
107+
current = script_dir
108+
while True:
109+
parts.insert(0, os.path.basename(current))
110+
parent = os.path.dirname(current)
111+
if parent == current or not os.path.isfile(
112+
os.path.join(parent, "__init__.py")
113+
):
114+
pkg_root = parent
115+
break
116+
current = parent
117+
118+
return "module", ".".join(parts), pkg_root
119+
120+
86121
def run_observatory(
87122
script_path: str,
88123
script_argv: list[str],
@@ -102,14 +137,46 @@ def run_observatory(
102137
output_json = "observatory_report.json"
103138

104139
title = f"Observatory: {os.path.basename(script_path)}"
140+
mode, target, pkg_root = _resolve_run_mode(script_path)
105141

106142
try:
107143
with Observatory.enable_context(config={}):
108-
runpy.run_path(script_path, run_name="__main__")
144+
if mode == "module":
145+
if pkg_root is not None and pkg_root not in sys.path:
146+
sys.path.insert(0, pkg_root)
147+
logging.info("[Observatory CLI] Running as module: %s", target)
148+
runpy.run_module(target, run_name="__main__", alter_sys=True)
149+
else:
150+
logging.info("[Observatory CLI] Running as script: %s", target)
151+
runpy.run_path(target, run_name="__main__")
109152
except SystemExit:
110153
pass
154+
except ImportError as exc:
155+
logging.error("[Observatory CLI] Import error in '%s': %s", script_path, exc)
156+
if "relative import" in str(exc) or "attempted relative import" in str(exc):
157+
logging.error(
158+
" Hint: this script uses relative imports and must run as a Python module.\n"
159+
" Option A — ensure the script's directory contains __init__.py so it is\n"
160+
" auto-detected as a package member.\n"
161+
" Option B — pass a dotted module name instead of a file path:\n"
162+
" python -m executorch.devtools.observatory"
163+
" <package>.<subpackage>.<module> [args...]"
164+
)
165+
elif mode == "module":
166+
logging.error(
167+
" Hint: module '%s' could not be imported.\n"
168+
" Ensure the package root '%s' is on PYTHONPATH or the package is installed.",
169+
target,
170+
pkg_root or "unknown",
171+
)
111172
except Exception as exc:
112-
logging.error("[Observatory CLI] Script raised: %s", exc)
173+
logging.error(
174+
"[Observatory CLI] '%s' raised: %s (run mode: %s, target: %s)",
175+
os.path.basename(script_path),
176+
exc,
177+
mode,
178+
target,
179+
)
113180
finally:
114181
os.makedirs(os.path.dirname(output_html) or ".", exist_ok=True)
115182
os.makedirs(os.path.dirname(output_json) or ".", exist_ok=True)

0 commit comments

Comments
 (0)