flashspec 0.1.2__tar.gz → 0.1.3__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 (100) hide show
  1. {flashspec-0.1.2 → flashspec-0.1.3}/.github/workflows/benchmark.yml +1 -1
  2. {flashspec-0.1.2 → flashspec-0.1.3}/.github/workflows/ci.yml +5 -5
  3. {flashspec-0.1.2 → flashspec-0.1.3}/.github/workflows/gpu_tests.yml +2 -2
  4. {flashspec-0.1.2 → flashspec-0.1.3}/CHANGELOG.md +41 -1
  5. {flashspec-0.1.2 → flashspec-0.1.3}/CITATION.cff +1 -1
  6. {flashspec-0.1.2 → flashspec-0.1.3}/PKG-INFO +3 -2
  7. {flashspec-0.1.2 → flashspec-0.1.3}/flashspec/__init__.py +4 -1
  8. flashspec-0.1.3/flashspec/kernels/__init__.py +72 -0
  9. {flashspec-0.1.2 → flashspec-0.1.3}/pyproject.toml +7 -2
  10. {flashspec-0.1.2 → flashspec-0.1.3}/tests/unit/test_verify_kernel.py +100 -0
  11. flashspec-0.1.2/flashspec/kernels/__init__.py +0 -18
  12. {flashspec-0.1.2 → flashspec-0.1.3}/.gitignore +0 -0
  13. {flashspec-0.1.2 → flashspec-0.1.3}/.readthedocs.yaml +0 -0
  14. {flashspec-0.1.2 → flashspec-0.1.3}/.zenodo.json +0 -0
  15. {flashspec-0.1.2 → flashspec-0.1.3}/AGENTS.md +0 -0
  16. {flashspec-0.1.2 → flashspec-0.1.3}/CONTRIBUTING.md +0 -0
  17. {flashspec-0.1.2 → flashspec-0.1.3}/LICENSE +0 -0
  18. {flashspec-0.1.2 → flashspec-0.1.3}/Makefile +0 -0
  19. {flashspec-0.1.2 → flashspec-0.1.3}/PUBLISHING.md +0 -0
  20. {flashspec-0.1.2 → flashspec-0.1.3}/README.md +0 -0
  21. {flashspec-0.1.2 → flashspec-0.1.3}/benchmarks/README.md +0 -0
  22. {flashspec-0.1.2 → flashspec-0.1.3}/benchmarks/__init__.py +0 -0
  23. {flashspec-0.1.2 → flashspec-0.1.3}/benchmarks/baselines.py +0 -0
  24. {flashspec-0.1.2 → flashspec-0.1.3}/benchmarks/benchmark_kernels.py +0 -0
  25. {flashspec-0.1.2 → flashspec-0.1.3}/benchmarks/compare_baselines.py +0 -0
  26. {flashspec-0.1.2 → flashspec-0.1.3}/benchmarks/configs/llama3_70b.yaml +0 -0
  27. {flashspec-0.1.2 → flashspec-0.1.3}/benchmarks/configs/llama3_8b.yaml +0 -0
  28. {flashspec-0.1.2 → flashspec-0.1.3}/benchmarks/configs/mistral_7b.yaml +0 -0
  29. {flashspec-0.1.2 → flashspec-0.1.3}/benchmarks/results/.gitkeep +0 -0
  30. {flashspec-0.1.2 → flashspec-0.1.3}/benchmarks/results/baseline.json +0 -0
  31. {flashspec-0.1.2 → flashspec-0.1.3}/benchmarks/run_all.py +0 -0
  32. {flashspec-0.1.2 → flashspec-0.1.3}/benchmarks/sweep_draft_sizes.py +0 -0
  33. {flashspec-0.1.2 → flashspec-0.1.3}/benchmarks/sweep_gamma.py +0 -0
  34. {flashspec-0.1.2 → flashspec-0.1.3}/deploy/Dockerfile +0 -0
  35. {flashspec-0.1.2 → flashspec-0.1.3}/deploy/docker-compose.yml +0 -0
  36. {flashspec-0.1.2 → flashspec-0.1.3}/deploy/k8s/flashspec-deployment.yaml +0 -0
  37. {flashspec-0.1.2 → flashspec-0.1.3}/docs/api/index.md +0 -0
  38. {flashspec-0.1.2 → flashspec-0.1.3}/docs/architecture.md +0 -0
  39. {flashspec-0.1.2 → flashspec-0.1.3}/docs/bandit.md +0 -0
  40. {flashspec-0.1.2 → flashspec-0.1.3}/docs/benchmarks.md +0 -0
  41. {flashspec-0.1.2 → flashspec-0.1.3}/docs/index.md +0 -0
  42. {flashspec-0.1.2 → flashspec-0.1.3}/docs/kernels.md +0 -0
  43. {flashspec-0.1.2 → flashspec-0.1.3}/docs/mkdocs.yml +0 -0
  44. {flashspec-0.1.2 → flashspec-0.1.3}/docs/requirements.txt +0 -0
  45. {flashspec-0.1.2 → flashspec-0.1.3}/flashspec/bandit/__init__.py +0 -0
  46. {flashspec-0.1.2 → flashspec-0.1.3}/flashspec/bandit/base.py +0 -0
  47. {flashspec-0.1.2 → flashspec-0.1.3}/flashspec/bandit/oracle.py +0 -0
  48. {flashspec-0.1.2 → flashspec-0.1.3}/flashspec/bandit/thompson.py +0 -0
  49. {flashspec-0.1.2 → flashspec-0.1.3}/flashspec/bandit/ucb.py +0 -0
  50. {flashspec-0.1.2 → flashspec-0.1.3}/flashspec/engine/__init__.py +0 -0
  51. {flashspec-0.1.2 → flashspec-0.1.3}/flashspec/engine/drafter.py +0 -0
  52. {flashspec-0.1.2 → flashspec-0.1.3}/flashspec/engine/speculative.py +0 -0
  53. {flashspec-0.1.2 → flashspec-0.1.3}/flashspec/engine/verifier.py +0 -0
  54. {flashspec-0.1.2 → flashspec-0.1.3}/flashspec/export/__init__.py +0 -0
  55. {flashspec-0.1.2 → flashspec-0.1.3}/flashspec/export/onnx.py +0 -0
  56. {flashspec-0.1.2 → flashspec-0.1.3}/flashspec/kernels/_reference.py +0 -0
  57. {flashspec-0.1.2 → flashspec-0.1.3}/flashspec/kernels/gather_kernel.py +0 -0
  58. {flashspec-0.1.2 → flashspec-0.1.3}/flashspec/kernels/verify_kernel.py +0 -0
  59. {flashspec-0.1.2 → flashspec-0.1.3}/flashspec/metrics/__init__.py +0 -0
  60. {flashspec-0.1.2 → flashspec-0.1.3}/flashspec/metrics/acceptance.py +0 -0
  61. {flashspec-0.1.2 → flashspec-0.1.3}/flashspec/metrics/latency.py +0 -0
  62. {flashspec-0.1.2 → flashspec-0.1.3}/flashspec/metrics/throughput.py +0 -0
  63. {flashspec-0.1.2 → flashspec-0.1.3}/flashspec/py.typed +0 -0
  64. {flashspec-0.1.2 → flashspec-0.1.3}/flashspec/sampling/__init__.py +0 -0
  65. {flashspec-0.1.2 → flashspec-0.1.3}/flashspec/sampling/rejection.py +0 -0
  66. {flashspec-0.1.2 → flashspec-0.1.3}/flashspec/sampling/typical.py +0 -0
  67. {flashspec-0.1.2 → flashspec-0.1.3}/flashspec/utils/__init__.py +0 -0
  68. {flashspec-0.1.2 → flashspec-0.1.3}/flashspec/utils/config.py +0 -0
  69. {flashspec-0.1.2 → flashspec-0.1.3}/flashspec/utils/device.py +0 -0
  70. {flashspec-0.1.2 → flashspec-0.1.3}/flashspec/utils/logging.py +0 -0
  71. {flashspec-0.1.2 → flashspec-0.1.3}/mkdocs.yml +0 -0
  72. {flashspec-0.1.2 → flashspec-0.1.3}/notebooks/01_quickstart.py +0 -0
  73. {flashspec-0.1.2 → flashspec-0.1.3}/paper/Makefile +0 -0
  74. {flashspec-0.1.2 → flashspec-0.1.3}/paper/figures/.gitkeep +0 -0
  75. {flashspec-0.1.2 → flashspec-0.1.3}/paper/flashspec.bib +0 -0
  76. {flashspec-0.1.2 → flashspec-0.1.3}/paper/flashspec.tex +0 -0
  77. {flashspec-0.1.2 → flashspec-0.1.3}/paper/joss/README.md +0 -0
  78. {flashspec-0.1.2 → flashspec-0.1.3}/paper/joss/paper.bib +0 -0
  79. {flashspec-0.1.2 → flashspec-0.1.3}/paper/joss/paper.md +0 -0
  80. {flashspec-0.1.2 → flashspec-0.1.3}/scripts/__init__.py +0 -0
  81. {flashspec-0.1.2 → flashspec-0.1.3}/scripts/check_regression.py +0 -0
  82. {flashspec-0.1.2 → flashspec-0.1.3}/scripts/download_models.py +0 -0
  83. {flashspec-0.1.2 → flashspec-0.1.3}/scripts/export_draft.py +0 -0
  84. {flashspec-0.1.2 → flashspec-0.1.3}/scripts/profile_kernel.py +0 -0
  85. {flashspec-0.1.2 → flashspec-0.1.3}/scripts/update_benchmark_results.py +0 -0
  86. {flashspec-0.1.2 → flashspec-0.1.3}/social/linkedin_post.md +0 -0
  87. {flashspec-0.1.2 → flashspec-0.1.3}/social/x_thread.md +0 -0
  88. {flashspec-0.1.2 → flashspec-0.1.3}/tests/__init__.py +0 -0
  89. {flashspec-0.1.2 → flashspec-0.1.3}/tests/chaos/__init__.py +0 -0
  90. {flashspec-0.1.2 → flashspec-0.1.3}/tests/chaos/test_bandit_adversarial.py +0 -0
  91. {flashspec-0.1.2 → flashspec-0.1.3}/tests/conftest.py +0 -0
  92. {flashspec-0.1.2 → flashspec-0.1.3}/tests/integration/__init__.py +0 -0
  93. {flashspec-0.1.2 → flashspec-0.1.3}/tests/integration/test_e2e_greedy.py +0 -0
  94. {flashspec-0.1.2 → flashspec-0.1.3}/tests/integration/test_e2e_sampling.py +0 -0
  95. {flashspec-0.1.2 → flashspec-0.1.3}/tests/integration/test_onnx_parity.py +0 -0
  96. {flashspec-0.1.2 → flashspec-0.1.3}/tests/unit/__init__.py +0 -0
  97. {flashspec-0.1.2 → flashspec-0.1.3}/tests/unit/test_acceptance.py +0 -0
  98. {flashspec-0.1.2 → flashspec-0.1.3}/tests/unit/test_bandit.py +0 -0
  99. {flashspec-0.1.2 → flashspec-0.1.3}/tests/unit/test_config.py +0 -0
  100. {flashspec-0.1.2 → flashspec-0.1.3}/tests/unit/test_sampling.py +0 -0
@@ -25,7 +25,7 @@ jobs:
25
25
  cache: pip
26
26
 
27
27
  - name: Install bench dependencies
28
- run: pip install -e ".[bench]"
28
+ run: pip install -e ".[bench,gpu]"
29
29
 
30
30
  - name: Build Docker image
31
31
  run: docker build -t flashspec:latest -f deploy/Dockerfile .
@@ -22,7 +22,7 @@ jobs:
22
22
  python-version: "3.11"
23
23
  cache: pip
24
24
  - name: Install dev dependencies
25
- run: pip install -e ".[dev]" --extra-index-url https://download.pytorch.org/whl/cpu
25
+ run: pip install -e ".[dev,gpu]" --extra-index-url https://download.pytorch.org/whl/cpu
26
26
  - name: make lint
27
27
  run: make lint
28
28
  - name: pip-audit (fail on CRITICAL CVE — §15)
@@ -46,7 +46,7 @@ jobs:
46
46
  python-version: "3.11"
47
47
  cache: pip
48
48
  - name: Install
49
- run: pip install -e ".[dev]" --extra-index-url https://download.pytorch.org/whl/cpu
49
+ run: pip install -e ".[dev,gpu]" --extra-index-url https://download.pytorch.org/whl/cpu
50
50
  - name: make test
