Skip to content

ML-Based BSR Periodicity Prediction

ML-Based BSR Periodicity Prediction

A per-UE, runtime adaptation of the periodic Buffer Status Report (BSR) timer in the OCUDU DU-High scheduler. Instead of one static 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.

ML-based BSR periodicity architecture. An offline ML platform (train_bsr_periodicity.py, an SVR-RFF pipeline, model artifacts, and analysis tooling) exports a compiled seed and a runtime model. The gNB-DU du_high runs the scheduler (bsr_trigger_tracker, interarrival_slots, ul_delay_tracker, bsr_ml::predictor), a dataset logger, and the du_manager damping gate plus actuation procedure that sends F1AP UEContextModificationRequired. The OCUDU gNB stack runs on a physical host with a 5G UE via a Liteon RU and PTP fronthaul, and a 5G Core.
The two halves of the feature. An offline Python platform trains and exports the model; the in-RAN C++ runtime predicts per UE on the scheduler path, damps the result, and actuates it over F1AP. Both read one config surface: the DU YAML 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:

#CheckEffect
1already-appliedskip if the recommendation equals the value in force
2hysteresisskip if the recommendation is within one index step of the value in force
3min-dwellskip 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.

Closed-loop actuation message sequence across the 5G UE, DU scheduler, DU du_manager, and gNB-CU-CP. Warm-up fills a 30-sample window while the UE keeps the static timer; predict emits a recommended periodicBSR-Timer; the damping gate suppresses recommendations that fail hysteresis or min-dwell and passes the first move that clears both; actuate packs a CellGroupConfig and sends F1AP UEContextModificationRequired; the CU-CP emits an RRC Reconfiguration with the new BSR-Config and the UE applies it.
Closed-loop actuation: warm-up → prediction → damping gate → F1AP UEContextModificationRequired → RRC Reconfiguration → UE applies. The inbound Confirm/Refuse is not consumed by the DU; actuation is fire-and-forget.

Key components

PathRole
lib/scheduler/support/bsr_periodicity_predictor.hInference, runtime model load, hot-swap.
lib/scheduler/support/bsr_periodicity_model.incCompiled 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.pyTrain a model; export the .inc and .model.
ml/analysis/analyze_bsr_run.py, compare_runs.pyPer-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:

ModeFlagsBehaviour
Collectdataset_loggingWrites the CSV. No prediction, no change.
Shadow+ inferencePredicts and reports per UE. Nothing is applied.
Closed loop+ actuationReconfigures 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.

Dumbbell chart comparing the ML-driven BSR periodicity against the static-timer baseline across three metrics: mean uplink rate in kbps (baseline about 1000, ML about 1680, +68%), total uplink data in MB (baseline about 340, ML about 400, +18%), and buffer occupied as a percentage (+100%). Each metric shows a light dot for the baseline and a dark dot for ML, joined by a line whose length is the delta.

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_path swaps 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

  1. 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.
  2. OCUDU RAN source code, TOSSI Foundation. github.com/TOSSI-Foundation/OCUDU-RAN (dev branch).
  3. 3GPP TS 38.321, Medium Access Control (MAC) protocol specification, clause 5.4.5 (BSR triggers).
  4. 3GPP TS 38.331, Radio Resource Control (RRC) protocol specification, BSR-Config.
  5. 3GPP TS 38.473, F1 Application Protocol (F1AP), clause 8.3.5 (UE Context Modification Required).