Skip to content

PROPOSAL (do not merge): what core/ means, and a naming scheme - #148

Closed
balbasty wants to merge 6 commits into
mainfrom
design/core-split-proposal
Closed

PROPOSAL (do not merge): what core/ means, and a naming scheme#148
balbasty wants to merge 6 commits into
mainfrom
design/core-split-proposal

Conversation

@balbasty

Copy link
Copy Markdown
Collaborator

This is a design proposal, not a migration. It is a draft and should
stay one until the owner decides. The second commit is a prototype kept
only so the cost figures are measurements; drop it and the design still
stands.

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

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.

It decides every borderline file: it 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).

Two premises in the brief that do not hold

  • 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__ and pulls
    <cuda_fp16.h> under nvcc. The real constraint is narrower: a core/
    header must compile under both compilers and mean the right thing in each.
    So no fourth directory and no core/cpu + core/cuda split is needed.
  • core/dispatch.h:28,48 says canUse32BitIndexMath comes from
    core/autocast.h.
    It is defined at impl/kernels/utils.h:668. That is
    why 17 files in src/ include the kernels layer directly — the dependency
    inversion 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 keep
their 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.inl

impl/kernels/ then holds only per-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.h contains an independent parallel hierarchy of the
    same abstraction. Decide its fate separately.
  • tetrahedron.h — this is not a placement question. Both copies are
    referenced by nothing, and impl/cpu/tetrahedron.h does not compile
    (uses parallel_for/GRAIN_SIZE with no includes, and sits in ff::tetra
    rather than ff::cpu::tetra). Nothing builds it, so nothing catches it.
  • splinc.h — the owner's instinct is right and the one-character gap
    from spline.h is the trap worth naming out loud.

Naming

The vox idea 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 of
evidence that this workaround is load-bearing: impl/cpu/pushpull.h:37-82
has a lambda named pull shadowing the function pull it sits inside, and
teeny's commit b7dbd08 fixed exactly this collision with exactly this
vox. Recommended: ff::<device>::<module>::vox, 28 sites. elem is the
more accurate name by the codebase's own prose — the doc gives the trade
rather than pretending it is settled.

atomic.h is the case that tests the scheme, and it is a real bug, confirmed
by brace-depth scan: the whole CUDA branch is at global scope, including
an overload of CUDA's own built-in atomicAdd (line 263) and ATen's
AtomicFPOp / gpuAtomicAdd names verbatim — in an installed header, with
fastfields-torch as a planned downstream consumer. FF_GPU_ATOMIC_INTEGER
is also defined, never used, and never #undef'd.

On internal helpers: the _ sigil marks two unrelated things (116 distinct
identifiers, 2,943 mentions — overwhelmingly narrowed locals like
_stride_out, and only incidentally functions). Keep it for locals; for
functions adopt the convention #147 is already landing (ff::cuda::flow_slice::,
a named nested namespace, no sigil) rather than inventing a competing one.

Thread-count API

ff::set_num_threads / get_num_threadsalready exist, fully
implemented, in threadpool.inl:58-69; parallel_impl.h already wraps them
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) reaches ThreadPool(size_t(-1)) and spawns
threads until 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 /2 default is a deliberate x86-only SMT
correction — keep it, document it, 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) and
cudaGetLastError/cudaPeekAtLastError appear zero times anywhere, so a
cudaErrorLaunchOutOfResources is discarded silently and the output tensor
keeps whatever it held. Adding launch error checking is the highest-value item
in this document and is independent of everything else in it.

Gate

Prototype commit, clang++, legs default,lib — row-for-row identical to
tools/test-baseline.expected: 13 cpu-lib suites, 59,886 checks, 0
failures
, plus the hub's 2 suites / 14 checks. --check reports NOT
COMPARABLE on leg coverage (2 of 6 recorded legs), not on any differing
row; the other four legs vary only the bound/spline compile policy, which
this change does not touch.

tools/move-core-headers.py --checkclean, no dependency leak, idempotent.

