{"id":3931,"date":"2026-07-28T03:42:07","date_gmt":"2026-07-28T03:42:07","guid":{"rendered":"https:\/\/tucumandevelopers.com\/index.php\/2026\/07\/28\/i-taught-myself-integer-only-neural-net-training-in-rust-and-ran-into-a-real-quantization-problem\/"},"modified":"2026-07-28T03:42:07","modified_gmt":"2026-07-28T03:42:07","slug":"i-taught-myself-integer-only-neural-net-training-in-rust-and-ran-into-a-real-quantization-problem","status":"publish","type":"post","link":"https:\/\/tucumandevelopers.com\/index.php\/2026\/07\/28\/i-taught-myself-integer-only-neural-net-training-in-rust-and-ran-into-a-real-quantization-problem\/","title":{"rendered":"I taught myself integer-only neural net training in Rust \u2014 and ran into a real quantization problem"},"content":{"rendered":"<div>\n<div>\n<div data-article-id=\"4249512\" id=\"article-body\">\n<p>I&#8217;m a 10th grade student teaching myself Rust by building something instead of just reading theory. The project is called Green-AI \u2014 an attempt to explore more energy-efficient AI training by using only integer arithmetic, no floating point anywhere except for timing measurements.<br \/> Turns out this breaks in a genuinely interesting way, and fixing it taught me more about numerical precision than any tutorial did.<\/p>\n<p><strong>The problem: a &#8220;dead zone&#8221; in fixed-point training<\/strong><\/p>\n<p>A standard weight update in my integer neuron looks like this: <\/p>\n<div>\n<pre><code><span>fn<\/span> <span>update<\/span><span>(<\/span><span>&amp;<\/span><span>self<\/span><span>,<\/span> <span>error<\/span><span>:<\/span> <span>i32<\/span><span>,<\/span> <span>input<\/span><span>:<\/span> <span>i32<\/span><span>)<\/span> <span>-&gt;<\/span> <span>i32<\/span> <span>{<\/span> <span>(((<\/span><span>error<\/span> <span>as<\/span> <span>i64<\/span><span>)<\/span> <span>*<\/span> <span>(<\/span><span>input<\/span> <span>as<\/span> <span>i64<\/span><span>))<\/span> <span>&gt;&gt;<\/span> <span>14<\/span><span>)<\/span> <span>as<\/span> <span>i32<\/span> <span>}<\/span> <\/code><\/pre>\n<div>\n<\/p><\/div>\n<\/p><\/div>\n<p>That &gt;&gt; 14 is doing the job floating-point division normally does \u2014 it scales the update down to a sane range. The problem: once the error gets small enough, (error * input) &gt;&gt; 14 rounds down to 0, even though the error isn&#8217;t actually zero. The weights just&#8230; stop updating. Training silently stalls.<br \/> I benchmarked this &#8220;Standard&#8221; approach over 1000 runs: it converges fast, but lands with a final weight error of 77 compared to the true target weights. It gets stuck in the dead zone before it ever gets close.<\/p>\n<p><strong>My fix: ABSL (Adaptive Bitshift Learning)<\/strong><\/p>\n<p>My fix was to make the shift amount adaptive to the size of the error \u2014 so updates never round away to nothing: <\/p>\n<div>\n<pre><code><span>fn<\/span> <span>update<\/span><span>(<\/span><span>&amp;<\/span><span>self<\/span><span>,<\/span> <span>error<\/span><span>:<\/span> <span>i32<\/span><span>,<\/span> <span>input<\/span><span>:<\/span> <span>i32<\/span><span>)<\/span> <span>-&gt;<\/span> <span>i32<\/span> <span>{<\/span> <span>let<\/span> <span>abs_error<\/span> <span>=<\/span> <span>error<\/span><span>.abs<\/span><span>();<\/span> <span>let<\/span> <span>shift<\/span> <span>=<\/span> <span>match<\/span> <span>abs_error<\/span> <span>{<\/span> <span>e<\/span> <span>if<\/span> <span>e<\/span> <span>&gt;<\/span> <span>15000<\/span> <span>=&gt;<\/span> <span>17<\/span><span>,<\/span> <span>e<\/span> <span>if<\/span> <span>e<\/span> <span>&gt;<\/span> <span>8000<\/span> <span>=&gt;<\/span> <span>16<\/span><span>,<\/span> <span>e<\/span> <span>if<\/span> <span>e<\/span> <span>&gt;<\/span> <span>4000<\/span> <span>=&gt;<\/span> <span>15<\/span><span>,<\/span> <span>e<\/span> <span>if<\/span> <span>e<\/span> <span>&gt;<\/span> <span>1500<\/span> <span>=&gt;<\/span> <span>14<\/span><span>,<\/span> <span>e<\/span> <span>if<\/span> <span>e<\/span> <span>&gt;<\/span> <span>500<\/span> <span>=&gt;<\/span> <span>12<\/span><span>,<\/span> <span>e<\/span> <span>if<\/span> <span>e<\/span> <span>&gt;<\/span> <span>200<\/span> <span>=&gt;<\/span> <span>10<\/span><span>,<\/span> <span>e<\/span> <span>if<\/span> <span>e<\/span> <span>&gt;<\/span> <span>80<\/span> <span>=&gt;<\/span> <span>8<\/span><span>,<\/span> <span>e<\/span> <span>if<\/span> <span>e<\/span> <span>&gt;<\/span> <span>20<\/span> <span>=&gt;<\/span> <span>7<\/span><span>,<\/span> <span>_<\/span> <span>=&gt;<\/span> <span>6<\/span><span>,<\/span> <span>};<\/span> <span>(((<\/span><span>error<\/span> <span>as<\/span> <span>i64<\/span><span>)<\/span> <span>*<\/span> <span>(<\/span><span>input<\/span> <span>as<\/span> <span>i64<\/span><span>))<\/span> <span>&gt;&gt;<\/span> <span>shift<\/span><span>)<\/span> <span>as<\/span> <span>i32<\/span> <span>}<\/span> <\/code><\/pre>\n<div>\n<\/p><\/div>\n<\/p><\/div>\n<p>Large errors get damped more (bigger shift, prevents overshoot), small errors get amplified more (smaller shift, prevents stalling).<br \/> I also tried stochastic rounding as an alternative fix, which is closer to what&#8217;s actually used in quantized-training resaerch. You probabilistically round the truncated fraction up or down instead of always down, so updates accumulate correctly on average.<\/p>\n<p><strong>Results<\/strong><br \/> Algorithm <br \/> Avg time\/run <br \/> Final weight error<br \/> Converges after<\/p>\n<p>Standard (fixed shift)<br \/> 132.5 \u00b5s<br \/> 77<br \/> 1928 steps<\/p>\n<p>Stochastic Rounding<br \/> 206.8 \u00b5s<br \/> 9<br \/> 846 steps<\/p>\n<p>AdaptiveShift (ABSL)<br \/> 143.6 \u00b5s<br \/> 5<br \/> 2230 steps<\/p>\n<p>ABSL ends up the most accurate of the three, and noticeably faster per step than stochastic rounding. The trade-off: it takes more steps to fully settle, likely because it keeps making meaningful updates near the target instead of stalling out early \u2014 accuracy over speed-to-convergence.<\/p>\n<p><strong>What I&#8217;m still figuring out<\/strong><br \/> The shift thresholds (15000, 8000, 4000&#8230;) are hand-tuned magic numbers for this one neuron&#8217;s weight scale. I don&#8217;t yet know how to make them scale automatically to different layer sizes.<br \/> Everything so far is tested on a single neuron, not a real multi-layer network with backprop.<br \/> Is &#8220;adaptive step size based on error magnitude&#8221; a known technique outside of fixed-point contexts? It feels conceptually close to things like Rprop, but I got here from a completely different angle (avoiding integer truncation).<\/p>\n<p>Repo&#8217;s here if you want to poke at the code: <a href=\"https:\/\/github.com\/Mojo0869\/green-ai\" target=\"_blank\" rel=\"noopener noreferrer\">https:\/\/github.com\/Mojo0869\/green-ai<\/a><\/p>\n<p>Feedback very welcome, especially from anyone who&#8217;s worked with quantized or fixed-point training before \u2014 I&#8217;d love to know what I&#8217;m missing.<\/p>\n<\/p><\/div>\n<\/div>\n<\/div>\n<\/div>\n<p>Fuente: <a href=\"https:\/\/dev.to\/mojo0869\/i-taught-myself-integer-only-neural-net-training-in-rust-and-ran-into-a-real-quantization-problem-bli\">Art\u00edculo original<\/a><\/p>\n","protected":false},"excerpt":{"rendered":"<p>I&#8217;m a 10th grade student teaching myself Rust by building something instead of just reading theory. The project is called Green-AI \u2014 an attempt to explore more energy-efficient AI training by using only integer arithmetic, no floating point anywhere except for timing measurements. Turns out this breaks in a genuinely interesting way, and fixing it [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":3930,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":"","jetpack_publicize_message":"","jetpack_publicize_feature_enabled":true,"jetpack_social_post_already_shared":true,"jetpack_social_options":{"image_generator_settings":{"template":"highway","default_image_id":0,"font":"","enabled":false},"version":2}},"categories":[41],"tags":[],"class_list":["post-3931","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-devto"],"jetpack_publicize_connections":[],"_links":{"self":[{"href":"https:\/\/tucumandevelopers.com\/index.php\/wp-json\/wp\/v2\/posts\/3931","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/tucumandevelopers.com\/index.php\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/tucumandevelopers.com\/index.php\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/tucumandevelopers.com\/index.php\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/tucumandevelopers.com\/index.php\/wp-json\/wp\/v2\/comments?post=3931"}],"version-history":[{"count":0,"href":"https:\/\/tucumandevelopers.com\/index.php\/wp-json\/wp\/v2\/posts\/3931\/revisions"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/tucumandevelopers.com\/index.php\/wp-json\/wp\/v2\/media\/3930"}],"wp:attachment":[{"href":"https:\/\/tucumandevelopers.com\/index.php\/wp-json\/wp\/v2\/media?parent=3931"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/tucumandevelopers.com\/index.php\/wp-json\/wp\/v2\/categories?post=3931"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/tucumandevelopers.com\/index.php\/wp-json\/wp\/v2\/tags?post=3931"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}