Skip to content

Commit cf691c4

Browse files
authored
Fix aliasing (#125)
This PR gives a quick, not-super-optimal, but working, solution to the problem by implementing the aliasing information basically as a doubly-linked list. On a reshape, both input and output record each others name, and to check whether an alias is alive, we just traverse the aliasing double-linked list in all the directions with a bfs. Imo for our current cases this is good enough performance-wise since there usually aren't so many aliasing chains happening, so I don't want to invest more time in the performance aspect. As a bonus, I sprinkled a few refactors in there, the removal of bitrotten `fromVariableBuffer`s, refactor of `hoistConstant` to not use `fromVariableBuffer` (be careful with those shapes!), and refactor of TransientBuffer's `__init__` function to use its super's one (reuse and all that). ## Changed - Removed `fromVariableBuffer` - Refactored `hoistConstant` - Refactored TransientBuffer's `__init__` ## Fixed - Fixed aliasing
1 parent dc111be commit cf691c4

File tree

5 files changed

+50
-122
lines changed

5 files changed

+50
-122
lines changed

CHANGELOG.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ This file contains the changelog for the Deeploy project. The changelog is divid
44
## Unreleased (Planned Release Target: v0.2.1)
55

66
### List of Pull Requests
7+
- Fix aliasing [#125](https://github.com/pulp-platform/Deeploy/pull/125)
78
- Support for 1D Autoencoder [#98](https://github.com/pulp-platform/Deeploy/pull/98)
89
- Refactor Logging for Improved Debugging [#115](https://github.com/pulp-platform/Deeploy/pull/115)
910
- Add reuse-tool as an SPDX license header linter [#113](https://github.com/pulp-platform/Deeploy/pull/113)
@@ -74,6 +75,9 @@ This file contains the changelog for the Deeploy project. The changelog is divid
7475
- Changed types and added correct casts to fix many compiler warnings in the PULP target library
7576
- Use [reuse-tool](https://github.com/fsfe/reuse-tool) in pre-commit, CI, and Makefile for SPDX license header linting
7677
- Deployer workflow now uses `prepare(...)` instead of `generateFunction(...)`.
78+
- Removed `fromVariableBuffer`
79+
- Refactored `hoistConstant`
80+
- Refactored TransientBuffer's `__init__`
7781

7882
### Fixed
7983
- Prevent node duplication for graphs generated via GraphSurgeon
@@ -84,6 +88,7 @@ This file contains the changelog for the Deeploy project. The changelog is divid
8488
- Corrected method usage in `importDeeployState` to call `NetworkContext.importNetworkContext` instead of the incorrect method name
8589
- Correctly return `signProp` from `setupDeployer` instead of hardcoding the value to `False` in `testMVP.py`
8690
- Fixed `Unsqueeze` Op. when using ONNX opset 13 or higher (from attribute to input)
91+
- Fixed aliasing
8792

8893
### Removed
8994
- Delete outdated and unused `.gitlab-ci.yml` file

Deeploy/CommonExtensions/CodeTransformationPasses/MemoryAllocation.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -141,7 +141,7 @@ def apply(self,
141141
assert buffer._live == True, f"Tried to deallocate already dead buffer {buffer.name}"
142142
buffer._live = False
143143
# Don't deallocate if it's an alias of a live buffer
144-
if not buffer.has_live_ancestors(ctxt = ctxt):
144+
if not buffer.has_live_aliases(ctxt):
145145
memoryLevel = "None" if not hasattr(buffer, "_memoryLevel") else buffer._memoryLevel
146146
if memoryLevel not in ctxt._dynamicSize:
147147
ctxt._dynamicSize[memoryLevel] = 0

Deeploy/DeeployTypes.py

Lines changed: 31 additions & 87 deletions
Original file line numberDiff line numberDiff line change
@@ -238,7 +238,7 @@ class VariableBuffer():
238238
allocTemplate: NodeTemplate #: NodeTemplate: Holds the buffer's allocation code
239239
deallocTemplate: NodeTemplate #: NodeTemplate: Holds the buffer's deallocation code
240240

241-
def __init__(self, name: str = '', shape = [1], alias_of: Optional[List[str]] = []):
241+
def __init__(self, name: str = '', shape = [1], aliases: Optional[List[str]] = None):
242242
self.name: str = name #: str: Canonical name that this buffer is registered as in the NetworkContext
243243
self.shape: Sequence[
244244
int] = shape #: Sequence[int]: Represents the dimensions of the underlying tensor as a sequence of dimension sizes
@@ -257,7 +257,7 @@ def __init__(self, name: str = '', shape = [1], alias_of: Optional[List[str]] =
257257
self.is_input: bool = False
258258
self.is_output: bool = False
259259

260-
self.alias_of: List[str] = alias_of if alias_of is not None else []
260+
self.aliases: Set[str] = set(aliases) if aliases is not None else set()
261261

262262
def _bufferRepresentation(self) -> Dict:
263263
return {"type": self._instance, "name": self.name, "size": int(np.prod(self.shape))}
@@ -324,42 +324,7 @@ def __getstate__(self):
324324
def fromNode(cls, node: gs.Node):
325325
return (cls(name = node.name, shape = node.shape if not isinstance(node, gs.Constant) else node.values.shape))
326326

327-
def add_aliases(self, aliases_to_add: List[str]):
328-
"""
329-
Adds list of aliases to the alias_of attribute.
330-
Parameters
331-
----------
332-
alias_to_add : List[str]
333-
List of names of aliases to add to the alias_of attribute.
334-
Returns
335-
-------
336-
None
337-
"""
338-
339-
if not hasattr(self, "alias_of"):
340-
return None
341-
342-
for alias in aliases_to_add:
343-
if alias not in self.alias_of:
344-
self.alias_of.append(alias)
345-
346-
return None
347-
348-
def get_aliases_of(self):
349-
"""
350-
Getter function for the alias_of attribute.
351-
Returns
352-
-------
353-
List[str]
354-
List of names o all aliases of this VariableBuffer.
355-
"""
356-
357-
if hasattr(self, "alias_of"):
358-
return self.alias_of
359-
else:
360-
return list()
361-
362-
def has_live_ancestors(self, ctxt: NetworkContext) -> bool:
327+
def has_live_aliases(self, ctxt: NetworkContext) -> bool:
363328
"""Checks whether this VariableBuffer has any live ancestors, i.e. buffers that are still live and are aliased by this buffer.
364329
Parameters
365330
----------
@@ -370,14 +335,18 @@ def has_live_ancestors(self, ctxt: NetworkContext) -> bool:
370335
bool
371336
True if this VariableBuffer has any live ancestors, False otherwise
372337
"""
373-
if not hasattr(self, "alias_of"):
374-
return False
375-
376-
for alias in self.alias_of:
377-
if ctxt.lookup(alias)._live:
378-
return True
379-
380-
return False
338+
# Do a breadth-first search across the aliasing double-linked list
339+
live = self._live
340+
queue = set(self.aliases)
341+
visited = set(self.name)
342+
while len(queue) > 0:
343+
next = queue.pop()
344+
buffNext = ctxt.lookup(next)
345+
assert isinstance(buffNext, VariableBuffer)
346+
live |= buffNext._live
347+
visited.add(next)
348+
queue |= buffNext.aliases - visited
349+
return live
381350

382351
def sizeInBytes(self) -> int:
383352
"""Returns the size of this VariableBuffer in bytes
@@ -398,28 +367,13 @@ class TransientBuffer(VariableBuffer):
398367
"""
399368

400369
def __init__(self, name: str = '', size = 0):
401-
self.name = name
402-
self.size = size #: int: Total BYTE size of this TransientBuffer
403-
404-
# Do not override - Should be written in the parsing passes
405-
self._users = []
370+
super().__init__(name, shape = (size,))
406371

407372
# Do not override - Should be written in the parsing passes
408373
self._type: Type[Pointer] = PointerClass(VoidType)
409-
410-
# Do not override - Should be written in the deployment passes
411-
self._live = False
412-
413-
# Do not override - Set in Templates depending on platform
414-
self._deploy = True
415-
416-
self.is_input: bool = False
417-
self.is_output: bool = False
418-
419-
self.alias_of: List[str] = []
374+
self.size = size
420375

421376
def __eq__(self, other):
422-
423377
ret = all([self.name == other.name, self.size == other.size])
424378
return ret
425379

@@ -432,10 +386,6 @@ def __str__(self) -> str:
432386
def __repr__(self) -> str:
433387
return f'TransientBuffer: name: {self.name}, size: {self.size}'
434388

435-
@classmethod
436-
def fromVariableBuffer(cls, buffer: VariableBuffer):
437-
ret = cls(name = buffer.name, size = np.prod(buffer.shape) * buffer._type.typeWidth // 8)
438-
439389
def sizeInBytes(self) -> int:
440390
return int(self.size)
441391

@@ -479,12 +429,6 @@ def __repr__(self) -> str:
479429
def _bufferRepresentation(self) -> Dict:
480430
return {"type": self._type, "name": self.name, "size": int(np.prod(self.shape)), "values": self._valueString()}
481431

482-
@classmethod
483-
def fromVariableBuffer(cls, buffer: VariableBuffer, values):
484-
ret = cls(name = buffer.name, shape = buffer.shape, values = values)
485-
486-
return ret
487-
488432

489433
class StructBuffer(VariableBuffer):
490434
"""Class to represent Struct object needed by the generated C Code
@@ -999,12 +943,15 @@ def hoistReference(self,
999943
ref._instance = ref._type(name, ctxt = self)
1000944
return ref
1001945

1002-
def hoistConstant(self, node: gs.Node, name: str = '', _type: Optional[Type[Pointer]] = None) -> str:
1003-
"""Register a ConstantBuffer extracted directly from a graphsurgeon Node
946+
def hoistConstant(self,
947+
constant: gs.Constant,
948+
name: Optional[str] = None,
949+
_type: Optional[Type[Pointer]] = None) -> str:
950+
"""Register a ConstantBuffer extracted directly from a graphsurgeon Constant
1004951
1005952
Parameters
1006953
----------
1007-
node : gs.Node
954+
constant : gs.Constant
1008955
graphsurgeon.Node containing a single constant output
1009956
name : str
1010957
Name of the ConstantBuffer to be registered
@@ -1017,21 +964,18 @@ def hoistConstant(self, node: gs.Node, name: str = '', _type: Optional[Type[Poin
1017964
Returns the name of the newly registed ConstantBuffer
1018965
1019966
"""
967+
assert len(constant.outputs) <= 1, f"Constant {constant.name} has more than one output"
1020968

1021-
assert len(node.outputs) <= 1, f"Constant {node.name} has more than one output"
969+
name = name if name is not None else constant.name
1022970

1023-
if name == "":
1024-
name = node.name
971+
# LMACAN: The shape needs to be copied into a tuple for pickling to work. Don't ask me why..
972+
buffer = self.ConstantBuffer(name, tuple(constant.shape), constant.values)
973+
self.add(buffer, 'global')
1025974

1026-
# SCHEREMO: This is currently heuristic, but should be annotated in ONNX
1027-
localBuffer = self.VariableBuffer.fromNode(node = node)
1028-
globalBuffer = self.ConstantBuffer.fromVariableBuffer(localBuffer, values = node.values)
1029-
globalBuffer.name = name
1030-
globalBuffer._type = _type
975+
if _type is not None:
976+
self.annotateType(name, _type)
1031977

1032-
self.add(globalBuffer, 'global')
1033-
1034-
return globalBuffer.name
978+
return name
1035979

1036980
def addUser(self, name: str, node: gs.Node):
1037981
"""Adds an operator's name to the _user list of a VariableBuffer in the context

Deeploy/Targets/Generic/Parsers.py

Lines changed: 4 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -1059,44 +1059,18 @@ def parseNodeCtxt(self,
10591059

10601060
class ReshapeParser(NodeParser):
10611061

1062-
def __init__(self):
1063-
super().__init__()
1064-
10651062
def parseNode(self, node: gs.Node) -> (bool):
1066-
10671063
ret = all([len(node.inputs) == 2, len(node.outputs) == 1])
1068-
10691064
return ret
10701065

10711066
def parseNodeCtxt(self,
10721067
ctxt: NetworkContext,
10731068
node: gs.Node,
10741069
channels_first: bool = True) -> Tuple[NetworkContext, bool]:
1075-
1076-
# Define names of node inputs and outputs, according to the ONNX standard
1077-
inputs = ['data_in', 'shape']
1078-
outputs = ['data_out']
1079-
1080-
# Map inputs and outputs to their corresponding names in the operator representation
1081-
for idx, inputNode in enumerate(node.inputs):
1082-
self.operatorRepresentation[inputs[idx]] = ctxt.lookup(inputNode.name).name
1083-
for idx, outputNode in enumerate(node.outputs):
1084-
self.operatorRepresentation[outputs[idx]] = ctxt.lookup(outputNode.name).name
1085-
1086-
# Update alias_of parameter for the output node
1087-
output_node = ctxt.lookup(node.outputs[outputs.index("data_out")].name)
1088-
input_node = ctxt.lookup(node.inputs[inputs.index("data_in")].name)
1089-
1090-
# Prepare new aliases
1091-
new_output_node_aliases = input_node.get_aliases_of()
1092-
new_output_node_aliases.append(input_node.name)
1093-
1094-
# Add new aliases
1095-
output_node.add_aliases(aliases_to_add = new_output_node_aliases)
1096-
1097-
# Compute data size
1098-
self.operatorRepresentation['size'] = np.prod(ctxt.lookup(node.inputs[0].name).shape)
1099-
1070+
for tensor, symName in zip(node.inputs, ['data_in', 'shape']):
1071+
self.operatorRepresentation[symName] = ctxt.lookup(tensor.name).name
1072+
for tensor, symName in zip(node.outputs, ['data_out']):
1073+
self.operatorRepresentation[symName] = ctxt.lookup(tensor.name).name
11001074
return ctxt, True
11011075

11021076

Deeploy/Targets/Generic/Templates/ReshapeTemplate.py

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44

55
from typing import Dict, List, Tuple
66

7-
from Deeploy.DeeployTypes import NetworkContext, NodeTemplate, OperatorRepresentation
7+
from Deeploy.DeeployTypes import NetworkContext, NodeTemplate, OperatorRepresentation, VariableBuffer
88

99

1010
class _ReshapeTemplate(NodeTemplate):
@@ -25,9 +25,14 @@ def alignToContext(self, ctxt: NetworkContext,
2525
ctxt.globalObjects[operatorRepresentation["shape"]]._deploy = False
2626
ctxt.globalObjects[operatorRepresentation["shape"]]._live = False
2727

28-
inBuffer = ctxt.lookup(operatorRepresentation['data_in'])
29-
outBuffer = ctxt.lookup(operatorRepresentation['data_out'])
30-
outBuffer._alias = inBuffer.name
28+
bufferIn = ctxt.lookup(operatorRepresentation['data_in'])
29+
assert isinstance(bufferIn, VariableBuffer)
30+
bufferOut = ctxt.lookup(operatorRepresentation['data_out'])
31+
assert isinstance(bufferOut, VariableBuffer)
32+
33+
# Link aliases to each buffer
34+
bufferIn.aliases.add(bufferOut.name)
35+
bufferOut.aliases.add(bufferIn.name)
3136

3237
return ctxt, operatorRepresentation, []
3338

0 commit comments

Comments
 (0)