51
51
  run: make test
52
52
  - name: Upload coverage XML
@@ -70,7 +70,7 @@ jobs:
70
70
  python-version: "3.11"
71
71
  cache: pip
72
72
  - name: Install
73
- run: pip install -e ".[dev]" --extra-index-url https://download.pytorch.org/whl/cpu
73
+ run: pip install -e ".[dev,gpu]" --extra-index-url https://download.pytorch.org/whl/cpu
74
74
  - name: make test-chaos
75
75
  run: make test-chaos
76
76
 
@@ -86,7 +86,7 @@ jobs:
86
86
  python-version: "3.11"
87
87
  cache: pip
88
88
  - name: Install
89
- run: pip install -e ".[dev]" --extra-index-url https://download.pytorch.org/whl/cpu
89
+ run: pip install -e ".[dev,gpu]" --extra-index-url https://download.pytorch.org/whl/cpu
90
90
  - name: Download coverage artifact
91
91
  uses: actions/download-artifact@v4
92
92
  with:
@@ -117,7 +117,7 @@ jobs:
117
117
  cache: pip
118
118
  - name: Install with ONNX extras
119
119
  run: |
120
- pip install -e ".[dev,onnx]" \
120
+ pip install -e ".[dev,onnx,gpu]" \
121
121
  --extra-index-url https://download.pytorch.org/whl/cpu
