Skip to content

Add DeepSeek V4 target-only CPU inference - #165

Open
DrewZt wants to merge 27 commits into
JustVugg:devfrom
whale-agent-lab:dev
Open

Add DeepSeek V4 target-only CPU inference#165
DrewZt wants to merge 27 commits into
JustVugg:devfrom
whale-agent-lab:dev

Conversation

@DrewZt

@DrewZt DrewZt commented Jul 14, 2026

Copy link
Copy Markdown

Summary

This PR now contains the DeepSeek V4 target-only CPU engine requested in the latest review. DSpark speculative decoding has been removed from #165 and preserved on DrewZt:pr165-full-dspark-backup for a separate stacked follow-up.

The target engine:

  • loads official sharded DeepSeek V4 safetensors checkpoints;
  • implements target prefill and greedy decode, compressed attention, mHC, routed/shared experts, and RAM-tiered ExpertStore caching;
  • keeps target dense residency independent of DSpark;
  • uses shared st.h for safetensors indexing/range I/O;
  • uses shared quant.h for canonical fmt7 MXFP4 matmul;
  • is wired into coli run, coli chat, coli serve, and coli web;
  • accepts --no-dspark as a compatibility no-op.

Production is consolidated in c/deepseek_v4.c. The standalone c/v4 launcher, DSpark runtime/build units, DSpark fixtures/tests, and committed .safetensors fixtures have been removed.

Shared infrastructure status

Checkpoint path Current implementation Status
Safetensors indexing/range reads shared st.h migrated
fmt7 standard MXFP4 matmul shared quant.h migrated
fmt7 resident rows16 expert cache minimal V4-private layout TODO(upstream-fmt7-rows16): migrate when shared quant exposes a resident rows16 API
fmt8 E4M3 + UE8M0 128x128 scales minimal V4-private decoder TODO(upstream-fmt8-ue8m0): replace when shared fmt8 UE8M0 decode exists

The last two paths remain only to keep the target engine usable and are explicitly marked for migration.

Unified serving

openai_server.py detects deepseek_v4, renders native multi-turn V4 markers, and launches the persistent SUBMIT/DATA/DONE protocol. Serving is target-only, greedy, one KV slot, and rejects tools/grammar. Requests re-prefill context while the engine, dense tensors, head, and expert cache stay warm.

Fixture and CI

The target-only fixture is generated from pinned PyTorch 2.13.0+cpu, Transformers 5.14.1, and safetensors 0.8.0. Its reference comes from official DeepseekV4ForCausalLM; there is no C-engine oracle fallback. Generated safetensors are ignored and not committed.

The V4 CI job checks teacher-forcing and greedy token identity, compressed/long prompts beyond the 64-token prefill boundary, repeated engine/session lifetime, --no-dspark compatibility, and two requests through one persistent server process.

Validation

  • make -C c check on Windows UCRT64: all C tests and 283 Python tests passed (21 platform skips)
  • clean make -C c deepseek-v4 ARCH=x86-64-v3
  • generated target-only tiny oracle: all token-exact checks passed
  • shared st.h pread/mirror and fmt8 loader tests passed
  • unified CLI and OpenAI server tests passed
  • real 48-shard DeepSeek V4 Flash target inference, 64 GiB planner budget:
    • output: The capital of France is Paris.
    • target dense: 43/43 layers resident, 6.266 GiB
    • target expert cache: 55.68 GiB
    • TTFT: 14.161 s
    • post-first-token time: 7.157 s
    • generated: 8 tokens (EOS before the 10-token cap)
    • target_only=1

The real-checkpoint output was also reproduced through c/coli run.

Commit structure

  1. feat(st): support V4 checkpoint metadata
  2. refactor(v4): preserve target-only runtime
  3. feat(cli): route DeepSeek V4 through coli serving
  4. test(v4): generate target-only oracle in CI
  5. docs(v4): describe target-only engine split

Follow-up

The stacked DSpark PR will restore the saved speculative runtime on this target baseline, make --no-dspark a real switch again, restore deterministic DSpark identity tests, and provide on/off performance and acceptance data across high-, medium-, and low-acceptance prompts.

@DrewZt DrewZt changed the title Dev Add DeepSeek V4 Flash CPU inference with NVMe expert streaming Jul 14, 2026
@DrewZt
DrewZt marked this pull request as draft July 14, 2026 14:21

@rajpratham1 rajpratham1 left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a very impressive contribution and clearly represents a significant amount of engineering work. The implementation covers a complete DeepSeek V4 CPU inference pipeline including runtime, expert streaming, quantization, safetensors loading, CLI tooling, documentation, and an extensive unit test suite.

Because this PR introduces an entirely new inference stack across many core components, I'd prefer additional review before approval.

Some areas that would benefit from closer review include:

  • Long-term API stability for the new DeepSeek V4 interfaces.
  • Memory ownership and lifetime throughout the expert streaming/runtime pipeline.
  • Performance characteristics of the NVMe streaming implementation under sustained inference.
  • Cross-platform compatibility (Windows/Linux/macOS) for filesystem and I/O paths.
  • Validation against larger real-world models beyond the included unit tests.

Overall the direction looks very promising, but given the size and architectural impact of this change, I think it should receive another maintainer review before merging.

@DrewZt

DrewZt commented Jul 14, 2026

Copy link
Copy Markdown
Author

Thanks for the thoughtful review. I agree that another maintainer review is appropriate given the size of the change.

A few clarifications on the areas you mentioned:

API stability: the new interfaces are currently scoped to the DeepSeek V4 engine and should be considered experimental. They are not intended to establish a stable generic model API at this stage.
Memory ownership: the expert-store API uses explicit lookup/release semantics, and the unit tests cover cache reuse and resource accounting. I agree that this area deserves focused review, and I can add more ownership/lifetime documentation where the contracts are not clear enough.
Sustained NVMe performance: the current documentation contains single-run measurements. I still need to add repeated and longer-running tests, including cache behavior, disk throughput, and memory stability over sustained decoding.
Cross-platform support: the DeepSeek V4 engine is intentionally limited to x86-64 Linux and Windows/MSYS2 for now. macOS, PowerPC, and other platforms are gated out of the V4 build and continue to run the existing GLM checks unchanged.
Full-model validation: the engine has been exercised end to end with the actual DeepSeek-V4-Flash-DSpark checkpoint. The current oracle path validates deterministic target-token reproduction and DSpark on/off identity; comparison against the official Transformers implementation is supported by the tooling but still needs broader validation.

I’m happy to address targeted follow-up findings, add more validation, or split parts of the implementation into staged PRs if the maintainers feel that would make review and long-term maintenance easier.

@DrewZt
DrewZt marked this pull request as ready for review July 14, 2026 17:36
@DrewZt

DrewZt commented Jul 14, 2026

Copy link
Copy Markdown
Author

I pushed a follow-up series through 82e7760 addressing the API-boundary and ownership concerns raised in the review.

The main changes are:

  • the DeepSeek V4 engine/session API is now explicitly marked experimental;
  • implementation-specific safetensors and ExpertStore interfaces have been moved out of the public API;
  • model paths, indexes, expert stores, resident weights, head caches, and DSpark runtime state now have explicit engine ownership;
  • engine/session lifetime accounting is shared by the production and ownership-test paths;
  • partially initialized engines and DSpark runners are cleaned up correctly on failure;
  • the ExpertStore lookup/release lease contract is documented and covered by regression tests;
  • ownership fault-injection hooks and test objects are isolated from production builds;
  • V4 session-owned tokenizer allocations are now released when the session is destroyed;
  • the existing GLM tokenizer loading behavior and GLM runtime paths remain unchanged.

The latest changes pass:

make -C c check -j8

