fuzzgpu 0.2.0__tar.gz → 0.3.0__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 (68) hide show
  1. {fuzzgpu-0.2.0 → fuzzgpu-0.3.0}/Cargo.lock +4 -4
  2. {fuzzgpu-0.2.0 → fuzzgpu-0.3.0}/PKG-INFO +8 -7
  3. {fuzzgpu-0.2.0 → fuzzgpu-0.3.0}/README.md +7 -6
  4. {fuzzgpu-0.2.0 → fuzzgpu-0.3.0}/crates/fuzzgpu-core/Cargo.toml +1 -1
  5. {fuzzgpu-0.2.0 → fuzzgpu-0.3.0}/crates/fuzzgpu-core/src/gpu.rs +43 -47
  6. {fuzzgpu-0.2.0 → fuzzgpu-0.3.0}/crates/fuzzgpu-core/src/jaro.rs +2 -1
  7. {fuzzgpu-0.2.0 → fuzzgpu-0.3.0}/crates/fuzzgpu-core/src/levenshtein.rs +5 -7
  8. {fuzzgpu-0.2.0 → fuzzgpu-0.3.0}/crates/fuzzgpu-core/src/needleman.rs +3 -3
  9. {fuzzgpu-0.2.0 → fuzzgpu-0.3.0}/crates/fuzzgpu-core/src/simd.rs +1 -1
  10. {fuzzgpu-0.2.0 → fuzzgpu-0.3.0}/crates/fuzzgpu-core/tests/differential.rs +19 -9
  11. {fuzzgpu-0.2.0 → fuzzgpu-0.3.0}/crates/fuzzgpu-python/Cargo.toml +2 -2
  12. {fuzzgpu-0.2.0 → fuzzgpu-0.3.0}/crates/fuzzgpu-wasm/Cargo.toml +1 -1
  13. {fuzzgpu-0.2.0 → fuzzgpu-0.3.0}/pyproject.toml +1 -1
  14. {fuzzgpu-0.2.0 → fuzzgpu-0.3.0}/Cargo.toml +0 -0
  15. {fuzzgpu-0.2.0 → fuzzgpu-0.3.0}/LICENSE +0 -0
  16. {fuzzgpu-0.2.0 → fuzzgpu-0.3.0}/assets/logo.png +0 -0
  17. {fuzzgpu-0.2.0 → fuzzgpu-0.3.0}/crates/fuzzgpu-core/benches/bench.rs +0 -0
  18. {fuzzgpu-0.2.0 → fuzzgpu-0.3.0}/crates/fuzzgpu-core/src/damerau.rs +0 -0
  19. {fuzzgpu-0.2.0 → fuzzgpu-0.3.0}/crates/fuzzgpu-core/src/fuzz.rs +0 -0
  20. {fuzzgpu-0.2.0 → fuzzgpu-0.3.0}/crates/fuzzgpu-core/src/lib.rs +0 -0
  21. {fuzzgpu-0.2.0 → fuzzgpu-0.3.0}/crates/fuzzgpu-core/src/shaders/damerau.wgsl +0 -0
  22. {fuzzgpu-0.2.0 → fuzzgpu-0.3.0}/crates/fuzzgpu-core/src/shaders/damerau_matrix.wgsl +0 -0
  23. {fuzzgpu-0.2.0 → fuzzgpu-0.3.0}/crates/fuzzgpu-core/src/shaders/jaro.wgsl +0 -0
  24. {fuzzgpu-0.2.0 → fuzzgpu-0.3.0}/crates/fuzzgpu-core/src/shaders/jaro_matrix.wgsl +0 -0
  25. {fuzzgpu-0.2.0 → fuzzgpu-0.3.0}/crates/fuzzgpu-core/src/shaders/levenshtein.wgsl +0 -0
  26. {fuzzgpu-0.2.0 → fuzzgpu-0.3.0}/crates/fuzzgpu-core/src/shaders/levenshtein_cdist_myers.wgsl +0 -0
  27. {fuzzgpu-0.2.0 → fuzzgpu-0.3.0}/crates/fuzzgpu-core/src/shaders/levenshtein_matrix.wgsl +0 -0
  28. {fuzzgpu-0.2.0 → fuzzgpu-0.3.0}/crates/fuzzgpu-core/src/shaders/levenshtein_myers.wgsl +0 -0
  29. {fuzzgpu-0.2.0 → fuzzgpu-0.3.0}/crates/fuzzgpu-core/src/shaders/levenshtein_short.wgsl +0 -0
  30. {fuzzgpu-0.2.0 → fuzzgpu-0.3.0}/crates/fuzzgpu-core/src/shaders/needleman_affine.wgsl +0 -0
  31. {fuzzgpu-0.2.0 → fuzzgpu-0.3.0}/crates/fuzzgpu-core/src/shaders/needleman_wavefront.wgsl +0 -0
  32. {fuzzgpu-0.2.0 → fuzzgpu-0.3.0}/crates/fuzzgpu-core/tests/differential.proptest-regressions +0 -0
  33. {fuzzgpu-0.2.0 → fuzzgpu-0.3.0}/crates/fuzzgpu-core/tests/fixtures/broken.wgsl +0 -0
  34. {fuzzgpu-0.2.0 → fuzzgpu-0.3.0}/crates/fuzzgpu-core/tests/kernel_registration.rs +0 -0
  35. {fuzzgpu-0.2.0 → fuzzgpu-0.3.0}/crates/fuzzgpu-python/src/lib.rs +0 -0
  36. {fuzzgpu-0.2.0 → fuzzgpu-0.3.0}/crates/fuzzgpu-wasm/src/lib.rs +0 -0
  37. {fuzzgpu-0.2.0 → fuzzgpu-0.3.0}/crates/fuzzgpu-wasm/tests/differential_harness.js +0 -0
  38. {fuzzgpu-0.2.0 → fuzzgpu-0.3.0}/crates/fuzzgpu-wasm/tests/js_api.test.cjs +0 -0
  39. {fuzzgpu-0.2.0 → fuzzgpu-0.3.0}/python/fuzzgpu/__init__.py +0 -0
  40. {fuzzgpu-0.2.0 → fuzzgpu-0.3.0}/python/fuzzgpu/__init__.pyi +0 -0
  41. {fuzzgpu-0.2.0 → fuzzgpu-0.3.0}/python/fuzzgpu/distance/DamerauLevenshtein.py +0 -0
  42. {fuzzgpu-0.2.0 → fuzzgpu-0.3.0}/python/fuzzgpu/distance/Hamming.py +0 -0
  43. {fuzzgpu-0.2.0 → fuzzgpu-0.3.0}/python/fuzzgpu/distance/Indel.py +0 -0
  44. {fuzzgpu-0.2.0 → fuzzgpu-0.3.0}/python/fuzzgpu/distance/Jaro.py +0 -0
  45. {fuzzgpu-0.2.0 → fuzzgpu-0.3.0}/python/fuzzgpu/distance/JaroWinkler.py +0 -0
  46. {fuzzgpu-0.2.0 → fuzzgpu-0.3.0}/python/fuzzgpu/distance/LCSseq.py +0 -0
  47. {fuzzgpu-0.2.0 → fuzzgpu-0.3.0}/python/fuzzgpu/distance/Levenshtein.py +0 -0
  48. {fuzzgpu-0.2.0 → fuzzgpu-0.3.0}/python/fuzzgpu/distance/OSA.py +0 -0
  49. {fuzzgpu-0.2.0 → fuzzgpu-0.3.0}/python/fuzzgpu/distance/Postfix.py +0 -0
  50. {fuzzgpu-0.2.0 → fuzzgpu-0.3.0}/python/fuzzgpu/distance/Prefix.py +0 -0
  51. {fuzzgpu-0.2.0 → fuzzgpu-0.3.0}/python/fuzzgpu/distance/__init__.py +0 -0
  52. {fuzzgpu-0.2.0 → fuzzgpu-0.3.0}/python/fuzzgpu/distance/__init__.pyi +0 -0
  53. {fuzzgpu-0.2.0 → fuzzgpu-0.3.0}/python/fuzzgpu/distance/_common.py +0 -0
  54. {fuzzgpu-0.2.0 → fuzzgpu-0.3.0}/python/fuzzgpu/fuzz.py +0 -0
  55. {fuzzgpu-0.2.0 → fuzzgpu-0.3.0}/python/fuzzgpu/fuzz.pyi +0 -0
  56. {fuzzgpu-0.2.0 → fuzzgpu-0.3.0}/python/fuzzgpu/process.py +0 -0
  57. {fuzzgpu-0.2.0 → fuzzgpu-0.3.0}/python/fuzzgpu/process.pyi +0 -0
  58. {fuzzgpu-0.2.0 → fuzzgpu-0.3.0}/tests/test_api_signatures.py +0 -0
  59. {fuzzgpu-0.2.0 → fuzzgpu-0.3.0}/tests/test_basic.py +0 -0
  60. {fuzzgpu-0.2.0 → fuzzgpu-0.3.0}/tests/test_concurrency.py +0 -0
  61. {fuzzgpu-0.2.0 → fuzzgpu-0.3.0}/tests/test_edge_cases.py +0 -0
  62. {fuzzgpu-0.2.0 → fuzzgpu-0.3.0}/tests/test_gpu_health.py +0 -0
  63. {fuzzgpu-0.2.0 → fuzzgpu-0.3.0}/tests/test_gpu_threshold_env.py +0 -0
  64. {fuzzgpu-0.2.0 → fuzzgpu-0.3.0}/tests/test_invariants.py +0 -0
  65. {fuzzgpu-0.2.0 → fuzzgpu-0.3.0}/tests/test_out_buffers.py +0 -0
  66. {fuzzgpu-0.2.0 → fuzzgpu-0.3.0}/tests/test_rapidfuzz_compat.py +0 -0
  67. {fuzzgpu-0.2.0 → fuzzgpu-0.3.0}/tests/test_stress.py +0 -0
  68. {fuzzgpu-0.2.0 → fuzzgpu-0.3.0}/tests/wasm_python_differential.py +0 -0
