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 CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ This file contains the changelog for the Deeploy project. The changelog is divid
- Aligned CLI commands across the project
- Added @runwangdl as a code owner
- Skip emitting duplicate `testInputVector` data for inputs placed in L3 (loaded at runtime from the readfs hex instead), reducing test binary size
- Tiler (`TilerExtension`, `MemoryScheduler`) and `NetworkContext.dealiasBuffer` use directed `VariableBuffer.alias_of` instead of the legacy `_alias` attribute (#201)

### Fixed
- Fix Neureka's output-channels subtile size (in ConvTemplate) and Dense/DW/PW tile constraints
Expand All @@ -85,6 +86,7 @@ This file contains the changelog for the Deeploy project. The changelog is divid
### Removed
- removed experimental `enable3x3` flag, from Neureka Engine. Now, 3x3 mode is enabled by default.
- `testDMA.py` was an old test; we now have `test_dmas.py` instead.
- Legacy `_alias` workaround in Generic/PULPOpen `ReshapeTemplate` (tiling now uses `alias_of`)

## Release v0.2.1 (2026-02-05) [#158](https://github.com/pulp-platform/Deeploy/pull/158)

Expand Down
12 changes: 10 additions & 2 deletions Deeploy/DeeployTypes.py
Original file line number Diff line number Diff line change
Expand Up @@ -259,6 +259,9 @@ def __init__(self, name: str = '', shape = [1], aliases: Optional[List[str]] = N
self.is_output: bool = False

self.aliases: Set[str] = set(aliases) if aliases is not None else set()
# Directed "I am an alias of these storage ancestors" (tiling / dealiasBuffer).
# Distinct from symmetric self.aliases used by has_live_aliases.
self.alias_of: Set[str] = set()

def _bufferRepresentation(self) -> Dict:
return {"type": self._instance, "name": self.name, "size": int(np.prod(self.shape))}
Expand Down Expand Up @@ -563,9 +566,14 @@ def dealiasBuffer(self, name: str) -> str:
"""
seenAliases: Set[str] = set()
alias = self.lookup(name)
while hasattr(alias, "_alias"):
assert isinstance(alias, VariableBuffer)
while alias.alias_of:
seenAliases.add(alias.name)
alias = self.lookup(alias._alias)
# Reshape and other current aliasers have a single storage parent.
# Pick a deterministic parent if multiple are ever present.
parentName = sorted(alias.alias_of)[0]
alias = self.lookup(parentName)
assert isinstance(alias, VariableBuffer)
Comment on lines +570 to +576

@coderabbitai coderabbitai Bot Aug 11, 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.

🗄️ Data Integrity & Integration | 🟠 Major | 🏗️ Heavy lift

Define one valid storage resolution rule for multi-parent aliases.

alias_of now permits multiple parents, but these consumers select a parent by name order. This can assign an arbitrary address. In Deeploy/TilingExtension/TilerExtension.py, an in-pattern parent can satisfy line 340 while dealiasBuffer() selects another root that is absent from nodeList; line 345 then fails.

  • Deeploy/DeeployTypes.py#L570-L576: resolve all parent paths and require one canonical storage root, or reject conflicting roots.
  • Deeploy/TilingExtension/MemoryScheduler.py#L658-L661: use the canonical storage parent or verify that all eligible parents have the same address.
  • Deeploy/TilingExtension/TilerExtension.py#L337-L345: resolve the parent selected from the current pattern instead of an unrelated lexicographic parent.
  • DeeployTest/testTypes.py#L227-L252: add a multi-parent case where the in-pattern parent is not the lexicographically first parent.
📍 Affects 4 files
  • Deeploy/DeeployTypes.py#L570-L576 (this comment)
  • Deeploy/TilingExtension/MemoryScheduler.py#L658-L661
  • Deeploy/TilingExtension/TilerExtension.py#L337-L345
  • DeeployTest/testTypes.py#L227-L252
🤖 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 `@Deeploy/DeeployTypes.py` around lines 570 - 576, Define a canonical
storage-root resolution rule across all alias consumers: in
Deeploy/DeeployTypes.py lines 570-576, update the alias-resolution logic to
traverse all parent paths and accept only one canonical root or reject
conflicting roots; in Deeploy/TilingExtension/MemoryScheduler.py lines 658-661,
use that canonical parent or verify all eligible parents share its address; in
Deeploy/TilingExtension/TilerExtension.py lines 337-345, resolve the parent
selected by the current pattern rather than choosing an unrelated lexicographic
parent; and in DeeployTest/testTypes.py lines 227-252, add coverage where the
in-pattern parent is not lexicographically first.

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.

I think this is a valid comment, can you please take a look?

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' '== Relevant file locations =='
fd -t f '^(DeeployTypes|MemoryScheduler|TilerExtension|testTypes)\.py$' .

printf '%s\n' '== Alias-related definitions, writes, and reads =='
rg -n -C 4 --glob '*.py' \
  '(?:\.alias_of\b|alias_of\s*=|dealiasBuffer\s*\(|aliases\.(?:add|update)\s*\()' \
  Deeploy DeeployTest

printf '%s\n' '== TilerExtension outline =='
ast-grep outline Deeploy/TilingExtension/TilerExtension.py --items all --type function --match '.*alias.*|.*Memory.*|.*convert.*' || true

printf '%s\n' '== MemoryScheduler outline =='
ast-grep outline Deeploy/TilingExtension/MemoryScheduler.py --items all --type function --match '.*alias.*|.*Memory.*|.*solution.*' || true

printf '%s\n' '== Current sibling ranges =='
sed -n '320,360p' Deeploy/TilingExtension/TilerExtension.py
sed -n '640,675p' Deeploy/TilingExtension/MemoryScheduler.py
sed -n '210,270p' DeeployTest/testTypes.py

Length of output: 21435


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '%s\n' '== All direct alias_of mutations =='
rg -n -C 3 --glob '*.py' \
  '(?:\.alias_of\.(?:add|update|clear|remove|discard)\s*\(|\.alias_of\s*=)' \
  .

printf '%s\n' '== aliasedBlocks use sites and scheduling continuation =='
rg -n -C 6 '\baliasedBlocks\b' Deeploy/TilingExtension/MemoryScheduler.py
sed -n '620,760p' Deeploy/TilingExtension/MemoryScheduler.py

printf '%s\n' '== Static schedule method context =='
sed -n '263,365p' Deeploy/TilingExtension/TilerExtension.py

printf '%s\n' '== Existing tests that construct MemoryBlock patterns =='
rg -n -C 3 --glob 'test*.py' \
  '(?:MemoryBlock\s*\(|_convertCtxtToStaticSchedule\s*\(|getBlockVector\s*\(|scheduleMemoryConstraints\s*\()' \
  DeeployTest

Length of output: 12040


@Xeratec, I confirmed that the finding is valid.

VariableBuffer.alias_of is a Set[str], and the PR objective requires multi-alias support. However, NetworkContext.dealiasBuffer() selects sorted(alias.alias_of)[0]. This selection does not define a storage rule when parent paths reach different roots.

There is a concrete failure in Deeploy/TilingExtension/TilerExtension.py. The code enters the alias branch when any parent is in blockNames. It then calls dealiasBuffer(). If the lexicographically first parent is outside the pattern, aliasNodes is empty and the assertion fails.

MemoryScheduler.py has a related issue. It assigns the address space from a selected immediate in-pattern parent. However, its global-buffer shortcut first uses the root returned by dealiasBuffer(). That result can be unrelated to the in-pattern parent.

The current built-in reshape template adds only one parent. Therefore, current reshape generation does not expose the defect. This does not invalidate the finding because the new representation and PR scope permit multiple parents.

I recommend keeping the finding open and changing the implementation to do one of these actions:

  1. Require all alias_of paths to resolve to the same storage root, and reject conflicting roots.
  2. Define an explicit selected-parent rule and use that same parent in dealiasBuffer(), MemoryScheduler, and TilerExtension.

The test should add two parents where the in-pattern parent is not lexicographically first. It should verify either correct allocation from the selected storage root or a clear rejection of conflicting roots.

You are interacting with an AI system.

assert alias.name not in seenAliases, "Circular aliasing detected!"
return alias.name

Expand Down
8 changes: 2 additions & 6 deletions Deeploy/Targets/Generic/Templates/ReshapeTemplate.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,12 +33,8 @@ def alignToContext(self, ctxt: NetworkContext,
# Link aliases to each buffer
bufferIn.aliases.add(bufferOut.name)
bufferOut.aliases.add(bufferIn.name)

# Tiling still reads the legacy single-valued `_alias` attribute
# (TilerExtension / MemoryScheduler). Set it here so platforms that
# rely on Reshape pointer-passthrough during tiling don't each need
# to carry the same workaround in a subclass.
bufferOut._alias = bufferIn.name
# Directed storage parent for tiling / dealiasBuffer
bufferOut.alias_of.add(bufferIn.name)

return ctxt, operatorRepresentation, []

Expand Down
21 changes: 0 additions & 21 deletions Deeploy/Targets/PULPOpen/Templates/ReshapeTemplate.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,6 @@
#
# SPDX-License-Identifier: Apache-2.0

from typing import Dict, List, Tuple

from Deeploy.DeeployTypes import NetworkContext, OperatorRepresentation, VariableBuffer
from Deeploy.Targets.Generic.Templates.ReshapeTemplate import _ReshapeTemplate as _GenericReshapeTemplate


Expand All @@ -13,24 +10,6 @@ class _ReshapeTemplate(_GenericReshapeTemplate):
def __init__(self, templateStr):
super().__init__(templateStr)

def alignToContext(self, ctxt: NetworkContext,
operatorRepresentation: OperatorRepresentation) -> Tuple[NetworkContext, Dict, List[str]]:

ctxt, operatorRepresentation, _ = super().alignToContext(ctxt, operatorRepresentation)

# Get buffers
bufferIn = ctxt.lookup(operatorRepresentation['data_in'])
assert isinstance(bufferIn, VariableBuffer)

bufferOut = ctxt.lookup(operatorRepresentation['data_out'])
assert isinstance(bufferOut, VariableBuffer)

# HACK: Tiling wasn't updated in the Fix aliasing PR so we have to still
# set the _alias argument
bufferOut._alias = bufferIn.name

return ctxt, operatorRepresentation, []


referenceTemplate = _ReshapeTemplate("""
// Reshape (Name: ${nodeName}, Op: ${nodeOp})
Expand Down
19 changes: 10 additions & 9 deletions Deeploy/TilingExtension/MemoryScheduler.py
Original file line number Diff line number Diff line change
Expand Up @@ -301,11 +301,11 @@ def filterTensorMemoryConstraint(ctxt: NetworkContext, tensorMemoryConstraint: T

buffer = ctxt.lookup(tensorName)
# JUNGVI: Buffer targeted by alias have to say alive as long as their "aliasers"
if hasattr(buffer, "_alias"):
alias = buffer._alias
if alias in tensorLifetimeMap.keys():
prevLifetime = tensorLifetimeMap[alias]
tensorLifetimeMap[alias] = tuple((prevLifetime[0], stepIdx))
if buffer.alias_of:
for alias in buffer.alias_of:
if alias in tensorLifetimeMap.keys():
prevLifetime = tensorLifetimeMap[alias]
tensorLifetimeMap[alias] = tuple((prevLifetime[0], stepIdx))

if tensorName in tensorLifetimeMap.keys():
prevLifetime = tensorLifetimeMap[tensorName]
Expand Down Expand Up @@ -369,7 +369,7 @@ def _buildCostVector(self, ctxt, graph, tensorMap, memoryLevel):
cost = wordCost * c.multiBufferCoefficient

# SCHEREMO: In-place operator outputs are "costless" whenever their input is in the same pattern
if hasattr(ctxt.lookup(node), "_alias") and ctxt.lookup(node)._alias in neighbors:
if ctxt.lookup(node).alias_of and any(a in neighbors for a in ctxt.lookup(node).alias_of):
cost = 0

costVector.append(cost)
Expand Down Expand Up @@ -655,9 +655,10 @@ def permMatrix2permList(permMatrix: np.ndarray) -> List[int]:
continue

# SCHEREMO: Don't fully unroll aliases here - this is pattern-sensitive!
if hasattr(_buffer, "_alias") and _buffer._alias in blockNames:
_alias = ctxt.lookup(memoryBlock.name)._alias
aliasedBlocks.append((memoryBlock, _alias))
inPatternParents = [a for a in _buffer.alias_of if a in blockNames]
if inPatternParents:
# Prefer a deterministic immediate parent when multiple exist
aliasedBlocks.append((memoryBlock, sorted(inPatternParents)[0]))
continue

upperIdx = blockIdx
Expand Down
7 changes: 4 additions & 3 deletions Deeploy/TilingExtension/TilerExtension.py
Original file line number Diff line number Diff line change
Expand Up @@ -296,7 +296,8 @@ def _convertCtxtToStaticSchedule(self, ctxt: NetworkContext,

_buffer = ctxt.lookup(node.name)
# SCHEREMO: If alias buffers have zero cost, they don't contribute to the currentMax and their addrSpace is None
if hasattr(_buffer, "_alias") and (ctxt.is_global(_buffer._alias) or _buffer._alias in blockNames):
if _buffer.alias_of and (any(ctxt.is_global(a) for a in _buffer.alias_of)
or any(a in blockNames for a in _buffer.alias_of)):
continue

currentMax = max(currentMax, node._addrSpace[1])
Expand Down Expand Up @@ -333,10 +334,10 @@ def _convertCtxtToStaticSchedule(self, ctxt: NetworkContext,
if _buffer._memoryLevel != memoryLevel:
continue

if hasattr(_buffer, "_alias") and ctxt.is_global(_buffer._alias):
if _buffer.alias_of and any(ctxt.is_global(a) for a in _buffer.alias_of):
continue

if hasattr(_buffer, "_alias") and _buffer._alias in blockNames:
if _buffer.alias_of and any(a in blockNames for a in _buffer.alias_of):

alias = ctxt.dealiasBuffer(tensorName)
aliasNodes = [node for node in nodeList if node.name == alias]
Expand Down
4 changes: 1 addition & 3 deletions DeeployTest/testSchedulingExtension.py
Original file line number Diff line number Diff line change
Expand Up @@ -195,9 +195,7 @@ def validateDynamicMemoryLayoutSolution(ctxt: NetworkContext, tilingSchedule: Ti
_buffer = ctxt.lookup(block.name)
for other in otherBlocks:
_otherBuffer = ctxt.lookup(other.name)
if (hasattr(_buffer, "_alias")
and _buffer._alias == other.name) or (hasattr(_otherBuffer, "_alias")
and _otherBuffer._alias == block.name):
if (other.name in _buffer.alias_of) or (block.name in _otherBuffer.alias_of):
collisions.append(False)
continue

Expand Down
29 changes: 29 additions & 0 deletions DeeployTest/testTypes.py
Original file line number Diff line number Diff line change
Expand Up @@ -224,6 +224,34 @@ def testPointerTypeEquivalence():
return True


def testDealiasBufferUsesAliasOf():
"""Regression for #201: dealiasBuffer walks directed alias_of, not legacy _alias."""
ctxt = NetworkContext(VariableBuffer, ConstantBuffer, StructBuffer, TransientBuffer)

bufferIn = VariableBuffer("reshape_in", shape = [4, 4])
bufferOut = VariableBuffer("reshape_out", shape = [16])
ctxt.add(bufferIn, "local")
ctxt.add(bufferOut, "local")

bufferIn.aliases.add(bufferOut.name)
bufferOut.aliases.add(bufferIn.name)
bufferOut.alias_of.add(bufferIn.name)

assert not hasattr(bufferOut, "_alias"), "legacy _alias must not be required for dealiasing"
assert ctxt.dealiasBuffer(bufferOut.name) == bufferIn.name
assert ctxt.dealiasBuffer(bufferIn.name) == bufferIn.name

bufferOut2 = VariableBuffer("reshape_out2", shape = [2, 8])
ctxt.add(bufferOut2, "local")
bufferOut.aliases.add(bufferOut2.name)
bufferOut2.aliases.add(bufferOut.name)
bufferOut2.alias_of.add(bufferOut.name)

assert ctxt.dealiasBuffer(bufferOut2.name) == bufferIn.name

return True


if __name__ == "__main__":
testImmediateSerialization()
testImmediatePromotion()
Expand All @@ -239,3 +267,4 @@ def testPointerTypeEquivalence():
testPointerSerialization()
testPointerPromotion()
testPointerTypeEquivalence()
testDealiasBufferUsesAliasOf()
Loading