multi-repo-setup · git:20260615.5d90348 · 2026-06-15 · sha256 25bd83572d07f758
multi-repo-setup git:20260615.5d90348A
Immutable. This exact content is served forever at /api/v1/blob/25bd83572d07f758.
---
name: multi-repo-setup
description: Set up a cross-repo investigation when a workload from another repo (pypto, pypto-lib, etc.) needs to be run, especially when you want to swap in simpler-main HEAD or the current worktree's simpler instead of the version that repo pins. Clones-or-updates each external repo every invocation so stale local clones don't lie about CI parity. MUST invoke before chasing "X doesn't work on simpler" reports where X lives outside this repo.
---
# Multi-Repo Investigation Setup
The skill lives in the simpler repo, so `$PWD` when you invoke it is
already a simpler worktree — nothing to clone for simpler itself.
Every other repo gets cloned-or-updated to a canonical local path each
time. The default is to follow each repo's own pinning. Simpler dev
often diverges from that to swap in either simpler-main HEAD or the
current worktree.
## When to invoke
Invoke before:
- running an external-repo workload (`python <ext>/some_test.py`,
pytest in another repo, etc.) for the first time in a session, or
- reporting "X doesn't work on simpler" where X lives in another repo.
Skip for work that stays inside `simpler/` (its own pytest, examples,
unit tests).
## Step 1: The repo graph (URLs)
For an Ascend workload investigation, the cast is usually some subset
of these. `simpler` is `$PWD` — the other repos are cloned under the
worktree's **`build/`** (gitignored, so they never get committed and stay
co-located with the simpler you're testing). `$BUILD` =
`$(git rev-parse --show-toplevel)/build` (Step 2 defines it).
| repo | role | GitHub URL | local clone |
| ---- | ---- | ---------- | ----------- |
| simpler | host runtime + DFX (this repo) | <https://github.com/hw-native-sys/simpler> | `$PWD` (the current worktree) |
| pypto | compiler + Python frontend; vendors a simpler submodule at `runtime/` | <https://github.com/hw-native-sys/pypto> | `$BUILD/pypto` |
| pypto-lib | model workloads (qwen3, deepseek, etc.) — imports pypto + simpler | <https://github.com/hw-native-sys/pypto-lib> | `$BUILD/pypto-lib` |
| pto-isa | ISA spec; sets `PTO_ISA_ROOT` (required to BUILD the simpler runtime) | <https://github.com/hw-native-sys/pto-isa> | `$BUILD/pto-isa` |
| PTOAS | `ptoas` assembler — **provided globally**, no clone | (on dev box / CI) | `/usr/local/bin/ptoas-bin` via `pypto-setup` |
Drop rows your investigation doesn't need. Add rows for ad-hoc repos in
the same format.
## Step 2: Clone-or-update every external repo
Clones live under the worktree's `build/` (gitignored — co-located with
the simpler you test, never committed). Run this each invocation — never
trust the existing clone to be current. Same shell function handles both
"doesn't exist yet" and "already there, just sync to origin/main":
```bash
BUILD="$(git rev-parse --show-toplevel)/build" # gitignored; holds the external clones
mkdir -p "$BUILD"
ensure_repo() {
local url=$1 dir=$2
if [ -d "$dir/.git" ]; then
git -C "$dir" fetch origin --quiet
git -C "$dir" reset --hard origin/main
else
git clone "$url" "$dir"
fi
git -C "$dir" submodule update --init --recursive --depth 1
}
ensure_repo https://github.com/hw-native-sys/pypto "$BUILD/pypto"
ensure_repo https://github.com/hw-native-sys/pypto-lib "$BUILD/pypto-lib"
ensure_repo https://github.com/hw-native-sys/pto-isa "$BUILD/pto-isa"
# add more as needed
```
Notes:
- `reset --hard origin/main` overwrites any local changes in the clone.
If you have edits there, stash / commit them before invoking this skill.
- `submodule update --init --recursive` matches the pin recorded in
each repo's commit — that's the version the repo's CI runs against.
- PTOAS / CANN / gcc-15 are **provided globally** — no download. Step 2.5's
`pypto-setup --export` points `PTOAS_ROOT` at them.
## Step 2.5: Toolchain env — `pypto-setup` + `PTO_ISA_ROOT`
The dev box and CI runners ship ptoas, CANN, and gcc-15 globally; the
`pypto-setup` helper hands you the env in one line. `PTO_ISA_ROOT` is the
one piece it does NOT set (it's per-user) — point it at the `build/` clone:
```bash
eval "$(pypto-setup --export)" # exports PTOAS_ROOT, ASCEND_HOME_PATH, GCC15_ROOT, PATH
export PTO_ISA_ROOT="$BUILD/pto-isa" # required to BUILD the simpler runtime
```
Run `pypto-setup` (no args) to see every global component, its path, and
whether it's present. Why each matters:
- **`PTOAS_ROOT`** unset → the JIT compile silently sets `skip_ptoas=True` →
no `kernel_config.py` emitted → the runtime can't assemble kernels onboard.
- **`PTO_ISA_ROOT`** unset → the simpler runtime build fails with
`PTO-ISA not available`.
## Step 3: Override — pick which simpler the workload sees
The default is what pypto's submodule pins. Simpler dev usually wants
ONE of these instead:
### Override A: simpler `origin/main` HEAD
You want the latest merged simpler against the external workload (e.g.
confirm a recently merged PR didn't break a downstream case).
```bash
ensure_repo https://github.com/hw-native-sys/simpler "$BUILD/simpler-main"
source <your-venv>/bin/activate
pip install --no-build-isolation "$BUILD/simpler-main"
```
### Override B: this worktree's simpler (your PR branch)
You want to test in-flight changes (current PR / dev branch) against the
external workload. `PTO_ISA_ROOT` (Step 2.5) must be exported first — the
install builds the onboard runtime against it. A stale simpler whose ABI
doesn't match the pypto compiler surfaces as **507018** on the first run.
```bash
source .venv/bin/activate
pip install --no-build-isolation . # needs PTO_ISA_ROOT set (Step 2.5)
```
### Either way: verify which simpler actually loaded
A previous session may have left a user-site editable hook
(`_simpler_editable.pth`) that shadows your venv install. Always
verify:
```bash
python -c "import simpler, simpler_setup; \
print('simpler :', simpler.__file__); \
print('simpler_setup:', simpler_setup.__file__)"
```
Both paths must point at the simpler you intended. If either points at
`~/.local/...` or another worktree, clean up and reinstall:
```bash
rm -f ~/.local/lib/python*/site-packages/_simpler_editable.{pth,py}
pip uninstall -y simpler
pip install --no-build-isolation <the-simpler-you-want>
```
### Note on `-e`
Prefer plain `pip install --no-build-isolation .` over `-e .` unless
you actively edit the package and need re-import to pick up changes
without reinstall. Editable installs leak the `_simpler_editable.pth`
hook into user-site, which survives sessions and shadows the next
venv install. Non-editable installs don't.
## Step 4: Install pypto / pypto-lib
For pypto, plain install too — let pypto's vendored `runtime/` stay
unbuilt because you've already provided simpler via Step 3:
```bash
pip install --no-build-isolation "$BUILD/pypto"
# Do NOT `pip install "$BUILD/pypto/runtime"` — that would
# overwrite the simpler you installed in Step 3 with pypto's older pin.
```
pypto-lib is import-only (no install). Set `PYTHONPATH` and run
scripts out of its tree directly:
```bash
export PYTHONPATH="$BUILD/pypto-lib"
```
Re-verify the loaded simpler after every install — pypto's install can
re-resolve dependencies in ways that change what wins.
## Worked example: qwen3 decode_layer onboard (a2a3) + DFX
End-to-end, assuming Steps 1–4 done (repos in `$BUILD`, env from Step 2.5,
worktree simpler installed). The case is
`$BUILD/pypto-lib/models/qwen3/14b/decode_layer.py`. Onboard runs MUST hold a
per-die lock (see `.claude/rules/running-onboard.md`) and pass the
`onboard-arch-precheck` gate.
```bash
.claude/skills/onboard-arch-precheck/check.sh a2a3 || exit 1 # refuse wrong-arch BEFORE locking
cd "$BUILD/pypto-lib/models/qwen3/14b"
# Round 1 — dep_gen (topology) ; Round 2 — swimlane (clean timing). NEVER co-run:
# dep_gen perturbs the timing the overhead analysis reads.
task-submit --device auto --device-num 1 --run "python decode_layer.py -p a2a3 -d \$TASK_DEVICE"
task-submit --device auto --device-num 1 --run "python decode_layer.py -p a2a3 -d \$TASK_DEVICE --no-dep-gen --enable-l2-swimlane"
```
Both write to `build_output/_jit_*/dfx_outputs/` (`deps.json` from round 1,
`l2_swimlane_records.json` from round 2). Then analyze from the simpler worktree
(its `simpler_setup/tools` wins by cwd precedence):
```bash
# ROUND1_DIR / ROUND2_DIR are the two build_output/_jit_*/dfx_outputs/ dirs above.
python -m simpler_setup.tools.sched_overhead_analysis \
--l2-swimlane-records-json "ROUND2_DIR/l2_swimlane_records.json" \
--deps-json "ROUND1_DIR/deps.json"
# Visual: add the Overhead Analysis track to the Perfetto trace
python -m simpler_setup.tools.swimlane_converter "ROUND2_DIR/l2_swimlane_records.json" \
--deps-json "ROUND1_DIR/deps.json" --overhead -o swimlane.json
```
See [docs/dfx/sched-overhead-model.md](../../../docs/dfx/sched-overhead-model.md)
for what the report and the 8 overhead tracks mean.
## Step 5: If the workload fails — start with "is it CI-gated?"
The most informative first check is whether the workload is gated by
that repo's CI workflow.
| case | meaning | how to triage |
| ---- | ------- | ------------- |
| **CI-gated** | the repo's CI runs this exact script today; it was passing as of the last green CI run | A failure now usually points at a **recent code change** — your in-flight simpler changes, or a commit landed in pypto / pypto-lib since the last CI run. Bisect against `origin/main` of each repo. |
| **not CI-gated** | the script is in the repo but no workflow invokes it | Read the docstring first. Files like this are often "intent" / "EXPECTED / INTENT program" / experimental drafts — they may be documented as expected-to-fail. Treat as workload bug, not simpler bug, unless proven otherwise. |
Quick check:
```bash
F=<workload>.py
grep -nE "python .*$(basename $F)" "$BUILD/"*/.github/workflows/*.yml
# Match → CI-gated. No match → not CI-gated.
```
Also worth checking: pypto's CI sometimes pulls pypto-lib and runs a
specific subset (see pypto's `.github/workflows/ci.yml` step "Run
pypto-lib ... example"). That subset is the actual cross-repo gate.
### Common surface errors → first suspect
| symptom | likely layer | first check |
| ------- | ------------ | ----------- |
| `ModuleNotFoundError: No module named 'pypto'` | pypto not installed in this venv | reinstall pypto from your local clone |
| `import simpler` resolves to wrong path | user-site `.pth` hook shadowing venv | remove `_simpler_editable.{pth,py}` from user-site |
| `FileNotFoundError: kernel_config.py not found in ...` | `PTOAS_ROOT` unset → pypto auto-skips ptoas → no `kernel_config.py` emitted | `eval "$(pypto-setup --export)"` (Step 2.5) |
| `OSError: PTO-ISA not available` (during simpler build) | `PTO_ISA_ROOT` unset | `export PTO_ISA_ROOT="$BUILD/pto-isa"` (Step 2.5) |
| script exits 0 but no device run | compile-only smoke fallback (golden data dir missing) | pass `--data-dir <golden>` or `--smoke` explicitly |
| `aclrtSynchronizeStreamWithTimeout (AICPU) failed: 507018` | binary skew (simpler runtime vs pypto compiler ABI), OR device log is the only ground truth | rebuild simpler against the matching pypto (Step 3/4); then read `~/ascend/log/debug/device-N/device-<pid>_*.log` |
| `BFloat16 did not match Float` at validate | golden data shape mismatch (data older than code) | regenerate golden via the workload's `gen_*_golden.py` |
When the surface is `507018` / `507899` / `507046`, **do not stop at
the host log**. The host only reports CANN's verdict; the actual AICPU
state lives in `~/ascend/log/debug/device-N/`:
```bash
LOG=~/ascend/log/debug/device-$DEVICE_ID/device-$PID_*.log
grep -oE "task_id=[0-9]+ state=RUNNING" "$LOG" | sort -u
grep "state=RUNNING" "$LOG" | head -1 | grep -oE 'kernels=\[[^]]+\]'
grep "completed=" "$LOG" | head -1
```
If the same task hangs across every retry on every chip, it's the
workload (or your code change), not chip contention.
## Anti-patterns
- ❌ **Trusting an existing local clone without `git fetch`**. Your
clone is whatever you last fetched, possibly weeks behind. Step 2
exists precisely to make this not a thing.
- ❌ **Using `-e` "just to be safe"**. Editable installs leak a
user-site finder hook that survives sessions and shadows the next
venv install. Plain install is the default; reach for `-e` only
when you'll actively edit.
- ❌ **Blaming chip contention before reading the device log**. The
device log either shows the contention signature (sibling-die cores
with `cond_reg_state=ack` from another owner) or it doesn't.
- ❌ **Treating any failing workload as "simpler broke it"**. Step 5's
CI-gate check separates "your simpler change broke a CI case" (real)
from "this file was always expected to fail" (not your problem).
- ❌ **Skipping `.claude/rules/running-onboard.md`** on onboard
hardware runs. Multi-repo flows don't waive the per-die lock.