Equimo 0.4.2__tar.gz → 0.4.4__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.
Files changed (44) hide show
  1. {equimo-0.4.2/src/Equimo.egg-info → equimo-0.4.4}/PKG-INFO +1 -1
  2. {equimo-0.4.2 → equimo-0.4.4}/pyproject.toml +1 -1
  3. {equimo-0.4.2 → equimo-0.4.4/src/Equimo.egg-info}/PKG-INFO +1 -1
  4. equimo-0.4.4/src/equimo/__init__.py +1 -0
  5. {equimo-0.4.2 → equimo-0.4.4}/src/equimo/layers/attention.py +12 -13
  6. {equimo-0.4.2 → equimo-0.4.4}/src/equimo/layers/convolution.py +291 -0
  7. equimo-0.4.2/src/equimo/__init__.py +0 -1
  8. {equimo-0.4.2 → equimo-0.4.4}/LICENSE.md +0 -0
  9. {equimo-0.4.2 → equimo-0.4.4}/README.md +0 -0
  10. {equimo-0.4.2 → equimo-0.4.4}/setup.cfg +0 -0
  11. {equimo-0.4.2 → equimo-0.4.4}/src/Equimo.egg-info/SOURCES.txt +0 -0
  12. {equimo-0.4.2 → equimo-0.4.4}/src/Equimo.egg-info/dependency_links.txt +0 -0
  13. {equimo-0.4.2 → equimo-0.4.4}/src/Equimo.egg-info/requires.txt +0 -0
  14. {equimo-0.4.2 → equimo-0.4.4}/src/Equimo.egg-info/top_level.txt +0 -0
  15. {equimo-0.4.2 → equimo-0.4.4}/src/equimo/conversion/__init__.py +0 -0
  16. {equimo-0.4.2 → equimo-0.4.4}/src/equimo/conversion/utils.py +0 -0
  17. {equimo-0.4.2 → equimo-0.4.4}/src/equimo/experimental/__init__.py +0 -0
  18. {equimo-0.4.2 → equimo-0.4.4}/src/equimo/experimental/text.py +0 -0
  19. {equimo-0.4.2 → equimo-0.4.4}/src/equimo/io.py +0 -0
  20. {equimo-0.4.2 → equimo-0.4.4}/src/equimo/layers/__init__.py +0 -0
  21. {equimo-0.4.2 → equimo-0.4.4}/src/equimo/layers/activation.py +0 -0
  22. {equimo-0.4.2 → equimo-0.4.4}/src/equimo/layers/downsample.py +0 -0
  23. {equimo-0.4.2 → equimo-0.4.4}/src/equimo/layers/dropout.py +0 -0
  24. {equimo-0.4.2 → equimo-0.4.4}/src/equimo/layers/ffn.py +0 -0
  25. {equimo-0.4.2 → equimo-0.4.4}/src/equimo/layers/generic.py +0 -0
  26. {equimo-0.4.2 → equimo-0.4.4}/src/equimo/layers/mamba.py +0 -0
  27. {equimo-0.4.2 → equimo-0.4.4}/src/equimo/layers/norm.py +0 -0
  28. {equimo-0.4.2 → equimo-0.4.4}/src/equimo/layers/patch.py +0 -0
  29. {equimo-0.4.2 → equimo-0.4.4}/src/equimo/layers/posemb.py +0 -0
  30. {equimo-0.4.2 → equimo-0.4.4}/src/equimo/layers/sharing.py +0 -0
  31. {equimo-0.4.2 → equimo-0.4.4}/src/equimo/layers/squeeze_excite.py +0 -0
  32. {equimo-0.4.2 → equimo-0.4.4}/src/equimo/models/__init__.py +0 -0
  33. {equimo-0.4.2 → equimo-0.4.4}/src/equimo/models/emamodel.py +0 -0
  34. {equimo-0.4.2 → equimo-0.4.4}/src/equimo/models/fastervit.py +0 -0
  35. {equimo-0.4.2 → equimo-0.4.4}/src/equimo/models/lowformer.py +0 -0
  36. {equimo-0.4.2 → equimo-0.4.4}/src/equimo/models/mlla.py +0 -0
  37. {equimo-0.4.2 → equimo-0.4.4}/src/equimo/models/partialformer.py +0 -0
  38. {equimo-0.4.2 → equimo-0.4.4}/src/equimo/models/reduceformer.py +0 -0
  39. {equimo-0.4.2 → equimo-0.4.4}/src/equimo/models/shvit.py +0 -0
  40. {equimo-0.4.2 → equimo-0.4.4}/src/equimo/models/vit.py +0 -0
  41. {equimo-0.4.2 → equimo-0.4.4}/src/equimo/models/vssd.py +0 -0
  42. {equimo-0.4.2 → equimo-0.4.4}/src/equimo/ops/scan.py +0 -0
  43. {equimo-0.4.2 → equimo-0.4.4}/src/equimo/utils.py +0 -0
  44. {equimo-0.4.2 → equimo-0.4.4}/tests/test_models.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: Equimo
3
- Version: 0.4.2
3
+ Version: 0.4.4
4
4
  Summary: Implementation of popular vision models in Jax
5
5
  Classifier: Development Status :: 4 - Beta
6
6
  Classifier: Intended Audience :: Developers
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "Equimo"
3
- version = "0.4.2"
3
+ version = "0.4.4"
4
4
  description = "Implementation of popular vision models in Jax"
5
5
  readme = "README.md"
6
6
  requires-python = ">=3.11"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: Equimo
3
- Version: 0.4.2
3
+ Version: 0.4.4
4
4
  Summary: Implementation of popular vision models in Jax
5
5
  Classifier: Development Status :: 4 - Beta
6
6
  Classifier: Intended Audience :: Developers
@@ -0,0 +1 @@
1
+ __version__ = "0.4.4"
@@ -1766,6 +1766,8 @@ class RFAttention(eqx.Module):
1766
1766
 
1767
1767
 
1768
1768
  class RFAttentionBlock(eqx.Module):
1769
+ residual: bool = eqx.field(static=True)
1770
+
1769
1771
  context_module: RFAttention
1770
1772
  local_module: MBConv
1771
1773
  drop_path1: DropPathAdd
@@ -1789,9 +1791,11 @@ class RFAttentionBlock(eqx.Module):
1789
1791
  local_drop: float = 0.0,
1790
1792
  drop_path: float | List[float] = 0.0,
1791
1793
  residual_mbconv: bool = False,
1794
+ residual: bool = True,
1792
1795
  **kwargs,
1793
1796
  ):
1794
1797
  key_context, key_local = jr.split(key, 2)
1798
+ self.residual = residual
1795
1799
 
1796
1800
  if isinstance(drop_path, list):
1797
1801
  if len(drop_path) != 2:
@@ -1841,20 +1845,15 @@ class RFAttentionBlock(eqx.Module):
1841
1845
  key_context, key_local, key_dr1, key_dr2 = jr.split(key, 4)
1842
1846
 
1843
1847
  # TODO: some prenorm?
1844
- x = self.drop_path1(
1845
- x,
1846
- self.context_module(x, inference=inference, key=key_context),
1847
- inference=inference,
1848
- key=key_dr1,
1849
- )
1850
- x = self.drop_path2(
1851
- x,
1852
- self.local_module(x, inference=inference, key=key_local),
1853
- inference=inference,
1854
- key=key_dr2,
1855
- )
1848
+ x1 = self.context_module(x, inference=inference, key=key_context)
1849
+ if self.residual:
1850
+ x1 = self.drop_path1(x, x1, inference=inference, key=key_dr1)
1856
1851
 
1857
- return x
1852
+ x2 = self.local_module(x, inference=inference, key=key_local)
1853
+ if self.residual:
1854
+ x2 = self.drop_path2(x1, x2, inference=inference, key=key_dr2)
1855
+
1856
+ return x2
1858
1857
 
1859
1858
 
1860
1859
  class ConvAttention(eqx.Module):
@@ -1403,3 +1403,294 @@ def finalize_ghostnet(model: eqx.Module) -> eqx.Module:
1403
1403
 
1404
1404
  is_g_module = lambda m: isinstance(m, GenericGhostModule)
1405
1405
  return jax.tree_util.tree_map(_finalize_leaf, model, is_leaf=is_g_module)
