Equimo 0.4.4__tar.gz → 0.4.5__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {equimo-0.4.4/src/Equimo.egg-info → equimo-0.4.5}/PKG-INFO +1 -1
- {equimo-0.4.4 → equimo-0.4.5}/pyproject.toml +1 -1
- {equimo-0.4.4 → equimo-0.4.5/src/Equimo.egg-info}/PKG-INFO +1 -1
- equimo-0.4.5/src/equimo/__init__.py +1 -0
- {equimo-0.4.4 → equimo-0.4.5}/src/equimo/layers/convolution.py +463 -165
- equimo-0.4.4/src/equimo/__init__.py +0 -1
- {equimo-0.4.4 → equimo-0.4.5}/LICENSE.md +0 -0
- {equimo-0.4.4 → equimo-0.4.5}/README.md +0 -0
- {equimo-0.4.4 → equimo-0.4.5}/setup.cfg +0 -0
- {equimo-0.4.4 → equimo-0.4.5}/src/Equimo.egg-info/SOURCES.txt +0 -0
- {equimo-0.4.4 → equimo-0.4.5}/src/Equimo.egg-info/dependency_links.txt +0 -0
- {equimo-0.4.4 → equimo-0.4.5}/src/Equimo.egg-info/requires.txt +0 -0
- {equimo-0.4.4 → equimo-0.4.5}/src/Equimo.egg-info/top_level.txt +0 -0
- {equimo-0.4.4 → equimo-0.4.5}/src/equimo/conversion/__init__.py +0 -0
- {equimo-0.4.4 → equimo-0.4.5}/src/equimo/conversion/utils.py +0 -0
- {equimo-0.4.4 → equimo-0.4.5}/src/equimo/experimental/__init__.py +0 -0
- {equimo-0.4.4 → equimo-0.4.5}/src/equimo/experimental/text.py +0 -0
- {equimo-0.4.4 → equimo-0.4.5}/src/equimo/io.py +0 -0
- {equimo-0.4.4 → equimo-0.4.5}/src/equimo/layers/__init__.py +0 -0
- {equimo-0.4.4 → equimo-0.4.5}/src/equimo/layers/activation.py +0 -0
- {equimo-0.4.4 → equimo-0.4.5}/src/equimo/layers/attention.py +0 -0
- {equimo-0.4.4 → equimo-0.4.5}/src/equimo/layers/downsample.py +0 -0
- {equimo-0.4.4 → equimo-0.4.5}/src/equimo/layers/dropout.py +0 -0
- {equimo-0.4.4 → equimo-0.4.5}/src/equimo/layers/ffn.py +0 -0
- {equimo-0.4.4 → equimo-0.4.5}/src/equimo/layers/generic.py +0 -0
- {equimo-0.4.4 → equimo-0.4.5}/src/equimo/layers/mamba.py +0 -0
- {equimo-0.4.4 → equimo-0.4.5}/src/equimo/layers/norm.py +0 -0
- {equimo-0.4.4 → equimo-0.4.5}/src/equimo/layers/patch.py +0 -0
- {equimo-0.4.4 → equimo-0.4.5}/src/equimo/layers/posemb.py +0 -0
- {equimo-0.4.4 → equimo-0.4.5}/src/equimo/layers/sharing.py +0 -0
- {equimo-0.4.4 → equimo-0.4.5}/src/equimo/layers/squeeze_excite.py +0 -0
- {equimo-0.4.4 → equimo-0.4.5}/src/equimo/models/__init__.py +0 -0
- {equimo-0.4.4 → equimo-0.4.5}/src/equimo/models/emamodel.py +0 -0
- {equimo-0.4.4 → equimo-0.4.5}/src/equimo/models/fastervit.py +0 -0
- {equimo-0.4.4 → equimo-0.4.5}/src/equimo/models/lowformer.py +0 -0
- {equimo-0.4.4 → equimo-0.4.5}/src/equimo/models/mlla.py +0 -0
- {equimo-0.4.4 → equimo-0.4.5}/src/equimo/models/partialformer.py +0 -0
- {equimo-0.4.4 → equimo-0.4.5}/src/equimo/models/reduceformer.py +0 -0
- {equimo-0.4.4 → equimo-0.4.5}/src/equimo/models/shvit.py +0 -0
- {equimo-0.4.4 → equimo-0.4.5}/src/equimo/models/vit.py +0 -0
- {equimo-0.4.4 → equimo-0.4.5}/src/equimo/models/vssd.py +0 -0
- {equimo-0.4.4 → equimo-0.4.5}/src/equimo/ops/scan.py +0 -0
- {equimo-0.4.4 → equimo-0.4.5}/src/equimo/utils.py +0 -0
- {equimo-0.4.4 → equimo-0.4.5}/tests/test_models.py +0 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "0.4.5"
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import math
|
|
2
2
|
import operator
|
|
3
|
-
from typing import Callable, Literal, Optional, Sequence, Tuple
|
|
3
|
+
from typing import Callable, Literal, Optional, Sequence, Tuple, Union
|
|
4
4
|
|
|
5
5
|
import equinox as eqx
|
|
6
6
|
import jax
|
|
@@ -11,6 +11,7 @@ from jaxtyping import Array, Float, PRNGKeyArray
|
|
|
11
11
|
|
|
12
12
|
from equimo.layers.dropout import DropPathAdd
|
|
13
13
|
from equimo.layers.norm import LayerScale
|
|
14
|
+
from equimo.layers.squeeze_excite import SEModule
|
|
14
15
|
from equimo.utils import make_divisible, nearest_power_of_2_divisor
|
|
15
16
|
|
|
16
17
|
|
|
@@ -1046,31 +1047,50 @@ class UIB(eqx.Module):
|
|
|
1046
1047
|
|
|
1047
1048
|
|
|
1048
1049
|
class GenericGhostModule(eqx.Module):
|
|
1049
|
-
"""
|
|
1050
|
+
"""GhostNet v3-like module with GroupNorm and training-time branch fusion.
|
|
1050
1051
|
|
|
1052
|
+
- Uses shared GroupNorm after linear summations (keeps GN's batch-size robustness).
|
|
1053
|
+
- Preserves the linear pre-norm structure of the reference:
|
|
1054
|
+
primary: [skip (identity) + scale(1x1) + Σ conv_k] → GN → act
|
|
1055
|
+
cheap: [skip (identity, when ratio=2) + scale(1x1 depthwise) + Σ depthwise conv_k] → GN → act
|
|
1056
|
+
- Provides test-time fusion into single Conv2d layers (still followed by GN+act).
|
|
1051
1057
|
|
|
1052
|
-
|
|
1053
|
-
ayers accross multiple parallel branches to allow using other norm layers
|
|
1054
|
-
while still fusing convolutions at test-time.
|
|
1058
|
+
Input/Output convention: (C, H, W).
|
|
1055
1059
|
"""
|
|
1056
1060
|
|
|
1061
|
+
# Static configuration
|
|
1057
1062
|
mode: Literal["original", "shortcut"] = eqx.field(static=True)
|
|
1058
1063
|
out_channels: int = eqx.field(static=True)
|
|
1059
1064
|
num_conv_branches: int = eqx.field(static=True)
|
|
1065
|
+
kernel_size: int = eqx.field(static=True)
|
|
1066
|
+
dw_size: int = eqx.field(static=True)
|
|
1067
|
+
stride: int = eqx.field(static=True)
|
|
1068
|
+
primary_has_skip: bool = eqx.field(static=True)
|
|
1069
|
+
primary_has_scale: bool = eqx.field(static=True)
|
|
1070
|
+
cheap_has_skip: bool = eqx.field(static=True)
|
|
1071
|
+
cheap_has_scale: bool = eqx.field(static=True)
|
|
1072
|
+
|
|
1073
|
+
# Runtime flags
|
|
1060
1074
|
inference: bool
|
|
1061
1075
|
|
|
1076
|
+
# Inference
|
|
1062
1077
|
primary_conv: eqx.nn.Conv2d
|
|
1063
1078
|
cheap_operation: eqx.nn.Conv2d
|
|
1064
1079
|
|
|
1080
|
+
# Training
|
|
1065
1081
|
primary_rpr_conv: list[eqx.nn.Conv2d]
|
|
1082
|
+
primary_rpr_scale: eqx.nn.Conv2d | eqx.nn.Identity
|
|
1066
1083
|
primary_shared_norm: eqx.nn.GroupNorm
|
|
1067
1084
|
primary_activation: Callable
|
|
1068
|
-
|
|
1069
|
-
cheap_rpr_skip: eqx.nn.Conv2d | eqx.nn.Identity
|
|
1085
|
+
|
|
1070
1086
|
cheap_rpr_conv: list[eqx.nn.Conv2d]
|
|
1087
|
+
cheap_rpr_scale: eqx.nn.Conv2d | eqx.nn.Identity
|
|
1071
1088
|
cheap_shared_norm: eqx.nn.GroupNorm
|
|
1072
1089
|
cheap_activation: Callable
|
|
1090
|
+
|
|
1073
1091
|
short_conv: eqx.nn.Identity | eqx.nn.Sequential
|
|
1092
|
+
pool2: eqx.nn.AvgPool2d
|
|
1093
|
+
gate_scale: float = eqx.field(static=True)
|
|
1074
1094
|
|
|
1075
1095
|
def __init__(
|
|
1076
1096
|
self,
|
|
@@ -1086,8 +1106,9 @@ class GenericGhostModule(eqx.Module):
|
|
|
1086
1106
|
mode: Literal["original", "shortcut"] = "original",
|
|
1087
1107
|
key: PRNGKeyArray,
|
|
1088
1108
|
):
|
|
1089
|
-
|
|
1090
|
-
|
|
1109
|
+
# Key management
|
|
1110
|
+
key_primary, key_cheap, key_pscale, key_cscale, key_s1, key_s2, key_s3 = (
|
|
1111
|
+
jr.split(key, 7)
|
|
1091
1112
|
)
|
|
1092
1113
|
key_ps = jr.split(key_primary, num_conv_branches)
|
|
1093
1114
|
key_cs = jr.split(key_cheap, num_conv_branches)
|
|
@@ -1095,10 +1116,22 @@ class GenericGhostModule(eqx.Module):
|
|
|
1095
1116
|
init_channels = math.ceil(out_channels / ratio)
|
|
1096
1117
|
new_channels = init_channels * (ratio - 1)
|
|
1097
1118
|
|
|
1119
|
+
primary_has_skip = (in_channels == init_channels) and (stride == 1)
|
|
1120
|
+
primary_has_scale = kernel_size > 1
|
|
1121
|
+
cheap_has_skip = init_channels == new_channels
|
|
1122
|
+
cheap_has_scale = dw_size > 1
|
|
1123
|
+
|
|
1098
1124
|
self.inference = False
|
|
1099
1125
|
self.mode = mode
|
|
1100
1126
|
self.num_conv_branches = num_conv_branches
|
|
1101
1127
|
self.out_channels = out_channels
|
|
1128
|
+
self.kernel_size = kernel_size # MOD
|
|
1129
|
+
self.dw_size = dw_size # MOD
|
|
1130
|
+
self.stride = stride # MOD
|
|
1131
|
+
self.primary_has_skip = primary_has_skip # MOD
|
|
1132
|
+
self.primary_has_scale = primary_has_scale # MOD
|
|
1133
|
+
self.cheap_has_skip = cheap_has_skip # MOD
|
|
1134
|
+
self.cheap_has_scale = cheap_has_scale # MOD
|
|
1102
1135
|
|
|
1103
1136
|
# Those are actually placeholders, updated at each epoch, only used at inference time
|
|
1104
1137
|
self.primary_conv = eqx.nn.Conv2d(
|
|
@@ -1107,20 +1140,22 @@ class GenericGhostModule(eqx.Module):
|
|
|
1107
1140
|
kernel_size=kernel_size,
|
|
1108
1141
|
stride=stride,
|
|
1109
1142
|
padding=kernel_size // 2,
|
|
1143
|
+
use_bias=False,
|
|
1110
1144
|
key=key_primary,
|
|
1111
1145
|
)
|
|
1112
1146
|
self.cheap_operation = eqx.nn.Conv2d(
|
|
1113
1147
|
in_channels=init_channels,
|
|
1114
1148
|
out_channels=new_channels,
|
|
1115
1149
|
kernel_size=dw_size,
|
|
1116
|
-
stride=
|
|
1150
|
+
stride=1, # MOD
|
|
1117
1151
|
padding=dw_size // 2,
|
|
1118
|
-
groups=
|
|
1152
|
+
groups=init_channels,
|
|
1153
|
+
use_bias=False,
|
|
1119
1154
|
key=key_cheap,
|
|
1120
1155
|
)
|
|
1121
1156
|
|
|
1157
|
+
# Primary training branches
|
|
1122
1158
|
init_num_groups = nearest_power_of_2_divisor(init_channels, 32)
|
|
1123
|
-
# TODO: test with some dropout?
|
|
1124
1159
|
self.primary_rpr_conv = [
|
|
1125
1160
|
eqx.nn.Conv2d(
|
|
1126
1161
|
in_channels=in_channels,
|
|
@@ -1128,44 +1163,56 @@ class GenericGhostModule(eqx.Module):
|
|
|
1128
1163
|
kernel_size=kernel_size,
|
|
1129
1164
|
stride=stride,
|
|
1130
1165
|
padding=kernel_size // 2,
|
|
1166
|
+
use_bias=False,
|
|
1131
1167
|
key=key_ps[i],
|
|
1132
1168
|
)
|
|
1133
1169
|
for i in range(num_conv_branches)
|
|
1134
1170
|
]
|
|
1171
|
+
self.primary_rpr_scale = (
|
|
1172
|
+
eqx.nn.Conv2d(
|
|
1173
|
+
in_channels=in_channels,
|
|
1174
|
+
out_channels=init_channels,
|
|
1175
|
+
kernel_size=1,
|
|
1176
|
+
stride=1,
|
|
1177
|
+
padding=0,
|
|
1178
|
+
use_bias=False,
|
|
1179
|
+
key=key_pscale,
|
|
1180
|
+
)
|
|
1181
|
+
if primary_has_scale
|
|
1182
|
+
else eqx.nn.Identity()
|
|
1183
|
+
)
|
|
1135
1184
|
self.primary_shared_norm = eqx.nn.GroupNorm(init_num_groups, init_channels)
|
|
1136
1185
|
self.primary_activation = act_layer
|
|
1137
1186
|
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
out_channels=new_channels,
|
|
1141
|
-
kernel_size=1,
|
|
1142
|
-
stride=1,
|
|
1143
|
-
padding=0,
|
|
1144
|
-
groups=1,
|
|
1145
|
-
key=key_crs,
|
|
1146
|
-
)
|
|
1147
|
-
self.cheap_rpr_skip = eqx.nn.Conv2d(
|
|
1148
|
-
init_channels,
|
|
1149
|
-
new_channels,
|
|
1150
|
-
kernel_size=1,
|
|
1151
|
-
stride=stride,
|
|
1152
|
-
padding=0,
|
|
1153
|
-
groups=1,
|
|
1154
|
-
key=key_skip,
|
|
1155
|
-
)
|
|
1187
|
+
# Cheap training branches (depthwise)
|
|
1188
|
+
newchannels_num_groups = nearest_power_of_2_divisor(new_channels, 32)
|
|
1156
1189
|
self.cheap_rpr_conv = [
|
|
1157
1190
|
eqx.nn.Conv2d(
|
|
1158
1191
|
in_channels=init_channels,
|
|
1159
1192
|
out_channels=new_channels,
|
|
1160
1193
|
kernel_size=dw_size,
|
|
1161
|
-
stride=
|
|
1194
|
+
stride=1,
|
|
1162
1195
|
padding=dw_size // 2,
|
|
1163
|
-
groups=
|
|
1196
|
+
groups=init_channels,
|
|
1197
|
+
use_bias=False,
|
|
1164
1198
|
key=key_cs[i],
|
|
1165
1199
|
)
|
|
1166
1200
|
for i in range(self.num_conv_branches)
|
|
1167
1201
|
]
|
|
1168
|
-
|
|
1202
|
+
self.cheap_rpr_scale = (
|
|
1203
|
+
eqx.nn.Conv2d(
|
|
1204
|
+
in_channels=init_channels,
|
|
1205
|
+
out_channels=new_channels,
|
|
1206
|
+
kernel_size=1,
|
|
1207
|
+
stride=1,
|
|
1208
|
+
padding=0,
|
|
1209
|
+
groups=init_channels,
|
|
1210
|
+
use_bias=False,
|
|
1211
|
+
key=key_cscale,
|
|
1212
|
+
)
|
|
1213
|
+
if cheap_has_scale
|
|
1214
|
+
else eqx.nn.Identity()
|
|
1215
|
+
)
|
|
1169
1216
|
self.cheap_shared_norm = eqx.nn.GroupNorm(newchannels_num_groups, new_channels)
|
|
1170
1217
|
self.cheap_activation = act_layer
|
|
1171
1218
|
|
|
@@ -1211,29 +1258,39 @@ class GenericGhostModule(eqx.Module):
|
|
|
1211
1258
|
else eqx.nn.Identity()
|
|
1212
1259
|
)
|
|
1213
1260
|
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
operator.add, [conv(x) for conv in self.primary_rpr_conv]
|
|
1261
|
+
self.pool2 = eqx.nn.AvgPool2d(
|
|
1262
|
+
kernel_size=2, stride=2, padding=0, use_ceil=False
|
|
1217
1263
|
)
|
|
1218
|
-
|
|
1219
|
-
|
|
1220
|
-
cheap_branches = [self.cheap_rpr_skip(x1), self.cheap_rpr_scale(x1)] + [
|
|
1221
|
-
conv(x1) for conv in self.cheap_rpr_conv
|
|
1222
|
-
]
|
|
1264
|
+
self.gate_scale = 1.0
|
|
1223
1265
|
|
|
1224
|
-
|
|
1225
|
-
|
|
1266
|
+
def training_features(self, x):
|
|
1267
|
+
# Primary path pre-norm linear sum
|
|
1268
|
+
terms = []
|
|
1269
|
+
if self.primary_has_skip:
|
|
1270
|
+
terms.append(x) # identity
|
|
1271
|
+
if not isinstance(self.primary_rpr_scale, eqx.nn.Identity):
|
|
1272
|
+
terms.append(self.primary_rpr_scale(x))
|
|
1273
|
+
terms.extend(conv(x) for conv in self.primary_rpr_conv)
|
|
1274
|
+
x1_sum = jax.tree_util.tree_reduce(operator.add, terms)
|
|
1275
|
+
x1 = self.primary_activation(self.primary_shared_norm(x1_sum))
|
|
1276
|
+
|
|
1277
|
+
# Cheap path pre-norm linear sum
|
|
1278
|
+
cheap_terms = []
|
|
1279
|
+
if self.cheap_has_skip:
|
|
1280
|
+
cheap_terms.append(x1) # identity
|
|
1281
|
+
if not isinstance(self.cheap_rpr_scale, eqx.nn.Identity):
|
|
1282
|
+
cheap_terms.append(self.cheap_rpr_scale(x1))
|
|
1283
|
+
cheap_terms.extend(conv(x1) for conv in self.cheap_rpr_conv)
|
|
1284
|
+
x2_sum = jax.tree_util.tree_reduce(operator.add, cheap_terms)
|
|
1285
|
+
x2 = self.cheap_activation(self.cheap_shared_norm(x2_sum))
|
|
1226
1286
|
|
|
1227
1287
|
out = jnp.concatenate([x1, x2], axis=0)
|
|
1228
|
-
|
|
1229
1288
|
return out
|
|
1230
1289
|
|
|
1231
1290
|
def inference_features(self, x):
|
|
1232
1291
|
x1 = self.primary_activation(self.primary_shared_norm(self.primary_conv(x)))
|
|
1233
1292
|
x2 = self.cheap_activation(self.cheap_shared_norm(self.cheap_operation(x1)))
|
|
1234
|
-
|
|
1235
1293
|
out = jnp.concatenate([x1, x2], axis=0)
|
|
1236
|
-
|
|
1237
1294
|
return out
|
|
1238
1295
|
|
|
1239
1296
|
def __call__(
|
|
@@ -1242,24 +1299,14 @@ class GenericGhostModule(eqx.Module):
|
|
|
1242
1299
|
key: PRNGKeyArray,
|
|
1243
1300
|
inference: Optional[bool] = None,
|
|
1244
1301
|
):
|
|
1245
|
-
|
|
1246
|
-
|
|
1247
|
-
# key_cs = jr.split(key_c, self.num_conv_branches)
|
|
1248
|
-
|
|
1249
|
-
if self.inference:
|
|
1250
|
-
out = self.inference_features(x)
|
|
1251
|
-
else:
|
|
1252
|
-
out = self.training_features(x)
|
|
1302
|
+
use_inference = self.inference if inference is None else inference
|
|
1303
|
+
out = self.inference_features(x) if use_inference else self.training_features(x)
|
|
1253
1304
|
|
|
1254
1305
|
if self.mode == "shortcut":
|
|
1255
|
-
res = self.short_conv(
|
|
1306
|
+
res = self.short_conv(self.pool2(x))
|
|
1256
1307
|
gating_signal = jax.image.resize(
|
|
1257
|
-
image=jax.nn.sigmoid(res),
|
|
1258
|
-
shape=(
|
|
1259
|
-
res.shape[0],
|
|
1260
|
-
out.shape[1],
|
|
1261
|
-
out.shape[2],
|
|
1262
|
-
), # (channels, height, width)
|
|
1308
|
+
image=jax.nn.sigmoid(res / self.gate_scale),
|
|
1309
|
+
shape=(res.shape[0], out.shape[1], out.shape[2]),
|
|
1263
1310
|
method="nearest",
|
|
1264
1311
|
)
|
|
1265
1312
|
out = out[: self.out_channels, :, :] * gating_signal
|
|
@@ -1267,142 +1314,393 @@ class GenericGhostModule(eqx.Module):
|
|
|
1267
1314
|
return out
|
|
1268
1315
|
|
|
1269
1316
|
|
|
1270
|
-
|
|
1271
|
-
|
|
1272
|
-
) -> GenericGhostModule:
|
|
1273
|
-
"""
|
|
1274
|
-
Fuses the weights of parallel training branches into single inference convolutions.
|
|
1317
|
+
class GhostBottleneck(eqx.Module):
|
|
1318
|
+
"""Ghost bottleneck with optional SE and re-parameterizable depthwise stage."""
|
|
1275
1319
|
|
|
1276
|
-
|
|
1277
|
-
|
|
1320
|
+
# Static config
|
|
1321
|
+
stride: int = eqx.field(static=True)
|
|
1322
|
+
dw_kernel_size: int = eqx.field(static=True)
|
|
1323
|
+
use_shortcut_mode_in_ghost1: bool = eqx.field(static=True)
|
|
1324
|
+
allow_identity_residual: bool = eqx.field(static=True)
|
|
1278
1325
|
|
|
1279
|
-
|
|
1280
|
-
module: An instance of GenericGhostModule with trained weights.
|
|
1326
|
+
inference: bool
|
|
1281
1327
|
|
|
1282
|
-
|
|
1283
|
-
|
|
1284
|
-
|
|
1285
|
-
|
|
1286
|
-
#
|
|
1287
|
-
|
|
1288
|
-
|
|
1289
|
-
|
|
1328
|
+
ghost1: "GenericGhostModule"
|
|
1329
|
+
ghost2: "GenericGhostModule"
|
|
1330
|
+
|
|
1331
|
+
dw_conv: eqx.nn.Conv2d | eqx.nn.Identity
|
|
1332
|
+
dw_rpr_conv: list[eqx.nn.Conv2d] # depthwise conv branches (no bias)
|
|
1333
|
+
dw_rpr_scale: eqx.nn.Conv2d | eqx.nn.Identity # optional 1x1 depthwise (no bias)
|
|
1334
|
+
dw_shared_norm: eqx.nn.GroupNorm | eqx.nn.Identity
|
|
1335
|
+
|
|
1336
|
+
se: eqx.Module | eqx.nn.Identity
|
|
1337
|
+
|
|
1338
|
+
shortcut: eqx.nn.Sequential | eqx.nn.Identity
|
|
1339
|
+
|
|
1340
|
+
def __init__(
|
|
1341
|
+
self,
|
|
1342
|
+
in_channels: int,
|
|
1343
|
+
mid_channels: int,
|
|
1344
|
+
out_channels: int,
|
|
1345
|
+
*,
|
|
1346
|
+
dw_kernel_size: int = 3,
|
|
1347
|
+
stride: int = 1,
|
|
1348
|
+
act_layer: Callable = jax.nn.relu,
|
|
1349
|
+
se_ratio: float = 0.0,
|
|
1350
|
+
layer_id: Optional[int] = None,
|
|
1351
|
+
use_shortcut_mode_in_ghost1: bool = True,
|
|
1352
|
+
allow_identity_residual: bool = True,
|
|
1353
|
+
key: PRNGKeyArray,
|
|
1354
|
+
):
|
|
1355
|
+
self.stride = stride
|
|
1356
|
+
self.dw_kernel_size = dw_kernel_size
|
|
1357
|
+
self.inference = False
|
|
1358
|
+
self.allow_identity_residual = allow_identity_residual
|
|
1359
|
+
|
|
1360
|
+
self.use_shortcut_mode_in_ghost1 = use_shortcut_mode_in_ghost1
|
|
1290
1361
|
|
|
1291
|
-
|
|
1292
|
-
|
|
1293
|
-
|
|
1294
|
-
|
|
1295
|
-
|
|
1362
|
+
k_g1, k_g2, k_dw_main, k_dw_scale, k_sc1, k_sc2 = jr.split(key, 6)
|
|
1363
|
+
k_dw_list = jr.split(k_dw_main, 3)
|
|
1364
|
+
|
|
1365
|
+
# ghost1 (expansion)
|
|
1366
|
+
self.ghost1 = GenericGhostModule(
|
|
1367
|
+
in_channels=in_channels,
|
|
1368
|
+
out_channels=mid_channels,
|
|
1369
|
+
kernel_size=1,
|
|
1370
|
+
ratio=2,
|
|
1371
|
+
dw_size=dw_kernel_size,
|
|
1372
|
+
stride=1 if self.use_shortcut_mode_in_ghost1 else 1,
|
|
1373
|
+
act_layer=act_layer,
|
|
1374
|
+
num_conv_branches=3,
|
|
1375
|
+
mode="shortcut" if self.use_shortcut_mode_in_ghost1 else "original",
|
|
1376
|
+
key=k_g1,
|
|
1296
1377
|
)
|
|
1297
1378
|
|
|
1298
|
-
|
|
1299
|
-
|
|
1300
|
-
|
|
1301
|
-
|
|
1302
|
-
|
|
1303
|
-
|
|
1379
|
+
# Depthwise stage (only if stride > 1)
|
|
1380
|
+
if stride > 1:
|
|
1381
|
+
# Training-time branches (depthwise, no bias); no activation; shared GN after sum
|
|
1382
|
+
self.dw_rpr_conv = [
|
|
1383
|
+
eqx.nn.Conv2d(
|
|
1384
|
+
in_channels=mid_channels,
|
|
1385
|
+
out_channels=mid_channels,
|
|
1386
|
+
kernel_size=dw_kernel_size,
|
|
1387
|
+
stride=stride,
|
|
1388
|
+
padding=(dw_kernel_size - 1) // 2,
|
|
1389
|
+
groups=mid_channels,
|
|
1390
|
+
use_bias=False,
|
|
1391
|
+
key=k_dw_list[i],
|
|
1392
|
+
)
|
|
1393
|
+
for i in range(3)
|
|
1394
|
+
]
|
|
1395
|
+
# Optional scale branch (1x1, depthwise, stride=stride)
|
|
1396
|
+
self.dw_rpr_scale = (
|
|
1397
|
+
eqx.nn.Conv2d(
|
|
1398
|
+
in_channels=mid_channels,
|
|
1399
|
+
out_channels=mid_channels,
|
|
1400
|
+
kernel_size=1,
|
|
1401
|
+
stride=stride,
|
|
1402
|
+
padding=0,
|
|
1403
|
+
groups=mid_channels,
|
|
1404
|
+
use_bias=False,
|
|
1405
|
+
key=k_dw_scale,
|
|
1406
|
+
)
|
|
1407
|
+
if dw_kernel_size > 1
|
|
1408
|
+
else eqx.nn.Identity()
|
|
1409
|
+
)
|
|
1410
|
+
# Shared GroupNorm
|
|
1411
|
+
num_groups_dw = nearest_power_of_2_divisor(mid_channels, 32)
|
|
1412
|
+
self.dw_shared_norm = eqx.nn.GroupNorm(num_groups_dw, mid_channels)
|
|
1413
|
+
|
|
1414
|
+
# Inference fused depthwise conv (weights filled by update)
|
|
1415
|
+
self.dw_conv = eqx.nn.Conv2d(
|
|
1416
|
+
in_channels=mid_channels,
|
|
1417
|
+
out_channels=mid_channels,
|
|
1418
|
+
kernel_size=dw_kernel_size,
|
|
1419
|
+
stride=stride,
|
|
1420
|
+
padding=(dw_kernel_size - 1) // 2,
|
|
1421
|
+
groups=mid_channels,
|
|
1422
|
+
use_bias=False,
|
|
1423
|
+
key=k_dw_main,
|
|
1424
|
+
)
|
|
1425
|
+
else:
|
|
1426
|
+
# No depthwise stage when stride == 1
|
|
1427
|
+
self.dw_rpr_conv = []
|
|
1428
|
+
self.dw_rpr_scale = eqx.nn.Identity()
|
|
1429
|
+
self.dw_shared_norm = eqx.nn.Identity()
|
|
1430
|
+
self.dw_conv = eqx.nn.Identity()
|
|
1431
|
+
|
|
1432
|
+
# SE
|
|
1433
|
+
if se_ratio is not None and se_ratio > 0.0:
|
|
1434
|
+
# Use provided SEModule with rd_ratio=se_ratio
|
|
1435
|
+
self.se = SEModule(dim=mid_channels, rd_ratio=se_ratio, key=k_sc1)
|
|
1436
|
+
else:
|
|
1437
|
+
self.se = eqx.nn.Identity()
|
|
1438
|
+
|
|
1439
|
+
# ghost2 (projection)
|
|
1440
|
+
self.ghost2 = GenericGhostModule(
|
|
1441
|
+
in_channels=mid_channels,
|
|
1442
|
+
out_channels=out_channels,
|
|
1443
|
+
kernel_size=1,
|
|
1444
|
+
ratio=2,
|
|
1445
|
+
dw_size=dw_kernel_size,
|
|
1446
|
+
stride=1,
|
|
1447
|
+
act_layer=act_layer,
|
|
1448
|
+
num_conv_branches=3,
|
|
1449
|
+
mode="original",
|
|
1450
|
+
key=k_g2,
|
|
1451
|
+
)
|
|
1452
|
+
|
|
1453
|
+
# Shortcut
|
|
1454
|
+
if (in_channels == out_channels) and (stride == 1):
|
|
1455
|
+
self.shortcut = eqx.nn.Identity()
|
|
1456
|
+
else:
|
|
1457
|
+
num_groups_sc_in = nearest_power_of_2_divisor(in_channels, 32)
|
|
1458
|
+
num_groups_sc_out = nearest_power_of_2_divisor(out_channels, 32)
|
|
1459
|
+
self.shortcut = eqx.nn.Sequential(
|
|
1460
|
+
[
|
|
1461
|
+
eqx.nn.Conv2d(
|
|
1462
|
+
in_channels=in_channels,
|
|
1463
|
+
out_channels=in_channels,
|
|
1464
|
+
kernel_size=dw_kernel_size,
|
|
1465
|
+
stride=stride,
|
|
1466
|
+
padding=(dw_kernel_size - 1) // 2,
|
|
1467
|
+
groups=in_channels,
|
|
1468
|
+
use_bias=False,
|
|
1469
|
+
key=k_sc1,
|
|
1470
|
+
),
|
|
1471
|
+
eqx.nn.GroupNorm(num_groups_sc_in, in_channels),
|
|
1472
|
+
eqx.nn.Conv2d(
|
|
1473
|
+
in_channels=in_channels,
|
|
1474
|
+
out_channels=out_channels,
|
|
1475
|
+
kernel_size=1,
|
|
1476
|
+
stride=1,
|
|
1477
|
+
padding=0,
|
|
1478
|
+
use_bias=False,
|
|
1479
|
+
key=k_sc2,
|
|
1480
|
+
),
|
|
1481
|
+
eqx.nn.GroupNorm(num_groups_sc_out, out_channels),
|
|
1482
|
+
]
|
|
1483
|
+
)
|
|
1304
1484
|
|
|
1305
|
-
|
|
1306
|
-
|
|
1485
|
+
def __call__(
|
|
1486
|
+
self,
|
|
1487
|
+
x: Float[Array, "channels height width"],
|
|
1488
|
+
*,
|
|
1489
|
+
key: PRNGKeyArray,
|
|
1490
|
+
inference: Optional[bool] = None,
|
|
1491
|
+
) -> Float[Array, "channels height width"]:
|
|
1492
|
+
use_inference = self.inference if inference is None else inference
|
|
1493
|
+
k_g1, k_g2 = jr.split(key, 2)
|
|
1307
1494
|
|
|
1308
|
-
|
|
1309
|
-
current_size = kernel.shape[-2:]
|
|
1310
|
-
if current_size == target_size:
|
|
1311
|
-
return kernel
|
|
1495
|
+
residual = x
|
|
1312
1496
|
|
|
1313
|
-
|
|
1314
|
-
pad_w = (target_size[1] - current_size[1]) // 2
|
|
1497
|
+
x = self.ghost1(x, key=k_g1, inference=use_inference)
|
|
1315
1498
|
|
|
1316
|
-
|
|
1317
|
-
|
|
1318
|
-
|
|
1319
|
-
|
|
1320
|
-
|
|
1321
|
-
|
|
1322
|
-
|
|
1323
|
-
|
|
1499
|
+
if self.stride > 1:
|
|
1500
|
+
if use_inference:
|
|
1501
|
+
x = self.dw_shared_norm(self.dw_conv(x))
|
|
1502
|
+
else:
|
|
1503
|
+
terms = []
|
|
1504
|
+
if not isinstance(self.dw_rpr_scale, eqx.nn.Identity):
|
|
1505
|
+
terms.append(self.dw_rpr_scale(x))
|
|
1506
|
+
terms.extend(conv(x) for conv in self.dw_rpr_conv)
|
|
1507
|
+
x = self.dw_shared_norm(jax.tree_util.tree_reduce(operator.add, terms))
|
|
1508
|
+
|
|
1509
|
+
x = self.se(x)
|
|
1510
|
+
|
|
1511
|
+
x = self.ghost2(x, key=k_g2, inference=use_inference)
|
|
1512
|
+
|
|
1513
|
+
if not isinstance(self.shortcut, eqx.nn.Identity):
|
|
1514
|
+
x += self.shortcut(residual)
|
|
1515
|
+
elif self.allow_identity_residual:
|
|
1516
|
+
x += residual
|
|
1517
|
+
|
|
1518
|
+
return x
|
|
1519
|
+
|
|
1520
|
+
|
|
1521
|
+
def _pad_kernel_to_target(kernel: Array, target_size: tuple[int, int]) -> Array:
|
|
1522
|
+
kh, kw = kernel.shape[-2], kernel.shape[-1]
|
|
1523
|
+
th, tw = target_size
|
|
1524
|
+
if (kh, kw) == (th, tw):
|
|
1525
|
+
return kernel
|
|
1526
|
+
pad_h = (th - kh) // 2
|
|
1527
|
+
pad_w = (tw - kw) // 2
|
|
1528
|
+
padding_config = [
|
|
1529
|
+
(0, 0), # out_channels
|
|
1530
|
+
(0, 0), # in_channels_per_group
|
|
1531
|
+
(pad_h, pad_h), # height
|
|
1532
|
+
(pad_w, pad_w), # width
|
|
1533
|
+
]
|
|
1534
|
+
return jnp.pad(kernel, padding_config, mode="constant", constant_values=0)
|
|
1535
|
+
|
|
1536
|
+
|
|
1537
|
+
def _make_identity_kernel_standard(out_ch: int, in_ch: int, kh: int, kw: int) -> Array:
|
|
1538
|
+
"""Identity kernel for standard conv (groups=1). Only valid if out_ch == in_ch."""
|
|
1539
|
+
assert out_ch == in_ch, "Standard identity requires out_ch == in_ch."
|
|
1540
|
+
k = jnp.zeros((out_ch, in_ch, kh, kw))
|
|
1541
|
+
cy, cx = kh // 2, kw // 2
|
|
1542
|
+
idx = jnp.arange(out_ch)
|
|
1543
|
+
k = k.at[idx, idx, cy, cx].set(1.0)
|
|
1544
|
+
return k
|
|
1545
|
+
|
|
1546
|
+
|
|
1547
|
+
def _make_identity_kernel_depthwise(out_ch: int, in_ch: int, kh: int, kw: int) -> Array:
|
|
1548
|
+
"""Identity kernel for depthwise conv with channel multiplier m = out_ch // in_ch."""
|
|
1549
|
+
assert out_ch % in_ch == 0, "Depthwise identity requires out_ch multiple of in_ch."
|
|
1550
|
+
m = out_ch // in_ch # channel multiplier
|
|
1551
|
+
k = jnp.zeros((out_ch, 1, kh, kw))
|
|
1552
|
+
cy, cx = kh // 2, kw // 2
|
|
1553
|
+
# For identity skip, we only use this when m==1 (ratio=2). Keep generic for clarity.
|
|
1554
|
+
for c in range(in_ch):
|
|
1555
|
+
for r in range(m):
|
|
1556
|
+
k = k.at[c * m + r, 0, cy, cx].set(1.0)
|
|
1557
|
+
return k
|
|
1558
|
+
|
|
1559
|
+
|
|
1560
|
+
def _update_ghostmodule(module: GenericGhostModule) -> GenericGhostModule:
|
|
1561
|
+
"""
|
|
1562
|
+
Fuses the training branches into the inference convolutions (weights only).
|
|
1563
|
+
Keeps GroupNorms; no bias is fused (bias is redundant with GN's affine).
|
|
1564
|
+
"""
|
|
1565
|
+
if not isinstance(module, GenericGhostModule):
|
|
1566
|
+
return module
|
|
1324
1567
|
|
|
1325
|
-
#
|
|
1326
|
-
|
|
1327
|
-
|
|
1328
|
-
)
|
|
1329
|
-
padded_skip_weight = pad_kernel_to_target(
|
|
1330
|
-
module.cheap_rpr_skip.weight, target_kernel_size
|
|
1568
|
+
# Fuse primary
|
|
1569
|
+
primary_w = jax.tree_util.tree_reduce(
|
|
1570
|
+
operator.add, [conv.weight for conv in module.primary_rpr_conv]
|
|
1331
1571
|
)
|
|
1572
|
+
if not isinstance(module.primary_rpr_scale, eqx.nn.Identity):
|
|
1573
|
+
target_k = module.primary_conv.weight.shape[-2:]
|
|
1574
|
+
scale_w = _pad_kernel_to_target(module.primary_rpr_scale.weight, target_k)
|
|
1575
|
+
primary_w = primary_w + scale_w
|
|
1576
|
+
if module.primary_has_skip:
|
|
1577
|
+
kh, kw = module.primary_conv.weight.shape[-2:]
|
|
1578
|
+
id_w = _make_identity_kernel_standard(
|
|
1579
|
+
module.primary_conv.out_channels, module.primary_conv.in_channels, kh, kw
|
|
1580
|
+
)
|
|
1581
|
+
primary_w = primary_w + id_w
|
|
1582
|
+
fused_primary = eqx.tree_at(lambda c: c.weight, module.primary_conv, primary_w)
|
|
1332
1583
|
|
|
1333
|
-
#
|
|
1334
|
-
|
|
1584
|
+
# Fuse cheap (depthwise)
|
|
1585
|
+
cheap_w = jax.tree_util.tree_reduce(
|
|
1335
1586
|
operator.add, [conv.weight for conv in module.cheap_rpr_conv]
|
|
1336
1587
|
)
|
|
1588
|
+
if not isinstance(module.cheap_rpr_scale, eqx.nn.Identity):
|
|
1589
|
+
target_k = module.cheap_operation.weight.shape[-2:]
|
|
1590
|
+
scale_w = _pad_kernel_to_target(module.cheap_rpr_scale.weight, target_k)
|
|
1591
|
+
cheap_w = cheap_w + scale_w
|
|
1592
|
+
if module.cheap_has_skip:
|
|
1593
|
+
init_ch = module.primary_conv.out_channels
|
|
1594
|
+
new_ch = module.cheap_operation.out_channels
|
|
1595
|
+
kh, kw = module.cheap_operation.weight.shape[-2:]
|
|
1596
|
+
if new_ch == init_ch:
|
|
1597
|
+
id_w = _make_identity_kernel_depthwise(new_ch, init_ch, kh, kw)
|
|
1598
|
+
cheap_w = cheap_w + id_w
|
|
1599
|
+
fused_cheap = eqx.tree_at(lambda c: c.weight, module.cheap_operation, cheap_w)
|
|
1600
|
+
|
|
1601
|
+
# Install fused convs
|
|
1602
|
+
module = eqx.tree_at(lambda m: m.primary_conv, module, fused_primary)
|
|
1603
|
+
module = eqx.tree_at(lambda m: m.cheap_operation, module, fused_cheap)
|
|
1604
|
+
|
|
1605
|
+
return module
|
|
1337
1606
|
|
|
1338
|
-
# Sum all weights: main convs + padded scale + padded skip.
|
|
1339
|
-
fused_cheap_weights = cheap_conv_weights + padded_scale_weight + padded_skip_weight
|
|
1340
1607
|
|
|
1341
|
-
|
|
1342
|
-
|
|
1343
|
-
|
|
1344
|
-
|
|
1608
|
+
def _finalize_ghostmodule(module: "GenericGhostModule") -> "GenericGhostModule":
|
|
1609
|
+
"""Finalize a GenericGhostModule for inference."""
|
|
1610
|
+
if not isinstance(module, GenericGhostModule):
|
|
1611
|
+
return module
|
|
1612
|
+
fused = _update_ghostmodule(module)
|
|
1613
|
+
# Drop training-only branches and switch to inference
|
|
1614
|
+
fused = eqx.tree_at(lambda m: m.primary_rpr_conv, fused, list())
|
|
1615
|
+
fused = eqx.tree_at(lambda m: m.cheap_rpr_conv, fused, list())
|
|
1616
|
+
fused = eqx.tree_at(lambda m: m.primary_rpr_scale, fused, eqx.nn.Identity())
|
|
1617
|
+
fused = eqx.tree_at(lambda m: m.cheap_rpr_scale, fused, eqx.nn.Identity())
|
|
1618
|
+
fused = eqx.tree_at(lambda m: m.inference, fused, True)
|
|
1619
|
+
return fused
|
|
1620
|
+
|
|
1621
|
+
|
|
1622
|
+
def _update_ghostbottleneck(module: "GhostBottleneck") -> "GhostBottleneck":
|
|
1623
|
+
"""Fuse depthwise stage and nested GhostModules for a GhostBottleneck."""
|
|
1624
|
+
if not isinstance(module, GhostBottleneck):
|
|
1625
|
+
return module
|
|
1626
|
+
|
|
1627
|
+
# Fuse depthwise stage (if present)
|
|
1628
|
+
if module.stride > 1 and len(module.dw_rpr_conv) > 0:
|
|
1629
|
+
dw_w = jax.tree_util.tree_reduce(
|
|
1630
|
+
operator.add, [conv.weight for conv in module.dw_rpr_conv]
|
|
1345
1631
|
)
|
|
1346
|
-
|
|
1347
|
-
|
|
1348
|
-
|
|
1349
|
-
|
|
1350
|
-
|
|
1351
|
-
lambda
|
|
1352
|
-
|
|
1353
|
-
|
|
1354
|
-
)
|
|
1632
|
+
if not isinstance(module.dw_rpr_scale, eqx.nn.Identity):
|
|
1633
|
+
target_k = module.dw_conv.weight.shape[-2:]
|
|
1634
|
+
scale_w = _pad_kernel_to_target(module.dw_rpr_scale.weight, target_k)
|
|
1635
|
+
dw_w = dw_w + scale_w
|
|
1636
|
+
fused_dw = eqx.tree_at(lambda c: c.weight, module.dw_conv, dw_w)
|
|
1637
|
+
module = eqx.tree_at(lambda m: m.dw_conv, module, fused_dw)
|
|
1638
|
+
|
|
1639
|
+
# Update nested GhostModules
|
|
1640
|
+
module = eqx.tree_at(lambda m: m.ghost1, module, _update_ghostmodule(module.ghost1))
|
|
1641
|
+
module = eqx.tree_at(lambda m: m.ghost2, module, _update_ghostmodule(module.ghost2))
|
|
1642
|
+
return module
|
|
1355
1643
|
|
|
1356
|
-
module = eqx.tree_at(lambda m: m.primary_conv, module, fused_primary_conv)
|
|
1357
|
-
module = eqx.tree_at(lambda m: m.cheap_operation, module, fused_cheap_operation)
|
|
1358
1644
|
|
|
1359
|
-
|
|
1645
|
+
def _finalize_ghostbottleneck(module: "GhostBottleneck") -> "GhostBottleneck":
|
|
1646
|
+
"""Finalize a GhostBottleneck for inference."""
|
|
1647
|
+
if not isinstance(module, GhostBottleneck):
|
|
1648
|
+
return module
|
|
1360
1649
|
|
|
1650
|
+
fused = _update_ghostbottleneck(module)
|
|
1361
1651
|
|
|
1362
|
-
|
|
1363
|
-
|
|
1364
|
-
|
|
1365
|
-
|
|
1366
|
-
"""
|
|
1652
|
+
# Drop depthwise training branches
|
|
1653
|
+
if fused.stride > 1:
|
|
1654
|
+
fused = eqx.tree_at(lambda m: m.dw_rpr_conv, fused, list())
|
|
1655
|
+
fused = eqx.tree_at(lambda m: m.dw_rpr_scale, fused, eqx.nn.Identity())
|
|
1367
1656
|
|
|
1368
|
-
|
|
1369
|
-
|
|
1370
|
-
|
|
1371
|
-
return update_fused_ghostmodule(module)
|
|
1657
|
+
# Finalize nested GhostModules
|
|
1658
|
+
fused = eqx.tree_at(lambda m: m.ghost1, fused, _finalize_ghostmodule(fused.ghost1))
|
|
1659
|
+
fused = eqx.tree_at(lambda m: m.ghost2, fused, _finalize_ghostmodule(fused.ghost2))
|
|
1372
1660
|
|
|
1373
|
-
|
|
1374
|
-
|
|
1661
|
+
# Switch to inference
|
|
1662
|
+
fused = eqx.tree_at(lambda m: m.inference, fused, True)
|
|
1663
|
+
return fused
|
|
1375
1664
|
|
|
1376
1665
|
|
|
1377
|
-
def
|
|
1666
|
+
def update_ghostnet(
|
|
1667
|
+
model: eqx.Module | Union["GenericGhostModule", "GhostBottleneck"],
|
|
1668
|
+
) -> eqx.Module | Union["GenericGhostModule", "GhostBottleneck"]:
|
|
1378
1669
|
"""
|
|
1379
|
-
|
|
1380
|
-
|
|
1381
|
-
This function recursively traverses the model and for each GenericGhostModule:
|
|
1382
|
-
1. Fuses the parallel convolutional branches.
|
|
1383
|
-
2. Replaces the training-only branches with Identity layers.
|
|
1670
|
+
Recursively fuse training branches for both GenericGhostModule and GhostBottleneck.
|
|
1671
|
+
Keeps GroupNorm layers; no bias fusion.
|
|
1384
1672
|
"""
|
|
1385
1673
|
|
|
1386
|
-
def
|
|
1387
|
-
if
|
|
1388
|
-
return
|
|
1674
|
+
def _update_leaf(m):
|
|
1675
|
+
if isinstance(m, GenericGhostModule):
|
|
1676
|
+
return _update_ghostmodule(m)
|
|
1677
|
+
if isinstance(m, GhostBottleneck):
|
|
1678
|
+
return _update_ghostbottleneck(m)
|
|
1679
|
+
return m
|
|
1389
1680
|
|
|
1390
|
-
|
|
1681
|
+
is_leaf = lambda m: isinstance(m, (GenericGhostModule, GhostBottleneck))
|
|
1682
|
+
return jax.tree_util.tree_map(_update_leaf, model, is_leaf=is_leaf)
|
|
1391
1683
|
|
|
1392
|
-
# identity_list = [eqx.nn.Identity()] * len(module.primary_rpr_conv)
|
|
1393
|
-
final_module = eqx.tree_at(lambda m: m.primary_rpr_conv, fused_module, list())
|
|
1394
|
-
final_module = eqx.tree_at(lambda m: m.cheap_rpr_conv, final_module, list())
|
|
1395
|
-
final_module = eqx.tree_at(
|
|
1396
|
-
lambda m: m.cheap_rpr_scale, final_module, eqx.nn.Identity()
|
|
1397
|
-
)
|
|
1398
|
-
final_module = eqx.tree_at(
|
|
1399
|
-
lambda m: m.cheap_rpr_skip, final_module, eqx.nn.Identity()
|
|
1400
|
-
)
|
|
1401
1684
|
|
|
1402
|
-
|
|
1685
|
+
def finalize_ghostnet(
|
|
1686
|
+
model: eqx.Module | Union["GenericGhostModule", "GhostBottleneck"],
|
|
1687
|
+
) -> eqx.Module | Union["GenericGhostModule", "GhostBottleneck"]:
|
|
1688
|
+
"""
|
|
1689
|
+
Recursively finalize both GenericGhostModule and GhostBottleneck for inference:
|
|
1690
|
+
- Fuse training branches.
|
|
1691
|
+
- Remove training-only branches.
|
|
1692
|
+
- Switch to inference path.
|
|
1693
|
+
"""
|
|
1694
|
+
|
|
1695
|
+
def _finalize_leaf(m):
|
|
1696
|
+
if isinstance(m, GenericGhostModule):
|
|
1697
|
+
return _finalize_ghostmodule(m)
|
|
1698
|
+
if isinstance(m, GhostBottleneck):
|
|
1699
|
+
return _finalize_ghostbottleneck(m)
|
|
1700
|
+
return m
|
|
1403
1701
|
|
|
1404
|
-
|
|
1405
|
-
return jax.tree_util.tree_map(_finalize_leaf, model, is_leaf=
|
|
1702
|
+
is_leaf = lambda m: isinstance(m, (GenericGhostModule, GhostBottleneck))
|
|
1703
|
+
return jax.tree_util.tree_map(_finalize_leaf, model, is_leaf=is_leaf)
|
|
1406
1704
|
|
|
1407
1705
|
|
|
1408
1706
|
class PartialConv2d(eqx.Module):
|
|
@@ -1592,7 +1890,7 @@ class FasterNetBlock(eqx.Module):
|
|
|
1592
1890
|
padding: str | int = "SAME",
|
|
1593
1891
|
norm_layer: eqx.Module | None = eqx.nn.GroupNorm,
|
|
1594
1892
|
norm_max_group: int = 32,
|
|
1595
|
-
act_layer: Callable | None =
|
|
1893
|
+
act_layer: Callable | None = jax.nn.relu,
|
|
1596
1894
|
dropout: float = 0.0,
|
|
1597
1895
|
drop_path: float = 0.0,
|
|
1598
1896
|
norm_kwargs: dict = {},
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
__version__ = "0.4.4"
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|