Skip to content

Commit a43c6b2

Browse files
committed
Conv CHW
1 parent 4847ff0 commit a43c6b2

25 files changed

Lines changed: 826 additions & 25 deletions

File tree

Deeploy/CommonExtensions/OptimizationPasses/TopologyOptimizationPasses/LoweringOptimizationPasses.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -222,6 +222,13 @@ def __init__(self):
222222
def _NCHWtoNHWC_fun(graph: gs.Graph, match: Match, name: str, default_channels_first: bool = True):
223223
node = next(iter((match.nodes_map.values())))
224224

225+
# RW: nodes tagged keep_channels_first (by PULPConvKeepCHWPass for the GAP9
226+
# channels-first conv path) must NOT be transposed — they run channels-first
227+
# natively. The tag is only ever set by that GAP9 pass, so this is inert for
228+
# every other target/network.
229+
if node.attrs.get("keep_channels_first", False):
230+
return graph
231+
225232
channels_first = node.attrs.get("channels_first", True)
226233
if (channels_first != default_channels_first):
227234
tensorIn = node.inputs[0]

Deeploy/Targets/GAP9/Bindings.py

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -209,6 +209,26 @@
209209
GAP9Transformer) for float_type in FloatDataTypes
210210
]
211211

212+
# RW: channels-first (NCHW) forward conv bindings. These call the trainlib
213+
# *_CHW forward kernels (pulp_conv2d_fp32_fw_cl / pulp_conv_dw_fp32_fw_cl),
214+
# which fork internally on the cluster (master-core dispatch), so they bind via
215+
# GAP9ClusterTransformer like the ConvGrad forward-style kernels — NOT the
216+
# per-core GAP9Transformer used by the HWC kernels.
217+
GAP9FloatConv2DCHWBindings = [
218+
NodeBinding(
219+
ConvChecker([PointerClass(float32_t), PointerClass(float32_t),
220+
PointerClass(float32_t)], [PointerClass(float32_t)]),
221+
FloatConvTemplate.reference2DIm2ColTemplate_CHW, GAP9ClusterTransformer)
222+
]
223+
224+
GAP9FloatDWConv2DCHWBindings = [
225+
NodeBinding(
226+
ConvChecker(
227+
[PointerClass(float_type), PointerClass(float_type),
228+
PointerClass(float_type)], [PointerClass(float_type)]),
229+
FloatConvTemplate.referenceDW2DIm2ColTemplate_CHW, GAP9ClusterTransformer) for float_type in FloatDataTypes
230+
]
231+
212232
GAP9RQSMatrixVecBindings = [
213233
NodeBinding(
214234
PULPLinearChecker([PointerClass(type1),
@@ -497,8 +517,8 @@
497517
NodeBinding(
498518
InPlaceAccumulatorV2Checker(
499519
[PointerClass(float32_t), PointerClass(float32_t),
500-
PointerClass(uint8_t)], [PointerClass(float32_t)]), FloatInPlaceAccumulatorV2Template.referenceTemplate,
501-
GAP9Transformer)
520+
PointerClass(uint8_t)], [PointerClass(float32_t)]), FloatInPlaceAccumulatorV2Template.singleCoreTemplate,
521+
GAP9ClusterTransformer)
502522
]
503523

504524
GAP9LayernormGradBinding = NodeBinding(

Deeploy/Targets/GAP9/Deployer.py

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,9 +15,12 @@
1515

1616
from Deeploy.AbstractDataTypes import Pointer
1717
from Deeploy.CommonExtensions.NetworkDeployers.SignPropDeployer import SignPropDeployer
18+
from Deeploy.CommonExtensions.OptimizationPasses.TopologyOptimizationPasses.LoweringOptimizationPasses import \
19+
PULPNCHWtoNHWCPass
1820
from Deeploy.DeeployTypes import ConstantBuffer, DeploymentPlatform, NodeTemplate, TopologyOptimizer, VariableBuffer
1921
from Deeploy.Targets.GAP9.Bindings import GAP9ClusterTransformer, GAP9SimpleTransformer, GAP9Transformer
2022
from Deeploy.Targets.PULPOpen.Deployer import PULPDeployer
23+
from Deeploy.Targets.PULPOpen.TopologyOptimizationPasses.Passes import PULPConvKeepCHWPass
2124

2225
# GAP9-specific L3 RAM allocation and loading templates
2326
_GAP9L3AllocTemplate = NodeTemplate("""
@@ -51,6 +54,7 @@ def __init__(self,
5154
name: str = 'DeeployNetwork',
5255
default_channels_first = False,
5356
deeployStateDir: str = "DeeployStateDir",
57+
conv_channels_first: bool = False,
5458
inputOffsets = {}):
5559
super().__init__(graph,
5660
deploymentPlatform,
@@ -67,6 +71,19 @@ def __init__(self,
6771
self.ClusterTransformer = GAP9ClusterTransformer
6872
self.SimpleTransformer = GAP9SimpleTransformer
6973

74+
# RW: channels-first forward conv. When enabled, tag every forward Conv
75+
# with keep_channels_first (PULPConvKeepCHWPass) *before* PULPNCHWtoNHWCPass
76+
# runs. Tagged convs skip the NCHW->NHWC transpose and bind to the *_CHW
77+
# kernels (their CHW mappers sit ahead of the HWC ones, gated by the tag),
78+
# so conv activations stay channels-first end-to-end — removing the stem /
79+
# per-conv transpose pairs that set MobileNetV1's L1 tiling floor.
80+
self.conv_channels_first = conv_channels_first
81+
if conv_channels_first:
82+
passes = self.loweringOptimizer.passes
83+
insertIdx = next(
84+
(i for i, p in enumerate(passes) if isinstance(p, PULPNCHWtoNHWCPass)), len(passes))
85+
passes.insert(insertIdx, PULPConvKeepCHWPass())
86+
7087
def generateBufferAllocationCode(self) -> str:
7188
retStr = SignPropDeployer.generateBufferAllocationCode(self)
7289

Deeploy/Targets/GAP9/Platform.py

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,9 @@
1313
# Import GAP9-specific tiler bindings
1414
from Deeploy.Targets.GAP9.Tiler import GAP9AddTilingReadyBindings, GAP9AveragePoolGrad2DTilingReadyBindings, \
1515
GAP9BatchNormalizationGradTilingReadyBindings, GAP9BatchNormInternalTilingReadyBindings, \
16-
GAP9ConcatTilingReadyBindings, GAP9Conv2DTilingReadyBindings, GAP9ConvGradBTilingReadyBindings, \
17-
GAP9ConvGradW2DTilingReadyBindings, GAP9ConvGradX2DTilingReadyBindings, GAP9DWConv2DTilingReadyBindings, \
16+
GAP9ConcatTilingReadyBindings, GAP9Conv2DCHWTilingReadyBindings, GAP9Conv2DTilingReadyBindings, \
17+
GAP9ConvGradBTilingReadyBindings, GAP9ConvGradW2DTilingReadyBindings, GAP9ConvGradX2DTilingReadyBindings, \
18+
GAP9DWConv2DCHWTilingReadyBindings, GAP9DWConv2DTilingReadyBindings, \
1819
GAP9DWConvGradW2DTilingReadyBindings, GAP9DWConvGradX2DTilingReadyBindings, GAP9FlattenTilingReadyBindings, \
1920
GAP9FPGELUGradTilingReadyBindings, GAP9FPGELUTilingReadyBindings, GAP9FPGEMMTilingReadyBindings, \
2021
GAP9GatherTilingReadyBindings, GAP9GlobalAveragePool2DTilingReadyBindings, \
@@ -55,7 +56,8 @@
5556
from Deeploy.Targets.PULPOpen.Layers import PULPRQSConvLayer, PULPRQSGEMMLayer
5657
from Deeploy.Targets.PULPOpen.Parsers import PULPConv1DParser, PULPConv2DParser, PULPConvGradW2DParser, \
5758
PULPConvGradX2DParser, PULPDWConv1DParser, PULPDWConv2DParser, PULPDWConvGradW2DParser, PULPDWConvGradX2DParser, \
58-
PULPFPConv2DParser, PULPFPDWConv2DParser, PULPGEMMParser, PULPMatrixVecParser, PULPPWConvGradW2DParser, \
59+
PULPFPConv2DCHWParser, PULPFPConv2DParser, PULPFPDWConv2DCHWParser, PULPFPDWConv2DParser, PULPGEMMParser, \
60+
PULPMatrixVecParser, PULPPWConvGradW2DParser, \
5961
PULPPWConvGradX2DParser, PULPTallGEMMParser
6062

6163
# Create GAP9-specific NodeMappers
@@ -81,6 +83,11 @@
8183
GAP9_Conv1DMapper = NodeMapper(PULPConv1DParser(), PULPRQSConv1DBindings)
8284
GAP9_DWConv1DMapper = NodeMapper(PULPDWConv1DParser(), [PULPDWConv1DBinding])
8385
GAP9_FPConv2DMapper = NodeMapper(PULPFPConv2DParser(), GAP9Conv2DTilingReadyBindings)
86+
# Channels-first (NCHW) forward conv mappers. Their parsers only accept Conv nodes
87+
# tagged keep_channels_first (PULPConvKeepCHWPass), so they are placed ahead of the
88+
# HWC mappers in the 'Conv' mapping and untagged convs fall through to the HWC path.
89+
GAP9_FPConv2DCHWMapper = NodeMapper(PULPFPConv2DCHWParser(), GAP9Conv2DCHWTilingReadyBindings)
90+
GAP9_FPDWConv2DCHWMapper = NodeMapper(PULPFPDWConv2DCHWParser(), GAP9DWConv2DCHWTilingReadyBindings)
8491
GAP9_Conv2DMapper = NodeMapper(PULPConv2DParser(), GAP9RQSConv2DTilingReadyBindings)
8592
GAP9_FPDWConv2DMapper = NodeMapper(PULPFPDWConv2DParser(), GAP9DWConv2DTilingReadyBindings)
8693
GAP9_DWConv2DMapper = NodeMapper(PULPDWConv2DParser(), GAP9RQSDWConv2DTilingReadyBindings)
@@ -133,7 +140,9 @@
133140
# GAP9-specific mapping using ClDma
134141
GAP9Mapping = {
135142
'Conv':
136-
ConvLayer([GAP9_FPConv2DMapper, GAP9_FPDWConv2DMapper]),
143+
ConvLayer([
144+
GAP9_FPConv2DCHWMapper, GAP9_FPDWConv2DCHWMapper, GAP9_FPConv2DMapper, GAP9_FPDWConv2DMapper
145+
]),
137146
'RequantizedConv':
138147
PULPRQSConvLayer([GAP9_Conv2DMapper, GAP9_DWConv2DMapper, GAP9_Conv1DMapper, GAP9_DWConv1DMapper]),
139148
'RequantizedGemm':

Deeploy/Targets/GAP9/Tiler.py

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,8 @@
1414

1515
from Deeploy.Targets.GAP9.Bindings import GAP9AddBindings, GAP9AveragePoolGrad2DBindings, \
1616
GAP9BatchNormalizationGradBindings, GAP9BatchNormInternalBindings, GAP9ConcatBindings, GAP9FloatConv2DBindings, \
17-
GAP9FloatConvGradBBindings, GAP9FloatConvGradW2DBindings, GAP9FloatConvGradX2DBindings, GAP9FloatDWConv2DBindings, \
17+
GAP9FloatConv2DCHWBindings, GAP9FloatConvGradBBindings, GAP9FloatConvGradW2DBindings, \
18+
GAP9FloatConvGradX2DBindings, GAP9FloatDWConv2DBindings, GAP9FloatDWConv2DCHWBindings, \
1819
GAP9FloatDWConvGradW2DBindings, GAP9FloatDWConvGradX2DBindings, GAP9FloatGELUBinding, GAP9FloatGELUGradBinding, \
1920
GAP9FloatGEMMBindings, GAP9FloatPWConvGradW2DBindings, GAP9FloatPWConvGradX2DBindings, GAP9GatherBindings, \
2021
GAP9GlobalAveragePool2DBindings, GAP9GlobalAveragePoolGrad2DBindings, GAP9iHardswishBindings, \
@@ -41,8 +42,10 @@
4142
from Deeploy.Targets.PULPOpen.TileConstraints.ConvGradConstraint import ConvGradBTileConstraint, \
4243
ConvGradW2DTileConstraint, ConvGradX2DIm2ColHWTileConstraint, DWConvGradW2DTileConstraint, \
4344
DWConvGradX2DTileConstraint, PWConvGradWTileConstraint, PWConvGradXTileConstraint
44-
from Deeploy.Targets.PULPOpen.TileConstraints.ConvTileConstraint import Conv2DTileConstraint, RQConv2DTileConstraint
45+
from Deeploy.Targets.PULPOpen.TileConstraints.ConvTileConstraint import Conv2DTileConstraint, \
46+
Conv2DTileConstraintCHW, RQConv2DTileConstraint
4547
from Deeploy.Targets.PULPOpen.TileConstraints.DWConvTileConstraint import DWConv2DTileConstraint, \
48+
DWConv2DTileConstraintCHW, \
4649
RQDWConv2DTileConstraint
4750
from Deeploy.Targets.PULPOpen.TileConstraints.GatherTileConstraint import GatherTileConstraint
4851
from Deeploy.Targets.PULPOpen.TileConstraints.GEMMTileConstraint import FloatGEMMTileConstraint, GEMMTileConstraint
@@ -78,6 +81,13 @@
7881
GAP9DWConv2DTilingReadyBindings = TilingReadyNodeBindings(nodeBindings = GAP9FloatDWConv2DBindings,
7982
tileConstraint = DWConv2DTileConstraint())
8083

84+
# Channels-first (NCHW) forward conv tiling-ready bindings (CHW kernels + NCHW tile constraints)
85+
GAP9Conv2DCHWTilingReadyBindings = TilingReadyNodeBindings(nodeBindings = GAP9FloatConv2DCHWBindings,
86+
tileConstraint = Conv2DTileConstraintCHW())
87+
88+
GAP9DWConv2DCHWTilingReadyBindings = TilingReadyNodeBindings(nodeBindings = GAP9FloatDWConv2DCHWBindings,
89+
tileConstraint = DWConv2DTileConstraintCHW())
90+
8191
GAP9RQSGEMMTilingReadyBindings = TilingReadyNodeBindings(nodeBindings = GAP9RQSGEMMBindings,
8292
tileConstraint = GEMMTileConstraint())
8393

Deeploy/Targets/PULPOpen/Parsers.py

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -183,6 +183,46 @@ def parseNodeCtxt(self,
183183
return ctxt, False
184184

185185

186+
# RW: channels-first (NCHW) forward conv parsers. Used by the GAP9 CHW conv
187+
# mappers, which sit ahead of the HWC mappers in the 'Conv' mapping list. These
188+
# parsers only accept a node tagged with attr "keep_channels_first" (set by
189+
# PULPConvKeepCHWPass when the deployer runs with conv_channels_first=True);
190+
# every other conv node makes parseNode return False and falls through to the
191+
# HWC mapper, so non-CHW networks are completely unaffected.
192+
#
193+
# The transpose-skipping pass keeps the conv activations physically NCHW, so the
194+
# dim indexing must be channels-first regardless of the deployer's
195+
# default_channels_first (which is NHWC/False for GAP9). We therefore force
196+
# channels_first=True into the base parseNodeCtxt, matching the kept NCHW tensor
197+
# shapes and the OIHW weight layout consumed by the *_CHW kernels.
198+
class PULPFPConv2DCHWParser(PULPFPConv2DParser):
199+
200+
def parseNode(self, node: gs.Node) -> (bool):
201+
if not bool(node.attrs.get("keep_channels_first", False)):
202+
return False
203+
return super().parseNode(node)
204+
205+
def parseNodeCtxt(self,
206+
ctxt: NetworkContext,
207+
node: gs.Node,
208+
channels_first: bool = True) -> Tuple[NetworkContext, bool]:
209+
return super().parseNodeCtxt(ctxt, node, channels_first = True)
210+
211+
212+
class PULPFPDWConv2DCHWParser(PULPFPDWConv2DParser):
213+
214+
def parseNode(self, node: gs.Node) -> (bool):
215+
if not bool(node.attrs.get("keep_channels_first", False)):
216+
return False
217+
return super().parseNode(node)
218+
219+
def parseNodeCtxt(self,
220+
ctxt: NetworkContext,
221+
node: gs.Node,
222+
channels_first: bool = True) -> Tuple[NetworkContext, bool]:
223+
return super().parseNodeCtxt(ctxt, node, channels_first = True)
224+
225+
186226
class PULPDWConv1DParser(RQSConv1DParser):
187227

188228
def __init__(self, noBiasHoisting = True):

Deeploy/Targets/PULPOpen/Templates/FloatConvTemplate.py

Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -127,6 +127,102 @@ def hoistTransientBuffers(self, ctxt: NetworkContext,
127127
}
128128
""")
129129

130+
class PULP2DFloatConvIm2ColTemplateCHW(NodeTemplate):
131+
# CHW regular conv: the pulp-trainlib CHW im2row writes the FULL im2col
132+
# matrix (ch_im_in * Hk * Wk * H_out * W_out), unlike the HWC kernel which
133+
# uses a per-core (n_cores * ch_im_in * Hk * Wk) scratch. Size accordingly,
134+
# else the trainlib write overflows the transient buffer.
135+
136+
def __init__(self, templateStr):
137+
super().__init__(templateStr)
138+
139+
@staticmethod
140+
def computeTransientBuffersSize(
141+
ctxt: NetworkContext,
142+
operatorRepresentation: OperatorRepresentation) -> List[Tuple[str, Union[int, IntVar]]]:
143+
im2col_dim = (operatorRepresentation["weight_type"].typeWidth //
144+
8) * operatorRepresentation['ch_im_in'] * operatorRepresentation[
145+
'dim_kernel_x'] * operatorRepresentation['dim_kernel_y'] * operatorRepresentation[
146+
'dim_im_out_x'] * operatorRepresentation['dim_im_out_y']
147+
im2col_name = operatorRepresentation['nodeName'] + "_buffer"
148+
return [(im2col_name, im2col_dim)]
149+
150+
def hoistTransientBuffers(self, ctxt: NetworkContext,
151+
operatorRepresentation: OperatorRepresentation) -> Tuple[NetworkContext, Dict, List[str]]:
152+
im2col_name, im2col_dim = PULP2DFloatConvIm2ColTemplateCHW.computeTransientBuffersSize(
153+
ctxt, operatorRepresentation)[0]
154+
ctxt.hoistTransientBuffer(im2col_name, im2col_dim)
155+
ctxt.lookup(im2col_name)._type.referencedType = ctxt.lookup(
156+
operatorRepresentation['data_in'])._type.referencedType
157+
operatorRepresentation['ctxtBuffer'] = im2col_name
158+
operatorRepresentation['ctxtBufferSize'] = im2col_dim
159+
return ctxt, operatorRepresentation, [im2col_name]
160+
161+
162+
reference2DIm2ColTemplate_CHW = PULP2DFloatConvIm2ColTemplateCHW("""
163+
// 2D FP Conv CHW with Im2Col and ChannelOut parallelism (Name: ${nodeName}, Op: ${nodeOp})
164+
165+
${data_in_type.typeName} ref_${data_out}_${data_in} = ${data_in};
166+
${data_out_type.typeName} ref_${data_out}_${data_out} = ${data_out};
167+
168+
for (uint32_t n=0; n<${batch}; ++n) {
169+
PULP_Conv2d_Im2Col_fp${data_in_type.referencedType.typeWidth}_fp${weight_type.referencedType.typeWidth}_fp${data_out_type.referencedType.typeWidth}_CHW(
170+
ref_${data_out}_${data_in},
171+
${dim_im_in_x},
172+
${dim_im_in_y},
173+
${ch_im_in},
174+
${weight},
175+
${ch_im_out},
176+
${dim_kernel_x},
177+
${dim_kernel_y},
178+
${stride_x},
179+
${stride_y},
180+
${bias}, ${has_bias},
181+
ref_${data_out}_${data_out},
182+
${padding_y_top},
183+
${padding_y_bottom},
184+
${padding_x_left},
185+
${padding_x_right},
186+
${ctxtBuffer}
187+
);
188+
189+
ref_${data_out}_${data_in} += ${ch_im_in} * ${dim_im_in_x} * ${dim_im_in_y};
190+
ref_${data_out}_${data_out} += ${ch_im_out} * ${dim_im_out_x} * ${dim_im_out_y};
191+
}
192+
""")
193+
194+
referenceDW2DIm2ColTemplate_CHW = PULP2DFloatDWConvIm2ColTemplate("""
195+
// 2D DW FP Conv CHW with Im2Col and ChannelOut parallelism (Name: ${nodeName}, Op: ${nodeOp})
196+
197+
${data_in_type.typeName} ref_${data_out}_${data_in} = ${data_in};
198+
${data_out_type.typeName} ref_${data_out}_${data_out} = ${data_out};
199+
200+
for (uint32_t n=0; n<${batch}; ++n) {
201+
PULP_DW_Conv2d_Im2Col_fp${data_in_type.referencedType.typeWidth}_fp${weight_type.referencedType.typeWidth}_fp${data_out_type.referencedType.typeWidth}_CHW(
202+
ref_${data_out}_${data_in},
203+
${dim_im_in_x},
204+
${dim_im_in_y},
205+
${ch_im_in},
206+
${weight},
207+
${ch_im_out},
208+
${dim_kernel_x},
209+
${dim_kernel_y},
210+
${stride_x},
211+
${stride_y},
212+
${bias}, ${has_bias},
213+
ref_${data_out}_${data_out},
214+
${padding_y_top},
215+
${padding_y_bottom},
216+
${padding_x_left},
217+
${padding_x_right},
218+
${ctxtBuffer}
219+
);
220+
221+
ref_${data_out}_${data_in} += ${ch_im_in} * ${dim_im_in_x} * ${dim_im_in_y};
222+
ref_${data_out}_${data_out} += ${ch_im_out} * ${dim_im_out_x} * ${dim_im_out_y};
223+
}
224+
""")
225+
130226
referenceDW2DIm2ColTemplate = PULP2DFloatDWConvIm2ColTemplate("""
131227
// 2D DW FP Conv HWC with Im2Col and ChannelOout parallelism (Name: ${nodeName}, Op: ${nodeOp})
132228

0 commit comments

Comments
 (0)