Skip to content

Build on arm64 and add a NEON path to the slot convert - #13

Merged
aferaudo merged 2 commits into
mainfrom
feat/arm64
Nov 2, 2026
Merged

aferaudo merged 2 commits into
mainfrom
feat/arm64

Conversation

@aferaudo

@aferaudo aferaudo commented Oct 14, 2026 •

Copy link
Copy Markdown
Collaborator

Summary

  • The codelets build on arm64. With -target bpf, clang does not search the multiarch include directory, where Ubuntu keeps glibc's bits/ headers (/usr/include/aarch64-linux-gnu on arm64). Every codelet failed with 'bits/libc-header-start.h' file not found. codelets/Makefile.defs now adds the host's multiarch directory, as jbpf's own test codelets do.
  • NEON path in e3_slot_convert.h. The shared convert header had only an AVX2+F16C loop. Its build guard let aarch64 through as if a NEON loop existed, so on ARM the gNB-side helper silently ran the scalar convert. The new loop gives the same bytes as AVX2+F16C. The microbenchmark builds on arm64 and hides NEON in its scalar mode.
  • Paired with wineslab/ocudu-e3-private#5: the convert header is shared byte for byte, so merge both together. The controller, libe3, the verifier and the adaptive_cpu dApp already built on arm64 unchanged.

Type of change

  • Bug fix (scalar convert on aarch64)
  • New feature / enhancement (arm64 build)
  • New Service Model
  • Refactor (no behavior change)
  • Documentation
  • Test / CI / packaging
  • Other (explain):

Linked issue

None.

Mandatory test checklist

  • arm64 build on NVIDIA Grace (Neoverse-V2), Ubuntu 24.04, GCC 13.3, clang 18:
    • ./build.sh and the verifier build;
    • make -C codelets builds both codelets and they pass the verifier (632 and 345 instructions);
    • no AVX/F16C/scalar warnings.
  • NEON = AVX2+F16C: for all 65,536 bf16 values at 6 scales, the NEON output is byte-identical to the AVX2+F16C output.
  • Microbenchmark on Grace: vector vs scalar agreement … IDENTICAL. For 4 ports, NEON takes 33.9 µs and the scalar convert 331 µs.
  • Over the air on aarch64, with the paired ocudu PR:
    • Setup: Foxconn RU, n78, 100 MHz, 4T4R, a COTS phone, the adaptive_cpu dApp over JSON.
    • The codelet loads through jbpf's arm64 JIT, and the gNB helper logs convert path=NEON.
    • Slots 8, 9, 18 and 19 reach the dApp at 100/s each. dApp IQ age p50 is 0.1 ms (0.7 ms with scalar). The dApp check passed.
  • x86: not re-run on hardware. On x86, codelets/Makefile.defs now also adds /usr/include/x86_64-linux-gnu, and the AVX2 loop is unchanged apart from the macro that selects it. CI builds x86.
  • Provenance: tested on top of Send the gNB's uplink symbol mask in every L1-KPM indication #11. Rebased onto main with no conflicts; the changed files are identical.
  • VERSION, libe3.pc, new dependencies: N/A, none changed.

CI checklist

  • build and measure is green for the head commit (to check once CI has run).
  • Commit policy: check_commit_trailers.py --base origin/main --head HEAD passes (Assisted-by trailer).

Twin-repo coordination

  • This PR does not change the E3 wire protocol or public ABI, OR a paired PR exists in each affected twin repo (link below).

codelets/include/e3_slot_convert.h must stay byte-identical to ocudu's ocudu_janus/e3/e3_slot_convert.h: ocudu's configure compares them. Paired PR: wineslab/ocudu-e3-private#5.

Workflow confirmation

  • Linear, fast-forward-able descendant of main (fbdf325), with no merge commits.
  • Two commits, each with an imperative subject and a what/why body.
  • I have read and followed CONTRIBUTING.md (libe3's, applied to this repository).

🤖 Generated with Claude Code

With `-target bpf`, clang does not search the multiarch include directory,
where Debian/Ubuntu keep glibc's bits/*.h. On aarch64 that is
/usr/include/aarch64-linux-gnu, so every codelet failed with
"'bits/libc-header-start.h' file not found". Add the host's multiarch
directory to the include path when it exists, as jbpf's own test codelets
do.

Assisted-by: Claude Opus 5.5 <noreply@anthropic.com>
e3_slot_convert.h only had an AVX2+F16C loop. Its build guard exempted
aarch64 as if a NEON loop existed, but there was none, so on ARM the
gNB-side publish helper ran the scalar convert in the PHY RX thread for
every uplink slot (~0.6 ms per slot on Grace).

Add the NEON loop: widen 8 bf16 to fp32, scale, and narrow with
FCVTN/FCVTN2, which rounds to nearest-even like _mm256_cvtps_ph with
_MM_FROUND_TO_NEAREST_INT. The scalar function uses the same instruction
on aarch64. Over every bf16 value at six scales, the NEON output is
byte-identical to the AVX2+F16C output.

vector_path_name() gives the path for the startup log. The
microbenchmark hides NEON too in its scalar mode (E3_NO_NEON).

Assisted-by: Claude Opus 5.5 <noreply@anthropic.com>
@aferaudo aferaudo self-assigned this Oct 14, 2026
@aferaudo
aferaudo merged commit e6feffe into main Nov 2, 2026
2 checks passed
@aferaudo
aferaudo deleted the feat/arm64 branch November 2, 2026 15:54
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