Skip to content
ULAP ONE
Research preview
AI-RAN WG3 · WI#6 · co-tenancy

Edge AI co-tenancy

not reported

Two questions. Are the RAN and the AI tenant sharing this accelerator without hurting each other, and how many real subscribers are actually on? Both answers are at the top; everything below is the evidence behind them.

As last swept · Reading the record… Right now · Unknown Real attaches · unknown Mode unknown
Waiting Not a snapshot Updated nothing has answered yet Sources 0 of 6 sources answering · 6 waiting
Host metrics no answer yet Attached UEs no answer yet Mode controller no answer yet 5G stack no answer yet Demand generator no answer yet MPS control plane no answer yet
Data provenance

What this page reads, and what the numbers on it mean

Waiting

Identity

Read by
Your browser, over this origin. No server-side cache sits in between.
Mode
Live poll. This is not a snapshot and it is not a recording.
Last read
not yet
Sources
6

Measurements

Host metrics
Waiting · never
GET /api/ran/metrics/host · every 1 s · CPU, GPU and memory split by tenant
Attached UEs
Waiting · never
GET /api/ran/ue/attached · every 2 s · the three-state attach measurement — a UE holding an address on a live kernel tunnel
Mode controller
Waiting · never
GET /api/mode/status · every 3 s · operating mode, the AI tenant state, TVWS occupancy and PAWS
5G stack
Waiting · never
GET /api/ran/stack/status · every 5 s · core network functions, gNB and UPF liveness
Demand generator
Waiting · never
GET /api/subscribers/status · every 5 s · the SIMULATED subscriber counter — a demand model, never an attach
MPS control plane
Waiting · never
GET /api/cotenancy/api/v1/cotenancy · every 5 s · the LIVE census — is a control daemon up right now, and is every client that COULD join its server inside it

Thresholds, and where they come from

Stale after 2.5 poll cadences (floor 4 s, cap 90 s)
Source: ui/src/lib/domain/liveness.svelte.ts — derived from wall clock, not set by the poll, so a loop that DIED is still detected
Not answering after 3 consecutive misses
Source: ui/src/lib/domain/liveness.svelte.ts — one miss is a blip, three is a fault
Static after 20 poll cadences with no change in the reported value (floor 60 s, cap 5 min)
Source: ui/src/lib/domain/liveness.svelte.ts — an ORTHOGONAL axis. A static source is still live: it answered. The two facts are separate because on 2026-08-03 a tile read 0.0 Mbps for fifteen minutes beside a chip that said "Live · just now". Only sources that report their value to feed.ok(id, value) are judged; the rest claim nothing.

What this proves

  • Each source above answered your browser at the stated time.
  • The CPU split between the RAN and the AI tenant is measured by ran-manager, not modelled.
  • The attach count comes from a live address on a live kernel tunnel, and carries the evidence that produced it.

What this does NOT prove

  • A live host-metrics feed does not prove L1 deadlines held. The published co-tenancy result is LDPC decode only, on a cleared GPU, by a bench harness.
  • A byte-rate over a ZMQ or loopback fabric is a user-plane number, not a radio measurement.
  • The simulated demand counter is not an attach count. The two are different quantities and must never be summed.

Environment

Node
GB10, sm_121, dev-class ARM edge box. Not an NVIDIA-qualified RAN platform.
ran-manager
A HOST process, not a compose service. Compose never brings it up.
Three artefacts
The MPS sweep, the cap-enforcement probe and the cuPHY latency run are BUILD SNAPSHOTS read from /artefacts/. Everything else on this page is a live poll.
Two isolation planes
The isolation question is answered twice, by two DIFFERENT gates, and the two are never merged. "As last swept" is the stored sweep artefact under mps.all-clients-managed — it describes a controlled benchmark run, where demanding a completely clean GPU is legitimate, and it can only describe the GPU at the moment that sweep ran. "Right now" is the live MPS control-plane census from wg3-cotenancy-exporter under mps.in-scope-clients-managed. When the exporter cannot be reached the live plane reads unknown; it does not borrow the artefact’s answer.
Why the live gate is scoped
MPS runs one server per uid at a time on a device, and a client of any other uid is queued on a pending list and blocks rather than failing. The CUDA workloads on this box span five uids, so at most one group can be managed at any instant. A gate asking "is every client managed" could never pass however the operator behaves — and trying to satisfy it by restarting foreign tenants into MPS hangs them; that was observed, and it silently dropped a GPU service onto the CPU. So the live gate covers the uid of the active server, and every tenant it does not cover is listed beside the verdict with its uid, its memory and the reason.

Provenance

Host metrics
GET /api/ran/metrics/host · every 1 s
Attached UEs
GET /api/ran/ue/attached · every 2 s
Mode controller
GET /api/mode/status · every 3 s
5G stack
GET /api/ran/stack/status · every 5 s
Demand generator
GET /api/subscribers/status · every 5 s
MPS control plane
GET /api/cotenancy/api/v1/cotenancy · every 5 s
Caveat, verbatim

The co-tenancy result is LDPC decode only, on a cleared GPU, measured by a bench harness rather than by this compose stack’s data plane.

Raw source

  • docs/THESIS.md section 2b

Are they sharing without hurting each other?

swept · mps.all-clients-managed live · mps.in-scope-clients-managed

As last swept

expired · cannot tell which boot

The GPU as the stored sweep recorded it. This says nothing about the GPU now, and its process identifiers can no longer be resolved.

Reading the record…

The build-snapshot artefacts have not been read yet.

Source /artefacts/mps-sweep-2026-08-03-livebox.json

Right now

live, every 5 s

The MPS control plane as this browser can read it, this second. A SCOPED gate: it covers the clients that could join the active server, and names every tenant it does not cover.

Unknown

Scope The scope of this gate could not be read, so which clients it even covers is unknown.

Nothing has been read from the live exporter yet, so whether this GPU is under MPS control right now is unknown. It is not a pass.

To green Nothing is wrong with the GPU yet — nothing has been asked. This resolves as soon as the co-tenancy exporter answers a poll.

MPS runs ONE SERVER PER UID at a time on a device. Only clients of the active server’s uid can join it; a client of any other uid is queued on a pending list and BLOCKS rather than failing. The CUDA workloads on this box span several uids, so at most one group can be managed at any instant — a gate asking "is every client managed" could never pass, and trying to satisfy it by restarting foreign tenants into MPS hangs them.

Control daemon
unknown — could not probe
Managed clients
unknown — not zero
Server uid
unknown — not uid 0
Unmanaged clients
unknown — not zero
Out of scope
unknown — the scope was not read

A thread cap PROVISIONS, it does not RESERVE. CUDA_MPS_ACTIVE_THREAD_PERCENTAGE limits how many SMs a client may use; NVIDIA’s documentation is explicit that kernels from different clients may still execute on the same SM. Every client being managed means every client has a cap — it is not hard isolation and it is not a guaranteed share. MIG hard-partitions; MPS shares with a cap.

Source GET /api/cotenancy/api/v1/cotenancy

Only the live plane has an answer The stored sweep could not be read, so there is nothing to compare. The live plane on the right is the only plane that can answer whether this GPU is under MPS control, and it is the one to read.

How many real subscribers are on?

live, every 2 s
Attached UEs
Unknown — could not ask

No poll has completed yet on this page load. Nothing is asserted.

A UE holding an IPv4 address on a tunnel interface that exists in the kernel right now. Not a process that exists, and not the AMF's registered-UE table.

This is not the simulated demand counter. That number is further down this page, under its own heading, and the two must never be added.

Source GET /api/ran/ue/attached

Attach more (actuator)

The co-tenancy measurements

Build snapshot — not a live reading

LDPC decode against the slot budget

not read

Reading the artefact…

MPS thread caps

config/cotenancy/mps-shares.yaml

Declared per-workload CUDA_MPS_ACTIVE_THREAD_PERCENTAGE shares. Two profiles, because this box runs no Aerial DU, so the paper-faithful split cannot be honoured here.

Declared MPS thread-cap shares per workload, two profiles.
WorkloadPaperThis box
cuphy-du-l160%0%
sr-worker20%50%
tvws-sensing10%20%
sr-worker-quality5%20%
ollama-llm5%10%
Applied right now, per live client

The live exporter has not reported a managed client list, so which caps are actually in force is unknown. It is not "none".

