Skip to content

resources constrains#1425

Merged
alexcos20 merged 3 commits into
next-4from
feature/resources_constrains
Jul 17, 2026
Merged

resources constrains#1425
alexcos20 merged 3 commits into
next-4from
feature/resources_constrains

Conversation

@alexcos20

@alexcos20 alexcos20 commented Jul 16, 2026

Copy link
Copy Markdown
Member

Fixes #1419

Compute resource constraints: group-by-type matching, absolute floor mode, and aggregate summing

Summary

Adds three backward-compatible fields to C2D ResourceConstraint so operators can express
resource rules that the previous model couldn't:

  • type — target a group of resources (e.g. every type:"gpu" resource) and aggregate
    their requested amounts, instead of naming a single exact id.
  • perUnit — choose between the existing ratio semantics (required = parentAmount × value,
    the default) and a new absolute floor/ceiling (perUnit:false) that is enforced only when
    the parent resource is requested.
  • aggregate — sum a single-id constraint's per-parent contribution across every
    requested parent that carries a matching aggregate constraint, into one shared target.

Together these let a compute environment enforce, in config alone, rules such as:

"If any CPU is selected, the job needs at least 1 GPU — no matter which GPU id."
(group type + perUnit:false floor)

"Each GPU needs 1–4 CPUs; renting 2 GPUs means 2–8 CPUs total."
(per-device GPUs + aggregate:true)

The change is additive: every existing constraint (exact id, ratio) behaves exactly as
before, down to the error-message strings.

Motivation

The cross-resource constraint engine matched resources strictly by exact id and only
supported a per-parent-unit ratio, evaluated per parent independently. That left three
things impossible to express:

  1. No "any GPU" concept — a constraint could only name one concrete id ({id:"gpu0"}).
    Listing several ids means AND (need all of them), not OR (any one).
  2. Ratio × max:1 wall — each physical GPU is its own discrete resource with max:1, so a
    ratio constraint (requiredMin = cpuAmount × 1) throws Cannot satisfy the moment a job
    requests more than 1 CPU.
  3. No cross-device summing — with the recommended one-resource-per-physical-GPU model, a
    companion constraint (e.g. cpu per GPU) is evaluated per GPU and does not sum, so "2 GPUs
    ⇒ 2–8 CPUs" wasn't expressible without collapsing the GPUs into a single counted resource
    (which loses per-device pinning).

What changed

Types & schema

  • src/@types/C2D/C2D.tsResourceConstraint now has optional type, perUnit, and
    aggregate; id is optional (exactly one of id | type).
  • src/utils/config/schemas.tsResourceConstraintSchema validates the new fields and
    adds three refinements: at least one and mutual exclusivity of id/type, and
    aggregate requires a single id (rejected on a type group).
    perUnit is intentionally optional() with no .default() — a default would inject
    perUnit:true into every parsed constraint and break existing deep-equal expectations.

Enforcement (src/components/c2d/compute_engine_base.ts)

  • checkResourceConstraints branches on id vs type:
    • perUnit (default true) → ratio (parentAmount × value), unchanged.
    • perUnit:false → absolute floor/ceiling (value), enforced only when the parent is
      requested.
    • Group (type) constraints aggregate the requested amounts across all matching resources
      the environment exposes, and use the sum of member maxes as the "cannot satisfy" ceiling.
  • Group auto-assignment: when a group floor isn't met, bumpGroupToFloor distributes the
    deficit across the group's members, preferring the ones with the most availability (lowest
    inUse), never exceeding any member's own max. Since each physical GPU is a single-DeviceID
    resource, exactly one GPU gets attached. inUse is populated on env.resources before
    constraint-checking runs (via getComputeEnvironments), so this preference is meaningful; the
    authoritative gate remains checkIfResourcesAreAvailable.
  • Aggregate summing: aggregate constraints are skipped by the per-parent loop and handled by
    a new applyAggregateConstraints pass that sums each aggregate constraint's contribution
    (perUnit ? parentAmount × value : value) by target id across all requested parents, then
    raises the target to the summed floor (capped by the target's own max) and rejects on the
    summed ceiling. It runs after the independent-constraint pass and only raises the floor, so it
    composes cleanly with group/floor and companion constraints.
  • New helpers: getGroupResourceIds, getGroupRequestedAmount, getGroupMax,
    bumpGroupToFloor, applyAggregateConstraints.
  • The exact-id / ratio path (and its Cannot satisfy … / Too much … error strings) is
    unchanged.

Docs

  • New docs/compute.md — a single source of truth for C2D configuration: node-wide
    resources → GPU setup (NVIDIA/AMD/Intel, multi-GPU, shareable devices, migration) → env
    refs → resource constraints (companion ratio, group + perUnit:false floor, the counted-vs-
    per-device min/max nuance, and aggregate summing — each with worked examples) →
    availability & tracking → pricing → free tier → verify → checklist → a full, schema-validated
    example config (two environments, GPUs, constraints, multi-chain pricing, free tiers).
  • Removed docs/GPU.md and docs/compute-pricing.md (merged into docs/compute.md);
    all references repointed in CLAUDE.md, README.md, docs/env.md, and docs/services.md.
  • docs/services.md now notes that services share the same compute environments and
    resource pool as compute jobs, linking to the new guide.

Constraint semantics at a glance

Field Effect
id Target a single resource by exact id.
type Target a group of resources by type, aggregating their requested amounts. Mutually exclusive with id.
min / max Bounds on the target. min auto-bumps up; max is enforced and never auto-reduces.
perUnit (default true) Ratio: bound = parentAmount × value. false → absolute floor/ceiling.
aggregate Single-id only. Sums this constraint's per-parent contribution with matching aggregate constraints across all requested parents into one shared target.

Backward compatibility

  • Fully additive. Existing configs (exact id, ratio, no new fields) parse and behave
    identically.
  • perUnit/aggregate are never injected into parsed output, so serialized/compared
    constraints are byte-identical to before.
  • Existing constraint, schema, and resolution tests remain unchanged and green.

Operator configuration (the features in action)

Group + floor — declare the GPU pool at the connection level; put the group+floor
constraint on the environment's cpu ref (not the pool — a pool-level constraint is
inherited by every env, and a GPU-less env would then reject all CPU jobs):

"environments": [{
  "resources": [
    { "id": "cpu", "min": 1, "max": 16,
      "constraints": [ { "type": "gpu", "min": 1, "perUnit": false } ] },
    { "id": "ram" }, { "id": "disk" },
    { "id": "GPU1" }, { "id": "GPU2" }
  ]
}]

type:"gpu" matches any GPU the env exposes (GPU1/GPU2). perUnit:false makes the floor
absolute. If a job selects a CPU with no GPU requested, the engine auto-assigns the freer GPU.

Aggregate — per-device GPUs that jointly scale a companion resource:

{ "id": "GPU1", "constraints": [{ "id": "cpu", "min": 1, "max": 4, "aggregate": true }] },
{ "id": "GPU2", "constraints": [{ "id": "cpu", "min": 1, "max": 4, "aggregate": true }] }

Renting both → cpu in [2, 8] (min 1+1, max 4+4); renting one → cpu in [1, 4].

Constraint recipes

Common patterns the new fields enable (all documented in docs/compute.md and covered by unit
tests; behavior/error strings verified against the engine):

# Recipe Constraint Result
1 RAM range per GPU { "id": "ram", "min": 8, "max": 16 } on a GPU RAM per GPU in [8, 16]; ram:20Too much ram … Max allowed: 16
2 One GPU per CPU (strict 1:1) { "type": "gpu", "min": 1 } on the cpu ref cpu:2 auto-assigns 2 GPUs; cpu:3 with 2 exposed → Cannot satisfy … requires at least 3 gpu resources, but max is 2
3 Cap accelerators per job { "type": "gpu", "max": 2, "perUnit": false } on the cpu ref at most 2 GPUs total; 3 → Too much gpu resources … Max allowed: 2, requested: 3
4 Total RAM scales with GPU count { "id": "ram", "min": 8, "aggregate": true } on each GPU 2 GPUs → RAM 16; 1 → 8
5 Several companions at once [{id:"ram",min:8},{id:"cpu",min:2},{id:"disk",min:20}] on a GPU renting it bumps RAM→8, CPU→2, disk→20 in one pass
6 Absolute RAM floor for a GPU job { "id": "ram", "min": 32, "perUnit": false } on a GPU RAM ≥ 32 regardless of GPU count (ratio would scale it)

Recipe 2 is the ratio counterpart of the group + floor example above: perUnit omitted means
"one GPU per CPU" (scales with CPU count), whereas perUnit:false means "at least one GPU
total" (independent of CPU count).

Notes / not included

  • Group matching (type) and aggregate are separate axes: type groups the target of one
    constraint; aggregate sums one-id constraints across parents. aggregate deliberately
    does not combine with a type target (rejected by schema).
  • Group matching is by type only (e.g. "gpu"). Matching by kind (discrete) would be a
    trivial follow-up using the same helpers if needed.

Files changed

  • src/@types/C2D/C2D.ts, src/utils/config/schemas.ts,
    src/components/c2d/compute_engine_base.ts — feature.
  • src/test/unit/compute.test.ts — tests.
  • docs/compute.md (new), docs/GPU.md + docs/compute-pricing.md (removed),
    docs/env.md, docs/services.md, README.md, CLAUDE.md — docs & references.

