fuzzgpu
Hardware-Accelerated Fuzzy String Matching & Sequence Alignment
Cross-platform GPU compute via WebGPU (wgpu) & Multi-Core CPU parallelism with Rayon. Zero CUDA dependencies.
Overview
fuzzgpu is a high-throughput string distance and sequence alignment engine written in Rust with native Python and WebAssembly bindings. It leverages GPU compute shaders (wgpu / WGSL) and Rayon multi-threading to accelerate large-scale batch queries and distance matrix computations across:
- Apple Silicon (Metal)
- Linux (Vulkan)
- Windows (DirectX 12 / Vulkan)
- Integrated GPUs (Intel Iris Xe, AMD Radeon)
- WebAssembly (In-browser execution)
No NVIDIA CUDA drivers or complex toolkits required.
What's New in v0.1.6
Drop-in rapidfuzz parity (Python)
The full Python layer is now byte-identical to rapidfuzz 3.14.5 over a 169,744-pair differential harness across ratio, partial_ratio, token_sort_ratio, token_set_ratio, token_ratio, WRatio, QRatio, partial_token_*, jaro, jaro_winkler, levenshtein, indel, hamming, osa — 0 mismatches.
Bug fixes (Rust core, float parity)
ratio/partial_ratiocutoff imprecision — port of rapidfuzz's load-bearingNormSim_to_NormDist = min(1, 1 - cutoff/100 + 1e-5)term. Without it, the branch-and-bound would silently reject ties at exact cutoffs (e.g.partial_ratio("park", "ba", score_cutoff=50.0)returned0instead of50).ratioscore formula — switched from((len-dist)/len)*100to(1 - dist/len)*100to match rapidfuzz C++'s exact ulp order (indel_normalized_similarity * 100).
New features (Python distance layer)
Editops/Opcodes/Editop/Opcode/MatchingBlock/ScoreAlignmentclasses (rapidfuzz-compatible list-/tuple-likes withas_list,as_opcodes,as_editops,as_matching_blocks,apply,inverse,remove_subsequence,from_*).Levenshtein.editops/.opcodes— exact Myers bit-parallel port withcommon_affix(suffix measured on post-prefix slice, matching rapidfuzz).LCSseqmodule — Myers LCS bit-parallel matrix + editops/opcodes (delete-checked-first traceback).PrefixandPostfixmodules with the C++1 - dist/maximumulp order.Hamming.editops/.opcodes— replace-per-mismatch + padding delete/insert model.Indel.editops/.opcodes— delegates toLCSseq(matching rapidfuzz C++).fuzz.partial_ratio_alignmentnow returnsScoreAlignment(score, src_start, src_end, dest_start, dest_end)— drop-in compatible with rapidfuzz.process.extract/extractOne/cdistdefault toWRatio(matches rapidfuzz 3.14.5).token_ratio/partial_token_ratioexposed at top level (fuzzgpu.token_ratio,fuzzgpu.partial_token_ratio).- All alignment types re-exported at the package root (
fuzzgpu.Editop,fuzzgpu.Editops, etc.).
Type stubs
__init__.pyi,fuzz.pyi,process.pyi,distance/__init__.pyiupdated for the new APIs.
Previous (v0.1.5
Bug fixes
- Damerau-Levenshtein safety gate now fires in release builds (
assert! not debug_assert!) — non-ASCII inputs no longer silently produce wrong distances in production wheels
- Needleman-Wunsch GPU f32 precision guard — scoring parameters that exceed the exact f32 integer range (2²⁴ = 16,777,216) now automatically route to CPU, preventing silent precision loss
- Wavefront shader race condition fixed —
diags[1] seed initialization consolidated into a single thread with a proper workgroupBarrier()
extract_one early-exit fixed — the break at score==100.0 now only fires after the is_better check
- Distance module processor bug fixed across all
distance/*.py modules — similarity/normalized_* now apply the processor once, then compute maximum on the processed strings
Optimizations
- Zero-allocation SIMD hot paths —
levenshtein_cdist, levenshtein_batch, and jaro_winkler_batch now use stack-allocated [&[u8]; 8] instead of per-group heap Vec, eliminating millions of tiny allocations at 1M-cell matrix scale
token_set_ratio uses Cow<str> to skip heap allocation when intersection/difference sets are empty
process.cdist fast path routes through the Rayon/GPU ratio_batch when the default scorer is used, instead of one Python call per cell
New features
partial_ratio_alignment(s1, s2) → (score, src_start, dest_start, length) — rapidfuzz-compatible alignment result
partial_token_sort_ratio, partial_token_set_ratio, QRatio — now exposed at the top level
- Jaro-Winkler GPU routing in Python —
jaro_winkler_batch and jaro_winkler_cdist now use the GPU kernel on discrete GPUs
- Needleman-Wunsch GPU routing in Python —
needleman_wunsch_affine_batch now uses GpuNeedlemanAffineKernel
editops and opcodes re-exported at the top level (fuzzgpu.editops, fuzzgpu.opcodes)
- Complete type stubs (
__init__.pyi, fuzz.pyi, process.pyi)
assert! not debug_assert!) — non-ASCII inputs no longer silently produce wrong distances in production wheelsdiags[1] seed initialization consolidated into a single thread with a proper workgroupBarrier()extract_one early-exit fixed — the break at score==100.0 now only fires after the is_better checkdistance/*.py modules — similarity/normalized_* now apply the processor once, then compute maximum on the processed stringslevenshtein_cdist, levenshtein_batch, and jaro_winkler_batch now use stack-allocated [&[u8]; 8] instead of per-group heap Vec, eliminating millions of tiny allocations at 1M-cell matrix scaletoken_set_ratio uses Cow<str> to skip heap allocation when intersection/difference sets are emptyprocess.cdist fast path routes through the Rayon/GPU ratio_batch when the default scorer is used, instead of one Python call per cellpartial_ratio_alignment(s1, s2) → (score, src_start, dest_start, length) — rapidfuzz-compatible alignment resultpartial_token_sort_ratio, partial_token_set_ratio, QRatio — now exposed at the top leveljaro_winkler_batch and jaro_winkler_cdist now use the GPU kernel on discrete GPUsneedleman_wunsch_affine_batch now uses GpuNeedlemanAffineKerneleditops and opcodes re-exported at the top level (fuzzgpu.editops, fuzzgpu.opcodes)__init__.pyi, fuzz.pyi, process.pyi)Benchmark Results
Hardware: Intel(R) Iris(R) Xe Graphics (Vulkan) + Intel Core i7 (Rayon uses all cores)
Versions: fuzzgpu 0.1.6 · rapidfuzz 3.14.5 · python-Levenshtein 0.27.4
Median of 7 runs after warmup. Reproduce: python benchmarks/bench_compare.py
Levenshtein Batch (1 query × N candidates, 10-char strings)
| Batch Size | fuzzgpu (GPU) |
fuzzgpu (CPU) |
rapidfuzz |
vs RF (GPU) | vs RF (CPU) |
|---|---|---|---|---|---|
| 100 | 0.12 ms | 0.01 ms | 0.03 ms | 0.25× | 2.21× |
| 1,000 | 0.83 ms | 0.09 ms | 0.15 ms | 0.18× | 1.67× |
| 10,000 | 2.60 ms | 0.87 ms | 1.47 ms | 0.57× | 1.68× |
| 50,000 | 9.92 ms | 5.04 ms | 6.38 ms | 0.64× | 1.27× |
Damerau-Levenshtein Batch (unrestricted Lowrance-Wagner)
| Batch Size | fuzzgpu (GPU) |
fuzzgpu (CPU) |
rapidfuzz |
vs RF (GPU) | vs RF (CPU) |
|---|---|---|---|---|---|
| 1,000 | 0.58 ms | 0.40 ms | 1.63 ms | 2.81× | 4.06× |
| 10,000 | 2.95 ms | 2.15 ms | 20.86 ms | 7.06× | 9.70× |
| 50,000 | 17.49 ms | 12.08 ms | 120.25 ms | 6.88× | 9.95× |
Note: rapidfuzz's
DamerauLevenshteinuses Optimal String Alignment (OSA). fuzzgpu implements the unrestricted Lowrance-Wagner (1975) algorithm which allows non-adjacent transpositions. For example:damerau("ca", "abc") == 2(fuzzgpu) vs3(rapidfuzz OSA). Usefuzzgpu.distance.OSAfor OSA-compatible semantics.
Installation
pip install fuzzgpu
# Rust
[dependencies]
fuzzgpu-core = "0.1.6"
Quickstart
import fuzzgpu
# ── Core distance metrics ─────────────────────────────────────────────────────
lev = fuzzgpu.levenshtein("kitten", "sitting") # 3
dam = fuzzgpu.damerau("ab", "ba") # 1 (transposition)
jw = fuzzgpu.jaro_winkler("MARTHA", "MARHTA") # 0.9611...
# ── Batch (auto-routed GPU/CPU) ───────────────────────────────────────────────
candidates = ["hallo", "hullo", "jello", "yellow", "hello world"] * 10_000
distances = fuzzgpu.levenshtein_batch("hello", candidates)
jw_scores = fuzzgpu.jaro_winkler_batch("hello", candidates, p=0.1)
nw_scores = fuzzgpu.needleman_wunsch_affine_batch(
"AGTACGCA", candidates, match=2, mismatch=-1, gap_open=-3, gap_extend=-1
)
# ── Cross-product distance matrix ─────────────────────────────────────────────
matrix = fuzzgpu.levenshtein_cdist(["abc", "def", "xyz"], ["abd", "axy", "def"])
# ── Zero-allocation outputs (write into preallocated numpy arrays) ────────────
import numpy as np
out_u32 = np.empty(len(candidates), dtype=np.uint32)
out_f64 = np.empty(len(candidates), dtype=np.float64)
mat_u32 = np.empty((3, 3), dtype=np.uint32)
fuzzgpu.levenshtein_batch_into("hello", candidates, out_u32)
fuzzgpu.jaro_winkler_batch_into("hello", candidates, out_f64)
fuzzgpu.levenshtein_cdist_into(["abc", "def", "xyz"], ["abd", "axy", "def"], mat_u32)
# ── Global sequence alignment (Gotoh 1982 affine gap) ────────────────────────
score = fuzzgpu.needleman_wunsch_affine("AGTACGCA", "TATGC", 2, -1, -3, -1)
# ── Fuzzy ratios (rapidfuzz-compatible) ──────────────────────────────────────
from fuzzgpu.fuzz import (
ratio, partial_ratio, partial_ratio_alignment,
token_sort_ratio, token_set_ratio,
partial_token_sort_ratio, partial_token_set_ratio,
QRatio, WRatio,
)
ratio("fuzzy was a bear", "fuzzy was a bear") # 100.0
partial_ratio("hello", "oh hello there") # 100.0
score, src, dst, length = partial_ratio_alignment("hello", "oh hello there")
# (100.0, 0, 3, 5) ← window starts at char 3 of the longer string
token_sort_ratio("new york mets", "mets new york") # 100.0
token_set_ratio("fuzzy was a bear", "fuzzy bear") # 100.0
# ── Alignment helpers (rapidfuzz-compatible) ──────────────────────────────────
from fuzzgpu.distance import Levenshtein
ops = fuzzgpu.editops("kitten", "sitting") # top-level alias
codes = fuzzgpu.opcodes("kitten", "sitting")
# ── Search ────────────────────────────────────────────────────────────────────
from fuzzgpu.fuzz import extract, extractOne
best = extractOne("hellp", ["hello", "world", "help"], score_cutoff=50.0)
# ("help", 88.88888888888889, 2)
top_3 = extract("apple", ["apply", "ape", "banana", "applesauce"],
score_cutoff=50.0, limit=3)
# ── rapidfuzz.process-compatible API ─────────────────────────────────────────
from fuzzgpu.process import extract, extractOne, cdist
matrix = cdist(["hello", "world"], ["hallo", "wurld"]) # uses GPU/Rayon
# ── distance submodule (rapidfuzz.distance-compatible) ───────────────────────
from fuzzgpu.distance import Levenshtein, DamerauLevenshtein, Jaro, JaroWinkler
from fuzzgpu.distance import Hamming, OSA, Indel
Levenshtein.distance("kitten", "sitting") # 3
Levenshtein.normalized_similarity("kitten", "sitting") # 0.571...
Levenshtein.similarity(" abc ", "abc", processor=str.strip) # 3
DamerauLevenshtein.distance("ca", "abc") # 2 (unrestricted)
OSA.distance("ca", "abc") # 3 (OSA / rapidfuzz-compatible)
JaroWinkler.similarity("MARTHA", "MARHTA", prefix_weight=0.1) # 0.9611...
# ── Hardware diagnostics ──────────────────────────────────────────────────────
print(fuzzgpu.gpu_info()) # "Intel(R) Iris(R) Xe Graphics (Vulkan)"
print(fuzzgpu.hardware_info()) # adapter, threshold, last routing stats
fuzzgpu.set_gpu_threshold(100) # force GPU for batches >= 100 pairs
fuzzgpu.set_gpu_threshold(None) # restore auto-selection
fuzzgpu.set_cpu_only(True) # force CPU-only mode
Rust API
[dependencies]
fuzzgpu-core = "0.1.6" # GPU + CPU fallback
# fuzzgpu-core = { version = "0.1.6", default-features = false } # CPU-only
use fuzzgpu_core::levenshtein::gpu_ext::GpuLevenshteinKernel;
fn main() -> fuzzgpu_core::Result<()> {
let kernel = GpuLevenshteinKernel::get()?;
// Batch
let pairs = vec![("kitten", "sitting"), ("hello", "hullo")];
let distances = kernel.compute(&pairs)?; // [3, 1]
// Cross-product matrix
let matrix = kernel.compute_matrix(&["abc", "def"], &["abc", "xyz"])?;
// Multi-op batch (one GPU dispatch + readback amortized across all ops)
let mut batch = kernel.batch();
batch.add(&pairs);
batch.add(&[("foo", "bar"), ("test", "taste")]);
let results = batch.execute()?; // Vec<Vec<u32>>
Ok(())
}
Available GPU kernels: GpuLevenshteinKernel, GpuJaroKernel, GpuNeedlemanAffineKernel, GpuDamerauKernel.
WebAssembly
cd crates/fuzzgpu-wasm
wasm-pack build --target web --release
import init, {
levenshtein_distance, jaro_winkler, ratio, extract,
needleman_wunsch, needleman_wunsch_affine,
} from './pkg/fuzzgpu_wasm.js';
await init();
levenshtein_distance('kitten', 'sitting'); // 3
jaro_winkler('MARTHA', 'MARHTA', 0.1); // 0.9611...
// Needleman-Wunsch scores are i64 → JavaScript BigInt
needleman_wunsch('AGTACGCA', 'TATGC', 2n, -1n, -2n); // 1n
needleman_wunsch_affine('AGTACGCA', 'TATGC', 2n, -1n, -3n, -1n); // -2n
Technical Architecture
Execution pipeline
┌─────────────────────────┐
│ User Query / API │
└────────────┬────────────┘
│
Batch size / dataset assessment
│
┌──────────────────────┴──────────────────────┐
▼ ▼
Small batches (< threshold) Large batches (≥ threshold)
│ │
┌───────────────────┐ ┌─────────────────────────────┐
│ Rayon Parallel │ │ wgpu WebGPU Compute │
│ Myers bit-vector │ │ WGSL shaders │
│ AVX512/AVX2/NEON │ │ Metal / Vulkan / DX12 │
└───────────────────┘ └─────────────────────────────┘
Key design points
- Myers (1999) bit-vector — O(n) Levenshtein for patterns ≤ 64 chars, zero inner DP loop. Vectorized with AVX512 (8 texts/vector), AVX2 (4), NEON (2), portable scalar fallback. ISA detected at runtime via cached CPUID; override with
FUZZGPU_SIMD=portable|neon|avx2|avx512. - Unrestricted Damerau-Levenshtein — Full Lowrance-Wagner (1975) with non-adjacent transpositions. GPU shader keeps the full DP matrix in workgroup shared memory (≤ 32 chars ASCII).
- Gotoh (1982) affine gaps — 3-state recurrence, O(n) memory. GPU shader computes in f32; automatically routes to CPU when scoring parameters exceed f32 exact range (2²⁴).
- WGSL shaders require no adapter features — bit-vectors implemented as u32×2 pairs (no
SHADER_INT64), works on every WebGPU backend including browsers and integrated GPUs. - Metric-aware routing — iGPUs auto-route Jaro/Damerau to CPU (where AVX2 SIMD wins); discrete GPUs dispatch above a scaled threshold.
hardware_info()shows every routing decision. - Dispatch lock — serializes GPU calls across threads to work around
gfx-rs/wgpu#10085(heap corruption under ≥3 concurrent dispatchers on Intel iGPUs). - Zero-copy Python bindings —
Bound<PyString>pointers, noVec<String>copies;*_intoAPIs write directly into caller-supplied numpy arrays.
GPU kernels
| Kernel | Algorithm | Max length | Notes |
|---|---|---|---|
levenshtein.wgsl |
Standard DP | 256 chars | General path |
levenshtein_short.wgsl |
SLM row DP | 64 chars | Transposed layout, no register spill |
levenshtein_myers.wgsl |
Myers bit-vector | 64 chars | Shared Peq per workgroup, 2×u32 bitmask |
levenshtein_cdist_myers.wgsl |
Row-wise Myers | 64 chars | One workgroup per matrix row |
levenshtein_matrix.wgsl |
2D DP grid | 256 chars | O(N+M) data upload |
jaro.wgsl |
Bitmap matcher | 128 chars | 128-bit registers, transposed layout |
jaro_matrix.wgsl |
2D Jaro grid | 128 chars | O(N+M) data upload |
damerau.wgsl |
Lowrance-Wagner | 32 chars ASCII | Full matrix in SLM, non-adjacent transpositions |
damerau_matrix.wgsl |
2D Damerau grid | 32 chars ASCII | Same |
needleman_affine.wgsl |
Gotoh serial | 128 chars | f32 scores, one thread per pair |
needleman_wavefront.wgsl |
Gotoh wavefront | 128 chars | Anti-diagonal parallel, O(m+n) steps |
Project Structure
fuzzgpu/
├── crates/
│ ├── fuzzgpu-core/ # Core Rust engine + GPU shaders
│ ├── fuzzgpu-python/ # PyO3 Python extension
│ └── fuzzgpu-wasm/ # wasm-bindgen WebAssembly module
├── python/fuzzgpu/ # Python package wrapper + type stubs
│ ├── distance/ # rapidfuzz.distance-compatible modules
│ ├── fuzz.py # rapidfuzz.fuzz-compatible scorers
│ └── process.py # rapidfuzz.process-compatible helpers
├── fuzz/ # libFuzzer targets + stable self-harness
├── benchmarks/ # Comparative benchmark scripts
├── tests/ # Python pytest suite (174 tests)
└── docs/GPU_TESTING.md # Fault injection & GPU test conventions
Building from Source
# Prerequisites: Rust 1.87+, Python 3.10+, maturin
git clone https://github.com/kuntal-devrat/fuzzgpu.git
cd fuzzgpu
maturin develop --release
pytest tests/ -v
cargo test --workspace
Environment Variables
| Variable | Effect |
|---|---|
FUZZGPU_USE_CPU |
Force CPU-only mode |
FUZZGPU_FORCE_GPU |
Error (not fallback) on GPU failure in Python |
FUZZGPU_DEBUG |
Log GPU→CPU fallback decisions |
FUZZGPU_SIMD |
Force ISA: portable|neon|avx2|avx512 |
FUZZGPU_READBACK_TIMEOUT_MS |
GPU readback timeout (default 10000 ms) |
FUZZGPU_SKIP_DISPATCH_LOCK |
Bypass serialization lock (repro only) |
FUZZGPU_REQUIRE_GPU |
In tests: fail instead of skip when no GPU |
WGPU_BACKEND |
Force wgpu backend: vulkan|metal|dx12 |
PROPTEST_CASES |
Override proptest case count |
License
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distributions
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file fuzzgpu-0.1.6.tar.gz.
File metadata
- Download URL: fuzzgpu-0.1.6.tar.gz
- Upload date:
- Size: 435.8 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via:
maturin/1.14.1
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
f0e59715cabe375b0bbd9e9b3e8fe60c3847e805637ef2a76fa2618227e12d2b
|
|
| MD5 |
af8e669fb87fa320a85f7807d9041dd6
|
|
| BLAKE2b-256 |
ea39f246d149b09a228df3a49a475f4567c0338cee542981b3c1848662da260c
|
File details
Details for the file fuzzgpu-0.1.6-cp310-abi3-win_amd64.whl.
File metadata
- Download URL: fuzzgpu-0.1.6-cp310-abi3-win_amd64.whl
- Upload date:
- Size: 3.3 MB
- Tags: CPython 3.10+, Windows x86-64
- Uploaded using Trusted Publishing? No
- Uploaded via:
maturin/1.14.1
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
e715aafe3bff4390b26b675dde769d707a20ff2fc27a7a52029880b6eaaac7c9
|
|
| MD5 |
6e3df5516a31159a4ed793813623808d
|
|
| BLAKE2b-256 |
16d926cb544c62600356c042cde39ce770950e1789adc71913ed1d950a1435fe
|
File details
Details for the file fuzzgpu-0.1.6-cp310-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl.
File metadata
- Download URL: fuzzgpu-0.1.6-cp310-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
- Upload date:
- Size: 3.4 MB
- Tags: CPython 3.10+, manylinux: glibc 2.17+ x86-64
- Uploaded using Trusted Publishing? No
- Uploaded via:
maturin/1.14.1
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
93956e352a5f974ca5721f21fb9e9fd8ea42361b49b3750a2bd99afd4c9a90a1
|
|
| MD5 |
2aaefa9dd6dc1a769a2cb2ed4fc1c288
|
|
| BLAKE2b-256 |
a646901cda4d739bbdebe5b4301f21986857feb89445273db35c8aa73dfc003e
|
File details
Details for the file fuzzgpu-0.1.6-cp310-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl.
File metadata
- Download URL: fuzzgpu-0.1.6-cp310-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl
- Upload date:
- Size: 3.4 MB
- Tags: CPython 3.10+, manylinux: glibc 2.17+ ARM64
- Uploaded using Trusted Publishing? No
- Uploaded via:
maturin/1.14.1
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
023e61476e8e9da1b21c7132c26ca856564f2b3ad098df30c900d3934c377ff2
|
|
| MD5 |
acbb5e893cee3e8b268c70ddd2db89d5
|
|
| BLAKE2b-256 |
8c912a607fa1926c71a229e1e9ae05954df13269eb906844649d2f9cbab87a9c
|
File details
Details for the file fuzzgpu-0.1.6-cp310-abi3-macosx_11_0_arm64.whl.
File metadata
- Download URL: fuzzgpu-0.1.6-cp310-abi3-macosx_11_0_arm64.whl
- Upload date:
- Size: 2.7 MB
- Tags: CPython 3.10+, macOS 11.0+ ARM64
- Uploaded using Trusted Publishing? No
- Uploaded via:
maturin/1.14.1
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
08e47c1c0d4cf12c994bb5424ead01eb135b5d01f8de2a58f93f1a88902aba8d
|
|
| MD5 |
349a29c394ba8d1de7ac365c963583ec
|
|
| BLAKE2b-256 |
64e712cf78d5501546ed43e9b1403e25bec6b88cd91ed2c0943a0a87a6fdf87b
|
File details
Details for the file fuzzgpu-0.1.6-cp310-abi3-macosx_10_12_x86_64.whl.
File metadata
- Download URL: fuzzgpu-0.1.6-cp310-abi3-macosx_10_12_x86_64.whl
- Upload date:
- Size: 2.8 MB
- Tags: CPython 3.10+, macOS 10.12+ x86-64
- Uploaded using Trusted Publishing? No
- Uploaded via:
maturin/1.14.1
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
3bc653ec25cfe0ca2d2952fa52c8cf841edfa25ec3b38629147da78bf973f7fa
|
|
| MD5 |
e3443f6f04ee86a4adb5b7125c6137cd
|
|
| BLAKE2b-256 |
dc11812f0e80a62ca6d28353a64dfbec66121bd82453b4384f0f72bb1b87261e
|