Skip to content

refactor(semantic): never fuse multiply-add in the semantic scores - #168

Merged
ata-sesli merged 1 commit into
mainfrom
refactor/never-fuse
Nov 11, 2026
Merged

ata-sesli merged 1 commit into
mainfrom
refactor/never-fuse

Conversation

@ata-sesli

Copy link
Copy Markdown
Member

What does this PR do?

Makes the semantic scores identical on every platform by never fusing a * b + c into one fused multiply-add (FMA) instruction.

The problem. A fused multiply-add rounds once instead of twice, so its result can differ from a separate multiply and add in the last bit. The C code never chose either way; the compiler did:

Release build C fused?
macOS arm64 (Apple clang) yes
Linux x86_64 and arm64 (gcc, -std=c11) no
x86_64 macOS / Windows (baseline CPU, no FMA instruction) no

The Zig ports then made two different choices. src/semantic/fp.zig (#166, also used in pass_semantic_edges.zig from #167) fuses wherever the CPU has FMA, copying clang. That changed Linux arm64 compared with the C release. The store quantizer (#165) never fuses, which changed macOS arm64.

The effect. Scores move by about 1e-6. That matters only where a score sits right at a threshold: a SEMANTICALLY_RELATED edge at exactly 0.75, or a dot product of almost exactly zero when vectors are bucketed by sign. In those cases two machines indexing the same repository can disagree on an edge.

The change. One rule everywhere: never fuse.

  • fp.zig is removed. Its call sites in semantic.zig, rotsq.zig and pass_semantic_edges.zig become plain a * b + c (or x += a * b), with the same operand order.
  • Zig never fuses on its own, so every platform computes the same values.
  • The store quantizer already follows this rule and is unchanged.
  • The rule is stated at the top of semantic.zig.

Why not always fuse? Release builds target the baseline x86_64 CPU (-mcpu=baseline), which has no FMA instruction. There, @mulAdd becomes a software fma call inside the semantic hot loops.

Behavior change. Compared with the previous release, macOS arm64 and Windows arm64 can differ by one rounding step. In rare cases that changes an edge whose score sits exactly at the threshold. Linux and x86_64 builds compute what the C release computed.

Verification

  • libcbm_zig.a builds natively on macOS arm64 (zig build-lib -O ReleaseFast, as in Makefile.cbm).
  • zig fmt --check passes on the changed files.
  • scripts/check-build-sources.sh passes.

Not run locally: the test suites, the cross-target builds and a cross-platform edge comparison. CI covers the tests and builds.

Checklist

  • Every commit is signed off (git commit -s)
  • Tests pass locally (make -f Makefile.cbm test): not run locally; CI runs the suites
  • Lint passes (make -f Makefile.cbm lint-ci): not run
  • New behavior is covered by a test: the existing semantic suites cover these functions; no new test

The C semantic code never asked for fused multiply-add, but compilers
decided for it: Apple clang fused `a * b + c` on arm64, while the Linux
release (gcc -std=c11) and the baseline-CPU x86_64 builds did not. The
semantic scores therefore differed in the last bit between platforms,
which can flip a SEMANTICALLY_RELATED edge whose score sits exactly at
the threshold.

The Zig port (#166) copied clang's choice through fp.mulAdd, which fuses
wherever the CPU has FMA. That kept macOS arm64 unchanged but changed
Linux arm64, and the store quantizer (#165) never fused at all.

Use one rule everywhere instead: never fuse. Zig does not fuse on its
own, so `a * b + c` is written as-is and every platform computes the
same scores. fp.zig is removed; its call sites in semantic.zig,
rotsq.zig and pass_semantic_edges.zig become plain expressions with the
same operand order. Fusing everywhere was not an option: release builds
target the baseline x86_64 CPU, which has no FMA instruction, so @mulAdd
would become a software fma call in the hot loops.

Signed-off-by: ata-sesli <atasesli@protonmail.com>
@ata-sesli
ata-sesli merged commit 3939476 into main Nov 11, 2026
10 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant