myxce/.kiro/steering/resource-model.md
Rongsong Shen 5b05d51b7a
docs(steering): resource decision and question log (PR-1..3 proposals, R/P/N items)
Persistent log of the resource-design discussion: the user's proposals
(generic supply map incl. provisioned supply; a resource provider proxy
between the controller and upstream providers; datasets in the map with
staging as data provisioning) with analysis, the user's 2026-10-11 input
(duration/decay must-have but future; RBAC in the apiserver, discuss later;
OCI containers via application profiles; CSI compatibility preferred;
GPU/topology scheduler-first and extensible), the open R1-R9/P1-P9 items
and new questions N1-N4, C3. resource-model.md, the provisioner research,
roadmap and CLAUDE.md point at it.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-12 04:00:47 +00:00

11 KiB
Raw Blame History

Resource Model: one vocabulary, three layers

Status: direction adopted 2026-10-11 (resource-model discovery, after the 2026-10-10 resource-provisioner research). The open decisions at the end are the user's and still pending; specs built on this document must not settle them silently.

Why

XCE is heading toward a unified platform for applications such as LLM training and deployment (serving), not only classic HPC batch. So resource features come from IBM LSF (consumables, rusage, elim, licenses, -app profiles, data manager) and Kubernetes (extended resources, device plugins, CSI volumes, Dynamic Resource Allocation claims). Three asks drive this document:

  1. Treat data and volumes as resources.
  2. Let admins define custom resources.
  3. Give all resources one interface.

The answer is yes to all three, as one resource type vocabulary and one provider contract over three layers of behaviour. It is not a single mechanism: none of LSF, Slurm, PBS or Kubernetes uses one mechanism for everything, and a per-job claim object for slots/mem would put lifecycle cost and WAL traffic on the scheduler's hottest path.

Current state (2026-10-11)

  • Descriptor: xce_resource_t (src/include/xce/types.h:133-145) has three value kinds (string, numeric, boolean) and the flags BUILTIN, DYNAMIC, GLOBAL, SHARED, EXTERNAL, RELEASE, DEFAULT_HOST, CONSUMABLE and LIMIT (types.h:81-89). Config (resources.yaml via xceadmin) reaches only CONSUMABLE and RELEASE. resourcemap is dropped at build (xceadmin/build.py:38-43), and its capnp fields are commented out (controller.capnp:213-214).
  • Works with config only: static per-host custom resources (numeric, boolean, string, consumable or not) declared in resources.yaml, with per-host values in hosts.yaml.
  • Missing:
    • runtime custom values: only a worker connection may update values (controller/opupd.c:11-26); the 11 load indices are compiled in (worker/src/resource_sync.c:84-104); there is no elim-like reporter;
    • cluster-wide pools such as licenses: the pool machinery exists for limit virtual resources (sharedInst, scheduler/src/resource.c:292), but mark_host_res() rejects SHARED (controller/worker.c:259). The scope choice is pending in resource-distribution-mapping;
    • device or volume identity: allocations are counts, and xce_res_account_t.instid is a per-process counter, not an identity;
    • lifecycle (create, stage, attach, release);
    • rusage duration/decay: parsed, ignored (alloc_runner.c:429);
    • affinity[]: parsed (lib/resreq.c:127), never used;
    • GPUs: only an unmapped gpu_req queue string.
  • Data: only scratch capacity (tmp/maxtmp). File staging is parsed but never performed (bug-1937). Sandbox workspaces (job-sandbox-execution) are a planned per-job storage lifecycle, but are not modelled as resources.

The model

Resource type (one descriptor for every resource)

name, kind (numeric, boolean, string, later string-set), unit, order, consumable, scope (host, host group, shared pool, external), reporter (static, builtin, provider), interval, layer, instances (counted, or identified with attributes), provider (reference; optional). This makes today's flags explicit, and it is the descriptor that resource-distribution-mapping extends.

Three layers

Layer What it is Examples Scheduler sees
L1 attribute not consumed; matched by select[] host model, dataset X cached here, NVLink domain, switch block cached values (as today)
L2 counter consumed amount, host or pool scope slots, mem, scratch GB, licenses, filesystem quota cached resvals/sharedInst (as today)
L3 instance / claim which unit, optionally with a lifecycle GPU, MIG slice, NIC rail, port; volumes, burst buffer, checkpoint and model-weight stores identified inventory with attributes; claim placement as a constraint

A resource opts into L3 only if it needs identity or lifecycle. L1 and L2 stay as fast and deterministic as now.

Provider contract

A resource can have a provider with optional capabilities: report (values, or an instance inventory with attributes), prepare/attach before use, release/cleanup after, and health. It shares the transport and rules of the resource-provisioner contract (.kiro/specs/resource-provisioner/research.md):

  • JSON documents; one-shot scripts first, a persistent JSON-lines mode later;
  • every mutating action idempotent, keyed by an op_id;
  • owner tags on every external object;
  • an intent fact committed before any external action;
  • a lease epoch on facts;
  • orphan scans;
  • started with posix_spawn() or Go os/exec, never fork() in a threaded process (tech.md "Process Creation").

Where providers run:

  • node-local (GPU, scratch, mounts): under the worker, which does the prepare step (devices, cgroup, env);
  • external (cloud, storage arrays, license servers): under a provisioner daemon with its own controller role.

Claims (L3 with a lifecycle)

  • Template claim (like a K8s ResourceClaimTemplate): written in a job's resreq or application profile, created per job, task or replica, and released with it. Examples: scratch, staging, KV cache. This is the LSF mental model.
  • Standalone claim (like a K8s PVC or ResourceClaim): a controller entity with its own id, owner/project, access mode (RWO, ROX, RWX, RWOP) and reclaim policy (retain/delete). Many jobs or replicas reference it (e.g. --claim ckpt-7:rw), and it outlives them. It is re-bound on restart, resize or replica replacement. Examples: checkpoint volumes (one writer gang), model weights (read-many by every replica), dataset caches.
  • States: PENDING → PREPARING → READY → RELEASING → RELEASED/FAILED. Dispatch waits until every claim of the job (or of the whole gang) is READY.

Rules every resource design must keep

  • The scheduler only chooses. It reads numbers, attributes and inventory from its cached session snapshot (the DRA "structured parameters" model). It never calls a provider and never waits for one. Prepare, attach, staging and release happen in the controller, worker or provisioner after dispatch. Claim readiness is an input event, not something the scheduler polls. This keeps sessions fast and sim/replay deterministic; the simulator models provider latency.
  • Persistence: instance assignments (which GPU or port), claim records, shared totals and borrowed amounts are WAL facts or snapshot fields. They must round-trip through every save/restore and apply seam (persistence.md field-completeness; run /xce-audit-invariants). A claim that costs money or holds data is durable from its first version.
  • HA: claims follow the resource-provisioner discipline: intent before action, op_id, epoch, owner tags, orphan classes.
  • Batch stays LSF-style: counted consumables, rusage, max(reserved, actual) accounting (resource-accounting.md), licenses as shared pools, limits as virtual resources. These need no lifecycle and stay constant-cost per session.
  • Request syntax stays LSF-style: select[], rusage[], span[], same[], affinity[], extended with attribute predicates over instances. There is no CEL engine in the scheduler.
  • Security:
    • provider credentials stay in the provider process, never in job exdata, cluster config sent to workers, or the wire;
    • every reporter and provider role is on the per-role opcode allowlist (peer-authentication-hardening);
    • users never choose arbitrary mount paths (application-profile mount rules).

Mapping to LSF and Kubernetes

XCE LSF Kubernetes
resource type descriptor lsf.shared + ResourceMap DeviceClass / StorageClass / extended resource
L1/L2 reporter (static, builtin, provider report) elim device plugin ListAndWatch / DRA ResourceSlice
L2 shared pool shared resource, License Scheduler (extended resource at cluster level)
L3 identified instance -gpu, affinity device plugin Allocate / DRA device
template claim rusage + data manager staging ResourceClaimTemplate, ephemeral volume
standalone claim (none) PVC, ResourceClaim
worker prepare/publish (job starter, cgroup) NodePrepareResources, CSI NodeStage/Publish
provider create/attach resource connector CSI controller
access mode (none) RWO/ROX/RWX/RWOP

Adopt the Kubernetes concepts. Whether XCE also speaks the CSI or device-plugin wire protocols is open decision 4.

LLM training and serving

  • Accelerator topology: NVLink/NVSwitch domains, GPU and NIC on one PCIe switch, IB rails. These are L3 instance attributes plus constraints between instances, served by an allocator that consumes affinity[]. Cluster-level topology (switch block) is an L1 host attribute that same[]/span[] already pack against.
  • MIG/MPS: partitions are reported as instances. Changing a layout is a provider action that drains the GPU, never a scheduler decision.
  • Gangs: one parallel job is already placed all-or-nothing (parjob-placement). A gang of several jobs (launcher plus workers) needs a gang group; the whole gang and its claims are READY before any part starts.
  • Elastic resize and restart: standalone claims are re-bound to the new host set.
  • Serving: replicas share one read-many weights claim and have per-replica template claims. A rolling update attaches version N+1 while version N stays attached.

Phases

  1. resource-distribution-mapping: shared pools (option 2) plus the descriptor fields above. Unblocks licenses (elastic-shared-resources) and shared quotas.
  2. custom-resource-reporting: runtime custom values and inventory (option 3): reporters other than the worker connection, a string-set kind for dataset locality, and interval enforced.
  3. resource-instances: identified GPU, MIG, NIC and port instances with attributes, durable assignment, and a topology-aware allocator using affinity[]. Shared with service-endpoint (ports).
  4. resource-claims: template and standalone claims as WAL entities, the provider contract, worker prepare/publish, and a stage-in/stage-out gate. First users: sandbox scratch, transfer_files staging, checkpoint volumes.
  5. gang-groups: gangs of several jobs. Claim re-attach in job-resize and shared weights claims in service-replica-set rollouts.

Config-only now: document the static custom resources that already work.

Open decisions

Kept in resource-questions.md, the persistent decision and question log (R1–R9 from this document, the provisioner's P1–P9, new questions, and proposals PR-1 to PR-3 that may reshape the phases above: one supply map that includes provisioned supply, a resource provider proxy, datasets in the map with staging as provisioning). Check it before writing requirements for any phase.