Skip to content

[Bug]: d3 decision requests keep ~13 CPU cores busy (thread pool spin-waits); no way to cap threads #882

Open

Description

@chetan-metrum-ai

Environment: FastFlowLM 1.0.11, Windows 11 Pro 26200, Ryzen AI 9 HX PRO 475 (12 cores / 24 threads, XDNA 2), NPU driver 32.0.20102.3930. flm serve --decision d3:nano (no chat model loaded).

What we see: back-to-back /v1/systemone requests run at about 0.40 s each with the NPU busy roughly 56% of the time, but flm.exe uses 12.9 cores (54% of all logical CPUs) while doing so: about 5.3 CPU-seconds per 0.4 s decision, 37% of it kernel time, spread over about 25 threads at about 44% each. At idle the server uses 0 CPU. A chat model on the same server (qwen3.6-moe:35b-a3b decode) uses only about 4% CPU, so this is specific to the decision path.

Restricting CPU affinity shows about half of it is waiting, not work:

flm.exe CPU affinity Decision median CPU used Kernel share
24 logical CPUs (default) 399 ms 12.9 cores 37%
8 logical CPUs 426 ms (+7%) 6.4 cores 68%
2 logical CPUs 822 ms 1.8 cores 40%

d3_npu.dll contains CPU matmul paths (cpu_mm, cpu_func, lm_head, AVX-512) and sizes its pool from hardware_concurrency. It looks like the worker threads spin and yield while the NPU runs.

Impact: a decision stream next to other CPU work slows both. With llama.cpp chat on the CPU at the same time, decisions went from 400 to 527 ms (+32%) and chat dropped 13%.

Requests:

  1. An option or env var to cap the decision path's CPU threads (for example --threads or FLM_CPU_THREADS).
  2. Block on the NPU completion (fence or event) instead of spin-waiting, or spin briefly and then sleep.

Happy to re-test a build on this machine.

Activity

  1. chetan-metrum-ai commented on Oct 30, 2026

    @chetan-metrum-ai
    Author

    Same pattern, much stronger, with qwen3.8-mtp:27b on FLM 1.0.11 (same machine). One 32-token answer at temperature: 0, freshly started server each time:

    flm.exe CPU affinity Decode First token CPU used Kernel share Threads in process
    24 logical CPUs (default) 0.95 tok/s 4.2 s 20.4 cores 51% 197
    8 logical CPUs 1.17 tok/s 3.7 s 5.7 cores 92% 440
    2 logical CPUs 1.52 tok/s (+60%) 3.2 s 1.7 cores 71% 488
    qwen3.5:9b, 24 CPUs (control) 9.9 tok/s 1.5 s 0.3 cores 39% 26

    The NPU stays 95–97% busy in every case, but restricting the process to fewer CPUs makes decode and first token faster, so the extra host threads are contending rather than doing useful work. The 27B module seems to create hundreds of threads (197–488 in a fresh process, against 26 for qwen3.5:9b). A cap on host threads, or blocking waits instead of spinning, would likely help the 27B more than d3.

  2. self-assigned this
    on Oct 30, 2026
  3. tawei-amd commented on Oct 30, 2026

    @tawei-amd
    Collaborator

    Thanks for the detailed report and the affinity tables, they pointed us straight at the cause. We are working on a fix and are aiming for the next release.

    What we found: the host-side loops of the d3 engine run on OpenMP, and an OpenMP team keeps its idle workers spinning through every NPU dispatch (a decision alternates short host loops with blocking NPU waits, once per layer). Each calling thread of the server also gets its own team, so the thread count grows well past one pool (about 120 threads on our test machine). We reproduced the CPU burn on Linux with a single calling thread (about 11.6 cores busy for a 390-token request, with the same latency as 4.7 cores). We have not been able to test Windows, and your 37% kernel time suggests the Windows OpenMP runtime spins harder than libgomp.

    The fix replaces OpenMP in d3 with one shared worker pool whose idle workers spin for a few microseconds and then sleep, so it applies to every d3 model. We will also keep the thread count adjustable. Please note that about 4 of those cores are real parallel work, so we do not expect CPU use to fall to the single-thread level.

    Since you offered to re-test: once a build is ready we would be glad if you could run it on your machine and compare it with your numbers above. We will post here when it is available.

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

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions