stackformers 4.3.0__tar.gz → 5.0.0rc3__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 (161) hide show
  1. {stackformers-4.3.0 → stackformers-5.0.0rc3}/.gitignore +2 -1
  2. stackformers-5.0.0rc3/AGENTS.md +19 -0
  3. {stackformers-4.3.0 → stackformers-5.0.0rc3}/CHANGELOG.md +246 -0
  4. stackformers-5.0.0rc3/Justfile +73 -0
  5. {stackformers-4.3.0 → stackformers-5.0.0rc3}/PKG-INFO +114 -3
  6. stackformers-5.0.0rc3/README.md +265 -0
  7. stackformers-5.0.0rc3/examples/README.md +58 -0
  8. stackformers-5.0.0rc3/examples/causal_language_model.py +99 -0
  9. stackformers-5.0.0rc3/examples/encoder_decoder_onnxruntime.py +357 -0
  10. stackformers-5.0.0rc3/examples/image_encoder.py +90 -0
  11. stackformers-5.0.0rc3/examples/padded_and_packed.py +77 -0
  12. stackformers-5.0.0rc3/examples/point_cloud_encoder.py +87 -0
  13. stackformers-5.0.0rc3/examples/subclassed_encoder.py +153 -0
  14. stackformers-5.0.0rc3/pyproject.toml +52 -0
  15. stackformers-5.0.0rc3/pyrefly.toml +31 -0
  16. stackformers-5.0.0rc3/pytest.ini +4 -0
  17. stackformers-5.0.0rc3/ruff.toml +21 -0
  18. stackformers-5.0.0rc3/stackformers/DEVLOG.md +396 -0
  19. stackformers-5.0.0rc3/stackformers/__init__.py +258 -0
  20. stackformers-5.0.0rc3/stackformers/_result.py +16 -0
  21. {stackformers-4.3.0 → stackformers-5.0.0rc3}/stackformers/attention/README.md +10 -0
  22. {stackformers-4.3.0 → stackformers-5.0.0rc3}/stackformers/attention/__init__.py +16 -1
  23. {stackformers-4.3.0 → stackformers-5.0.0rc3}/stackformers/attention/bias.py +4 -1
  24. stackformers-5.0.0rc3/stackformers/attention/cache.py +40 -0
  25. stackformers-5.0.0rc3/stackformers/attention/cache_validation.py +53 -0
  26. stackformers-5.0.0rc3/stackformers/attention/cached.py +181 -0
  27. {stackformers-4.3.0 → stackformers-5.0.0rc3}/stackformers/attention/config.py +27 -10
  28. {stackformers-4.3.0 → stackformers-5.0.0rc3}/stackformers/attention/cross_attn.py +35 -10
  29. {stackformers-4.3.0 → stackformers-5.0.0rc3}/stackformers/attention/distance_bias.py +17 -5
  30. {stackformers-4.3.0 → stackformers-5.0.0rc3}/stackformers/attention/factory.py +6 -2
  31. stackformers-5.0.0rc3/stackformers/attention/layout.py +32 -0
  32. {stackformers-4.3.0 → stackformers-5.0.0rc3}/stackformers/attention/ops.py +25 -11
  33. stackformers-5.0.0rc3/stackformers/attention/protocols.py +46 -0
  34. {stackformers-4.3.0 → stackformers-5.0.0rc3}/stackformers/attention/self_attn.py +23 -8
  35. stackformers-5.0.0rc3/stackformers/attention/varlen_backend.py +167 -0
  36. stackformers-5.0.0rc3/stackformers/cross_attender.py +183 -0
  37. stackformers-5.0.0rc3/stackformers/decoder.py +211 -0
  38. stackformers-5.0.0rc3/stackformers/decoder_cache.py +349 -0
  39. {stackformers-4.3.0 → stackformers-5.0.0rc3}/stackformers/encoder.py +16 -4
  40. stackformers-5.0.0rc3/stackformers/feedforward/DEVLOG.md +27 -0
  41. {stackformers-4.3.0 → stackformers-5.0.0rc3}/stackformers/feedforward/README.md +3 -1
  42. stackformers-5.0.0rc3/stackformers/feedforward/__init__.py +33 -0
  43. {stackformers-4.3.0 → stackformers-5.0.0rc3}/stackformers/feedforward/config.py +28 -4
  44. {stackformers-4.3.0 → stackformers-5.0.0rc3}/stackformers/feedforward/factory.py +17 -2
  45. {stackformers-4.3.0 → stackformers-5.0.0rc3}/stackformers/feedforward/geglu.py +13 -3
  46. stackformers-5.0.0rc3/stackformers/feedforward/gelu.py +36 -0
  47. stackformers-5.0.0rc3/stackformers/feedforward/hardswish_glu.py +47 -0
  48. {stackformers-4.3.0 → stackformers-5.0.0rc3}/stackformers/feedforward/protocols.py +5 -7
  49. {stackformers-4.3.0 → stackformers-5.0.0rc3}/stackformers/feedforward/relu_squared.py +13 -3
  50. {stackformers-4.3.0 → stackformers-5.0.0rc3}/stackformers/feedforward/swiglu.py +13 -3
  51. stackformers-5.0.0rc3/stackformers/layers.py +158 -0
  52. {stackformers-4.3.0 → stackformers-5.0.0rc3}/stackformers/mlm/README.md +4 -2
  53. {stackformers-4.3.0 → stackformers-5.0.0rc3}/stackformers/mlm/head.py +14 -2
  54. stackformers-5.0.0rc3/stackformers/mlm/head_cosine.py +39 -0
  55. {stackformers-4.3.0 → stackformers-5.0.0rc3}/stackformers/mlm/masking.py +10 -1
  56. {stackformers-4.3.0 → stackformers-5.0.0rc3}/stackformers/mlm/protocols.py +8 -11
  57. {stackformers-4.3.0 → stackformers-5.0.0rc3}/stackformers/mlm/wrapper.py +30 -11
  58. stackformers-5.0.0rc3/stackformers/norm/__init__.py +5 -0
  59. {stackformers-4.3.0 → stackformers-5.0.0rc3}/stackformers/norm/config.py +4 -0
  60. {stackformers-4.3.0 → stackformers-5.0.0rc3}/stackformers/norm/factory.py +6 -2
  61. stackformers-5.0.0rc3/stackformers/norm/protocols.py +14 -0
  62. {stackformers-4.3.0 → stackformers-5.0.0rc3}/stackformers/positional/__init__.py +12 -0
  63. {stackformers-4.3.0 → stackformers-5.0.0rc3}/stackformers/positional/config.py +38 -16
  64. {stackformers-4.3.0 → stackformers-5.0.0rc3}/stackformers/positional/factory.py +6 -2
  65. {stackformers-4.3.0 → stackformers-5.0.0rc3}/stackformers/positional/learned.py +6 -2
  66. {stackformers-4.3.0 → stackformers-5.0.0rc3}/stackformers/positional/none.py +8 -3
  67. {stackformers-4.3.0 → stackformers-5.0.0rc3}/stackformers/positional/protocols.py +4 -4
  68. {stackformers-4.3.0 → stackformers-5.0.0rc3}/stackformers/positional/rope1d.py +13 -4
  69. {stackformers-4.3.0 → stackformers-5.0.0rc3}/stackformers/positional/rope2d.py +16 -4
  70. {stackformers-4.3.0 → stackformers-5.0.0rc3}/stackformers/positional/rope_nd.py +9 -3
  71. stackformers-5.0.0rc3/stackformers/positional/validation.py +12 -0
  72. {stackformers-4.3.0 → stackformers-5.0.0rc3}/stackformers/presets/README.md +4 -2
  73. {stackformers-4.3.0 → stackformers-5.0.0rc3}/stackformers/presets/__init__.py +14 -0
  74. {stackformers-4.3.0 → stackformers-5.0.0rc3}/stackformers/presets/cross_attender.py +71 -18
  75. {stackformers-4.3.0 → stackformers-5.0.0rc3}/stackformers/presets/decoder.py +90 -28
  76. {stackformers-4.3.0 → stackformers-5.0.0rc3}/stackformers/presets/encoder.py +89 -23
  77. stackformers-5.0.0rc3/stackformers/presets/variable_width_encoder.py +363 -0
  78. stackformers-5.0.0rc3/stackformers/py.typed +1 -0
  79. {stackformers-4.3.0 → stackformers-5.0.0rc3}/stackformers/sequence.py +6 -3
  80. stackformers-5.0.0rc3/tests/attention/legacy_api_rc1.json +1404 -0
  81. stackformers-5.0.0rc3/tests/attention/test_contracts.py +70 -0
  82. stackformers-5.0.0rc3/tests/attention/test_cross_attn_cache.py +135 -0
  83. stackformers-5.0.0rc3/tests/attention/test_empty_rows.py +72 -0
  84. stackformers-5.0.0rc3/tests/attention/test_geometry.py +72 -0
  85. stackformers-5.0.0rc3/tests/attention/test_legacy_api.py +202 -0
  86. {stackformers-4.3.0 → stackformers-5.0.0rc3}/tests/attention/test_ops.py +42 -18
  87. stackformers-5.0.0rc3/tests/attention/test_sdpa_export.py +104 -0
  88. {stackformers-4.3.0 → stackformers-5.0.0rc3}/tests/attention/test_self_attn.py +49 -0
  89. {stackformers-4.3.0 → stackformers-5.0.0rc3}/tests/attention/test_varlen_backend.py +82 -8
  90. {stackformers-4.3.0 → stackformers-5.0.0rc3}/tests/conftest.py +41 -0
  91. stackformers-5.0.0rc3/tests/export_utils.py +145 -0
  92. stackformers-5.0.0rc3/tests/feedforward/test_gelu.py +72 -0
  93. stackformers-5.0.0rc3/tests/feedforward/test_hardswish_glu.py +75 -0
  94. stackformers-5.0.0rc3/tests/mlm/test_empty_selection.py +93 -0
  95. stackformers-5.0.0rc3/tests/mlm/test_head_cosine.py +48 -0
  96. {stackformers-4.3.0 → stackformers-5.0.0rc3}/tests/mlm/test_wrapper.py +50 -8
  97. stackformers-5.0.0rc3/tests/norm_topology_helpers.py +87 -0
  98. stackformers-5.0.0rc3/tests/positional/test_cacheable.py +69 -0
  99. {stackformers-4.3.0 → stackformers-5.0.0rc3}/tests/positional/test_rope.py +148 -44
  100. {stackformers-4.3.0 → stackformers-5.0.0rc3}/tests/positional/test_rope_nd.py +6 -3
  101. stackformers-5.0.0rc3/tests/presets/test_cross_attender.py +144 -0
  102. stackformers-5.0.0rc3/tests/presets/test_decoder.py +139 -0
  103. stackformers-5.0.0rc3/tests/presets/test_encoder.py +275 -0
  104. stackformers-5.0.0rc3/tests/presets/test_variable_width_encoder.py +566 -0
  105. stackformers-5.0.0rc3/tests/test_core_results.py +221 -0
  106. stackformers-5.0.0rc3/tests/test_cross_attender.py +287 -0
  107. stackformers-5.0.0rc3/tests/test_decoder.py +332 -0
  108. stackformers-5.0.0rc3/tests/test_decoder_cache.py +281 -0
  109. {stackformers-4.3.0 → stackformers-5.0.0rc3}/tests/test_encoder.py +5 -1
  110. stackformers-5.0.0rc3/tests/test_encoder_decoder_example.py +79 -0
  111. stackformers-5.0.0rc3/tests/test_examples.py +57 -0
  112. stackformers-5.0.0rc3/tests/test_layers.py +289 -0
  113. {stackformers-4.3.0 → stackformers-5.0.0rc3}/tests/test_norm.py +6 -2
  114. stackformers-5.0.0rc3/tests/test_public_api.py +143 -0
  115. stackformers-5.0.0rc3/tests/test_result.py +84 -0
  116. {stackformers-4.3.0 → stackformers-5.0.0rc3}/tests/test_sequence.py +67 -22
  117. {stackformers-4.3.0 → stackformers-5.0.0rc3}/uv.lock +303 -46
  118. stackformers-4.3.0/Justfile +0 -68
  119. stackformers-4.3.0/README.md +0 -156
  120. stackformers-4.3.0/pyproject.toml +0 -72
  121. stackformers-4.3.0/stackformers/__init__.py +0 -150
  122. stackformers-4.3.0/stackformers/attention/protocols.py +0 -44
  123. stackformers-4.3.0/stackformers/attention/varlen_backend.py +0 -88
  124. stackformers-4.3.0/stackformers/cross_attender.py +0 -46
  125. stackformers-4.3.0/stackformers/decoder.py +0 -57
  126. stackformers-4.3.0/stackformers/feedforward/__init__.py +0 -5
  127. stackformers-4.3.0/stackformers/layers.py +0 -31
  128. stackformers-4.3.0/stackformers/norm/__init__.py +0 -3
  129. stackformers-4.3.0/stackformers/norm/protocols.py +0 -16
  130. stackformers-4.3.0/tests/presets/test_cross_attender.py +0 -36
  131. stackformers-4.3.0/tests/presets/test_decoder.py +0 -36
  132. stackformers-4.3.0/tests/presets/test_encoder.py +0 -102
  133. stackformers-4.3.0/tests/test_cross_attender.py +0 -118
  134. stackformers-4.3.0/tests/test_decoder.py +0 -141
  135. stackformers-4.3.0/tests/test_layers.py +0 -71
  136. {stackformers-4.3.0 → stackformers-5.0.0rc3}/.claudeignore +0 -0
  137. {stackformers-4.3.0 → stackformers-5.0.0rc3}/.python-version +0 -0
  138. {stackformers-4.3.0 → stackformers-5.0.0rc3}/.vscode/settings.json +0 -0
  139. {stackformers-4.3.0 → stackformers-5.0.0rc3}/CLAUDE.md +0 -0
  140. {stackformers-4.3.0 → stackformers-5.0.0rc3}/LICENSE +0 -0
  141. {stackformers-4.3.0 → stackformers-5.0.0rc3}/PLAN.md +0 -0
  142. {stackformers-4.3.0 → stackformers-5.0.0rc3}/run-remote-tests.sh +0 -0
  143. {stackformers-4.3.0 → stackformers-5.0.0rc3}/stackformers/config.py +0 -0
  144. {stackformers-4.3.0 → stackformers-5.0.0rc3}/stackformers/mlm/config.py +0 -0
  145. {stackformers-4.3.0 → stackformers-5.0.0rc3}/stackformers/norm/README.md +0 -0
  146. {stackformers-4.3.0 → stackformers-5.0.0rc3}/stackformers/positional/README.md +0 -0
  147. {stackformers-4.3.0 → stackformers-5.0.0rc3}/tests/__init__.py +0 -0
  148. {stackformers-4.3.0 → stackformers-5.0.0rc3}/tests/attention/__init__.py +0 -0
  149. {stackformers-4.3.0 → stackformers-5.0.0rc3}/tests/attention/test_cross_attn.py +0 -0
  150. {stackformers-4.3.0 → stackformers-5.0.0rc3}/tests/attention/test_distance_bias.py +0 -0
  151. {stackformers-4.3.0 → stackformers-5.0.0rc3}/tests/attention/test_kernels.py +0 -0
  152. {stackformers-4.3.0 → stackformers-5.0.0rc3}/tests/feedforward/__init__.py +0 -0
  153. {stackformers-4.3.0 → stackformers-5.0.0rc3}/tests/feedforward/test_geglu.py +0 -0
  154. {stackformers-4.3.0 → stackformers-5.0.0rc3}/tests/feedforward/test_relu_squared.py +0 -0
  155. {stackformers-4.3.0 → stackformers-5.0.0rc3}/tests/feedforward/test_swiglu.py +0 -0
  156. {stackformers-4.3.0 → stackformers-5.0.0rc3}/tests/mlm/__init__.py +0 -0
  157. {stackformers-4.3.0 → stackformers-5.0.0rc3}/tests/mlm/test_head.py +0 -0
  158. {stackformers-4.3.0 → stackformers-5.0.0rc3}/tests/mlm/test_masking.py +0 -0
  159. {stackformers-4.3.0 → stackformers-5.0.0rc3}/tests/positional/__init__.py +0 -0
  160. {stackformers-4.3.0 → stackformers-5.0.0rc3}/tests/positional/test_learned_pos.py +0 -0
  161. {stackformers-4.3.0 → stackformers-5.0.0rc3}/tests/presets/__init__.py +0 -0