Summary by CodeRabbit

  • New Features

    • Added a unified Compute Configuration guide covering resources, GPUs, constraints, availability, pricing, and configuration examples.
    • Added support for group-based, absolute, ratio-based, and aggregate resource constraints.
    • Improved resource allocation behavior for satisfying group minimums and respecting maximums.
  • Documentation

    • Consolidated compute and GPU documentation into compute.md.
    • Clarified how services share compute environments and resource pools.
    • Updated documentation links and references throughout the project.

@coderabbitai

coderabbitai Bot commented Jul 16, 2026

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 108c008b-1e0d-4227-9ba7-4a1b4edbed18

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feature/resources_constrains

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@alexcos20

Copy link
Copy Markdown
Member Author

/run-security-scan

@alexcos20 alexcos20 linked an issue Jul 16, 2026 that may be closed by this pull request

@alexcos20 alexcos20 left a comment

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

AI automated code review (Gemini 3).

Overall risk: low

Summary:
This PR delivers a significant architectural improvement for compute resource configuration. It introduces a connection-level pool for resources, highly expressive constraints (group, absolute floor/ceilings, and aggregates), and excellent documentation with automated tests validating the examples. The code quality is excellent. Only one minor bug related to reading inUse state for the free tier was found.

Comments:
• [WARNING][bug] When resolving inUse for a resource in bumpGroupToFloor, it currently always defaults to the paid env.resources array, even when isFree is true. This could cause the sort heuristic to read from the wrong tier, potentially leading to suboptimal resource allocation for free jobs. Consider resolving envResources conditionally.

   protected bumpGroupToFloor(
     resources: ComputeResourceRequest[],
     env: ComputeEnvironment,
     ids: string[],
     deficit: number,
     isFree: boolean
   ): void {
     let remaining = deficit
+    const envResources = isFree ? (env.free?.resources ?? []) : (env.resources ?? [])
     const candidates = ids
       .map((id) => {
         const current = this.getResourceRequest(resources, id) ?? 0
         const { max } = this.getMaxMinResource(id, env, isFree)
-        const inUse = this.getResource(env.resources, id)?.inUse ?? 0
+        const inUse = this.getResource(envResources, id)?.inUse ?? 0
         return { id, current, headroom: max - current, inUse }
       })

• [INFO][style] Since constraint.id is typed as ComputeResourceType | undefined (which structurally includes any in your type definitions), TypeScript implicitly allows assigning it to string[] or passing it to getMaxMinResource without complaining. Adding an explicit cast makes the contract clearer, especially since Zod already guarantees its presence when isGroup is false.

-        const targetIds = isGroup
-          ? this.getGroupResourceIds(env, constraint.type!, isFree)
-          : [constraint.id]
+        const targetIds = isGroup
+          ? this.getGroupResourceIds(env, constraint.type!, isFree)
+          : [constraint.id as string]

• [INFO][other] The documentation here is truly exceptional. Consolidating the CPU/GPU/pricing setup, thoroughly explaining the constraint behavior, and backing up the "Full example" with an actual CI validation test (in compute.test.ts) is a fantastic engineering practice. Excellent work!

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🧹 Nitpick comments (2)
src/@types/C2D/C2D.ts (1)

39-40: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Reword "absolute aggregate" to avoid confusion with the aggregate field.

Lines 39-40 use "absolute aggregate" to describe perUnit:false semantics, but this same interface also has an unrelated aggregate:true field two lines below (line 42). The similar wording risks readers conflating the two distinct concepts.

✏️ Suggested wording
-  min?: number // min units of the constrained resource; per unit of parent when `perUnit` (default), absolute aggregate when `perUnit:false`
-  max?: number // max units of the constrained resource; per unit of parent when `perUnit` (default), absolute aggregate when `perUnit:false`
+  min?: number // min units of the constrained resource; per unit of parent when `perUnit` (default), absolute total when `perUnit:false`
+  max?: number // max units of the constrained resource; per unit of parent when `perUnit` (default), absolute total when `perUnit:false`
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/`@types/C2D/C2D.ts around lines 39 - 40, Reword the comments on the min
and max properties in the C2D interface to describe perUnit:false as a total or
overall amount, avoiding the term “aggregate” so it cannot be confused with the
separate aggregate field.
src/components/c2d/compute_engine_base.ts (1)

342-358: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Consider extracting the group/single-id target resolution into a small helper.

The isGroup ? ... : ... branch is repeated four times back-to-back to compute targetIds, targetLabel, targetMax, and currentAmount. Extracting this into a single helper (e.g. returning { targetIds, targetLabel, targetMax, currentAmount }) would reduce duplication and make future changes to group-resolution logic single-sourced.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/components/c2d/compute_engine_base.ts` around lines 342 - 358, The
repeated group-versus-single target resolution in the constraint-processing
method should be centralized. Extract the logic currently used for targetIds,
targetLabel, targetMax, and currentAmount into a small helper returning those
values, then consume the helper’s result while preserving the existing behavior
and dynamic currentAmount lookup.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@docs/compute.md`:
- Around line 137-138: Update the GPU identity rules in the compute
documentation to be platform-neutral: require a unique resource id for every
physical GPU, but scope exactly-one DeviceID to NVIDIA deviceRequests and
acknowledge platform-specific mappings such as AMD and Intel
init.advanced.Devices.
- Line 72: Add the text language identifier to the fenced Markdown blocks for
the tree and cost-output examples in the documentation, including both
occurrences, so each fence uses a recognized language tag instead of an
unqualified fence.

---

Nitpick comments:
In `@src/`@types/C2D/C2D.ts:
- Around line 39-40: Reword the comments on the min and max properties in the
C2D interface to describe perUnit:false as a total or overall amount, avoiding
the term “aggregate” so it cannot be confused with the separate aggregate field.

In `@src/components/c2d/compute_engine_base.ts`:
- Around line 342-358: The repeated group-versus-single target resolution in the
constraint-processing method should be centralized. Extract the logic currently
used for targetIds, targetLabel, targetMax, and currentAmount into a small
helper returning those values, then consume the helper’s result while preserving
the existing behavior and dynamic currentAmount lookup.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: fad70ace-f734-42c8-9201-bb85deeb8f16

📥 Commits

Reviewing files that changed from the base of the PR and between 7bcf7f0 and dccfce3.

📒 Files selected for processing (11)
  • CLAUDE.md
  • README.md
  • docs/GPU.md
  • docs/compute-pricing.md
  • docs/compute.md
  • docs/env.md
  • docs/services.md
  • src/@types/C2D/C2D.ts
  • src/components/c2d/compute_engine_base.ts
  • src/test/unit/compute.test.ts
  • src/utils/config/schemas.ts
💤 Files with no reviewable changes (2)
  • docs/compute-pricing.md
  • docs/GPU.md

Comment thread docs/compute.md

## Configuration layout

```

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Add languages to the fenced Markdown blocks.

The tree and cost-output fences need identifiers to satisfy Markdown linting.

Proposed fix
-```
+```text

Also applies to: 920-920

🧰 Tools
🪛 markdownlint-cli2 (0.23.0)

[warning] 72-72: Fenced code blocks should have a language specified

(MD040, fenced-code-language)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@docs/compute.md` at line 72, Add the text language identifier to the fenced
Markdown blocks for the tree and cost-output examples in the documentation,
including both occurrences, so each fence uses a recognized language tag instead
of an unqualified fence.

Source: Linters/SAST tools

Comment thread docs/compute.md Outdated
Comment on lines +137 to +138
- Each physical GPU is its own resource with a unique `id` and exactly **one** `DeviceID`.
- `kind: "discrete"` (non-fungible): only one job at a time can use the device. This is the

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Make the GPU identity rule platform-neutral.

This says every physical GPU must have exactly one DeviceID, but the AMD and Intel examples use init.advanced.Devices instead. Restrict DeviceID to NVIDIA deviceRequests, or describe the requirement as a unique resource ID plus platform-specific device mapping.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@docs/compute.md` around lines 137 - 138, Update the GPU identity rules in the
compute documentation to be platform-neutral: require a unique resource id for
every physical GPU, but scope exactly-one DeviceID to NVIDIA deviceRequests and
acknowledge platform-specific mappings such as AMD and Intel
init.advanced.Devices.

Comment thread src/components/c2d/compute_engine_base.ts
@alexcos20

Copy link
Copy Markdown
Member Author

fix: validate constraint ceilings after all floor bumps settle

Resource constraint checking was single-pass: each parent's min and max
were evaluated in the order resources appear in the env definition. A
later parent's min bump could raise a target past an earlier parent's
already-checked max, admitting jobs that violate a declared ceiling.

Example: gpu0 {id:'cpu', max:6} and gpu1 {id:'cpu', min:8}, both
requested with cpu=4. With gpu0 listed first, its max check passed
(cpu=4), then gpu1's min bumped cpu to 8 — the job was admitted with 8
cpu against gpu0's ceiling of 6. With the order reversed, the same
request was rejected.

checkResourceConstraints is now two-phase: satisfy all direct floors,
apply aggregate constraints, then validate every direct ceiling against
the final settled amounts. Outcome is order-independent; unsatisfiable
combinations are always rejected. Error messages unchanged.

Adds unit tests covering the contradictory-constraint scenario in both
env orderings, plus single-GPU requests that must keep working.

@alexcos20
alexcos20 requested a review from dnsi0 July 17, 2026 12:24
@alexcos20
alexcos20 merged commit 38507db into next-4 Jul 17, 2026
10 checks passed
@alexcos20
alexcos20 deleted the feature/resources_constrains branch July 17, 2026 14:04
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Compute resources constrains

2 participants