Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 6 additions & 3 deletions crates/ppvm-pauli-sum/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,14 @@ bon = "3.7.2"
approx = { version = "0.5.1", optional = true }
indexmap = { version = "2.11.4", optional = true }

# Native-only optional deps: dashmap pulls rayon (OS threads) and gxhash needs
# AES intrinsics — neither builds on wasm32. Pruned there; the matching features
# go inert (see the `not(target_arch = "wasm32")` gates on the config modules).
# Native-only optional dep: dashmap pulls rayon (OS threads), which doesn't build
# on wasm32. Pruned there; the feature goes inert (see the config module gates).
[target.'cfg(not(target_arch = "wasm32"))'.dependencies]
dashmap = { version = "6.1.0", features = ["rayon"], optional = true }

# gxhash only compiles with hardware AES enabled; pruned otherwise, which drops
# the gxhash configs (same condition on the config gates).
[target.'cfg(all(target_feature = "aes", any(target_arch = "x86_64", target_arch = "aarch64")))'.dependencies]
gxhash = { version = "3.5.0", features = ["deterministic"], optional = true }

[dev-dependencies]
Expand Down
14 changes: 11 additions & 3 deletions crates/ppvm-pauli-sum/examples/trotter_qubit_sweep.rs
Original file line number Diff line number Diff line change
Expand Up @@ -225,8 +225,16 @@ fn main() {
println!("{n},fxhash,{bytes},{fx_t:.6},{fx_len}");
eprintln!("n={n:3} bytes={bytes:2} fxhash {fx_t:8.4}s ({fx_len} terms)");

let (gx_t, gx_len) = sweep!(ByteGxHashF64, n, it, p);
println!("{n},gxhash,{bytes},{gx_t:.6},{gx_len}");
eprintln!("n={n:3} bytes={bytes:2} gxhash {gx_t:8.4}s ({gx_len} terms)");
// The gxhash configs need the `gxhash` feature and AES at compile time.
#[cfg(all(
feature = "gxhash",
target_feature = "aes",
Comment thread
Copilot marked this conversation as resolved.
any(target_arch = "x86_64", target_arch = "aarch64")
))]
{
let (gx_t, gx_len) = sweep!(ByteGxHashF64, n, it, p);
println!("{n},gxhash,{bytes},{gx_t:.6},{gx_len}");
eprintln!("n={n:3} bytes={bytes:2} gxhash {gx_t:8.4}s ({gx_len} terms)");
}
}
}
15 changes: 15 additions & 0 deletions crates/ppvm-pauli-sum/src/config/dashmap.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,11 @@ impl<const N: usize, C: Coefficient + Sync + Send, St: Strategy, W: PauliWordTra
}

/// `DashMap`-backed concurrent [`Config`] with `[u8; N]` storage and `gxhash`.
#[cfg(all(
feature = "gxhash",
target_feature = "aes",
any(target_arch = "x86_64", target_arch = "aarch64")
))]
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct ByteGxHash<
const N: usize,
Expand All @@ -36,6 +41,11 @@ pub struct ByteGxHash<
W: PauliWordTrait + Sync + Send = PauliWord<[u8; N], gxhash::GxBuildHasher>,
>(PhantomData<(C, St, W)>);

#[cfg(all(
feature = "gxhash",
target_feature = "aes",
any(target_arch = "x86_64", target_arch = "aarch64")
))]
impl<const N: usize, C: Coefficient + Sync + Send, St: Strategy, W: PauliWordTrait + Sync + Send>
Config for ByteGxHash<N, C, St, W>
{
Expand All @@ -50,4 +60,9 @@ impl<const N: usize, C: Coefficient + Sync + Send, St: Strategy, W: PauliWordTra
/// [`ByteFxHash`] specialised to `f64` coefficients.
pub type ByteFxHashF64<const N: usize, St = NoStrategy> = ByteFxHash<N, f64, St>;
/// [`ByteGxHash`] specialised to `f64` coefficients.
#[cfg(all(
feature = "gxhash",
target_feature = "aes",
any(target_arch = "x86_64", target_arch = "aarch64")
))]
pub type ByteGxHashF64<const N: usize, St = NoStrategy> = ByteGxHash<N, f64, St>;
22 changes: 17 additions & 5 deletions crates/ppvm-pauli-sum/src/config/indexmap.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,9 +29,13 @@ impl<const N: usize, C: Coefficient, St: Strategy, W: PauliWordTrait> Config

