Field · Weak-Signal Decoding and Machine Learning

Stones from another hill.

The classical decoding chain already sits at the edge of theory: matched filtering is optimal detection for a known waveform, and LDPC belief propagation is near-optimal error correction. Adding deep learning on top means answering one question first: where is the gap? This is what we measured, the three walls we hit, and exactly where.

Real target: 69% missed at −26 dB End-to-end: 0/30 at −20 dB Ten-config union = one config's 100% Data generation was 4.5× the training step

BG1SB  ·   ·  about 14 minutes

The most useful output of this work is not a model. It is three tables: where the target is, how hard the walls are, and what we did not test. All three can be recomputed, and all three can be overturned.

Four questions this article answers

  • FactWhere is the actual gap in weak-signal decoding?
  • FactHow far is an end-to-end model from the classical chain, and why?
  • InferenceHow can a training objective offer a shortcut that lowers loss without decoding better?
  • ExcludedWhich conclusions did we never verify at all?
Three-minute read:§01 Target§03 Walls§05 Ask

The target is low SNR, not overlap

Three tables settled every design that follows. None came from preference.

FT8 is a weak-signal mode: 79 symbols of 0.16 s each, data carried by a (174, 91) LDPC code, the whole burst 12.64 s inside a 15 s slot. Decoding runs in four steps: a sync search, per-symbol tone soft values, belief propagation, then a CRC and callsign-structure filter. The first question is how much room that chain has left.

We swept the configuration knobs — profile, cycles, sensitivity, low threshold — into ten configurations and ran their union over the same real corpus. The union stopped growing: the deepest single configuration already reached 98.8% of it.

Fact · The knob space is spent

150 real slots, 2936 messages. The configuration that had been in production reached 95.2%; a three-configuration union 99.1%; the best single configuration 100.0% of the reliable reference set (messages at least two configurations agree on). Going higher means changing the input or the judge, not the parameters.

Changing the judge is not an option: the decoder is part of the product and the only trusted referee. So the question becomes where weak signals are lost. We built a simulator — why it can be trusted is the next section — and swept signal-to-noise with synthesized signals inside real noise.

Nominal SNR Fast path Deep configuration
−26 dB6%31%
−24 dB19%63%
−22 dB56%100%
−20 dB81%100%

In the real corpus, 10.0% of decoded messages sit at −18 dB or below, 2.9% at −20 dB or below. There are people on the cliff: at −26 dB, 69% of the signals are missed.

One result ran against intuition and deserves its own paragraph. We expected same-frequency overlap to be the target. Every slot averages sixteen signal pairs overlapping in time within 25 Hz, which sounds exactly like the case where the classical chain fights itself. It does not: the deep configuration already reaches 98.8% of the union — the nine subtraction passes inside it exist for this case. There is little to collect there.

Inference · Why only the weak tail is left

Overlap is handled, strong signals are handled. What nobody handles is a signal whose absolute strength is below the detection threshold. No trick applies there, only theory: can energy be gathered from a longer coherent integration?

Labels come from the encoder itself

For a simulation to be trusted, its labels must come from the same place as the signal on the air.

Training needs (noise, clean) pairs. We did not reimplement the FT8 packing rules — that creates a second copy of the standard, and that is where a training target starts drifting. Instead the encoder answers: call the low-level encode interface for a clean waveform, take a discrete Fourier coefficient at each of the eight tone frequencies per symbol, keep the strongest, and read 174 bits back through the inverse Gray mapping. No noise, no demodulation error.

One thing only became clear while doing it: this code has two parity-check matrices. Both are correct, and they have different jobs.

MatrixSourceRow weightJob
Dense H = [G | I] generator in the encoder's source about 45 verification: H·c = 0
Sparse Tanner graph table in the decoder's source, three checks per bit 7 decoding: belief propagation, graph attention

I ran decoding on the dense one first, and the result was strange: satisfied checks fell while the bit error rate rose (42 to 36, 12.2% to 16.2%). That is what a decoder looks like when its graph is not the code's graph. Min-sum is defined on the sparse graph; the dense form is only another basis of the same code. With the decoder's own table, belief propagation behaved at once: BER 0.0018 at σ=0.7, and 97.2% of whole codewords.

Fact · The noise floor is real

The noise is not white: it is the noise floor of real slots, with real atmospheric noise, fading and in-band interference. Busy slots get their already-decoded signals notched out by 34 dB before serving as a bed — otherwise other stations land in the noise target, and the first thing the model learns is to suppress real signals (which then actually happened). The SNR calibration was measured too: on a white bed, nominal and decoder-reported SNR agree within 1 dB; on a real bed the report runs systematically 3 dB high, from a ratio bias under non-stationary noise.