@@ -308,9 +308,9 @@ dependencies = [
308
308
 
309
309
  [[package]]
310
310
  name = "either"
311
- version = "1.17.0"
311
+ version = "1.18.0"
312
312
  source = "registry+https://github.com/rust-lang/crates.io-index"
313
- checksum = "9e5e8f6c15a24b9a3ee5efec809ccd006d3b30e8b3bb63c39af737c7f87daa1d"
313
+ checksum = "252afb9ae5eaa683babdc6a068b3f5726eb19e05070c731f9b2a23a7c3e8ed34"
314
314
 
315
315
  [[package]]
316
316
  name = "equivalent"
@@ -372,7 +372,7 @@ dependencies = [
372
372
 
373
373
  [[package]]
374
374
  name = "fuzzgpu-core"
375
- version = "0.2.0"
375
+ version = "0.3.0"
376
376
  dependencies = [
377
377
  "bytemuck",
378
378
  "criterion",
@@ -386,7 +386,7 @@ dependencies = [
386
386
 
387
387
  [[package]]
388
388
  name = "fuzzgpu-python"
389
- version = "0.2.0"
389
+ version = "0.3.0"
390
390
  dependencies = [
391
391
  "fuzzgpu-core",
392
392
  "log",
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: fuzzgpu
3
- Version: 0.2.0
3
+ Version: 0.3.0
4
4
  Classifier: Development Status :: 4 - Beta
5
5
  Classifier: Intended Audience :: Developers
6
6
  Classifier: License :: OSI Approved :: MIT License
@@ -37,7 +37,8 @@ Project-URL: Repository, https://github.com/kuntal-devrat/fuzzgpu
37
37
 
38
38
  *Cross-platform GPU compute via WebGPU (`wgpu`) & Multi-Core CPU parallelism with Rayon. Zero CUDA dependencies.*
39
39
 
40
- [![PyPI Version](https://img.shields.io/badge/pypi-v0.2.0-blue.svg?style=flat-square)](https://pypi.org/project/fuzzgpu/)
40
+ [![PyPI
41
+ Version](https://img.shields.io/badge/pypi-v0.3.0-blue.svg?style=flat-square)](https://pypi.org/project/fuzzgpu/)
41
42
  [![License: MIT](https://img.shields.io/badge/License-MIT-green.svg?style=flat-square)](https://opensource.org/licenses/MIT)
42
43
  [![Rust](https://img.shields.io/badge/rust-1.87+-orange.svg?style=flat-square)](https://www.rust-lang.org)
43
44
  [![Cross Platform](https://img.shields.io/badge/platform-Windows%20%7C%20macOS%20%7C%20Linux%20%7C%20WASM-lightgrey.svg?style=flat-square)](https://github.com/kuntal-devrat/fuzzgpu)
@@ -60,7 +61,7 @@ No NVIDIA CUDA drivers or complex toolkits required.
60
61
 
61
62
  ---
62
63
 
63
- ## What's New in v0.2.0
64
+ ## What's New in v0.3.0
64
65
 
65
66
  ### Production hardening
66
67
  - **Kernel `get()` panics eliminated** — all four GPU kernels (`GpuLevenshteinKernel`,
@@ -164,7 +165,7 @@ The full Python layer is now byte-identical to rapidfuzz 3.14.5 over a 169,744-p
164
165
  ## Benchmark Results
165
166
 
166
167
  *Hardware: Intel(R) Iris(R) Xe Graphics — integrated GPU (Vulkan) + Intel Core i7 (Rayon, all cores)*
167
- *Versions: fuzzgpu 0.2.0 · rapidfuzz 3.14.5 · python-Levenshtein 0.27.4*
168
+ *Versions: fuzzgpu 0.3.0 · rapidfuzz 3.14.5 · python-Levenshtein 0.27.4*
168
169
  *Median of 7 runs after warmup. Reproduce: `python benchmarks/bench_compare.py`*
169
170
 
170
171
  > **GPU class note:** These numbers are from an **integrated GPU** (iGPU), which shares memory
@@ -229,7 +230,7 @@ pip install fuzzgpu
229
230
  ```toml
230
231
  # Rust
231
232
  [dependencies]
232
- fuzzgpu-core = "0.2.0"
233
+ fuzzgpu-core = "0.3.0"
233
234
  ```
234
235
 
235
236
  ---
@@ -325,7 +326,7 @@ fuzzgpu.set_cpu_only(True) # force CPU-only mode
325
326
 
326
327
  ```toml
327
328
  [dependencies]
328
- fuzzgpu-core = "0.2.0" # GPU + CPU fallback
329
+ fuzzgpu-core = "0.3.0" # GPU + CPU fallback
329
330
  # fuzzgpu-core = { version = "0.1.7", default-features = false } # CPU-only
330
331
  ```
331
332
 
@@ -471,7 +472,7 @@ cargo test --workspace
471
472
  | `FUZZGPU_DEBUG` | Log GPU→CPU fallback decisions |
472
473
  | `FUZZGPU_SIMD` | Force ISA: `portable\|neon\|avx2\|avx512` |
473
474
  | `FUZZGPU_READBACK_TIMEOUT_MS` | GPU readback timeout (default 10000 ms) |
474
- | `FUZZGPU_SKIP_DISPATCH_LOCK` | Bypass serialization lock (repro only) |
475
+ | `FUZZGPU_SKIP_DISPATCH_LOCK` | Opt-in GPU dispatch serialization (safety valve for the rare gfx-rs/wgpu#10085 crash class on Intel D3D12; dispatch is fully concurrent by default) |
475
476
  | `FUZZGPU_REQUIRE_GPU` | In tests: fail instead of skip when no GPU |
476
477
  | `WGPU_BACKEND` | Force wgpu backend: `vulkan\|metal\|dx12` |
477
478
  | `PROPTEST_CASES` | Override proptest case count |
@@ -8,7 +8,8 @@
8
8
 
9
9
  *Cross-platform GPU compute via WebGPU (`wgpu`) & Multi-Core CPU parallelism with Rayon. Zero CUDA dependencies.*
10
10
 
11
- [![PyPI Version](https://img.shields.io/badge/pypi-v0.2.0-blue.svg?style=flat-square)](https://pypi.org/project/fuzzgpu/)
11
+ [![PyPI
12
+ Version](https://img.shields.io/badge/pypi-v0.3.0-blue.svg?style=flat-square)](https://pypi.org/project/fuzzgpu/)
12
13
  [![License: MIT](https://img.shields.io/badge/License-MIT-green.svg?style=flat-square)](https://opensource.org/licenses/MIT)
13
14
  [![Rust](https://img.shields.io/badge/rust-1.87+-orange.svg?style=flat-square)](https://www.rust-lang.org)
14
15
  [![Cross Platform](https://img.shields.io/badge/platform-Windows%20%7C%20macOS%20%7C%20Linux%20%7C%20WASM-lightgrey.svg?style=flat-square)](https://github.com/kuntal-devrat/fuzzgpu)
@@ -31,7 +32,7 @@ No NVIDIA CUDA drivers or complex toolkits required.
31
32
 
32
33
  ---
33
34
 
34
- ## What's New in v0.2.0
35
+ ## What's New in v0.3.0
35
36
 
36
37
  ### Production hardening
37
38
  - **Kernel `get()` panics eliminated** — all four GPU kernels (`GpuLevenshteinKernel`,
@@ -135,7 +136,7 @@ The full Python layer is now byte-identical to rapidfuzz 3.14.5 over a 169,744-p
135
136
  ## Benchmark Results
136
137
 
137
138
  *Hardware: Intel(R) Iris(R) Xe Graphics — integrated GPU (Vulkan) + Intel Core i7 (Rayon, all cores)*
138
- *Versions: fuzzgpu 0.2.0 · rapidfuzz 3.14.5 · python-Levenshtein 0.27.4*
139
+ *Versions: fuzzgpu 0.3.0 · rapidfuzz 3.14.5 · python-Levenshtein 0.27.4*
139
140
  *Median of 7 runs after warmup. Reproduce: `python benchmarks/bench_compare.py`*
140
141
 
141
142
  > **GPU class note:** These numbers are from an **integrated GPU** (iGPU), which shares memory
@@ -200,7 +201,7 @@ pip install fuzzgpu
200
201
  ```toml
201
202
  # Rust
202
203
  [dependencies]
203
- fuzzgpu-core = "0.2.0"
204
+ fuzzgpu-core = "0.3.0"
204
205
  ```
205
206
 
206
207
  ---
@@ -296,7 +297,7 @@ fuzzgpu.set_cpu_only(True) # force CPU-only mode
296
297
 
297
298
  ```toml
298
299
  [dependencies]
299
- fuzzgpu-core = "0.2.0" # GPU + CPU fallback
300
+ fuzzgpu-core = "0.3.0" # GPU + CPU fallback
300
301
  # fuzzgpu-core = { version = "0.1.7", default-features = false } # CPU-only
301
302
  ```
302
303
 
@@ -442,7 +443,7 @@ cargo test --workspace
442
443
  | `FUZZGPU_DEBUG` | Log GPU→CPU fallback decisions |
443
444
  | `FUZZGPU_SIMD` | Force ISA: `portable\|neon\|avx2\|avx512` |
444
445
  | `FUZZGPU_READBACK_TIMEOUT_MS` | GPU readback timeout (default 10000 ms) |
445
- | `FUZZGPU_SKIP_DISPATCH_LOCK` | Bypass serialization lock (repro only) |
446
+ | `FUZZGPU_SKIP_DISPATCH_LOCK` | Opt-in GPU dispatch serialization (safety valve for the rare gfx-rs/wgpu#10085 crash class on Intel D3D12; dispatch is fully concurrent by default) |
446
447
  | `FUZZGPU_REQUIRE_GPU` | In tests: fail instead of skip when no GPU |
447
448
  | `WGPU_BACKEND` | Force wgpu backend: `vulkan\|metal\|dx12` |
448
449
  | `PROPTEST_CASES` | Override proptest case count |
@@ -1,6 +1,6 @@
1
1
  [package]
2
2
  name = "fuzzgpu-core"
3
- version = "0.2.0"
3
+ version = "0.3.0"
4
4
  edition = "2021"
5
5
  description = "GPU-accelerated fuzzy string matching engine"
6
6
  license = "MIT"
@@ -25,25 +25,23 @@ pub fn require_gpu() -> bool {
25
25
  .unwrap_or(false)
26
26
  }
27
27
 
28
- /// Whether the dispatch-serialization workaround is bypassed. Shared by the
29
- /// test-only lock below and the production [`GpuEngine::dispatch_lock`] so
30
- /// `FUZZGPU_SKIP_DISPATCH_LOCK=1` disables BOTH the repro harness needs the
31
- /// bypass to reproduce upstream gfx-rs/wgpu#10085 under real concurrency.
32
- pub(crate) fn dispatch_lock_bypass() -> bool {
28
+ /// Opt-in dispatch serialization (safety valve). `FUZZGPU_SKIP_DISPATCH_LOCK=1`
29
+ /// re-enables the GPU-dispatch serialization that used to be the default
30
+ /// workaround for the rare gfx-rs/wgpu#10085 crash class (heap corruption on
31
+ /// Intel D3D12 under >=3 concurrent dispatchers on a shared device). Dispatch
32
+ /// is fully concurrent by default; set this env var on affected Intel hardware
33
+ /// to serialize both the production dispatch path and the test suite.
34
+ pub(crate) fn dispatch_serialize() -> bool {
33
35
  std::env::var("FUZZGPU_SKIP_DISPATCH_LOCK")
34
36
  .map(|v| v == "1" || v.eq_ignore_ascii_case("true"))
35
37
  .unwrap_or(false)
36
38
  }
37
39
 
38
- /// Serialize GPU access across tests. This is a workaround for a wgpu/driver
39
- /// crash on Intel Iris Xe under >=3 concurrent dispatchers on the shared
40
- /// device (heap corruption on DX12, segfault on Vulkan) see
41
- /// `repro/wgpu-parallel-crash` and upstream gfx-rs/wgpu#10085.
42
- ///
43
- /// Setting `FUZZGPU_SKIP_DISPATCH_LOCK=1` bypasses the workaround so the
44
- /// underlying crash can be reproduced / bisected in CI or locally.
40
+ /// Serialize GPU access across tests. Only active when
41
+ /// `FUZZGPU_SKIP_DISPATCH_LOCK=1` (opt-in safety valve); by default returns
42
+ /// `None` so the test suite runs with fully concurrent GPU dispatch.
45
43
  pub fn gpu_test_lock() -> Option<std::sync::MutexGuard<'static, ()>> {
46
- if dispatch_lock_bypass() {
44
+ if !dispatch_serialize() {
47
45
  return None;
48
46
  }
49
47
  Some(
@@ -99,21 +97,10 @@ pub(crate) fn effective_shader_source(real: &'static str) -> std::borrow::Cow<'s
99
97
  std::borrow::Cow::Borrowed(real)
100
98
  }
101
99
 
102
- /// Serialize GPU access across tests.
103
- ///
104
- /// This is a workaround for a `wgpu-core` bug (`Queue::drop` spurious
105
- /// `assert!(queue_empty)` panic under concurrent dispatch) filed as
106
- /// gfx-rs/wgpu#10085. The fix is in branch `fix/queue-drop-drain-loop`
107
- /// (PR pending merge into `gfx-rs/wgpu`).
108
- ///
109
- /// **Removal plan:** once the wgpu fix ships in a released version and
110
- /// `fuzzgpu` bumps its dependency to that version, remove:
111
- /// - This lock and its static
112
- /// - `dispatch_lock_bypass()` and `FUZZGPU_SKIP_DISPATCH_LOCK` support
113
- /// - `GpuEngine::dispatch_lock` field and `dispatch_lock()` method
114
- ///
115
- /// Setting `FUZZGPU_SKIP_DISPATCH_LOCK=1` bypasses the workaround so the
116
- /// underlying crash can be reproduced / bisected in CI or locally.
100
+ /// Test-suite dispatch serialization, active only when
101
+ /// `FUZZGPU_SKIP_DISPATCH_LOCK=1` (opt-in safety valve for the rare
102
+ /// gfx-rs/wgpu#10085 crash class on Intel D3D12). By default the suite runs
103
+ /// with fully concurrent GPU dispatch.
117
104
  pub static GPU_TEST_DISPATCH_LOCK: std::sync::Mutex<()> = std::sync::Mutex::new(());
118
105
 
119
106
  /// Serializes tests that mutate `GPU_THRESHOLD_OVERRIDE`.
@@ -236,13 +223,11 @@ pub struct GpuEngine {
236
223
  pub info: GpuInfo,
237
224
  pub max_buffer_size: u64,
238
225
  pub max_storage_buffer_binding_size: u32,
239
- /// Serializes GPU dispatch across threads. The upstream wgpu/driver crash
240
- /// (gfx-rs/wgpu#10085) triggers under >=3 concurrent dispatchers on the
241
- /// shared device (Intel iGPUs, DX12/Vulkan) e.g. two Python threads
242
- /// calling the GIL-releasing GPU bindings simultaneously. Every public GPU
226
+ /// Opt-in dispatch serialization (safety valve for the rare
227
+ /// gfx-rs/wgpu#10085 crash class on Intel D3D12). By default dispatch is
228
+ /// fully concurrent; when `FUZZGPU_SKIP_DISPATCH_LOCK=1` every public GPU
243
229
  /// entry point holds this lock for the duration of its dispatch + readback,
244
- /// so at most one submission is ever in flight. `FUZZGPU_SKIP_DISPATCH_LOCK`
245
- /// bypasses it (repro harness only).
230
+ /// so at most one submission is ever in flight.
246
231
  dispatch_lock: std::sync::Mutex<()>,
247
232
  }
248
233
 
@@ -533,11 +518,11 @@ impl GpuEngine {
533
518
  Ok(bytes)
534
519
  }
535
520
 
536
- /// Take the production dispatch lock (see the field doc). Returns `None`
537
- /// when `FUZZGPU_SKIP_DISPATCH_LOCK` is set so the repro harness can
538
- /// reproduce upstream #10085 under real concurrency.
521
+ /// Take the opt-in dispatch serialization lock (see the field doc).
522
+ /// Returns `None` unless `FUZZGPU_SKIP_DISPATCH_LOCK=1` is set dispatch
523
+ /// is fully concurrent by default.
539
524
  pub(crate) fn dispatch_lock(&self) -> Option<std::sync::MutexGuard<'_, ()>> {
540
- if dispatch_lock_bypass() {
525
+ if !dispatch_serialize() {
541
526
  return None;
542
527
  }
543
528
  Some(self.dispatch_lock.lock().unwrap_or_else(|e| e.into_inner()))
@@ -722,13 +707,16 @@ mod tests {
722
707
 
723
708
  /// `set_gpu_threshold` must override the auto value and be resettable to
724
709
  /// auto (`None`) at runtime (a `Mutex`, not a one-shot `OnceLock`). The
725
- /// override is global, so this test holds the GPU test lock serializing
726
- /// it against every other GPU test, which would otherwise see the
727
- /// temporary override (e.g. a 1000-pair fault-injection test routed to
728
- /// CPU instead of dispatching) — and restores `None` before returning.
710
+ /// override is global, so this test holds `GPU_THRESHOLD_TEST_LOCK`
711
+ /// excluding every other test that mutates or force-reads the override
712
+ /// (fault-injection tests route to CPU instead of dispatching when a
713
+ /// concurrent test steals the override mid-flight) — and restores `None`
714
+ /// before returning.
729
715
  #[test]
730
716
  fn test_set_gpu_threshold_override_and_reset() {
731
- let _gpu_guard = gpu_test_lock();
717
+ let _thr = GPU_THRESHOLD_TEST_LOCK
718
+ .lock()
719
+ .unwrap_or_else(|e| e.into_inner());
732
720
  GpuEngine::set_gpu_threshold(Some(1234));
733
721
  assert_eq!(GpuEngine::gpu_threshold_override(), Some(1234));
734
722
  if let Ok(engine) = GpuEngine::get() {
@@ -808,12 +796,20 @@ mod tests {
808
796
  disarm_shader_error_fault();
809
797
  }
810
798
 
811
- /// dispatch_lock_bypass must read FUZZGPU_SKIP_DISPATCH_LOCK and return
812
- /// true only when it is set to '1' or 'true' (case-insensitive).
799
+ /// dispatch_serialize must read FUZZGPU_SKIP_DISPATCH_LOCK and return
800
+ /// true only when it is set to '1' or 'true' (case-insensitive). The
801
+ /// serialization is opt-in: unset (default) means fully concurrent.
813
802
  #[test]
814
- fn test_dispatch_lock_bypass_default_is_false() {
803
+ fn test_dispatch_serialize_opt_in() {
804
+ std::env::remove_var("FUZZGPU_SKIP_DISPATCH_LOCK");
805
+ assert!(!dispatch_serialize());
806
+ std::env::set_var("FUZZGPU_SKIP_DISPATCH_LOCK", "1");
807
+ assert!(dispatch_serialize());
808
+ std::env::set_var("FUZZGPU_SKIP_DISPATCH_LOCK", "true");
809
+ assert!(dispatch_serialize());
810
+ std::env::set_var("FUZZGPU_SKIP_DISPATCH_LOCK", "0");
811
+ assert!(!dispatch_serialize());
815
812
  std::env::remove_var("FUZZGPU_SKIP_DISPATCH_LOCK");
816
- assert!(!dispatch_lock_bypass());
817
813
  }
818
814
 
819
815
  /// force_gpu_threshold sets the override and its Drop resets to None.
@@ -817,7 +817,8 @@ pub mod gpu_ext {
817
817
  // Decode 4×u32 per pair and assemble the f64 score host-side
818
818
  // (bit-exact with the CPU reference). GPU_RECOMPUTE pairs are
819
819
  // returned as -1.0; compute_batch recomputes them on CPU.
820
- for (t, part) in raw.chunks_exact(4).enumerate() {
820
+ for (t, chunk_start) in (0..raw.len()).step_by(4).enumerate() {
821
+ let part = &raw[chunk_start..chunk_start + 4];
821
822
  if part[0] == GPU_RECOMPUTE {
822
823
  gpu_results.push(-1.0);
823
824
  } else {
@@ -2040,8 +2040,9 @@ pub mod gpu_ext {
2040
2040
  };
2041
2041
  // Force GPU dispatch: below the auto threshold the fault is never
2042
2042
  // exercised because the batch routes to CPU before reaching the GPU
2043
- // readback path.
2044
- GpuEngine::set_gpu_threshold(Some(1));
2043
+ // readback path. The RAII guard holds the threshold lock, so no
2044
+ // concurrent test can steal or reset the override mid-dispatch.
2045
+ let _force = crate::gpu::force_gpu_threshold(1);
2045
2046
 
2046
2047
  let a = gen_strings(1000, 0xABCDEF01);
2047
2048
  let b = gen_strings(1000, 0x23456789);
@@ -2054,7 +2055,6 @@ pub mod gpu_ext {
2054
2055
  crate::gpu::arm_readback_timeout_fault();
2055
2056
  let result = kernel.compute(&pairs);
2056
2057
  crate::gpu::disarm_readback_timeout_fault();
2057
- GpuEngine::set_gpu_threshold(None);
2058
2058
 
2059
2059
  match result {
2060
2060
  Err(FuzzGpuError::Timeout(_)) => {} // expected
@@ -2070,7 +2070,7 @@ pub mod gpu_ext {
2070
2070
  let Some(kernel) = gpu_kernel_or_skip() else {
2071
2071
  return;
2072
2072
  };
2073
- GpuEngine::set_gpu_threshold(Some(1));
2073
+ let _force = crate::gpu::force_gpu_threshold(1);
2074
2074
 
2075
2075
  let a = gen_strings(30, 0x11111111);
2076
2076
  let b = gen_strings(30, 0x22222222);
@@ -2080,7 +2080,6 @@ pub mod gpu_ext {
2080
2080
  crate::gpu::arm_readback_timeout_fault();
2081
2081
  let result = kernel.compute_matrix(&refs_a, &refs_b);
2082
2082
  crate::gpu::disarm_readback_timeout_fault();
2083
- GpuEngine::set_gpu_threshold(None);
2084
2083
 
2085
2084
  match result {
2086
2085
  Err(FuzzGpuError::Timeout(_)) => {} // expected
@@ -2097,7 +2096,7 @@ pub mod gpu_ext {
2097
2096
  let Some(kernel) = gpu_kernel_or_skip() else {
2098
2097
  return;
2099
2098
  };
2100
- GpuEngine::set_gpu_threshold(Some(1));
2099
+ let _force = crate::gpu::force_gpu_threshold(1);
2101
2100
 
2102
2101
  let a = gen_strings(1000, 0x0BADF00D);
2103
2102
  let b = gen_strings(1000, 0xF00DBABE);
@@ -2110,7 +2109,6 @@ pub mod gpu_ext {
2110
2109
  crate::gpu::arm_small_buffer_fault();
2111
2110
  let result = kernel.compute(&pairs);
2112
2111
  crate::gpu::disarm_small_buffer_fault();
2113
- GpuEngine::set_gpu_threshold(None);
2114
2112
 
2115
2113
  match result {
2116
2114
  Err(FuzzGpuError::BufferError(_)) => {} // expected
@@ -1485,7 +1485,6 @@ mod tests {
1485
1485
  #[cfg(feature = "gpu")]
1486
1486
  mod gpu_tests {
1487
1487
  use super::*;
1488
- use crate::gpu::GpuEngine;
1489
1488
  use crate::needleman::gpu_ext::GpuNeedlemanAffineKernel;
1490
1489
 
1491
1490
  fn gpu_kernel_or_skip() -> Option<&'static GpuNeedlemanAffineKernel> {
@@ -1532,7 +1531,9 @@ mod tests {
1532
1531
  let Some(kernel) = gpu_kernel_or_skip() else {
1533
1532
  return;
1534
1533
  };
1535
- GpuEngine::set_gpu_threshold(Some(1));
1534
+ // RAII: holds the threshold lock so no concurrent test can steal
1535
+ // the override, and restores `None` on drop.
1536
+ let _force = crate::gpu::force_gpu_threshold(1);
1536
1537
 
1537
1538
  let mut state: u64 = 0xDEAD_CAFE;
1538
1539
  let mut pairs: Vec<(String, String)> = Vec::with_capacity(500);
@@ -1567,7 +1568,6 @@ mod tests {
1567
1568
  .map(|(a, b)| needleman_wunsch_affine(a, b, 2, -1, -3, -1))
1568
1569
  .collect();
1569
1570
  assert_eq!(gpu, cpu, "GPU NW must match CPU for normal scoring range");
1570
- GpuEngine::set_gpu_threshold(None);
1571
1571
  }
1572
1572
  }
1573
1573
  }
@@ -779,7 +779,7 @@ pub fn jaro_bitpar(a: &[u8], b: &[u8]) -> f64 {
779
779
 
780
780
  let cand = pos_b[a[i] as usize] & window & !matched_b;
781
781
  if cand != 0 {
782
- let lowest = cand & cand.wrapping_neg();
782
+ let lowest = cand.isolate_lowest_one();
783
783
  matched_b |= lowest;
784
784
  matched_a |= 1u64 << i;
785
785
  matches += 1;
@@ -532,17 +532,27 @@ mod gpu_differential {
532
532
  use fuzzgpu_core::needleman::gpu_ext::GpuNeedlemanAffineKernel;
533
533
  use std::sync::{Mutex, MutexGuard};
534
534
 
535
- /// Serializes GPU access across the differential tests. The lib suite's
536
- /// equivalent (`gpu::GPU_TEST_DISPATCH_LOCK`) is `#[cfg(test)]`-only and
537
- /// invisible to integration tests, so this is a local copy — the same
538
- /// workaround for the same wgpu/driver crash (gfx-rs/wgpu#10085) observed
539
- /// under >=3 concurrent dispatchers on the shared device (Intel Iris Xe).
535
+ /// Opt-in GPU serialization across the differential tests, mirroring the
536
+ /// lib's contract: fully concurrent by default; when
537
+ /// `FUZZGPU_SKIP_DISPATCH_LOCK=1` (opt-in safety valve for the rare
538
+ /// gfx-rs/wgpu#10085 crash class) the lock serializes dispatch.
540
539
  static GPU_TEST_DISPATCH_LOCK: Mutex<()> = Mutex::new(());
541
540
 
542
- fn gpu_test_lock() -> MutexGuard<'static, ()> {
543
- GPU_TEST_DISPATCH_LOCK
544
- .lock()
545
- .unwrap_or_else(|e| e.into_inner())
541
+ fn dispatch_serialize() -> bool {
542
+ std::env::var("FUZZGPU_SKIP_DISPATCH_LOCK")
543
+ .map(|v| v == "1" || v.eq_ignore_ascii_case("true"))
544
+ .unwrap_or(false)
545
+ }
546
+
547
+ fn gpu_test_lock() -> Option<MutexGuard<'static, ()>> {
548
+ if !dispatch_serialize() {
549
+ return None;
550
+ }
551
+ Some(
552
+ GPU_TEST_DISPATCH_LOCK
553
+ .lock()
554
+ .unwrap_or_else(|e| e.into_inner()),
555
+ )
546
556
  }
547
557
 
548
558
  /// When `FUZZGPU_REQUIRE_GPU` is set (CI with a software Vulkan adapter),
@@ -1,6 +1,6 @@
1
1
  [package]
2
2
  name = "fuzzgpu-python"
3
- version = "0.2.0"
3
+ version = "0.3.0"
4
4
  edition = "2021"
5
5
  description = "Python bindings for FuzzGPU: GPU-accelerated fuzzy string matching engine"
6
6
  license = "MIT"
@@ -17,7 +17,7 @@ default = ["gpu"]
17
17
  gpu = ["fuzzgpu-core/gpu"]
18
18
 
19
19
  [dependencies]
20
- fuzzgpu-core = { path = "../fuzzgpu-core", version = "0.2.0", default-features = false }
20
+ fuzzgpu-core = { path = "../fuzzgpu-core", version = "0.3.0", default-features = false }
21
21
  pyo3 = { workspace = true, features = ["abi3-py310"] }
22
22
  numpy = "0.23"
23
23
  pollster = { workspace = true }
@@ -1,6 +1,6 @@
1
1
  [package]
2
2
  name = "fuzzgpu-wasm"
3
- version = "0.2.0"
3
+ version = "0.3.0"
4
4
  edition = "2021"
5
5
 
6
6
  [workspace]
@@ -4,7 +4,7 @@ build-backend = "maturin"
4
4
 
5
5
  [project]
6
6
  name = "fuzzgpu"
7
- version = "0.2.0"
7
+ version = "0.3.0"
8
8
  description = "Hardware-accelerated fuzzy string matching & sequence alignment — cross-platform, no CUDA required"
9
9
  readme = "README.md"
10
10
  license = { text = "MIT" }
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes