ML-Based BSR Periodicity Prediction
ML-Based BSR Periodicity Prediction
periodicBSR-Timer applied to every UE, a small learned model predicts each UE's next uplink data arrival, maps it to the nearest legal timer, and reconfigures that UE over F1AP. No ML runtime is linked into the RAN, and the whole feature is disabled by default.The periodicBSR-Timer (TS 38.331 BSR-Config) is a single static value applied to every UE. A short timer keeps the gNB's view of the UE buffer fresh but costs signalling; a long timer is cheap but the gNB learns about new uplink data late. This feature predicts each UE's next uplink data interarrival time from its recent history, snaps it to the nearest legal periodicBSR-Timer, and reconfigures that UE at runtime, per UE.
bsr_ml block.Source. This feature is part of OCUDU RAN: github.com/TOSSI-Foundation/OCUDU-RAN.
How It Works
The model
A Support Vector Regression over Random Fourier Features (SVR-RFF). The input is a sliding window of the last 30 uplink interarrival times (in slots) for one UE; the output is the predicted next interarrival, in slots.
StandardScaler -> RBFSampler(gamma=1, n_components=100) -> SVR(kernel="linear", C=100, epsilon=0.1)
Inference in C++ is a direct evaluation of the fitted arrays, so no ML runtime is linked into the DU:
x' = (x - scaler_mean) / scaler_scale
Z_j = sqrt(2/n) * cos(x' . W_j + b_j)
y = sum_j coef_j * Z_j + intercept
Mapping to the standard
The predicted slot count is converted to subframes (y / 2^numerology) and snapped to the nearest legal periodicBSR-Timer value (TS 38.331). infinity is excluded, as it has no finite distance.
1, 5, 10, 16, 20, 32, 40, 64, 80, 128, 160, 320, 640, 1280, 2560 (subframes = ms)
Training data
Only regular BSRs are used (TS 38.321 clause 5.4.5). Periodic and padding BSRs are timer- and grant-driven, not new-data arrivals, so they carry no interarrival signal. The in-RAN dataset logger classifies every BSR by trigger type and records the interarrival only for regular ones.
The damping gate
A raw prediction stream would thrash the UE with reconfigurations, so three checks run in order (du_manager_impl.cpp) before any change is applied:
| # | Check | Effect |
|---|---|---|
| 1 | already-applied | skip if the recommendation equals the value in force |
| 2 | hysteresis | skip if the recommendation is within one index step of the value in force |
| 3 | min-dwell | skip if fewer than 10 s have elapsed since the last reconfiguration |
Checks 2 and 3 are guarded on a previously applied value, so the first move away from the configured static timer is always allowed. Both constants (BSR_ACT_HYSTERESIS_STEPS, BSR_ACT_MIN_DWELL_SEC) are compile-time and are not exposed in the YAML.
Actuation
Once the gate passes, the DU rewrites bsr_cfg.periodic_timer, packs a CellGroupConfig diff, and sends F1AP UEContextModificationRequired (TS 38.473 clause 8.3.5) with DUtoCURRCInformation.cellGroupConfig and cause = action_desirable_for_radio_reasons. The CU-CP forwards it to the UE as an RRC Reconfiguration, and the UE applies the new timer. The DU sends and returns; it does not wait for UEContextModificationConfirm / Refuse.
Key components
| Path | Role |
|---|---|
lib/scheduler/support/bsr_periodicity_predictor.h | Inference, runtime model load, hot-swap. |
lib/scheduler/support/bsr_periodicity_model.inc | Compiled seed model (the fallback baked into the binary). |
lib/scheduler/logging/bsr_ml_dataset_logger.{h,cpp} | BSR trigger classifier and dataset logger. |
lib/du/.../du_bsr_periodicity_actuation_procedure.{h,cpp} | F1AP actuation procedure. |
ml/training/train_bsr_periodicity.py | Train a model; export the .inc and .model. |
ml/analysis/analyze_bsr_run.py, compare_runs.py | Per-run BSR counts and overhead; side-by-side run comparison. |
The shipped seed was trained on 60,386 sliding windows from 15 real captures. It is compiled into the DU binary via the .inc, so inference works out of the box with no external model file. A .model file is an optional hot-swappable override, reloaded on mtime change without a restart; the .inc and .model should be exported from the same training run.
Configuration
All ML parameters live in the DU YAML bsr_ml block, and everything is disabled by default:
bsr_ml:
inference:
enabled: false # predict a periodicBSR-Timer per UE
model_path: ml/models/bsr_periodicity.model # runtime model; empty -> compiled seed
actuation:
enabled: false # apply the prediction via F1AP; needs inference
dataset_logging:
enabled: false # write one CSV row per BSR
output_dir: ml/datasets
scenario: default # tag written into each row
The static baseline the ML moves away from is a normal MAC setting, not part of the bsr_ml block:
cell_cfg:
mac_cell_group:
bsr_cfg:
periodic_bsr_timer: 40 # subframes; must be a legal TS 38.331 value
The three flags are independent layers, meant to be enabled in order:
| Mode | Flags | Behaviour |
|---|---|---|
| Collect | dataset_logging | Writes the CSV. No prediction, no change. |
| Shadow | + inference | Predicts and reports per UE. Nothing is applied. |
| Closed loop | + actuation | Reconfigures the UE over F1AP. |
actuation without inference is a no-op. Logging must be at info or the actuation lines are not printed. Python dependencies: numpy, scikit-learn, pyyaml (plus matplotlib for plots).
Deployment, Compilation and Usage
The feature ships in the OCUDU RAN repository. Clone it and check out the dev branch before building:
git clone https://github.com/TOSSI-Foundation/OCUDU-RAN.git
cd OCUDU-RAN
git checkout dev
1. Collect a dataset
Run with a static timer and logging on; prediction and actuation stay off.
sudo ./build/apps/du/odu -c configs/du_bsr_collect.yaml
Check the capture before using it. Only the regular count trains; a window is 30 samples, so a UE needs more than 30 regular BSRs before it yields even one training row.
python3 ml/analysis/analyze_bsr_run.py ml/datasets/bsr_ml_dataset_<ts>.csv
2. Train a model and compile
Back up the untracked seed first (git cannot restore it), then train and export both artifacts:
cp lib/scheduler/support/bsr_periodicity_model.inc /tmp/seed.inc.bak
python3 ml/training/train_bsr_periodicity.py ml/datasets/bsr_ml_dataset_*.csv \
--inc-out lib/scheduler/support/bsr_periodicity_model.inc \
--model-out ml/models/bsr_periodicity.model
Training prints held-out error and a mapping demo; confirm predictions span more than one timer bucket. Only captures taken with inference: false should be used, or training on a capture the model itself influenced becomes a feedback loop.
Regenerating the compiled .inc seed requires a rebuild to take effect (the runtime .model does not):
cmake --build build -j$(nproc) --target odu
3. Enable inference (shadow mode)
Predict and report per UE without applying anything. This is the safe way to evaluate a model on a live cell.
bsr_ml:
inference: { enabled: true, model_path: ml/models/bsr_periodicity.model }
actuation: { enabled: false }
The predicted timer appears per UE in the predicted_periodicity_subframes column of the dataset CSV.
4. Enable actuation (closed loop)
sudo ./build/apps/du/odu -c configs/du_bsr_ml_e2e.yaml
Expected, in order:
ue=0: Actuating BSR periodicity change sf40 -> sf64 via F1AP UE Context Modification Required.
ue=0 du_ue_id=0: Sending UEContextModificationRequired (adaptive BSR periodicity).
The CU-CP then emits an RRC Reconfiguration carrying the new BSR-Config.
5. Compare against a static timer
Capture one run per fixed timer, then compare:
python3 ml/analysis/compare_runs.py sf10=ml/datasets/run_sf10.csv \
sf64=ml/datasets/run_sf64.csv \
ML=ml/datasets/run_ML.csv
The comparison is only valid if the runs carried the same traffic; check the traffic-match rows (total MB, mean kbps, buffer-occupied %) first. The reporting-delay bound row is the applied timer itself and is comparable across runs; the D1 Little's-law row is sampled at BSR instants, so treat it as indicative only.
Operational Notes
- Warm-up. No prediction is emitted for a UE until it has produced 30 regular BSRs. On an idle UE this never happens, and the feature is inert by design.
- Reconfiguration rate. Bounded to one change per UE per 10 s by min-dwell, and one-index-step moves are suppressed entirely.
- First move. Hysteresis and min-dwell only apply once the ML has applied a value, so the first transition away from the static timer is not damped.
- Silent suppression. The gate does not log when it drops a recommendation, so the absence of actuation lines does not imply the predictor is idle.
- Hot reload. Overwriting
model_pathswaps the model on the next mtime poll, with no restart; the compiled seed remains the fallback if the file is absent or fails to load. - Confirm/Refuse. The DU does not consume
UEContextModificationConfirm/Refuse; the reconfiguration is fire-and-forget from the DU's point of view. - Measured effect. On real hardware the periodic-BSR overhead fell from 153.4 to 9.7 BSRs per occupied-second while the buffer-occupied fraction rose from 7% to 19% (2.7×), because the timer lengthens for idle UEs and only reports when data is actually pending.
References
- N. Chukhno, S. Saafi and S. Andreev, “ML-Aided Dynamic BSR Periodicity Adjustment for Enhanced UL Scheduling in Cellular Systems,” in IEEE Open Journal of the Communications Society, vol. 6, pp. 3513–3527, 2025, doi: 10.1109/OJCOMS.2025.3561002.
Keywords: dynamic scheduling; predictive models; 3GPP; scheduling; quality of service; telecommunication traffic; uplink; prediction algorithms; long short-term memory; ultra-reliable low-latency communication; buffer status report; uplink scheduling; cellular networks; machine learning; uplink traffic prediction; wireless communications; machine learning for communications. - OCUDU RAN source code, TOSSI Foundation. github.com/TOSSI-Foundation/OCUDU-RAN (
devbranch). - 3GPP TS 38.321, Medium Access Control (MAC) protocol specification, clause 5.4.5 (BSR triggers).
- 3GPP TS 38.331, Radio Resource Control (RRC) protocol specification,
BSR-Config. - 3GPP TS 38.473, F1 Application Protocol (F1AP), clause 8.3.5 (UE Context Modification Required).