One yardstick for everything after: the SNR-versus-recall curve on synthesized signals, plus the union gain and precision checks on real slots. Every idea gets compared on the same data.

Three attempts, three walls

Each wall has a reproducible fingerprint, not an impression.

Wall one: a demapper alone is doomed to tie

The first model predicted sets: input the slot's complex time-frequency plane, output queries, each carrying presence, frequency, delay and the 174-bit codeword. Phase must be kept — at these SNR levels the evidence comes from coherent integration, and discarding phase throws away half the available energy.

Two architecture mistakes came out of looking at the data rather than guessing. First, the prediction head mean-pooled over all symbols, which discards ordering; the model sat at 65% bit accuracy until it predicted three bits per symbol instead. Second, the STFT frame grid is 40 ms while a symbol is 160 ms, and a signal can start anywhere, so every group of four frames straddled two symbols — at +10 dB, a plain argmax could not read the tones.

Fixed, it trained to 97% bit accuracy. At the message level it produced nothing.

SNRModel, whole codewordClassical decoder
−26 dB0/30 (74.3% bits)13/30
−24 dB0/30 (81.1% bits)22/30
−22 dB0/30 (88.6% bits)28/30
−20 dB0/30 (93.7% bits)30/30

The reason is arithmetic. At −20 dB the model gets 93.7% of bits right, and a message needs all 174: 0.937 to the power of 174 is about one in ten thousand. The classical chain collects roughly 10 dB of coding gain from LDPC there. The model collected none.

Ninety-seven percent accurate, and not one message came out.

Inference · Per-bit objectives cannot learn a code

Accuracy is a misleading metric here. It treats all 174 bits alike, while a message demands all of them; that gap is precisely why error-correcting codes exist, and it is the part a per-bit objective can never close.

Wall two: putting the code's structure into the loss

If per-bit does not work, make "is this a valid codeword" part of the loss. There is a differentiable form: each check is the XOR of the bits it touches, so the probability that a check holds can be written as a soft XOR.

I tried two versions and ended up without a trustworthy implementation.

FormValuesGradient
Sign/magnitude soft XOR valid codeword 0.05, one flip 0.98, random 0.5 (= 0.693) 1e-19 (that version)
LDPC tanh rule contradictory values after two rewrites not verified

The first returned gradients of order 1e-19. The second, the tanh rule from the literature, gave 0.5 for one input computed directly and 2.98 for the same input through the model path — the two rewrites disagree, which means the fault is in my implementation, not in the idea. So this is recorded as a failed implementation and not as a conclusion.

Where you cannot measure a gradient, suspect your ruler first.

Wall three: a graph-attention decoder

The third route makes decoding its own component: input 174 log-likelihood ratios, output refined ones, with attention masked by the Tanner graph — bits sharing a check can see each other, and graph attention is exactly message passing. The model then only has to refine, not infer the code.

The baseline had to be fixed first (the sparse-graph lesson above). Fixed, belief propagation is strong: BER 0.0018 and 97.2% whole codewords at σ=0.7; 0.0321 and 65.2% at σ=0.8.

σRaw decisionBelief propagationNeural decoder
0.700.07590.00180.0759
0.800.10740.03210.1073
1.000.15910.14210.1592

The neural decoder's output does not equal its input. It scales it by two: out ≈ 2.005 × in, correlation 0.9998, and the per-bit sign agreement is 99.88% — BER 0.1058 against 0.1059, so almost no decision changed. It learned a gain.

The scale of a log-likelihood ratio is an arbitrary convention; a hard decision does not depend on it, but the cross-entropy used for training does. Doubling the ratios lowers the loss slightly, so the model found the shortcut: no better decoding, lower loss. Between the identity and a perfect output there is 0.299 of loss headroom, and the optimiser never went there.

Thesis · Scale is not the only problem, but it is today's problem

This run was 8,000 steps, fifty minutes, 0.8 M parameters. Comparable work in the literature uses 10⁵ to 10⁶ steps plus re-encoding tricks. Our reading: the route is not falsified, but it has been shown impossible to judge on this machine's timescale.

Keep the books on the card, not on the feeling

Four traps, each reproducible, each worth a test.

The first trap was compute in the wrong place. Generating one sample took 46 ms while a training step on sixteen samples took 161 ms — data generation was 4.5 times the model. A rented GPU would have spent most of its time waiting for the CPU. Two changes brought it to 133 ms: a thread pool (the heavy calls release the GIL) and caching the codeword (every sample was re-deriving it by encoding a waveform and reading its tones back, 20 to 40 ms of pure waste). A step went from 771 ms to 133 ms, and the bottleneck moved back to the accelerator.

The second trap was a sign convention. The loss read "output above zero" as bit one, while the LLR convention reads "above zero" as bit zero. The two are opposite, and the model learned to invert the entire channel.

