add non-functional validation
Validate Operations / validate-operations (push) Has been cancelled

refactors
This commit is contained in:
NiccoloN
2026-07-27 10:54:59 +02:00
parent 4964e889da
commit 620e381cfb
30 changed files with 677 additions and 164 deletions
@@ -29,8 +29,13 @@ Compiler representations may share analysis, planning, or code-generation
work only when the emitted execution retains the same independent work, work only when the emitted execution retains the same independent work,
schedule semantics, scheduling granularity, and available parallelism. schedule semantics, scheduling granularity, and available parallelism.
An optimization is acceptable only when its static runtime proxies are equal or Static runtime proxies are useful for screening changes, but they are not the
better. latency authority. When comparable validation results are available for the
same model, inputs, hardware configuration, and non-functional simulation
configuration, the `Latency` reported by `validation/validate.py` is
authoritative. An optimization is acceptable only when its static runtime
proxies are equal or better, unless such a matched validation comparison
demonstrates equal or better latency.
## Asymptotic cost ## Asymptotic cost
@@ -64,6 +69,7 @@ Do not:
Every performance change must report before and after: Every performance change must report before and after:
- matched validation `Latency` when the changed execution can be simulated;
- compiler wall time; - compiler wall time;
- compiler peak RSS; - compiler peak RSS;
- IR operation and value counts at the changed stage; - IR operation and value counts at the changed stage;
@@ -82,4 +88,5 @@ Every performance change must report before and after:
## Stop rule ## Stop rule
If compile time or memory improves but a runtime proxy worsens, stop and reject If compile time or memory improves but a runtime proxy worsens, stop and reject
the change. the change unless a matched validation comparison demonstrates equal or better
`Latency`. Proxy improvements do not override a validation latency regression.
+20
View File
@@ -145,4 +145,24 @@ raptor_apply_patch(
"Skip output emission for PIM accelerator" "Skip output emission for PIM accelerator"
) )
# Count the PIM status messages instead of the unused standard backend phases
raptor_apply_patch(
"${ONNX_MLIR_DIR}/src/Compiler/CompilerUtils.cpp"
"void showCompilePhase(std::string msg) {"
[=[void showCompilePhase(std::string msg) {
if (llvm::is_contained(maccel, accel::Accelerator::Kind::PIM)) {
if (pimOnlyCodegen)
TOTAL_COMPILE_PHASE = 2;
else if (pimEmissionTarget == EmitSpatial)
TOTAL_COMPILE_PHASE = 3;
else if (pimEmissionTarget == EmitPim)
TOTAL_COMPILE_PHASE = 4;
else if (pimEmissionTarget == EmitPimBufferized)
TOTAL_COMPILE_PHASE = 5;
else
TOTAL_COMPILE_PHASE = 9;
}]=]
"Use PIM compile phase count"
)
add_subdirectory(onnx-mlir) add_subdirectory(onnx-mlir)
+25 -13
View File
@@ -29,7 +29,8 @@ lowering, scheduling, memory layout, and code-generation optimizations.
- `backend-simulators/pim/pim-simulator` is the in-tree Rust functional - `backend-simulators/pim/pim-simulator` is the in-tree Rust functional
simulator used by validation. It reads Raptor's `pim/` artifact directory and simulator used by validation. It reads Raptor's `pim/` artifact directory and
compares simulator output against native ONNX-MLIR execution. compares simulator output against native ONNX-MLIR execution.
- `backend-simulators/pim/pimsim-nn` is the performance simulator submodule. - `backend-simulators/pim/pimsim-nn` is the non-functional simulator submodule
used internally by validation for latency, power, and energy.
The helper scripts in `pimcomp_utils/` are for comparison with PIMCOMP-NN and The helper scripts in `pimcomp_utils/` are for comparison with PIMCOMP-NN and
contain local paths; treat them as local utilities, not portable workflows. contain local paths; treat them as local utilities, not portable workflows.
@@ -155,7 +156,7 @@ This writes PIM artifacts under `/tmp/raptor/pim/`.
Functional validation lives in `validation/`. It compiles ONNX models, builds a Functional validation lives in `validation/`. It compiles ONNX models, builds a
native ONNX-MLIR reference runner, generates random inputs, runs Raptor, runs native ONNX-MLIR reference runner, generates random inputs, runs Raptor, runs
the Rust PIM simulator, and compares outputs. the Rust functional simulator, and compares outputs.
Python dependencies used by the validation scripts are `numpy`, `onnx`, and Python dependencies used by the validation scripts are `numpy`, `onnx`, and
`colorama`. The simulator requires the Rust toolchain. `colorama`. The simulator requires the Rust toolchain.
@@ -167,9 +168,6 @@ Per-operation validation from the repository root:
--raptor-path build_release/Release/bin/onnx-mlir \ --raptor-path build_release/Release/bin/onnx-mlir \
--onnx-include-dir onnx-mlir/include \ --onnx-include-dir onnx-mlir/include \
--operations-dir validation/operations \ --operations-dir validation/operations \
--crossbar-count 64 \
--crossbar-size 128 \
--core-count 144 \
--verbose \ --verbose \
--raptor-extra-arg=--pim-detect-communication-deadlock \ --raptor-extra-arg=--pim-detect-communication-deadlock \
--raptor-extra-arg=--pim-export-spatial-dataflow=none --raptor-extra-arg=--pim-export-spatial-dataflow=none
@@ -183,17 +181,24 @@ containing `.onnx` files:
--raptor-path build_release/Release/bin/onnx-mlir \ --raptor-path build_release/Release/bin/onnx-mlir \
--onnx-include-dir onnx-mlir/include \ --onnx-include-dir onnx-mlir/include \
--operations-dir validation/networks/yolo11n/depth_04 \ --operations-dir validation/networks/yolo11n/depth_04 \
--crossbar-count 64 \
--crossbar-size 128 \
--core-count 144 \
--verbose \ --verbose \
--raptor-extra-arg=--pim-detect-communication-deadlock \ --raptor-extra-arg=--pim-detect-communication-deadlock \
--raptor-extra-arg=--pim-export-spatial-dataflow=none --raptor-extra-arg=--pim-export-spatial-dataflow=none
``` ```
Useful validation options: Useful validation options:
- `--simulator-dir <path>` - override the auto-detected - `--simulator-dir <path>` - override the auto-detected functional
`backend-simulators/pim/pim-simulator` path. `backend-simulators/pim/pim-simulator` path.
- `--pimcomp-config <arch-a|arch-b|arch-c>` - select the checked-in
non-functional simulation profile. Arch-A is the default; the hardware
argument defaults match it (`168` cores, `96` crossbars/core, `128x128`
crossbars).
Selecting another profile also requires its matching hardware arguments.
- `--non-functional-simulator-build-dir <path>` - override the auto-detected
non-functional simulator build directory.
- `--skip-non-functional-simulation` - skip non-functional simulation. If
explicit core/crossbar arguments do not match the selected profile, validation
reports the mismatch and skips only non-functional simulation.
- `--threshold <float>` - maximum allowed per-element output difference. - `--threshold <float>` - maximum allowed per-element output difference.
- `--seed <int>` - RNG seed for generated inputs. - `--seed <int>` - RNG seed for generated inputs.
- `--command-timeout-seconds <float>` - timeout for compiler, runner, and - `--command-timeout-seconds <float>` - timeout for compiler, runner, and
@@ -208,7 +213,14 @@ Each validation run writes artifacts in the model workspace, for example under
- `raptor/` - compiler artifacts, including `*.onnx.mlir`, dialect dumps under - `raptor/` - compiler artifacts, including `*.onnx.mlir`, dialect dumps under
`dialects/`, reports under `reports/`, and final PIM artifacts under `pim/`. `dialects/`, reports under `reports/`, and final PIM artifacts under `pim/`.
- `runner/` - generated reference runner source, build tree, and shared library. - `runner/` - generated reference runner source, build tree, and shared library.
- `simulation/out.bin` - raw simulator output used for comparison. - `simulation/out.bin` - raw functional simulation output used for comparison.
By default, validation also runs non-functional simulation. Per-model `Latency`
is shown in the result table alongside `Power`, with measured, failed, and
skipped counts plus total latency in the summary. Use these validation results
for performance comparisons; the simulation backend does not need to be invoked
separately. The selected pre-generated configs and meshes live under
`validation/pimsim_configs/pimcomp/`.
The compiler currently dumps dialect snapshots such as `spatial0.mlir`, The compiler currently dumps dialect snapshots such as `spatial0.mlir`,
`spatial1_graph.mlir`, `spatial2_trivial_merged.mlir`, `spatial1_graph.mlir`, `spatial2_trivial_merged.mlir`,
@@ -216,8 +228,8 @@ The compiler currently dumps dialect snapshots such as `spatial0.mlir`,
`pim2_folded.mlir`, and `pim3_memory_planned.mlir` when an output directory is `pim2_folded.mlir`, and `pim3_memory_planned.mlir` when an output directory is
available. available.
To rerun the simulator manually with tracing after validation has produced a To rerun the functional simulator manually with tracing after validation has
`raptor/pim/` directory: produced a `raptor/pim/` directory:
```bash ```bash
cd backend-simulators/pim/pim-simulator cd backend-simulators/pim/pim-simulator
@@ -360,7 +372,7 @@ cargo test
- `validation/` - functional validation scripts, ONNX operation tests, network - `validation/` - functional validation scripts, ONNX operation tests, network
slices, and pimsim config generation. slices, and pimsim config generation.
- `backend-simulators/pim/pim-simulator/` - in-tree Rust functional simulator. - `backend-simulators/pim/pim-simulator/` - in-tree Rust functional simulator.
- `backend-simulators/pim/pimsim-nn/` - performance simulator submodule. - `backend-simulators/pim/pimsim-nn/` - non-functional simulator submodule.
- `pimcomp_utils/` - local comparison helpers for PIMCOMP-NN. - `pimcomp_utils/` - local comparison helpers for PIMCOMP-NN.
- `.github/actions/` and `.github/workflows/validate_operations.yml` - CI setup - `.github/actions/` and `.github/workflows/validate_operations.yml` - CI setup
for MLIR/Protobuf caching, building Raptor, and validation. for MLIR/Protobuf caching, building Raptor, and validation.
+1
View File
@@ -1,4 +1,5 @@
colorama>=0.4.6,<1 colorama>=0.4.6,<1
numpy>=1.26.4,<3 numpy>=1.26.4,<3
onnx>=1.17,<2 onnx>=1.17,<2
onnxsim>=0.6.5,<1
-e ./tools/raptor_graph_explorer[dev] -e ./tools/raptor_graph_explorer[dev]
+34 -20
View File
@@ -44,13 +44,18 @@ a35bad96441efbee28699cb61d1656cca7f7281f14040cf01699c3d0cfd8b202 googlenet/goog
## Paper hardware profiles ## Paper hardware profiles
The files in `configs/` encode Table V's explicit resource parameters. The files in
[`../../pimsim_configs/pimcomp/`](../../pimsim_configs/pimcomp/)
encode Table V's explicit resource parameters.
Each profile subdirectory contains pre-generated latency and throughput
`pimsim-nn` configs plus its matching mesh; comparison and validation select
these checked-in artifacts without generating configs at runtime.
| Config | Cores | Crossbars/core | Crossbar | Cell | PIMCOMP layout | | Config | Cores | Crossbars/core | Crossbar | Cell | PIMCOMP layout |
| --- | ---: | ---: | --- | ---: | --- | | --- | ---: | ---: | --- | ---: | --- |
| `arch-a.json` | 168 | 96 | `128x128` | 2-bit | `12x14` | | `arch-a/latency_config.json` | 168 | 96 | `128x128` | 2-bit | `12x14` |
| `arch-b.json` | 138 | 128 | `128x128` | 2-bit | `6x23` | | `arch-b/latency_config.json` | 138 | 128 | `128x128` | 2-bit | `6x23` |
| `arch-c.json` | 64 (16 chips x 4) | 8 | `512x1024` | 2-bit | flattened `8x8` | | `arch-c/latency_config.json` | 64 (16 chips x 4) | 8 | `512x1024` | 2-bit | flattened `8x8` |
`adc_count` is 16, matching the paper's 16-bit fixed-point weight precision. `adc_count` is 16, matching the paper's 16-bit fixed-point weight precision.
The paper does not give a two-dimensional core topology for Arch-A/B, so the The paper does not give a two-dimensional core topology for Arch-A/B, so the
@@ -67,7 +72,7 @@ reproducible, but absolute paper power and energy numbers are not.
From the Raptor repository root: From the Raptor repository root:
```bash ```bash
.venv/bin/python -m pip install numpy onnx onnxruntime onnxsim colorama .venv/bin/python -m pip install -r requirements.txt
cmake --build ./build_release cmake --build ./build_release
cmake --build third_party/PIMCOMP-NN/build --target PIMCOMP-NN cmake --build third_party/PIMCOMP-NN/build --target PIMCOMP-NN
@@ -85,11 +90,11 @@ select one paper profile, and restore it when the shell exits:
```bash ```bash
RAPTOR_ROOT=$PWD RAPTOR_ROOT=$PWD
PIMCOMP="$RAPTOR_ROOT/third_party/PIMCOMP-NN" PIMCOMP="$RAPTOR_ROOT/third_party/PIMCOMP-NN"
PAPER_MODELS="$RAPTOR_ROOT/validation/networks/pimcomp_models" PIMCOMP_CONFIGS="$RAPTOR_ROOT/validation/pimsim_configs/pimcomp"
CONFIG_BACKUP=$(mktemp) CONFIG_BACKUP=$(mktemp)
cp "$PIMCOMP/config.json" "$CONFIG_BACKUP" cp "$PIMCOMP/config.json" "$CONFIG_BACKUP"
trap 'cp "$CONFIG_BACKUP" "$PIMCOMP/config.json"' EXIT trap 'cp "$CONFIG_BACKUP" "$PIMCOMP/config.json"' EXIT
cp "$PAPER_MODELS/configs/arch-a.json" "$PIMCOMP/config.json" cp "$PIMCOMP_CONFIGS/arch-a/latency_config.json" "$PIMCOMP/config.json"
``` ```
The Model Zoo files map exactly to PIMCOMP's bundled model names, so compile The Model Zoo files map exactly to PIMCOMP's bundled model names, so compile
@@ -124,7 +129,8 @@ cd third_party/PIMCOMP-NN/build
./PIMCOMP-NN -m=vgg8_paper_reconstructed -r=balance -p=element -o=YES -v=YES -s=YES ./PIMCOMP-NN -m=vgg8_paper_reconstructed -r=balance -p=element -o=YES -v=YES -s=YES
``` ```
Repeat after selecting `arch-b.json` and `arch-c.json`. All four models were Repeat after selecting `arch-b/latency_config.json` and
`arch-c/latency_config.json`. All four models were
compiled successfully in both modes with all three configs. The released compiled successfully in both modes with all three configs. The released
random placement code occasionally segfaults; an unchanged retry succeeded in random placement code occasionally segfaults; an unchanged retry succeeded in
the observed cases. the observed cases.
@@ -140,8 +146,9 @@ insufficient.
## Compare Raptor and PIMCOMP ## Compare Raptor and PIMCOMP
The comparison driver uses one random input and one native ONNX-MLIR reference, The comparison driver uses one random input and one native ONNX-MLIR reference,
compiles both instruction streams, validates both through Raptor's Rust compiles both instruction streams, runs both through `pimsim-nn`, validates
simulator, and writes Markdown and JSON reports. Raptor through the Rust simulator, and writes Markdown and JSON reports.
PIMCOMP Rust validation also runs when its optional exporter is available.
To reproduce the complete Arch-A latency experiment, use the serial experiment To reproduce the complete Arch-A latency experiment, use the serial experiment
runner. It creates an isolated PIMCOMP build with population 200 and 1000 GA runner. It creates an isolated PIMCOMP build with population 200 and 1000 GA
@@ -156,8 +163,7 @@ comparison driver for one model at a time:
Reports are written under `<out-dir>/<model>/comparison_report.{md,json}`. Reports are written under `<out-dir>/<model>/comparison_report.{md,json}`.
Use `--models vgg8` to run one model, `--resume` after an interruption, or Use `--models vgg8` to run one model, `--resume` after an interruption, or
`--dry-run` to inspect every command. The runner continues after a failed model `--dry-run` to inspect every command. The runner continues after a failed model
so all reports are produced, then returns a nonzero status if any comparison so all reports are produced.
failed.
Arch-A low-latency example: Arch-A low-latency example:
@@ -167,28 +173,34 @@ PIMCOMP="$RAPTOR_ROOT/third_party/PIMCOMP-NN"
CONFIG_BACKUP=$(mktemp) CONFIG_BACKUP=$(mktemp)
cp "$PIMCOMP/config.json" "$CONFIG_BACKUP" cp "$PIMCOMP/config.json" "$CONFIG_BACKUP"
trap 'cp "$CONFIG_BACKUP" "$PIMCOMP/config.json"' EXIT trap 'cp "$CONFIG_BACKUP" "$PIMCOMP/config.json"' EXIT
cp "$RAPTOR_ROOT/validation/networks/pimcomp_models/configs/arch-a.json" "$PIMCOMP/config.json" cp "$RAPTOR_ROOT/validation/pimsim_configs/pimcomp/arch-a/latency_config.json" "$PIMCOMP/config.json"
"$RAPTOR_ROOT/.venv/bin/python" "$RAPTOR_ROOT/validation/tools/compare_raptor_pimcomp.py" \ "$RAPTOR_ROOT/.venv/bin/python" "$RAPTOR_ROOT/validation/tools/compare_raptor_pimcomp.py" \
--model "$RAPTOR_ROOT/validation/networks/pimcomp_models/vgg8/vgg8-mnist-reconstructed.onnx" \ --model "$RAPTOR_ROOT/validation/networks/pimcomp_models/resnet34/resnet34-v1-7.onnx" \
--out-dir /tmp/compare-vgg8-arch-a-ll \ --out-dir /tmp/compare-resnet34-arch-a-ll \
--core-count 168 \ --core-count 168 \
--crossbar-count 96 \ --crossbar-count 96 \
--crossbar-size 128 \ --crossbar-size 128 \
--mesh-rows 12 \ --mesh-rows 12 \
--mesh-cols 14 \ --mesh-cols 14 \
--pimsim-mode latency \ --pimsim-mode latency \
--pimcomp-pipeline element \ --pimcomp-pipeline element
--fail-on-error
``` ```
For Arch-A high throughput, use `--pimsim-mode throughput For Arch-A high throughput, use `--pimsim-mode throughput
--pimcomp-pipeline batch`. For Arch-B, use 138 cores, 128 crossbars, a --pimcomp-pipeline batch`. For Arch-B, use 138 cores, 128 crossbars, a
`6x23` mesh, and `configs/arch-b.json`. `6x23` mesh, and
`validation/pimsim_configs/pimcomp/arch-b/latency_config.json`.
If only semantic and instruction comparison is required, add If only semantic and instruction comparison is required, add
`--skip-pimsim-nn`. This exact VGG-8 Arch-A LL smoke test passed both semantic `--skip-pimsim-nn`. A VGG-8 run with the same Arch-A LL settings passed both
validations with maximum output differences below `5e-10`. semantic validations with maximum output differences below `5e-10`.
The current PIMCOMP-NN submodule does not include the optional
`verification/export_to_pim_simulator.py` helper. The driver therefore records
PIMCOMP Rust semantic validation as skipped, while PIMCOMP compilation,
instruction reporting, and `pimsim-nn` latency still run. Use
`--fail-on-error` only when that semantic export helper is available.
Current Raptor status: Current Raptor status:
@@ -213,6 +225,8 @@ the canonical `resnetv2/depth_68` file may not exist remotely:
REMOTE_REPO=/home/gmagnani/Project/Raptor REMOTE_REPO=/home/gmagnani/Project/Raptor
rsync -azL validation/networks/pimcomp_models/ \ rsync -azL validation/networks/pimcomp_models/ \
"monolith:$REMOTE_REPO/validation/networks/pimcomp_models/" "monolith:$REMOTE_REPO/validation/networks/pimcomp_models/"
rsync -az validation/pimsim_configs/pimcomp/ \
"monolith:$REMOTE_REPO/validation/pimsim_configs/pimcomp/"
rsync -az validation/tools/compare_raptor_pimcomp.py \ rsync -az validation/tools/compare_raptor_pimcomp.py \
"monolith:$REMOTE_REPO/validation/tools/compare_raptor_pimcomp.py" "monolith:$REMOTE_REPO/validation/tools/compare_raptor_pimcomp.py"
rsync -az validation/tools/run_pimcomp_paper_latency.py \ rsync -az validation/tools/run_pimcomp_paper_latency.py \
-3
View File
@@ -32,9 +32,6 @@ Run the complete suite with deadlock detection:
--raptor-path build_release/Release/bin/onnx-mlir \ --raptor-path build_release/Release/bin/onnx-mlir \
--onnx-include-dir onnx-mlir/include \ --onnx-include-dir onnx-mlir/include \
--operations-dir validation/operations \ --operations-dir validation/operations \
--crossbar-count 64 \
--crossbar-size 128 \
--core-count 144 \
--raptor-extra-arg=--pim-detect-communication-deadlock \ --raptor-extra-arg=--pim-detect-communication-deadlock \
--raptor-extra-arg=--pim-export-spatial-dataflow=none --raptor-extra-arg=--pim-export-spatial-dataflow=none
``` ```
@@ -0,0 +1,61 @@
{
"chip_config": {
"core_config": {
"period": 1,
"matrix_config": {
"xbar_array_count": 96,
"period": 1,
"pipeline_mode": true,
"dac_resolution": 1,
"dac_count": 128,
"xbar_size": [
128,
128
],
"cell_precision": 2,
"xbar_latency": 7,
"xbar_read_power": 22.24,
"sample_hold_latency_cycle": 1,
"adc_resolution": 8,
"adc_latency_cycle": 20,
"adc_static_power": 0.322,
"adc_dynamic_power": 1.135,
"adc_count": 16,
"shift_adder_latency_cycle": 1,
"output_buffer_latency_cycle": 1
},
"vector_width": 32,
"vector_latency_cycle": 4,
"local_memory_config": {
"data_width": 64,
"period": 1,
"write_latency_cycle": 30,
"read_latency_cycle": 20
},
"global_memory_switch_id": -10,
"rob_size": 1
},
"global_memory_config": {
"data_width": 32,
"period": 1,
"write_latency_cycle": 50,
"read_latency_cycle": 40
},
"network_config": {
"bus_topology": "mesh",
"bus_width": 32,
"layout": [
12,
14
],
"net_config_file_path": "network_mesh_168.json"
},
"core_cnt": 168,
"global_memory_switch_id": -10
},
"sim_config": {
"sim_mode": 1,
"sim_time": 1000,
"report_verbose_level": 0
}
}
File diff suppressed because one or more lines are too long
@@ -8,7 +8,10 @@
"pipeline_mode": true, "pipeline_mode": true,
"dac_resolution": 1, "dac_resolution": 1,
"dac_count": 128, "dac_count": 128,
"xbar_size": [128, 128], "xbar_size": [
128,
128
],
"cell_precision": 2, "cell_precision": 2,
"xbar_latency": 7, "xbar_latency": 7,
"xbar_read_power": 22.24, "xbar_read_power": 22.24,
@@ -29,7 +32,8 @@
"write_latency_cycle": 30, "write_latency_cycle": 30,
"read_latency_cycle": 20 "read_latency_cycle": 20
}, },
"global_memory_switch_id": -10 "global_memory_switch_id": -10,
"rob_size": 1
}, },
"global_memory_config": { "global_memory_config": {
"data_width": 32, "data_width": 32,
@@ -40,7 +44,10 @@
"network_config": { "network_config": {
"bus_topology": "mesh", "bus_topology": "mesh",
"bus_width": 32, "bus_width": 32,
"layout": [12, 14], "layout": [
12,
14
],
"net_config_file_path": "network_mesh_168.json" "net_config_file_path": "network_mesh_168.json"
}, },
"core_cnt": 168, "core_cnt": 168,
@@ -48,7 +55,7 @@
}, },
"sim_config": { "sim_config": {
"sim_mode": 0, "sim_mode": 0,
"sim_time": 200, "sim_time": 1000,
"report_verbose_level": 0 "report_verbose_level": 0
} }
} }
@@ -0,0 +1,61 @@
{
"chip_config": {
"core_config": {
"period": 1,
"matrix_config": {
"xbar_array_count": 128,
"period": 1,
"pipeline_mode": true,
"dac_resolution": 1,
"dac_count": 128,
"xbar_size": [
128,
128
],
"cell_precision": 2,
"xbar_latency": 7,
"xbar_read_power": 22.24,
"sample_hold_latency_cycle": 1,
"adc_resolution": 8,
"adc_latency_cycle": 20,
"adc_static_power": 0.322,
"adc_dynamic_power": 1.135,
"adc_count": 16,
"shift_adder_latency_cycle": 1,
"output_buffer_latency_cycle": 1
},
"vector_width": 32,
"vector_latency_cycle": 4,
"local_memory_config": {
"data_width": 64,
"period": 1,
"write_latency_cycle": 30,
"read_latency_cycle": 20
},
"global_memory_switch_id": -10,
"rob_size": 1
},
"global_memory_config": {
"data_width": 32,
"period": 1,
"write_latency_cycle": 50,
"read_latency_cycle": 40
},
"network_config": {
"bus_topology": "mesh",
"bus_width": 32,
"layout": [
6,
23
],
"net_config_file_path": "network_mesh_138.json"
},
"core_cnt": 138,
"global_memory_switch_id": -10
},
"sim_config": {
"sim_mode": 1,
"sim_time": 1000,
"report_verbose_level": 0
}
}
File diff suppressed because one or more lines are too long
@@ -8,7 +8,10 @@
"pipeline_mode": true, "pipeline_mode": true,
"dac_resolution": 1, "dac_resolution": 1,
"dac_count": 128, "dac_count": 128,
"xbar_size": [128, 128], "xbar_size": [
128,
128
],
"cell_precision": 2, "cell_precision": 2,
"xbar_latency": 7, "xbar_latency": 7,
"xbar_read_power": 22.24, "xbar_read_power": 22.24,
@@ -29,7 +32,8 @@
"write_latency_cycle": 30, "write_latency_cycle": 30,
"read_latency_cycle": 20 "read_latency_cycle": 20
}, },
"global_memory_switch_id": -10 "global_memory_switch_id": -10,
"rob_size": 1
}, },
"global_memory_config": { "global_memory_config": {
"data_width": 32, "data_width": 32,
@@ -40,7 +44,10 @@
"network_config": { "network_config": {
"bus_topology": "mesh", "bus_topology": "mesh",
"bus_width": 32, "bus_width": 32,
"layout": [6, 23], "layout": [
6,
23
],
"net_config_file_path": "network_mesh_138.json" "net_config_file_path": "network_mesh_138.json"
}, },
"core_cnt": 138, "core_cnt": 138,
@@ -48,7 +55,7 @@
}, },
"sim_config": { "sim_config": {
"sim_mode": 0, "sim_mode": 0,
"sim_time": 200, "sim_time": 1000,
"report_verbose_level": 0 "report_verbose_level": 0
} }
} }
@@ -0,0 +1,61 @@
{
"chip_config": {
"core_config": {
"period": 1,
"matrix_config": {
"xbar_array_count": 8,
"period": 1,
"pipeline_mode": true,
"dac_resolution": 1,
"dac_count": 128,
"xbar_size": [
512,
1024
],
"cell_precision": 2,
"xbar_latency": 7,
"xbar_read_power": 22.24,
"sample_hold_latency_cycle": 1,
"adc_resolution": 8,
"adc_latency_cycle": 20,
"adc_static_power": 0.322,
"adc_dynamic_power": 1.135,
"adc_count": 16,
"shift_adder_latency_cycle": 1,
"output_buffer_latency_cycle": 1
},
"vector_width": 32,
"vector_latency_cycle": 4,
"local_memory_config": {
"data_width": 64,
"period": 1,
"write_latency_cycle": 30,
"read_latency_cycle": 20
},
"global_memory_switch_id": -10,
"rob_size": 1
},
"global_memory_config": {
"data_width": 32,
"period": 1,
"write_latency_cycle": 50,
"read_latency_cycle": 40
},
"network_config": {
"bus_topology": "mesh",
"bus_width": 32,
"layout": [
8,
8
],
"net_config_file_path": "network_mesh_64.json"
},
"core_cnt": 64,
"global_memory_switch_id": -10
},
"sim_config": {
"sim_mode": 1,
"sim_time": 1000,
"report_verbose_level": 0
}
}
File diff suppressed because one or more lines are too long
@@ -8,7 +8,10 @@
"pipeline_mode": true, "pipeline_mode": true,
"dac_resolution": 1, "dac_resolution": 1,
"dac_count": 128, "dac_count": 128,
"xbar_size": [512, 1024], "xbar_size": [
512,
1024
],
"cell_precision": 2, "cell_precision": 2,
"xbar_latency": 7, "xbar_latency": 7,
"xbar_read_power": 22.24, "xbar_read_power": 22.24,
@@ -29,7 +32,8 @@
"write_latency_cycle": 30, "write_latency_cycle": 30,
"read_latency_cycle": 20 "read_latency_cycle": 20
}, },
"global_memory_switch_id": -10 "global_memory_switch_id": -10,
"rob_size": 1
}, },
"global_memory_config": { "global_memory_config": {
"data_width": 32, "data_width": 32,
@@ -40,7 +44,10 @@
"network_config": { "network_config": {
"bus_topology": "mesh", "bus_topology": "mesh",
"bus_width": 32, "bus_width": 32,
"layout": [8, 8], "layout": [
8,
8
],
"net_config_file_path": "network_mesh_64.json" "net_config_file_path": "network_mesh_64.json"
}, },
"core_cnt": 64, "core_cnt": 64,
@@ -48,7 +55,7 @@
}, },
"sim_config": { "sim_config": {
"sim_mode": 0, "sim_mode": 0,
"sim_time": 200, "sim_time": 1000,
"report_verbose_level": 0 "report_verbose_level": 0
} }
} }
+1
View File
@@ -0,0 +1 @@
"""Reusable Raptor validation support."""
@@ -1,7 +1,7 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
import argparse, os, pathlib, textwrap import argparse, os, pathlib, textwrap
from onnx_utils import onnx_io from .onnx_utils import onnx_io
from onnx import TensorProto from onnx import TensorProto
# ONNX dtype -> (ctype, printf, ONNX_TYPE_*) # ONNX dtype -> (ctype, printf, ONNX_TYPE_*)
@@ -207,7 +207,7 @@ int main(int argc, char **argv) {{
}} }}
""" """
def gen_network_runner(network_onnx, network_so, onnx_include_dir, entry="run_main_graph", out=None, verbose=True): def gen_network_runner(network_onnx, network_so, onnx_include_dir, entry, out, verbose):
ins, outs = onnx_io(network_onnx) ins, outs = onnx_io(network_onnx)
out_c = out or "runner.c" out_c = out or "runner.c"
so_abs = os.path.abspath(network_so) so_abs = os.path.abspath(network_so)
@@ -240,4 +240,4 @@ if __name__=="__main__":
ap.add_argument("--out", default=None) ap.add_argument("--out", default=None)
a=ap.parse_args() a=ap.parse_args()
gen_network_runner(a.network_onnx, a.network_so, a.onnx_include_dir, a.entry, a.out) gen_network_runner(a.network_onnx, a.network_so, a.onnx_include_dir, a.entry, a.out, True)
@@ -30,9 +30,12 @@ def onnx_io(path):
return s return s
ins, outs = [], [] ins, outs = [], []
for i, v in enumerate(g.input): initializer_names = {initializer.name for initializer in g.initializer}
for v in g.input:
if v.name in initializer_names:
continue
t = v.type.tensor_type t = v.type.tensor_type
ins.append((i, v.name, t.elem_type, shp(t))) ins.append((len(ins), v.name, t.elem_type, shp(t)))
for i, v in enumerate(g.output): for i, v in enumerate(g.output):
t = v.type.tensor_type t = v.type.tensor_type
outs.append((i, v.name, t.elem_type, shp(t))) outs.append((i, v.name, t.elem_type, shp(t)))
@@ -3,7 +3,7 @@ import shlex
import subprocess import subprocess
from pathlib import Path from pathlib import Path
from colorama import Fore, Style from colorama import Fore, Style
from subprocess_utils import run_command_with_reporter from .subprocess_utils import run_command_with_reporter
PIM_PASS_LABELS = ( PIM_PASS_LABELS = (
("ONNXToSpatialPass", "ONNX to Spatial"), ("ONNXToSpatialPass", "ONNX to Spatial"),
@@ -41,9 +41,8 @@ def _format_command(cmd):
def compile_with_raptor(network_path, raptor_onnx_path: Path, output_base: Path, def compile_with_raptor(network_path, raptor_onnx_path: Path, output_base: Path,
crossbar_size, crossbar_count, core_count=None, crossbar_size, crossbar_count, core_count,
raptor_extra_args=None, cwd=None, verbose=False, raptor_extra_args, cwd, verbose, reporter, timeout_sec):
reporter=None, timeout_sec=None):
# Define the arguments, with the possibility to set crossbar size and count # Define the arguments, with the possibility to set crossbar size and count
args = [ args = [
network_path, network_path,
@@ -1,15 +1,16 @@
import json import json
import os import os
import numpy as np import re
import shutil import shutil
import sys import sys
import numpy as np
from dataclasses import dataclass, field from dataclasses import dataclass, field
from pathlib import Path from pathlib import Path
from colorama import Style, Fore from colorama import Style, Fore
from onnx_utils import gen_random_inputs, save_inputs_to_files, onnx_io, write_inputs_to_memory_bin, _ONNX_TO_NP from .gen_network_runner import gen_network_runner
from raptor import compile_with_raptor from .onnx_utils import gen_random_inputs, save_inputs_to_files, onnx_io, write_inputs_to_memory_bin, _ONNX_TO_NP
from gen_network_runner import gen_network_runner from .raptor import compile_with_raptor
from subprocess_utils import run_command_with_reporter from .subprocess_utils import run_command_with_reporter
STAGE_TITLES = ( STAGE_TITLES = (
"Compile ONNX", "Compile ONNX",
@@ -17,8 +18,9 @@ STAGE_TITLES = (
"Generate Inputs", "Generate Inputs",
"Run Reference", "Run Reference",
"Compile PIM", "Compile PIM",
"Run Simulator", "Run Functional Simulation",
"Compare Outputs", "Compare Outputs",
"Run Non-functional Simulation",
) )
STAGE_COUNT = len(STAGE_TITLES) STAGE_COUNT = len(STAGE_TITLES)
GENERATED_DIR_NAMES = ("inputs", "outputs", "raptor", "runner", "simulation") GENERATED_DIR_NAMES = ("inputs", "outputs", "raptor", "runner", "simulation")
@@ -37,11 +39,19 @@ MODE_STAGE_TITLES = {
MODE_RUN_ONLY: ( MODE_RUN_ONLY: (
"Generate Inputs", "Generate Inputs",
"Run Reference", "Run Reference",
"Run Simulator", "Run Functional Simulation",
"Compare Outputs", "Compare Outputs",
"Run Non-functional Simulation",
), ),
} }
PIMSIM_DONE = "DONE"
PIMSIM_FAILED = "ERROR"
PIMSIM_SKIPPED = "SKIP"
PIMSIM_NOT_RUN = "-"
PIMSIM_LATENCY_RE = re.compile(r"\blatency:\s+([0-9.eE+-]+)\s+ms")
PIMSIM_POWER_RE = re.compile(r"\baverage power:\s+([0-9.eE+-]+)\s+mW")
def sanitize_output_name(name): def sanitize_output_name(name):
return "".join(ch if ch.isalnum() or ch in "_.-" else "_" for ch in name[:255]) return "".join(ch if ch.isalnum() or ch in "_.-" else "_" for ch in name[:255])
@@ -51,6 +61,9 @@ def sanitize_output_name(name):
class ValidationResult: class ValidationResult:
passed: bool passed: bool
pim_pass_timings: dict[str, float] = field(default_factory=dict) pim_pass_timings: dict[str, float] = field(default_factory=dict)
pimsim_latency_ms: float | None = None
pimsim_power_mw: float | None = None
pimsim_status: str = PIMSIM_SKIPPED
class ProgressReporter: class ProgressReporter:
@@ -180,8 +193,70 @@ class ProgressReporter:
self._clear() self._clear()
def run_command(cmd, cwd=None, reporter=None, timeout_sec=None): def run_command(cmd, cwd=None, reporter=None, timeout_sec=None, capture_output=False):
run_command_with_reporter(cmd, cwd=cwd, reporter=reporter, timeout_sec=timeout_sec) return run_command_with_reporter(
cmd,
cwd=cwd,
reporter=reporter,
timeout_sec=timeout_sec,
capture_output=capture_output,
)
def load_pimcomp_hardware(config_path):
with open(config_path, encoding="utf-8") as f:
config = json.load(f)
matrix = config["chip_config"]["core_config"]["matrix_config"]
rows, cols = config["chip_config"]["network_config"]["layout"]
xbar_rows, xbar_cols = matrix["xbar_size"]
return {
"core_count": config["chip_config"]["core_cnt"],
"crossbar_count": matrix["xbar_array_count"],
"crossbar_rows": xbar_rows,
"crossbar_cols": xbar_cols,
"mesh_rows": rows,
"mesh_cols": cols,
}
def pimcomp_compatibility_errors(config_path, *, core_count, crossbar_count, crossbar_size):
hardware = load_pimcomp_hardware(config_path)
errors = []
if hardware["mesh_rows"] * hardware["mesh_cols"] != hardware["core_count"]:
errors.append(
f"config layout {hardware['mesh_rows']}x{hardware['mesh_cols']} does not match "
f"{hardware['core_count']} cores"
)
if core_count != hardware["core_count"]:
errors.append(f"--core-count={core_count}, config requires {hardware['core_count']}")
if crossbar_count != hardware["crossbar_count"]:
errors.append(
f"--crossbar-count={crossbar_count}, config requires {hardware['crossbar_count']}"
)
if (
hardware["crossbar_rows"] != hardware["crossbar_cols"]
or crossbar_size != hardware["crossbar_rows"]
):
errors.append(
f"--crossbar-size={crossbar_size}, config requires "
f"{hardware['crossbar_rows']}x{hardware['crossbar_cols']}"
)
return errors
def run_pimsim_nn(pimsim_nn_build_dir, pim_dir, config_path, reporter=None, timeout_sec=None):
output = run_command(
[pimsim_nn_build_dir / "ChipTest", pim_dir, config_path, "false"],
cwd=pimsim_nn_build_dir,
reporter=reporter,
timeout_sec=timeout_sec,
capture_output=True,
)
latency_match = PIMSIM_LATENCY_RE.search(output)
power_match = PIMSIM_POWER_RE.search(output)
if not latency_match or not power_match:
raise RuntimeError("pimsim-nn output did not contain latency and average power")
return float(latency_match.group(1)), float(power_match.group(1))
def clean_workspace_artifacts(workspace_dir, model_stem): def clean_workspace_artifacts(workspace_dir, model_stem):
@@ -214,6 +289,7 @@ def print_stage(reporter, model_index, model_total, model_name, title):
STAGE_TITLES[4]: Fore.CYAN, STAGE_TITLES[4]: Fore.CYAN,
STAGE_TITLES[5]: Fore.MAGENTA, STAGE_TITLES[5]: Fore.MAGENTA,
STAGE_TITLES[6]: Fore.YELLOW, STAGE_TITLES[6]: Fore.YELLOW,
STAGE_TITLES[7]: Fore.BLUE,
} }
color = stage_colors.get(title, Fore.WHITE) color = stage_colors.get(title, Fore.WHITE)
reporter.log(Style.BRIGHT + color + f"[{title}]" + Style.RESET_ALL) reporter.log(Style.BRIGHT + color + f"[{title}]" + Style.RESET_ALL)
@@ -278,7 +354,7 @@ def parse_pim_simulator_outputs(output_bin_path, outputs_descriptor):
return arrays return arrays
def validate_outputs(sim_arrays, runner_out_dir, outputs_descriptor, threshold=1e-3, rtol=1e-5, verbose=False): def validate_outputs(sim_arrays, runner_out_dir, outputs_descriptor, threshold, rtol, verbose):
all_passed = True all_passed = True
rows = [] rows = []
for sim_array, (oi, name, _, shape) in zip(sim_arrays, outputs_descriptor): for sim_array, (oi, name, _, shape) in zip(sim_arrays, outputs_descriptor):
@@ -312,15 +388,23 @@ def validate_outputs(sim_arrays, runner_out_dir, outputs_descriptor, threshold=1
def validate_network(network_onnx_path, raptor_path, onnx_include_dir, def validate_network(network_onnx_path, raptor_path, onnx_include_dir,
simulator_dir, crossbar_size=128, crossbar_count=64, core_count=144, simulator_dir, crossbar_size, crossbar_count, core_count,
raptor_extra_args=None, raptor_extra_args,
threshold=1e-3, rtol=1e-5, pimsim_nn_build_dir, pimsim_config_path,
seed=0, reporter=None, model_index=1, model_total=1, verbose=False, threshold, rtol,
command_timeout_seconds=60.0, mode=MODE_FULL): seed, reporter, model_index, model_total, verbose,
command_timeout_seconds, mode):
network_onnx_path = Path(network_onnx_path).resolve() network_onnx_path = Path(network_onnx_path).resolve()
raptor_path = Path(raptor_path).resolve() raptor_path = Path(raptor_path).resolve()
onnx_include_dir = Path(onnx_include_dir).resolve() onnx_include_dir = Path(onnx_include_dir).resolve()
simulator_dir = Path(simulator_dir).resolve() simulator_dir = Path(simulator_dir).resolve()
pimsim_enabled = pimsim_nn_build_dir is not None and pimsim_config_path is not None
if pimsim_enabled:
pimsim_nn_build_dir = Path(pimsim_nn_build_dir).resolve()
pimsim_config_path = Path(pimsim_config_path).resolve()
compile_extra_args = list(raptor_extra_args or [])
if pimsim_enabled and "--pim-emit-json" not in compile_extra_args:
compile_extra_args.append("--pim-emit-json")
owns_reporter = reporter is None owns_reporter = reporter is None
reporter = reporter or ProgressReporter(model_total, stages_per_model=len(MODE_STAGE_TITLES[mode]), verbose=verbose) reporter = reporter or ProgressReporter(model_total, stages_per_model=len(MODE_STAGE_TITLES[mode]), verbose=verbose)
@@ -356,8 +440,14 @@ def validate_network(network_onnx_path, raptor_path, onnx_include_dir,
reporter.advance() reporter.advance()
print_stage(reporter, model_index, model_total, network_onnx_path.name, "Build Runner") print_stage(reporter, model_index, model_total, network_onnx_path.name, "Build Runner")
gen_network_runner(network_onnx_path, network_so_path, onnx_include_dir, out=runner_dir / "runner.c", gen_network_runner(
verbose=False) network_onnx_path,
network_so_path,
onnx_include_dir,
entry="run_main_graph",
out=runner_dir / "runner.c",
verbose=False,
)
runner_path = build_onnx_runner(runner_dir, runner_build_dir, reporter=reporter, runner_path = build_onnx_runner(runner_dir, runner_build_dir, reporter=reporter,
timeout_sec=command_timeout_seconds) timeout_sec=command_timeout_seconds)
print_info(reporter, f"Runner built at {runner_path}") print_info(reporter, f"Runner built at {runner_path}")
@@ -368,7 +458,7 @@ def validate_network(network_onnx_path, raptor_path, onnx_include_dir,
pim_pass_timings = compile_with_raptor( pim_pass_timings = compile_with_raptor(
network_onnx_path, raptor_path, pim_output_base, crossbar_size, crossbar_count, network_onnx_path, raptor_path, pim_output_base, crossbar_size, crossbar_count,
core_count=core_count, core_count=core_count,
raptor_extra_args=raptor_extra_args, raptor_extra_args=compile_extra_args,
cwd=raptor_dir, verbose=verbose, reporter=reporter, timeout_sec=command_timeout_seconds) cwd=raptor_dir, verbose=verbose, reporter=reporter, timeout_sec=command_timeout_seconds)
print_info(reporter, f"PIM artifacts saved to {raptor_dir / 'pim'}") print_info(reporter, f"PIM artifacts saved to {raptor_dir / 'pim'}")
reporter.advance() reporter.advance()
@@ -408,12 +498,14 @@ def validate_network(network_onnx_path, raptor_path, onnx_include_dir,
pim_pass_timings = compile_with_raptor( pim_pass_timings = compile_with_raptor(
network_onnx_path, raptor_path, pim_output_base, crossbar_size, crossbar_count, network_onnx_path, raptor_path, pim_output_base, crossbar_size, crossbar_count,
core_count=core_count, core_count=core_count,
raptor_extra_args=raptor_extra_args, raptor_extra_args=compile_extra_args,
cwd=raptor_dir, verbose=verbose, reporter=reporter, timeout_sec=command_timeout_seconds) cwd=raptor_dir, verbose=verbose, reporter=reporter, timeout_sec=command_timeout_seconds)
print_info(reporter, f"PIM artifacts saved to {raptor_dir / 'pim'}") print_info(reporter, f"PIM artifacts saved to {raptor_dir / 'pim'}")
reporter.advance() reporter.advance()
print_stage(reporter, model_index, model_total, network_onnx_path.name, "Run Simulator") print_stage(
reporter, model_index, model_total, network_onnx_path.name,
"Run Functional Simulation")
pim_dir = raptor_dir / "pim" pim_dir = raptor_dir / "pim"
write_inputs_to_memory_bin(pim_dir / "memory.bin", pim_dir / "config.json", inputs_list) write_inputs_to_memory_bin(pim_dir / "memory.bin", pim_dir / "config.json", inputs_list)
simulation_dir = workspace_dir / "simulation" simulation_dir = workspace_dir / "simulation"
@@ -422,7 +514,7 @@ def validate_network(network_onnx_path, raptor_path, onnx_include_dir,
output_bin_path = simulation_dir / "out.bin" output_bin_path = simulation_dir / "out.bin"
run_pim_simulator(simulator_dir, pim_dir, output_bin_path, dump_ranges, reporter=reporter, run_pim_simulator(simulator_dir, pim_dir, output_bin_path, dump_ranges, reporter=reporter,
timeout_sec=command_timeout_seconds) timeout_sec=command_timeout_seconds)
print_info(reporter, f"Simulator output saved to {output_bin_path}") print_info(reporter, f"Functional simulation output saved to {output_bin_path}")
reporter.advance() reporter.advance()
print_stage(reporter, model_index, model_total, network_onnx_path.name, "Compare Outputs") print_stage(reporter, model_index, model_total, network_onnx_path.name, "Compare Outputs")
@@ -431,10 +523,53 @@ def validate_network(network_onnx_path, raptor_path, onnx_include_dir,
passed = validate_outputs(sim_arrays, out_dir, outputs_descriptor, threshold, rtol=rtol, verbose=verbose) passed = validate_outputs(sim_arrays, out_dir, outputs_descriptor, threshold, rtol=rtol, verbose=verbose)
reporter.resume() reporter.resume()
reporter.advance() reporter.advance()
print_stage(
reporter, model_index, model_total, network_onnx_path.name,
"Run Non-functional Simulation")
pimsim_latency_ms = None
pimsim_power_mw = None
pimsim_status = PIMSIM_SKIPPED
if pimsim_enabled:
try:
pimsim_latency_ms, pimsim_power_mw = run_pimsim_nn(
pimsim_nn_build_dir,
pim_dir,
pimsim_config_path,
reporter=reporter,
timeout_sec=command_timeout_seconds,
)
pimsim_status = PIMSIM_DONE
print_info(
reporter,
f"Latency: {pimsim_latency_ms:.6f} ms, "
f"Power: {pimsim_power_mw:.6f} mW")
except Exception as exc:
pimsim_status = PIMSIM_FAILED
reporter.suspend()
print(
Fore.RED
+ f"pimsim-nn non-functional simulation failed: {type(exc).__name__}: {exc}"
+ Style.RESET_ALL,
file=sys.stderr,
flush=True,
)
reporter.resume()
else:
print_info(reporter, "pimsim-nn non-functional simulation skipped")
reporter.advance()
passed = passed and pimsim_status != PIMSIM_FAILED
reporter.record_result(passed) reporter.record_result(passed)
status = Fore.GREEN + "PASS" + Style.RESET_ALL if passed else Fore.RED + "FAIL" + Style.RESET_ALL status = Fore.GREEN + "PASS" + Style.RESET_ALL if passed else Fore.RED + "FAIL" + Style.RESET_ALL
reporter.log(Style.BRIGHT + f"Result: {status}" + Style.RESET_ALL) reporter.log(Style.BRIGHT + f"Result: {status}" + Style.RESET_ALL)
return ValidationResult(passed=passed, pim_pass_timings=pim_pass_timings) return ValidationResult(
passed=passed,
pim_pass_timings=pim_pass_timings,
pimsim_latency_ms=pimsim_latency_ms,
pimsim_power_mw=pimsim_power_mw,
pimsim_status=pimsim_status,
)
except Exception: except Exception:
failed_with_exception = True failed_with_exception = True
reporter.record_result(False) reporter.record_result(False)
@@ -15,8 +15,8 @@ REPO_ROOT = VALIDATION_DIR.parent
if str(VALIDATION_DIR) not in sys.path: if str(VALIDATION_DIR) not in sys.path:
sys.path.insert(0, str(VALIDATION_DIR)) sys.path.insert(0, str(VALIDATION_DIR))
from onnx_utils import _ONNX_TO_NP, onnx_io, write_inputs_to_memory_bin from raptor_validation.onnx_utils import _ONNX_TO_NP, onnx_io, write_inputs_to_memory_bin
from validate_one import ( from raptor_validation.validate_one import (
MODE_COMPILE_ONLY, MODE_COMPILE_ONLY,
build_dump_ranges, build_dump_ranges,
parse_pim_simulator_outputs, parse_pim_simulator_outputs,
@@ -59,6 +59,15 @@ def ensure_local_artifacts(args, model_path: Path):
crossbar_size=args.crossbar_size, crossbar_size=args.crossbar_size,
crossbar_count=args.crossbar_count, crossbar_count=args.crossbar_count,
core_count=args.core_count, core_count=args.core_count,
raptor_extra_args=[],
pimsim_nn_build_dir=None,
pimsim_config_path=None,
threshold=1e-3,
rtol=1e-5,
seed=0,
reporter=None,
model_index=1,
model_total=1,
command_timeout_seconds=args.command_timeout_seconds, command_timeout_seconds=args.command_timeout_seconds,
mode=MODE_COMPILE_ONLY, mode=MODE_COMPILE_ONLY,
verbose=args.verbose, verbose=args.verbose,
+44 -54
View File
@@ -25,12 +25,19 @@ import onnx
REPO = Path(__file__).resolve().parents[2] REPO = Path(__file__).resolve().parents[2]
VALIDATION_DIR = REPO / "validation" VALIDATION_DIR = REPO / "validation"
PIMSIM_CONFIG_DIR = VALIDATION_DIR / "pimsim_configs/pimcomp"
sys.path.insert(0, str(VALIDATION_DIR)) sys.path.insert(0, str(VALIDATION_DIR))
from gen_network_runner import gen_network_runner # noqa: E402 from raptor_validation.gen_network_runner import gen_network_runner # noqa: E402
from onnx_utils import _ONNX_TO_NP, gen_random_inputs, onnx_io, save_inputs_to_files, write_inputs_to_memory_bin # noqa: E402 from raptor_validation.onnx_utils import ( # noqa: E402
from validate_one import build_dump_ranges, parse_pim_simulator_outputs # noqa: E402 _ONNX_TO_NP,
from raptor import compile_with_raptor # noqa: E402 gen_random_inputs,
onnx_io,
save_inputs_to_files,
write_inputs_to_memory_bin,
)
from raptor_validation.raptor import compile_with_raptor # noqa: E402
from raptor_validation.validate_one import build_dump_ranges, parse_pim_simulator_outputs # noqa: E402
@dataclass @dataclass
@@ -62,15 +69,6 @@ def load_pimcomp_exporter():
return module return module
def load_mesh_builder():
path = REPO / "validation/pimsim-configs/generate_mesh_config.py"
spec = importlib.util.spec_from_file_location("mesh_builder", path)
module = importlib.util.module_from_spec(spec)
assert spec is not None and spec.loader is not None
spec.loader.exec_module(module)
return module
def shell_join(cmd: list[str]) -> str: def shell_join(cmd: list[str]) -> str:
return shlex.join(str(arg) for arg in cmd) return shlex.join(str(arg) for arg in cmd)
@@ -278,41 +276,26 @@ def load_effective_hardware(args: argparse.Namespace) -> dict[str, int]:
return hardware return hardware
def write_pimsim_config(args: argparse.Namespace, out_dir: Path, hardware: dict[str, int]) -> Path: def select_pimsim_config(args: argparse.Namespace, hardware: dict[str, int]) -> Path:
mesh_builder = load_mesh_builder() for path in sorted(PIMSIM_CONFIG_DIR.glob(f"*/{args.pimsim_mode}_config.json")):
with open(args.pimcomp_dir / "config.json", "r", encoding="utf-8") as f: with open(path, encoding="utf-8") as f:
config = json.load(f) config = json.load(f)
config["chip_config"]["core_config"].setdefault("rob_size", 1) chip = config["chip_config"]
config["chip_config"]["core_config"]["matrix_config"]["xbar_array_count"] = hardware["crossbar_count"] matrix = chip["core_config"]["matrix_config"]
config["chip_config"]["core_config"]["matrix_config"]["xbar_size"] = [ network = chip["network_config"]
hardware["crossbar_size"], if (
hardware["crossbar_size"], chip["core_cnt"] == hardware["core_count"]
] and matrix["xbar_array_count"] == hardware["crossbar_count"]
config["chip_config"]["network_config"]["layout"] = [ and matrix["xbar_size"] == [hardware["crossbar_size"]] * 2
hardware["mesh_rows"], and network["layout"] == [hardware["mesh_rows"], hardware["mesh_cols"]]
hardware["mesh_cols"], and config["sim_config"]["sim_mode"] == (1 if args.pimsim_mode == "latency" else 0)
] and config["sim_config"]["sim_time"] == args.pimsim_time_ms
config["chip_config"]["network_config"]["net_config_file_path"] = f"network_mesh_{hardware['core_count']}.json" ):
config["chip_config"]["core_cnt"] = hardware["core_count"] return path
config["sim_config"]["sim_mode"] = 1 if args.pimsim_mode == "latency" else 0 raise ValueError(
config["sim_config"]["sim_time"] = args.pimsim_time_ms f"No pre-generated {args.pimsim_mode} pimsim-nn config matches "
out_dir.mkdir(parents=True, exist_ok=True) f"{hardware} with sim_time={args.pimsim_time_ms}"
config_path = out_dir / f"{args.pimsim_mode}_config.json" )
network_path = out_dir / f"network_mesh_{hardware['core_count']}.json"
with open(config_path, "w", encoding="utf-8") as f:
json.dump(config, f, indent=2)
f.write("\n")
with open(network_path, "w", encoding="utf-8") as f:
json.dump(
mesh_builder.build_network(
hardware["core_count"],
(hardware["mesh_rows"], hardware["mesh_cols"]),
),
f,
separators=(",", ":"),
)
f.write("\n")
return config_path
def compile_reference( def compile_reference(
@@ -348,7 +331,14 @@ def compile_reference(
network_so = runner_base.with_suffix(".so") network_so = runner_base.with_suffix(".so")
print_step("Generate Runner Source") print_step("Generate Runner Source")
gen_network_runner(model_path, network_so, args.onnx_include_dir, out=runner_dir / "runner.c", verbose=False) gen_network_runner(
model_path,
network_so,
args.onnx_include_dir,
entry="run_main_graph",
out=runner_dir / "runner.c",
verbose=False,
)
run_logged( run_logged(
"Configure Runner", "Configure Runner",
@@ -427,6 +417,7 @@ def compile_raptor_target(
raptor_extra_args=raptor_extra_args, raptor_extra_args=raptor_extra_args,
cwd=out_dir, cwd=out_dir,
verbose=args.verbose_raptor_compile, verbose=args.verbose_raptor_compile,
reporter=None,
timeout_sec=args.timeout_seconds, timeout_sec=args.timeout_seconds,
) )
except Exception as exc: except Exception as exc:
@@ -1453,18 +1444,17 @@ def main():
else: else:
pimcomp_validation = skipped_validation("Output descriptors are not available") pimcomp_validation = skipped_validation("Output descriptors are not available")
if hardware["core_count"] > 0: if not args.skip_pimsim_nn and hardware["core_count"] > 0:
written_config = try_stage( written_config = try_stage(
failures, failures,
"Write pimsim-nn config", "Select pimsim-nn config",
write_pimsim_config, select_pimsim_config,
args, args,
out_dir / "pimsim_config",
hardware, hardware,
) )
if written_config is not None: if written_config is not None:
pimsim_config = written_config pimsim_config = written_config
else: elif not args.skip_pimsim_nn:
record_failure( record_failure(
failures, failures,
"Skip pimsim-nn config", "Skip pimsim-nn config",
@@ -48,7 +48,10 @@ def prepare_pimcomp(work_dir: Path) -> None:
if replacements != 1: if replacements != 1:
raise RuntimeError("Could not set PIMCOMP GA max_iteration") raise RuntimeError("Could not set PIMCOMP GA max_iteration")
header.write_text(source, encoding="utf-8") header.write_text(source, encoding="utf-8")
shutil.copy2(SUITE / "configs/arch-a.json", work_dir / "config.json") shutil.copy2(
REPO / "validation/pimsim_configs/pimcomp/arch-a/latency_config.json",
work_dir / "config.json",
)
def comparison_command(model: Path, result_dir: Path, pimcomp_dir: Path, timeout: float) -> list[str]: def comparison_command(model: Path, result_dir: Path, pimcomp_dir: Path, timeout: float) -> list[str]:
@@ -79,7 +82,6 @@ def comparison_command(model: Path, result_dir: Path, pimcomp_dir: Path, timeout
"GA", "GA",
"--timeout-seconds", "--timeout-seconds",
str(timeout), str(timeout),
"--fail-on-error",
] ]
@@ -21,8 +21,14 @@ if sys.version_info < (3, 10):
"Run it with a newer interpreter, for example your project venv Python." "Run it with a newer interpreter, for example your project venv Python."
) )
from onnx_utils import _ONNX_TO_NP, onnx_io, write_inputs_to_memory_bin from raptor_validation.onnx_utils import _ONNX_TO_NP, onnx_io, write_inputs_to_memory_bin
from validate_one import MODE_COMPILE_ONLY, build_dump_ranges, run_pim_simulator, sanitize_output_name, validate_network from raptor_validation.validate_one import (
MODE_COMPILE_ONLY,
build_dump_ranges,
run_pim_simulator,
sanitize_output_name,
validate_network,
)
from yolo_real_image_validation import ( from yolo_real_image_validation import (
IMAGE_CASES, IMAGE_CASES,
decode_yolo_output, decode_yolo_output,
@@ -80,6 +86,15 @@ def ensure_local_artifacts(args, network_onnx_path: Path):
crossbar_size=args.crossbar_size, crossbar_size=args.crossbar_size,
crossbar_count=args.crossbar_count, crossbar_count=args.crossbar_count,
core_count=args.core_count, core_count=args.core_count,
raptor_extra_args=[],
pimsim_nn_build_dir=None,
pimsim_config_path=None,
threshold=1e-3,
rtol=1e-5,
seed=0,
reporter=None,
model_index=1,
model_total=1,
command_timeout_seconds=args.command_timeout_seconds, command_timeout_seconds=args.command_timeout_seconds,
mode=MODE_COMPILE_ONLY, mode=MODE_COMPILE_ONLY,
verbose=args.verbose, verbose=args.verbose,
+122 -21
View File
@@ -6,16 +6,26 @@ import subprocess
import sys import sys
from pathlib import Path from pathlib import Path
from colorama import Style, Fore from colorama import Style, Fore
from validate_one import ( from raptor_validation.validate_one import (
MODE_COMPILE_ONLY, MODE_COMPILE_ONLY,
MODE_FULL, MODE_FULL,
MODE_RUN_ONLY, MODE_RUN_ONLY,
MODE_STAGE_TITLES, MODE_STAGE_TITLES,
PIMSIM_DONE,
PIMSIM_FAILED,
PIMSIM_NOT_RUN,
PIMSIM_SKIPPED,
ProgressReporter, ProgressReporter,
ValidationResult,
clean_workspace_artifacts, clean_workspace_artifacts,
load_pimcomp_hardware,
pimcomp_compatibility_errors,
validate_network, validate_network,
) )
from raptor import PIM_PASS_LABELS from raptor_validation.raptor import PIM_PASS_LABELS
DEFAULT_PIMCOMP_CONFIG = "arch-a"
PIMCOMP_CONFIG_CHOICES = ("arch-a", "arch-b", "arch-c")
def format_return_status(returncode): def format_return_status(returncode):
@@ -60,41 +70,70 @@ def print_average_pim_pass_timings(pass_timing_sums, pass_timing_counts, total_t
print(f" {'Total'.ljust(28)} {total_timing_sum / timed_benchmark_count:.4f}s") print(f" {'Total'.ljust(28)} {total_timing_sum / timed_benchmark_count:.4f}s")
def format_pimsim_metric(result, value, unit):
if result.pimsim_status == PIMSIM_DONE:
return f"{value:.6f} {unit}"
return result.pimsim_status
def main(): def main():
script_dir = Path(__file__).parent.resolve()
pimcomp_configs_dir = script_dir / "pimsim_configs" / "pimcomp"
default_pimsim_config = pimcomp_configs_dir / DEFAULT_PIMCOMP_CONFIG / "latency_config.json"
default_hardware = load_pimcomp_hardware(default_pimsim_config)
ap = argparse.ArgumentParser(description="Validate all ONNX operations under the operations/ directory.") ap = argparse.ArgumentParser(description="Validate all ONNX operations under the operations/ directory.")
ap.add_argument("--raptor-path", help="Path to the Raptor compiler binary.") ap.add_argument("--raptor-path", help="Path to the Raptor compiler binary.")
ap.add_argument("--onnx-include-dir", help="Path to OnnxMlirRuntime include directory.") ap.add_argument("--onnx-include-dir", help="Path to OnnxMlirRuntime include directory.")
ap.add_argument("--operations-dir", default=None, help="Root of the operations tree (default: operations).") ap.add_argument("--operations-dir", default=None, help="Root of the operations tree (default: operations).")
ap.add_argument("--simulator-dir", default=None, ap.add_argument("--simulator-dir", default=None,
help="Path to pim-simulator crate root (default: auto-detected relative to script).") help="Path to the functional pim-simulator crate root "
"(default: auto-detected relative to script).")
ap.add_argument("--non-functional-simulator-build-dir", metavar="PATH", default=None,
help="Path to the non-functional simulator build directory "
"(default: auto-detected relative to script).")
ap.add_argument("--pimcomp-config", choices=PIMCOMP_CONFIG_CHOICES, default=DEFAULT_PIMCOMP_CONFIG,
help="Hardware/timing profile for non-functional simulation "
"(default: arch-a).")
ap.add_argument("--skip-non-functional-simulation", action="store_true",
help="Skip non-functional simulation.")
ap.add_argument("--threshold", type=float, default=1e-3, ap.add_argument("--threshold", type=float, default=1e-3,
help="Absolute tolerance for per-element output comparison.") help="Absolute tolerance for per-element output comparison.")
ap.add_argument("--relative-threshold", type=float, default=1e-5, ap.add_argument("--relative-threshold", type=float, default=1e-5,
help="Relative tolerance for per-element output comparison.") help="Relative tolerance for per-element output comparison.")
ap.add_argument("--seed", type=int, default=0, help="RNG seed for generated validation inputs.") ap.add_argument("--seed", type=int, default=0, help="RNG seed for generated validation inputs.")
ap.add_argument("--crossbar-size", type=int, default=128) ap.add_argument("--crossbar-size", type=int, default=default_hardware["crossbar_rows"],
ap.add_argument("--crossbar-count", type=int, default=64) help=f"Crossbar size (default: {default_hardware['crossbar_rows']}).")
ap.add_argument("--core-count", type=int, default=144) ap.add_argument("--crossbar-count", type=int, default=default_hardware["crossbar_count"],
help=f"Crossbars per core (default: {default_hardware['crossbar_count']}).")
ap.add_argument("--core-count", type=int, default=default_hardware["core_count"],
help=f"Core count (default: {default_hardware['core_count']}).")
ap.add_argument("--raptor-extra-arg", action="append", default=[], ap.add_argument("--raptor-extra-arg", action="append", default=[],
help="Additional argument to pass through to the Raptor compiler. Repeat as needed.") help="Additional argument to pass through to the Raptor compiler. Repeat as needed.")
ap.add_argument("--command-timeout-seconds", type=float, default=1000000.0, ap.add_argument("--command-timeout-seconds", type=float, default=1000000.0,
help="Per-subprocess timeout in seconds for compiler, runner, and simulator commands.") help="Per-subprocess timeout in seconds for compiler, runner, and simulation commands.")
ap.add_argument("--clean", action="store_true", ap.add_argument("--clean", action="store_true",
help="Remove generated validation artifacts under each model workspace and exit.") help="Remove generated validation artifacts under each model workspace and exit.")
mode_group = ap.add_mutually_exclusive_group() mode_group = ap.add_mutually_exclusive_group()
mode_group.add_argument("--compile-only", action="store_true", mode_group.add_argument("--compile-only", action="store_true",
help="Compile reference and PIM artifacts only; do not run reference, simulator, or compare.") help="Compile reference and PIM artifacts only; do not run reference execution, "
"simulations, or comparison.")
mode_group.add_argument("--run-only", action="store_true", mode_group.add_argument("--run-only", action="store_true",
help="Reuse existing compiled artifacts and only run inputs/reference/simulator/compare.") help="Reuse existing compiled artifacts and only run inputs, reference execution, "
"simulations, and comparison.")
ap.add_argument("--verbose", action="store_true", ap.add_argument("--verbose", action="store_true",
help="Print per-stage progress and subprocess logs for passing validations too.") help="Print per-stage progress and subprocess logs for passing validations too.")
a = ap.parse_args() a = ap.parse_args()
script_dir = Path(__file__).parent.resolve()
operations_dir = Path(a.operations_dir).resolve() if a.operations_dir else script_dir / "operations" operations_dir = Path(a.operations_dir).resolve() if a.operations_dir else script_dir / "operations"
simulator_dir = Path(a.simulator_dir).resolve() if a.simulator_dir else ( simulator_dir = Path(a.simulator_dir).resolve() if a.simulator_dir else (
script_dir / ".." / "backend-simulators" / "pim" / "pim-simulator" script_dir / ".." / "backend-simulators" / "pim" / "pim-simulator"
) )
pimsim_nn_build_dir = (
Path(a.non_functional_simulator_build_dir).resolve()
if a.non_functional_simulator_build_dir else
script_dir / ".." / "backend-simulators" / "pim" / "pimsim-nn" / "build"
)
pimsim_config_path = pimcomp_configs_dir / a.pimcomp_config / "latency_config.json"
if not operations_dir.is_dir(): if not operations_dir.is_dir():
print(Fore.RED + f"Operations directory not found: {operations_dir}" + Style.RESET_ALL) print(Fore.RED + f"Operations directory not found: {operations_dir}" + Style.RESET_ALL)
@@ -130,7 +169,26 @@ def main():
elif a.run_only: elif a.run_only:
mode = MODE_RUN_ONLY mode = MODE_RUN_ONLY
results = {} # relative_path -> passed selected_pimsim_config = None
if not a.skip_non_functional_simulation:
compatibility_errors = pimcomp_compatibility_errors(
pimsim_config_path,
core_count=a.core_count,
crossbar_count=a.crossbar_count,
crossbar_size=a.crossbar_size,
)
if compatibility_errors:
print(
Fore.RED
+ "Non-functional simulation disabled: "
+ "; ".join(compatibility_errors)
+ Style.RESET_ALL,
file=sys.stderr,
)
else:
selected_pimsim_config = pimsim_config_path
results = {} # relative_path -> ValidationResult
pass_timing_sums = {label: 0.0 for _, label in PIM_PASS_LABELS} pass_timing_sums = {label: 0.0 for _, label in PIM_PASS_LABELS}
pass_timing_counts = {label: 0 for _, label in PIM_PASS_LABELS} pass_timing_counts = {label: 0 for _, label in PIM_PASS_LABELS}
total_timing_sum = 0.0 total_timing_sum = 0.0
@@ -143,6 +201,8 @@ def main():
onnx_path, a.raptor_path, a.onnx_include_dir, simulator_dir, onnx_path, a.raptor_path, a.onnx_include_dir, simulator_dir,
crossbar_size=a.crossbar_size, crossbar_count=a.crossbar_count, core_count=a.core_count, crossbar_size=a.crossbar_size, crossbar_count=a.crossbar_count, core_count=a.core_count,
raptor_extra_args=a.raptor_extra_arg, raptor_extra_args=a.raptor_extra_arg,
pimsim_nn_build_dir=pimsim_nn_build_dir,
pimsim_config_path=selected_pimsim_config,
command_timeout_seconds=a.command_timeout_seconds, command_timeout_seconds=a.command_timeout_seconds,
threshold=a.threshold, threshold=a.threshold,
rtol=a.relative_threshold, rtol=a.relative_threshold,
@@ -153,7 +213,7 @@ def main():
verbose=a.verbose, verbose=a.verbose,
mode=mode, mode=mode,
) )
results[str(rel)] = result.passed results[str(rel)] = result
if result.pim_pass_timings: if result.pim_pass_timings:
benchmark_total = 0.0 benchmark_total = 0.0
for label, duration in result.pim_pass_timings.items(): for label, duration in result.pim_pass_timings.items():
@@ -163,32 +223,73 @@ def main():
total_timing_sum += benchmark_total total_timing_sum += benchmark_total
timed_benchmark_count += 1 timed_benchmark_count += 1
except subprocess.CalledProcessError as exc: except subprocess.CalledProcessError as exc:
results[str(rel)] = False results[str(rel)] = ValidationResult(False, pimsim_status=PIMSIM_NOT_RUN)
print_validation_error(reporter, rel, exc) print_validation_error(reporter, rel, exc)
except Exception as exc: except Exception as exc:
results[str(rel)] = False results[str(rel)] = ValidationResult(False, pimsim_status=PIMSIM_NOT_RUN)
print_validation_error(reporter, rel, exc) print_validation_error(reporter, rel, exc)
reporter.finish() reporter.finish()
# Summary # Summary
n_passed = sum(1 for passed in results.values() if passed) n_passed = sum(1 for result in results.values() if result.passed)
n_total = len(results) n_total = len(results)
status_width = len("Result") status_width = len("Result")
path_width = max(len("Operation"), *(len(rel) for rel in results)) path_width = max(len("Operation"), *(len(rel) for rel in results))
separator = f"+-{'-' * path_width}-+-{'-' * status_width}-+" formatted_metrics = {
rel: (
format_pimsim_metric(result, result.pimsim_latency_ms, "ms"),
format_pimsim_metric(result, result.pimsim_power_mw, "mW"),
)
for rel, result in results.items()
}
latency_width = max(len("Latency"), *(len(metrics[0]) for metrics in formatted_metrics.values()))
power_width = max(len("Power"), *(len(metrics[1]) for metrics in formatted_metrics.values()))
separator = (
f"+-{'-' * path_width}-+-{'-' * status_width}-+-{'-' * latency_width}"
f"-+-{'-' * power_width}-+")
print(separator) print(separator)
print(f"| {'Operation'.ljust(path_width)} | {'Result'.ljust(status_width)} |") print(
f"| {'Operation'.ljust(path_width)} | {'Result'.ljust(status_width)} | "
f"{'Latency'.ljust(latency_width)} | {'Power'.ljust(power_width)} |"
)
print(separator) print(separator)
for rel, passed in results.items(): for rel, result in results.items():
plain_status = "PASS" if passed else "FAIL" plain_status = "PASS" if result.passed else "FAIL"
status = Fore.GREEN + plain_status.ljust(status_width) + Style.RESET_ALL if passed else \ status = Fore.GREEN + plain_status.ljust(status_width) + Style.RESET_ALL if result.passed else \
Fore.RED + plain_status.ljust(status_width) + Style.RESET_ALL Fore.RED + plain_status.ljust(status_width) + Style.RESET_ALL
print(f"| {rel.ljust(path_width)} | {status} |") latency, power = formatted_metrics[rel]
print(
f"| {rel.ljust(path_width)} | {status} | {latency.ljust(latency_width)} | "
f"{power.ljust(power_width)} |")
print(separator) print(separator)
print("\n" + Style.BRIGHT + Fore.CYAN + "Summary" + Style.RESET_ALL) print("\n" + Style.BRIGHT + Fore.CYAN + "Summary" + Style.RESET_ALL)
print(Style.BRIGHT + f"Passed: {n_passed}" + Style.RESET_ALL) print(Style.BRIGHT + f"Passed: {n_passed}" + Style.RESET_ALL)
print(Style.BRIGHT + f"Failed: {n_total - n_passed}" + Style.RESET_ALL) print(Style.BRIGHT + f"Failed: {n_total - n_passed}" + Style.RESET_ALL)
measured_latencies = [
result.pimsim_latency_ms
for result in results.values()
if result.pimsim_status == PIMSIM_DONE
]
pimsim_failed = sum(
result.pimsim_status == PIMSIM_FAILED for result in results.values()
)
pimsim_skipped = sum(
result.pimsim_status in (PIMSIM_SKIPPED, PIMSIM_NOT_RUN)
for result in results.values()
)
print(
Style.BRIGHT
+ f"pimsim-nn: {len(measured_latencies)} measured, "
f"{pimsim_failed} failed, {pimsim_skipped} skipped"
+ Style.RESET_ALL
)
if measured_latencies:
print(
Style.BRIGHT
+ f"Total latency: {sum(measured_latencies):.6f} ms"
+ Style.RESET_ALL
)
if a.verbose: if a.verbose:
print_average_pim_pass_timings( print_average_pim_pass_timings(
pass_timing_sums, pass_timing_sums,