122
122
  - name: Run ONNX parity tests
123
123
  run: pytest tests/integration/test_onnx_parity.py -v
@@ -20,7 +20,7 @@ jobs:
20
20
  python-version: "3.11"
21
21
  cache: pip
22
22
  - name: Install
23
- run: pip install -e ".[dev]"
23
+ run: pip install -e ".[dev,gpu]"
24
24
  - name: make test-gpu
25
25
  run: make test-gpu
26
26
  - name: Run slow distribution tests (N=10,000 KS gate)
@@ -45,7 +45,7 @@ jobs:
45
45
  python-version: "3.11"
46
46
  cache: pip
47
47
  - name: Install bench dependencies
48
- run: pip install -e ".[bench]"
48
+ run: pip install -e ".[bench,gpu]"
49
49
  - name: make bench-quick
50
50
  run: make bench-quick
51
51
  - name: Assert throughput ≥ 1.5× AR
@@ -10,7 +10,47 @@ Versioning follows [Semantic Versioning 2.0.0](https://semver.org/).
10
10
 
11
11
  ## [Unreleased]
12
12
 
13
- ### Added
13
+ No unreleased changes. Next planned milestone: v0.2.0 (real GPU benchmark
14
+ results, completed paper figures, arXiv submission).
15
+
16
+ ---
17
+
18
+ ## [0.1.3] — Critical packaging fix (Windows/macOS install)
19
+
20
+ ### Fixed
21
+ - **Critical**: `pip install flashspec` failed completely on Windows and
22
+ macOS with `ERROR: No matching distribution found for triton>=3.0.0` —
23
+ Triton publishes official PyPI wheels for Linux only, but `triton>=3.0.0`
24
+ was a hard core dependency, making the package uninstallable on those
25
+ platforms. (Reported against v0.1.0–v0.1.2.)
26
+ - `pyproject.toml`: moved `triton` out of `dependencies` into a new
27
+ `gpu` extra with a `platform_system == 'Linux'` environment marker:
28
+ `pip install flashspec[gpu]`.
29
+ - `flashspec/kernels/__init__.py`: now imports `verify_kernel` and
30
+ `gather_kernel` inside a `try/except ImportError`. If Triton is
31
+ unavailable, `verify_tokens` and `gather_accepted` are bound to a
32
+ wrapper that raises an actionable `ImportError` only when *called*
33
+ (not at import time), explaining `pip install flashspec[gpu]`
34
+ (Linux-only) and pointing to the cross-platform
35
+ `flashspec.kernels._reference` module (identical numerics, verified in
36
+ `tests/unit/test_verify_kernel.py`). Adds module-level
37
+ `flashspec.kernels.TRITON_AVAILABLE: bool`.
38
+ - `tests/unit/test_verify_kernel.py`: added
39
+ `TestKernelsPackageWithoutTriton` (3 tests, CPU-only, no GPU/Triton
40
+ required) verifying the package imports cleanly and raises the
41
+ actionable error when Triton is absent.
42
+ - `.github/workflows/{ci,gpu_tests,benchmark}.yml`: updated `pip install`
43
+ steps to request the `gpu` extra wherever Triton-backed kernel tests
44
+ or benchmarks actually run (all on `ubuntu-22.04`/self-hosted Linux
45
+ runners, where the extra resolves normally).
46
+ - `README.md`: rewrote the Installation section — `pip install flashspec`
47
+ is now correctly described as cross-platform (CPU reference kernels),
48
+ with `pip install flashspec[gpu]` for Linux+CUDA accelerated kernels.
49
+ - Bumped version `0.1.0` → `0.1.3` in `pyproject.toml` and
50
+ `flashspec/__init__.py` (0.1.0–0.1.2 are broken on non-Linux and
51
+ should be marked yanked on PyPI after this release).
52
+
53
+ ---
14
54
  - Initial project scaffold following AGENTS.md specification.
15
55
  - `flashspec.utils.config`: Pydantic v2 configuration models (`FlashSpecConfig`,
16
56
  `BanditConfig`, `SamplingConfig`, `MetricsConfig`). All immutable (`frozen=True`).
@@ -27,7 +27,7 @@ abstract: >-
27
27
  O(sqrt(K T log T)) regret bound. The output distribution is
28
28
  provably identical to standard autoregressive sampling, verified
29
29
  continuously via a Kolmogorov-Smirnov test in CI.
30
- version: 0.1.0
30
+ version: 0.1.3
31
31
  date-released: "2026-06-12"
32
32
  license: Apache-2.0
33
33
  repository-code: "https://github.com/Mattral/FlashSpec"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: flashspec
3
- Version: 0.1.2
3
+ Version: 0.1.3
4
4
  Summary: Adaptive speculative-decoding inference engine with Triton-optimised verification and online bandit draft selection
5
5
  Project-URL: Homepage, https://github.com/Mattral/FlashSpec
6
6
  Project-URL: Repository, https://github.com/Mattral/FlashSpec
@@ -136,7 +136,6 @@ Requires-Dist: numpy>=1.26.0
136
136
  Requires-Dist: pydantic>=2.0.0
137
137
  Requires-Dist: torch>=2.2.0
138
138
  Requires-Dist: transformers>=4.40.0
139
- Requires-Dist: triton>=3.0.0
140
139
  Provides-Extra: bench
141
140
  Requires-Dist: lm-eval>=0.4; extra == 'bench'
142
141
  Requires-Dist: matplotlib>=3.8; extra == 'bench'
@@ -152,6 +151,8 @@ Requires-Dist: ruff>=0.4.0; extra == 'dev'
152
151
  Provides-Extra: docs
153
152
  Requires-Dist: mkdocs-material>=9.5; extra == 'docs'
154
153
  Requires-Dist: mkdocstrings[python]>=0.24; extra == 'docs'
154
+ Provides-Extra: gpu
155
+ Requires-Dist: triton>=3.0.0; (platform_system == 'Linux') and extra == 'gpu'
155
156
  Provides-Extra: onnx
156
157
  Requires-Dist: onnx>=1.16; extra == 'onnx'
157
158
  Requires-Dist: onnxruntime-gpu>=1.18; extra == 'onnx'
@@ -14,6 +14,7 @@ Public API surface (AGENTS.md §13.2 — do not modify without explicit approval
14
14
  flashspec.register (draft model decorator)
15
15
  flashspec.get_drafter
16
16
  flashspec.list_drafters
17
+ flashspec.TRITON_AVAILABLE (bool — True only on Linux with triton installed)
17
18
 
18
19
  References
19
20
  ----------
@@ -25,6 +26,7 @@ References
25
26
 
26
27
  from flashspec.engine.drafter import get_drafter, list_drafters, register
27
28
  from flashspec.engine.speculative import GenerationResult, SpeculativeEngine
29
+ from flashspec.kernels import TRITON_AVAILABLE
28
30
  from flashspec.utils.config import BanditConfig, FlashSpecConfig, MetricsConfig, SamplingConfig
29
31
 
30
32
  __all__ = [
@@ -34,10 +36,11 @@ __all__ = [
34
36
  "MetricsConfig",
35
37
  "SamplingConfig",
36
38
  "SpeculativeEngine",
39
+ "TRITON_AVAILABLE",
37
40
  "get_drafter",
38
41
  "list_drafters",
39
42
  "register",
40
43
  ]
41
44
 
42
- __version__ = "0.1.0"
45
+ __version__ = "0.1.3"
43
46
  __author__ = "Min Htet Myet (Mattral)"
@@ -0,0 +1,72 @@
1
+ """Triton-optimised kernels sub-package.
2
+
3
+ All consumers of kernels must import from this module, not from the
4
+ individual kernel files directly. This is enforced by import-linter.
5
+
6
+ Private implementation modules:
7
+ - ``verify_kernel``: token acceptance kernel (requires Triton).
8
+ - ``gather_kernel``: accepted-token gather kernel (requires Triton).
9
+ - ``_reference``: pure-PyTorch reference implementations (cross-platform,
10
+ used by tests and as the runtime fallback when Triton is unavailable).
11
+
12
+ Notes
13
+ -----
14
+ Triton has no official PyPI wheels for Windows or macOS. On those
15
+ platforms (or any platform where ``triton`` is not installed),
16
+ ``verify_tokens`` and ``gather_accepted`` are bound to wrappers that raise
17
+ a clear, actionable ``ImportError`` when called, rather than failing at
18
+ import time. Use ``flashspec.kernels._reference`` directly for a
19
+ cross-platform pure-PyTorch implementation with identical numerics
20
+ (verified in ``tests/unit/test_verify_kernel.py``).
21
+
22
+ To enable the accelerated kernels, install on Linux with a CUDA-capable
23
+ GPU::
24
+
25
+ pip install flashspec[gpu]
26
+ """
27
+
28
+ from __future__ import annotations
29
+
30
+ from typing import TYPE_CHECKING, Any
31
+
32
+ __all__ = [
33
+ "gather_accepted",
34
+ "verify_tokens",
35
+ "TRITON_AVAILABLE",
36
+ ]
37
+
38
+ if TYPE_CHECKING:
39
+ import torch
40
+
41
+ try:
42
+ from flashspec.kernels.gather_kernel import gather_accepted
43
+ from flashspec.kernels.verify_kernel import verify_tokens
44
+
45
+ TRITON_AVAILABLE: bool = True
46
+ except ImportError as _exc: # pragma: no cover — exercised on non-Linux CI
47
+ _IMPORT_ERROR = _exc
48
+ TRITON_AVAILABLE = False
49
+
50
+ def _triton_unavailable(*_args: Any, **_kwargs: Any) -> "torch.Tensor":
51
+ """Raise an actionable error explaining how to get Triton kernels.
52
+
53
+ Raises
54
+ ------
55
+ ImportError
56
+ Always. Explains that Triton is not installed on this platform
57
+ and points to the pure-PyTorch reference implementation.
58
+ """
59
+ raise ImportError(
60
+ "flashspec's accelerated Triton kernels are not available "
61
+ f"(original error: {_IMPORT_ERROR}).\n\n"
62
+ "Triton ships official PyPI wheels for Linux only. To use the "
63
+ "GPU-accelerated kernels, install on Linux with a CUDA-capable "
64
+ "GPU via:\n\n pip install flashspec[gpu]\n\n"
65
+ "For a cross-platform pure-PyTorch implementation with "
66
+ "identical numerics, use "
67
+ "flashspec.kernels._reference.verify_tokens_reference and "
68
+ "flashspec.kernels._reference.gather_accepted_reference instead."
69
+ )
70
+
71
+ verify_tokens = _triton_unavailable
72
+ gather_accepted = _triton_unavailable
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "flashspec"
7
- version = "0.1.2"
7
+ version = "0.1.3"
8
8
  description = "Adaptive speculative-decoding inference engine with Triton-optimised verification and online bandit draft selection"
9
9
  readme = "README.md"
10
10
  license = { file = "LICENSE" }
@@ -20,13 +20,18 @@ classifiers = [
20
20
  ]
21
21
  dependencies = [
22
22
  "torch>=2.2.0",
23
- "triton>=3.0.0",
24
23
  "transformers>=4.40.0",
25
24
  "pydantic>=2.0.0",
26
25
  "numpy>=1.26.0",
27
26
  ]
28
27
 
29
28
  [project.optional-dependencies]
29
+ # Triton has no official PyPI wheels for Windows or macOS — it is Linux-only.
30
+ # Without this extra, flashspec runs on the pure-PyTorch reference kernels
31
+ # (flashspec.kernels._reference), which are numerically identical (see
32
+ # tests/unit/test_verify_kernel.py) but slower. Install this extra on Linux
33
+ # with a CUDA-capable GPU to enable the accelerated Triton kernels.
34
+ gpu = ["triton>=3.0.0; platform_system == 'Linux'"]
30
35
  onnx = ["onnx>=1.16", "onnxruntime-gpu>=1.18"]
31
36
  dev = [
32
37
  "pytest>=8.0",
@@ -368,3 +368,103 @@ class TestVerifyTokensTritonParity:
368
368
  assert (first_rej.cpu() == 0).all(), (
369
369
  f"Expected all first_rej==0, got {first_rej.cpu().tolist()}"
370
370
  )
371
+
372
+
373
+ # ── Triton-unavailable fallback (cross-platform, no GPU required) ───────────
374
+
375
+
376
+ class TestKernelsPackageWithoutTriton:
377
+ """``flashspec.kernels`` must import successfully even if Triton is
378
+ absent (e.g. on Windows or macOS, where Triton ships no PyPI wheels).
379
+
380
+ Simulates the absence of Triton by removing ``triton`` and the kernel
381
+ submodules from ``sys.modules`` and blocking their import, then
382
+ re-importing ``flashspec.kernels`` fresh.
383
+ """
384
+
385
+ def test_kernels_package_imports_without_triton(self) -> None:
386
+ """``import flashspec.kernels`` succeeds even when Triton is missing."""
387
+ import builtins
388
+ import importlib
389
+ import sys
390
+
391
+ real_import = builtins.__import__
392
+
393
+ def fake_import(name, *args, **kwargs):
394
+ if name == "triton" or name.startswith("triton."):
395
+ raise ModuleNotFoundError(f"No module named '{name}'")
396
+ return real_import(name, *args, **kwargs)
397
+
398
+ # Drop any cached modules so re-import re-triggers the try/except.
399
+ for mod in list(sys.modules):
400
+ if mod.startswith("flashspec.kernels"):
401
+ del sys.modules[mod]
402
+
403
+ builtins.__import__ = fake_import
404
+ try:
405
+ kernels = importlib.import_module("flashspec.kernels")
406
+ finally:
407
+ builtins.__import__ = real_import
408
+ for mod in list(sys.modules):
409
+ if mod.startswith("flashspec.kernels"):
410
+ del sys.modules[mod]
411
+
412
+ assert kernels.TRITON_AVAILABLE is False, (
413
+ "TRITON_AVAILABLE should be False when triton import fails"
414
+ )
415
+
416
+ def test_verify_tokens_raises_actionable_import_error_without_triton(self) -> None:
417
+ """Calling verify_tokens without Triton raises ImportError mentioning flashspec[gpu]."""
418
+ import builtins
419
+ import importlib
420
+ import sys
421
+
422
+ real_import = builtins.__import__
423
+
424
+ def fake_import(name, *args, **kwargs):
425
+ if name == "triton" or name.startswith("triton."):
426
+ raise ModuleNotFoundError(f"No module named '{name}'")
427
+ return real_import(name, *args, **kwargs)
428
+
429
+ for mod in list(sys.modules):
430
+ if mod.startswith("flashspec.kernels"):
431
+ del sys.modules[mod]
432
+
433
+ builtins.__import__ = fake_import
434
+ try:
435
+ kernels = importlib.import_module("flashspec.kernels")
436
+ with pytest.raises(ImportError, match=r"flashspec\[gpu\]"):
437
+ kernels.verify_tokens(None, None, None, None)
438
+ finally:
439
+ builtins.__import__ = real_import
440
+ for mod in list(sys.modules):
441
+ if mod.startswith("flashspec.kernels"):
442
+ del sys.modules[mod]
443
+
444
+ def test_gather_accepted_raises_actionable_import_error_without_triton(self) -> None:
445
+ """Calling gather_accepted without Triton raises ImportError mentioning _reference."""
446
+ import builtins
447
+ import importlib
448
+ import sys
449
+
450
+ real_import = builtins.__import__
451
+
452
+ def fake_import(name, *args, **kwargs):
453
+ if name == "triton" or name.startswith("triton."):
454
+ raise ModuleNotFoundError(f"No module named '{name}'")
455
+ return real_import(name, *args, **kwargs)
456
+
457
+ for mod in list(sys.modules):
458
+ if mod.startswith("flashspec.kernels"):
459
+ del sys.modules[mod]
460
+
461
+ builtins.__import__ = fake_import
462
+ try:
463
+ kernels = importlib.import_module("flashspec.kernels")
464
+ with pytest.raises(ImportError, match="_reference"):
465
+ kernels.gather_accepted(None, None)
466
+ finally:
467
+ builtins.__import__ = real_import
468
+ for mod in list(sys.modules):
469
+ if mod.startswith("flashspec.kernels"):
470
+ del sys.modules[mod]
@@ -1,18 +0,0 @@
1
- """Triton-optimised kernels sub-package.
2
-
3
- All consumers of kernels must import from this module, not from the
4
- individual kernel files directly. This is enforced by import-linter.
5
-
6
- Private implementation modules:
7
- - ``verify_kernel``: token acceptance kernel.
8
- - ``gather_kernel``: accepted-token gather kernel.
9
- - ``_reference``: pure-PyTorch reference implementations (tests only).
10
- """
11
-
12
- from flashspec.kernels.gather_kernel import gather_accepted
13
- from flashspec.kernels.verify_kernel import verify_tokens
14
-
15
- __all__ = [
16
- "gather_accepted",
17
- "verify_tokens",
18
- ]
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes