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
Comment on lines +71 to +74

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

This is a bit too verbose for the changelogs, can you compress it?


### 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")
Comment on lines +260 to +261

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

--enable-3x3 is removed in #188 as 3x3 are now natively supported.


config = DeeployTestConfig(
test_name = test_name,
test_dir = test_dir_abs,
Expand Down
Binary file added Tutorials/Images/DeeploySystem.png

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

docs/_static/tutorials/introduction dir already has this file

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

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

docs/_static/tutorials/introduction dir already has this file

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

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

docs/_static/tutorials/introduction dir already has this file

Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

docs/_static/tutorials/introduction dir already has this file

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 |

@coderabbitai coderabbitai Bot Jul 28, 2026

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.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Maybe check on this too.

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.

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '%s\n' '--- Tutorials/PartIII_skeletons/iLeakyReLU/README.md ---'
cat -n Tutorials/PartIII_skeletons/iLeakyReLU/README.md

printf '%s\n' '--- TODOs and SIMD implementation context ---'
rg -n -C 5 'TODO\(student\)|vOut|vMax|shift|store|load' \
  Tutorials/PartIII_skeletons/iLeakyReLU/iLeakyReLU_simd.c

Length of output: 4223


@Victor-Jung, checked: this is still valid.

The skeleton provides SIMD chunking and the vector load, but the packed shift, packed signed max, and final output store remain TODO(student) work—the current vOut[i] = x is only a placeholder.

A matching Line 12 description would be:

| `iLeakyReLU_simd.c` | SIMD chunking + vector load provided; packed shift/max/store TODO | Fill in Step 6b after the scalar works |

| `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 thread
Victor-Jung marked this conversation as resolved.
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 thread
Victor-Jung marked this conversation as resolved.

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