1406
+
1407
+
1408
+ class PartialConv2d(eqx.Module):
1409
+ """Partial 2D convolution on the channel dimension.
1410
+
1411
+ This layer applies a standard 2D convolution only to the first `C // n_dim`
1412
+ input channels and leaves the remaining channels untouched (identity). It
1413
+ follows the "partial convolution" idea used to increase throughput by
1414
+ reducing compute on a subset of channels while preserving overall tensor
1415
+ shape, as explored in [1].
1416
+
1417
+ References:
1418
+ [1]. Chen et al., "Run, Don't Walk: Chasing Higher FLOPS for Faster Neural
1419
+ Networks" [arXiv:2303.03667](https://arxiv.org/abs/2303.03667).
1420
+
1421
+ Implementation details:
1422
+ - Let `C` be `in_channels` and `c = C // n_dim`. Only the first `c`
1423
+ channels are convolved with a `Conv2d(c, c, ...)`. The remaining
1424
+ `C - c` channels are forwarded unchanged.
1425
+ - The forward pass uses a functional "update-slice" pattern
1426
+ (`x.at[:c, ...].set(y1)`), which compiles to an efficient
1427
+ `dynamic_update_slice` under XLA. With JIT buffer donation, this can be
1428
+ performed in-place by the compiler.
1429
+ - Spatial dimensions must be preserved by the convolution so that the
1430
+ updated slice matches the input slice shape (e.g., use `stride=1` and
1431
+ `padding="SAME"`). If spatial dimensions change, the slice update will
1432
+ fail with a shape error.
1433
+
1434
+ Attributes
1435
+ - dim: Number of channels to be convolved, computed as `in_channels // n_dim`.
1436
+ This is treated as a static field for compilation stability.
1437
+ - conv: The underlying `eqx.nn.Conv2d(c, c, ...)` applied to the first `c`
1438
+ channels.
1439
+
1440
+ Notes
1441
+ - FLOPs reduction is approximately `c / C = 1 / n_dim` relative to a full
1442
+ convolution with the same kernel.
1443
+ """
1444
+
1445
+ dim: int = eqx.field(static=True)
1446
+
1447
+ conv: eqx.nn.Conv2d
1448
+
1449
+ def __init__(
1450
+ self,
1451
+ in_channels: int,
1452
+ n_dim: int,
1453
+ *,
1454
+ key: PRNGKeyArray,
1455
+ kernel_size: int = 3,
1456
+ padding: str | int = "SAME",
1457
+ use_bias: bool = False,
1458
+ **kwargs,
1459
+ ):
1460
+ """
1461
+ Initialize a PartialConv2d layer.
1462
+
1463
+ Parameters
1464
+ - in_channels: Total number of input channels `C`.
1465
+ - n_dim: Divisor used to determine the number of convolved channels.
1466
+ The layer will convolve `C // n_dim` channels and leave the
1467
+ remaining channels untouched. Must be > 0, and
1468
+ `C // n_dim` must be >= 1 for a meaningful layer.
1469
+ - key: PRNG key used to initialize the underlying convolution weights.
1470
+ - kernel_size: Convolution kernel size (passed to `eqx.nn.Conv2d`).
1471
+ - padding: Convolution padding (passed to `eqx.nn.Conv2d`). Use
1472
+ `"SAME"` to preserve spatial dimensions with `stride=1`.
1473
+ Integer or other forms supported by `eqx.nn.Conv2d` are also
1474
+ accepted, but must preserve H and W for the slice update.
1475
+ - use_bias: Whether to include a bias term in the underlying convolution.
1476
+ - **kwargs: Forwarded to `eqx.nn.Conv2d` (e.g., `dilation`, `groups`).
1477
+ """
1478
+ self.dim = in_channels // n_dim
1479
+ assert self.dim >= 1, "in_channels // n_dim must be >= 1"
1480
+ assert self.dim <= in_channels, (
1481
+ "Computed convolved channels exceed total channels"
1482
+ )
1483
+
1484
+ if isinstance(padding, str):
1485
+ assert padding.upper() == "SAME", (
1486
+ 'When padding is a string, it must be "SAME"'
1487
+ )
1488
+ else:
1489
+ # If padding is numeric, ensure it preserves H, W for stride=1
1490
+ # For Conv2d with dilation d and kernel k, effective kernel is k_eff = (k - 1) * d + 1.
1491
+ # Output H' = H + 2*pad - k_eff + 1; preserving H requires k_eff odd and pad = k_eff // 2.
1492
+ dilation = kwargs.get("dilation", 1)
1493
+ if isinstance(dilation, (tuple, list)):
1494
+ # Require isotropic dilation for simplicity
1495
+ assert len(dilation) == 2 and dilation[0] == dilation[1], (
1496
+ "dilation must be an int or an equal pair"
1497
+ )
1498
+ dilation = dilation[0]
1499
+ assert isinstance(dilation, int) and dilation >= 1, (
1500
+ "dilation must be a positive int"
1501
+ )
1502
+ assert isinstance(kernel_size, int) and kernel_size >= 1, (
1503
+ "kernel_size must be a positive int"
1504
+ )
1505
+
1506
+ k_eff = (kernel_size - 1) * dilation + 1
1507
+ assert isinstance(padding, int) and padding >= 0, (
1508
+ "padding must be a non-negative int"
1509
+ )
1510
+ assert k_eff % 2 == 1 and padding == k_eff // 2, (
1511
+ "Integer padding must preserve spatial size: require effective kernel odd and "
1512
+ f"padding == ((kernel_size-1)*dilation+1)//2; got k_eff={k_eff}, padding={padding}"
1513
+ )
1514
+
1515
+ self.conv = eqx.nn.Conv2d(
1516
+ in_channels=self.dim,
1517
+ out_channels=self.dim,
1518
+ kernel_size=kernel_size,
1519
+ stride=1,
1520
+ padding=padding,
1521
+ use_bias=use_bias,
1522
+ key=key,
1523
+ **kwargs,
1524
+ )
1525
+
1526
+ def __call__(
1527
+ self,
1528
+ x: Float[Array, "channels height width"],
1529
+ *args,
1530
+ **kwargs,
1531
+ ) -> Float[Array, "channels height width"]:
1532
+ c = self.dim
1533
+ x1 = x[:c, :, :]
1534
+ y1 = self.conv(x1)
1535
+
1536
+ return x.at[:c, :, :].set(y1)
1537
+
1538
+
1539
+ class FasterNetBlock(eqx.Module):
1540
+ """
1541
+ FasterNet-style residual block with Partial Convolution-based spatial mixing
1542
+ and pointwise MLP, adapted to Equinox/JAX.
1543
+
1544
+ Structure
1545
+ - Spatial mixing: `PartialConv2d` applies a 3×3 convolution to the first
1546
+ `C // n_dim` channels and leaves the remaining channels unchanged. This
1547
+ reduces compute while keeping the tensor shape intact. See [1].
1548
+ - Channel MLP: two pointwise (1*1) convolutions expand and then project
1549
+ channels (`C -> mlp_ratio*C -> C`), with normalization and activation
1550
+ in between.
1551
+ - Regularization: includes dropout after the MLP and optional stochastic
1552
+ depth (DropPath) on the residual branch.
1553
+ - Residual: optional residual connection `y = x + DropPath(MLP(SpatialMix(x)))`.
1554
+
1555
+ Shape invariants
1556
+ - Input: `[channels, height, width]`
1557
+ - Output: `[channels, height, width]` (same spatial and channel dimensions)
1558
+
1559
+ References
1560
+ - [1] Chen et al., "Run, Don't Walk: Chasing Higher FLOPS for Faster Neural
1561
+ Networks" [arXiv:2303.03667](https://arxiv.org/abs/2303.03667).
1562
+
1563
+ Attributes
1564
+ - residual: Whether to use a residual connection with stochastic depth.
1565
+ - spatial_mixing: `PartialConv2d` performing partial 3×3 spatial mixing.
1566
+ - pw_conv1: Pointwise convolution expanding channels to `mlp_ratio * C`.
1567
+ - pw_conv2: Pointwise convolution projecting channels back to `C`.
1568
+ - norm: Normalization layer applied on the expanded channels.
1569
+ - act: Activation applied after normalization (defaults to identity if none).
1570
+ - dropout: Dropout applied after the MLP.
1571
+ - drop_path: Stochastic depth module for residual addition.
1572
+ """
1573
+
1574
+ residual: bool = eqx.field(static=True)
1575
+
1576
+ spatial_mixing: eqx.nn.Conv2d
1577
+ pw_conv1: eqx.nn.Conv2d
1578
+ pw_conv2: eqx.nn.Conv2d
1579
+ norm: eqx.Module
1580
+ act: eqx.Module
1581
+ dropout: eqx.nn.Dropout
1582
+ drop_path: DropPathAdd
1583
+
1584
+ def __init__(
1585
+ self,
1586
+ in_channels: int,
1587
+ *,
1588
+ key: PRNGKeyArray,
1589
+ n_dim: int = 4,
1590
+ mlp_ratio: int = 3,
1591
+ kernel_size: int = 3,
1592
+ padding: str | int = "SAME",
1593
+ norm_layer: eqx.Module | None = eqx.nn.GroupNorm,
1594
+ norm_max_group: int = 32,
1595
+ act_layer: Callable | None = None,
1596
+ dropout: float = 0.0,
1597
+ drop_path: float = 0.0,
1598
+ norm_kwargs: dict = {},
1599
+ residual: bool = True,
1600
+ **kwargs,
1601
+ ):
1602
+ """
1603
+ Initialize a FasterNetBlock.
1604
+
1605
+ Parameters
1606
+ - in_channels: Number of input/output channels `C`.
1607
+ - key: PRNG key used to initialize submodules. Internally split for
1608
+ spatial mixing and pointwise convolutions.
1609
+ - n_dim: Divisor determining the fraction of channels convolved by
1610
+ `PartialConv2d`; the convolved channels are `C // n_dim`.
1611
+ - mlp_ratio: Expansion ratio for the MLP (1×1 convs): hidden size is
1612
+ `mlp_ratio * C`.
1613
+ - kernel_size: Kernel size for the spatial mixing convolution (passed
1614
+ to `PartialConv2d`).
1615
+ - padding: Padding for the spatial mixing convolution. Use `"SAME"`
1616
+ to preserve spatial dimensions.
1617
+ - norm_layer: Normalization constructor applied on the expanded
1618
+ channels. If `eqx.nn.GroupNorm`, the number of groups is chosen as
1619
+ the largest power-of-two divisor of `hidden_channels` not exceeding
1620
+ `norm_max_group`. If `None`, uses identity.
1621
+ - norm_max_group: Maximum group count when using `GroupNorm`.
1622
+ - act_layer: Callable used to construct an activation function. If
1623
+ `None`, uses identity. Passed to `eqx.nn.Lambda`.
1624
+ - dropout: Dropout probability applied after the MLP branch.
1625
+ - drop_path: Stochastic depth probability on the residual branch.
1626
+ - norm_kwargs: Extra keyword arguments forwarded to the normalization
1627
+ layer constructor.
1628
+ - residual: Whether to add the residual connection (with DropPath).
1629
+ - **kwargs: Reserved for future extensions; forwarded where applicable.
1630
+
1631
+ Notes
1632
+ - The block preserves `[H, W]`. Ensure `PartialConv2d` is configured
1633
+ to preserve spatial dimensions (e.g., `"SAME"` padding).
1634
+ - When `norm_layer` is not `GroupNorm`, it should accept the channel
1635
+ count as its first argument.
1636
+ - The same input/output channel count `C` is used throughout the block.
1637
+ """
1638
+
1639
+ key_sm, key_pw1, key_pw2 = jr.split(key, 3)
1640
+ self.residual = residual
1641
+
1642
+ self.spatial_mixing = PartialConv2d(
1643
+ in_channels=in_channels, n_dim=n_dim, key=key_sm
1644
+ )
1645
+
1646
+ hidden_channels = mlp_ratio * in_channels
1647
+ self.pw_conv1 = eqx.nn.Conv2d(
1648
+ in_channels,
1649
+ hidden_channels,
1650
+ kernel_size=1,
1651
+ stride=1,
1652
+ padding="SAME",
1653
+ use_bias=False,
1654
+ key=key_pw1,
1655
+ )
1656
+ self.pw_conv2 = eqx.nn.Conv2d(
1657
+ hidden_channels,
1658
+ in_channels,
1659
+ kernel_size=1,
1660
+ stride=1,
1661
+ padding="SAME",
1662
+ use_bias=False,
1663
+ key=key_pw2,
1664
+ )
1665
+
1666
+ if norm_layer is not None:
1667
+ if norm_layer == eqx.nn.GroupNorm:
1668
+ num_groups = nearest_power_of_2_divisor(hidden_channels, norm_max_group)
1669
+ self.norm = eqx.nn.GroupNorm(num_groups, hidden_channels, **norm_kwargs)
1670
+ else:
1671
+ self.norm = norm_layer(hidden_channels, **norm_kwargs)
1672
+ else:
1673
+ self.norm = eqx.nn.Identity()
1674
+
1675
+ self.dropout = eqx.nn.Dropout(dropout)
1676
+ self.drop_path = DropPathAdd(drop_path)
1677
+ self.act = eqx.nn.Lambda(act_layer) if act_layer else eqx.nn.Identity()
1678
+
1679
+ def __call__(
1680
+ self,
1681
+ x: Float[Array, "channels height width"],
1682
+ key: PRNGKeyArray,
1683
+ inference: Optional[bool] = None,
1684
+ ) -> Float[Array, "channels height width"]:
1685
+ key_dropout, key_droppath = jr.split(key, 2)
1686
+ x1 = self.spatial_mixing(x)
1687
+ out = self.dropout(
1688
+ self.pw_conv2(self.act(self.norm(self.pw_conv1(x1)))),
1689
+ inference=inference,
1690
+ key=key_dropout,
1691
+ )
1692
+
1693
+ if self.residual:
1694
+ out = self.drop_path(x, out, inference=inference, key=key_droppath)
1695
+
1696
+ return out
@@ -1 +0,0 @@
1
- __version__ = "0.4.2"
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