BER 95.2% against a raw 4.8%. One minus, and that is the fingerprint.

Without the baseline column, that error reads as "the model is bad" instead of "the convention is reversed".

Fact · Two more traps

A mask tensor has to be registered as a module buffer, or the first batch fails with a device mismatch on the accelerator. And a disabled loss term must not be computed at all, not multiplied by zero — zero times NaN is NaN, so a term that is off by default can make the whole run diverge.

The compute estimate is written down too, for anyone in the same position: the whole-slot set-prediction model runs about 4.8 s per step on this laptop's eight-core GPU, which is five and a half days for 100,000 steps. A 24 GB desktop card should do 0.15 to 0.3 s per step: four to eight hours. That is not a small difference.

We need experience, not compute

Each question carries the measurements above, so nobody has to reproduce them first.

Our reading, open to disagreement. Learning can only enter where the classical chain cannot reach: for a single signal in white noise, matched filtering plus belief propagation already sits at the edge of theory. A learned demapper is doomed to tie and not worth the effort. Two places could genuinely win — a learned decoder on the graph, and joint detection and separation of multiple signals, because the classical chain is greedy candidate by candidate.

① How should the code enter the loss?

We tried a differentiable soft XOR and the LDPC tanh rule; one returned gradients of 1e-19 and the other exactly zero at high confidence. Among re-encoding, unrolled belief propagation and learned damping, which is most stable for a short code with a known graph? Is there a standard approach we are missing?

② Which architecture at this size?

At 174 bits with a known Tanner graph, what pays best: a graph-attention transformer, unrolled belief propagation, or classical belief propagation with a learned prior (such as a learned LLR correction)? Do we need a transformer at all, or is unrolled BP enough?

③ Is end-to-end worth attempting?

If the value of learning can only come from non-white channels — fading, multiple users, interference — how should the benchmark be designed? We have two thousand real slots and an exact simulator; what scenarios can we build where the classical chain provably loses?

④ Data efficiency and domain transfer

We measured that training on synthesized signals suppresses real ones (the enhanced copy lost a fifth), and that regenerating real signals into the training target did not help. With a thousand real examples and unlimited exact simulation, what is the most effective transfer approach?

⑤ Where do soft values come from?

The native chain exposes no LLRs. The options are a host-side demapper from audio, or closing the loop inside a learned chain. Is there a third way — for instance a model that only ranks candidates or corrects the a-priori information?

⑥ Where should the compute go?

If we get one shot: 100,000 steps of a whole-slot model, four to eight hours on a 24 GB card. Worth it, or better spent on smaller and more targeted experiments — questions one and two, for instance?

Excluded · What this article does not establish

We never trained on a GPU; every compute figure is an extrapolation. We never wired a learned decoder into the product, and never ran a learned decode end to end on real hardware. We did not show a graph-attention decoder cannot reach belief propagation — only that it did not on this machine's timescale. We did not test whether learning wins in non-white channels, which is exactly what we are asking about. We ran no experiments at all on joint multi-signal separation. And putting the code's structure into a loss is not even a failed idea here: we never produced a trustworthy implementation of it. Read that section as a failure log, not a finding.

More was left behind than concluded

A negative result is an asset, provided it can be recomputed.

Four reusable things came out of this round, independent of who won.

An exact label pipeline

Message to waveform to codeword, all from the encoder itself: unlimited simulated data with bit-exact labels. The sync pattern is asserted in the tests, because it is the only unambiguous witness that the whole chain is aligned.

Two verified parity-check matrices

Dense for verifying codewords, sparse for decoding, both parsed from the codec's own source and cross-checked against the encoder's output — any drift turns a test red.

A correct belief-propagation baseline

Along with the lesson about which graph it belongs on. Without that baseline, the table reading "95.2% equals one minus 4.8%" is just noise, and the sign convention error stays invisible.

The classical chain's measured ceiling

From 31% at −26 dB to 100% at −20 dB, and the fact that a ten-configuration union equals one configuration. Any smarter idea gets compared against this curve first.

Stating conclusions before the evidence is the easiest mistake in an article like this. We tried to work the other way: every claim has a table behind it, and every table can be rerun. The title borrows a line from the Book of Songs — stones from another hill can polish jade. The stones are in hand; the question is how to cut.

Fact · What shipped along the way

Two things from this work are live and stable. Unioning decoder configurations pushed recall to 100% of the reliable reference set, and two-stage publication shows decodes in the interface 2.4 s earlier (the fast path publishes first, the deep answer replaces the same slot, and the sequencer only ever consumes the complete batch). The masking enhancement runs host-side at a 0.7 noise-floor coefficient, adding 0.5% to 0.9% net, unioned with the untouched copy so it can only add.