Skip to content

Quantization - #37

Merged
Ra4ster merged 2 commits into
mainfrom
Quantization
Oct 30, 2026
Merged

Ra4ster merged 2 commits into
mainfrom
Quantization

Conversation

@Ra4ster

@Ra4ster Ra4ster commented Oct 30, 2026

Copy link
Copy Markdown
Collaborator

Description

Three things, all discovered/fixed while validating whether 4-bit quantized FullPCN training works end to end, then extending Python access to match:

  1. Fixed a real gradient-killing bug in the fp4_e2m1 quantized path. derivScratch/feedbackError/proj/feedbackScratch were T* (the lossy 4-bit storage type), but sigmoid's derivative range ([0, 0.25]) is entirely below Fp4E2M1Value's smallest nonzero grid magnitude (0.5) -- every derivative silently rounded to 0, so gradient flow through the hidden layer was a complete no-op at any learning rate. Changed those four buffers to AccumT (added 5 new mixed-precision backend primitives to support it: MatMulAccumMixed, AxpyIntoAccum, AxpyIntoFromAccum, ActivationDerivativeIntoAccum, MultiplyIntoAccum). Verified bit-exact against the pre-fix float32 baseline (the fix must not change T == AccumT behavior at all), and verified via direct belief/weight-movement diagnostics that fp4_e2m1 now trains for real -- separately proven via a controlled toy classification task reaching 100% accuracy, matching float32, once inference_rate is scaled for sigmoid's smaller derivative magnitude. (Real-task accuracy on MNIST at this storage width is still an open question -- not what this PR is about; the mechanism is proven correct, hyperparameter search for a specific downstream task is separate work.)

  2. Added Python bindings for ModelIO save/load, across every bound network type (FullPCNetwork_f32/f64/fp4_e2m1, the original FullPCNetwork, DiscriminativePCNetwork, SimplePCNetwork, GaussSeidelPCNetwork, DirectKPPCNetwork, FullConvPCNetwork, ConvPCNetwork, SimpleConvPCNetwork) -- there was previously no way to persist a trained model from Python at all. Found and fixed a real bug in ModelIO.h itself while doing this: Save()/Load() never called the SyncStateDictFromDevice()/SyncStateDictToDevice() hooks that Layer.h's own doc comments say are required, so FullConvPCNetwork.save()/.load() on a CUDA-device network would have silently written/read stale host staging buffers instead of the real device weights.

  3. Fixed a systemic crash bug found while checking whether a device-resident FullPCLayerT (the quantization family) could even exist: every network constructor taking a device parameter (8 of them -- FullPCNetwork, FullConvPCNetwork, ConvPCNetwork, SimpleConvPCNetwork, GaussSeidelPCNetwork, DirectKPPCNetwork, SimplePCNetwork, FullPCNetworkT) was marked noexcept while its body calls CreateBackend/CreateBackendT, which can throw for an unsupported GPU request. Throwing out of noexcept is std::terminate() -- confirmed live: FullPCNetwork_fp4_e2m1(4, device='cuda') from Python crashed the whole interpreter (SIGABRT) instead of raising. Removed noexcept from all 8; verified each now raises a clean, catchable RuntimeError instead.

  4. Added Python bindings for the quantization formats themselves (Fp4E2M1Value, Posit4_0/1/2Value, their decode_*/encode_* functions, and their grid tables as module constants) -- previously only reachable transitively through a FullPCNetwork_fp4_e2m1 instance's train_step()/predict(), never directly inspectable or callable.

Closes #

Type of change

  • Bug fix
  • New feature
  • Performance improvement
  • Documentation
  • Build/tooling
  • Breaking change (existing C++ or Python API behavior changes)

(Not checking "Breaking change": removing noexcept from 8 constructors is source-compatible for every existing caller -- it only expands what's allowed to happen (a clean exception instead of a crash), it doesn't change any call site. Flagged for reviewers below anyway, since it's a signature-level change worth knowing about.)

Testing

  • python build.py Release builds and DeepityTests passes
  • python build.py Debug builds and DeepityTests passes (required for anything correctness-sensitive)
  • pyright passes on deepity/ (11 files, 0 errors/0 warnings) -- see reviewer notes on examples/ below

Platform(s) and compiler(s) tested: Fedora 44, GCC 16.2.1 (/usr/bin/c++), Ninja generator, CUDA toolkit not present on this machine (so the CUDA-path fixes are verified via clean-exception behavior, not a real GPU run -- see reviewer notes).

Beyond the two build.py passes above:

  • Full clean rebuild (ninja -t clean + rebuild, 267/267 targets) -- zero errors, zero new warnings (all pre-existing, confined to CPUBackendT.h's deliberate "out of scope" throw stubs for unsupported T/AccumT combinations).
  • fp4_e2m1 round-trip through the high-level FullPCN Python wrapper (not just the raw bound class): train → save → load into a fresh instance, weights match exactly.
  • Dtype-mismatch guard: loading an F32 checkpoint into an F64 network raises RuntimeError with a clear message.
  • device='cuda' through the full FullPCN(..., dtype='fp4_e2m1', device='cuda') path raises cleanly; process stays alive (previously SIGABRT).
  • All 4 quantization format decode/encode round-trips verified on-grid across 200 random values each, including the canonical-zero edge case (encode(-0.0) doesn't set the sign bit, matching the documented C++ behavior).

Performance impact

Not applicable to the Python-binding/bug-fix work (items 2-4). Item 1's new mixed-precision backend primitives (MatMulAccumMixed etc.) only run when T != AccumT (i.e. only for the quantized formats) -- the existing T == AccumT (float/double) fast paths were verified bit-exact and untouched; no perf regression expected or measured for those.

Checklist

  • I've read CONTRIBUTING.md, including the portability rules and numerical gotchas relevant to this change.
  • I've added a CHANGELOG.md entry under [Unreleased] (for any user-facing change) -- not done yet, flagging rather than guessing at wording.
  • I've updated relevant docs (README, Doxygen comments) if behavior or the public API changed -- new code has inline Doxygen comments; README itself doesn't yet mention the new Python-side save()/load()/quantization-format API surface.
  • New/changed code follows the project's .clang-format style -- for files fully authored this session (QuantFormatBindings.{h,cpp}, tBitPackedGemmPrototype.cpp, the ModelIO.h diff): ran clang-format -i, now 0 diff. NetworkBindings.cpp/LayerBindings.cpp have pre-existing formatting drift from before this PR (confirmed: main's own versions of those files have 1000+ line clang-format diffs already) -- not reformatted here, since doing so would bloat this diff with unrelated whole-file reformatting.

Notes for reviewers

  • examples/mnist_fullpcn.py was not actually type-checked. The project's own pyrightconfig.json globally excludes examples/, and that exclude wins over an explicit file argument on the pyright command line -- confirmed directly (pyright examples/mnist_fullpcn.py reports "files analyzed: 0"). Not something this PR fixes; flagging since the PR template calls out examples/ as something to check.
  • No real CUDA hardware available to test on. The noexcept fix is verified by the exception path (every constructor now raises cleanly instead of crashing), but I could not verify the success path (an actual CUDABackend constructing and running) on this machine. FullPCLayerT (the quantization family) has no CUDA backend implementation at all yet regardless (CreateBackendT unconditionally throws for DEVICE_GPU) -- this PR does not add one, it only makes the rejection clean instead of fatal.
  • ConvPCLayer/SimpleConvPCLayer's CUDA save/load safety is unverified. Unlike FullConvPCLayer, they never got SyncStateDictFromDevice/ToDevice overrides -- the ModelIO.h fix in this PR (item 2) handles sync correctly where those hooks exist and is a no-op where they don't, so it doesn't make this worse, but it also doesn't prove those two layer types are CUDA-safe to save/load. Separate, pre-existing gap.
  • tests/tBitPackedGemmPrototype.cpp is included -- a standalone, deliberately-unintegrated proof-of-concept for generic sub-byte bit-packing (not wired into CMakeLists.txt, compiles standalone via g++ -O2 -std=c++20). It's parked exploratory work, not a feature this PR claims to ship; happy to drop it from this PR if you'd rather it not ride along.
  • Item 1's fix deliberately left v (momentum) and adjoint (ePC) as T*, matching the original templating plan's own stated scope -- momentum/ePC precision at low bit-width wasn't separately validated here (both are off by default).

…M/ADAMW gradient path needed new MatMulAccum/SumRowsAccum backend methods (the old MatMul/SumRows only worked by accident when T==AccumT), and GetRequiredElements()'s arena-capacity math assumed 16-element padding, which undercounts for any T smaller than 4 bytes.

- Phase 3 (hwy::float16_t, correctness-first wrapper per your choice): replaced ~79 generic cast sites in CPUBackendT.cpp with hwy::ConvertScalarTo for cross-compiler portability; fixed a CMakeLists.txt gap (hwy was linked PRIVATE, breaking any external consumer of the now-public hwy::float16_t type).
- Phase 4 (3 posit formats — Posit4_0Value, Posit4_1Value, Posit4_2Value): mechanically identical to Fp4E2M1Value's proven pattern, as the plan predicted — all three compiled and passed verification on the first try.
- Verification harness (tests/tFullPCLayerTemplatingVerify.cpp) grew to 15 passing checks across all phases; unregistered from CMakeLists.txt now that every gate has passed, with the source file kept on disk per this project's existing convention for one-time verification tests.
@Ra4ster Ra4ster self-assigned this Oct 30, 2026
@Ra4ster Ra4ster added enhancement New feature or request cpp-core The core C++ engine labels Oct 30, 2026
@Ra4ster
Ra4ster added this pull request to stack #39 October 30, 2026 17:54
@Ra4ster
Ra4ster merged commit 4a17c68 into main Oct 30, 2026
8 of 11 checks passed
@github-project-automation github-project-automation Bot moved this from Backlog to Done in Main Feature Board Oct 30, 2026
@Ra4ster
Ra4ster deleted the Quantization branch October 30, 2026 19:49
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

cpp-core The core C++ engine enhancement New feature or request

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

1 participant