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.
- {stackformers-4.3.0 → stackformers-5.0.0rc3}/.gitignore +2 -1
- stackformers-5.0.0rc3/AGENTS.md +19 -0
- {stackformers-4.3.0 → stackformers-5.0.0rc3}/CHANGELOG.md +246 -0
- stackformers-5.0.0rc3/Justfile +73 -0
- {stackformers-4.3.0 → stackformers-5.0.0rc3}/PKG-INFO +114 -3
- stackformers-5.0.0rc3/README.md +265 -0
- stackformers-5.0.0rc3/examples/README.md +58 -0
- stackformers-5.0.0rc3/examples/causal_language_model.py +99 -0
- stackformers-5.0.0rc3/examples/encoder_decoder_onnxruntime.py +357 -0
- stackformers-5.0.0rc3/examples/image_encoder.py +90 -0
- stackformers-5.0.0rc3/examples/padded_and_packed.py +77 -0
- stackformers-5.0.0rc3/examples/point_cloud_encoder.py +87 -0
- stackformers-5.0.0rc3/examples/subclassed_encoder.py +153 -0
- stackformers-5.0.0rc3/pyproject.toml +52 -0
- stackformers-5.0.0rc3/pyrefly.toml +31 -0
- stackformers-5.0.0rc3/pytest.ini +4 -0
- stackformers-5.0.0rc3/ruff.toml +21 -0
- stackformers-5.0.0rc3/stackformers/DEVLOG.md +396 -0
- stackformers-5.0.0rc3/stackformers/__init__.py +258 -0
- stackformers-5.0.0rc3/stackformers/_result.py +16 -0
- {stackformers-4.3.0 → stackformers-5.0.0rc3}/stackformers/attention/README.md +10 -0
- {stackformers-4.3.0 → stackformers-5.0.0rc3}/stackformers/attention/__init__.py +16 -1
- {stackformers-4.3.0 → stackformers-5.0.0rc3}/stackformers/attention/bias.py +4 -1
- stackformers-5.0.0rc3/stackformers/attention/cache.py +40 -0
- stackformers-5.0.0rc3/stackformers/attention/cache_validation.py +53 -0
- stackformers-5.0.0rc3/stackformers/attention/cached.py +181 -0
- {stackformers-4.3.0 → stackformers-5.0.0rc3}/stackformers/attention/config.py +27 -10
- {stackformers-4.3.0 → stackformers-5.0.0rc3}/stackformers/attention/cross_attn.py +35 -10
- {stackformers-4.3.0 → stackformers-5.0.0rc3}/stackformers/attention/distance_bias.py +17 -5
- {stackformers-4.3.0 → stackformers-5.0.0rc3}/stackformers/attention/factory.py +6 -2
- stackformers-5.0.0rc3/stackformers/attention/layout.py +32 -0
- {stackformers-4.3.0 → stackformers-5.0.0rc3}/stackformers/attention/ops.py +25 -11
- stackformers-5.0.0rc3/stackformers/attention/protocols.py +46 -0
- {stackformers-4.3.0 → stackformers-5.0.0rc3}/stackformers/attention/self_attn.py +23 -8
- stackformers-5.0.0rc3/stackformers/attention/varlen_backend.py +167 -0
- stackformers-5.0.0rc3/stackformers/cross_attender.py +183 -0
- stackformers-5.0.0rc3/stackformers/decoder.py +211 -0
- stackformers-5.0.0rc3/stackformers/decoder_cache.py +349 -0
- {stackformers-4.3.0 → stackformers-5.0.0rc3}/stackformers/encoder.py +16 -4
- stackformers-5.0.0rc3/stackformers/feedforward/DEVLOG.md +27 -0
- {stackformers-4.3.0 → stackformers-5.0.0rc3}/stackformers/feedforward/README.md +3 -1
- stackformers-5.0.0rc3/stackformers/feedforward/__init__.py +33 -0
- {stackformers-4.3.0 → stackformers-5.0.0rc3}/stackformers/feedforward/config.py +28 -4
- {stackformers-4.3.0 → stackformers-5.0.0rc3}/stackformers/feedforward/factory.py +17 -2
- {stackformers-4.3.0 → stackformers-5.0.0rc3}/stackformers/feedforward/geglu.py +13 -3
- stackformers-5.0.0rc3/stackformers/feedforward/gelu.py +36 -0
- stackformers-5.0.0rc3/stackformers/feedforward/hardswish_glu.py +47 -0
- {stackformers-4.3.0 → stackformers-5.0.0rc3}/stackformers/feedforward/protocols.py +5 -7
- {stackformers-4.3.0 → stackformers-5.0.0rc3}/stackformers/feedforward/relu_squared.py +13 -3
- {stackformers-4.3.0 → stackformers-5.0.0rc3}/stackformers/feedforward/swiglu.py +13 -3
- stackformers-5.0.0rc3/stackformers/layers.py +158 -0
- {stackformers-4.3.0 → stackformers-5.0.0rc3}/stackformers/mlm/README.md +4 -2
- {stackformers-4.3.0 → stackformers-5.0.0rc3}/stackformers/mlm/head.py +14 -2
- stackformers-5.0.0rc3/stackformers/mlm/head_cosine.py +39 -0
- {stackformers-4.3.0 → stackformers-5.0.0rc3}/stackformers/mlm/masking.py +10 -1
- {stackformers-4.3.0 → stackformers-5.0.0rc3}/stackformers/mlm/protocols.py +8 -11
- {stackformers-4.3.0 → stackformers-5.0.0rc3}/stackformers/mlm/wrapper.py +30 -11
- stackformers-5.0.0rc3/stackformers/norm/__init__.py +5 -0
- {stackformers-4.3.0 → stackformers-5.0.0rc3}/stackformers/norm/config.py +4 -0
- {stackformers-4.3.0 → stackformers-5.0.0rc3}/stackformers/norm/factory.py +6 -2
- stackformers-5.0.0rc3/stackformers/norm/protocols.py +14 -0
- {stackformers-4.3.0 → stackformers-5.0.0rc3}/stackformers/positional/__init__.py +12 -0
- {stackformers-4.3.0 → stackformers-5.0.0rc3}/stackformers/positional/config.py +38 -16
- {stackformers-4.3.0 → stackformers-5.0.0rc3}/stackformers/positional/factory.py +6 -2
- {stackformers-4.3.0 → stackformers-5.0.0rc3}/stackformers/positional/learned.py +6 -2
- {stackformers-4.3.0 → stackformers-5.0.0rc3}/stackformers/positional/none.py +8 -3
- {stackformers-4.3.0 → stackformers-5.0.0rc3}/stackformers/positional/protocols.py +4 -4
- {stackformers-4.3.0 → stackformers-5.0.0rc3}/stackformers/positional/rope1d.py +13 -4
- {stackformers-4.3.0 → stackformers-5.0.0rc3}/stackformers/positional/rope2d.py +16 -4
- {stackformers-4.3.0 → stackformers-5.0.0rc3}/stackformers/positional/rope_nd.py +9 -3
- stackformers-5.0.0rc3/stackformers/positional/validation.py +12 -0
- {stackformers-4.3.0 → stackformers-5.0.0rc3}/stackformers/presets/README.md +4 -2
- {stackformers-4.3.0 → stackformers-5.0.0rc3}/stackformers/presets/__init__.py +14 -0
- {stackformers-4.3.0 → stackformers-5.0.0rc3}/stackformers/presets/cross_attender.py +71 -18
- {stackformers-4.3.0 → stackformers-5.0.0rc3}/stackformers/presets/decoder.py +90 -28
- {stackformers-4.3.0 → stackformers-5.0.0rc3}/stackformers/presets/encoder.py +89 -23
- stackformers-5.0.0rc3/stackformers/presets/variable_width_encoder.py +363 -0
- stackformers-5.0.0rc3/stackformers/py.typed +1 -0
- {stackformers-4.3.0 → stackformers-5.0.0rc3}/stackformers/sequence.py +6 -3
- stackformers-5.0.0rc3/tests/attention/legacy_api_rc1.json +1404 -0
- stackformers-5.0.0rc3/tests/attention/test_contracts.py +70 -0
- stackformers-5.0.0rc3/tests/attention/test_cross_attn_cache.py +135 -0
- stackformers-5.0.0rc3/tests/attention/test_empty_rows.py +72 -0
- stackformers-5.0.0rc3/tests/attention/test_geometry.py +72 -0
- stackformers-5.0.0rc3/tests/attention/test_legacy_api.py +202 -0
- {stackformers-4.3.0 → stackformers-5.0.0rc3}/tests/attention/test_ops.py +42 -18
- stackformers-5.0.0rc3/tests/attention/test_sdpa_export.py +104 -0
- {stackformers-4.3.0 → stackformers-5.0.0rc3}/tests/attention/test_self_attn.py +49 -0
- {stackformers-4.3.0 → stackformers-5.0.0rc3}/tests/attention/test_varlen_backend.py +82 -8
- {stackformers-4.3.0 → stackformers-5.0.0rc3}/tests/conftest.py +41 -0
- stackformers-5.0.0rc3/tests/export_utils.py +145 -0
- stackformers-5.0.0rc3/tests/feedforward/test_gelu.py +72 -0
- stackformers-5.0.0rc3/tests/feedforward/test_hardswish_glu.py +75 -0
- stackformers-5.0.0rc3/tests/mlm/test_empty_selection.py +93 -0
- stackformers-5.0.0rc3/tests/mlm/test_head_cosine.py +48 -0
- {stackformers-4.3.0 → stackformers-5.0.0rc3}/tests/mlm/test_wrapper.py +50 -8
- stackformers-5.0.0rc3/tests/norm_topology_helpers.py +87 -0
- stackformers-5.0.0rc3/tests/positional/test_cacheable.py +69 -0
- {stackformers-4.3.0 → stackformers-5.0.0rc3}/tests/positional/test_rope.py +148 -44
- {stackformers-4.3.0 → stackformers-5.0.0rc3}/tests/positional/test_rope_nd.py +6 -3
- stackformers-5.0.0rc3/tests/presets/test_cross_attender.py +144 -0
- stackformers-5.0.0rc3/tests/presets/test_decoder.py +139 -0
- stackformers-5.0.0rc3/tests/presets/test_encoder.py +275 -0
- stackformers-5.0.0rc3/tests/presets/test_variable_width_encoder.py +566 -0
- stackformers-5.0.0rc3/tests/test_core_results.py +221 -0
- stackformers-5.0.0rc3/tests/test_cross_attender.py +287 -0
- stackformers-5.0.0rc3/tests/test_decoder.py +332 -0
- stackformers-5.0.0rc3/tests/test_decoder_cache.py +281 -0
- {stackformers-4.3.0 → stackformers-5.0.0rc3}/tests/test_encoder.py +5 -1
- stackformers-5.0.0rc3/tests/test_encoder_decoder_example.py +79 -0
- stackformers-5.0.0rc3/tests/test_examples.py +57 -0
- stackformers-5.0.0rc3/tests/test_layers.py +289 -0
- {stackformers-4.3.0 → stackformers-5.0.0rc3}/tests/test_norm.py +6 -2
- stackformers-5.0.0rc3/tests/test_public_api.py +143 -0
- stackformers-5.0.0rc3/tests/test_result.py +84 -0
- {stackformers-4.3.0 → stackformers-5.0.0rc3}/tests/test_sequence.py +67 -22
- {stackformers-4.3.0 → stackformers-5.0.0rc3}/uv.lock +303 -46
- stackformers-4.3.0/Justfile +0 -68
- stackformers-4.3.0/README.md +0 -156
- stackformers-4.3.0/pyproject.toml +0 -72
- stackformers-4.3.0/stackformers/__init__.py +0 -150
- stackformers-4.3.0/stackformers/attention/protocols.py +0 -44
- stackformers-4.3.0/stackformers/attention/varlen_backend.py +0 -88
- stackformers-4.3.0/stackformers/cross_attender.py +0 -46
- stackformers-4.3.0/stackformers/decoder.py +0 -57
- stackformers-4.3.0/stackformers/feedforward/__init__.py +0 -5
- stackformers-4.3.0/stackformers/layers.py +0 -31
- stackformers-4.3.0/stackformers/norm/__init__.py +0 -3
- stackformers-4.3.0/stackformers/norm/protocols.py +0 -16
- stackformers-4.3.0/tests/presets/test_cross_attender.py +0 -36
- stackformers-4.3.0/tests/presets/test_decoder.py +0 -36
- stackformers-4.3.0/tests/presets/test_encoder.py +0 -102
- stackformers-4.3.0/tests/test_cross_attender.py +0 -118
- stackformers-4.3.0/tests/test_decoder.py +0 -141
- stackformers-4.3.0/tests/test_layers.py +0 -71
- {stackformers-4.3.0 → stackformers-5.0.0rc3}/.claudeignore +0 -0
- {stackformers-4.3.0 → stackformers-5.0.0rc3}/.python-version +0 -0
- {stackformers-4.3.0 → stackformers-5.0.0rc3}/.vscode/settings.json +0 -0
- {stackformers-4.3.0 → stackformers-5.0.0rc3}/CLAUDE.md +0 -0
- {stackformers-4.3.0 → stackformers-5.0.0rc3}/LICENSE +0 -0
- {stackformers-4.3.0 → stackformers-5.0.0rc3}/PLAN.md +0 -0
- {stackformers-4.3.0 → stackformers-5.0.0rc3}/run-remote-tests.sh +0 -0
- {stackformers-4.3.0 → stackformers-5.0.0rc3}/stackformers/config.py +0 -0
- {stackformers-4.3.0 → stackformers-5.0.0rc3}/stackformers/mlm/config.py +0 -0
- {stackformers-4.3.0 → stackformers-5.0.0rc3}/stackformers/norm/README.md +0 -0
- {stackformers-4.3.0 → stackformers-5.0.0rc3}/stackformers/positional/README.md +0 -0
- {stackformers-4.3.0 → stackformers-5.0.0rc3}/tests/__init__.py +0 -0
- {stackformers-4.3.0 → stackformers-5.0.0rc3}/tests/attention/__init__.py +0 -0
- {stackformers-4.3.0 → stackformers-5.0.0rc3}/tests/attention/test_cross_attn.py +0 -0
- {stackformers-4.3.0 → stackformers-5.0.0rc3}/tests/attention/test_distance_bias.py +0 -0
- {stackformers-4.3.0 → stackformers-5.0.0rc3}/tests/attention/test_kernels.py +0 -0
- {stackformers-4.3.0 → stackformers-5.0.0rc3}/tests/feedforward/__init__.py +0 -0
- {stackformers-4.3.0 → stackformers-5.0.0rc3}/tests/feedforward/test_geglu.py +0 -0
- {stackformers-4.3.0 → stackformers-5.0.0rc3}/tests/feedforward/test_relu_squared.py +0 -0
- {stackformers-4.3.0 → stackformers-5.0.0rc3}/tests/feedforward/test_swiglu.py +0 -0
- {stackformers-4.3.0 → stackformers-5.0.0rc3}/tests/mlm/__init__.py +0 -0
- {stackformers-4.3.0 → stackformers-5.0.0rc3}/tests/mlm/test_head.py +0 -0
- {stackformers-4.3.0 → stackformers-5.0.0rc3}/tests/mlm/test_masking.py +0 -0
- {stackformers-4.3.0 → stackformers-5.0.0rc3}/tests/positional/__init__.py +0 -0
- {stackformers-4.3.0 → stackformers-5.0.0rc3}/tests/positional/test_learned_pos.py +0 -0
- {stackformers-4.3.0 → stackformers-5.0.0rc3}/tests/presets/__init__.py +0 -0
|
@@ -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.
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
2
|
Name: stackformers
|
|
3
|
-
Version:
|
|
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
|