knowledge: den Polyfill in den Browser bringen — wasm32 SIMD128, und die zwei Arten, auf die es still nichts tut - #278
Conversation
.claude/knowledge/wasm-simd-consumer-guide.md -- wie ein Consumer ndarray::simd auf wasm32 tatsaechlich bekommt, und die zwei Arten, auf die es still nichts tut. Warum ausgerechnet wasm: auf x86 und ARM ist das Polyfill-Versprechen fast gratis, weil ein Autovektorisierer Register hat, auf die er zielen kann -- eine Skalarschleife wird oft zufaellig SIMD und niemand merkt den Unterschied. Auf wasm32 gibt es OHNE +simd128 ueberhaupt keine Vektorregister. Der Browser ist also der eine Ort, an dem "wir verlassen uns auf LLVM" bis zum Skalar durchfaellt, ohne Fehler und ohne Warnung. Das Rezept, mit den zwei Stolpersteinen, die je einen Build kosten wenn man sie raet: der Default-Branch ist master, nicht main; und default-features = false entfernt simd, weil das Modul hinter std liegt. Die zwei stillen Fehlschlaege: (a) das Flag fehlt -- Skalar-Fallback, korrekte Ergebnisse, null Vektoren; (b) der Code ist gar nicht im Artefakt, weil ein cdylib nur exportierte Items behaelt. Gemessener Fall: 2 SIMD-Instruktionen und null Layout-Symbole im ausgelieferten Modul, waehrend derselbe Code als rlib 800 in einer Funktion trug. Und die Mess-Falle, die ich selbst gestellt habe: eine Funktion NICHT ueber ein awk-Fenster um ihren Namen isolieren. Das ist keine Funktionsgrenze, und die rlib steckt voller ndarray-eigener vektorisierter Funktionen -- die Zahl kann fast vollstaendig fremder Code sein. Symboltabelle fragen (llvm-nm + --disassemble-symbols). Dazu die Tabelle der Semantik-Abweichungen des wasm32-Backends (mul_add ohne FMA, Baum- statt Folge-Reduktion, round-half-to-even, NaN in min/max), weil ein Parity-Test ueber mehrere Backends sie tolerieren muss -- relative Toleranz statt Gleichheit, und die Skalar-Referenz sollte dieselbe Funktion sein, die der Tail benutzt. Format nach dem Vorbild von vertical-simd-consumer-contract.md (READ BY / P0 TRIGGERS).
|
Important Review available on request
Reviews should be triggered manually for repositories with fewer than 10 stars. Select Trigger review above or comment ⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Pro Plus Run ID: Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
Bugbot couldn't run - usage limit reachedBugbot is counted against Cursor usage for this user or team, and this run hit a usage or spend limit. A user or team admin can review and increase usage limits in the Cursor dashboard. (requestId: serverGenReqId_27388446-b794-4060-b61a-e3a69b8c0bdf) |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 338325c535
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| or use the checked-in profile, which exists for exactly this: | ||
|
|
||
| ```bash | ||
| cargo build --target wasm32-unknown-unknown --config .cargo/config-wasm.toml |
There was a problem hiding this comment.
Point consumers at a config they actually own
When this recipe is followed from a consumer that declares ndarray as a Git dependency, .cargo/config-wasm.toml exists only inside ndarray's cached checkout, not at this path in the consumer. Cargo resolves the --config argument from the invoking workspace, so this alternative command fails unless the consumer independently copied the file; retain the RUSTFLAGS command or explicitly instruct consumers to create/check in an equivalent config.
Useful? React with 👍 / 👎.
| So the honest shape of a vector/scalar parity test is a **relative | ||
| tolerance**, not equality: | ||
|
|
||
| ```rust | ||
| assert!((a - b).abs() <= 1e-4 * b.abs().max(1.0)); |
There was a problem hiding this comment.
Use operation-aware cross-backend assertions
For the exact divergent inputs listed immediately above, this relative-tolerance assertion does not provide parity: a half-way round case differs by 1, and a wasm min/max producing NaN makes the comparison false even when the scalar backend intentionally returns the numeric operand. Consumers following this recipe will therefore write tests that fail on valid backend behavior; the guide should prescribe explicit handling or exclusion of ties and NaNs, reserving numerical tolerance for reduction/FMA rounding differences.
Useful? React with 👍 / 👎.
| and the scalar reference should be **the same function the tail uses** — | ||
| one definition, so the two cannot drift apart. | ||
|
|
||
| Enabling `relaxed-simd` restores a fused `mul_add` via `f32x4_relaxed_madd`, |
There was a problem hiding this comment.
Avoid promising fusion for relaxed SIMD
When a runtime selects the permitted unfused behavior for f32x4.relaxed_madd, enabling relaxed-simd does not restore fused semantics. The repository already records this explicitly in src/simd_ops.rs:969-971: fusion is implementation-defined and the same wasm binary may round differently across runtimes. Calling it fused can lead consumers to rely on FMA results or determinism that the backend does not guarantee.
Useful? React with 👍 / 👎.
.claude/knowledge/wasm-simd-consumer-guide.md— wie ein Consumerndarray::simdaufwasm32tatsächlich bekommt. Eine von drei Hälften; die Companions liegen in a2ui-rs (Rezept) und OGAR (Substrat).Warum ausgerechnet wasm ein eigenes Dokument verdient
Auf x86 und ARM ist das Polyfill-Versprechen fast gratis: ein Autovektorisierer hat Register, auf die er zielen kann, also wird eine Skalarschleife oft zufällig SIMD und niemand merkt den Unterschied.
Auf wasm32 gibt es ohne
+simd128überhaupt keine Vektorregister. Der Autovektorisierer kann nichts finden, egal wie gut die Schleife geformt ist. Der Browser ist damit der eine Ort, an dem „wir verlassen uns auf LLVM" bis zum Skalar durchfällt — ohne Fehler, ohne Warnung, mit korrekten Ergebnissen.Das macht wasm zugleich zum stärksten Argument für die Polyfill-Regel und zum leichtesten Ort, an dem man glaubt, ihr gefolgt zu sein.
Das Rezept, mit den zwei Stolpersteinen, die je einen Build kosten
master, nichtmain—branch = "main"scheitert mitcannot locate remote-tracking branch.default-features = falseallein entferntsimd: das Modul liegt hinterstd(#[cfg(feature = "std")] pub mod simd), und der Fehler (unresolved import ndarray::simd) nennt die Ursache nicht.Dazu:
[patch]für lokale Sibling-Arbeit gehört in.cargo/config.toml, nicht ins Manifest — ein Pfad im Manifest wandert in CI- und Container-Builds, die keinen Sibling haben.Die zwei stillen Fehlschläge
(a) Das Flag fehlt — Skalar-Fallback, korrekte Ergebnisse, null Vektoren. Das ist bewusst so (der Code muss auch ohne SIMD-Unterstützung funktionieren), und genau deshalb beschwert sich nichts.
(b) Der Code ist gar nicht im Artefakt. Ein
cdylibbehält nur exportierte Items. Gemessener Fall: 2 SIMD-Instruktionen und null Layout-Symbole im ausgelieferten Modul, während derselbe Code als rlib 800 in einer Funktion trug. Wenn eine wasm-SIMD-Messung nahe null herauskommt, erst prüfen ob der Code drin ist — dann erst auf das Flag schließen.Und die Mess-Falle, die ich selbst gestellt habe
Eine Funktion nicht über ein
awk-Fenster um ihren Namen isolieren. Das ist keine Funktionsgrenze, und die rlib steckt voller ndarray-eigener vektorisierter Funktionen — die Zahl kann fast vollständig fremder Code sein. Symboltabelle fragen (llvm-nm+--disassemble-symbols).Die Textfenster-Form gab 801 wo die symbol-genaue Antwort 800 ist. Aus Glück richtig — und eine Messung, die aus Glück stimmt, ist keine Messung.
Semantik-Abweichungen des wasm32-Backends
Als Tabelle, weil ein Parity-Test über mehrere Backends sie tolerieren muss:
mul_addohne FMA (zwei Rundungen, außer mitrelaxed-simd), Baum- statt Folge-Reduktion,round-half-to-even, NaN-Propagation inmin/max. Konsequenz: relative Toleranz statt Gleichheit, und die Skalar-Referenz sollte dieselbe Funktion sein, die der Tail benutzt — eine Definition, damit die beiden nicht driften können.Alles davon steht bereits in
src/simd_wasm.rs; hier ist es aus Consumer-Sicht zusammengezogen, mit der Test-Konsequenz daneben.Format nach dem Vorbild von
vertical-simd-consumer-contract.md(READ BY:/P0 TRIGGERS:). Nur Doku, kein Code.Generated by Claude Code