scripts/cotenancy/start-mps.sh reads this file, but it currently starts nvidia-cuda-mps-control and applies none of these values. A declared cap that is never set is a plan, not a control, and this page will not render it as one.

A thread cap PROVISIONS, it does not RESERVE. CUDA_MPS_ACTIVE_THREAD_PERCENTAGE limits how many SMs a client may use; NVIDIA’s documentation is explicit that kernels from different clients may still execute on the same SM. Every client being managed means every client has a cap — it is not hard isolation and it is not a guaranteed share. MIG hard-partitions; MPS shares with a cap.

The isolation gate

mps.all-clients-managed

Was the GPU free of processes outside the MPS server for the whole run?

As last swept · Reading the record… Right now · Unknown
Rule
mps.all_clients_managed must be true
As last swept
artefact not read
Right now
unknown — the live census could not be read, not zero
Severity
blocking
Blocks
that this run demonstrates isolation between the RAN and AI clients

What each tenant is taking right now

Live poll

RAN

n78 TDD 3.6 GHz 50 MHz PLMN 342-99
CPU --%
gNB
unknown
Core
--
NGAP
--
UPF
unknown
Demand DL / UL
-- / -- not reported
CPU breakdown

Not reported. An empty bar here would read as "no CPU is being used", which is a measurement nobody took.

AI

SR worker unknown SR and sensing on the same GPU
CPU --%
SR worker
SR worker unknown
Model
--
Compute
--
Sensing
Sensing unknown
Frames/s
--
Latency
--
Dropped
--
TVWS spectrum
Channel 21
Unknown
Model conf
--
Backhaul
--

Research preview, not a spectrum-access authority. On the 28-channel UHF survey of 2026-08-04 (560 records, 10 occupied / 15 vacant, ground truth from measured physics) this checkpoint detected 2 of 10 live multiplexes and called 48 of 300 vacant records occupied, at 0.93 to 0.99 confidence. With the physics detector wired in — same unchanged checkpoint, one process, differing only in whether a calibrated control floor is passed — that becomes 10 of 10 multiplexes and 0 of 15 vacant channels called occupied. Not clean yet: UHF 28 still asserts OCCUPIED on 8 of 20 records, and that threshold was not moved. This caveat used to blame the threshold for calibration on synthetic noise; a measured receiver-noise null falsified that on 2026-08-05 — real receiver noise tops out at comb z = 2.289 against the synthetic 2.19, and 0 of 300 records reach the 3.5 threshold. The firing arrives through the antenna and its cause is now open: a genuine emission below the energy floor, which would mean the ground truth needs revising rather than the detector, or front-end intermodulation. The RX-gain sweep that separates them has not been run. The earlier “40 of 80” figure came from a 2026-07-29 set that was entirely occupied, on which answering OCCUPIED unconditionally also scores 80 of 80 — it measured no discrimination. Rollout is opt-in and this service was not restarted: with no calibrated control floor configured the engine falls back to the legacy CNN verdict and reports occupancy_source: cnn-confidence, so the channel state above is the classifier’s unless a floor is set. Model confidence is not accuracy: high confidence has been measured on wrong answers. PAWS grant state is the authority.

PAWS database
Status
unknown
Waiver
none

Accelerator

nvidia-smi · every tenant combined
GPU --%
MEM --%

Utilisation, not framebuffer occupancy. The DCGM framebuffer counters do not exist on GB10, so there is no memory-occupancy number to show and none is invented. This gauge is the whole accelerator, including any tenant outside the MPS server — which is exactly why the isolation gate above cannot attribute it.

Attached subscribers

Count unknown
Attached subscribers — from the evidence behind the attach measurement
IMSITYPEATTACHEDIPTUNNELPROFILEDL MbpsUL Mbps
ran-manager did not answer. How many UEs are attached is unknown — this is not zero, and this table is not a statement about the network.

Simulated load — a demand model, not subscribers

Never add this to the attach count

Demand generator

subscriber-sim
Simulated UE records
Unknown — could not ask

No poll has completed yet on this page load.

The length of an in-memory list of generated UE records, producing synthetic demand.

No NAS registration, no PDU session, no tunnel, no radio. Nothing in the 5G core has heard of these.

Source GET /api/subscribers/status

This counter is real work — the generator really is holding that many records and really is producing that much synthetic demand. What it is not is an attach. It used to be printed elsewhere in this console under the label "Attached UEs" while three real UERANSIM UEs were on tunnels and the same payload reported a real count of zero.

Mode control

mode-controller degrade below 9 Mbps · recover above 12 Mbps

Force locks the controller. Reset returns it to autonomous hysteresis, which is trace-driven unless real UPF metrics have been started.

Events observed by this page
Events observed by this page since it was opened
TIMEEVENT
Nothing has changed since this page was opened.
The co-tenancy claim

What sharing one GPU between a radio and an AI tenant does and does not show

Scoped claim Not a qualified RAN platform Isolation reading the record…

Identity

Node
GB10, sm_121, dev-class ARM edge box
RAN
Open5GS core and an srsRAN Project gNB, ZMQ virtual RF by default
AI tenants
SPAN super-resolution on the display path, ResNet18 TVWS sensing
Gate
Mode controller with asymmetric hysteresis and dwell timers

Measurements

Real attaches, right now
unknown — could not ask
From /api/ran/ue/attached — a UE holding an IPv4 address on a kernel tunnel that exists right now. An unknown carries no number at all.
Simulated demand counter
unknown — could not ask
A different quantity. Nothing in the 5G core has heard of these. Never add the two.
Demand DL / UL
-- / -- Mbps
Source: not reported. Over a sim or loopback fabric this is a user-plane byte-rate, not a radio number.

Thresholds, and where they come from

Degrade UL below 9 Mbps for 3 s
Source: mode-controller hysteresis configuration
Recover UL above 12 Mbps for 12 s
Source: mode-controller hysteresis configuration — deliberately asymmetric to stop flapping
mps.all-clients-managed mps.all_clients_managed must be true
Source: ui/src/lib/domain/benchmarks/contracts/gpu.ts — blocking

What this proves

  • Two tenants are scheduled on one accelerator at the same time, and the split of CPU between them is measured rather than asserted.
  • The mode controller demonstrably yields AI bandwidth and GPU back to the radio when the uplink degrades, with a dwell timer that stops the decision oscillating.
  • The attach count on this page is evidence-backed: it is a live address on a live tunnel, checked against the kernel, not the AMF’s registration table.

What this does NOT prove

  • It does NOT show that L1 deadlines hold under AI load on this compose stack. The published co-tenancy result is LDPC DECODE ONLY, on a cleared GPU, measured by a bench harness — not by this data plane.
  • It does NOT show isolation between the RAN and AI clients. The sweep that would have shown it ran with uncontrolled tenants on the GPU and its blocking gate fails.
  • It does NOT make this node an NVIDIA-qualified RAN platform. It is a dev-class ARM edge box.
  • A byte-rate over a ZMQ or loopback fabric is a user-plane number and is NOT a radio measurement.
  • The simulated demand counter is not an attach count and the two must never be summed or substituted.

Environment

Fabric
ZMQ virtual RF by default; conducted SDR and O-RU 7.2x are the other two fabrics
GPU
One accelerator, shared. GPU framebuffer occupancy does not exist as a counter on GB10.
Bulk UEs
UERANSIM — real NAS registration and a real IP, L3 and above only

Provenance

Host metrics
GET /api/ran/metrics/host
Attach measurement
GET /api/ran/ue/attached
Simulated counter
GET /api/subscribers/status
Mode, TVWS, PAWS
GET /api/mode/status
Stack liveness
GET /api/ran/stack/status
Isolation, caps, latency
/artefacts/mps-sweep-2026-08-03-livebox.json, mps-cap-result.json, cuphy-per-slot-20260729-005456.json
The scoped claim
docs/THESIS.md section 2b
Caveat, verbatim

The co-tenancy result is LDPC decode only, on a cleared GPU, measured by a bench harness rather than by this compose stack's data plane. The node is a dev-class ARM edge box and is not an NVIDIA-qualified RAN platform. Anywhere this console shows a byte-rate over a sim or loopback fabric, it is a user-plane byte-rate and not a radio number.