/// `IndexMap`-backed [`Config`] with `[u8; N]` storage and `gxhash`.
///
/// `gxhash` is AES-based and native-only, so this config is unavailable on
/// `wasm32`; use [`ByteFxHash`] there.
#[cfg(all(feature = "gxhash", not(target_arch = "wasm32")))]
/// `gxhash` needs hardware AES, so this config is only available when it is
/// enabled (e.g. `-C target-feature=+aes`); use [`ByteFxHash`] otherwise.
#[cfg(all(
feature = "gxhash",
target_feature = "aes",
any(target_arch = "x86_64", target_arch = "aarch64")
))]
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct ByteGxHash<
const N: usize,
Expand All @@ -40,7 +44,11 @@ pub struct ByteGxHash<
W: PauliWordTrait = PauliWord<[u8; N], gxhash::GxBuildHasher>,
>(PhantomData<(C, St, W)>);

#[cfg(all(feature = "gxhash", not(target_arch = "wasm32")))]
#[cfg(all(
feature = "gxhash",
target_feature = "aes",
any(target_arch = "x86_64", target_arch = "aarch64")
))]
impl<const N: usize, C: Coefficient, St: Strategy, W: PauliWordTrait> Config
for ByteGxHash<N, C, St, W>
{
Expand All @@ -59,7 +67,11 @@ pub type ByteFxHashF64<
Wd = PauliWord<[u8; N], fxhash::FxBuildHasher>,
> = ByteFxHash<N, f64, St, Wd>;
/// [`ByteGxHash`] specialised to `f64` coefficients.
#[cfg(all(feature = "gxhash", not(target_arch = "wasm32")))]
#[cfg(all(
feature = "gxhash",
target_feature = "aes",
any(target_arch = "x86_64", target_arch = "aarch64")
))]
pub type ByteGxHashF64<
const N: usize,
St = NoStrategy,
Expand Down
6 changes: 4 additions & 2 deletions crates/ppvm-pauli-sum/src/config/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,11 @@ pub mod dashmap;
pub mod indexmap;

/// Pre-built configs using `gxhash` — fast on platforms with AES
/// hardware acceleration. Requires the `gxhash` feature.
/// hardware acceleration. Requires the `gxhash` feature and AES enabled at
/// compile time (e.g. `-C target-feature=+aes`).
#[cfg(all(
feature = "gxhash",
any(target_arch = "x86", target_arch = "x86_64", target_arch = "aarch64")
target_feature = "aes",
any(target_arch = "x86_64", target_arch = "aarch64")
))]
pub mod gxhash;
6 changes: 3 additions & 3 deletions crates/ppvm-pauli-word/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,9 @@ itertools = "0.14.0"
serde = { version = "1.0.228", features = ["derive"], optional = true }
bincode = { version = "2.0.1", optional = true }

# gxhash needs AES intrinsics and does not build on wasm32; pruned there (the
# `gxhash` feature goes inert). No non-test code in this crate names the crate.
[target.'cfg(not(target_arch = "wasm32"))'.dependencies]
# gxhash only compiles with hardware AES enabled; pruned otherwise (the `gxhash`
# feature goes inert). No non-test code in this crate names the crate.
[target.'cfg(all(target_feature = "aes", any(target_arch = "x86_64", target_arch = "aarch64")))'.dependencies]
gxhash = { version = "3.5.0", features = ["deterministic"], optional = true }

[features]
Expand Down
10 changes: 6 additions & 4 deletions crates/ppvm-tableau-sum/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,15 @@ rand = "0.10.1"
smallvec = "1.15"
ppvm-pauli-word = { version = "0.1.0", path = "../ppvm-pauli-word" }

# Native-only deps: gxhash (AES, used for the word_fingerprint hasher — see the
# target-gated alias in src/storage/mod.rs) and rayon (OS threads). Pruned on
# wasm32; fxhash backs the fingerprint there.
# Native-only dep: rayon (OS threads). Pruned on wasm32.
[target.'cfg(not(target_arch = "wasm32"))'.dependencies]
gxhash = "3.5.0"
rayon = { version = "1", optional = true }

# gxhash backs the word_fingerprint hasher but only compiles with hardware AES
# enabled; fxhash is used otherwise (same condition in src/storage/mod.rs).
[target.'cfg(all(target_feature = "aes", any(target_arch = "x86_64", target_arch = "aarch64")))'.dependencies]
gxhash = "3.5.0"

[dev-dependencies]
criterion = "0.8.2"
rayon = "1"
Expand Down
26 changes: 19 additions & 7 deletions crates/ppvm-tableau-sum/src/storage/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,17 @@ pub use entry_store::{Branch, EntryStore};
use fxhash::FxHashMap;
use ppvm_traits::traits::Clifford;

