Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@ toolchain/**/*/
package.json
package-lock.json
node_modules
**/.venv/
*.sif

# Documentation
docs/_autosummary
Expand Down
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ This file contains the changelog for the Deeploy project. The changelog is divid
- Added GAP9 Platform Support: Deployer, Bindings, Templates, Tiler, DMA (L3Dma/MchanDma), target library, CI workflows
- Per-layer microbenchmarking on PULPOpen via `--profileMicrobenchmark`: new `PULPMicrobenchmark` code-transformation pass + `perf_utils.h` helpers report cycles, instructions, stalls and cache misses per layer in `RunNetwork`
- Add support for the Generic target for the following operators [Ceil](https://onnx.ai/onnx/operators/onnx__Ceil.html), [Floor](https://onnx.ai/onnx/operators/onnx__Floor.html), [Clip](https://onnx.ai/onnx/operators/onnx__Clip.html), [Sub](https://onnx.ai/onnx/operators/onnx__Sub.html), [Exp](https://onnx.ai/onnx/operators/onnx__Exp.html), [Sigmoid](https://onnx.ai/onnx/operators/onnx__Sigmoid.html), [Swish](https://onnx.ai/onnx/operators/onnx__Swish.html), [HardSigmoid](https://onnx.ai/onnx/operators/onnx__HardSigmoid.html), [HardSwish](https://onnx.ai/onnx/operators/onnx__HardSwish.html), [InstanceNormalization](https://onnx.ai/onnx/operators/onnx__InstanceNormalization.html), [GroupNormalization](https://onnx.ai/onnx/operators/onnx__GroupNormalization.html), [AveragePool](https://onnx.ai/onnx/operators/onnx__AveragePool.html), [GlobalAveragePool](https://onnx.ai/onnx/operators/onnx__GlobalAveragePool.html), [GlobalMaxPool](https://onnx.ai/onnx/operators/onnx__GlobalMaxPool.html).
- Document that `--profileTiling` currently crashes GVSoC on the larger microLlama graphs (invalid access), so students recognise it as a known bug rather than their own mistake

### Changed
- `aie.dialects` API: move `link_with` from `aie_d.core()` to `aie_d.external_func()` (mlir-aie v1.3.2)
Expand Down Expand Up @@ -67,6 +68,10 @@ This file contains the changelog for the Deeploy project. The changelog is divid
- Reduce RunNetwork stack usage by scoping per-layer variables with braces and moving tileIdxPtr allocation into per-layer execution blocks
- Fix invalid escape sequence python error in DeeployTypes.py: appearing when using pytest to launch regressions
- Fix GAP9 board tests with `--defaultMemLevel L3` reading garbage inputs: place all gapy `--flash-property` options before the positional subcommand and use `image flash run` so the readfs partition (input hex files) is flashed to the device
- Forward `--neureka-wmem` and `--enable-3x3` from `deeployRunner` to the generator. Both were accepted by argparse and then dropped, so enabling the Neureka weight memory produced byte-identical cycle counts; `microLlama64` autoregressive now improves from 776k to 712k cycles with the flag
- Fix Part III `deploy.sh` half-applying its `Platform.py` patch: the parser-import replacement matched a single-line needle that no longer exists (the import block is line-wrapped), so the mapper was injected without its import and every PULP runner failed with `NameError: name 'iLeakyReLUParser' is not defined`. The patch is now anchored on the mapper definition and fails loudly if that anchor disappears
- Fix Part III Step 6a to use `addTileSizeDivisibleConstraint` instead of `addMinTileSizeConstraint`; the latter only forces the leftover tile to be at least `modulo` elements and gives no divisibility guarantee, so the documented multiple-of-16 SIMD alignment did not hold
- Fix Deeploy 101 and Part III tutorial errors: `--profileTiling` is a valueless flag (`--profileTiling=L2` aborts argparse), the intrinsics inventory path moved to `TargetLibraries/third_party/`, Step 2 needs `cd ../../../DeeployTest`, and the Step 4/5 solutions were missing their `Platform.py` imports

### Removed
- `testDMA.py` was an old test; we now have `test_dmas.py` instead.
Expand Down
7 changes: 7 additions & 0 deletions DeeployTest/testUtils/deeployRunner.py
Original file line number Diff line number Diff line change
Expand Up @@ -253,6 +253,13 @@ def create_config_from_args(args: argparse.Namespace,
if getattr(args, 'profileMicrobenchmark', False):
gen_args_list.append("--profileMicrobenchmark")

# Neureka-specific switches
if getattr(args, 'neureka_wmem', False):
gen_args_list.append("--neureka-wmem")

if getattr(args, 'enable_3x3', False):
gen_args_list.append("--enable-3x3")

config = DeeployTestConfig(
test_name = test_name,
test_dir = test_dir_abs,
Expand Down
Binary file added Tutorials/Images/DeeploySystem.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added Tutorials/Images/EthLogoNeg.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added Tutorials/Images/Siracusa.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
23 changes: 23 additions & 0 deletions Tutorials/PartIII_skeletons/iLeakyReLU/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
# SoCDAML Part III - Student skeletons for `iLeakyReLU`

These files are your starting points for the Part III lab. Each one
contains the surrounding boilerplate; the conceptually interesting
parts are marked with `TODO(student)` comments and short hints.

| File | What's in it | What to do |
|------|--------------|------------|
| `generate.py` | Complete ONNX + golden-value generator | Run it (Step 1) |
| `iLeakyReLU.h` | Complete kernel header | Copy to `TargetLibraries/PULPOpen/inc/kernel/` (Step 3) |
| `iLeakyReLU.c` | Multi-core chunking provided; inner loop TODO | Fill the TODO, copy to `TargetLibraries/PULPOpen/src/` (Step 3) |
| `iLeakyReLU_simd.c` | SIMD chunking + load/max/store provided; one TODO line | Fill in Step 6b after the scalar works |

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Align the SIMD description with the skeleton.

Line 12 says load/max/store are provided, but iLeakyReLU_simd.c leaves the packed shift, max, and final store for the student. Update this row so the lab instructions match the actual file.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@Tutorials/PartIII_skeletons/iLeakyReLU/README.md` at line 12, Update the
iLeakyReLU_simd.c entry in the README table to state that packed shift, max, and
final store remain TODOs for the student, while retaining the existing SIMD
chunking context and Step 6b guidance.

| `iLeakyReLUParser.py` | `parseNode` and `parseNodeCtxt` are TODO | Fill in, paste class into `Deeploy/Targets/Generic/Parsers.py` (Step 2) |
| `iLeakyReLUTemplate.py` | Mako template body is TODO | Fill in, copy to `Deeploy/Targets/PULPOpen/Templates/` (Step 4) |
| `iLeakyReLUTileConstraint.py` | Inherits `UnaryTileConstraint`; performance constraint TODO | Fill in (Step 5 + Step 6a), copy to `Deeploy/Targets/PULPOpen/TileConstraints/` |

The `docs/tutorials/introduction.md` ("Adding a New Operator")
walks through the six steps in order and includes collapsed solutions to
peek at when you're stuck.

If you really need the answer key, look in
`Deeploy/Tutorials/PartIII_solution/iLeakyReLU/`, but try the lab
first; you'll learn far more.
62 changes: 62 additions & 0 deletions Tutorials/PartIII_skeletons/iLeakyReLU/generate.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
#!/usr/bin/env python3
# ----------------------------------------------------------------------
# File: generate.py (SoCDAML Part III - Step 1, provided complete)
#
# Builds the single-node ONNX graph + golden tensors that DeeployTest's
# harness will use to validate your iLeakyReLU implementation.
#
# Run from this directory:
# python generate.py
#
# Outputs:
# network.onnx, inputs.npz, outputs.npz
#
# Quantization-friendly LeakyReLU formula used here:
# out[i] = x if x >= 0
# (mul*x) >> shift otherwise
# ----------------------------------------------------------------------
# SPDX-FileCopyrightText: 2026 ETH Zurich and University of Bologna
#
# SPDX-License-Identifier: Apache-2.0

import numpy as np
import onnx
from onnx import TensorProto, helper

SHAPE = (1, 16, 64, 64)
MUL = 1
SHIFT = 3
SEED = 0xC0FFEE


def golden(x, mul, shift):
pos = x.astype(np.int32)
neg = (mul * pos) >> shift
out = np.where(pos >= 0, pos, neg)
return np.clip(out, -128, 127).astype(np.int8)


def build_onnx():
in_value = helper.make_tensor_value_info('data_in', TensorProto.INT8, SHAPE)
out_value = helper.make_tensor_value_info('data_out', TensorProto.INT8, SHAPE)
node = helper.make_node('iLeakyReLU', ['data_in'], ['data_out'], name = 'iLeakyReLU_0', mul = MUL, shift = SHIFT)
graph = helper.make_graph([node], 'iLeakyReLU_single_node', [in_value], [out_value])
model = helper.make_model(graph, producer_name = 'SoCDAML-PartIII')
model.opset_import[0].version = 13
model.ir_version = 7
return model


def main():
rng = np.random.default_rng(SEED)
x = rng.integers(low = -128, high = 127, size = SHAPE, dtype = np.int8)
y = golden(x, MUL, SHIFT)
onnx.save(build_onnx(), 'network.onnx')
np.savez('inputs.npz', data_in = x)
np.savez('outputs.npz', data_out = y)
Comment on lines +55 to +56

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Write Deeploy-compatible NPZ artifacts.

The generator currently writes data_in/data_out as int8. The reference generator and Deeploy harness expect input/output keys stored as int64; otherwise Step 1 can fail before kernel validation.

Suggested fix
-    np.savez('inputs.npz', data_in = x)
-    np.savez('outputs.npz', data_out = y)
+    np.savez('inputs.npz', input = x.astype(np.int64))
+    np.savez('outputs.npz', output = y.astype(np.int64))
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
np.savez('inputs.npz', data_in = x)
np.savez('outputs.npz', data_out = y)
np.savez('inputs.npz', input = x.astype(np.int64))
np.savez('outputs.npz', output = y.astype(np.int64))
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@Tutorials/PartIII_skeletons/iLeakyReLU/generate.py` around lines 55 - 56,
Update the NPZ generation in the tutorial script to save the arrays under the
Deeploy-compatible keys input and output, converting both x and y to int64
before writing. Preserve the existing input/output data generation while
replacing data_in/data_out and int8 storage.

print(f"OK: network.onnx, inputs.npz, outputs.npz "
f"(shape={SHAPE}, mul={MUL}, shift={SHIFT})")


if __name__ == '__main__':
main()
36 changes: 36 additions & 0 deletions Tutorials/PartIII_skeletons/iLeakyReLU/iLeakyReLU.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
/* =====================================================================
* Title: iLeakyReLU.c (SoCDAML Part III - Step 3 skeleton)
*
* Plain-C int8 LeakyReLU. The per-core chunking boilerplate is provided.
* Fill in the inner loop body marked `TODO(student)`.
*
* Goal: out[i] = (in[i] >= 0) ? in[i] : ((mul * in[i]) >> shift)
*
* Hints:
* - Cast in[i] to int32_t before the multiply to avoid 8-bit overflow.
* - Cast the final result back to int8_t before storing.
*
* Drop into: TargetLibraries/PULPOpen/src/iLeakyReLU.c
* ===================================================================== */
/* SPDX-FileCopyrightText: 2026 ETH Zurich and University of Bologna
*
* SPDX-License-Identifier: Apache-2.0
*/

#include "DeeployPULPMath.h"
#include "pmsis.h"

void PULPiLeakyReLU_i8_i8(int8_t *pIn, int8_t *pOut, uint32_t size, int32_t mul,
int32_t shift) {
uint32_t cid = pi_core_id();
uint32_t nC = NUM_CORES;
uint32_t per = (size + nC - 1) / nC;
uint32_t start = cid * per;
uint32_t end = (start + per > size) ? size : (start + per);

for (uint32_t i = start; i < end; i++) {
// TODO(student): compute pOut[i] from pIn[i], mul, shift.
// Replace the following line:
pOut[i] = 0;
}
}
21 changes: 21 additions & 0 deletions Tutorials/PartIII_skeletons/iLeakyReLU/iLeakyReLU.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
/* =====================================================================
* Title: iLeakyReLU.h (SoCDAML Part III - Step 3, provided)
*
* Header for the iLeakyReLU PULP kernel.
* Drop into: TargetLibraries/PULPOpen/inc/kernel/iLeakyReLU.h
* and add `#include "kernel/iLeakyReLU.h"` to DeeployPULPMath.h.
* ===================================================================== */
/* SPDX-FileCopyrightText: 2026 ETH Zurich and University of Bologna
*
* SPDX-License-Identifier: Apache-2.0
*/

#ifndef __DEEPLOY_KERNEL_ILEAKYRELU_H_
#define __DEEPLOY_KERNEL_ILEAKYRELU_H_

#include "DeeployPULPMath.h"

void PULPiLeakyReLU_i8_i8(int8_t *pIn, int8_t *pOut, uint32_t size, int32_t mul,
int32_t shift);

#endif // __DEEPLOY_KERNEL_ILEAKYRELU_H_
35 changes: 35 additions & 0 deletions Tutorials/PartIII_skeletons/iLeakyReLU/iLeakyReLUParser.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
# ----------------------------------------------------------------------
# File: iLeakyReLUParser.py (SoCDAML Part III - Step 2 skeleton)
#
# Paste this class into:
# Deeploy/Targets/Generic/Parsers.py
#
# Imports already present in that file (math, numpy as np,
# onnx_graphsurgeon as gs, NodeParser, NetworkContext).
# ----------------------------------------------------------------------
# SPDX-FileCopyrightText: 2026 ETH Zurich and University of Bologna
#
# SPDX-License-Identifier: Apache-2.0


class iLeakyReLUParser(NodeParser):

def __init__(self):
super().__init__()

def parseNode(self, node: gs.Node) -> bool:
# TODO(student): return False if the node doesn't have exactly
# one input, exactly one output, and both 'mul' and 'shift'
# attributes. On success, store them into
# self.operatorRepresentation as ints and return True.
return False

def parseNodeCtxt(self, ctxt: NetworkContext, node: gs.Node, channels_first: bool = True):
# TODO(student): look up the input and output tensors from ctxt
# using node.inputs[0].name / node.outputs[0].name, and populate
# self.operatorRepresentation with:
# 'data_in' -> input tensor name
# 'data_out' -> output tensor name
# 'size' -> int(np.prod(input_shape))
# Return (ctxt, True).
return ctxt, False
30 changes: 30 additions & 0 deletions Tutorials/PartIII_skeletons/iLeakyReLU/iLeakyReLUTemplate.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
# ----------------------------------------------------------------------
# File: iLeakyReLUTemplate.py (SoCDAML Part III - Step 4 skeleton)
#
# Drop this file into:
# Deeploy/Targets/PULPOpen/Templates/iLeakyReLUTemplate.py
# ----------------------------------------------------------------------
# SPDX-FileCopyrightText: 2026 ETH Zurich and University of Bologna
#
# SPDX-License-Identifier: Apache-2.0

from Deeploy.DeeployTypes import NodeTemplate


class _iLeakyReLUTemplate(NodeTemplate):

def __init__(self, templateStr):
super().__init__(templateStr)


# TODO(student): fill in the Mako template body so it emits a single
# call to your C kernel:
#
# PULPiLeakyReLU_i8_i8(<data_in>, <data_out>, <size>, <mul>, <shift>);
#
# All five `${...}` substitutions correspond to keys you populated in
# the parser (or that Deeploy fills automatically for tensor names).
referenceTemplate = _iLeakyReLUTemplate("""
// iLeakyReLU (Name: ${nodeName}, Op: ${nodeOp})
// TODO(student): emit the kernel call here.
""")
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
# ----------------------------------------------------------------------
# File: iLeakyReLUTileConstraint.py (SoCDAML Part III - Step 5+6a skeleton)
#
# Drop this file into:
# Deeploy/Targets/PULPOpen/TileConstraints/iLeakyReLUTileConstraint.py
#
# UnaryTileConstraint already implements the geometry and serializer
# you need for an elementwise op. You only have to subclass it. In
# Step 6a you'll add a performance constraint on top.
# ----------------------------------------------------------------------
# SPDX-FileCopyrightText: 2026 ETH Zurich and University of Bologna
#
# SPDX-License-Identifier: Apache-2.0

from typing import Dict

from Deeploy.DeeployTypes import NetworkContext
from Deeploy.Targets.Generic.TileConstraints.UnaryTileConstraint import UnaryTileConstraint
from Deeploy.TilingExtension.TilerModel import TilerModel


class iLeakyReLUTileConstraint(UnaryTileConstraint):

@staticmethod
def addGeometricalConstraint(tilerModel: TilerModel, parseDict: Dict, ctxt: NetworkContext) -> TilerModel:
tilerModel = UnaryTileConstraint.addGeometricalConstraint(tilerModel, parseDict, ctxt)

# TODO(student, Step 6a): add a performance constraint so the
# innermost tile dim is a multiple of 16. Helpful API:
# tilerModel.addTileSizeDivisibleConstraint(parseDict, name,
# tensorDimVar, modulo)
# See: Deeploy/Targets/PULPOpen/TileConstraints/GEMMTileConstraint.py
# for a usage example.

return tilerModel
52 changes: 52 additions & 0 deletions Tutorials/PartIII_skeletons/iLeakyReLU/iLeakyReLU_simd.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
/* =====================================================================
* Title: iLeakyReLU_simd.c (SoCDAML Part III - Step 6b skeleton)
*
* SIMD version of iLeakyReLU using XPULP packed 4x8b operations.
* The per-core chunking is provided. Fill in the inner SIMD body.
*
* Key identity (worth deriving on paper before reading hints below):
* LeakyReLU(x) = (x >= 0) ? x : (x >> shift)
* = max(x, x >> shift)
* because arithmetic right shift makes a negative value LESS negative
* (or zero) and doesn't change the sign of a non-negative value.
*
* Strategy hint (one path, two intrinsic-level operations per 4 lanes):
* - load v4s lane: v4s x = vIn[i];
* - per-lane signed shift: v4s s = x >> shift; (GCC vector ext)
* - signed packed max: __builtin_pulp_max4(x, s);
*
* For the lab we assume `mul == 1` (the generator picks mul=1, shift=3).
*
* Drop into: TargetLibraries/PULPOpen/src/iLeakyReLU.c (overwrite scalar)
* ===================================================================== */
/* SPDX-FileCopyrightText: 2026 ETH Zurich and University of Bologna
*
* SPDX-License-Identifier: Apache-2.0
*/

#include "DeeployPULPMath.h"
#include "pmsis.h"

void PULPiLeakyReLU_i8_i8(int8_t *pIn, int8_t *pOut, uint32_t size, int32_t mul,
int32_t shift) {
(void)mul; // SIMD path assumes mul == 1

uint32_t cid = pi_core_id();
uint32_t nC = NUM_CORES;
uint32_t per = (size + nC - 1) / nC;
per &= ~0x3u;
uint32_t start = cid * per;
uint32_t end = (start + per > size) ? size : (start + per);
Comment on lines +36 to +39

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Do not round per-core chunks down without covering the remainder.

per &= ~0x3u can make per zero or leave the final elements outside every core’s [start, end) range. For example, when size = 16 and nC = 8, per becomes zero and no element is processed. Partition complete vectors by vector index and handle size % 4 exactly once with a scalar tail; the innermost-dimension multiple-of-16 constraint does not guarantee safe per-core chunking.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@Tutorials/PartIII_skeletons/iLeakyReLU/iLeakyReLU_simd.c` around lines 36 -
39, Update the per-core partitioning in the iLeakyReLU SIMD worker so rounding
does not drop elements or produce zero-sized chunks. Partition complete
4-element vectors by vector index across cores, then ensure the size % 4 scalar
tail is processed exactly once, preserving coverage of every element even when
size is smaller than or not evenly divisible by nC.


v4s *vIn = (v4s *)(pIn + start);
v4s *vOut = (v4s *)(pOut + start);
uint32_t nVec = (end - start) >> 2;

for (uint32_t i = 0; i < nVec; i++) {
v4s x = vIn[i];
// TODO(student): one line to compute `s` from `x` and `shift`,
// one line to blend `x` and `s` with the packed
// signed max intrinsic and store it.
vOut[i] = x; // <- placeholder, replace
}
}
Loading
Loading