Raw source

  • docs/THESIS.md section 2b scopes the claim exactly
  • /evidence.html the benchmark artefacts behind it
Blocking gate

Was the GPU free of processes outside the MPS server for the whole run?

Reading the record…

Identity

Gate
mps.all-clients-managed
Rule
mps.all_clients_managed must be true
Severity
blocking
Artefact
/artefacts/mps-sweep-2026-08-03-livebox.json
Recorded
not read
Blocks the claim
that this run demonstrates isolation between the RAN and AI clients

Measurements

Uncontrolled tenants on the GPU at start of run
artefact not read
Uncontrolled default-mode tenants share the same SMs. With any present, the measurement is of the whole machine, not of the capped client.
Reportable as a clean MPS run
not stated
The producer refuses the label itself; the UI is not making this judgement.
Deadline guarantee supported
not stated
A false here is the artefact declining to support a deadline claim from its own data.
Largest AI cap observed passing
withheld
Withheld on a partial sweep: an envelope read from cells that were never measured is not an envelope.
Cells completed
not stated
The artefact states no partial-run note.
End-of-run GPU check
did not run
A check that did not run proves nothing, and its empty result is not a clean result.

Thresholds, and where they come from

mps.all-clients-managed mps.all_clients_managed must be true
Source: ui/src/lib/domain/benchmarks/contracts/gpu.ts — blocking

What this proves

  • The sweep ran, on this box, and its per-level latencies were recorded with the cap in force on the AI client.
  • The harness censused every CUDA process on the GPU and published the list rather than assuming a clean machine.

What this does NOT prove

  • Uncontrolled tenants were on the GPU for this run. No isolation claim is available from it, and the latency it reports is not attributable to the capped client.
  • It does NOT show what a thread cap would do on a clean GPU. An uncontrolled neighbour is outside the MPS server, so a cap sweep run against neighbours measures the neighbours at any cap value.
  • A provisioning knob cannot yield an isolation guarantee even on a clean run: the documentation is explicit that kernels from different clients may still execute on the same SM.

Environment

Scope
not stated by the artefact
Timing
not stated by the artefact
Slot budget
not stated

Provenance

Raw artefact
/artefacts/mps-sweep-2026-08-03-livebox.json

Raw source

Latency against a stated budget

cuPHY LDPC decode versus the 500 us slot budget

Artefact not read Not a full slot

Identity

Artefact
/artefacts/cuphy-per-slot-20260729-005456.json
Recorded
not read
Device
no device block in this schema version — host attribution rests on prose
Series quoted
cuphy_decode
Series NOT quoted
pyaerial_decode

Measurements

Slot budget
not stated
The producer’s stated budget, not a 3GPP requirement.
p50
not stated
n = not stated
p90
not stated
cuphy_decode
p99
not stated
cuphy_decode
p99.9
not stated
cuphy_decode
max
not stated
cuphy_decode
Worst overrun
not stated
Zero means nothing in this series crossed the budget.
The wrapper series, for contrast
not stated
pyAerial’s public decode() re-copies the LLR block and the output on every call. Quoting it as PHY latency would measure the Python binding, not the decoder.

Thresholds, and where they come from

Slot budget not stated
Source: slot_budget_us in the artefact, which is the producer’s stated budget, not a 3GPP requirement.

What this proves

  • On this run the LDPC decoder stayed inside the stated budget on every one of its samples, and the distribution behind that is in the artefact.

What this does NOT prove

  • LDPC decode only. A 5G slot also carries control channels, DMRS and the rest of the chain.
  • It does NOT support a full-slot L1 deadline claim; is_a_full_slot is false in the artefact itself.
  • Random Gaussian LLRs are correct for deadline analysis and wrong for throughput claims.

Environment

LLR mode
not stated by the artefact
Series note
not stated by the artefact

Provenance

Raw artefact
/artefacts/cuphy-per-slot-20260729-005456.json

Raw source

Amini Amini Infratech for the Global South

Every link and every data fetch on this console is origin-relative. One origin fans out by path: / gateway, /video/, /grafana/, /prom/, /api/. An absolute http://localhost:NNNN URL works on exactly one machine, the one it was written on, and is blocked as mixed content the moment the page is served over https, which is how every operator actually reaches this stack.