Skip to content

cell M1: QoS classes (CellScheduler) — 5QI sets each UE's HARQ budget, discard timer and link adaptation - #569

Merged
josephnef merged 1 commit into
masterfrom
feat/m1-qos-scheduler
Nov 13, 2026
Merged

josephnef merged 1 commit into
masterfrom
feat/m1-qos-scheduler

Conversation

@josephnef

Copy link
Copy Markdown
Collaborator

Closes the last open M1 deliverable of #255 ("CellScheduler — 5QI-style QoS classes producing the grant map"), epic #254.

M1's grid is fixed (one DL and one UL opportunity per UE per superframe), so a class can't buy airtime. It decides how each UE is served, and who is served when the pool is full.

src/cell/CellScheduler.h (headless, ctest cell_core)

  • qos_class(5qi): 5QI 1, 2, 3, 5, 7, 9 with their standardized characteristics (3GPP TS 23.501 table 5.7.4-1): resource type, default priority level, PDB, PER.
  • policy_for(class, superframe):
    • HARQ attempts = the superframes that fit the PDB (≥ 1, capped by CELL_HARQ_ATTEMPTS);
    • a DL discard timer (PDCP's): the PDB, never below one superframe + 5 ms, the wait for the UE's slot on a perfect link;
    • link adaptation from the PER target: ≥ 10⁻² tolerant, ≤ 10⁻⁵ strict (+1 dB hysteresis);
    • pdb_unmet when the PDB is shorter than one superframe. One DL opportunity per superframe can't meet it, and that's said rather than pretended.
  • admission_order() (priority, GBR first at a tie, first come) and preempt_for() (only a strictly lower priority). Headless; the DU doesn't preempt yet.

DU (examples/cell): CELL_QOS="ue:5qi,...", with each UE's policy logged (cell.qos) and discards logged (cell.dl.discard). A UE that isn't listed is served exactly as before (the cell's HARQ and link adaptation, no discard); DAPS's buffered PDUs, for one, are older than any budget. A malformed or unknown entry stops the DU at start.

Rig (testrig m1_qos.sh: UE1's DL at a fixed 54 Mb/s, link adaptation off, −15 dB per-packet attenuation so about half the first transmissions are lost; 3 interleaved rounds, all pass):

class PDUs lost delivered on attempt 1/2/3/4 discarded
none (4 attempts) 4.8 % 58/26/10/5 % 0
5QI 9 (2, 300 ms) 19 % 71/29 % 127
5QI 1 (1, 107 ms, pdb_unmet) 41 % 100 % 0

A tighter budget bounds the delay (each retry is a superframe) and pays in delivery on this deliberately lossy link. Run-to-run variance is large: 5QI 9 missed 72 / 103 / 168, because the attenuation sits on a steep edge (−14 dB lost nothing, −17 everything).

🤖 Generated with Claude Code

@qodo-free-for-open-source-projects

Copy link
Copy Markdown

PR Summary by Qodo

Add 5QI-based per-UE QoS policies to the cell DU

✨ Enhancement 🧪 Tests 📝 Documentation 🕐 20-40 Minutes

Grey Divider

AI Description

• Derive per-UE HARQ, discard, and link-adaptation policies from six standardized 5QI classes.
• Apply policies through CELL_QOS while preserving existing behavior for unlisted UEs.
• Add headless admission rules, self-tests, and documentation of lossy-link rig results.
Diagram

graph TD
  E["CELL_QOS"] --> C["5QI classes"] --> P["UE policy"] --> D["DU DL state"] --> H["HARQ sender"]
  D --> L["Link adaptation"]
  P --> Q["DL queue discard"]
  C --> A["Admission rules"]
Loading
High-Level Assessment

Keep the headless policy derivation and reuse the existing per-UE HARQ and link-adaptation components. Dynamic slot allocation would be a substantially different scheduler and cannot provide a benefit within M1’s fixed grid; DU preemption can be integrated separately.

Files changed (4) +290 / -4

Enhancement (2) +208 / -4
main.cppApply configured QoS policies in the DU +85/-4

Apply configured QoS policies in the DU

• Parses CELL_QOS and constructs policy-specific downlink state for configured UEs, including newly admitted or arriving UEs. Tracks queued-PDU age, discards expired PDUs, and logs policies and discards; unlisted UEs retain their existing behavior.

examples/cell/main.cpp

CellScheduler.hDefine 5QI classes and derive per-UE service policies +123/-0

Define 5QI classes and derive per-UE service policies

• Adds six 5QI definitions and derives HARQ limits, discard timers, link-adaptation settings, and a flag for PDBs shorter than a superframe. Provides headless priority-based admission ordering and preemption selection.

src/cell/CellScheduler.h

Tests (1) +32 / -0
cell_selftest.cppTest QoS policy derivation and admission rules +32/-0

Test QoS policy derivation and admission rules

• Covers supported and unknown classes, attempt limits at different superframe lengths, discard timing, link-adaptation thresholds, admission ordering, and strict-priority preemption.

tests/cell_selftest.cpp

Documentation (1) +50 / -0
cell-m1.mdDocument fixed-grid QoS behavior and rig observations +50/-0

Document fixed-grid QoS behavior and rig observations

• Explains how 5QI characteristics affect attempt limits, discard timers, link adaptation, and headless admission. Records the lossy-link rig results and the delivery trade-off of tighter budgets.

docs/cell-m1.md

@qodo-free-for-open-source-projects

qodo-free-for-open-source-projects Bot commented Nov 13, 2026 •

Copy link
Copy Markdown

Code Review by Qodo

🐞 Bugs (3) 📘 Rule violations (1) 📎 Requirement gaps (0) 🎨 UX issues (0) 🔗 Cross-repo conflicts (0) 📜 Skill insights (0)

Grey Divider


Action required

1. Expired downlink packets are retransmitted 🐞 Bug ≡ Correctness
Description
The discard scan only examines queued packet numbers in the new-transmission branch; retransmissions
instead load their payload directly from DlUe::pdus. When feedback is absent, the default
three-superframe HARQ timeout lets a 5QI 9 packet be retransmitted roughly 307 ms after its first
attempt, beyond its 300 ms discard budget.
Code

examples/cell/main.cpp[R2360-2363]

+              auto &dq = qi->second.sns;
+              for (auto it = dq.begin(); it != dq.end();) {
+                auto a = qi->second.at_ms.find(*it);
+                if (a != qi->second.at_ms.end() && now_q - a->second > pol->second.discard_ms) {
Evidence
The policy sets the 5QI 9 budget to 300 ms and permits two attempts; the sender's default timeout
makes the second attempt due three 102.4 ms superframes after the first. The retransmission branch
reads stored payloads before and outside the new discard scan.

src/cell/CellScheduler.h[73-81]
src/cell/Harq.h[105-111]
src/cell/Harq.h[145-177]
examples/cell/main.cpp[2310-2318]
examples/cell/main.cpp[2354-2380]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
The QoS discard timer applies only to queued new transmissions, allowing HARQ retransmissions to carry expired packets.
## Fix Focus Areas
- examples/cell/main.cpp[2310-2318]
- examples/cell/main.cpp[2354-2379]
- src/cell/Harq.h[145-177]
## Recommended Fix
Retain packet arrival times while packets are in flight. Check their ages before building retransmissions, and retire expired HARQ payloads and their sender entries rather than airing them. Cover the no-feedback timeout path in a test.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools



Remediation recommended

2. Milestone doc repeats the CellScheduler header rules 📘 Rule violation ⚙ Maintainability
Description
The new QoS classes section in docs/cell-m1.md restates the PDB-to-HARQ-budget, discard-timer,
PER-to-link-adaptation and priority-admission rules almost word for word from the header comment in
src/cell/CellScheduler.h. Both copies must now be kept in step, and the doc's copy of the policy
derivation (floor(PDB / superframe), the superframe + 5 ms discard floor) will drift from the code
once the header changes. The doc should refer to the header for the mechanism and keep only the
measured rig results.
Code

docs/cell-m1.md[R103-108]

+- **The PDB sets the HARQ budget:** a retransmission comes at least a
+  superframe after the attempt before it, so the attempts that fit are
+  floor(PDB / superframe), at least one, capped by `CELL_HARQ_ATTEMPTS`.
+- **The PDB also sets a discard timer** (PDCP's): a DL PDU queued longer is
+  dropped, not sent late (`cell.dl.discard`). It is never below one
+  superframe plus 5 ms, since a PDU can wait that long for its UE's DL slot on
Evidence
Rule 14 asks that new cross-cutting documentation not duplicate header doc-comments. The doc
section's bullets on the PDB-driven HARQ budget and discard timer mirror the header's file-level
comment, so the same mechanism is now documented in two places.

CLAUDE.md: Keep CLAUDE.md Facts Scoped, Nonduplicative, and Balanced: CLAUDE.md: Keep CLAUDE.md Facts Scoped, Nonduplicative, and Balanced: CLAUDE.md: Keep CLAUDE.md Facts Scoped, Nonduplicative, and Balanced: CLAUDE.md: Keep CLAUDE.md Facts Scoped, Nonduplicative, and Balanced
docs/cell-m1.md[103-109]
src/cell/CellScheduler.h[1-23]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

The issue below was found during a code review. Follow the provided context and guidance below and implement a solution
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution
Issue description
The QoS section added to docs/cell-m1.md repeats the policy rules (PDB to HARQ attempts, discard timer floor, PER to link adaptation, priority admission) that are already documented in the header comment of src/cell/CellScheduler.h, so the two copies can drift apart.
Fix Focus Areas
- docs/cell-m1.md[103-124]
- src/cell/CellScheduler.h[1-23]
Recommended Fix
In docs/cell-m1.md, shorten the bullets on the class characteristics, HARQ budget, discard timer, link adaptation and priority to one line each that point to src/cell/CellScheduler.h for the derivation. Keep the DU-specific notes (CELL_QOS, cell.qos, cell.dl.discard, the unlisted-UE behaviour) and the rig results table with its variance and steep-edge notes.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


3. Requeued packets can escape discard 🐞 Bug ≡ Correctness
Description
The at_ms compaction retains timestamps only for packet numbers currently in q.sns, although
packets selected for HARQ have left that queue and can later be requeued. After enough newer
downlink arrivals trigger compaction, a handover-arrival requeue can restore an older packet without
its timestamp, so the discard scan never recognizes its age.
Code

examples/cell/main.cpp[R1943-1947]

+            std::map<uint32_t, int64_t> keep;
+            for (uint32_t sn : q.sns)
+              if (auto a = q.at_ms.find(sn); a != q.at_ms.end())
+                keep.insert(*a);
+            q.at_ms.swap(keep);
Evidence
Selection removes packet numbers from the queue, while handover arrival can later requeue them.
Compaction retains only queued packet numbers, and the discard condition requires a matching
timestamp; it has no fallback for a requeued number whose timestamp was deleted.

examples/cell/main.cpp[1941-1948]
examples/cell/main.cpp[1307-1322]
examples/cell/main.cpp[2360-2383]
examples/cell/cell.h[556-574]
examples/cell/cell.h[584-623]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
Timestamp compaction drops ages for packets temporarily outside the queue, even though handover handling can put those packets back.
## Fix Focus Areas
- examples/cell/main.cpp[1941-1948]
- examples/cell/main.cpp[2361-2366]
- examples/cell/main.cpp[1307-1322]
## Recommended Fix
Track arrival times through selection and requeue, including HARQ-held packets in timestamp cleanup. Ensure every requeued packet retains its original arrival time, and test compaction followed by handover-arrival requeue.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


4. Unknown QoS values can pass validation 🐞 Bug ≡ Correctness
Description
The CELL_QOS parser converts the parsed unsigned 5QI to uint8_t before calling qos_class,
without validating its original range. For example, CELL_QOS=1:257 is accepted as 5QI 1 instead of
stopping startup, and UE 1 receives that class's HARQ, discard, and adaptation policy.
Code

examples/cell/main.cpp[R676-678]

+      const auto cls = sscanf(item.c_str(), "%u:%u%c", &ue, &qi, &tail) == 2 && ue >= 1 && ue <= 254
+                           ? devourer::cell::qos_class(static_cast<uint8_t>(qi))
+                           : std::nullopt;
Evidence
The parser reads qi as unsigned, narrows it to eight bits for lookup, and applies any returned
class to the UE. The class table includes 5QI 1, so 257 narrows to an accepted entry.

examples/cell/main.cpp[674-683]
src/cell/CellScheduler.h[45-58]
examples/cell/main.cpp[1333-1346]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
An out-of-range 5QI wraps to a known class before the parser checks whether it is supported.
## Fix Focus Areas
- examples/cell/main.cpp[674-683]
## Recommended Fix
Reject parsed 5QI values outside the `uint8_t` range before conversion, then check the unmodified value against the supported classes. Add a startup-parser test for `1:257`.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


Grey Divider

Tip of the day
💡 Did you know, you can start a comment with 'qodo' or '@qodo' to chat about any finding

More tips ↗ | Customize Qodo ↗ | Qodo docs ↗

Grey Divider

Qodo Logo

…E's HARQ budget, DL discard timer and link adaptation

src/cell/CellScheduler.h: the 5QIs 1 2 3 5 7 9 with their standardized
characteristics (TS 23.501 table 5.7.4-1); policy_for() derives a UE's HARQ
attempts (1 + the HARQ round trips that fit its PDB after the first
superframe), a DL discard timer (the PDB, never below a superframe + 5 ms;
also applied to a due retransmission) and link adaptation from the PER
target, flagging a PDB shorter than a superframe (pdb_unmet);
admission_order() and preempt_for() rank by priority level (headless).
HarqSender::abandon(seq). The DU takes CELL_QOS="ue:5qi,..." (cell.qos,
cell.dl.discard); unlisted UEs are served as before.

Rig (testrig m1_qos.sh, lossy DL): no class 8.1 % lost (deliveries up to
attempt 4); 5QI 9 at the cell's 3-superframe round trip 1 attempt, 52 %; with
a 1-superframe round trip 2 attempts, 33 %; 5QI 1 54 %.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
@josephnef

Copy link
Copy Markdown
Collaborator Author

Review fixes pushed:

  1. Expired packets retransmitted — fixed, and it exposed a policy flaw. Each HARQ frame's first-attempt time is now recorded; a due retransmission past the UE's discard timer is given up (HarqSender::abandon(seq), logged as cell.dl.discard with retx). On the rig that discarded nearly every 5QI 9 retry. Every retransmission here comes from the 3-superframe timeout (HARQ stats: 0 NACKs, because the UE's feedback reaches the DU a superframe or two late), so a retry lands ~307 ms after the first attempt, not a superframe later as the policy had assumed. policy_for now counts retries at the configured round trip: attempts = 1 + floor((PDB − superframe) / (timeout_sf × superframe)).
    • On a 100 TU grid with a 3-superframe round trip, 5QI 9 gets 1 attempt; with CELL_HARQ_TIMEOUT_SF=1 it gets 2.
    • The rig table in the docs covers both, with 0 discards now.
  2. The docs repeat the header — trimmed. The docs point to CellScheduler.h for the mechanism and keep the rig results and what they showed.
  3. Requeued packets escape discard — fixed. Compaction now keeps timestamps younger than 10 s, as well as anything queued, so a PDU back from HARQ flight keeps its age.
  4. CELL_QOS=1:257 passes as 5QI 1 — fixed: the 5QI is range-checked before narrowing.

Rig on the final build (m1_qos.sh, 2 interleaved rounds, all pass):

class lost on attempt 1/2/3/4
none 8.1 % 53/31/11/4 %
5QI 9 52 % 100 %
5QI 9, 1-sf round trip 33 % 67/33 %
5QI 1 54 % 100 %

m6_daps_dl passes on this code (unclassed UEs are unchanged).

@josephnef
josephnef force-pushed the feat/m1-qos-scheduler branch from 0b58880 to 85d96c5 Compare November 13, 2026 06:57
@josephnef

Copy link
Copy Markdown
Collaborator Author

The Qodo comment still lists the original four findings. All four are fixed in the pushed head (see the fix comment above):

Adding skip-qodo-gate.

@josephnef josephnef added the skip-qodo-gate Bypass the Qodo review gate (outage / maintainer decision) label Nov 13, 2026
@josephnef
josephnef merged commit 6844aa3 into master Nov 13, 2026
30 of 31 checks passed
@josephnef
josephnef deleted the feat/m1-qos-scheduler branch November 13, 2026 07:09
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

skip-qodo-gate Bypass the Qodo review gate (outage / maintainer decision)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant