-
Notifications
You must be signed in to change notification settings - Fork 27
Add tile transfer annotation #127
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: devel
Are you sure you want to change the base?
Changes from all commits
defb946
84ba647
0096442
a1973ae
45554e2
cf67362
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -2,18 +2,15 @@ | |
| # | ||
| # SPDX-License-Identifier: Apache-2.0 | ||
|
|
||
| import copy | ||
| from abc import abstractmethod | ||
| from typing import Dict, List, Optional, Tuple, Union | ||
|
|
||
| import numpy as np | ||
| from ortools.constraint_solver.pywrapcp import IntVar | ||
|
|
||
| from Deeploy.DeeployTypes import NetworkContext, OperatorRepresentation | ||
| from Deeploy.TilingExtension.MemoryConstraints import MemoryConstraint, NodeMemoryConstraint, TensorMemoryConstraint | ||
| from Deeploy.TilingExtension.MemoryConstraints import NodeMemoryConstraint | ||
| from Deeploy.TilingExtension.TilerModel import TilerModel | ||
| from Deeploy.TilingExtension.TilingCodegen import AbsoluteHyperRectangle, HyperRectangle, MemoryTransfer, \ | ||
| TilingSchedule, VariableReplacementScheme, computeTileHyperRectangles | ||
| from Deeploy.TilingExtension.TilingCodegen import AbsoluteHyperRectangle, TilingSchedule, VariableReplacementScheme | ||
|
|
||
|
|
||
| class TileConstraint(): | ||
|
|
@@ -91,81 +88,17 @@ def sanitizeTilingSchedule(tilingSchedule: TilingSchedule) -> TilingSchedule: | |
|
|
||
| @classmethod | ||
| def wrapTilingSolution( | ||
| cls, tilingSolution: NodeMemoryConstraint, targetMemLevel: str, ctxt: NetworkContext, | ||
| operatorRepresentation: OperatorRepresentation) -> Tuple[VariableReplacementScheme, List[TilingSchedule]]: | ||
|
|
||
| def getMemoryTransfer(tensorConstraint: TensorMemoryConstraint, sourceCube: HyperRectangle, | ||
| sourceMemoryLevel: str, targetMemoryLevel: str) -> MemoryTransfer: | ||
|
|
||
| size = np.prod(sourceCube.dims) | ||
| sourceConstraint = MemoryConstraint(sourceMemoryLevel, size) | ||
| sourceConstraint.shape = sourceCube.dims | ||
|
|
||
| destConstraint = copy.copy(tensorConstraint.memoryConstraints[targetMemoryLevel]) | ||
|
|
||
| if any(dim1 > dim2 for dim1, dim2 in zip(destConstraint.shape, sourceConstraint.shape)): | ||
| destConstraint.shape = sourceConstraint.shape | ||
|
|
||
| return MemoryTransfer(sourceConstraint, destConstraint) | ||
|
|
||
| def _offsetAdd(offsetA: Tuple[int, ...], offsetB: Tuple[int, ...]) -> Tuple[int, ...]: | ||
| return tuple(dimA + dimB for dimA, dimB in zip(offsetA, offsetB)) | ||
|
|
||
| def getCubeTransfers(tensorConstraint: TensorMemoryConstraint, sourceCubes: List[AbsoluteHyperRectangle], | ||
| sourceMemoryLevel: str, | ||
| targetMemoryLevel: str) -> Tuple[List[AbsoluteHyperRectangle], List[int]]: | ||
| solution = [] | ||
| solutionLengths = [] | ||
|
|
||
| for sourceCube in sourceCubes: | ||
| memTransfer = getMemoryTransfer(tensorConstraint, sourceCube.rectangle, sourceMemoryLevel, | ||
| targetMemoryLevel) | ||
| solutionCubes = computeTileHyperRectangles(memTransfer) | ||
| solutionAbsoluteCubes = [ | ||
| AbsoluteHyperRectangle(rectangle = cube, | ||
| absoluteOffset = _offsetAdd(sourceCube.absoluteOffset, cube.offset)) | ||
| for cube in solutionCubes | ||
| ] | ||
| solution += solutionAbsoluteCubes | ||
| solutionLengths.append(len(solutionAbsoluteCubes)) | ||
|
|
||
| return solution, solutionLengths | ||
|
|
||
| cls, tilingSolution: NodeMemoryConstraint, targetMemLevel: str, ctxt: NetworkContext, | ||
| operatorRepresentation: OperatorRepresentation, | ||
| transfers: Dict[str, | ||
| List[List[AbsoluteHyperRectangle]]]) -> Tuple[VariableReplacementScheme, List[TilingSchedule]]: | ||
| assert len(tilingSolution.outputTensorMemoryConstraints) == 1, "Expected node to have only one output!" | ||
|
Comment on lines
90
to
95
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Validate transfers structure early; clarify expected shape. wrapTilingSolution indexes transfers[outVar], implying transfers must be var-keyed (not memory-level keyed). Add a guard and a clear error to prevent silent KeyErrors. - assert len(tilingSolution.outputTensorMemoryConstraints) == 1, "Expected node to have only one output!"
- outVar, _ = next(iter(tilingSolution.outputTensorMemoryConstraints.items()))
+ assert len(tilingSolution.outputTensorMemoryConstraints) == 1, "Expected node to have only one output!"
+ outVar, _ = next(iter(tilingSolution.outputTensorMemoryConstraints.items()))
+ if outVar not in transfers:
+ raise KeyError(f"Missing transfers for output '{outVar}'. Expected var-keyed mapping.")As per coding guidelines
🤖 Prompt for AI Agents |
||
|
|
||
| outVar, outTensorConstraint = next(iter(tilingSolution.outputTensorMemoryConstraints.items())) | ||
| memoryPath = list(outTensorConstraint.memoryConstraints.keys()) | ||
|
|
||
| assert targetMemLevel in memoryPath, \ | ||
| f"Target memory level {targetMemLevel} does not exist in the memory path {memoryPath}" | ||
|
|
||
| targetIdx = memoryPath.index(targetMemLevel) | ||
|
|
||
| if targetIdx == 0: | ||
| # SCHEREMO: Watch out - this happens if inputs are in L(N+1) but outputs only in L(N) | ||
| targetIdx = 1 | ||
|
|
||
| fullShape = ctxt.lookup(outVar).shape | ||
| initialOffset = (0,) * len(fullShape) | ||
| outputCubes = [ | ||
| AbsoluteHyperRectangle(rectangle = HyperRectangle(offset = initialOffset, dims = tuple(fullShape)), | ||
| absoluteOffset = initialOffset) | ||
| ] | ||
|
|
||
| for source, target in zip(memoryPath[:targetIdx], memoryPath[1:targetIdx + 1]): | ||
| outputCubes, solutionLengths = getCubeTransfers(outTensorConstraint, outputCubes, source, target) | ||
|
|
||
| arrayOfCubes = [] | ||
| _idx = 0 | ||
| for idxLen in solutionLengths: | ||
| arrayOfCubes += [outputCubes[_idx:_idx + idxLen]] | ||
| _idx += idxLen | ||
| outVar, _ = next(iter(tilingSolution.outputTensorMemoryConstraints.items())) | ||
|
|
||
| varReplacements = [] | ||
| tilingSchedules = [] | ||
|
|
||
| for _outputCubes in arrayOfCubes: | ||
|
|
||
| for _outputCubes in transfers[outVar]: | ||
| varReplacement, tilingSchedule = cls.serializeTilingSolution(tilingSolution, _outputCubes, targetMemLevel, | ||
| ctxt, operatorRepresentation) | ||
| sanitizedTilingSchedule = cls.sanitizeTilingSchedule(tilingSchedule) | ||
|
|
||
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -36,6 +36,7 @@ | |||||||||||||||||||||||
| from Deeploy.TilingExtension.MemoryScheduler import MemoryBlock, MemoryScheduler | ||||||||||||||||||||||||
| from Deeploy.TilingExtension.TileConstraint import TileConstraint | ||||||||||||||||||||||||
| from Deeploy.TilingExtension.TilerModel import TilerModel | ||||||||||||||||||||||||
| from Deeploy.TilingExtension.TilingCodegen import AbsoluteHyperRectangle, HyperRectangle, computeTileHyperRectangles | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| TilingSolution = List[PatternMemoryConstraints] | ||||||||||||||||||||||||
| MemoryMap = Dict[str, List[List[MemoryBlock]]] | ||||||||||||||||||||||||
|
|
@@ -940,6 +941,34 @@ def testMemoryMapCorrectness(self, memoryMap: Dict[str, List[List[MemoryBlock]]] | |||||||||||||||||||||||
| assert stepIdx in range(lifetime[0], lifetime[-1] + | ||||||||||||||||||||||||
| 1), f"Invalid memory map! Buffer {tensor.name} is not alive at step {stepIdx}!" | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| def getTransfers(self, tensorMc: TensorMemoryConstraint) -> Dict[str, List[List[AbsoluteHyperRectangle]]]: | ||||||||||||||||||||||||
| transfers: Dict[str, List[List[AbsoluteHyperRectangle]]] = {} | ||||||||||||||||||||||||
| mcs = list(tensorMc.memoryConstraints.items()) | ||||||||||||||||||||||||
| for (externalMemory, externalMc), (localMemory, localMc) in zip(mcs[:-1], mcs[1:]): | ||||||||||||||||||||||||
| # TODO: Should we also use externalMemory as a key in the transfers? | ||||||||||||||||||||||||
| if externalMemory not in transfers: | ||||||||||||||||||||||||
| assert externalMc.shape is not None | ||||||||||||||||||||||||
| shape = externalMc.shape | ||||||||||||||||||||||||
| zeroOffset = (0,) * len(shape) | ||||||||||||||||||||||||
| externalAbsoluteRectangles = [AbsoluteHyperRectangle(HyperRectangle(zeroOffset, shape), zeroOffset)] | ||||||||||||||||||||||||
| else: | ||||||||||||||||||||||||
| # Flatten | ||||||||||||||||||||||||
| externalAbsoluteRectangles = [rect for _list in transfers[externalMemory] for rect in _list] | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| transfers[localMemory] = [[ | ||||||||||||||||||||||||
| AbsoluteHyperRectangle(rect, tuple(a + b | ||||||||||||||||||||||||
| for a, b in zip(extAbsRect.absoluteOffset, rect.offset))) | ||||||||||||||||||||||||
| for rect in computeTileHyperRectangles(extAbsRect.rectangle.dims, localMc.shape) | ||||||||||||||||||||||||
| ] | ||||||||||||||||||||||||
| for extAbsRect in externalAbsoluteRectangles] | ||||||||||||||||||||||||
| return transfers | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| def getIoTransfers(self, | ||||||||||||||||||||||||
| patternMc: PatternMemoryConstraints) -> Dict[str, Dict[str, List[List[AbsoluteHyperRectangle]]]]: | ||||||||||||||||||||||||
| assert len(patternMc.nodeConstraints) == 1, "Only layerwise supported for now!" | ||||||||||||||||||||||||
| tMcs = patternMc.nodeConstraints[0].tensorMemoryConstraints | ||||||||||||||||||||||||
| return {name: self.getTransfers(mc) for name, mc in tMcs.items()} | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
|
Comment on lines
+966
to
+971
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🛠️ Refactor suggestion | 🟠 Major Layer-wise assertion will break multi-node patterns at runtime. tile() calls getIoTransfers() for every pattern, but getIoTransfers() asserts a single step. This will raise on schedules where a pattern has multiple nodes. Either support multi-step or avoid asserting here. Proposed minimal fix: default to the last step (layer-wise remains unchanged), enabling non-layer-wise schedules. - def getIoTransfers(self,
- patternMc: PatternMemoryConstraints) -> Dict[str, Dict[str, List[List[AbsoluteHyperRectangle]]]]:
- assert len(patternMc.nodeConstraints) == 1, "Only layerwise supported for now!"
- tMcs = patternMc.nodeConstraints[0].tensorMemoryConstraints
- return {name: self.getTransfers(mc) for name, mc in tMcs.items()}
+ def getIoTransfers(self,
+ patternMc: PatternMemoryConstraints) -> Dict[str, Dict[str, List[List[AbsoluteHyperRectangle]]]]:
+ # Prefer layer-wise; if not, use the last step to represent the pattern's effective IO.
+ step_idx = -1
+ tMcs = patternMc.nodeConstraints[step_idx].tensorMemoryConstraints
+ return {name: self.getTransfers(mc) for name, mc in tMcs.items()}Based on learnings 📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| class TilerDeployerWrapper(NetworkDeployerWrapper): | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
|
|
@@ -996,6 +1025,7 @@ def tile(self, tilingSolution: Optional[TilingSolution] = None, memoryMap: Optio | |||||||||||||||||||||||
| # SCHEREMO: Annotate execution block with solution | ||||||||||||||||||||||||
| for layer, pattern in zip(self.layerBinding.values(), tilingSolution): | ||||||||||||||||||||||||
| layer.mapper.binder.executionBlock.patternMemoryConstraint = pattern | ||||||||||||||||||||||||
| layer.mapper.binder.executionBlock.transfers = self.tiler.getIoTransfers(pattern) | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| # SCHEREMO: Code generation STUB | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Add None‑guard for
baseExecutionBlock.transfersbefore accessing.items().The type annotation declares
transfersas aDict, but ifbaseExecutionBlock.transfersisNone, line 141 will raise anAttributeErrorwhen calling.items().Apply this diff to add a None check:
🤖 Prompt for AI Agents