Skip to content

fix(device): re-check ResourceQuota in Fit() for non-NVIDIA backends - #2377

Closed
Lakshya77089 wants to merge 1 commit into
Project-HAMi:masterfrom
Lakshya77089:fix/fit-quota-all-backends
Closed

fix(device): re-check ResourceQuota in Fit() for non-NVIDIA backends#2377
Lakshya77089 wants to merge 1 commit into
Project-HAMi:masterfrom
Lakshya77089:fix/fit-quota-all-backends

Conversation

@Lakshya77089

@Lakshya77089 Lakshya77089 commented Aug 5, 2026

Copy link
Copy Markdown
Contributor

What type of PR is this?

/kind bug

What this PR does / why we need it:

Follow-up to #2347, from @mesutoezdil's review there. #2347 made the admission
webhook enforce ResourceQuota for every backend. Admission alone is not enough.

Namespace usage is only recorded by QuotaManager.AddUsage at Filter time, not
at admission. So a burst of pods created together all reach the webhook before
any of them has been scheduled, all read the same value of Used, and all pass.
NVIDIA caught this on the way through Fit() — by the time the second pod is
scored, the first one's usage has been recorded. No other backend had that
second gate, so the whole burst schedules and the namespace ends up over its
limit.

Reproduces as a five-replica Deployment against a quota sized for two pods: on
nvidia.com/gpumem the extra replicas are denied, on
cambricon.com/mlu.smlu.vmemory all five schedule.

fitQuota was the only implementation and it lived in pkg/device/nvidia,
hardcoded to NvidiaGPUDevice and MemoryFactor. Both of those are now
per-backend values after #2347, so the helper moves to pkg/device as
FitQuotaWithPodDevices and takes them as arguments. NVIDIA keeps its
one-line wrapper and behaves exactly as before.

The five backends that share NVIDIA's Fit() shape — ascend, cambricon, hygon,
iluvatar, mthreads — call it at the same point NVIDIA always has: after memreq
is resolved, before the per-card capacity checks.

if !device.FitQuotaWithPodDevices(tmpDevs, allocated, pod.Namespace, k.Type,
    int64(memreq), int64(k.Coresreq), cam.GetResourceNames().MemoryFactor) {
    reason[common.ResourceQuotaNotFit]++
    klog.V(3).InfoS(common.ResourceQuotaNotFit, "pod", klog.KObj(pod), "memreq", memreq, "coresreq", k.Coresreq)
    continue
}

The helper counts cards already picked for this pod, both the ones in tmpDevs
and the ones in allocated, so a multi-card request is weighed as a whole
rather than waved through one card at a time.

Tests

TestFitQuotaWithPodDevices and TestFitQuotaWithPodDevicesNilAllocated in
pkg/device/quota_test.go cover the accumulation across tmpDevs and
allocated, cores exceeding on their own, the memory factor raising the limit,
and a namespace with no quota.

TestDevices_FitResourceQuota in pkg/device/cambricon/device_test.go is the
one that matters — it drives the real Fit() and asserts a card with plenty of
free memory is still refused when the namespace is out of quota, with
ResourceQuotaNotFit in the reason. It fails on master and passes here.

Which issue(s) this PR fixes:
Refs #2363

Deliberately Refs rather than Fixes, because #2363 also covers the backends
below.

Special notes for your reviewer:

Scope. amd, awsneuron, enflame, kunlun vdevice and metax sgpu also expose
quota-capable resources, but their Fit() is shaped differently — no shared
memreq resolution block to hang the check on. Wiring them up means reading
each allocation loop on its own terms, and I would rather not bury five
one-line insertions and five bespoke ones in the same diff. #2363 stays open
for them.

The bigger question from #2363 is still open and I have not tried to answer it
here: reserving quota at admission instead of at Filter would close the window
for every backend at once, rather than adding a gate per backend. That is a
larger change and wants your input before anyone writes it. This PR is the
incremental step, and the shared helper is needed either way.

One behaviour note: a pod that previously scheduled while its namespace was
over quota will now be refused on these five backends, with
ResourceQuotaNotFit in the filter reason. That is the fix working, but it is
a visible change for anyone who had been relying on the quota not being
enforced.

Not validated on real hardware. The change is in the scheduler extender's Fit
path and is covered by unit tests, which CONTRIBUTING allows for
scheduler-scoped changes.

Does this PR introduce a user-facing change?:

Namespace ResourceQuota is now re-checked during scheduling for Ascend,
Cambricon, Hygon, Iluvatar and Mthreads devices, closing a window where pods
created concurrently could all pass admission and collectively exceed the limit.

AI assistance disclosure: this change was developed with Claude Code — codebase
exploration, the shared helper, and the tests. Flagging the extent up front per
CONTRIBUTING.

Summary by CodeRabbit

  • Bug Fixes
    • Device scheduling now respects namespace-level memory and core quotas across supported device types.
    • Requests exceeding available quotas are rejected with clearer diagnostic information, while other eligible devices continue to be evaluated.
    • Quota calculations account for resources already selected or allocated across containers and finalized device combinations, including topology-aware selections.
    • Namespaces without configured quotas remain unrestricted.

@hami-robot hami-robot Bot added kind/bug Something isn't working dco-signoff: yes labels Aug 5, 2026
@hami-robot
hami-robot Bot requested review from DSFans2014 and archlitchi August 5, 2026 08:05
@hami-robot hami-robot Bot added the size/L label Aug 5, 2026
@coderabbitai

coderabbitai Bot commented Aug 5, 2026

Copy link
Copy Markdown

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Device fitting now validates pod-level memory and core quotas across accelerator backends. Shared quota accounting includes temporary and allocated device usage. Tests cover quota limits, scaling factors, unrestricted namespaces, nil allocations, and topology selection.

Changes

Resource quota enforcement

Layer / File(s) Summary
Shared quota accounting
pkg/device/quota.go, pkg/device/quota_test.go
FitQuotaWithPodDevices combines requested resources with temporary and allocated device usage. Tests cover quota limits, scaling factors, unrestricted namespaces, and nil allocations.
Backend Fit quota checks
pkg/device/ascend/device.go, pkg/device/cambricon/device.go, pkg/device/hygon/device.go, pkg/device/iluvatar/device.go, pkg/device/mthreads/device.go, pkg/device/nvidia/device.go
Backend Fit methods reject quota-exceeding candidates. Ascend validates quota after topology selection. NVIDIA uses the shared quota accounting function.
Backend quota validation tests
pkg/device/*/device_test.go
Tests cover successful requests, exhausted memory and core quotas, ResourceQuotaNotFit, scaled quotas, unrestricted namespaces, and topology-aware charging.

Estimated code review effort: 3 (Moderate) | ~20 minutes

Sequence Diagram(s)

sequenceDiagram
  participant DeviceFit
  participant FitQuotaWithPodDevices
  participant LocalQuotaManager
  DeviceFit->>FitQuotaWithPodDevices: submit requested resources and selected devices
  FitQuotaWithPodDevices->>LocalQuotaManager: evaluate combined quota usage
  LocalQuotaManager-->>FitQuotaWithPodDevices: return quota fit result
  FitQuotaWithPodDevices-->>DeviceFit: accept or reject candidate
Loading

Possibly related issues

  • Project-HAMi issue 2363: Adds quota re-checks to non-NVIDIA Fit methods and centralizes accumulated quota validation.
  • Project-HAMi issue 2157: Enforces resource quotas in non-NVIDIA device fitting paths with shared validation and tests.

Possibly related PRs

Suggested reviewers: dsfans2014

Poem

A rabbit checks each device in line,
Memory and cores must fit just right.
Quotas guard the pod’s small store,
Failed candidates leave the queue.
Shared checks keep results precise.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 50.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely identifies the main change: re-checking ResourceQuota in Fit() for non-NVIDIA device backends.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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.

@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

🤖 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 `@pkg/device/ascend/device.go`:
- Around line 515-520: Move the FitQuotaWithPodDevices call out of the
provisional topology-selection loop, so candidate cards are not quota-checked or
charged before computeBestCombination selects originReq. After the best topology
combination is chosen, validate that selected combination with
FitQuotaWithPodDevices and apply ResourceQuotaNotFit handling there, preserving
the existing quota accounting for the final devices only.

In `@pkg/device/quota.go`:
- Around line 101-117: Make the quota check-and-use operation atomic across the
scheduling flow rooted at FitQuotaWithPodDevices: serialize concurrent checks
and subsequent usage updates for the same namespace and device, or reserve quota
atomically once device allocation is finalized. Ensure the lock or reservation
spans FitQuota through the later AddUsage call so concurrent Fit operations
cannot overlap quota usage.
🪄 Autofix

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: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 0a4cc570-2f4b-4977-b26a-f1c8dc6d2411

📥 Commits

Reviewing files that changed from the base of the PR and between e6d0902 and 557f315.

📒 Files selected for processing (9)
  • pkg/device/ascend/device.go
  • pkg/device/cambricon/device.go
  • pkg/device/cambricon/device_test.go
  • pkg/device/hygon/device.go
  • pkg/device/iluvatar/device.go
  • pkg/device/mthreads/device.go
  • pkg/device/nvidia/device.go
  • pkg/device/quota.go
  • pkg/device/quota_test.go

Comment thread pkg/device/ascend/device.go Outdated
Comment thread pkg/device/quota.go
@codecov

codecov Bot commented Aug 5, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 95.91837% with 2 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
pkg/device/ascend/device.go 86.66% 2 Missing ⚠️
Flag Coverage Δ
unittests 64.51% <95.91%> (+0.30%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

Files with missing lines Coverage Δ
pkg/device/cambricon/device.go 85.87% <100.00%> (+2.60%) ⬆️
pkg/device/hygon/device.go 92.82% <100.00%> (+0.17%) ⬆️
pkg/device/iluvatar/device.go 61.33% <100.00%> (+3.60%) ⬆️
pkg/device/mthreads/device.go 88.97% <100.00%> (+2.72%) ⬆️
pkg/device/nvidia/device.go 97.66% <100.00%> (-0.05%) ⬇️
pkg/device/quota.go 78.26% <100.00%> (+2.82%) ⬆️
pkg/device/ascend/device.go 84.97% <86.66%> (+0.29%) ⬆️

... and 1 file with indirect coverage changes

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@coderabbitai

coderabbitai Bot commented Aug 5, 2026

Copy link
Copy Markdown

Note

GitHub couldn't provide a complete incremental comparison for this pull request, so CodeRabbit is performing a full review instead. This review may take a little longer.

@coderabbitai

coderabbitai Bot commented Aug 5, 2026

Copy link
Copy Markdown

Note

GitHub couldn't provide a complete incremental comparison for this pull request, so CodeRabbit is performing a full review instead. This review may take a little longer.

@archlitchi

Copy link
Copy Markdown
Member

please resolve these conflicts

@Lakshya77089
Lakshya77089 force-pushed the fix/fit-quota-all-backends branch from 2e13cb2 to c0ea46b Compare August 6, 2026 05:52
@coderabbitai

coderabbitai Bot commented Aug 6, 2026

Copy link
Copy Markdown

Note

GitHub couldn't provide a complete incremental comparison for this pull request, so CodeRabbit is performing a full review instead. This review may take a little longer.

Comment thread pkg/device/ascend/device_test.go Outdated
qm := device.NewQuotaManager()
t.Cleanup(func() { delete(qm.Quotas, "ascend-topo") })

// Room for the two cards actually requested (32768) but not for all four

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Please remove these comments

Comment thread pkg/device/ascend/device.go Outdated
@archlitchi

Copy link
Copy Markdown
Member

/assign

@Lakshya77089
Lakshya77089 force-pushed the fix/fit-quota-all-backends branch from c0ea46b to d36803a Compare August 6, 2026 13:10
@hami-robot

hami-robot Bot commented Aug 6, 2026

Copy link
Copy Markdown
Contributor

[APPROVALNOTIFIER] This PR is NOT APPROVED

This pull-request has been approved by: Lakshya77089
Once this PR has been reviewed and has the lgtm label, please ask for approval from archlitchi. For more information see the Kubernetes Code Review Process.

The full list of commands accepted by this bot can be found here.

Details Needs approval from an approver in each of these files:

Approvers can indicate their approval by writing /approve in a comment
Approvers can cancel approval by writing /approve cancel in a comment

@coderabbitai

coderabbitai Bot commented Aug 6, 2026

Copy link
Copy Markdown

Note

GitHub couldn't provide a complete incremental comparison for this pull request, so CodeRabbit is performing a full review instead. This review may take a little longer.

@archlitchi

Copy link
Copy Markdown
Member

please sync with master to pass the UT

@Lakshya77089

Copy link
Copy Markdown
Contributor Author

Thanks for the review — both done.

Removed the comments inside method bodies, 25 lines across ascend/device.go and the ascend, cambricon, hygon, iluvatar and mthreads test files. I kept the doc comments directly above declarations, since those are the usual Go convention; say the word if you would rather those went too and I will strip them as well.

While rebasing I also had to update the quota fixtures in my tests. #2313 changed FitQuota to gate on Quota.LimitSet rather than Limit != 0, so a Quota built as a struct literal instead of through AddQuota now reads as "no limit configured" and every deny case silently passed. Same thing that had TestFitResourceQuotaNonNvidia and two others failing on master until you fixed those fixtures this morning.

Rebased onto c5e99b6 and CI is green.

@Lakshya77089

Copy link
Copy Markdown
Contributor Author

@archlitchi gentle ping on this one when you have a moment. The in-body comments you asked about are removed, and I've rebased onto current master — CI is green.

Comment thread pkg/device/quota.go
}
}
}
return GetLocalCache().FitQuota(ns, mem, memoryFactor, core, deviceName)

@Shouren Shouren Aug 9, 2026

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

@Lakshya77089 I think we shouldn't remove the log message in the previouse code.

Usage is only recorded at Filter time, so pods created together all pass
admission against the same figure. nvidia re-checked in Fit(); no other
backend did, so a burst could schedule past a namespace limit.

Move nvidia's fitQuota helper into pkg/device as FitQuotaWithPodDevices
and call it from ascend, cambricon, hygon, iluvatar and mthreads, which
share nvidia's Fit() shape. The other backends have a different shape and
are left for a follow-up.

Refs Project-HAMi#2363

Signed-off-by: Lakshya77089 <lakshyasharma7708@gmail.com>
@Lakshya77089

Copy link
Copy Markdown
Contributor Author

@Shouren good catch — that was an accidental drop when the helper moved out of the nvidia package, not intentional. Restored it in FitQuotaWithPodDevices so every backend now logs the accumulated mem/cores the same way nvidia did. Pushed.

@mesutoezdil

Copy link
Copy Markdown
Contributor

You can view the relevant rule here.
https://github.com/Project-HAMi/HAMi/blob/master/CONTRIBUTING.md#contribution-gates
"4. Review replies. The reply you post must be written by you and must address the specific point raised. Verbatim or canned AI replies, or replies that do not engage the comment, lead to the PR being closed."

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants