Skip to content

Slice-ML: Hierarchical Deep Learning for RAN Resource Slicing

Slice-ML divides one radio carrier between an eMBB slice and a URLLC slice, and then decides who transmits inside each. These are two different problems at two different speeds, so the feature solves them with two independent control loops: a slow controller moves the boundary between slices once per second, and per-slot attention schedulers rank users inside a slice. It runs on the OCUDU-RAN distributed unit rather than in simulation.

As with the other AI-RAN features in this stack, every part defaults to disabled, the conventional scheduler is the fallback at every decision point, and no ML framework is embedded in the DU.

AI-RAN hierarchical learning for RAN resource slicing. On the left, 5G UEs attach through a LiteON RU and a fronthaul switch with PTP grandmaster. A shared 273 PRB carrier is split between an eMBB slice (sst 1, throughput, tolerates delay, target SSR 0.90) and a URLLC slice (sst 2, small packets, no delay, target SSR 0.99), with the slow loop moving the boundary between them. Across the top, an offline Python training toolchain of four scripts: dataset streams producing slice-period and UE-period CSV with an opt-in scenario label; train 1 and 2, the actor LSTM and value baseline where model 2 exists only here; train 3 then 4, the URLLC scheduler before the eMBB scheduler which is trained against model 3's behaviour; and verify and analyse, checking C++ against framework arithmetic parity, rollout timing and the reward table. Model files pass through validate, atomic swap and hot-reload with no restart. Below that, the in-RAN ML runtime holds three models in C++ with float32, zero allocation on the hot path and no ML framework linked in: the slice controller (model 1, LSTM 256 to FC 512, softmax over 19, one per cell), attention policies (models 3 and 4, encoder-decoder with a set function and cached projection), a model loader that watches, validates and atomically swaps while keeping the running model if a file is invalid, and dataset loggers writing two CSV streams. In the centre, two independent control loops that never invoke each other. The slow loop runs at 1 second on model 1 through seven stages: scheduler metrics per period, a metrics consumer one per cell, an inference step taking 23 inputs to 19 outputs, hysteresis and gates that rate-limit, actuation by slice reconfiguration request, the inter-slice scheduler applying new min and max PRB, and read-back verification that warns on mismatch, closing the loop by re-reading the ratios the cell actually applied. The fast loop runs per slot on models 3 and 4: the slice scheduler triggers a timed attention rollout, and if it lands within the slot budget priority equals units won and the DU's existing allocator places them in frequency, while if it does not the rollout is discarded and the conventional policy runs, so the slot deadline is never missed and repeated overruns retire the model. One coupling edge is highlighted: a changed minimum PRB ratio is notified to that slice's policy and becomes the shared-region flag carried on every UE by unit pair, and nothing else crosses. At the bottom, OCUDU-RAN shows Slice-ML attaching at two existing seams, the metrics report and the per-slice policy interface, with no new thread and no new process, alongside scheduler, inter-slice scheduler, F1AP-C, MAC, RLC, PDCP-C, PDCP-U, F1AP-U, RRC, NGAP, GTP-U, SDAP, E2AP and DU manager. On the right, a panel lists the four models and which run in the DU, the objective R equals 0.01 times eMBB Mbps minus 5.0 times the positive part of 0.90 minus SSR eMBB minus 5.0 times the positive part of 0.99 minus SSR URLLC, and three rollout stages: collect with neither infer nor actuate, shadow with infer only, and actuate with both live.
Two control loops at two speeds, meeting at exactly one point. The slow loop moves the slice boundary once per second; the fast loop ranks users inside a slice every slot. Slice-ML attaches at two existing seams in the DU, the metrics report and the per-slice policy interface, adding no new thread and no new process.

What is decided, and by what

eMBB (enhanced mobile broadband) carries large downloads. It wants throughput and tolerates delay. URLLC (ultra-reliable low-latency communication) carries small frequent packets. It tolerates low throughput and does not tolerate delay. Both draw on the same carrier. A slice is the logical partition serving one class, identified by a service type (sst) and differentiator (sd).

LoopPeriodWhat it decidesModel
Slice control1 sthe min/max PRB ratio each slice getsLSTM, FC, softmax over 19 actions
In-slice schedulingone slotwhich users to serve, and how muchattention encoder-decoder, one per slice

The two loops are independent. They meet at exactly one point: when the controller changes a slice's minimum ratio, that slice's scheduler sees the new dedicated size as an input feature. Neither invokes the other.

The objective

The slice controller optimises one quantity, evaluated per one-second period:

R  =  0.01 . eMBB_Mbps  -  5.0 . max(0, 0.90 - SSR_eMBB)  -  5.0 . max(0, 0.99 - SSR_URLLC)

SSR is the slice satisfaction ratio, the fraction of a slice's users whose demand was met. Each penalty applies only when its target is missed.

The weighting is deliberately asymmetric. An SLA breach costs roughly 500 times more than a megabit of throughput earns. A controller trained on this reward is expected to trade throughput for reliability whenever the two conflict. That is the objective working as designed, not a defect.

Satisfaction is computed inside the DU's metrics path and reported per slice, per period. SSR_eMBB counts users with an empty download buffer or a delivered rate at or above the configured target. SSR_URLLC counts users with an empty download buffer and nothing pending uplink.

The reward exists twice, in C++ for the live controller and in Python for training and analysis. The analysis tooling imports the constants directly from the training script, so the two cannot drift apart.

Implementation

Two halves

In-RAN runtime (C++). A metrics consumer registered with the DU's reporting service owns one slice controller per cell. Two self-contained predictors perform inference with no ML framework, no dynamic allocation on the hot path, and single-precision arithmetic with a cached projection for the attention encoder. Model files are a small text header followed by weights, and they can be hot-reloaded.

Offline platform (Python). Four training scripts, one per model. A reward analyser that shares its constants with the training code. Verification harnesses that check the C++ predictors reproduce the training framework's arithmetic, and a rollout benchmark. The offline C++ tools compile standalone and are not part of the DU build.

The four models

#RoleArchitectureRuns in the DU
1Slice controller, chooses the PRB splitLSTM 256, FC 512, softmax(19)yes
2Value baseline, trains the controllerLSTM 256, FC 512, scalarno, training only
3URLLC scheduler, ranks users in the sliceattention enc-dec, 3 feat, 2 ctxyes
4eMBB scheduler, ranks users in the sliceattention enc-dec, 2 feat, 1 ctxyes

Model 2 has no counterpart in the DU. It produces the learning signal that trains Model 1 and is then set aside. Training order is sequential, with the URLLC scheduler trained before the eMBB scheduler that learns against it, but at runtime the three deployed models never call each other.

Slice control loop

Once per second the scheduler emits a metrics report. The controller reduces it to four observations, appends a one-hot encoding of the action currently in force for 23 inputs in total, runs one LSTM step, and takes the argmax over 19 candidate actions.

Each action is a pair of minimum and maximum PRB ratios for the two slices. Twelve come from the source paper's allocation zones. Seven are plain splits added for this deployment. The minimum ratio is a floor a slice is guaranteed. The maximum is a ceiling it may borrow up to when the other slice is idle.

A proposed change must clear two stages before it is applied. First hysteresis: the same action must repeat for several consecutive periods. Then safety gates: a URLLC minimum floor, a cap on total reservation, and a minimum interval since the last change.

Actuation is a runtime reconfiguration request through the DU's existing path, never a config-file edit. Editing YAML would require a restart and drop every connected user.

On the following period the controller re-reads the ratios the cell actually reports and warns if they do not match what was requested. This matters: a reconfiguration whose slice identity does not match is accepted and then silently dropped, so without the read-back the log would claim a switch that never happened.

Each cell gets its own controller, created on first sight of that cell, so recurrent state and switch counters cannot leak between cells.

The slow control loop in five stages. One, collect RAN metrics: load, throughput, SLA and PRB usage. Two, the LSTM actor predicts the optimal slice allocation. Three, compute the PRB split as a per-slice minimum and maximum allocation. Four, apply the slice reconfiguration through the DU RRM over standard interfaces. Five, verify and monitor, confirming the applied split and observing its impact. A tortoise at the centre marks this as the slow loop.
The slow loop, once per second. Stage five is what closes it: the controller re-reads the ratios the cell actually applied rather than assuming the request took effect.

In-slice scheduling

The DU already constructs one scheduling policy per slice, so the models attach with no dispatch logic. Each configured slice names its own policy, and any slice that does not is left with the conventional scheduler.

The models work on units, which are groups of adjacent PRBs, rather than individual resource blocks. The grouping factor is configured per slice and does two jobs: it keeps the problem inside the range the models were trained over, and it controls cost, because the encoder's work scales with the square of the pair count. A model asked to run far outside its trained range refuses to load, and the slice keeps the conventional scheduler.

The model does not allocate spectrum. The decoder emits (user, unit) pairs. The number of units a user wins becomes that user's priority, and the DU's existing allocator performs frequency placement. The model contributes the who-and-how-much judgement, while the allocator keeps the 3GPP placement constraints the model was never given.

Both roles are downlink only. Uplink on both slices is delegated to the conventional policy.

The fast control loop in five stages. One, get UE and channel state: CQI, buffer status, QoS and slice ID. Two, the attention model ranks UEs and units. Three, select the UE and unit ranking, deciding which UEs get how many units. Four, the existing allocator performs frequency placement under 3GPP constraints. Five, transmit the scheduled transmissions on the air. A hare at the centre marks this as the fast loop.
The fast loop, once per slot. The model stops at stage three: it contributes the who-and-how-much judgement, and the DU's existing allocator keeps the placement constraints the model was never given.
AspecteMBB schedulerURLLC scheduler
Per-pair features2: channel gain, shared-region flag3: channel gain, pending demand, shared-region flag
Context inputs1: units still free2: power remaining, units still free
Decoder objectivelift users toward a rate floordrain pending demand within a power budget
Selection constraintunit must be freeunit free, power feasible, demand outstanding

Safety

  • Every rollout is timed against the slot budget. One that overruns is discarded and the conventional policy runs for that slot instead, so an overrun costs scheduling quality and never the slot deadline.
  • After repeated consecutive overruns the model is switched off for the rest of the run, and the slice falls back permanently.
  • A model is accepted onto a slice only if its declared role, feature widths and context widths match what that slice requires. Any mismatch logs a warning and keeps the conventional scheduler.
  • The controller can run complete, observing and inferring and logging, with actuation switched off. This validates a model against live traffic without touching the radio.
  • Replacing the conventional policy on a slice also replaces its quality-of-service weighting, so guaranteed-bitrate commitments on that slice become the model's responsibility.

Configuration

There are two blocks. Per-slice policy sits inside cell_cfg.slicing, and the controller and dataset logging sit in a top-level slice_ml block. A complete working example ships in the repository as configs/slicemanager.yaml.

cell_cfg:
  slicing:
    - sst: 1                          # eMBB
      sd: 1
      sched_cfg:
        min_prb_policy_ratio: 50      # the controller overwrites this at runtime
        max_prb_policy_ratio: 100
        policy:
          attention_ml:
            model_path: ml/slicemanager/embb_scheduler.model
            role: embb
            prb_group: 32
    - sst: 2                          # URLLC
      sd: 1
      sched_cfg:
        min_prb_policy_ratio: 50
        max_prb_policy_ratio: 100
        policy:
          attention_ml:
            model_path: ml/slicemanager/urllc_scheduler.model
            role: urllc
            prb_group: 16

slice_ml:
  dataset_logging:
    enabled: true
    output_dir: ml/datasets/slice_datasets
    scenario: baseline                # free-text label, becomes a CSV column
    target_dl_rate_kbps: 4000.0       # eMBB satisfaction yardstick
    delay_budget_ms: 10.0

  inference:
    enabled: true
    model_path: ml/slicemanager/slicemanager_actor.model
    apply: false                      # false is shadow mode: observe and log, never actuate
    default_action_idx: 13            # must match the ratios configured above
    min_urllc_prb_ratio: 10
    max_total_min_ratio: 100
    switch_hysteresis_periods: 5
    min_periods_between_switches: 30
    plmn: "00101"
    embb_sst: 1
    embb_sd: 1                        # must match cell_cfg.slicing exactly
    urllc_sst: 2
    urllc_sd: 1

Keys that need care

KeyWhy it matters
applyfalse is shadow mode. Leave it false until a model has been validated against live traffic.
default_action_idxMust correspond to the ratios actually configured on the slices, or the controller starts from a false belief about the current state.
embb_sd / urllc_sdSlice identity must match cell_cfg.slicing exactly, including network, service type and differentiator. A mismatch means reconfiguration is addressed to a slice that does not exist, and it is silently dropped.
prb_groupMust match the grouping the model was trained at. Too small a value pushes the problem outside the trained range and the policy refuses to load.

The URLLC slice must also be advertised by the CU. Add it to the tracking-area slice support list in the CU configuration alongside the eMBB slice, or UEs will never attach to it.

Getting the source and building

git clone https://github.com/TOSSI-Foundation/OCUDU-RAN.git
cd OCUDU-RAN
git checkout slice_ml

Every path below is relative to that clone, and the commands run from its root. Install dependencies and build with the project's own scripts, which are what CI uses:

sudo docker/scripts/install_dependencies.sh
docker/scripts/builder.sh -c gcc -m "-j$(nproc)" -DBUILD_TESTING=On .

This produces the CU and DU binaries under build/apps/. To confirm the tree is sound before going further:

cd build && ctest -j"$(nproc)" --schedule-random --output-on-failure

The Python side needs numpy for analysis and torch to train any of the four models. The standalone C++ verification and benchmark tools compile themselves on first use and need only a C++17 compiler. They are not part of the DU build.

Rollout

The feature is designed to be adopted in stages, and each one is safe to stop at.

Stagedataset_logging.enabledinference.enabledinference.applySlice policy
1, Collecttruefalsefalseomitted
2, Shadowtruetruefalseomitted
3, Slice controltruetruetrueomitted
4, Fulltruetruetrueattention_ml

Stage 3 enables the slow loop alone, and stage 4 adds the per-slot schedulers. Because the two loops are independent, either can run without the other.

Collecting data

Run the stack with dataset logging enabled and inference off, then generate traffic that represents what you want the controller to learn: an eMBB download alongside a periodic small-packet URLLC flow.

The DU writes two CSV streams per run into dataset_logging.output_dir, distinguished by prefix: slice_ml_slicemanager_<timestamp>.csv, one row per slice per one-second period, and slice_ml_ue_<timestamp>.csv, one row per UE per period. The two files of a run share a timestamp and are used together, and the analysis tooling derives the second path from the first. Set dataset_logging.scenario to a distinct label per run so captures can be grouped later.

Collect enough periods for the controller to have seen varied conditions. A run that never puts both slices under load teaches nothing: if the URLLC slice carries no traffic, its satisfaction is trivially perfect, the penalty term is identically zero, and the controller optimises a throughput-only objective.

Training

All four models are trained from the repository root. Every script defaults to CPU, so pass --device cuda to any of them to use a GPU. Set a variable for the capture directory so the commands stay portable:

DS=ml/datasets/slice_datasets

Model 3, URLLC scheduler

This trains on a synthetic environment and reads no captures. Train it before Model 4.

python3 ml/training/train_urllc_scheduler.py \
  --model-out ml/slicemanager/urllc_scheduler.model \
  --d-model 32 --n-heads 4 \
  --cell-bw-mhz 100 --prb-group 16 \
  --epochs 100 --batch 512 --steps-per-epoch 20 \
  --users 4 --arrival 1.5

Model 4, eMBB scheduler

This trains against the URLLC model, so it must come second.

python3 ml/training/train_embb_scheduler.py \
  --model-out ml/slicemanager/embb_scheduler.model \
  --urllc-model ml/slicemanager/urllc_scheduler.model \
  --d-model 32 --n-heads 4 \
  --cell-bw-mhz 100 --prb-group 32 \
  --epochs 100 --batch 128 --steps-per-epoch 20 \
  --users 6

Set --d-model and --n-heads explicitly for both schedulers. The script defaults are wider than a slot budget allows. Inference cost scales with the square of the model width, so a model trained at the defaults will overrun its deadline, trip the watchdog and disable itself, and the slice then silently runs the conventional scheduler. Always confirm with the benchmark below before trusting a live run.

Model 1, slice controller

This trains on the captures collected above.

python3 ml/training/train_slicemanager_actor.py "$DS"/slice_ml_slicemanager_*.csv \
  --model-out ml/slicemanager/slicemanager_actor.model \
  --d-hidden 256 --d-fc 512 \
  --epochs 200 --lr 1e-3 \
  --shuffle-prev-action

--shuffle-prev-action matters. The controller takes the previous action as an input, and in collected data that action usually equals the correct next one. Without the flag the network learns the identity function and never switches. It is a deliberate departure from the paper's input formulation.

The script reports held-out accuracy against a majority-class baseline. If it warns that the model does not beat that baseline, do not promote it. A constant policy is doing at least as well, and the usual cause is too few distinct runs or too little variation across them.

Model 2, value baseline

Same captures. This is training only, and nothing loads it at runtime.

python3 ml/training/train_slicemanager_critic.py "$DS"/slice_ml_slicemanager_*.csv \
  --model-out ml/slicemanager/slicemanager_critic.model \
  --d-hidden 256 --d-fc 512 \
  --epochs 200 --gamma 0.99

Verifying a trained model

Run these after every retrain, before any live run. First check that the C++ predictors reproduce the training framework's arithmetic:

bash ml/analysis/verify_slice_ml_forward.sh  ml/slicemanager/slicemanager_actor.model
bash ml/analysis/verify_attention_forward.sh ml/slicemanager/urllc_scheduler.model
bash ml/analysis/verify_attention_forward.sh ml/slicemanager/embb_scheduler.model

Each prints a maximum absolute difference and whether the selected actions agree. Expect a negligible difference and identical selections. Then confirm the schedulers fit their deadline:

bash ml/analysis/bench_attention_rollout.sh ml/slicemanager/embb_scheduler.model 50
bash ml/analysis/bench_attention_rollout.sh ml/slicemanager/urllc_scheduler.model 50

The benchmark prints total microseconds per rollout across a grid of user and unit counts. Find the row matching your deployment, meaning the UE count you expect on that slice and the unit count implied by your carrier width and prb_group, then confirm it sits under one slot. At 30 kHz subcarrier spacing a slot is 500 us.

These scripts locate their own dependencies and compile into a temporary directory, so the first run takes noticeably longer than later ones.

Running

./build/apps/cu/ocu -c configs/cu.yml
./build/apps/du/odu -c configs/slicemanager.yaml

Confirm from the DU log that each piece came up: the controller announcing itself, its mode (apply or shadow) and its starting action; one attention-model load line per configured slice, giving role, unit count and deadline; and a periodic heartbeat carrying the observation, the utility and the chosen action.

Two log lines indicate a real problem. A warning that an actuation did not take effect means the slice identities in slice_ml.inference do not match cell_cfg.slicing. A warning that a model was disabled after consecutive deadline overruns means the model is too wide, or prb_group too small for the carrier. Retrain narrower or raise prb_group, then re-check with the benchmark.

Analysing a run

Score captures with the reward the controller was trained on:

python3 ml/analysis/slice_ml_reward.py --by file

--dir points the tool at a capture directory other than the configured default. --by file reports one row per capture, which is what you want when comparing runs; the default groups by the scenario label instead, which collides when several runs share a label. --scenario filters to captures whose key contains a substring, and --quiet drops the explanatory header.

The output gives, per capture, the mean reward and the three terms that sum to it, alongside both satisfaction ratios and eMBB throughput. A second table reports radio-side context such as throughput, error rates and modulation per slice. That context explains why a penalty term is what it is, without being part of the reward.

To compare configurations, run each one identically except for the setting under test, label them distinctly with dataset_logging.scenario, and compare their rewards. Reading the result honestly needs two habits:

  • Check that both slices were actually loaded. A run where the URLLC slice was idle scores near zero because the penalty term is absent, not because the configuration was good.
  • Compare ranges, not just means. With a small number of runs per configuration, only a separation where the ranges do not overlap is evidence of anything.

Hot-swapping a model

The controller watches its model file for modification. A replacement is validated, swapped in atomically, and the recurrent state is reset, all without restarting the DU. A file that fails validation is rejected and the running model kept. Writing a new model to the configured path is therefore enough to promote it.

References

M. Setayesh, S. Bahrami and V. W. S. Wong, "Resource Slicing for eMBB and URLLC Services in Radio Access Network Using Hierarchical Deep Learning", IEEE Transactions on Wireless Communications, vol. 21, no. 11, 2022. PDF

3GPP scope

The scheduling algorithm is implementation-specific in TS 38.300. The standard constrains what a scheduler emits, which remains the allocator's responsibility. Slice-ML introduces no new procedures, no new UE signalling and no radio-interface changes. Slice reconfiguration uses the DU's existing RRM policy path.

Source

OCUDU-RAN, branch slice_ml: github.com/TOSSI-Foundation/OCUDU-RAN

Related AI-RAN work: ML-based UL MCS, ML-based BSR periodicity and predictive CSI for link adaptation. End-to-end slicing across UE, RAN, Core and SMO is covered in network slicing.