Skip to content

fix(device): enforce ResourceQuota in Fit() for all backends - #2397

Closed
Nitish08-08 wants to merge 1 commit into
Project-HAMi:masterfrom
Nitish08-08:fix/new-issue
Closed

fix(device): enforce ResourceQuota in Fit() for all backends#2397
Nitish08-08 wants to merge 1 commit into
Project-HAMi:masterfrom
Nitish08-08:fix/new-issue

Conversation

@Nitish08-08

@Nitish08-08 Nitish08-08 commented Aug 6, 2026

Copy link
Copy Markdown

Problem

Namespace ResourceQuota enforcement has a race window for every backend
except NVIDIA.

The admission webhook records no device usage — that happens at Filter
time via QuotaManager.AddUsage. So when a burst of pods is created
back-to-back, they all reach the webhook before any has been scheduled,
all read the same Used value, and all pass.

NVIDIA catches this because its Fit() method calls a private fitQuota
helper that re-checks quota after each device assignment. The second
pod in the burst sees the first pod's recorded usage and is correctly
denied when over limit.

Every other backend — cambricon, ascend, hygon, iluvatar, mthreads,
metax, amd, enflame, kunlun, awsneuron, vastai, biren — has no such
check. The whole burst schedules and the namespace ends up over its
limit.

This was identified during review of #2347, which fixed the case where
non-NVIDIA quotas were ignored outright. This is the remaining half.

Reproducer

  1. Register a non-NVIDIA backend (e.g. cambricon).
  2. Set a namespace ResourceQuota on
    limits.cambricon.com/mlu.smlu.vmemory sized for ~2 pods.
  3. Create a Deployment with 5 replicas requesting that resource.
  4. All 5 pods pass admission and schedule. Usage exceeds the limit.
  5. Repeat with nvidia.com/gpumem — excess replicas are correctly denied.

Solution

Extract the accumulate-then-check logic from nvidia's private fitQuota
helper into a shared FitQuotaForDevice function that any backend can
call. Every backend's Fit() method now invokes this shared function
before assigning a device, closing the race.

The shared function:

  • Sums memory and core usage from the current allocation round (tmpDevs)
    and from previously allocated containers in the pod (allocated)
  • Delegates to QuotaManager.FitQuota with the backend's MemoryFactor

Files changed

File Change
pkg/device/quota.go New FitQuotaForDevice function
pkg/device/quota_test.go TestFitQuotaForDevice (8 test cases)
pkg/device/nvidia/device.go Removed private fitQuota, use shared function
pkg/device/cambricon/device.go Add FitQuotaForDevice call in Fit()
pkg/device/ascend/device.go Add FitQuotaForDevice call in Fit()
pkg/device/hygon/device.go Add FitQuotaForDevice call in Fit()
pkg/device/iluvatar/device.go Add FitQuotaForDevice call in Fit()
pkg/device/mthreads/device.go Add FitQuotaForDevice call in Fit()
pkg/device/metax/device.go Add FitQuotaForDevice call in Fit()
pkg/device/metax/sdevice.go Add FitQuotaForDevice call in Fit()
pkg/device/amd/device.go Add FitQuotaForDevice call in Fit()
pkg/device/enflame/device.go Add FitQuotaForDevice call in Fit()
pkg/device/enflame/gcu.go Add FitQuotaForDevice call in Fit()
pkg/device/kunlun/device.go Add FitQuotaForDevice call in Fit()
pkg/device/kunlun/vdevice.go Add FitQuotaForDevice call in Fit()
pkg/device/awsneuron/device.go Add FitQuotaForDevice call in Fit()
pkg/device/vastai/device.go Add FitQuotaForDevice call in Fit()
pkg/device/biren/device.go Add FitQuotaForDevice call in Fit()

How to verify

go test ./pkg/device/... -run TestFitQuotaForDevice -short --race -count=1 -v

Closes #2363

Summary by CodeRabbit

  • New Features
    • Added resource-quota validation for device allocation across supported accelerator types.
    • Allocation now accounts for requested memory and cores alongside existing device capacity checks.
    • When a candidate exceeds quota, it is skipped so other eligible devices can still be considered.
  • Bug Fixes
    • Improved allocation failure reporting with quota-specific reasons and diagnostic logging.
  • Tests
    • Added coverage for quota checks involving temporary allocations, existing allocations, missing quotas, and namespaces.

FitQuota was only called from nvidia's Fit(), leaving a race window
for every other backend. A burst of pods created back-to-back all
pass admission with the same Used value and all get scheduled,
exceeding the namespace quota.

Move the accumulate-then-check logic from nvidia's private fitQuota
helper to a shared FitQuotaForDevice function in pkg/device/quota.go.
Every backend now calls this from its Fit() method, closing the race.

Closes Project-HAMi#2363
@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: Nitish08-08
Once this PR has been reviewed and has the lgtm label, please assign shouren for approval. 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

@hami-robot

hami-robot Bot commented Aug 6, 2026

Copy link
Copy Markdown
Contributor

Thanks for your pull request. Before we can look at it, you'll need to add a 'DCO signoff' to your commits.

📝 Please follow instructions in the contributing guide to update your commits with the DCO

Full details of the Developer Certificate of Origin can be found at developercertificate.org.

The list of commits missing DCO signoff:

  • 321d38d fix(device): enforce ResourceQuota in Fit() for all backends
Details

Instructions for interacting with me using PR comments are available here. If you have questions or suggestions related to my behavior, please file an issue against the kubernetes-sigs/prow repository. I understand the commands that are listed here.

@github-actions github-actions Bot added the kind/bug Something isn't working label Aug 6, 2026
@hami-robot hami-robot Bot added the size/L label Aug 6, 2026
@coderabbitai

coderabbitai Bot commented Aug 6, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

Changes

Device quota enforcement

Layer / File(s) Summary
Shared quota validation
pkg/device/quota.go, pkg/device/quota_test.go
Adds FitQuotaForDevice to aggregate requested and allocated usage, then validates memory and core quotas. Tests cover quota limits, temporary allocations, existing allocations, and missing quotas.
Backend Fit integration
pkg/device/{amd,ascend,awsneuron,biren,cambricon,enflame,hygon,iluvatar,kunlun,metax,mthreads,vastai}/...
Adds quota checks to non-NVIDIA device fitting paths. Failed candidates record ResourceQuotaNotFit, log request details, and are skipped or rejected.
NVIDIA quota migration
pkg/device/nvidia/device.go
Removes the local fitQuota helper and delegates validation to FitQuotaForDevice.

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

Possibly related issues

Possibly related PRs

Suggested reviewers: fouof, dsfans2014

Poem

A rabbit checks each GPU’s share,
Counts memory and cores with care.
Quotas guide the fitting queue,
Bad candidates are skipped from view.
One shared check now keeps things right—
Hop, hop, allocations take flight!

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 25.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 summarizes the main change: enforcing ResourceQuota in Fit() across all device backends.
Linked Issues check ✅ Passed The changes implement quota re-checks for all listed backends, add shared accumulation logic, update NVIDIA, and add focused tests for issue #2363.
Out of Scope Changes check ✅ Passed All changes directly support ResourceQuota enforcement in Fit() and the shared quota-checking implementation described in issue #2363.
✨ Finishing Touches 💡 1
🛠️ Fix failing CI checks 💡
  • Create stacked PR
  • Commit on current branch
🧪 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 requested review from DSFans2014 and FouoF August 6, 2026 04:44

@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: 5

🧹 Nitpick comments (1)
pkg/device/quota_test.go (1)

293-297: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Add a MemoryFactor test case.

All new cases use MemoryFactor: 1. Add a case with a factor greater than one. This verifies that FitQuotaForDevice forwards scaled memory usage correctly.

Based on PR context, MemoryFactor defines the conversion between recorded memory usage and the ResourceQuota limit.

🤖 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 `@pkg/device/quota_test.go` around lines 293 - 297, Add a test case in the
quota test cases around FitQuotaForDevice using a MemoryFactor greater than one,
with expected quota values reflecting the converted memory usage. Keep the
existing MemoryFactor: 1 cases unchanged and verify that FitQuotaForDevice
forwards scaled memory usage correctly.
🤖 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/amd/device.go`:
- Around line 315-319: Calculate the converted core usage as coreReq before the
FitQuotaForDevice call in the AMD device allocation flow, using the same
conversion later used for ContainerDevice.Usedcores. Pass coreReq instead of
k.Coresreq to FitQuotaForDevice, and reuse that value when persisting Usedcores
to keep quota accounting consistent.

In `@pkg/device/awsneuron/device.go`:
- Around line 431-436: Update the k.Nums > 1 branch to validate quota before
returning, using the complete selected allocation that includes both temporary
and previously allocated devices. Reuse device.FitQuotaForDevice with the same
resource requirements and AWSNeuronDevice parameters as the single-device path,
and preserve the existing quota failure handling.

In `@pkg/device/enflame/device.go`:
- Around line 434-438: Update the Enflame resource-name construction used by
FitQuotaForDevice so GetResourceNames() includes the MemoryFactor from the
Enflame configuration before the quota check. Preserve the existing quota
arguments and add a regression test verifying GB-configured memory quotas
correctly accept a matching 4-GB profile after conversion.

In `@pkg/device/metax/sdevice.go`:
- Around line 374-379: Update MetaxSDevices.Fit around FitQuotaForDevice to
construct the selected ContainerDevices allocation before quota validation
instead of referencing undeclared tmpDevs. Aggregate the exact selected devices
and validate the memory and core values that will be persisted, using zero core
usage for Online allocations; pass this completed allocation to
FitQuotaForDevice before finalizing the device list.

In `@pkg/device/quota.go`:
- Around line 95-113: Make FitQuotaForDevice perform the quota fit check and
usage reservation atomically instead of only calling GetLocalCache().FitQuota.
Reuse the QuotaManager synchronization and reservation path so concurrent
requests cannot all pass against unchanged usage; the successful check must
record mem and core usage before returning, while rejected requests leave usage
unchanged.

---

Nitpick comments:
In `@pkg/device/quota_test.go`:
- Around line 293-297: Add a test case in the quota test cases around
FitQuotaForDevice using a MemoryFactor greater than one, with expected quota
values reflecting the converted memory usage. Keep the existing MemoryFactor: 1
cases unchanged and verify that FitQuotaForDevice forwards scaled memory usage
correctly.
🪄 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: 3fab7099-b7b2-4026-b2b7-da794b249f5a

📥 Commits

Reviewing files that changed from the base of the PR and between e6d0902 and 321d38d.

📒 Files selected for processing (18)
  • pkg/device/amd/device.go
  • pkg/device/ascend/device.go
  • pkg/device/awsneuron/device.go
  • pkg/device/biren/device.go
  • pkg/device/cambricon/device.go
  • pkg/device/enflame/device.go
  • pkg/device/enflame/gcu.go
  • pkg/device/hygon/device.go
  • pkg/device/iluvatar/device.go
  • pkg/device/kunlun/device.go
  • pkg/device/kunlun/vdevice.go
  • pkg/device/metax/device.go
  • pkg/device/metax/sdevice.go
  • pkg/device/mthreads/device.go
  • pkg/device/nvidia/device.go
  • pkg/device/quota.go
  • pkg/device/quota_test.go
  • pkg/device/vastai/device.go

Comment thread pkg/device/amd/device.go
Comment on lines +315 to +319
if !device.FitQuotaForDevice(tmpDevs, allocated, pod.Namespace, int64(memReq), int64(k.Coresreq), AMDDevice, amddevice.GetResourceNames()) {
reason[common.ResourceQuotaNotFit]++
klog.V(3).InfoS(common.ResourceQuotaNotFit, "pod", pod.Name, "memreq", memReq, "coresreq", k.Coresreq)
continue
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Pass the converted core usage to the quota check.

Line 315 passes k.Coresreq, but Lines 326-358 convert that percentage to coreReq and persist coreReq in ContainerDevice.Usedcores. The quota check can undercount the current allocation when dev.Totalcore is not 100.

Calculate coreReq before this check. Pass coreReq to FitQuotaForDevice.

Based on PR context, quota usage is accumulated from ContainerDevice.Usedcores.

🤖 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 `@pkg/device/amd/device.go` around lines 315 - 319, Calculate the converted
core usage as coreReq before the FitQuotaForDevice call in the AMD device
allocation flow, using the same conversion later used for
ContainerDevice.Usedcores. Pass coreReq instead of k.Coresreq to
FitQuotaForDevice, and reuse that value when persisting Usedcores to keep quota
accounting consistent.

Comment on lines +431 to +436
if !device.FitQuotaForDevice(tmpDevs, allocated, pod.Namespace, int64(k.Memreq), int64(k.Coresreq), AWSNeuronDevice, neuron.GetResourceNames()) {
reason[common.ResourceQuotaNotFit]++
klog.V(3).InfoS(common.ResourceQuotaNotFit, "pod", pod.Name, "memreq", k.Memreq, "coresreq", k.Coresreq)
continue
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Check quota before the multi-device return.

The k.Nums > 1 branch at Lines 369-392 returns before Line 431. Multi-device AWS Neuron requests therefore bypass the new quota validation.

Validate the complete selected allocation before the return. Include temporary and previously allocated devices in that validation.

🤖 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 `@pkg/device/awsneuron/device.go` around lines 431 - 436, Update the k.Nums > 1
branch to validate quota before returning, using the complete selected
allocation that includes both temporary and previously allocated devices. Reuse
device.FitQuotaForDevice with the same resource requirements and AWSNeuronDevice
parameters as the single-device path, and preserve the existing quota failure
handling.

Comment on lines +434 to +438
if !device.FitQuotaForDevice(tmpDevs, allocated, pod.Namespace, int64(profileMemoryMiB), int64(profileCorePercent), EnflameVGCUDevice, enf.GetResourceNames()) {
reason[common.ResourceQuotaNotFit]++
klog.V(3).InfoS(common.ResourceQuotaNotFit, "pod", pod.Name, "memreq", profileMemoryMiB, "coresreq", profileCorePercent)
continue
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

rg -n -C 5 \
  'MemoryFactor|profileMemoryMiB|EnflameResourceNameGCUMemory|FitQuotaForDevice' \
  pkg/device/enflame/device.go pkg/device/quota.go pkg/device/quota_test.go

Repository: Project-HAMi/HAMi

Length of output: 15058


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '--- quota implementation ---\n'
sed -n '1,150p' pkg/device/quota.go

printf '\n--- Enflame device structs and resource names ---\n'
sed -n '70,110p' pkg/device/enflame/device.go
sed -n '478,488p' pkg/device/enflame/device.go

printf '\n--- FitQuota signatures/usages ---\n'
rg -n 'func \([^)]*\) FitQuota|func FitQuota|MemoryFactor|ResourceNames' pkg/device -S

Repository: Project-HAMi/HAMi

Length of output: 246


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo '--- quota implementation ---'
sed -n '1,150p' pkg/device/quota.go

echo
echo '--- Enflame device structs and resource names ---'
sed -n '70,110p' pkg/device/enflame/device.go
sed -n '478,488p' pkg/device/enflame/device.go

echo
echo '--- FitQuota signatures/usages ---'
rg -n 'func \([^)]*\) FitQuota|func FitQuota|MemoryFactor|ResourceNames' pkg/device -S

Repository: Project-HAMi/HAMi

Length of output: 17587


Set the Enflame memory conversion factor before enforcing this quota check.

GetResourceNames() returns a zero MemoryFactor, so FitQuotaForDevice() passes usage in MiB into FitQuota() without scaling the configured quota limit. When users configure the Enflame memory quota in GB, a 4-GB profile is checked as 4096 MiB and can exceed a limit like 4. Add MemoryFactor from the Enflame config to GetResourceNames() and add a regression test.

🤖 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 `@pkg/device/enflame/device.go` around lines 434 - 438, Update the Enflame
resource-name construction used by FitQuotaForDevice so GetResourceNames()
includes the MemoryFactor from the Enflame configuration before the quota check.
Preserve the existing quota arguments and add a regression test verifying
GB-configured memory quotas correctly accept a matching 4-GB profile after
conversion.

Source: MCP tools

Comment on lines +374 to +379
if !device.FitQuotaForDevice(tmpDevs, allocated, pod.Namespace, int64(memreq), int64(request.Coresreq), MetaxSGPUDevice, mats.GetResourceNames()) {
reason[common.ResourceQuotaNotFit]++
klog.V(3).InfoS(common.ResourceQuotaNotFit, "pod", pod.Name, "memreq", memreq, "coresreq", request.Coresreq)
continue
}

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 | 🔴 Critical | 🏗️ Heavy lift

Build the selected allocation before quota validation.

tmpDevs is not declared in MetaxSDevices.Fit, so this package does not compile.

Do not fix this by adding an empty map. This check runs before the final device list is selected, so an empty map cannot aggregate a multi-device allocation. Build the selected ContainerDevices first, then validate the exact memory and core values that will be persisted. Use zero core usage for Online allocations, which later store coreReq = 0.

🤖 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 `@pkg/device/metax/sdevice.go` around lines 374 - 379, Update MetaxSDevices.Fit
around FitQuotaForDevice to construct the selected ContainerDevices allocation
before quota validation instead of referencing undeclared tmpDevs. Aggregate the
exact selected devices and validate the memory and core values that will be
persisted, using zero core usage for Online allocations; pass this completed
allocation to FitQuotaForDevice before finalizing the device list.

Comment thread pkg/device/quota.go
Comment on lines +95 to +113
func FitQuotaForDevice(tmpDevs map[string]ContainerDevices, allocated *PodDevices, ns string, memreq int64, coresreq int64, deviceName string, resourceNames ResourceNames) bool {
mem := memreq
core := coresreq
for _, val := range tmpDevs[deviceName] {
mem += int64(val.Usedmem)
core += int64(val.Usedcores)
}
if allocated != nil {
if podSingleDevice, exists := (*allocated)[deviceName]; exists {
for _, containerDevices := range podSingleDevice {
for _, val := range containerDevices {
mem += int64(val.Usedmem)
core += int64(val.Usedcores)
}
}
}
}
klog.V(4).Infoln("FitQuotaForDevice: device", deviceName, "mem", mem, "cores", core)
return GetLocalCache().FitQuota(ns, mem, resourceNames.MemoryFactor, core, deviceName)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

ast-grep outline pkg/device/quota.go --items all
rg -n -C 5 '\b(FitQuotaForDevice|FitQuota|AddUsage|RmUsage)\b' pkg
rg -n -C 5 '\.Fit\(|FitQuotaForDevice\(' pkg

Repository: Project-HAMi/HAMi

Length of output: 50373


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Inspect quota implementation and relevant scheduler paths.
sed -n '50,190p' pkg/device/quota.go
printf '\n--- scheduler relevant sections ---\n'
sed -n '130,175p' pkg/scheduler/scheduler.go
sed -n '910,1005p' pkg/scheduler/scheduler.go

printf '\n--- webhook quota path ---\n'
sed -n '110,170p' pkg/scheduler/webhook.go

printf '\n--- all direct calls to device quota functions outside tests ---\n'
rg -n -C 3 '\.FitQuotaForDevice\(|FitQuotaForDevice\(|GetLocalCache\(\)\.FitQuota\(' `git ls-files '*_test.go' | sed 's/.*//;q'` 2>/dev/null >/tmp/except_tests.txt || true
rg -n -C 3 '\.FitQuotaForDevice\(|FitQuotaForDevice\(|device\.GetLocalCache\(\)\.FitQuota\(' --glob '!**/*_test.go' pkg

printf '\n--- search for quota related methods and lock patterns ---\n'
rg -n -C 3 'FitQuota|QuotaManager|mutex\.RLock|mutex\.Lock|AddUsage|TakeAndDelete' pkg --glob '!**/*_test.go'

Repository: Project-HAMi/HAMi

Length of output: 10814


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Behavioral probe of the QuotaManager state update semantics with an RLock-only check.
# This model matches: FitQuota reads Quota.Used under RLock; AddUsage records usage under Lock;
# multiple Fits can complete before the winning reservation is written.
cat > /tmp/quota_concurrency_model.go <<'EOF'
package main

import (
	"context"
	"fmt"
	"sync"
	"time"
)

type Quota struct {
	Limit int64
	Used  int64
}

type QuotaManager struct {
	Quotas map[string]Quota
	mu     sync.RWMutex
}

func fit(q *QuotaManager, ns string, request int64) bool {
	q.mu.RLock()
	v := q.Quotas[ns]
	accept := v.Used+request <= v.Limit
	q.mu.RUnlock()
	if !accept {
		return false
	}
	time.Sleep(time.Microsecond)
	return true
}

func addUsage(q *QuotaManager, ns string, usage int64) {
	q.mu.Lock()
	q.Quotas[ns].Used += usage
	q.mu.Unlock()
}

func main() {
	m := &QuotaManager{Quotas: map[string]Quota{"ns": {Limit: 1000}}}
	var done sync.WaitGroup
	ctx := context.Background()
	for i := 0; i < 5; i++ {
		done.Add(1)
		go func(id int) {
			defer done.Done()
			select {
			case <-time.After(time.Microsecond):
			case <-ctx.Done():
				return
			}
			if fit(m, "ns", 300) {
				println("fit", id)
				addUsage(m, "ns", 300)
			}
		}(i)
	}
	done.Wait()
	fmt.Printf("final_used=%d\n", m.Quotas["ns"].Used)
}
EOF
go run /tmp/quota_concurrency_model.go

Repository: Project-HAMi/HAMi

Length of output: 273


🏁 Script executed:

#!/bin/bash
set -euo pipefail

cat > /tmp/quota_concurrency_model.go <<'EOF'
package main

import (
	"fmt"
	"sync"
	"sync/atomic"
	"time"
)

type Quota struct {
	Limit int64
	Used  int64
}

type QuotaManager struct {
	Quotas map[string]*Quota
	mu     sync.RWMutex
}

func fit(q *QuotaManager, ns string, request int64) bool {
	q.mu.RLock()
	v := q.Quotas[ns]
	accept := v.Used+request <= v.Limit
	q.mu.RUnlock()
	if !accept {
		return false
	}
	time.Sleep(time.Microsecond)
	return true
}

func addUsage(q *QuotaManager, ns string, usage int64) {
	q.mu.Lock()
	defer q.mu.Unlock()
	if q.Quotas[ns] == nil {
		q.Quotas[ns] = &Quota{Limit: 1000}
	}
	q.Quotas[ns].Used += usage
}

func main() {
	m := &QuotaManager{Quotas: map[string]*Quota{"ns": {Limit: 1000}}}
	var attempts, successes atomic.Int64
	var wg sync.WaitGroup
	for i := 0; i < 50; i++ {
		wg.Add(1)
		go func() {
			defer wg.Done()
			if fit(m, "ns", 300) {
				values := make(chan bool, 1)
				go func() { values <- addUsage(m, "ns", 300) }()
				time.Sleep(time.Microsecond * 20)
				select {
				case <-values:
					successes.Add(1)
				default:
					m.mu.RLock()
					used := m.Quotas["ns"].Used
					m.mu.RUnlock()
					if used == 300 {
						successes.Add(1)
					}
				}
				attempts.Add(1)
			}
		}()
	}
	wg.Wait()
	fmt.Printf("attempts=%d successes=%d final_used=%d\n", attempts.Load(), successes.Load(), m.Quotas["ns"].Used)
}
EOF
go run /tmp/quota_concurrency_model.go

Repository: Project-HAMi/HAMi

Length of output: 267


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Remove the probe from output; it is not needed for a deterministic code-base result because
# the source shows FitQuota only reads while AddUsage later mutates separately.
true

Repository: Project-HAMi/HAMi

Length of output: 155


Make quota admission atomic.

FitQuotaForDevice only calls QuotaManager.FitQuota, which reads quota usage under RLock. The usage is added later by QuotaManager.AddUsage under Lock. Concurrent Fit checks can all pass while Used is unchanged, then each increments the same Used value. Merge the fit check with usage reservation, or serialize both steps so one winner records the new usage and later requests see the updated quota.

🤖 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 `@pkg/device/quota.go` around lines 95 - 113, Make FitQuotaForDevice perform
the quota fit check and usage reservation atomically instead of only calling
GetLocalCache().FitQuota. Reuse the QuotaManager synchronization and reservation
path so concurrent requests cannot all pass against unchanged usage; the
successful check must record mem and core usage before returning, while rejected
requests leave usage unchanged.

@Lakshya77089

Copy link
Copy Markdown
Contributor

There's overlap here with #2377, which is open against the same issue and covers ascend, cambricon, hygon, iluvatar and mthreads. Flagging that early so we don't both spend time on the same five.

One concrete thing worth checking in your ascend change. CodeRabbit raised this on #2377 and it applies to the same insertion point here: in topology mode k.Nums is not decremented, so that loop keeps collecting candidate cards and the originReq subset is only chosen afterwards by computeBestCombination. Charging quota per candidate therefore counts cards the final combination may never use, and once the running total passes the limit the remaining candidates are rejected, which shrinks the pool the combination is picked from. Your insert looks like it sits at that same spot without a needTopology guard.

#2377 handles it by skipping the per-card check when needTopology is set and validating the chosen selection once instead. Whichever PR ends up carrying this, it probably wants that guard.

Your coverage of amd, awsneuron, biren, enflame, kunlun, metax sgpu and vastai goes further than #2377 does — I left those out because their Fit() shapes differ. Happy to defer on the wider set if maintainers would rather take one PR; just wanted the topology point on the record either way.

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.

Quota is only re-checked in Fit() for NVIDIA, leaving a race window for every other backend

3 participants