Sequencing

Must land after#145 and #146. The script does not hard-code an include
delimiter — it measures the tree's dominant one — so it emits "fastfields/…"
before that sweep and <fastfields/…> after, and a rebase is resolved by
re-running it, never by hand.

Verified independent: #147 touches src/lib-cuda/ only (16 files, none
under include/) — zero overlap, and its flow_slice:: seam is an input
to the naming section rather than a conflict. #143 intersects only on the
~16 src/*.cpp whose utils.h include line changes — different lines in the
same files. CI is neutral: ci.yml:102 already puts impl/kernels/ and
core/ 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

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 (it 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.
* 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.
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; the
CUDA backend launches all 39 kernels at the architectural maximum of 1024
threads per block and checks 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 the in-flight <fastfields/...> sweep by
being re-run rather than hand-merged.
Co-Authored-By: Claude Opus 5 <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 output of `tools/move-core-headers.py` run on
85fdac7, nothing else. Drop it and the design in the previous commit still
stands.
10 headers moved, 4,262 lines
82 files rewritten, 150 insertions / 150 deletions
`--check` clean and idempotent afterwards, no dependency leak
Measured gate on this tree (clang++, legs default,lib):
distance 2352/0 distance_mesh 4622/0 distance_spline 704/0
posdef 4012/0 pushpull 308/0 pushpull_backward 6381/0
reg_field 19250/0 reg_flow 16347/0 reg_op 186/0
resize 630/0 restrict 65/0 solve_field 452/0
splinc 4577/0 lib_device_check 5/0 lib_splinc_bound 9/0
Row-for-row identical to tools/test-baseline.expected: 13 cpu-lib suites,
59,886 checks, 0 failures, plus the hub's 2 suites / 14 checks. Two of the
six recorded legs were run, so `--check` reports NOT COMPARABLE on leg
coverage rather than on any differing row; the remaining four legs vary
only the bound/spline compile policy, which this change does not touch.
One thing the move breaks that is easy to miss, and is fixed here:
tools/test-baseline.sh probes for impl/kernels/bounds.h to validate tree
layout, so the gate tool dies before running a single test, with a message
that reads like a broken checkout. tools/consolidate.sh and the six-repo
probe in the same script reference the same paths and are deliberately NOT
updated -- they describe the frozen pre-consolidation layout.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016AjQcY78NgbagPSbPJRr6Z
#146 (angle-bracket includes) and #143 (FF_INDEX32) landed on main while
this proposal was being written, which turned the script's central claim
from an assertion into something demonstrable. The rebase was done the way
the docstring prescribes -- revert, merge main, re-run unedited -- and the
same script emitted <fastfields/...> on the new base with no edit and no
hand-merging. Both runs are now recorded in Appendix A.
Also corrects the vox migration cost: 26 FF_NAMESPACE_BEGIN(<module>) sites
across 25 files, not 28 across 26, with the per-module breakdown. The
earlier figure came from a scan that counted non-module namespaces
(bound, spline, meta, internal, _splinc) alongside the module ones.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016AjQcY78NgbagPSbPJRr6Z
… on the new base
Regenerated by re-running `tools/move-core-headers.py` unedited on
de288a9 (main after #146 and #143), which is the whole point: the earlier
prototype commit was reverted rather than hand-merged, and this is what the
script produced on its own.
10 headers moved, 4,262 lines
81 files rewritten, now emitting <fastfields/...> instead of "fastfields/..."
`--check`: clean, idempotent, no dependency leak
Drop this commit and the design in the preceding ones still stands.
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. 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 Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016AjQcY78NgbagPSbPJRr6Z
@balbastyClaude

Copy link
Copy Markdown
CollaboratorAuthor

Superseded by #149 — same content, same two commits, rebased onto main after #146 and #143 landed, on a branch with a corrected commit trailer. Closing this one so there is a single place to review.


Generated by Claude Code

Sign up for freeto 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.

2 participants

@balbasty@claude