Skip to content

Add runtime-selected 16-lane SHA-256 backend for Merkle hashing - #4157

Draft
masih wants to merge 1 commit into
masih/1789147125-lthash-simd-backendfrom
masih/1789163252-tmhash-simd-merkle
Draft

Add runtime-selected 16-lane SHA-256 backend for Merkle hashing#4157
masih wants to merge 1 commit into
masih/1789147125-lthash-simd-backendfrom
masih/1789163252-tmhash-simd-merkle

Conversation

@masih

@masih masih commented Sep 11, 2026

Copy link
Copy Markdown
Collaborator

Stacked on #4151. Tendermint's Merkle hashing (merkle.HashFromByteSlices: tx hashes, part sets, commit signatures, results, validator sets) hashes every leaf and every tree level as independent SHA-256 calls, which is the batch shape a multi-lane kernel wants, but crypto/sha256 already uses single-lane SHA-NI so the achievable win is smaller than the LtHash case and had to be measured rather than assumed.

This adds sei-tendermint/crypto/tmhash with the same runtime-selected backend pattern as the LtHash PR: SumBatch(prefix, msgs, out) is served by a default backend (a reused sha256.New(), always compiled) or, under GOEXPERIMENT=simd on a CPU with AVX-512F/VBMI/VBMI2, by a generated 16-lane archsimd.Uint32x16 SHA-256 kernel that loads, prefixes, pads and transposes sixteen messages in-register (VPERMB / VPERMT2D) and falls back to scalar for remainders and mixed lengths. SEI_TMHASH_BACKEND=default pins the portable path. HashFromByteSlices keeps its signature and, when a multi-lane backend is active and there are at least sixteen leaves, builds the tree level by level, pairing adjacent nodes and carrying an odd trailing node up, which yields exactly the RFC 6962 getSplitPoint shape; a differential test checks totals 1 to 70 against the recursive implementation and the tmhash tests check every backend against crypto/sha256 across block and padding boundaries.

Two Go 1.27 findings surfaced while benchmarking and are handled here. The compiler never emits VZEROUPPER after archsimd code, so the SHA-NI scalar path that followed ran several times slower with dirty ZMM state; the SIMD backend now calls a one-instruction assembly vzeroupper before handing off. Separately, with GOEXPERIMENT=simd on an AVX-512 machine the runtime's async preemption restores all 32 ZMM registers without VZEROUPPER, which made the pinned-default merkle benchmark 2 to 4x slower and noisy in the same binary; the CI job therefore takes the default column from a plain build and the SIMD column from the experiment build. That second effect is worth keeping in mind for any decision to ship a GOEXPERIMENT=simd binary, since it affects all legacy-SSE code in the process, not just this package.

Local numbers on an Intel Xeon Platinum 8559C (benchstat, n=8): the 16-lane kernel is 2.0x faster than SHA-NI on 1024 inner nodes (124 µs to 61 µs), 1.6x on 256-byte leaves and 1.3x on 1 KiB leaves, and the whole tree for 1024 x 32-byte leaves goes from 223 µs to 122 µs. This is a per-block cost of a few thousand hashes, so the wall-clock effect on a node is modest; the PR mainly shows the shape of the consensus-side integration. The SIMD hash backends workflow runs both packages' tests with and without the experiment and posts the benchstat table as a job summary and PR comment.

Tests: go test -race ./crypto/tmhash ./crypto/merkle with and without GOEXPERIMENT=simd, golangci-lint under both builds, make fmtcheck, and the benchmarks above.

Adds sei-tendermint/crypto/tmhash with a default crypto/sha256 backend and a
16-lane AVX-512 kernel built under goexperiment.simd, and routes
merkle.HashFromByteSlices through level-batched hashing when a multi-lane
backend is active. Output is byte-identical to the recursive tree.
@devin-ai-integration

Copy link
Copy Markdown
Contributor

🤖 Devin AI Engineer

I'll be helping with this pull request! Here's what you should know:

✅ I will automatically:

  • Address comments on this PR. Add '(aside)' to your comment to have me ignore it.
  • Look at CI failures and help fix them

Note: I can only respond to comments from users who have write access to this repository.

⚙️ Control Options:

  • Disable automatic comment, CI, and merge conflict monitoring

@github-actions

github-actions Bot commented Sep 11, 2026

Copy link
Copy Markdown

The latest Buf updates on your PR. Results from workflow Buf / buf (pull_request).

BuildFormatLintBreakingUpdated (UTC)
✅ passed✅ passed✅ passed✅ passedSep 11, 2026, 9:51 PM

