Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
0ace569
Add SoCDAML Exercise and update README
Victor-Jung Jun 17, 2025
6bdfb42
Add SoCDAML Part III: hands-on lab for adding a new int8 operator (iL…
May 20, 2026
4d73284
Move Part III lab to docs/tutorials/introduction.md
May 24, 2026
adb9ffb
Apply pre-commit fixes (SPDX headers, yapf/clang-format/shfmt)
May 24, 2026
c512971
[docs] Add instructions to build container from scratch
May 25, 2026
bf63deb
[fix] Forward --neureka-wmem and --enable-3x3 from the runner to the …
Jul 28, 2026
0986239
[fix] Make Part III deploy.sh robust to Platform.py import rewrapping
Jul 28, 2026
2bc7385
[fix] Use addTileSizeDivisibleConstraint for the Step 6a SIMD alignme…
Jul 28, 2026
709997c
[docs] Fix Part III flags, paths and imports, and document the scratc…
Jul 28, 2026
e9cc2ea
[docs] Fix dangling SoCDAML.md reference
Jul 28, 2026
97ed2fd
[docs] Add changelog entries for the SoCDAML tutorial fixes
Jul 28, 2026
b1322f8
[docs] Fix duplicated article in the Part III intro paragraph
Aug 2, 2026
d7bb6a2
[docs] Fix profiling sentence grammar in the --profileTiling section
Aug 2, 2026
30ca7b5
[docs] Correct the Singularity installation step count to six
Aug 2, 2026
d3a82f4
[docs] Add language identifiers to the Part III code fences
Aug 2, 2026
5bf74e4
[fix] Include the positive int8 boundary in the iLeakyReLU fixtures
Aug 2, 2026
01fd265
[docs] Match the skeleton README SIMD row to what the file leaves as …
Aug 2, 2026
1d56459
[fix] Make deploy.sh undo remove only its own test artifacts
Aug 2, 2026
66adef2
[fix] Keep ccache and pip caches on scratch through --cleanenv
Aug 2, 2026
b18a122
[docs] Compress the SoCDAML changelog entries
Aug 2, 2026
d26f76f
[docs] Drop unreferenced image copies already present under docs/_static
Aug 2, 2026
d6c0e8e
[fix] Reject iLeakyReLU attributes the SIMD kernel cannot honour
Aug 2, 2026
ec95fba
[docs] Add the missing PR-list and Added entries for the Part III lab
Aug 2, 2026
a898924
[fix] Use GELUChecker so iLeakyReLU output keeps its signedness
Aug 2, 2026
e5db9ac
[fix] Make the deploy.sh recovery command work from the script's own …
Aug 2, 2026
d2b367d
[fix] Store the iLeakyReLU fixtures as int8 under the ONNX tensor names
Aug 2, 2026
ce21954
[docs] Document the bind-source failure when the cache variables are …
Aug 2, 2026
3d228f3
[build] Ignore the fixtures students generate from the Part III skeleton
Aug 2, 2026
1b6a4b5
[fix] Partition iLeakyReLU SIMD work by vector so no element is dropped
Aug 2, 2026
3b14e3a
[fix] Stop forwarding --enable-3x3, removed from the generator in #188
Aug 2, 2026
845a33b
[fix] Apply the Part III core-library edits as a git patch
Aug 2, 2026
7530369
[fix] Drop the redundant --neureka-wmem forwarding fixed upstream in …
Aug 3, 2026
04f5ed9
[fix] Keep the copied files when the core patch cannot be reverted
Aug 3, 2026
4ee0555
[fix] Scope deploy.sh recovery advice to the patched files only
Aug 3, 2026
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
9 changes: 9 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 Expand Up @@ -61,3 +63,10 @@ CHANGELOG_GEN.md
# Claude context file
CLAUDE.md
Container/xrt-debs/

# Fixtures students generate by running the Part III skeleton generate.py.
# Scoped to the skeleton directory on purpose: the equivalents under
# Tutorials/PartIII_solution/ and DeeployTest/Tests/ are tracked on purpose.
Tutorials/PartIII_skeletons/*/inputs.npz
Tutorials/PartIII_skeletons/*/outputs.npz
Tutorials/PartIII_skeletons/*/network.onnx
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ This file contains the changelog for the Deeploy project. The changelog is divid
- Fix for python error when using python 3.12.11 [#189]( https://github.com/pulp-platform/Deeploy/pull/189)
- Add support for Operators for Generic target needed in MAGIA [#193]( https://github.com/pulp-platform/Deeploy/pull/193)
- Fix GAP9 L3 Board Tests: readfs Flash Ordering and Duplicate Input Data [#196](https://github.com/pulp-platform/Deeploy/pull/196)
- Add SoCDAML Part III: hands-on lab for adding a new int8 operator [#194](https://github.com/pulp-platform/Deeploy/pull/194)

### Added
- tests for Regular and DW Conv2D with 3x3 kernel
Expand All @@ -40,6 +41,8 @@ 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).
- SoCDAML Part III lab: add an int8 `iLeakyReLU` to Deeploy and optimise it on Siracusa from scalar to tiled multi-core XPULP SIMD, with student skeletons and a TA reference under `Tutorials/`
- Document that `--profileTiling` crashes GVSoC on the larger microLlama graphs (invalid access)

### Changed
- Refactor the topology optimization pass `NeurekaReshapePointwiseConvolutionPass` and Neureka's Tile constraints
Expand Down Expand Up @@ -77,6 +80,7 @@ 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
- Fix Deeploy 101 tutorial errors: `--profileTiling` usage and the moved intrinsics inventory path

### Removed
- removed experimental `enable3x3` flag, from Neureka Engine. Now, 3x3 mode is enabled by default.
Expand Down
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 and the vector load provided; packed shift, max and store are 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 = 128, 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
64 changes: 64 additions & 0 deletions Tutorials/PartIII_skeletons/iLeakyReLU/iLeakyReLU_simd.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
/* =====================================================================
* 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;

// Whole 4-element vectors are split across the cores by vector index, so
// that no element is lost when size / nC is small or size is not a
// multiple of 4 * nC.
uint32_t nVec = size >> 2;
uint32_t perVec = (nVec + nC - 1) / nC;
uint32_t vStart = cid * perVec;
uint32_t vEnd = (vStart + perVec > nVec) ? nVec : (vStart + perVec);

v4s *vIn = (v4s *)pIn;
v4s *vOut = (v4s *)pOut;

for (uint32_t i = vStart; i < vEnd; 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
}

// The trailing size % 4 elements never fill a vector; one core handles
// them. Disjoint from every vector chunk above, so no sync is needed.
if (cid == 0) {
for (uint32_t i = nVec << 2; i < size; i++) {
int32_t xs = (int32_t)pIn[i];
pOut[i] = (int8_t)((xs >= 0) ? xs : (xs >> shift));
}
}
}
Loading
Loading