Rung 6 (M9) — HARD GATE #1 — Eigen MLP + pybind11/nanobind¶
Nav: ← Rung 5 · Rung 7 → · Source: Phase 5 · P5.1/P5.2
HARD GATE
If this artifact is not shipped by end of Month 9, the roadmap PAUSES for 14 days, a post-mortem is written to
../99_pre_mortem/, and the artifact is rescoped and shipped before Month 10 begins.Why this is a hard gate: everything after M9 (production inference, capstones) assumes you have a working C++/Python ML integration. Without Rung 6, Rung 7 has no model to serve and Rung 8 has no library to package. This is the first artifact on the ladder that a hiring manager for an applied C++/ML role in 2026 will actually value on its own merits. Everything before this is prerequisite; everything after is amplification.
Rung 6 is where the whole roadmap either pays off or it doesn’t. It is the smallest possible artifact that credibly says: “I write C++ that Python people can pip install.” That sentence is the entire pitch of an applied-C++/ML engineer in 2026.
What It Is¶
A public GitHub repo named mynn-cpp containing:
A minimal MLP implementation in C++20, using Eigen for dense linear algebra.
Layers:
Linear,ReLU,Softmax,CrossEntropyLoss. That’s it. No convnets, no attention, no batchnorm. This is the base scope.SGD optimizer with configurable learning rate and momentum. Optionally Adam if time permits.
Manual backprop (write the gradients yourself — do not use autodiff). This is deliberate; the point is to prove you understand the math and can express it in Eigen.
Trained on MNIST, reaching ≥ 95% test accuracy.
Python bindings via both pybind11 and nanobind — two branches or two subdirs. Ship one; the other is a stretch.
Published as a test PyPI package that installs with
pip install --index-url https://test.pypi.org/simple/ mynn-cpp.A Jupyter notebook
demo.ipynbthat: installs the package, trains an MLP on MNIST, reports accuracy, plots a loss curve, and compares to a matched-hyperparameter PyTorch baseline.A blog post writeup on your personal blog (even a plain gh-pages markdown blog is fine).
Why It Matters (Employer Signal)¶
One line: “Applied ML engineer who owns the C++ layer — can wrap it for Python users and ship it.”
This is the exact job description at the ML-inference and ML-platform teams that pay top of market in 2026. Nanobind has been adopted by JAX, MLX, and PennyLane; pybind11 is still the incumbent. Being fluent in the transition between the two is a specific, current signal. Combined with the Eigen numerical layer, this rung produces the specific artifact that inference-team hiring managers screen for.
The blog post is not decorative. It is the artifact that gets shared, forwarded, and links back to the repo. Without it, the repo lives in obscurity.
Acceptance Checklist — THE HARD-GATE CRITERIA¶
Every box must be checked before M9 closes. If any box is unchecked at end of M9, invoke the reset protocol below.
Correctness¶
MNIST test accuracy ≥ 95% (784 → 128 → 64 → 10, or similar).
Matches PyTorch to within 1% on the same random seed and hyperparameters (loss trajectory should track).
Gradient check: numerical vs. analytical gradients agree to 1e-5 relative error on a small network.
Performance¶
Compiles with
-O3 -march=native -DNDEBUG -DEIGEN_NO_DEBUG.Training-time benchmark against PyTorch on the same hardware, same batch size, same epochs. Document the number honestly (win, loss, or tie).
Inference latency benchmark: single-sample and batch-256, in microseconds.
Packaging¶
pip install-able from GitHub (pip install git+https://github.com/YOU/mynn-cpp.git).Uploaded to test PyPI.
pyproject.toml+ scikit-build-core or setuptools-based build.Wheel builds for at least Linux x86_64 via
cibuildwheelin CI.Import works:
import mynn; net = mynn.MLP([784, 128, 64, 10]); net.forward(x).
Bindings¶
pybind11 binding shipped (base scope).
NumPy array interop works both directions (input + gradient output).
Zero-copy where possible; document where you copy.
Stretch: nanobind binding on a second branch, benchmarked for compile time and import time against pybind11.
Signal¶
README.mdwith an architecture diagram (draw with excalidraw or plain ASCII — both fine).demo.ipynbnotebook committed; renders on GitHub.Blog post published on personal blog covering: motivation, architecture, backprop derivation, PyTorch comparison, honest performance numbers, what you would change.
Blog post shared on r/cpp, r/MachineLearning, and Hacker News (Show HN).
LinkedIn post with the blog link, targeted at your Zoho + wider ML network.
CI + Quality¶
CI: build + test + wheel-build on Ubuntu latest.
Tests pass under ASan and UBSan.
clang-tidyruns in CI on the C++ code; no new warnings.License: MIT, with attribution to Eigen (MPL2).
Reset Protocol — What Happens If This Ships Late¶
If end of M9 arrives and any acceptance box is unchecked, do the following. Do not negotiate with yourself.
STOP. Do not begin Month 10 material. Close the M10 folder. Put the tabs away.
Two-week pause. Fourteen days from the day M9 ended.
Post-mortem in
../99_pre_mortem/. Write a dated file2027-XX-XX_m9_gate_miss.mdcovering:What was scoped for M9 and what shipped.
Which specific acceptance boxes are unchecked and why.
The single blocking constraint: time, knowledge gap, motivation, or scope. Pick one — they usually don’t overlap as much as it feels.
One concrete change to your process going forward.
Rescope. Take the artifact down to the smallest version that ships in 14 days:
Cut nanobind if you did pybind11 (or vice versa).
Cut the PyTorch comparison if the C++ trains at 95%.
Cut the blog post to a README-length writeup (still on personal blog).
Cut Adam if only SGD works.
Keep: 95% MNIST, pip-installable, blog writeup.
Ship the rescoped version by day 14.
Resume the roadmap one month behind. M10 becomes what M11 was; M11 becomes what M12 was. The Rung 7 hard gate moves to end of M12 accordingly.
This is not a punishment protocol. This is the mechanism that keeps 13 months of effort from producing zero shipped artifacts.
Common Ways This Rung Fails¶
Scope creep into a full framework. You start adding conv layers, dropout, batchnorm. Stop. Ship the MLP. You can add layers after publication as v0.2.
PyTorch comparison is too aggressive. You compare against PyTorch on GPU. Meaningless. Compare CPU-to-CPU with identical hyperparameters and identical batch sizes. Even then, expect to lose — PyTorch’s CPU kernels are extremely optimized. Losing is fine; report the number.
You get to 92% MNIST and call it good. No. 95% is the gate. If 95% is not achievable with your MLP topology, you need a bigger hidden layer or a better initialization (He init, not zero init).
You skip the pip-installable step because “it works locally.” The pip-installable step is the whole point of pybind11. Skipping this collapses the rung’s signal from “applied ML engineer” to “person who wrote an MLP.”
You don’t write the blog post. Then no one finds the repo. The blog post is what turns the artifact into a signal. Write 1500 words minimum; more is fine.
You pick nanobind AND pybind11 as base scope. Pick one for base. The other is a stretch. If you try both, you ship neither.
What Most People Get Wrong¶
They underestimate the packaging step. Writing an MLP in Eigen is a weekend. Making it pip install-able across Python versions and platforms via CI is the hard part, and it’s the part that carries the entire signal of the rung. Budget at least a full week of M9 for packaging. Use cibuildwheel — do not try to hand-roll wheel builds.
The second failure mode: they forget that this is a communication artifact, not just a code artifact. The blog post, the diagram, the loss curve chart, the honest PyTorch comparison table — those are what a hiring manager reads. The code they will skim for 90 seconds. Optimize the top of the funnel.
Third: they compare against PyTorch and get discouraged when they lose on speed. PyTorch’s CPU kernels use MKL/oneDNN and are the product of hundreds of engineer-years. You will lose. The signal is that you tried the comparison and reported honestly, not that you won.
Extension Challenges (Only After Base Ships)¶
nanobind binding on a branch, with a compile-time + import-time benchmark against pybind11. Blog addendum. Very current in 2026.
CPU SIMD hand-tuning of one Linear layer (AVX2 gemm) and benchmark against Eigen’s version. High signal for HFT-adjacent roles.
CUDA backend — large scope. Only if you have absurd slack. This shifts the signal into GPU territory, which is not your core positioning — skippable.
Quantized inference (int8) with a small calibration set. Interesting angle for inference-engineering roles.
ONNX export so a trained
mynnmodel can be loaded into ONNX Runtime. This is the natural bridge to Rung 7.
The ONNX-export stretch is the most strategically useful. It makes Rung 7’s onboarding easier because you already have an artifact you can serve.
Links to Source Phase Files¶
Engineering plan:
../06_phase_5_cpp_for_ml_data/— P5.1 (Eigen MLP) and P5.2 (pybind11 wrap) specs.Packaging tooling:
../11_tools_setup/— cibuildwheel, scikit-build-core.If M9 is drifting:
../13_discipline/, and pre-write your post-mortem in../99_pre_mortem/at week 3 of M9 as an early-warning signal.
Nav: ← Rung 5 · Rung 7 → · Source: Phase 5 · P5.1/P5.2