Skip to content

NUMA-aware placement: keep hot pools on the GPU's node, let cold pools (expert file tail, conversation caches) use the other socket — opt-in #1900

Closed as not planned

Description

@G0-0000

On dual-socket machines, memory on the other CPU socket is roughly 2x the latency away (QPI/UPI), and this engine currently has no NUMA-aware placement: the recommended practice is to pin the whole process to the GPU's node by hand (systemd CPUAffinity + NUMAPolicy=bind/NUMAMask, or numactl). Two references: #674 measured engine-wide binding to the GPU node at about +5% over default; #1300 notes there is no supported way to confine the pool to one NUMA node.

That works, but it throws away the other socket's RAM: on machines where both sockets have plenty of memory, cold data (the expert file tail not held in VRAM/RAM, conversation-cache snapshots) must live in the GPU node's RAM or be re-read from disk, while the second node sits idle.

Suggestion (opt-in, default behavior unchanged — the shape is the maintainers' call):

  1. A manual flag placing designated cold pools on a chosen NUMA node (e.g. the expert file-tier pages and conversation caches);
  2. Or an auto mode: read the topology at startup; keep hot pools (resident expert complement, KV streaming buffers, embeddings) on the GPU's node, and place cold pools on another node when it has free memory. In either case, honoring the rule that a new feature is opt-in and the default path stays byte-identical.

Or, if a maintainer would rather settle on the design, we can write the implementation — happy to prepare the PR once the shape (flag naming, scope, semantics) is decided.

Why it could help:

Not proposed, to preempt confusion:

Implementation notes (for whoever picks this up):

  • Page-cache pages land on the node of the thread that first touches them, so cold reads would need their own pinned buffers or reader threads with their own policy;
  • It must coordinate with external pinning: with a blanket cgroup/systemd binding to the GPU node, the engine cannot place anything on the other node — the docs should say so, and an auto mode would need blanket pinning lifted;
  • numa_balancing can disturb static placement.

Measured on: 2-socket Xeon E5 v4 (~94 GB total; 64 GB attached to the GPU's socket), Tesla V100-32G, Flash-Next UD-IQ4_XS, v0.1.42 — hot pools: 27 GiB resident experts + 1.55 GiB KV host buffers + 644 MiB embeddings; cold candidates: ≈4.4 GiB expert tail + 4 GiB conversation caches; other socket ~10 GiB reclaimable.

Activity

  1. MrKop-git commented on Oct 15, 2026

    @MrKop-git

    Measured data point supporting this: +37-40% prompt throughput from external numactl binding on a dual-socket PC.

    We measured the simple approach this issue describes as "pin the whole process to the GPU's node" on a GTX 1080 Ti + 2x Xeon E5-2696 v4 (Broadwell-EP, no AVX-512, PCIe Gen3 x16, GPU on NUMA node 1):

    numactl --preferred=1 --cpunodebind=1 <engine>

    Results (same tools/strata_bench.py suite, 3 runs, before vs after, full data in #2684):

    Metric before after delta
    4K prompt, fresh 202.4 tok/s 276.3 tok/s +36.6%
    16K prompt, fresh 194.6 tok/s 272.6 tok/s +40.1%
    4K first token 19.92 s 14.62 s -26.6%
    agent turn first token 25.60 s 17.47 s -31.6%

    Decode was unchanged (23.6 → 21.5 tok/s, within noise) — the CPU expert pool is bandwidth-bound and locality helped prompts (sequential reads) more than decode (per-token expert reads are already fast enough locally).

    This is much larger than the +5% from #674. The difference is likely because our card only fits 1,513 of 24,576 experts in VRAM (6%), so 58-84% of routed experts are computed on the CPU pool — memory locality on the CPU side dominates. On a card that holds most experts in VRAM, the CPU-side NUMA penalty matters less.

    Relevance to this proposal: the whole-process binding we measured has the limitation you describe — it confines everything to one node and wastes the other socket's RAM. Your engine-level split (hot pools on the GPU node, cold pools on the other socket) would capture the same locality win for the hot path while freeing the second node's RAM for the file tail and conversation caches. The 37-40% number here is a strong argument for making that split a first-class feature.

    One additional data point: after NUMA binding, the official --calibrate moved the optimal settings — pcie-frac 0.32 → 0.20 and spec-min-p 0.50 → 0.70 (the CPU computes missed experts faster on local DDR4, so the engine wants a smaller GPU PCIe share and only high-confidence drafts). A NUMA-aware engine could re-calibrate these automatically.

    🤖 Generated with Claude Code

  2. Niko1221 commented on Oct 19, 2026

    @Niko1221
    Owner

    Thanks for taking the time to report this.

    We're resetting the tracker for 0.2.0-beta. There are more than 1,300 open issues and pull requests, too many for us to read properly, so we're closing everything that isn't being worked on right now. Closed here does not mean rejected.

    To reopen it takes about two minutes:

    1. Re-test on 0.2.0-beta. It may already be fixed. Write down what you see.
    2. Start the title with its area: [Engine], [GUI], [TUI], [Benchmark], [Docs] or [Other].
    3. Fill in the parts of the issue form: what happened, the Strata version, your graphics card, memory and system.
    4. Comment /reopen. A check runs and either reopens the issue or lists what is still missing.

    The rules are in CONTRIBUTING.md.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions