Headline evidence
Five KPIs at a glance — model quality, test coverage, and the honest disclosure that the channel classifier is weak on this feature set. Jetson AGX Thor latency is measured (ONNX Runtime CPU execution provider; see the Jetson section).
Engineering decision summary
The dashboard readout in six decisions: what worked, what failed, what is deployment-ready, and what still needs hardware validation.
Link adaptation result
Higher-order QAM increases bits per symbol but pushes the BER floor to higher SNR; QPSK clears the 1e-6 simulation floor far earlier than 64/256-QAM.
Multipath stress result
TDL BLER remains 11.2%-20.0% at 30 dB; the diversity-1 perfect-CSI receiver still needs coding/HARQ/MIMO to become a production link.
Estimator result
Neural denoises best at low SNR, MMSE wins once priors dominate at high SNR, and LS is the transparent baseline.
Edge deployment result
ONNX INT8 is 1.9x smaller than FP32 ONNX with +0.003 dB MAE drift; CPU latency ratio this run 1.0x, too small a model for a consistent speedup.
Weak result
Channel classifier accuracy is 0.552; current constellation statistics do not separate AWGN vs Rayleigh reliably.
Hardware validation status
Jetson AGX Thor p50/p95/p99 latency is available.
Honest boundaries
Link reliability tiers
Operational interpretation of the CP-OFDM AWGN BER curves. Thresholds are deterministic and intentionally simple: Excellent BER <= 1e-6, Usable <= 1e-3, Degraded <= 1e-2, Unreliable above that. TDL BLER is frame-level under diversity-1 perfect-CSI simulation, so it is interpreted separately as a multipath stress test, not an SLA.
| Modulation | Excellent | Usable | Degraded | Unreliable below |
|---|---|---|---|---|
| OFDM-QPSK | 14 dB | 10 dB | 8 dB | < 8 dB |
| OFDM-16QAM | 20 dB | 18 dB | 14 dB | < 14 dB |
| OFDM-64QAM | 26 dB | 24 dB | 20 dB | < 20 dB |
| OFDM-256QAM | not reached | 30 dB | 26 dB | < 26 dB |
Methodology
Everything below is regenerable by make verify from a fresh clone. Synthetic dataset for the ML layer; classical BER curves are verified against textbook predictions.
| PHY modem | CP-OFDM, 64 subcarriers, CP=16, Gray-coded square QAM (M = 4 / 16 / 64 / 256) |
|---|---|
| Channel models | 3GPP TR 38.901 TDL-A / TDL-B / TDL-C (Tables 7.7.2-1/2/3, NLOS multi-tap, ensemble-averaged Rayleigh) |
| Single-carrier baseline | QPSK over AWGN (1M bits) + flat Rayleigh ensemble (N=200 × 10k bits) |
| Channel convention | Transmit-power-SNR — verified (|h|² fade penalty does not cancel out) |
| Channel estimation | Pilot-based — LS / MMSE (exponential PDP prior) / Neural (PyTorch MLP) compared head-to-head on TDL-C |
| Link-estimation ML dataset | Synthetic 12-feature CSV — 500 samples, 125 random holdout (no oracle leakage; enforced as a hard project rule) |
| Edge deployment | PyTorch → FP32 ONNX → INT8 ONNX (dynamic PTQ via onnxruntime.quantization) |
| Validation harness | 77 pytest tests, ruff lint, CI matrix on Python 3.11 + 3.12 |
Adaptive QAM — CP-OFDM BER vs SNR (AWGN)
QPSK / 16-QAM / 64-QAM / 256-QAM on a 64-subcarrier CP-OFDM modem. Same Gray-coded square constellation logic across all four orders, normalised to unit average symbol energy. The curves below match textbook 5G NR link-adaptation tables — what a scheduler reads to pick MCS from CQI feedback.
Engineering readout: Adaptive modulation behaves as expected: QPSK is robust at low SNR, while 64/256-QAM need much cleaner channels. That makes this section the link-adaptation baseline, not a claim about production NR scheduling.
BER at fixed SNR points (AWGN)
| Modulation | 0 dB | 6 dB | 12 dB | 18 dB | 24 dB | 30 dB |
|---|---|---|---|---|---|---|
| OFDM-QPSK | 1.61e-01 | 2.22e-02 | 2.00e-05 | < 1e-6 | < 1e-6 | < 1e-6 |
| OFDM-16QAM | 2.86e-01 | 1.41e-01 | 2.85e-02 | 1.20e-04 | < 1e-6 | < 1e-6 |
| OFDM-64QAM | 3.57e-01 | 2.41e-01 | 1.13e-01 | 2.26e-02 | 2.20e-04 | < 1e-6 |
| OFDM-256QAM | 3.94e-01 | 3.07e-01 | 2.00e-01 | 9.36e-02 | 1.99e-02 | 2.01e-04 |
Reading the table: higher-order QAM packs more bits per symbol but needs higher SNR to recover them. 256-QAM still has BER 2e-4 at 30 dB; QPSK is below the 1e-6 simulation floor by 6 dB. This is exactly the trade-off behind the CQI → MCS table.
3GPP TR 38.901 TDL channel BLER — perfect-CSI receiver
Ensemble-averaged frame error rate under TDL-A / TDL-B / TDL-C (NLOS multi-tap fading from TR 38.901 §7.7.2). Each (profile, SNR) point averages 80 independent channel realisations × 4096 bits with perfect channel-state information at the receiver. BLER pinned at 1.0 below 12 dB and only 10–20 % at 30 dB is the honest diversity-1 multipath result — exactly why real 5G uses LDPC + HARQ + MIMO on top.
BLER at fixed SNR points
| Profile | 0 dB | 6 dB | 12 dB | 18 dB | 24 dB | 30 dB |
|---|---|---|---|---|---|---|
| TDL-A | 1.000 | 1.000 | 0.988 | 0.887 | 0.362 | 0.113 |
| TDL-B | 1.000 | 1.000 | 1.000 | 0.988 | 0.675 | 0.188 |
| TDL-C | 1.000 | 1.000 | 1.000 | 0.838 | 0.500 | 0.200 |
TDL-B has the widest delay-spread power → harder for the per-subcarrier equaliser to keep up. TDL-C is the "typical urban NLOS" reference used across the AI-PHY literature.
Pilot-based channel estimation — LS vs MMSE vs Neural (TDL-C)
All three estimators see the same TDL-C realisations and the same noisy received pilots (comb stride 4). The neural estimator is a small PyTorch MLP trained on ~2,500 synthetic frames covering −5 to +30 dB. Reading: neural wins at low SNR (better denoising than linear interpolation or the closed-form MMSE prior), MMSE wins at high SNR (optimal given known noise variance + delay-profile prior), LS lags everywhere. This is the textbook AI-PHY trade-off — surfaced, not polished away.
Channel-estimate MSE at fixed SNR points
| Estimator | 0 dB | 6 dB | 12 dB | 18 dB | 24 dB | 30 dB |
|---|---|---|---|---|---|---|
| LS | 2.466e-01 | 9.523e-02 | 6.518e-02 | 5.545e-02 | 5.656e-02 | 5.936e-02 |
| MMSE | 1.767e-01 | 4.407e-02 | 1.363e-02 | 3.510e-03 | 8.288e-04 | 2.356e-04 |
| Neural | 9.151e-02 | 2.933e-02 | 1.282e-02 | 7.269e-03 | 6.339e-03 | 8.354e-03 |
The left plot shows both MSE and resulting BLER side-by-side. Neural is competitive vs MMSE without needing the noise-variance / delay-spread priors — that's the DeepRx-pattern signal: a learned estimator that closes the gap to the analytical optimum.
FP32 → INT8 ONNX quantization (SNR estimator)
Same model, three deployment forms: PyTorch FP32 (training native), ONNX FP32 (portable), ONNX INT8 (dynamic post-training quantization via ONNX Runtime). The honest trade-off: 1.9× smaller file with sub-0.01 dB accuracy drift; CPU latency ratio this run 1.0×, since a model this small gains little from INT8 on CPU. This is the standard edge-AI pipeline that lands on Jetson, BlueField, or any TensorRT-backed inference target.
| Form | Holdout MAE (dB) | File size | CPU latency (µs / sample) |
|---|---|---|---|
| sklearn baseline | 1.2932 dB | — | — |
| PyTorch FP32 | 2.3267 dB | — | (in-memory) |
| ONNX FP32 | 2.3267 dB | 12,445 B | 9.77 |
| ONNX INT8 (dyn PTQ) | 2.3300 dB | 6,487 B | 9.50 |
Measured in this run on CPU: INT8 is 1.92x smaller and about the same speed per sample compared with FP32 ONNX, with an MAE change of +0.0034 dB on the holdout. Jetson AGX Thor latency is reported separately in reports/jetson_inference_benchmark.json.
Jetson AGX Thor benchmark — measured
Latency p50/p95/p99 (tail-aware, not just mean) and throughput on the actual device for both FP32 and INT8 ONNX models. Provider auto-selected in priority order TensorRT > CUDA > CPU; this run used CPUExecutionProvider (Thor CPU only; no CUDA or TensorRT execution provider was available, so this is not a GPU number).
| Model | p50 (µs) | p95 (µs) | p99 (µs) | Inferences / sec | Provider |
|---|---|---|---|---|---|
| ONNX FP32 | 5.94 | 6.07 | 6.95 | 166,814 | CPUExecutionProvider |
| ONNX INT8 | 8.79 | 9.31 | 10.41 | 99,637 | CPUExecutionProvider |
Device
- jetpack_marker_excerpt: # R38 (release), REVISION: 4.0, GCID: 43443517, BOARD: generic, EABI: aarch64, DATE: Wed Dec 31 00:15:19 UTC 2025
- jetpack_marker_file: /etc/nv_tegra_release
- jetson_model: NVIDIA Jetson AGX Thor Developer Kit
- machine: aarch64
- platform: Linux-6.8.12-tegra-aarch64-with-glibc2.39
- processor: aarch64
- python: 3.12.3
BER vs SNR — single-carrier QPSK baseline
AWGN: 1M-bit deterministic sweep. Rayleigh: ensemble-averaged N=200 realizations × 10,000 bits per SNR point, using the transmit-power-SNR convention so the diversity-1 penalty is visible (BER falls roughly as 1/SNR_linear), not cancelled by the |h|² factor at the receiver.
AWGN — full sweep (1M bits)
| SNR | BER |
|---|---|
| 0 dB | 2.42e-03 |
| 2 dB | 1.83e-04 |
| 4 dB | 5.00e-06 |
| 6 dB | < 1e-6 |
| 8 dB | < 1e-6 |
| 10 dB | < 1e-6 |
| 12 dB | < 1e-6 |
| 14 dB | < 1e-6 |
Rayleigh — ensemble averaged (N=200 × 10k bits)
| SNR | Avg BER |
|---|---|
| 0 dB | 4.18e-02 |
| 2 dB | 4.70e-02 |
| 4 dB | 2.67e-02 |
| 6 dB | 1.24e-02 |
| 8 dB | 8.29e-03 |
| 10 dB | 7.72e-03 |
| 12 dB | 5.77e-03 |
| 14 dB | 3.61e-03 |
ML link estimators — holdout performance
Four estimators trained on the synthetic link-condition CSV with a 25% random holdout. The channel classifier's weak accuracy is reported, not hidden — see the per-estimator interpretation below.
| Estimator | Primary metric | Secondary |
|---|---|---|
| SNR estimator | R² 0.687 | MAE 2.3556 |
| BER predictor | R² 0.916 | MAE 0.0036 |
| Channel classifier | acc 0.552 | (majority-class rate 0.520) |
| Link-quality scorer | R² 0.768 | MAE 5.8324 |
What each estimator is doing — and where it fails
Per-estimator interpretation in plain English. Each card includes the calibrated finding (what this model captures, why, and where the boundary is).
On AWGN links, constellation power and spread track SNR closely. Rayleigh fading scales the constellation, and symbol-averaged statistics cannot fully separate a deep fade from low SNR, so error on faded links is much larger. An earlier channel model normalized fading away and made this look nearly perfect (R² 0.999); the corrected model is the honest number.
BER follows from SNR via the Q-function in theory, but at low SNR the constellation spread carries information the textbook formula misses. Predicting measured BER from constellation statistics tracks the trend, but its error sits above the simulation's measurement resolution, so it is a rough estimate, not a substitute for the measured BER.
Honest weak result. AWGN and Rayleigh produce similar constellation statistics when averaged over symbols — distinguishing them needs higher-order features (envelope variance over time, autocorrelation) the current 12-feature set does not have. Accuracy close to the majority-class rate is the calibrated signal that this task wants a different feature design; surfaced rather than hidden.
The 0–100 link-quality target combines SNR and BER with a small Rayleigh penalty. The model inherits the SNR estimator's limits on faded links, so it is more a consistency check than a new capability.
Engineering quality signals
Repo discipline that you can verify in 60 seconds from a fresh clone.
Tests
77 / 77
pytest -q
Lint
clean
ruff check .
CI matrix
3.11 + 3.12
.github/workflows/ci.yml
End-to-end repro
one command
make verify