Uh oh!
There was an error while loading. Please reload this page.
PROPOSAL (do not merge): what core/ means, and a naming scheme - #149
PROPOSAL (do not merge): what core/ means, and a naming scheme#149balbasty wants to merge 5 commits into
Conversation
Answers the two questions the owner asked together -- what moves from impl/kernels/ to core/, and how namespaces, kernels and internal helpers should be named -- plus the thread-count API question that follows from them. The recommended definition, which decides every borderline file: core/ holds every header that more than one layer depends on and that is not itself the computation of a named fastfields operation -- device- specialised or not. impl/kernels/ keeps only the per-element maths of a named operation. That admits atomic.h (device-specialised, but not an operation) and the parallel/threadpool group, and it excludes splinc.h (which IS the operation exported as spline_coeff). Ten files move, 4,262 lines; the closure is clean, so core/ never acquires a dependency on impl/kernels/. Two premises in the brief turned out not to hold, and both are recorded with the evidence: * core/ was said to be barred from opening the device namespace. core/autocast.h already opens FF_NAMESPACE_BEGIN(FF_DEVICE), and core/cuda_switch.h exists to branch on __CUDACC__. The real constraint is narrower: a core/ header must compile under both compilers and mean the right thing in each. No fourth directory is needed, and no core/cpu + core/cuda split. * core/dispatch.h says canUse32BitIndexMath comes from core/autocast.h. It is defined in impl/kernels/utils.h, which is why 17 files in src/ include the kernels layer directly. The dependency inversion this proposal repairs already exists, and is mis-documented. Findings that are independent of any decision here, worth acting on separately: impl/cpu/tetrahedron.h does not compile and is referenced by nothing; impl/kernels/vector/ (2,389 lines) is used only by a scratch target and duplicates the point hierarchy in distance/mesh_utils.h; and the CUDA backend launches all 39 kernels at the architectural maximum of 1024 threads per block while checking for no launch error anywhere, so a cudaErrorLaunchOutOfResources would be discarded silently. tools/move-core-headers.py carries the move with a self-verifying --check, following the pattern of rename-macros.py and dedup-dispatch-helpers.py. It does not hard-code an include delimiter -- it measures the tree's dominant one -- so it survives a rebase by being re-run rather than hand-merged. That was demonstrated rather than asserted: #146 and #143 landed mid-review, and the same script produced the angle-bracket spelling on the new base with no edit. Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016AjQcY78NgbagPSbPJRr6Z
This commit exists so the proposal's cost figures are measurements rather than estimates. It is the unedited output of tools/move-core-headers.py run on de288a9, nothing else. Drop it and the design in the preceding commit still stands. 10 headers moved, 4,262 lines 81 files rewritten, emitting <fastfields/...> on this base --check clean, idempotent, no dependency leak core/ goes 5 -> 15 files; impl/kernels/ goes 57 -> 47 and then holds only per-operation implementations. The one-line tools/test-baseline.sh probe update rides along here because the script does not touch that file: the gate tool probes for impl/kernels/bounds.h to validate tree layout and dies before running a single test if it is absent -- with a message that reads like a broken checkout. tools/consolidate.sh and the six-repo probe in the same script name the same paths and are deliberately left alone; they describe the frozen pre-consolidation layout. Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016AjQcY78NgbagPSbPJRr6Z
balbasty
commented
Aug 20, 2026
GateMeasured with Result — row-for-row identical to
13 cpu-lib suites, 59,886 checks, 0 failures, plus the hub's 2 suites / 14 checks.
What was verified on which tree — stated preciselyThat run was on the pre-rebase tree (base
Idempotent, and the closure check is what guarantees Generated by Claude Code |
…d why CI found something no amount of reading would have: the `lint (clang-format, changed lines)` job fails on any file move, because git-clang-format does not honour rename detection. It sees include/fastfields/core/bounds.h as a path absent from the merge base, treats the whole file as changed, and demands all 800 lines conform -- for files that predate the gate and have never been clang-formatted. Measured on the prototype: 4,232 of the 4,236 reformat lines it asks for are that artifact. Only 4 are attributable to anything this change does. Two script fixes, which shrink the diff and are right on their own merits: * Leave any include alone whose existing spelling still resolves from the new location. parallel.h's "parallel_impl.h" needs no edit when both files move together. * Spell everything else absolutely rather than as a bare sibling name. core/ already refers to its own siblings that way (core/dispatch.h -> <fastfields/core/autocast.h>), so the moved files should adopt the convention of the directory they land in rather than import impl/kernels/'s relative style along with them. This also stops the move from fighting #146, which made <fastfields/...> the spelling for the public interface. With both, all ten moved files are byte-identical pure renames (verified by checksum against their originals) and every include edit falls on the 71 files that did not move: 82 files, 140 insertions / 140 deletions, down from 150/150. That does NOT fix the lint -- a zero-content-change rename is flagged just as hard, which is the measurement above. The verified mitigation is to clang-format the ten files in place, in impl/kernels/, as its own PR before the move; formatting a file drops it off the linter's list entirely. That is now written up in the proposal's sequencing section as the item most likely to change how this work is scheduled, with the alternatives (teach the lint to skip R100, or exempt the one PR) and the argument for paying the debt rather than deferring it. Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016AjQcY78NgbagPSbPJRr6Z
balbasty
commented
Aug 20, 2026
|
| reformat lines demanded | |
|---|---|
| the 10 moved files | 4,232 |
impl/kernels/resize.h (a genuinely changed include line) | 4 |
| everything else | 0 |
4,232 of 4,236 are the rename artifact. The genuine formatting debt this
change creates is four lines.
It is not fixable by making the move cleaner
I tried. f982e6c tightens the script so it leaves alone any include whose
existing spelling still resolves from the new location, and spells the rest
absolutely — matching what core/ already does for its own siblings
(core/dispatch.h → <fastfields/core/autocast.h>), which also stops the move
fighting #146. Result: all ten moved files are now byte-identical pure
renames, verified by checksum, and the diff drops from 150/150 to 140/140
across 82 files, with every include edit falling on the 71 files that did not
move.
The linter is unmoved. A zero-content-change rename is flagged exactly as hard —
the 4,232 figure above is measured after that tightening.
(The tightening is worth keeping regardless: pure renames are what make the
review of a relocation possible at all.)
Mitigation, verified
clang-format the ten files in place, in impl/kernels/, as its own PR,
before the move. Formatting a file drops it off the linter's list entirely —
confirmed by formatting one of them and watching it disappear from the output.
That PR is trivially green, because there the files are not renamed, so only the
lines it changes get checked and it changes them to be clean.
The cost is honest and worth stating: it reformats ~4,200 lines of bounds.h,spline.h, utils.h and friends and churns blame across them. That is
pre-existing debt the gate has not billed yet — every one of these files is one
rename away from being billed for it, with or without this proposal.
Alternatives, both CI-side rather than source-side, both leaving the debt in
place:
- teach the lint to skip files whose diff status is
R100; - exempt the one PR.
I lean towards paying it, because a formatting-only PR is reviewable and a
combined one is not — 4,200 lines of reflow would bury the twelve include lines
that carry the actual meaning. But it is the owner's call, and it is the single
item here most likely to change how this gets scheduled. Written up as §4.1 of
the proposal.
Generated by Claude Code
…base Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016AjQcY78NgbagPSbPJRr6Z
The Move row claimed spline.h was an addition to the owner's eight. It was not; the eight were atomic/batch/meta/parallel/bounds/spline/threadpool/utils and spline is among them. The two genuine additions are parallel_impl.h and threadpool.inl, which are the .inl/impl partners of files already on the list and cannot be separated from them. Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016AjQcY78NgbagPSbPJRr6Z
balbasty
commented
Aug 20, 2026
CI status on |
| Job | Result |
|---|---|
test-cpu (clang-dynamic) | ✅ |
test-cpu (clang-index64) | ✅ |
test-cpu (clang-cuda-default) | ✅ |
test-cpu (sanitize asan+ubsan) | ✅ |
test-cpu (tsan, grain=1) | ✅ |
test-hub | ✅ |
lint (codespell) | ✅ |
detect changed paths | ✅ |
test-cpu (clang-static) | running |
test-cpu (gcc-static) | running |
compile-probe-cuda | running (✅ on f982e6c, identical code) |
build-cuda (index32) / (index64) | running |
lint (clang-format, changed lines) | ❌ — see the comment above; this is the finding, not a defect |
Two things worth pulling out:
tsan (grain=1) is green. That is the leg that rebuilds the whole 59,886-check
suite with FF_GRAIN_SIZE=1 so it actually runs multi-threaded under
ThreadSanitizer. It is the one that matters most for this change, becauseparallel.h, parallel_impl.h, threadpool.h and threadpool.inl are four of
the ten files being relocated.
compile-probe-cuda passed on f982e6c, whose C++ is byte-identical to this
SHA (the two commits since are documentation only). That is the check that
matters for the §1.1 argument: nvcc digests the relocated core/ headers. Thebuild-cuda legs will confirm it end-to-end, including the hub link that gates
backend completeness.
The only red is clang-format, and it is red for a reason this PR documents
rather than one it introduces. codespell — which also runs tree-wide — is
green.
Generated by Claude Code
balbasty
commented
Aug 20, 2026
Update: Nine of fourteen green: Still running: Unchanged: I am deliberately not pushing anything further to this branch — an earlier Generated by Claude Code |
balbasty
commented
Aug 20, 2026
Agreed |
Answers the two questions asked together, plus the thread-count question
that follows from them. Full document:
docs/proposals/core-and-naming.md.Recommended definition
It decides every borderline file: it admits
atomic.h(device-specialised, butnot an operation) and the parallel/threadpool group, and it excludes
splinc.h(which is the operation exported as
spline_coeff).Two premises in the brief that do not hold
core/was said to be barred from opening the device namespace.core/autocast.halready opensFF_NAMESPACE_BEGIN(FF_DEVICE), andcore/cuda_switch.hexists to branch on__CUDACC__and pullscuda_fp16.hunder nvcc. The real constraint is narrower: acore/header must compile under both compilers and mean the right thing in each.
So no fourth directory, and no
core/cpu+core/cudasplit.core/dispatch.hlines 28 and 48 saycanUse32BitIndexMathcomes fromcore/autocast.h. It is defined atimpl/kernels/utils.h:668. That iswhy 17 files in
src/include the kernels layer directly — the dependencyinversion this proposal repairs is load-bearing today and mis-documented.
Also: the header-guard PR (#145) landed the opposite of what the brief
described — guards everywhere, no
#pragma once— and existing guards keeptheir names, so a file move needs no guard renames.
The file list
Ten files, 4,262 lines. Closure is clean and checked mechanically:
core/never acquires a dependency on
impl/kernels/.utils.h·bounds.h·spline.h·batch.h·atomic.h·meta.h·parallel.h·parallel_impl.h·threadpool.h·threadpool.inlcore/goes 5 → 15 files;impl/kernels/goes 57 → 47 and then holds onlyper-operation implementations.
Not moved, with reasons in the doc:
vector/(11 files, 2,389 lines) — not infrastructure, superseded.Its only includer is a scratch target that is compiled but never run, and
distance/mesh_utils.hcontains an independent parallel hierarchy of thesame abstraction. Decide its fate separately.
tetrahedron.h— this is not a placement question. Both copies arereferenced by nothing, and
impl/cpu/tetrahedron.hdoes not compile(uses
parallel_for/GRAIN_SIZEwith no includes, and sits inff::tetrarather than
ff::cpu::tetra). Nothing builds it, so nothing catches it.splinc.h— the owner's instinct is right, and the one-character gapfrom
spline.his the trap worth naming out loud.Naming
The
voxidea is right for a stronger reason than "marking voxelwise-ness":the kernels layer and the impl layer share a namespace — all seven module
namespaces are opened by both. It does not collide today only because every
kernel entry point is hidden inside a class template (
Kernels,RegFlow,Multiscale,Child) rather than being a free function. Two pieces ofevidence that this workaround is load-bearing:
impl/cpu/pushpull.hlines37–82 contain a lambda named
pullshadowing the functionpullit sitsinside, and
teeny's commitb7dbd08fixed exactly this collision withexactly this
vox. Recommended spelling:ff::⟨device⟩::⟨module⟩::vox—26 sites across 25 files.
elemis the more accurate name by the codebase'sown prose; the doc gives the trade rather than pretending it is settled.
atomic.his the case that tests the scheme, and it is a real bug, confirmedby brace-depth scan: the whole CUDA branch sits at global scope, including
an overload of CUDA's own built-in
atomicAdd(line 263) and ATen'sAtomicFPOp/gpuAtomicAddnames verbatim — in an installed header, withfastfields-torchas a planned downstream consumer.FF_GPU_ATOMIC_INTEGERis also defined, never used, and never
#undef'd.On internal helpers: the
_sigil marks two unrelated things (116 distinctidentifiers, 2,943 mentions — overwhelmingly narrowed locals like
_stride_out, and only incidentally functions). Keep it for locals; forfunctions adopt the convention #147 is already landing
(
ff::cuda::flow_slice::, a named nested namespace, no sigil) rather thaninventing a competing one. Anonymous namespaces will not serve here, though
all 21 dispatch TUs already use them — one nested in
ff::cudacannot hold aflow_matvecalongside the exportedff::cuda::flow_matvecwithout makingunqualified calls ambiguous.
Thread-count API
ff::set_num_threads/get_num_threadsalready exist, fullyimplemented, at
threadpool.inllines 58–69;parallel_impl.halready wrapsthem in a backend-abstracted pair covering the OpenMP and single-threaded
builds. The task is export, not design. Three things a public setter would
expose: the obvious public name is already taken by the internal helper;
set_parallel_threads(-1)reachesThreadPool(size_t(-1))and spawns threadsuntil the process dies; and the setter mutates two function-local statics with
no lock, in a pool where #97 has just fixed races reachable only at higher
thread counts. The
/2default is a deliberate x86-only SMT correction — keepit, document it, and note that CI therefore runs the pool at 2 threads.
CUDA: do not mirror it. But the real finding is not the API — all 39
launch sites use
CUDA_NUM_THREADS = 1024(the architectural maximum) andcudaGetLastError/cudaPeekAtLastErrorappear zero times anywhere, so acudaErrorLaunchOutOfResourcesis discarded silently and the output tensorkeeps whatever it held. Adding launch error checking is the highest-value item
in this document and is independent of everything else in it.
Gate
Recorded in a comment below.
tools/move-core-headers.py --check→ clean, no dependency leak, idempotent.Sequencing
Must land after#145 (#146 has now landed). The script does not hard-code
an include delimiter — it measures the tree's dominant one — so it emitted
quotes before #146 and angle brackets after, unedited. That was demonstrated,
not assumed: #146 and #143 landed while this was being written, and the
rebase was done by re-running the script, exactly as its docstring prescribes.
Verified independent: #147 touches
src/lib-cuda/only (16 files, noneunder
include/) — zero overlap, and itsflow_slice::seam is an input tothe naming section rather than a conflict. #143 intersected only on the 17
src/*.cppwhoseutils.hinclude line changes — same files, differentlines, and the merge was clean. CI is neutral:
ci.yml:102already putsimpl/kernels/andcore/in the same "trigger EVERYTHING" clause.Everything in the naming section should wait and land as separate PRs;
bundling a rename into a relocation makes both unreviewable.
Generated by Claude Code