// Hasher for the structural `word_fingerprint`. gxhash (AES-based) is fastest on
// native and exposes a `gxhash64` bulk free function, but it needs hardware AES
// and does not build on wasm32, so fall back to fxhash there. The fingerprint is
// Hasher for the structural `word_fingerprint`. gxhash (AES-based) is fastest and
// exposes a `gxhash64` bulk free function, but it only compiles with hardware AES
// enabled (never on wasm32), so fall back to fxhash otherwise. The fingerprint is
// a transient in-memory dedup key — collisions are resolved by
// `structurally_equal`, and it is never persisted or compared across builds — so
// the hasher may differ per target without affecting results.
use bitvec::view::{BitView, BitViewSized};
#[cfg(target_arch = "wasm32")]
#[cfg(not(all(
target_feature = "aes",
any(target_arch = "x86_64", target_arch = "aarch64")
)))]
use fxhash::FxHasher as FingerprintHasher;
use num::{
Complex, One, PrimInt, Zero,
Expand All @@ -27,7 +30,10 @@ use ppvm_tableau::{
data::GeneralizedTableau, sparsevec::SparseVector, tableau_index::TableauIndex,
};
use ppvm_traits::config::Config;
#[cfg(target_arch = "wasm32")]
#[cfg(not(all(
target_feature = "aes",
any(target_arch = "x86_64", target_arch = "aarch64")
)))]
use std::hash::Hasher;
use std::ops::AddAssign;

Expand Down Expand Up @@ -75,11 +81,17 @@ where
buf.extend_from_slice(bytemuck::bytes_of(&row.word.zbits.data));
}

#[cfg(not(target_arch = "wasm32"))]
#[cfg(all(
target_feature = "aes",
any(target_arch = "x86_64", target_arch = "aarch64")
))]
{
gxhash::gxhash64(&buf, 0)
}
#[cfg(target_arch = "wasm32")]
#[cfg(not(all(
target_feature = "aes",
any(target_arch = "x86_64", target_arch = "aarch64")
)))]
{
let mut hasher = FingerprintHasher::default();
hasher.write(&buf);
Expand Down
16 changes: 10 additions & 6 deletions crates/ppvm-traits/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -10,17 +10,21 @@ fxhash = "0.2.1"
num = "0.4.3"
indexmap = { version = "2.11.4", optional = true }

# Native-only optional deps: gxhash needs AES intrinsics, dashmap/rayon need OS
# threads, and ahash pulls getrandom — none build on wasm32. They are pruned on
# wasm, where the matching features go inert (see the `not(target_arch =
# "wasm32")` code gates), so a wasm build silently drops these acceleration
# paths. On 64-bit native nothing changes.
# Native-only optional deps: dashmap/rayon need OS threads and ahash pulls
# getrandom — none build on wasm32. They are pruned on wasm, where the matching
# features go inert (see the `not(target_arch = "wasm32")` code gates), so a
# wasm build silently drops these acceleration paths.
[target.'cfg(not(target_arch = "wasm32"))'.dependencies]
ahash = { version = "0.8.12", optional = true }
dashmap = { version = "6.1.0", features = ["rayon"], optional = true }
gxhash = { version = "3.5.0", features = ["deterministic"], optional = true }
rayon = { version = "1.11.0", optional = true }

# gxhash only compiles with hardware AES enabled (e.g. `-C target-feature=+aes`).
# Without it the `gxhash` feature goes inert and fxhash is used instead; the
# same condition gates the code. Keep it in sync across the crates.
[target.'cfg(all(target_feature = "aes", any(target_arch = "x86_64", target_arch = "aarch64")))'.dependencies]
gxhash = { version = "3.5.0", features = ["deterministic"], optional = true }

[dev-dependencies]
insta = { version = "1.43.2", features = ["yaml"] }

Expand Down
12 changes: 10 additions & 2 deletions crates/ppvm-traits/src/traits/hash.rs
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,11 @@ impl HashFinalize for fxhash::FxBuildHasher {
// max bucket 6, essentially ideal, vs fxhash's 2257 at 64 qubits), so the
// identity default is exactly right — folding would only pay the tag/bucket
// coupling cost above with no distribution benefit.
#[cfg(all(feature = "gxhash", not(target_arch = "wasm32")))]
#[cfg(all(
feature = "gxhash",
target_feature = "aes",
any(target_arch = "x86_64", target_arch = "aarch64")
))]
impl HashFinalize for gxhash::GxBuildHasher {}

#[cfg(test)]
Expand Down Expand Up @@ -99,7 +103,11 @@ mod tests {
}
}

#[cfg(all(feature = "gxhash", not(target_arch = "wasm32")))]
#[cfg(all(
feature = "gxhash",
target_feature = "aes",
any(target_arch = "x86_64", target_arch = "aarch64")
))]
#[test]
fn gxhash_never_folds() {
// gxhash already distributes its low bits, so it is the identity at
Expand Down
21 changes: 12 additions & 9 deletions docs/src/pages/develop.astro
Original file line number Diff line number Diff line change
Expand Up @@ -159,11 +159,11 @@ cargo bench -p ppvm-tableau --bench micro
cargo bench --bench micro -- "gates/single-qubit/h"</code></pre>

<p>
Rust edition 2024. On x86 the default hasher (gxhash) needs
AES/SSE2 target features; this repo sets them for x86_64 in
<code>.cargo/config.toml</code>, and CI does the same. On non-x86 hosts,
build with
<code>--no-default-features --features=indexmap,ahash</code> or similar.
Rust edition 2024. The fast hasher (gxhash) only compiles with AES
enabled at compile time; this repo enables it for x86_64 in
<code>.cargo/config.toml</code> (Apple Silicon has it by default), and CI
does the same. Without AES, the <code>gxhash</code> dependency is pruned
and fxhash is used instead (see the WebAssembly notes below).
</p>

<h3>WebAssembly (wasm32)</h3>
Expand All @@ -180,17 +180,20 @@ cargo build --target wasm32-unknown-unknown --workspace --exclude ppvm-python-na

<p>
The build is wasm-clean automatically. Native-only acceleration
dependencies — <code>gxhash</code> (AES intrinsics), <code>dashmap</code> →
<code>rayon</code> (OS threads), and <code>ahash</code> — live in
dependencies — <code>dashmap</code> → <code>rayon</code> (OS threads)
and <code>ahash</code> — live in
<code>[target.'cfg(not(target_arch = "wasm32"))'.dependencies]</code>
tables, so on wasm they are pruned and the matching features go inert
(the code that names those crates is gated with the same
<code>not(target_arch = "wasm32")</code>). The <code>fx64hash</code> configs
<code>not(target_arch = "wasm32")</code>). <code>gxhash</code> works the
same way but is gated on AES being enabled
(<code>all(target_feature = "aes", any(target_arch = "x86_64",
target_arch = "aarch64"))</code>), which wasm never has. The <code>fx64hash</code> configs
use native-word <code>[usize; N]</code> storage (<code>u64</code> on
64-bit, <code>u32</code> on wasm) since <code>bitvec</code> only implements
<code>BitStore</code> for <code>u64</code> on 64-bit pointer widths.
<code>ppvm-tableau-sum</code>'s structural fingerprint falls back from
<code>gxhash</code> to <code>fxhash</code> on wasm.
<code>gxhash</code> to <code>fxhash</code> whenever AES is unavailable.
</p>

<p>
Expand Down
5 changes: 3 additions & 2 deletions docs/src/pages/index.astro
Original file line number Diff line number Diff line change
Expand Up @@ -180,8 +180,9 @@ ppvm-tableau = {`{ git = "https://github.com/QuEraComputing/ppvm" }`}
ppvm-stim = {`{ git = "https://github.com/QuEraComputing/ppvm" }`}
ppvm-sym = {`{ git = "https://github.com/QuEraComputing/ppvm" }`}</code></pre>
<p class="install-pane-note">
Requires Rust edition 2024. On x86, set
<code>RUSTFLAGS="-C target-feature=+aes,+sse2"</code> — gxhash needs AES.
Requires Rust edition 2024. Set
<code>RUSTFLAGS="-C target-feature=+aes"</code> to use the faster
gxhash hasher; without AES, ppvm falls back to fxhash.
</p>
<a class="install-pane-link" href={`${base}/quickstart/rust`}>Continue to the Rust Quick Start →</a>
</section>
Expand Down
2 changes: 1 addition & 1 deletion skills/ppvm-usage/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -171,7 +171,7 @@ ppvm-stim = { git = "https://github.com/QuEraComputing/ppvm" } # for Stim e
ppvm-sym = { git = "https://github.com/QuEraComputing/ppvm" } # for symbolic propagation
```

On x86, set `RUSTFLAGS="-C target-feature=+aes,+sse2"` (gxhash needs AES). On other targets, build with `--no-default-features --features=indexmap,ahash` to drop gxhash.
Set `RUSTFLAGS="-C target-feature=+aes"` (or `-C target-cpu=native`) to use the faster gxhash hasher. Without AES enabled at compile time, gxhash is dropped automatically: the `gxhash` configs disappear and everything else falls back to fxhash.

### Pauli propagation

Expand Down
Loading