@@ -37,6 +37,7 @@ pip-log.txt
37
37
  pip-delete-this-directory.txt
38
38
 
39
39
  # Unit test / coverage reports
40
+ /work_dir/
40
41
  htmlcov/
41
42
  .tox/
42
43
  .nox/
@@ -216,4 +217,4 @@ __marimo__/
216
217
 
217
218
  # Streamlit
218
219
  .streamlit/secrets.toml
219
- x-transformers
220
+ x-transformers
@@ -0,0 +1,19 @@
1
+ # Repository guidance
2
+
3
+ ## Repository commands
4
+
5
+ Read `Justfile` before running setup, build, formatting, linting, type-checking, testing, or
6
+ cleanup commands. Prefer its existing recipes so local and automated workflows use the same
7
+ tool arguments and defaults.
8
+
9
+ ## Python structured records
10
+
11
+ Never use `dataclasses` in this repository. PyTorch recognizes `NamedTuple` and `TypedDict`
12
+ reliably across pytree, export, and compilation boundaries, while dataclasses require additional
13
+ registration and handling.
14
+
15
+ - Use `NamedTuple` for fixed-shape records with named fields, especially tensor inputs and outputs.
16
+ - Use `TypedDict` when the value must retain mapping semantics.
17
+ - Do not introduce `dataclass`, `@dataclass`, or imports from `dataclasses`.
18
+
19
+ Ruff enforces the import restriction through `TID251`.
@@ -6,6 +6,252 @@ The format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and
6
6
  adheres to [Semantic Versioning](https://semver.org/): MAJOR for breaking public API changes,
7
7
  MINOR for backwards-compatible features, PATCH for bug fixes and internal changes.
8
8
 
9
+ ## [Unreleased]
10
+
11
+ ## [5.0.0rc3] — 2026-09-26
12
+
13
+ ### New Features
14
+
15
+ - Accept shared or per-layer `dim_heads`, `heads`, `causal`, `ff_mult`, `dropout`, and
16
+ `norm_placement` in the variable-width encoder factory. Explicit `heads` allows independent
17
+ model and attention widths; omitting it retains exact `d_model // dim_head` inference.
18
+
19
+ ### Bug Fixes
20
+
21
+ - Allow model widths that are not divisible by the query-head count in self- and
22
+ cross-attention configs. Internal query width remains `heads * dim_head`; grouped
23
+ attention still requires `heads` divisible by `kv_heads`.
24
+
25
+ ## [5.0.0rc1] — 2026-09-26
26
+
27
+ ### Backwards Incompatible Changes
28
+
29
+ #### Immutable attention configuration
30
+
31
+ `SelfAttentionConfig` and `CrossAttentionConfig` previously allowed field assignment after
32
+ construction. They are now immutable, preventing a shared config from reinterpreting an
33
+ existing model's projections. Construct a new config and model for different architecture
34
+ settings instead of assigning config fields.
35
+
36
+ #### Matching cross-attention layouts
37
+
38
+ Cross-attention previously accepted mixed padded/packed layouts in its annotations and failed
39
+ during tensor reshaping. Its type contract now requires matching layouts, and mixed layouts
40
+ raise `ValueError` at runtime. Narrow both inputs together when calling through the `CrossAttn`
41
+ protocol, or convert them to the same layout before calling.
42
+
43
+ #### Empty reconstruction selections
44
+
45
+ Custom reconstruction heads must now return a differentiable zero loss for zero selected
46
+ tokens; earlier head contracts left empty-input behavior unspecified. Add an empty-input guard
47
+ that returns `prediction.sum()` to custom heads. Independent sampling is unchanged, so callers
48
+ must continue to support batches in which no tokens are selected.
49
+
50
+ ### Bug Fixes
51
+
52
+ - Return finite zero losses from both built-in MLM heads when no tokens are selected.
53
+ - Exclude masked keys completely and return zero attention contributions and gradients when
54
+ padding, windows, or additive bias exclude every key, including exported ONNX execution.
55
+ - Fix ONNX Runtime attention-mask shape errors for opsets 23–25 by explicitly expanding
56
+ the query dimension, including dynamic batch and sequence lengths.
57
+
58
+ ### Performance
59
+
60
+ - Avoid repadding input features on the packed self-attention path when using `NoAttnBias`.
61
+
62
+ ### Developers
63
+
64
+ - Include the `py.typed` marker so type checkers recognize annotations in installed wheels.
65
+ - Use shared `returns` Result values for core layout, geometry, cache, and backend checks.
66
+ Preserve public tensor/configuration returns, validation exceptions, and fallback warnings;
67
+ retain original backend failure detail for callers that handle outcomes directly.
68
+ - Apply rustic-python's strict Pyrefly and Ruff profile to the library, tests, and examples;
69
+ require Pyrefly 1.3.1 and Ruff 0.16.9 or newer and check Python 3.11 compatibility.
70
+ - Expose typed module-call signatures without replacing PyTorch's runtime hook dispatch,
71
+ type the experimental attention-kernel boundary, and replace stale type suppressions.
72
+ - Require ONNX Runtime parity at opsets 23–25 instead of accepting opset-23 failures or
73
+ excluding newer dynamo exports using PyTorch's legacy exporter limit.
74
+
75
+ ## [4.7.0b6] — 2026-08-31
76
+
77
+ ### Documentation
78
+
79
+ - Add runnable, CPU-friendly examples for equivalent padded and packed batches, causal language
80
+ modeling, image patches with RoPE-2D, continuous point coordinates with RoPE-ND, and both
81
+ focused and fully user-owned encoder subclassing.
82
+
83
+ ## [4.7.0b5] — 2026-08-28
84
+
85
+ ### Highlights
86
+
87
+ - Add an export-oriented decoder path that reuses cross-attention K/V projections and grows a
88
+ self-attention K/V cache one token at a time, while leaving existing encoder, decoder, and
89
+ attention APIs unchanged.
90
+
91
+ ### New Features
92
+
93
+ - Add composition-only `CachedSelfAttentionWrapper`, `CachedCrossAttentionWrapper`,
94
+ `DecoderCrossAttentionCacheBuilder`, and `CachedDecoderWrapper` APIs. The wrappers share the
95
+ existing model's parameters and support every built-in positional encoding and decoder
96
+ normalization topology.
97
+ - Define a C++/ONNX Runtime-friendly tensor boundary with required, non-optional cross and self
98
+ caches, a required `step_i` tensor, dynamically shaped batch/source/history axes, and a returned
99
+ self cache for the next decoder session run. Cached self-attention currently requires global
100
+ causal attention with `NoAttnBias`.
101
+
102
+ ### Documentation
103
+
104
+ - Add an encoder-decoder example that exports separate encoder, cross-cache builder, and cached
105
+ one-token decoder ONNX graphs, then verifies their autoregressive outputs with ONNX Runtime.
106
+ - Document cache layouts, tensor names, ownership, lifecycle, dynamic axes, and the initial
107
+ zero-length self-cache contract for C++ integration.
108
+
109
+ ### Developers
110
+
111
+ - Add eager parity, gradient, export, ONNX Runtime, positional-encoding, and stable `4.6.0` public
112
+ API regression coverage for the cached path.
113
+ - Standardize export-facing structured records on `NamedTuple` or `TypedDict` and enforce the
114
+ repository's no-dataclass policy with Ruff.
115
+
116
+ ## [4.7.0b4] — 2026-08-19
117
+
118
+ ### Backwards Incompatible Changes
119
+
120
+ #### Variable-width encoder configuration
121
+
122
+ `VariableWidthTransformerEncoderConfig` again stores `layers: list[VariableWidthEncoderLayerConfig]`
123
+ instead of the `d_models`/`dim_heads` representation introduced in `4.7.0b3`. Each layer record
124
+ keeps its attention, feed-forward, norm, positional-encoding, and attention-bias choices explicit
125
+ and validates their dimensions together. Migrate direct `4.7.0b3` constructors to complete layer
126
+ records. The convenience config factory now requires explicit keyword arguments; replace
127
+ `variable_width_encoder_config(d_models, dim_heads)` with
128
+ `variable_width_encoder_config(d_models=d_models, dim_heads=dim_heads)`.
129
+
130
+ ### New Features
131
+
132
+ - Export all concrete feed-forward configs and implementations, learned/ND positional encodings,
133
+ attention-bias configs, `VariableWidthEncoderLayerConfig`, and `node_encoder_config` from the
134
+ package root. Variable-width layers can select any public component config without subclassing.
135
+
136
+ ### Developers
137
+
138
+ - Add static and dynamic PyTorch/ONNX export coverage with ONNX Runtime parity for every public
139
+ feed-forward variant and the existing Transformer configuration paths.
140
+
141
+ ## [4.7.0b3] — 2026-08-19
142
+
143
+ ### Backwards Incompatible Changes
144
+
145
+ #### Variable-width encoder configuration
146
+
147
+ `VariableWidthTransformerEncoderConfig` now represents variable block geometry as parallel
148
+ `d_models` and `dim_heads` arrays, with `causal`, `ff_mult`, `dropout`, and `norm_placement` stored
149
+ once as shared settings. Earlier beta configs stored an array of
150
+ `VariableWidthEncoderLayerConfig` objects under `layers`; that class and its public export are
151
+ removed. Explicit constructors and serialized beta configs must migrate from values such as
152
+ `config.layers[index].attn.dim` to `config.d_models[index]`. The preset continues to construct
153
+ global attention with RoPE, RMSNorm, SwiGLU, and no attention bias. Callers of
154
+ `variable_width_encoder_config` are unchanged.
155
+
156
+ ## [4.7.0b2] — 2026-08-19
157
+
158
+ ### Added
159
+
160
+ - **`HardSwishGLU`** — a bias-free, parameter-matched gated feed-forward variant using
161
+ `nn.Hardswish` instead of SwiGLU's `nn.SiLU`. Select it with
162
+ `HardSwishGLUConfig(kind="hardswish_glu")`; serialized configurations preserve the distinction
163
+ between the exact SwiGLU operation and its piecewise-linear HardSwish alternative.
164
+
165
+ ### Compatibility
166
+
167
+ - Existing feed-forward classes, config discriminators, presets, and defaults are unchanged.
168
+ `HardSwishGLU` is opt-in and uses the same input/output shape and hidden-width rule as SwiGLU.
169
+
170
+ ## [4.7.0b1] — 2026-08-18
171
+
172
+ ### Added
173
+
174
+ - **Variable-width Transformer encoder preset** through `VariableWidthTransformerEncoder` and
175
+ `variable_width_encoder_config`. Callers provide one model width and head dimension per block;
176
+ the preset derives head counts and inserts a bias-free learned projection only where adjacent
177
+ model widths differ. Expanded layer configs retain explicit attention, feed-forward, norm,
178
+ positional-encoding, and attention-bias collaborators and support JSON round trips.
179
+
180
+ ### Compatibility
181
+
182
+ - Existing uniform-width encoder classes, configs, serialized payloads, checkpoints, and defaults
183
+ are unchanged. Variable-width models use their own preset and state-dict structure.
184
+
185
+ ## [4.6.0] — 2026-08-14
186
+
187
+ ### Added
188
+
189
+ - **Configurable normalization placement** for encoder, decoder, and cross-attender presets through
190
+ `NormPlacement = Literal["pre", "post", "sandwich", "reordered"]`. Each topology has a focused
191
+ layer class while the existing `TransformerLayer`, `DecoderLayer`, and `CrossAttenderLayer`
192
+ remain the pre-norm implementations. `"reordered"` implements the OLMo 2 residual layout
193
+ (`x + norm(branch(x))`), while sandwich uses distinct norms before and after every residual
194
+ branch—four per encoder/cross-attender layer and six per decoder layer.
195
+
196
+ ### Compatibility
197
+
198
+ - **The default remains `"pre"`.** Omitting `norm_placement` preserves the previous operation
199
+ order, exact outputs and gradients, positional `TransformerLayer` constructor, parameter names,
200
+ and state-dict shapes across encoder, decoder, and cross-attender layers. Config payloads without
201
+ the new field deserialize as pre-norm. The three legacy layer classes retain their original
202
+ constructors and module attributes.
203
+ - Serialized preset-config `model_dump()` output now includes
204
+ `"norm_placement": "pre"` unless callers request default-value exclusion. Sandwich is the only
205
+ mode that adds parameters because its post-branch norms must be independent.
206
+
207
+ ## [4.5.0] — 2026-08-14
208
+
209
+ ### Added
210
+
211
+ - **`GELUFFN`** — a standard bias-free, two-projection Transformer feed-forward network using
212
+ `nn.GELU(approximate="tanh")`. Select it with `GELUConfig(kind="gelu")`; its hidden width is
213
+ `int(dim * mult)`, matching other non-gated FFNs.
214
+
215
+ ## [4.4.0] — 2026-07-17
216
+
217
+ ### Added
218
+
219
+ - **`CosineHead`** (`stackformers/mlm/head_cosine.py`) — a `ReconstructionHead` that scores
220
+ a linear projection of the encoder's masked-position output against the target via
221
+ `1 - cosine_similarity`, instead of `RegressionHead`'s MSE.
222
+
223
+ ### Changed
224
+
225
+ - **`MLMWrapper`'s default `head` is now `CosineHead`, not `RegressionHead`.**
226
+ `mlm_loss`'s target is `input.x`, and nothing constrains its scale: once the tokenizer
227
+ trains purely under the main task (`mlm_loss` has zero gradient into it, per 4.3.1),
228
+ its output distribution is free to drift arbitrarily over training. MSE is sensitive
229
+ to that drift — matching magnitude is part of what it scores — while cosine
230
+ similarity scores direction only, so it stays meaningful regardless of where the
231
+ target's scale currently sits. `RegressionHead` is unaffected and still available;
232
+ pass `MLMWrapper(..., head=RegressionHead(dim))` to keep the previous behaviour.
233
+
234
+ ## [4.3.1] — 2026-07-17
235
+
236
+ ### Fixed
237
+
238
+ - **`MLMWrapper`'s masked pass leaked gradient to whatever produced `input.x`, despite
239
+ the target being detached.** `corrupted_x` was built from the live (undetached)
240
+ `input.x` at unmasked positions; self-attention mixes those into the masked
241
+ positions' predictions, so `mlm_loss` still reached back through every unmasked
242
+ position — confirmed empirically at 432/512 nonzero gradient entries under a
243
+ realistic 15% mask ratio. The existing test only covered 100% masking, which zeroes
244
+ that path as a side effect of `torch.where`'s backward and never exercised the
245
+ realistic partial-masking case.
246
+
247
+ Fixed by detaching `input.x` once, up front
248
+ (`input = input._replace(x=input.x.detach())`), so both `corrupted_x` and `target`
249
+ are built from the same severed copy. `mlm_loss` now trains only `mask_token`,
250
+ `encoder`, and `head` — never whatever produced `input.x` — regardless of mask
251
+ ratio. Added a regression test at the realistic mask ratio
252
+ (`test_wrapper_severs_gradient_to_upstream_input_under_partial_masking`) alongside
253
+ the existing full-masking one.
254
+
9
255
  ## [4.3.0] — 2026-07-17
10
256
 
11
257
  ### Added
@@ -0,0 +1,73 @@
1
+ set dotenv-load := false
2
+
3
+ python_sources := "stackformers/ tests/ examples/"
4
+
5
+ # List repository commands
6
+ default:
7
+ @just --list
8
+
9
+ # Install all dependencies (including dev)
10
+ [group('environment')]
11
+ sync:
12
+ uv sync --group dev
13
+
14
+ # Build distributions without retaining stale artifacts
15
+ [group('packaging')]
16
+ build:
17
+ uv build --clear
18
+
19
+ # Format source and tests
20
+ [group('quality')]
21
+ fmt:
22
+ uv run ruff format {{ python_sources }}
23
+
24
+ # Check formatting without modifying files
25
+ [group('quality')]
26
+ fmt-check:
27
+ uv run ruff format --check {{ python_sources }}
28
+
29
+ # Lint source and tests
30
+ [group('quality')]
31
+ lint:
32
+ uv run ruff check {{ python_sources }}
33
+
34
+ # Lint and auto-fix what's safe
35
+ [group('quality')]
36
+ lint-fix:
37
+ uv run ruff check --fix {{ python_sources }}
38
+
39
+ # Type-check with pyrefly
40
+ [group('quality')]
41
+ types:
42
+ uv run pyrefly check {{ python_sources }}
43
+
44
+ # Run tests
45
+ [group('test')]
46
+ test:
47
+ uv run pytest tests/ -q
48
+
49
+ # Run tests with coverage report
50
+ [group('test')]
51
+ test-cov:
52
+ uv run pytest tests/ --cov=stackformers --cov-report=term-missing -q
53
+
54
+ # Run a single test file or pattern, e.g.: just test-only tests/v1/test_encoder.py
55
+ [group('test')]
56
+ test-only target:
57
+ uv run pytest {{ target }} -v
58
+
59
+ # Run fmt-check + lint + types + test (CI gate)
60
+ [group('quality')]
61
+ check: fmt-check lint types test
62
+
63
+ # Format, lint-fix, then run all checks
64
+ [group('quality')]
65
+ fix: fmt lint-fix
66
+ just check
67
+
68
+ # Remove build artifacts and caches
69
+ [group('maintenance')]
70
+ clean:
71
+ rm -rf dist/ .venv/ .ruff_cache/ .pytest_cache/ __pycache__
72
+ find . -type d -name "__pycache__" -exec rm -rf {} +
73
+ find . -type d -name "*.egg-info" -exec rm -rf {} +
@@ -1,6 +1,6 @@
1
- Metadata-Version: 2.4
1
+ Metadata-Version: 2.5
2
2
  Name: stackformers
3
- Version: 4.3.0
3
+ Version: 5.0.0rc3
4
4
  Summary: Typed, composable, SOLID transformer library for PyTorch
5
5
  Project-URL: Homepage, https://github.com/Red-Eyed/stackformers
6
6
  Project-URL: Repository, https://github.com/Red-Eyed/stackformers
@@ -43,7 +43,9 @@ Requires-Dist: einops>=0.7
43
43
  Requires-Dist: jaxtyping>=0.2
44
44
  Requires-Dist: numpy>=2.4.4
45
45
  Requires-Dist: pydantic>=2.0
46
+ Requires-Dist: returns<0.29,>=0.28.0
46
47
  Requires-Dist: torch>=2.2
48
+ Requires-Dist: typing-extensions>=4.12
47
49
  Description-Content-Type: text/markdown
48
50
 
49
51
  # stackformers
@@ -55,6 +57,10 @@ Every architectural choice — positional encoding, normalization, feedforward v
55
57
  uv add stackformers
56
58
  ```
57
59
 
60
+ Runnable examples cover variable-length layouts, causal language modeling, image patches,
61
+ continuous point coordinates, user subclassing, and cached ONNX Runtime deployment. See the
62
+ [`examples` guide](examples/README.md).
63
+
58
64
  ---
59
65
 
60
66
  ## Why
@@ -108,6 +114,33 @@ from stackformers import windowed_encoder_config
108
114
  windowed_encoder_config(dim=512, heads=8, num_layers=6, window_size=128)
109
115
  ```
110
116
 
117
+ Variable-width encoder — one model and head dimension per Transformer block:
118
+
119
+ ```python
120
+ from stackformers import VariableWidthTransformerEncoder, variable_width_encoder_config
121
+
122
+ config = variable_width_encoder_config(
123
+ d_models=[512, 512, 768, 768, 1024],
124
+ dim_heads=[64, 64, 64, 64, 128],
125
+ )
126
+ model = VariableWidthTransformerEncoder(config)
127
+ ```
128
+
129
+ Omitting `heads` derives each block's head count as `d_model // dim_head`. Set `heads=12` or
130
+ `heads=[12, 12, 16, 16, 16]` to choose the internal attention width independently of `d_models`.
131
+ `dim_heads`, `heads`, `causal`, `ff_mult`, `dropout`, and `norm_placement` accept either a shared
132
+ value or a list with one entry per block. The preset inserts a bias-free
133
+ learned projection before a block when its residual width differs from the preceding block;
134
+ equal-width neighbors use an identity. The convenience factory expands each width pair into a
135
+ `VariableWidthEncoderLayerConfig` whose attention, feed-forward, norm, positional encoding, and
136
+ attention bias are all explicit. Construct these layer configs directly to replace any component;
137
+ the model does not rebuild or override them. The input width must match the first layer's
138
+ `attn.dim`, and the output uses the last layer's width.
139
+
140
+ Reference: [Wu et al., “Variable-Width Transformers” (2026)](https://arxiv.org/abs/2606.18246).
141
+ The paper uses parameter-free residual resizing; this preset instead makes each width transition
142
+ a learned linear projection, so it is a related experimental variant rather than an exact replica.
143
+
111
144
  Encoder–decoder:
112
145
 
113
146
  ```python
@@ -117,6 +150,43 @@ model = TransformerDecoder(plain_decoder_config(dim=512, heads=8, num_layers=6))
117
150
  out = model(make_padded_input(x, mask), make_padded_input(context, ctx_mask))
118
151
  ```
119
152
 
153
+ For autoregressive export, build cross K/V once and pass both required cache tensors to every
154
+ one-token decoder invocation:
155
+
156
+ ```python
157
+ import torch
158
+
159
+ from stackformers import (
160
+ CachedDecoderWrapper,
161
+ DecoderCrossAttentionCacheBuilder,
162
+ )
163
+
164
+ model.eval()
165
+ cache_builder = DecoderCrossAttentionCacheBuilder(model).eval()
166
+ cached_decoder = CachedDecoderWrapper(model).eval()
167
+
168
+ cross_cache = cache_builder(context_input)
169
+ self_kv_cache = torch.empty(num_layers, 2, batch, kv_heads, 0, dim_head)
170
+ step_i = torch.tensor([0], dtype=torch.int64)
171
+ cache_onnx = torch.onnx.export(cache_builder, (context_input,), dynamo=True)
172
+ decoder_onnx = torch.onnx.export(
173
+ cached_decoder,
174
+ (target_token, cross_cache, self_kv_cache, step_i),
175
+ dynamo=True,
176
+ )
177
+ ```
178
+
179
+ The cache builder runs once per encoder context. Each decoder call consumes exactly one target
180
+ token, the immutable cross cache, the growing self cache, and `step_i`; it returns the output token
181
+ and a self cache extended by one position. K/V tensors retain `kv_heads` rather than expanded query
182
+ heads. Cache construction positions cross K once, while cached decoding positions each new self K
183
+ and both self/cross Q, so all built-in positional encodings remain available. Batch, source, and
184
+ past-target axes are dynamic during export; model width, layer count, head geometry, and the
185
+ one-token axis remain static because weights constrain them.
186
+
187
+ See the complete eager, ONNX export, and raw ONNX Runtime demonstration in
188
+ [`examples/encoder_decoder_onnxruntime.py`](examples/encoder_decoder_onnxruntime.py).
189
+
120
190
  ### Explicit config
121
191
 
122
192
  Full control with JSON round-trip via `kind` discriminators:
@@ -141,6 +211,24 @@ model = TransformerEncoder(cfg)
141
211
  cfg2 = TransformerEncoderConfig.model_validate(cfg.model_dump())
142
212
  ```
143
213
 
214
+ `norm_placement` is available on `TransformerEncoderConfig`, `TransformerDecoderConfig`, and
215
+ `CrossAttenderConfig`, as well as their plain-config helpers. It accepts four layouts and defaults
216
+ to `"pre"`, so existing constructors, serialized configs, checkpoints, outputs, and gradients retain
217
+ the previous behavior when the field is omitted. For a residual branch `F` with norm `N`:
218
+
219
+ | Value | Branch equation | Reference |
220
+ | --- | --- | --- |
221
+ | `"pre"` | `x + F(N(x))` | [Xiong et al., 2020](https://proceedings.mlr.press/v119/xiong20b.html) |
222
+ | `"post"` | `N(x + F(x))` | [Vaswani et al., 2017](https://arxiv.org/abs/1706.03762) |
223
+ | `"sandwich"` | `x + N_post(F(N_pre(x)))` | [Ding et al., 2021](https://arxiv.org/abs/2105.13290) |
224
+ | `"reordered"` | `x + N(F(x))` | [Liu et al., 2022](https://arxiv.org/abs/2111.09883); [OLMo Team et al., 2025](https://arxiv.org/abs/2501.00656) |
225
+
226
+ Sandwich placement creates independent pre- and post-branch norms. Reordered placement follows
227
+ the OLMo 2 residual layout; enable QK-Norm separately in the attention config when reproducing the
228
+ broader OLMo 2 stabilization recipe. Preset construction maps each value to a focused encoder,
229
+ decoder, or cross-attender layer class. Decoder and cross-attender placement applies to the
230
+ target/query residual stream; the context sequence is not normalized or mutated by these layers.
231
+
144
232
  ### Custom wiring
145
233
 
146
234
  Wire layers yourself when presets aren't enough:
@@ -176,7 +264,7 @@ encoder = Encoder(layers=layers, final_norm=RMSNorm(RMSNormConfig(dim=512)))
176
264
  | Self-attention | Global, sliding-window (local); padded and packed backends; GQA / MQA |
177
265
  | Cross-attention | Global; padded and packed backends |
178
266
  | Positional encoding | RoPE-1D, RoPE-2D, none (null object) |
179
- | Feedforward | SwiGLU, GEGLU |
267
+ | Feedforward | SwiGLU, HardSwishGLU, GEGLU, GELU, ReLU² |
180
268
  | Normalization | RMSNorm, LayerNorm |
181
269
  | Presets | Encoder, Decoder, CrossAttender |
182
270
 
@@ -197,6 +285,29 @@ just test # test
197
285
  just check # full CI gate
198
286
  ```
199
287
 
288
+ The quality gate checks the library, tests, and examples against Python 3.11. Ruff enforces
289
+ annotations, common bug patterns, typing imports, and qualified, current suppressions.
290
+ Pyrefly uses the strict preset with explicit/implicit `Any`, untyped returns, missing
291
+ annotations, and stale suppressions treated as errors. Tool minimums are recorded in both
292
+ the development dependencies and checker configs.
293
+
294
+ Module classes expose `__call__ = forward` only under `TYPE_CHECKING`. This gives callers the
295
+ declared input/output contract while leaving PyTorch's hooks and runtime dispatch intact.
296
+ Local `Tensor` annotations at built-in PyTorch projection boundaries rely on those modules'
297
+ tensor-return contracts; they do not validate tensor shapes. Experimental kernel results remain
298
+ `object` until runtime validation. Export shape trees use opaque `object` leaves because
299
+ PyTorch validates their module-dependent structure.
300
+
301
+ Core admission checks return the shared `returns.result.Result[T, E]` type using `Success`
302
+ and `Failure`, so other libraries can inspect the same outcomes without converting custom
303
+ records. Public adapters preserve tensor returns and raise the stored exception with its
304
+ original type, message, and cause. Pydantic still presents configuration failures as
305
+ `ValidationError`. The experimental backend returns a typed fallback reason internally;
306
+ its existing adapter retains tensor-or-`None` returns and warning behavior. Expected failures
307
+ are explicit in these core signatures; unexpected Python or PyTorch exceptions can still
308
+ propagate. This path uses ordinary constructors, matching, and typed access supported by
309
+ Pyrefly; it requires no mypy plugin.
310
+
200
311
  ---
201
312
 
202
313
  ## License