@github-actions

Copy link
Copy Markdown

LtHash default vs SIMD (uci-default)

CPU: AMD EPYC 7R13 Processor

This runner CPU lacks AVX-512F + VBMI2, so only the default backend ran.

goos: linux
goarch: amd64
pkg: github.com/sei-protocol/sei-chain/sei-db/state_db/sc/flatkv/lthash
cpu: AMD EPYC 7R13 Processor
             │   default   │
             │   sec/op    │
Expand-16      2.776µ ± 0%
MixIn-16       343.7n ± 0%
MixOut-16      343.8n ± 0%
HashKV-16      3.118µ ± 0%
HashChunk-16   6.410m ± 0%
geomean        5.799µ

          │   default    │
          │     B/s      │
Expand-16   703.5Mi ± 0%

@codecov

codecov Bot commented Sep 11, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 93.33333% with 3 lines in your changes missing coverage. Please review.
✅ Project coverage is 65.41%. Comparing base (356d8e3) to head (39c0b09).

Files with missing lines Patch % Lines
sei-tendermint/crypto/tmhash/backend.go 88.88% 2 Missing ⚠️
sei-tendermint/crypto/merkle/tree.go 95.00% 1 Missing ⚠️
Additional details and impacted files

Impacted file tree graph

@@                           Coverage Diff                            @@
##           masih/1789147125-lthash-simd-backend    #4157      +/-   ##
========================================================================
- Coverage                                 65.48%   65.41%   -0.07%     
========================================================================
  Files                                      2090     2082       -8     
  Lines                                    157791   157360     -431     
========================================================================
- Hits                                     103325   102944     -381     
+ Misses                                    54325    54275      -50     
  Partials                                    141      141              
Flag Coverage Δ
sei-chain-pr 78.15% <93.33%> (?)
sei-db 74.50% <ø> (ø)
sei-db-state-db-pr ?

Flags with carried forward coverage won't be shown. Click here to find out more.

Files with missing lines Coverage Δ
sei-tendermint/crypto/tmhash/backend_default.go 100.00% <100.00%> (ø)
sei-tendermint/crypto/tmhash/backend_nosimd.go 100.00% <100.00%> (ø)
sei-tendermint/crypto/merkle/tree.go 94.54% <95.00%> (+0.25%) ⬆️
sei-tendermint/crypto/tmhash/backend.go 88.88% <88.88%> (ø)

... and 11 files with indirect coverage changes

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@github-actions

Copy link
Copy Markdown

tmhash / merkle default vs SIMD (uci-default)

CPU: AMD EPYC 7R13 Processor

This runner CPU lacks AVX-512F + VBMI + VBMI2, so only the default backend ran.

goos: linux
goarch: amd64
pkg: github.com/sei-protocol/sei-chain/sei-tendermint/crypto/merkle
cpu: AMD EPYC 7R13 Processor
                                                 │   default   │
                                                 │   sec/op    │
HashFromByteSlices/leaves=1024/leaf=32-16          260.2µ ± 1%
HashFromByteSlices/leaves=1024/leaf=512-16         574.7µ ± 0%
HashFromByteSlices/leaves=100/leaf=32-16           25.04µ ± 1%
HashFromByteSlicesBatched/leaves=1024/leaf=32-16   247.6µ ± 8%
geomean                                            174.5µ

                                                 │   default    │
                                                 │     B/s      │
HashFromByteSlices/leaves=1024/leaf=32-16          120.1Mi ± 1%
HashFromByteSlices/leaves=1024/leaf=512-16         870.1Mi ± 0%
HashFromByteSlices/leaves=100/leaf=32-16           121.9Mi ± 1%
HashFromByteSlicesBatched/leaves=1024/leaf=32-16   126.3Mi ± 9%
geomean                                            200.2Mi

pkg: github.com/sei-protocol/sei-chain/sei-tendermint/crypto/tmhash
                   │   default   │
                   │   sec/op    │
SumBatchInner-16     134.4µ ± 0%
SumBatchLeaf256-16   249.9µ ± 0%
SumBatchLeaf1K-16    694.4µ ± 0%
geomean              285.7µ

                   │    default    │
                   │      B/s      │
SumBatchInner-16      472.1Mi ± 0%
SumBatchLeaf256-16   1004.5Mi ± 1%
SumBatchLeaf1K-16     1.408Gi ± 0%
geomean               880.9Mi

@github-actions

Copy link
Copy Markdown

LtHash default vs SIMD (ubuntu-latest)

CPU: INTEL(R) XEON(R) PLATINUM 8573C

HashChunk is the end-to-end per-block path; vs base is simd relative to default.

goos: linux
goarch: amd64
pkg: github.com/sei-protocol/sei-chain/sei-db/state_db/sc/flatkv/lthash
cpu: INTEL(R) XEON(R) PLATINUM 8573C
            │   default    │                simd                │
            │    sec/op    │   sec/op     vs base               │
Expand-4       2.702µ ± 1%   1.131µ ± 0%  -58.16% (p=0.000 n=8)
MixIn-4       230.55n ± 0%   20.18n ± 0%  -91.24% (p=0.000 n=8)
MixOut-4      230.40n ± 0%   20.17n ± 1%  -91.25% (p=0.000 n=8)
HashKV-4       2.927µ ± 0%   1.169µ ± 1%  -60.06% (p=0.000 n=8)
HashChunk-4    6.015m ± 2%   2.491m ± 1%  -58.59% (p=0.000 n=8)
geomean        4.792µ        1.060µ       -77.87%

         │   default    │                 simd                  │
         │     B/s      │      B/s       vs base                │
Expand-4   722.8Mi ± 1%   1727.5Mi ± 1%  +138.99% (p=0.000 n=8)

@github-actions

Copy link
Copy Markdown

tmhash / merkle default vs SIMD (ubuntu-latest)

CPU: INTEL(R) XEON(R) PLATINUM 8573C

HashFromByteSlices is the end-to-end Merkle root; vs base is simd relative to default (SHA-NI).

goos: linux
goarch: amd64
pkg: github.com/sei-protocol/sei-chain/sei-tendermint/crypto/merkle
cpu: INTEL(R) XEON(R) PLATINUM 8573C
                                                │   default   │                simd                │
                                                │   sec/op    │   sec/op     vs base               │
HashFromByteSlices/leaves=1024/leaf=32-4          291.3µ ± 1%   160.1µ ± 1%  -45.04% (p=0.000 n=8)
HashFromByteSlices/leaves=1024/leaf=512-4         675.5µ ± 0%   473.1µ ± 1%  -29.97% (p=0.000 n=8)
HashFromByteSlices/leaves=100/leaf=32-4           28.58µ ± 1%   19.49µ ± 1%  -31.80% (p=0.000 n=8)
HashFromByteSlicesBatched/leaves=1024/leaf=32-4   269.3µ ± 0%   154.2µ ± 1%  -42.73% (p=0.000 n=8)
geomean                                           197.3µ        122.8µ       -37.73%

                                                │   default    │                 simd                 │
                                                │     B/s      │      B/s       vs base               │
HashFromByteSlices/leaves=1024/leaf=32-4          107.3Mi ± 1%    195.2Mi ± 5%  +81.94% (p=0.000 n=8)
HashFromByteSlices/leaves=1024/leaf=512-4         740.1Mi ± 0%   1056.8Mi ± 1%  +42.79% (p=0.000 n=8)
HashFromByteSlices/leaves=100/leaf=32-4           106.8Mi ± 1%    156.6Mi ± 1%  +46.63% (p=0.000 n=8)
HashFromByteSlicesBatched/leaves=1024/leaf=32-4   116.1Mi ± 0%    202.6Mi ± 1%  +74.61% (p=0.000 n=8)
geomean                                           177.1Mi         284.4Mi       +60.59%

pkg: github.com/sei-protocol/sei-chain/sei-tendermint/crypto/tmhash
                  │   default    │                 simd                  │
                  │    sec/op    │   sec/op     vs base                  │
SumBatchInner-4     156.31µ ± 1%   80.98µ ± 1%  -48.19% (p=0.000 n=16+8)
SumBatchLeaf256-4    295.5µ ± 1%   191.5µ ± 1%  -35.19% (p=0.000 n=16+8)
SumBatchLeaf1K-4     830.0µ ± 0%   620.8µ ± 1%  -25.21% (p=0.000 n=16+8)
geomean              337.2µ        212.7µ       -36.91%

                  │   default    │                  simd                   │
                  │     B/s      │      B/s       vs base                  │
SumBatchInner-4     406.1Mi ± 1%    783.8Mi ± 2%  +93.02% (p=0.000 n=16+8)
SumBatchLeaf256-4   849.2Mi ± 1%   1310.3Mi ± 1%  +54.29% (p=0.000 n=16+8)
SumBatchLeaf1K-4    1.178Gi ± 0%    1.575Gi ± 1%  +33.71% (p=0.000 n=16+8)
geomean             746.4Mi         1.155Gi       +58.50%

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant