Skip to content

Honest Failure Analysis

Every “binary = 32× faster / same accuracy” claim fails somewhere. This document catalogs failure modes, evidence, and mitigations used by this repo’s architecture.


F1 — Accuracy collapse from information loss

Failure: sign(x) destroys magnitude. Deep stacks of binary layers lose capacity; ImageNet gaps of 10–20+ points were common in early BNNs.

Why: Each layer’s output is only a Hamming distance; residual high-precision signal is gone.

Mitigations (proven): - Bi-Real / ReActNet: full-precision identity shortcuts around binary convs - Keep first and last layers in higher precision (standard in Larq guides) - Scaling factors (XNOR-Net α) to restore dynamic range - Ternary weights (BitNet b1.58) with explicit 0 for gating - Longer schedules (often 200+ epochs on ImageNet-class tasks)

This repo: FP first/last layers; Bi-Real-style residuals on the binary CNN; channel scales.


F2 — Gradient approximation (STE) mismatch

Failure: sign has zero derivative almost everywhere → plain backprop dies. Straight-Through Estimator (STE) pretends (\partial\mathrm{sign}/\partial x \approx 1_{|x|\le1}). Gradient mismatch → unstable or suboptimal minima.

Mitigations: - Clip STE to ([-1,1]) (BinaryNet) - Bi-Real’s piecewise-polynomial surrogate - ReActNet RSign / RPReLU (learnable thresholds) - SURGE (2026): learnable dual-path surrogate gradients - Weight clip on latent weights (|w| ≤ 1)

This repo: classic clipped STE + weight clip; documented as a remaining research risk.


F3 — BatchNorm sensitivity

Failure: BNNs without BN almost never train. With BN, running stats / momentum can destabilize because binary activations are discrete and noisy.

Mitigations: - BN (with affine) after every binary layer — non-negotiable in practice - Slightly lower momentum (Larq often suggests ~0.9) - Prefer Adam over SGD for latent weights

This repo: BatchNorm after binary layers; Adam; BN momentum 0.9.


F4 — First / last layer exceptions

Failure: Binarizing the stem (raw pixels / embeddings) or classifier logits destroys accuracy for little compute savings (those layers are small).

Mitigation: Always leave first and last layers FP16/FP32 (or at least ≥8-bit).

This repo: enforces this in BinaryMLP / BinaryCNN.


F5 — Training instability & slow convergence

Failure: Loss oscillates; needs more epochs than FP; LR schedules transfer poorly.

Mitigations: Adam; smaller LR; optional FP pretrain → binarize; gradient clipping; progressive quantization (train FP → fake-quantize → hard binary).


F6 — “Fake binary” in frameworks (kernel support gap)

Failure: PyTorch w.sign() @ x.sign() still uses FP32 GEMM. No packing → no speedup, often slowdown (extra sign kernels).

Evidence: arXiv:1911.04477 — custom XNOR kernel beats naive control, but loses to cuDNN FP on GPU unless kernels match vendor quality.

Mitigations: - Packed bit kernels (this repo’s bnn.kernels.packed) - Larq Compute Engine / bitnet.cpp / T-MAC for production - Never claim wall-clock wins from simulation alone

This repo: separates sim mode (trainable) from packed mode (measured speed).


F7 — GPU Tensor Core reality

Failure: On A100/H100, FP16/BF16/FP8/INT8 Tensor Cores dominate. Binary popcount kernels rarely beat them end-to-end for large matmuls unless extremely carefully engineered.

When binary/ternary still wins on GPU: - Memory-bound decode with packed weights (BitNet-style) - Huge batch from smaller footprint (BitNet 70B: 8.9× throughput)

When it loses: - Compute-bound large-batch training - Unoptimized custom CUDA vs cuBLAS/cuDNN

This environment: CPU-only PyTorch — the honest place to demo packing wins.


F8 — Amdahl’s law / non-binary ops

Failure: Softmax, LayerNorm/RMSNorm, attention scores, embeddings, data loading — remain FP. End-to-end speedup << layer speedup.

Mitigation: Quantize the dominant matmuls; accept residual FP overhead; report both kernel and end-to-end numbers (this repo’s benchmark does).


F9 — Deployment mismatch (train graph ≠ inference graph)

Failure: Training stores latent FP weights; forgetting to pack/export binary weights ships a fat FP model.

Mitigation: Explicit export (pack_binary_linear); dual checkpoints; CI check that packed size ≈ FP_size/32 for binary layers.


F10 — Ternary vs binary confusion

Failure: Marketing “1-bit LLM” when BitNet b1.58 is ternary (~1.58 bits). Pure ±1 LLMs historically lagged FP quality; ternary closed the gap.

Mitigation: Use the right tool: - Extreme edge CNN → binary (+ shortcuts) - LLM quality parity → ternary BitLinear

This repo: implements both BinaryLinear and TernaryLinear.


Failure → architecture checklist

Risk Gate in our solution
Accuracy Bi-Real residuals, FP stem/head, scales
Gradients STE + clip
BN Always present
Fake speed Packed XNOR benchmark required
GPU myth Document CPU vs GPU; no false 32× claim
Amdahl Report e2e + kernel
Export Pack utilities + size asserts
LLM path Document BitNet, don’t pretend MNIST BNN solves LLMs