The remaining validation work is focused on sustained NVMe behavior and broader full-model comparison against the official Transformers implementation. Those limitations remain documented in the PR and do not represent unresolved API or resource-lifetime issues.

I’m marking the PR ready for review and would appreciate another maintainer look, particularly at the revised API boundary and ownership model.

@steve-m

steve-m commented Jul 14, 2026

Copy link
Copy Markdown
Contributor

First full-model validation on Linux/x86-64 — plus a build fix and an AVX2 kernel series, branch ready to pull: steve-m/colibri@v4-avx2-kernels (5 commits on top of this PR's dev).

Hardware: Ryzen 9 3900X (Zen 2, 12C/24T, AVX2 no AVX-512), 62 GiB RAM, NVMe ~6 GB/s, Manjaro, gcc 15. Model: DeepSeek-V4-Flash-DSpark, --memory-gb 40.

Build fix you'll want regardless: two amalgam units (BLOCK_HYBRID, GENERATE_STATS) use pthreads without including pthread.h — gcc 14+ (C23) makes implicit declarations hard errors, so make deepseek-v4 fails out of the box on current Linux toolchains. One-line includes, first commit on the branch.

Kernel series (all behind a COLI_V4_AVX2=0 runtime kill-switch, float paths kept as fallbacks):

  • FP4 experts: int8 dot via pshufb-LUT + maddubs (doubled E2M1 values are integers), exact int32 accumulation per 32-block. On random tensors this measures ~4× closer to an fp64 oracle than the shipped float path (rel L2 0.027 vs 0.106).
  • BF16 head: 8-lane exact widening + zero-copy pass over the resident head (the elementwise coli_bf16_decode loop was 0.23 s/token on this box).
  • FP8 attention: fp32 bits built directly from E4M3 fields with a branchless denormal blend; same QDQ-activation math and product grouping as the reference, differs only by summation order.

Measured, 48-token free-form decode, OMP_NUM_THREADS=12: 0.53 tok/s (PR defaults) → 0.62 (threads = physical cores; SMT only adds barrier traffic) → 0.78 (FP4 int8) → 0.99 (head) → 1.12 tok/s (FP8). Verify phase −27%, TTFT 20 → 16.7 s. Validation at every step: your oracle tool's continuation_self_check 8/8 and DSpark on/off identity OK (the batch kernels are bitwise-identical per item to the single matvec by construction, so speculation identity survives), plus a new tests/test_native_quant_avx2.c (fp64-oracle error bounds incl. denormal-only tensors, bitwise dual/batch==single, dispatch kill-switch).

Also on the branch, off by default: a dual-SSD mirror (COLI_V4_MODEL_MIRROR) that hash-routes reads across two model copies at the coli_st_read_at choke point. Honest data point: it's ~5% slower warm (routing splits the OS page cache across two copies) — it's there for cold starts and low-RAM boxes; docs in the commit message.

Happy to split any of this into separate PRs against your dev, or adjust to taste.

@DrewZt

DrewZt commented Jul 15, 2026

Copy link
Copy Markdown
Author

First full-model validation on Linux/x86-64 — plus a build fix and an AVX2 kernel series, branch ready to pull: steve-m/colibri@v4-avx2-kernels (5 commits on top of this PR's dev).

Hardware: Ryzen 9 3900X (Zen 2, 12C/24T, AVX2 no AVX-512), 62 GiB RAM, NVMe ~6 GB/s, Manjaro, gcc 15. Model: DeepSeek-V4-Flash-DSpark, --memory-gb 40.

Build fix you'll want regardless: two amalgam units (BLOCK_HYBRID, GENERATE_STATS) use pthreads without including pthread.h — gcc 14+ (C23) makes implicit declarations hard errors, so make deepseek-v4 fails out of the box on current Linux toolchains. One-line includes, first commit on the branch.

Kernel series (all behind a COLI_V4_AVX2=0 runtime kill-switch, float paths kept as fallbacks):

  • FP4 experts: int8 dot via pshufb-LUT + maddubs (doubled E2M1 values are integers), exact int32 accumulation per 32-block. On random tensors this measures ~4× closer to an fp64 oracle than the shipped float path (rel L2 0.027 vs 0.106).
  • BF16 head: 8-lane exact widening + zero-copy pass over the resident head (the elementwise coli_bf16_decode loop was 0.23 s/token on this box).
  • FP8 attention: fp32 bits built directly from E4M3 fields with a branchless denormal blend; same QDQ-activation math and product grouping as the reference, differs only by summation order.

Measured, 48-token free-form decode, OMP_NUM_THREADS=12: 0.53 tok/s (PR defaults) → 0.62 (threads = physical cores; SMT only adds barrier traffic) → 0.78 (FP4 int8) → 0.99 (head) → 1.12 tok/s (FP8). Verify phase −27%, TTFT 20 → 16.7 s. Validation at every step: your oracle tool's continuation_self_check 8/8 and DSpark on/off identity OK (the batch kernels are bitwise-identical per item to the single matvec by construction, so speculation identity survives), plus a new tests/test_native_quant_avx2.c (fp64-oracle error bounds incl. denormal-only tensors, bitwise dual/batch==single, dispatch kill-switch).

Also on the branch, off by default: a dual-SSD mirror (COLI_V4_MODEL_MIRROR) that hash-routes reads across two model copies at the coli_st_read_at choke point. Honest data point: it's ~5% slower warm (routing splits the OS page cache across two copies) — it's there for cold starts and low-RAM boxes; docs in the commit message.

Happy to split any of this into separate PRs against your dev, or adjust to taste.

This is extremely helpful — thank you for doing the first independent full-model Linux/x86-64 validation and for documenting the performance progression in such detail. The Ryzen 3900X result directly addresses one of the main remaining validation gaps for this PR, and the 0.53 → 1.12 tok/s breakdown makes it much easier to see where the current bottlenecks are.

I checked the branch history and it looks like v4-avx2-kernels was based on f2ff5ad, which was the PR head when you started, rather than the current head 82e7760. The branches have since diverged, and the optimization branch is missing the API-boundary and resource-ownership follow-ups added after that snapshot.

Those later commits include the revised public/internal API separation, engine/session lifetime accounting, failure-path cleanup, isolated ownership-test objects, and V4 session tokenizer cleanup. The optimization work is still very valuable, but it should be rebased onto 82e7760 before integration so that those ownership changes are not accidentally overwritten or bypassed.

One clarification regarding the pthread build issue: I had already addressed the missing declaration problem in c9b626c, immediately after the snapshot your branch was based on, by adding -pthread -include pthread.h to the Linux V4 build flags. That fix is already present in the current PR head.

Your source-level includes may still be a cleaner and more localized solution, but the issue itself no longer needs a separate build-fix PR. When rebasing, please either drop the overlapping build-fix commit or call out why replacing the current compiler-level include with explicit includes in the two amalgam units would be preferable.

For the remaining work, I suggest splitting it into two focused follow-ups:

  1. AVX2 kernel series

    Please keep the FP4 expert, BF16 head, and FP8 attention kernels together with tests/test_native_quant_avx2.c in a dedicated PR rebased onto 82e7760.

    Keeping the scalar/float fallbacks and the COLI_V4_AVX2=0 runtime kill switch is a good compatibility approach. The fp64 error-bound tests, denormal cases, batch-versus-single checks, and dispatch-disable coverage are especially useful.

  2. Dual-SSD mirror

    I think COLI_V4_MODEL_MIRROR should remain a separate experimental PR. Its expected benefits and tradeoffs differ from the CPU kernels, and the warm-cache regression you measured is important context. It appears most relevant to cold starts and lower-memory systems rather than the default warm-cache configuration.

I also noticed that the branch diff appears to include generated test binaries such as:

c/tests/test_decode_batch
c/tests/test_i4_acc512
c/tests/test_idot

Please drop those artifacts when preparing the follow-up PRs.

After rebasing, please rerun the full-model correctness and performance validation because the runtime and ownership code has changed since f2ff5ad. The most useful checks would be:

make -C c check -j8
make -C c deepseek-v4

continuation_self_check
DSpark on/off identity
COLI_V4_AVX2=0 fallback comparison
48-token benchmark with the same model, RAM cap, and thread count

The current benchmark result is already valuable as independent validation of the earlier implementation. Once the rebased kernel series is reviewed, I’d be happy to add the reproducible Linux/x86-64 measurements to the documentation with credit to you and the exact hardware and commands.

Thanks again — this is a substantial and very useful contribution. Rebasing and splitting it should let us preserve the recent API and ownership work while giving the kernels and storage experiment the focused review they deserve.

@DrewZt

DrewZt commented Jul 15, 2026

Copy link
Copy Markdown
Author

Follow-up for @JustVugg: head af86de1 closes the remaining validation findings. The safetensors index now requires payload bytes to equal dtype width times shape numel; V4 config integers, floats, and compress ratios now have finite/integer/range checks. I also removed the dense-cache borrowed config pointer in favor of the engine-owned canonical config and made the DSpark oracle require exact output lengths on both paths. Fresh validation passed make check (all C tests plus 71 Python tests), x86-64-v3/native builds, ASan+UBSan+LSan, and the 48-shard MEMORY_GB=32 oracle (19/19 teacher forcing, 8/8 greedy, 8/8 continuation self-check, exact DSpark on/off identity). This supersedes my earlier blocker summary; AVX2 and dual-SSD work remain out of scope. Could you please review when convenient?

@steve-m

steve-m commented Jul 15, 2026

Copy link
Copy Markdown
Contributor

Pushed the rebased AVX2 kernel series to steve-m/colibri:v4-avx2-kernels (force-updated onto the current PR head af86de1, the ownership/API series you pushed after 82e7760) — 3 commits on top. Rebased rather than replayed the old branch, so the public/internal API split, engine/session ownership, failure-path cleanup, and session tokenizer changes are all preserved underneath — make -C c check (incl. the test_v4_ownership suite) passes on top of the series.

Two changes from the old v4-avx2-kernels snapshot:

  • Dropped the pthread build-fix commit. Your -pthread -include pthread.h in the Linux V4 CFLAGS already covers it, so the separate source-include commit is gone — the engine builds clean on gcc 15 without it. (I kept the compiler-flag approach; happy to add explicit #include <pthread.h> to the two amalgam units instead if you'd prefer the localized form, but it's not needed for the build.)
  • Left the dual-SSD mirror out of this series. It's off-by-default and ~5% slower warm on a single-cache box, so it doesn't belong in the kernel PR; I'll keep it on a separate branch if there's interest.

The three commits, all behind the COLI_V4_AVX2=0 runtime kill-switch with the float paths kept as fallbacks:

  1. FP4 experts — int8 dot (pshufb-LUT + maddubs; doubled E2M1 values are integers, exact int32 accumulation per 32-block). Drops in at the link level — native_quant_avx2.c provides coli_fp4_matvec_ref/coli_fp8_matvec_ref and wraps the renamed *_float_ref fallbacks, so no engine call sites change. The native_quant_* files were byte-identical to the snapshot, so this was a clean port.
  2. FP8 attention — E4M3 (fp32 bits built directly from the E4M3 fields with a branchless denormal blend; same QDQ-activation math and product grouping as the reference, differs only by summation order).
  3. BF16 head + zero-copy resident pass. A shared coli_v4_head_row_dot (8-lane exact bf16 widening + FMA across the batch) replaces the per-element coli_bf16_decode loops in both coli_v4_target_head_argmax_batch and the single-token head_argmax; when the head is resident in the engine head cache, it now dots the cached rows in place instead of re-reading per ROWS block.

Validation on the rebased branch — Ryzen 9 3900X (Zen 2, AVX2 no AVX-512), 62 GiB, gcc 16.1.1, DeepSeek-V4-Flash-DSpark, --memory-gb 40:

  • make -C c check — all pass, including test_native_quant_avx2 (fp64-oracle error bounds incl. denormal-only tensors, bitwise dual/batch==single, dispatch kill-switch) and the full test_v4_ownership suite. Builds clean on gcc 16 (only pre-existing warnings in deepseek_v4_dspark.c, none from the kernels).
  • make deepseek-v4-oracle (coli-self): continuation_self_check 8/8, teacher-forcing 19/19 positions, greedy 8/8 tokens, and DSpark on/off identity OK (no_dspark vs fixture, dspark vs fixture, on/off equality all OK). The head is resident (head=resident-bf16), so the zero-copy resident-head path is the one exercised.

Perf — AVX2 on vs the COLI_V4_AVX2=0 float fallback on this rebased head (48-token free-form, OMP_NUM_THREADS=12).

Kernel-only isolation, DSpark disabled (--no-dspark) — both runs issue an identical, deterministic 18318 expert requests, so this is a true A/B with no speculation-acceptance variance:

  • decode 0.76 → 0.98 tok/s (+29%)
  • prefill / TTFT 48.2 s → 31.2 s (−35%)

For reference, DSpark on (same prompt): decode 0.62 → 0.85 tok/s (+36%), TTFT 43.6 → 30.3 s (−30%) — but that pass had asymmetric acceptance (on 3/10 vs off 1/10 speculative tokens), so the isolated --no-dspark figure above is the honest kernel-only claim.

The float-path baseline matches the known physical-cores number, so the kill-switch cleanly isolates the kernel gain. TTFT is stable across the DSpark condition (~31 s on / ~48 s off), as expected — the first token is prefill + first decode, before any speculation. (Absolute tok/s is workload-dependent — this was a heavy ~80 GB-streaming pass; a lighter/warmer pass on this box peaks around 1.12 tok/s. The on/off ratio is the stable claim.)

@DrewZt

DrewZt commented Jul 16, 2026

Copy link
Copy Markdown
Author

Pushed the rebased AVX2 kernel series to steve-m/colibri:v4-avx2-kernels (force-updated onto the current PR head af86de1, the ownership/API series you pushed after 82e7760) — 3 commits on top. Rebased rather than replayed the old branch, so the public/internal API split, engine/session ownership, failure-path cleanup, and session tokenizer changes are all preserved underneath — make -C c check (incl. the test_v4_ownership suite) passes on top of the series.

Two changes from the old v4-avx2-kernels snapshot:

  • Dropped the pthread build-fix commit. Your -pthread -include pthread.h in the Linux V4 CFLAGS already covers it, so the separate source-include commit is gone — the engine builds clean on gcc 15 without it. (I kept the compiler-flag approach; happy to add explicit #include <pthread.h> to the two amalgam units instead if you'd prefer the localized form, but it's not needed for the build.)
  • Left the dual-SSD mirror out of this series. It's off-by-default and ~5% slower warm on a single-cache box, so it doesn't belong in the kernel PR; I'll keep it on a separate branch if there's interest.

The three commits, all behind the COLI_V4_AVX2=0 runtime kill-switch with the float paths kept as fallbacks:

  1. FP4 experts — int8 dot (pshufb-LUT + maddubs; doubled E2M1 values are integers, exact int32 accumulation per 32-block). Drops in at the link level — native_quant_avx2.c provides coli_fp4_matvec_ref/coli_fp8_matvec_ref and wraps the renamed *_float_ref fallbacks, so no engine call sites change. The native_quant_* files were byte-identical to the snapshot, so this was a clean port.
  2. FP8 attention — E4M3 (fp32 bits built directly from the E4M3 fields with a branchless denormal blend; same QDQ-activation math and product grouping as the reference, differs only by summation order).
  3. BF16 head + zero-copy resident pass. A shared coli_v4_head_row_dot (8-lane exact bf16 widening + FMA across the batch) replaces the per-element coli_bf16_decode loops in both coli_v4_target_head_argmax_batch and the single-token head_argmax; when the head is resident in the engine head cache, it now dots the cached rows in place instead of re-reading per ROWS block.

Validation on the rebased branch — Ryzen 9 3900X (Zen 2, AVX2 no AVX-512), 62 GiB, gcc 16.1.1, DeepSeek-V4-Flash-DSpark, --memory-gb 40:

  • make -C c check — all pass, including test_native_quant_avx2 (fp64-oracle error bounds incl. denormal-only tensors, bitwise dual/batch==single, dispatch kill-switch) and the full test_v4_ownership suite. Builds clean on gcc 16 (only pre-existing warnings in deepseek_v4_dspark.c, none from the kernels).
  • make deepseek-v4-oracle (coli-self): continuation_self_check 8/8, teacher-forcing 19/19 positions, greedy 8/8 tokens, and DSpark on/off identity OK (no_dspark vs fixture, dspark vs fixture, on/off equality all OK). The head is resident (head=resident-bf16), so the zero-copy resident-head path is the one exercised.

Perf — AVX2 on vs the COLI_V4_AVX2=0 float fallback on this rebased head (48-token free-form, OMP_NUM_THREADS=12).

Kernel-only isolation, DSpark disabled (--no-dspark) — both runs issue an identical, deterministic 18318 expert requests, so this is a true A/B with no speculation-acceptance variance:

  • decode 0.76 → 0.98 tok/s (+29%)
  • prefill / TTFT 48.2 s → 31.2 s (−35%)

For reference, DSpark on (same prompt): decode 0.62 → 0.85 tok/s (+36%), TTFT 43.6 → 30.3 s (−30%) — but that pass had asymmetric acceptance (on 3/10 vs off 1/10 speculative tokens), so the isolated --no-dspark figure above is the honest kernel-only claim.

The float-path baseline matches the known physical-cores number, so the kill-switch cleanly isolates the kernel gain. TTFT is stable across the DSpark condition (~31 s on / ~48 s off), as expected — the first token is prefill + first decode, before any speculation. (Absolute tok/s is workload-dependent — this was a heavy ~80 GB-streaming pass; a lighter/warmer pass on this box peaks around 1.12 tok/s. The on/off ratio is the stable claim.)

This looks excellent — thank you for rebasing the series carefully and for preserving the API and ownership work underneath it.

The updated scope is exactly what I was hoping for:

  • the overlapping pthread fix is removed;
  • the dual-SSD experiment is kept separate;
  • the AVX2 work is limited to three focused kernel commits;
  • the float fallbacks and COLI_V4_AVX2=0 escape hatch remain available;
  • the ownership suite and full-model oracle still pass on top of the series.

I also appreciate the distinction between the DSpark-enabled result and the --no-dspark isolation run. The deterministic 18,318-request A/B is the right primary performance claim because it removes speculative-acceptance variance. The resulting figures are both substantial and clearly scoped:

decode:       0.76 → 0.98 tok/s  (+29%)
prefill/TTFT: 48.2 → 31.2 s     (-35%)

The zero-copy resident-head path being exercised by the full-model oracle is also useful confirmation that the new path is covered rather than only compiled.

Since the kernel branch is stacked on top of #165, I think the cleanest next step is to keep the branch as-is for now and open a dedicated follow-up PR after #165 is merged. At that point it can be rebased onto the resulting upstream dev, leaving the follow-up diff limited to the three kernel commits and their tests.

Please preserve the current commit separation and validation details in that PR. In particular, the --no-dspark A/B should be the headline benchmark, with the DSpark-enabled figures included as additional workload-dependent context.

Thanks again — this is a strong follow-up series, and the careful validation and honest performance attribution make it much easier to review.

@DrewZt
DrewZt requested a review from rajpratham1 July 16, 2026 03:44
@DrewZt

DrewZt commented Jul 16, 2026

Copy link
Copy Markdown
Author

Pushed follow-up stability fix e4f87a8 for two issues found during full-model testing:

  • Fixed prompts longer than the internal 64-token batch limit by chunking target-layer and DSpark prefill while preserving absolute positions.
  • Fixed DSpark sparse-window accounting after speculative position jumps. The previous monotonically incremented valid counter could report more entries than were actually copyable (valid=43 copied=42), resulting in DSpark block failed.
  • Added stage-specific DSpark diagnostics and a regression test for sparse absolute-position window updates.

Validation:

  • The previous 74/76-token failure cases now complete successfully.
  • A near-limit 488-token prompt completed prefill and generated its first token successfully.
  • The DeepSeek V4 unit tests pass.
  • make deepseek-v4 succeeds.

@JustVugg

Copy link
Copy Markdown
Owner

I'm interested in this — DeepSeek V4 Flash on CPU with NVMe expert streaming is squarely what colibrì is for, and I'd like it in.

The one condition is that I need to run it on my own machine first. Not as a gate to be difficult: it's the rule I've had to learn the hard way this week. EXPERT_BUDGET went in on numbers nobody had reproduced and had to be quarantined a few days later (#303) — it turned out to be slower than not using it while claiming a speedup. I'm not going to do that to a second engine. Once something is in main, people run it, and if I can't run it I can't fix it for them.

So: as soon as I can get a checkpoint on this box, I'll test it and we'll work on it together. That's not a "no" parked forever — it's the next thing I want to do on this front.

Two things that would make it land sooner, and I'd rather ask than have you guess:

  1. A path to a small test model. The pattern already in the repo is tools/make_glm_oracle.py, which builds a tiny glm_tiny/ that the engine is scored token-exact against. If DeepSeek V4 can get the same — even a toy — then the engine proves itself on every CI run, on my box and everyone else's, without a 400 GB download. That's the single highest-leverage thing here: it turns "trust me" into "the test is green", and it's what will keep the engine alive in six months when neither of us is looking at it.
  2. Keep simplifying the implementation. 8,704 lines is a lot to carry, and the thinner it gets the faster it moves — for both of us. Anything that can lean on what glm.c already has (the streaming cache, the URING/DIRECT I/O path, compat.h) rather than reimplement it is line I don't have to review and you don't have to maintain. c/compat.h +7 is exactly the right kind of touch.

I saw the stability fix you pushed (e4f87a8) — thank you for staying on it. And CI landed today (#143 + #144): make check now runs on ubuntu/windows/macos for every PR, so you'll get a compile/test verdict in ~2 minutes instead of waiting on me.

Keeping this open. Let's keep going.

@DrewZt

DrewZt commented Jul 17, 2026

Copy link
Copy Markdown
Author

I'm interested in this — DeepSeek V4 Flash on CPU with NVMe expert streaming is squarely what colibrì is for, and I'd like it in.

The one condition is that I need to run it on my own machine first. Not as a gate to be difficult: it's the rule I've had to learn the hard way this week. EXPERT_BUDGET went in on numbers nobody had reproduced and had to be quarantined a few days later (#303) — it turned out to be slower than not using it while claiming a speedup. I'm not going to do that to a second engine. Once something is in main, people run it, and if I can't run it I can't fix it for them.

So: as soon as I can get a checkpoint on this box, I'll test it and we'll work on it together. That's not a "no" parked forever — it's the next thing I want to do on this front.

Two things that would make it land sooner, and I'd rather ask than have you guess:

  1. A path to a small test model. The pattern already in the repo is tools/make_glm_oracle.py, which builds a tiny glm_tiny/ that the engine is scored token-exact against. If DeepSeek V4 can get the same — even a toy — then the engine proves itself on every CI run, on my box and everyone else's, without a 400 GB download. That's the single highest-leverage thing here: it turns "trust me" into "the test is green", and it's what will keep the engine alive in six months when neither of us is looking at it.
  2. Keep simplifying the implementation. 8,704 lines is a lot to carry, and the thinner it gets the faster it moves — for both of us. Anything that can lean on what glm.c already has (the streaming cache, the URING/DIRECT I/O path, compat.h) rather than reimplement it is line I don't have to review and you don't have to maintain. c/compat.h +7 is exactly the right kind of touch.

I saw the stability fix you pushed (e4f87a8) — thank you for staying on it. And CI landed today (#143 + #144): make check now runs on ubuntu/windows/macos for every PR, so you'll get a compile/test verdict in ~2 minutes instead of waiting on me.

Keeping this open. Let's keep going.

Thanks — this gives me a clear integration path.

I’ll make the tiny DeepSeek V4 fixture the immediate priority. The goal will be a deterministic, independently generated checkpoint and reference that make check can validate without downloading the full model or installing PyTorch/Transformers during CI.

The initial test contract will cover:

  • target teacher forcing against the independent reference;
  • target greedy decoding against the same reference;
  • DSpark-off output matching the target reference exactly;
  • DSpark-on output remaining exactly identical to the target path;
  • compressed-attention and routed-expert execution;
  • a prompt longer than the internal 64-token batch boundary, covering the recent chunked-prefill fix.

The generator will remain available so the fixture is reproducible rather than an opaque committed artifact, while CI itself will use the checked-in tiny checkpoint and reference.

I’ll also continue removing local duplication where that does not change runtime or kernel boundaries. For the deeper shared-I/O work—particularly consolidating the streaming read path with the existing DIRECT/URING infrastructure—I would prefer to do that as a focused follow-up after this base engine and the already prepared AVX2 series land. That keeps the current correctness and performance baselines stable, avoids repeatedly invalidating the AVX2 branch, and lets the tiny token-exact oracle protect the later refactor across both scalar and optimized paths.

I’ll keep AVX2 and the dual-SSD experiment out of this PR, and I’ll document the shortest full-checkpoint smoke-test path for your machine alongside the tiny fixture.

@DrewZt

DrewZt commented Jul 17, 2026

Copy link
Copy Markdown
Author

Implemented the requested deterministic tiny DeepSeek V4 + DSpark oracle at head dac2f76.

  • Committed fixture size: 1,253,414 bytes (~1.20 MiB), including target and one-stage DSpark checkpoints.
  • Independent reference: official Transformers DeepseekV4ForCausalLM (Transformers 5.14.1), evaluated after the same dense FP8, routed-expert packed FP4, and BF16 round trips loaded by the C runtime. The C engine is never used to generate the reference.
  • Target teacher forcing: token-exact for the short, compressed-attention, and 72-token prompts.
  • Target greedy decode: exact IDs and exact length; truncated-prefix comparisons are explicitly rejected.
  • DSpark identity: drafting is exercised and enabled/disabled outputs exactly match the independent target reference.
  • Long prompt: the 72-token case crosses the internal 64-token prefill boundary for both target and DSpark.
  • Lifecycle: repeated engine/session open, generate, destroy checks pass.
  • CI: Linux x86-64 and Windows UCRT64 run the tiny oracle in make check; macOS passes the normal GLM tests and V4 platform gate. All three jobs passed: https://github.com/whale-agent-lab/colibri/actions/runs/29556719562
  • CI remains offline and does not install PyTorch or Transformers.

Manual local validation (from the repository root):

# Dedicated tiny target + DSpark token-exact oracle
make -C c deepseek-v4-tiny-check ARCH=x86-64-v3

# Or run the complete dependency-free check suite
make -C c check

To exercise the normal runtime explicitly with drafting disabled:

cd c
make deepseek-v4 ARCH=x86-64-v3
./deepseek_v4 deepseek_v4_tiny '<t005><t007><t009>' \
  --raw-prompt --draft-model deepseek_v4_tiny/dspark --no-dspark

The generator and regeneration package versions are documented in docs/deepseek-v4.md. AVX2/native-quant dispatch redesign and shared DIRECT/URING/read-helper consolidation remain out of scope.

@maikelthedev

maikelthedev commented Jul 17, 2026

Copy link
Copy Markdown
Contributor

1.12 tok/s (FP8).

sorry what?! That's impressive for NVMe.

@DrewZt

DrewZt commented Jul 18, 2026

Copy link
Copy Markdown
Author

1.12 tok/s (FP8).

sorry what?! That's impressive for NVMe.

The prompt 'What is the capital of France' have some uniqueness, the answer token got 100% dspark acceptance rate, that is the fastest speed boost.
For another 0% dspark acceptance rate special case 'hello', decode speed will drop to 0.59tok/s, which is slower than no-dspark situation

@maikelthedev

maikelthedev commented Jul 18, 2026 via email

Copy link
Copy Markdown
Contributor

@DrewZt

DrewZt commented Jul 18, 2026

Copy link
Copy Markdown
Author

On your own Pc, Vincent, how does it compare to GLM in speed if you don't mind me asking (And what are the specs of yours , and what OS) 🤔From: DrewZt @.>Sent: Saturday, July 18, 2026 12:07:24 pmTo: JustVugg/colibri @.>Cc: Maikel Frias Mosquea @.>; Comment @.>Subject: Re: [JustVugg/colibri] Add DeepSeek V4 Flash CPU inference with NVMe expert streaming (PR #165)DrewZt left a comment (JustVugg/colibri#165)1.12 tok/s (FP8).sorry what?! That's impressive for NVMe.The prompt 'What is the capital of France' have some uniqueness, the answer token got 100% dspark acceptance rate, that is the fastest speed boost. For another 0% dspark acceptance rate special case 'hello', decode speed will drop to 0.59tok/s, which is slower than no-dspark situation—Reply to this email directly, view it on GitHub, or unsubscribe.You are receiving this because you commented.

My pc is ai max 395+128gb ram+6gb/s ssd, tested glm with config think=0 and mtp=1, got around 0.7 tok/s

@JustVugg

Copy link
Copy Markdown
Owner

Status check: dev has moved substantially since this was opened (#391 refactor: glm.c → colibri.c + header modules, plus today's CUDA kernel rework in #298). A DeepSeek engine is very much on the roadmap, so this PR is interesting — but the rebase at this distance is a rewrite-sized job only the author can drive. Are you still working on it? If not I'll close it as superseded when we start the DeepSeek port, with credit for the groundwork.

@DrewZt

DrewZt commented Jul 21, 2026

Copy link
Copy Markdown
Author

Status check: dev has moved substantially since this was opened (#391 refactor: glm.c → colibri.c + header modules, plus today's CUDA kernel rework in #298). A DeepSeek engine is very much on the roadmap, so this PR is interesting — but the rebase at this distance is a rewrite-sized job only the author can drive. Are you still working on it? If not I'll close it as superseded when we start the DeepSeek port, with credit for the groundwork.

Yes, I’m still actively working on this. Please keep the PR open.

I reviewed the impact of the current dev changes, including the glm.ccolibri.c refactor and the newer build, I/O, tokenizer, and quantization infrastructure. A commit-by-commit rebase would be unnecessarily difficult, but the V4 engine itself does not need to be rewritten: most of the model-specific implementation remains isolated in files that do not exist upstream.

I’m going to treat this as a minimal forward port onto current dev, rather than replaying the old history or combining it with another large refactor.

Work I will complete before merging #165

  1. Port the independent V4 engine onto current dev

    • re-add the target and DSpark engine files on top of the current tree;
    • adapt the build targets, tests, cleanup rules, and platform gates to the current colibri build structure;
    • preserve the existing experimental engine/session API and ownership boundaries.
  2. Reconcile the shared-file changes manually

    • keep the current upstream compat.h as the base and add only V4-specific missing pieces;
    • combine the current tokenizer/o200k changes with the V4 tokenizer lifetime cleanup;
    • preserve the safetensors validation and JSON ownership fixes;
    • avoid replacing newer upstream files with their older Add DeepSeek V4 target-only CPU inference #165 versions.
  3. Keep the V4-specific runtime boundaries stable

    • retain the current V4 safetensors index and tensor I/O layer for this port;
    • retain the native FP4/FP8 quantization interfaces;
    • retain the ExpertStore, head-cache, target, and DSpark interfaces used by the validated implementation.

    The current upstream st.h and quant.h are useful foundations, but they do not yet represent the same tensor metadata or numerical formats as the V4 runtime. Folding those layers together during the port would substantially enlarge the correctness and review surface.

  4. Restore the automated correctness gate first

    • make the committed tiny Transformers oracle pass on Linux and Windows;
    • keep the macOS unsupported-runtime gate and portable infrastructure tests;
    • restore teacher-forcing, greedy, DSpark identity, lifecycle, compressed-attention, and >64-token prompt coverage.
  5. Repeat full-checkpoint validation

    • target teacher forcing;
    • greedy continuation;
    • DSpark enabled/disabled exact identity;
    • long-prompt prefill;
    • sustained decode and memory stability;
    • a fresh scalar --no-dspark performance baseline.
  6. Keep AVX2 and the deeper I/O refactor out of the base port

    • I will not change the native-quant dispatch, head-cache representation, or kernel-facing APIs during the forward port;
    • this keeps Steve’s three AVX2 commits reusable rather than forcing him to redesign the kernels.

I had already done additional local work around redundant expert reads and the streamed I/O path, but I intentionally did not push it onto #165. At that point the branch had a stable correctness baseline and Steve’s AVX2 series was stacked on top of it; changing the I/O and cache boundaries again would have invalidated both his branch and the existing measurements.

Work planned after #165 merges

  1. Steve’s AVX2 follow-up

    • rebase the focused FP4, FP8, and BF16-head kernel series onto the merged dev;
    • keep the scalar fallback and COLI_V4_AVX2=0 kill switch;
    • rerun the tiny oracle, full-model oracle, and deterministic --no-dspark A/B benchmark.
  2. Shared I/O consolidation

    • evaluate the V4 read path against the current st.h, DIRECT I/O, pipeline, and io_uring infrastructure;
    • extract only genuinely shared low-level mechanisms such as full-read handling, direct/buffered fd management, prefetch, and platform diagnostics;
    • preserve the V4-specific tensor metadata, native FP4 layout, compressed attention, and cache semantics where they do not match the GLM engine.
  3. Further implementation simplification

    • reduce duplicated infrastructure under the protection of both the tiny oracle and the AVX2 fallback tests;
    • keep that work in a focused follow-up rather than mixing model correctness, kernel optimization, and I/O refactoring into one review.

So the immediate objective is to restore #165 on current dev with the same validated model behavior and the smallest possible integration diff. Once that base is merged, the AVX2 and shared-I/O work can proceed as separate, measurable follow-ups.

@DrewZt

DrewZt commented Jul 21, 2026

Copy link
Copy Markdown
Author

The forward port is now pushed.

PR #165 now points to 9824fab, rebuilt as a fresh forward port on the current upstream dev at 4aca059.

The main colibri.c, storage/cache paths, and shared st.h/quant.h infrastructure remain unchanged. Steve’s AVX2 work and the broader I/O consolidation remain separate follow-ups.

Local make check, the tiny token-exact oracle, full-checkpoint validation, long-prompt tests, and sanitizer runs all pass.

The new PR workflows are currently waiting for maintainer approval. Could you approve them so the checks can run?

@JustVugg

JustVugg commented Aug 1, 2026

Copy link
Copy Markdown
Owner

DeepSeek-V4-Flash-0731 works is the headline here, and it changes what this PR is — it is no longer a port in progress, it is a working engine for a model people can download today. Thank you, and to @steve-m for the AVX2 kernel series and @anrasi for the datapoints.

I said earlier "today I merge it". I am walking that back, and you deserve the reasoning rather than silence, because most of it is our fault rather than yours — the tree moved underneath you while this was open.

Two things I got wrong about your PR, corrected first

I had this filed in my head as "duplicates c/json.h". It does not. You added json_free() to ours, fifteen lines, which is a contribution and not a duplicate. Sorry for the mischaracterisation.

I also described it as "seven .c files instead of the one-engine-file shape". That is unfair as stated: native_quant.c implements coli_e8m0_decode / coli_e2m1_decode / coli_e4m3fn_decode — real decoders you needed and that did not exist in this tree when you wrote them.

What changed underneath you, and why it matters

v1.4.0 shipped three days ago with two formats that land squarely on your file:

  • fmt=7 — e2m1 nibbles + UE8M0 scale per 32 (#705, Kimi K3's native MXFP4)
  • fmt=8 — fp8-e4m3 with 128×128 block scales (#528/#529, plus the FORMATS.md registry)

And here is why that is directly relevant. We measured DeepSeek-V4-Flash's actual shard headers rather than trusting config.json, and the checkpoint is:

format size
routed experts e2m1 nibbles + UE8M0 per 32 → exactly fmt=7 157.4 GB
dense (attn, shared, o-proj) fp8-e4m3, 128×128 blocks, UE8M0 scalesfmt=8 shape 8.4 GB
embed / lm_head BF16 1.1 GB

The experts are declared I8 with shapes that read as literal but are byte arrays of nibble pairs — the nibble histogram settles it: codes k and k+8 match to within 0.5%, which is e2m1 sign symmetry and which int4 cannot produce. config.json does say expert_dtype: fp4 and scale_fmt: ue8m0, in fields that are easy to miss under the quantization_config block that describes only the dense half.

So most of native_quant.c now has an in-tree equivalent, and fmt=8 needs its UE8M0 scale variant implemented (FORMATS.md already registers it as "RECOGNIZED and refused by name, not yet implemented") — which is a much smaller job than carrying your own decode path, and it benefits every engine rather than this one.

What we would like, and it is mostly deletion

The shape we have settled on across inkling.c, kimi_k3.c and olmoe.c — and it is genuinely load-bearing now that CI builds all four engines on three platforms:

  1. One engine file. c/deepseek_v4.c, the way kimi_k3.c is one file. It will still be large — your 8,792 lines are not padding, the hybrid attention and the hyper-connections are real work — but deepseek_v4_dspark.c, the six native_quant*.c, tensor_io.c and safetensors_index.c should fold in or go away.
  2. st.h instead of safetensors_index.c + tensor_io.c. Shard indexing, pread streaming, the mirror/O_DIRECT paths and the OOB hardening from security: reject malformed model tensors at the untrusted-mirror boundary (C half of #368) #413 all live there already, and an engine that reads weights its own way misses every fix the others get.
  3. quant.h for the decoders now that fmt=7 and fmt=8 exist.
  4. No .safetensors in git. c/deepseek_v4_tiny/model.safetensors and the dspark/ one should be generated by a tools/make_tiny_deepseek.py, the way tools/make_tiny_inkling.py does — binaries in history never come back out.
  5. Drop c/v4. The launcher is coli; a second entry point splits the surface that coli chat / serve / web all share.

And please wire the front end from the start

This is the part we would ask for even if nothing else changed. Users do not run engines directly — they run coli chat, coli web, coli serve. Two small edits get you all three:

# c/coli:160  model_arch()
if "deepseek" in model_type: return "deepseek"     # config.json says model_type: deepseek_v4

# c/coli:176  engine_for()
name = "inkling" if arch == "inkling" else "kimi_k3"   # -> add "deepseek_v4"

coli reads the model's config.json, picks the binary, and for non-GLM engines spawns the local gateway and attaches the TUI, so chat/web/serve/API all come from those two lines plus the chat template. Kimi K3 went through exactly this in #676 if you want a worked example.

DSpark

deepseek_v4_dspark.c is 4,057 lines of speculative decoding. Please leave it out of this PR and send it as a follow-up. Not because it is unwelcome — because a model that runs is the thing people are waiting for, and 4,000 lines of speculation review is what would keep this from landing for another week. Speculation also has to prove it earns its keep here (MTP is disabled on hosts where acceptance does not repay verification), so it wants its own measurements anyway.

What we can do to help

No deadline from us, and nothing here is a complaint about the engineering. It is a request to land the same work in the shape the tree grew into while this PR was open. If some of it does not fit the way DeepSeek V4 actually works, say so — you have run this model and we have not.

@JustVugg

JustVugg commented Aug 1, 2026

Copy link
Copy Markdown
Owner

if you want help i'm here too make this merge faster!

@JustVugg

JustVugg commented Aug 1, 2026

Copy link
Copy Markdown
Owner

Correcting myself on DSpark — I asked you to hold it back, and the reasoning I gave was worse than the counter-argument.

What I said was that 4,057 lines of speculative decoding would keep a working engine from landing for another week. That is true of the review cost, and it is the wrong thing to optimise for.

DSpark is DeepSeek's, not an optimisation we invented. If it is part of how the model is meant to run, shipping without it hands people a half DeepSeek and asks them to judge it — and the first thing everyone will want to try is exactly the thing that is missing. Judging a model without the decoding path its authors designed for it is not a fair test of either.

So: please keep it, but as the second of two consecutive PRs rather than folded into one.

  1. c/deepseek_v4.c — the engine, in the shape described above, with the coli wiring so chat/web/serve work from day one.
  2. deepseek_v4_dspark — straight after, on top of the first.

Nothing gets thrown away, you do not rewrite anything, and neither of us reviews 13,000 lines in one sitting. If the first lands and the second is a day behind it, that is fine — people can run the model while the speculation is reviewed, instead of waiting for both.

One thing I would still ask of the DSpark PR, and it is the same standard MTP is held to here rather than anything specific to your work: numbers showing it earns its keep. Acceptance rate, tokens per forward, and the wall-clock delta with it on and off, on a host where you have run both. We disable native MTP on machines where acceptance does not repay verification, and DSpark should be measurable the same way — including a way to turn it off. Our own speculative work has been humbling: EXPERT_BUDGET is quarantined in colibri.c because it did more I/O than it saved, and MTP has measured a 32% loss at some cache-hit rates.

Take the time you need on both. Nothing here is urgent in a way that should push you into rushing a 9,000-line engine — and I would rather wait than have you strip something the model needs because a maintainer told you to.

@DrewZt

DrewZt commented Aug 1, 2026

Copy link
Copy Markdown
Author

Understood — I’ll keep the existing DSpark implementation, but split the work into two consecutive PRs as suggested.

For #165, I’ll focus on the target-only DeepSeek V4 engine, migrate it to the current shared st.h and quant.h infrastructure where applicable, remove the separate launcher and committed checkpoint binaries, and wire it into coli chat, coli serve, and coli web.

I’ll preserve the current full implementation on a separate branch, then prepare the DSpark support as an immediately stacked follow-up on top of the target-engine PR. That follow-up will retain --no-dspark and include acceptance rate, tokens-per-forward, and wall-clock on/off measurements under the same hardware and memory configuration.

The target engine was developed and validated independently before DSpark was added, so this split should preserve the existing work without requiring the target runtime to be redesigned.

@DrewZt

DrewZt commented Aug 1, 2026

Copy link
Copy Markdown
Author

For the target-engine cleanup, the main shared-infrastructure blocker is that upstream fmt=8 does not yet support the UE8M0 block scales used by DeepSeek V4. Could you add the UE8M0 scale variant to the shared quant.h implementation?

The required weight format is:

raw E4M3FN weight bytes in row-major [O, I] layout;
128×128 weight blocks;
one UE8M0 byte per block;
scale shape ceil(O / 128) × ceil(I / 128);
UE8M0 decoding: 0xff is NaN, otherwise 2^(value - 127).

It should coexist with the existing f32-scale fmt=8 path. Once that shared support is available, I’ll validate it against the current V4 implementation and the full DeepSeek-V4-Flash-0731 checkpoint, then remove the private FP8 weight path from #165.

I’ll keep the activation QDQ and the CPU routed-expert kernels V4-specific for now, since they are separate from the missing shared weight-format support.

@DrewZt DrewZt changed the title Add DeepSeek V4 Flash CPU inference with NVMe expert streaming Add DeepSeek V4 target-only CPU inference Aug 2, 2026
@JustVugg

JustVugg commented Aug 2, 2026

Copy link
Copy Markdown
Owner

The piece you asked for is on dev. #779fmt=8 now accepts UE8M0 block scales alongside the f32 ones, so you can drop the private FP8 weight path whenever you are ready.

Your spec was exact and I checked it against the real tensors rather than implementing to the description: 128×128 blocks, ceil(O/128) × ceil(I/128), 0xff NaN, otherwise 2^(v-127). We have all 167 GB of DeepSeek-V4-Flash-0731 here now, and the attention sidecars read back as exactly 2^-12 and 2^-11 — UE8M0 can only encode powers of two, so a decode wrong by anything could not produce them. Dequantised weights land at |max| 0.094 / |mean| 0.018.

matmul_fp8 is untouched: the sidecar expands to f32 once at load, so the kernel stays one implementation with no branch in the hot loop. Every other format goes through the same call it did before, and an f32-scaled fmt=8 container behaves identically.

st.h needed work too, and this is the part you should read before your next push

It could not open the checkpoint at all. st_dtype_code handled BF16/F16/F32/U8/I8 and called exit(1) on anything else, so a probe pointed at the real shards died on the first tensor with unsupported dtype: I64.

#778 adds F8_E4M3 (4), F8_E8M0 (5) and I64 (6), plus st_dtype_esz() and the UE8M0 scale reader. It also closes something the addition would otherwise have opened: both float readers ended in an else that assumes F16 for any dtype that is not 0 or 2 — correct while only four types existed, and a silent trap the moment there are seven. They now refuse by name.

I can see your branch already carries its own st.h work — +132 lines, the same three dtypes plus shape[], rank, shard and sizes[512]. You wrote that because the shared reader could not read your model, which was the right call at the time. Two things follow:

The shared version is #778, and the ordinals are ours to fixst.h is read by four engines and every field added is paid for by all of them, so its shape gets decided in the shared tree rather than per-PR. Our numbering differs from yours (I64=6, F8_E4M3=4, F8_E8M0=5), and since these are internal codes that no container carries on disk, it is a find-and-replace on your side. Sorry for the churn — that one is on us for not asking what you already had before writing it.

Two of your four extra fields are worth having, and I would take them in a separate PR from you rather than reinventing them. shape[] and rank solve a real gap: st_tensor carries no shape today, which bit us in inkling.c (we had to thread the contraction dim through by hand) and bites harder on V4, where the routed experts are declared [2048, 2048] but are logically [2048, 4096] nibble pairs. Correction, and it reverses what I said about sizes[512]. I wrote that it would cost ~290 MB, having assumed it sat in st_tensor — one per tensor. It does not: it is in shards, next to fds[512]/dfds[512]/paths[512], so it is 4 KB once per model index, not per tensor. The arithmetic I objected with does not apply and I withdraw the objection.

It is also worth having on its own terms, which I should have seen given what I wrote two paragraphs down: our st.h calls fstat inside the load loop and uses fsz immediately without retaining it (st.h:439), so the bounds check that caught our truncated shard cannot be repeated afterwards — at a pread, or in a diagnostic. Keeping the size makes that possible for 4 KB. Send it together with shape[] and rank. shard I still have no opinion on.

On the download

No script needed. hf download deepseek-ai/DeepSeek-V4-Flash-0731 --local-dir <dir> in the docs, the way docs/inkling.md does it — GLM's download_glm52.py is the exception rather than the convention, and it exists because that model needed extra conversion steps.

One warning worth putting in your docs, because it cost us an hour: a download can finish truncated without saying so. One of our 48 shards arrived at 720 MB of 3.57 GB and the tool reported success — a cleanly-closed connection mid-transfer makes the read loop exit as if complete. What caught it was st.h's own bounds check (data_offsets [...] out of file bounds), not the downloader. So if the engine refuses a shard, check the file sizes against the repo before suspecting the engine. Verifying sizes after download is cheap and worth telling people to do.

Where that leaves your PR

Everything you listed on 1 August still stands: target-only engine, shared st.h/quant.h, no separate launcher, no committed checkpoint binaries, wired into coli chat/serve/web, DSpark stacked immediately behind with --no-dspark and the acceptance/tokens-per-forward/wall-clock numbers.

The blocker you named is gone. Take the time you need on the rest — and if anything else in the shared tree turns out to be missing rather than merely inconvenient, say so early. That is the second time this week the answer was "we should build that", and it is a much better use of a maintainer than reviewing a workaround.

Skater1808 pushed a commit to Skater1808/colibri that referenced this pull request Aug 2, 2026
Native fp8 checkpoints write the fmt=8 block scale as one UE8M0 byte per block
rather than one f32. The geometry is identical -- same shape, same meaning, same
multiply -- so the load path now reads the sidecar with st_read_scale_f32, which
accepts either encoding and always yields f32.

matmul_fp8 is untouched and stays a single implementation with no branch in the
hot loop, which is the point of expanding at load rather than decoding per block.

Every other format still goes through st_read_f32_cap exactly as before: fmt
0/1/2/4/5/6 are byte-for-byte unchanged. An f32-scaled fmt=8 container behaves
identically too, since st_read_scale_f32 dispatches to st_read_f32 for an F32
sidecar -- the same call that ran before.

Requested by DrewZt on JustVugg#165, where it was the one shared-infrastructure blocker
for moving the DeepSeek V4 engine onto the common quant path.

Verified against the real DeepSeek-V4-Flash-0731 checkpoint: the attention
sidecars read back as exactly 2^-12 and 2^-11, and the dequantised weights land
at |max| 0.094 / |mean| 0.018. All four engines build; make check 288 tests OK.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@DrewZt

DrewZt commented Aug 3, 2026

Copy link
Copy Markdown
Author

Following up on the shared st.h request: I opened #802 as the prerequisite PR.

#802 is based on the current dev (including #778 and #779) and deliberately contains only the shared metadata work:

  • retain rank and shape[ST_MAX_RANK] in each st_tensor;
  • retain sizes[512] alongside the primary shard file descriptors and paths;
  • reuse the recorded fstat size while indexing;
  • validate stored dimensions before retaining them;
  • leave the per-tensor shard field and V4-specific aliases out.

The branch is whale-agent-lab:codex/st-shape-rank-sizes, commit e68058d. The focused safetensors tests pass, all C gates pass on Windows/UCRT64, and the Python suite passes 288 tests with 32 platform skips.

Once #802 is merged, I will update #165 onto the resulting dev, adopt the shared dtype ordinals and UE8M0 scale path from #778/#779, remove the private FP8 weight compatibility path, and keep #165 target-only. The DSpark branch will then be restacked immediately after #165 with the requested on/off acceptance, tokens-per-forward, and wall-clock measurements.

@DrewZt

DrewZt commented Aug 3, 2026

Copy link
Copy Markdown
Author

@JustVugg, the stacked head advanced to f60e3be to include PR #802's new cross-platform hostile-shape regression tests. Could you please approve the replacement fork workflow runs?

The earlier 12e172c runs are obsolete. I will wait for the actual all-platform results for this exact head before posting the verification summary.

@anrasi

anrasi commented Aug 3, 2026

Copy link
Copy Markdown

Datapoint on the current head (12e172c) from the GB10 — the target-only migration builds and runs on aarch64, with one one-line fix you'll want before the aarch64 gate runs.

Build: make deepseek-v4 fails on aarch64/glibc at COLI_V4_UNIT_GENERATE_STATS:

deepseek_v4.c:7144:19: error: storage size of 'usage' isn't known
deepseek_v4.c:7145:19: error: 'RUSAGE_SELF' undeclared (first use in this function)

v4_serve_rss_gb() calls getrusage, but nothing in that unit includes <sys/resource.h> — on x86-64 glibc it arrives transitively, on aarch64 glibc it does not. Adding it to the unit's include block (next to #include <time.h>) fixes the build:

#ifndef _WIN32
#include <sys/resource.h>
#endif

With that in place, on this box (DGX Spark GB10: Grace 10× Cortex-X925 + 10× Cortex-A725, 121 GB LPDDR5x, Ubuntu 24.04, gcc 13.3.0):

Tiny oracle, head 12e172c: all checks PASS — teacher forcing + greedy token-exact, short/long sessions exact IDs and exact length, CLI beyond the old 512-token cap, and the persistent SUBMIT/DATA/DONE serve protocol token-exact. Fixture regenerated on-device with transformers 5.14.1 / torch 2.12.1.

Full model — the tokens/s I promised: DSpark checkpoint (48 shards) in target-only mode, coli run --ram 85, cold process, 48 generated tokens, engine lines verbatim:

ram_tiers available=85.00GiB dense=resident(6.27GiB) target_slots=143 target_cache=76.56GiB head=resident-bf16 projected=84.71GiB
v4_hot_policy pin_slots_per_layer=16 repin_interval=6 mode=resident-ram rows16=hot-pins
v4_tokens prompt=12 generated=48 total=60 expert_requests=15222 hits=11310 misses=3912 hit_rate=74.300 bytes=52300873728 target_only=1
timing time_to_first_token=37.560s after_first=93.337s
v4_rows16 packed_slots=805

= 0.51 tok/s decode, cold, TTFT 37.6 s, expert hit 74.3%, NEON rows16 active (805 packed slots). Output is coherent (a clean Rayleigh-scattering paragraph). Against the 0.82 tok/s (DSpark checkpoint, --no-dspark) reported above: that was a warm-process average — happy to add warm/repeat numbers to the verification summary if useful.

Two minor notes while here:

@jazir555

jazir555 commented Aug 3, 2026

Copy link
Copy Markdown

@DrewZt Will this work on my system with 32 GB ram and 12 GB vram? This is smaller than GLM 5.2 which runs on my system, this should be even easier and faster to run correct?

@JustVugg

JustVugg commented Aug 4, 2026

Copy link
Copy Markdown
Owner

@DrewZt is all ready for merge?

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

Labels

model-support Supporto a nuovi modelli needs-rebase Confligge, serve rebase dell'autore

Projects

None yet

Development

Successfully merging this pull request may close these issues.

8 participants