Skip to content

refactor(nvidia): split Fit into composable check pipeline - #2090

Closed
yxxhero wants to merge 3 commits into
Project-HAMi:masterfrom
yxxhero:refactor/nvidia-fit-pipeline
Closed

refactor(nvidia): split Fit into composable check pipeline#2090
yxxhero wants to merge 3 commits into
Project-HAMi:masterfrom
yxxhero:refactor/nvidia-fit-pipeline

Conversation

@yxxhero

@yxxhero yxxhero commented Jul 18, 2026

Copy link
Copy Markdown
Contributor

Summary

The 137-line Fit function in pkg/device/nvidia/device.go interleaved 13 distinct per-card failure checks in a single for-loop, making the reasoning hard to follow and per-branch unit testing impractical.

This refactor extracts the per-card checks into a pipeline of small, independently testable functions in fit_checks.go. Each returns a common.ReasonXxx string on failure or empty on pass. runCardChecks walks the pipeline and short-circuits on the first failure, preserving the original check order.

Motivation

  • Readability: 13 if ... continue branches compressed into a declarative pipeline.
  • Testability: Each check is a pure function testable in isolation. Fit coverage went from ~70% to 100%; all 11 check functions are at 100%.
  • Future-proofing: Adding a new per-card rule becomes "append to pipeline" instead of "add another branch inside the 140-line loop".

Changes

pkg/device/nvidia/device.go

Fit body went from 137 lines to ~80. The main loop now:

  1. Runs checkCardHealth first (precedence preserved over checkType).
  2. Calls nv.checkType + NUMA reset (kept in loop because of cross-card state).
  3. Calls normalizeCoresreq + computeMemreq (kept in loop because they mutate k).
  4. Constructs cardCheckCtx and calls runCardChecks for the remaining 8 checks.

pkg/device/nvidia/fit_checks.go (new)

  • cardCheckCtx: per-iteration context (request, pod, allocated, tmpDevsMap, deviceIndex, memreq, ...).
  • cardCheck type + cardCheckPipeline slice.
  • runCardChecks orchestrator.
  • 8 checkCardXxx functions + checkCardHealth + computeMemreq + normalizeCoresreq.

Tests

  • fit_checks_test.go: unit tests for every check function with table-driven positive/negative cases.
  • fit_extra_test.go: integration tests for previously-uncovered Fit branches (NumaNotFit, ResourceQuotaNotFit, CardNotFoundCustomFilterRule, MIG multi-slot selection, Coresreq>100 clamping, health precedence over type mismatch, etc).
  • fit_bench_test.go: benchmarks for Fit (3 scenarios) and runCardChecks.

Zero breaking changes

Dimension Status
Fit method signature unchanged
device.Devices interface unchanged
Exported symbols none added, none removed
Pre-existing TestDevices_Fit (13 cases) all pass
Pre-existing NUMA/Topology/CustomFilterRule/Quota tests all pass
pkg/scheduler/score_test.go (indirect Fit caller) all pass
Return values (fit, tmpDevs, reason string) bit-for-bit equivalent

The check order is also preserved: health → checkType → NUMA → UUID → TimeSlicing → normalize → memreq → quota → memory → core → exclusive → computeExhausted → customRule.

Known trade-off (disclosed)

normalizeCoresreq now runs before UUID/TimeSlicing instead of after them. This is functionally safe because:

  1. normalizeCoresreq only reads req.Coresreq and is idempotent (clamp to 100).
  2. computeMemreq only reads (req.Memreq, req.MemPercentagereq, dev.Totalmem) — it does not depend on Coresreq.
  3. The downstream 8 checks consume k.Coresreq / ctx.memreq; their final values are identical in both orderings.

Only observable difference: when every card fails UUID/TimeSlicing and Coresreq > 100, the original code would never log core limit can't exceed 100, while the refactored code logs it once. Log-only, no functional change.

Coverage report

Fit:                                70% -> 100%
runCardChecks:                              100%
computeMemreq / normalizeCoresreq:          100%
checkCardHealth/UUID/TimeSlicing/Quota:     100%
checkCardMemory/Core/Exclusive/             100%
checkCardComputeExhausted/CustomRule:       100%
pkg/device/nvidia total:                    72.1%

Benchmarks (8-card node, production-path DevicesMap)

BenchmarkFit_SingleCardFromEight-8     ~2000 ns/op    25 allocs/op
BenchmarkFit_FourCardsFromEight-8      ~3500 ns/op    57 allocs/op
BenchmarkFit_AllCardsFail-8            ~5100 ns/op   101 allocs/op
BenchmarkRunCardChecks_AllPass-8         ~70 ns/op     2 allocs/op

Review process

This PR went through six rounds of self-review before submission. Twenty real issues were caught and fixed, including two genuine bugs (health-check double-invocation, checkType precedence), four fake-green tests (regression guards that did not actually exercise the failure mode their names claimed), and several test-isolation / benchmark-fidelity improvements. Details are documented in the commit message.

AI Assistance Disclosure

This PR was authored with AI assistance (Claude Code) and is disclosed per CONTRIBUTING.md.

Test Plan

  • go build ./pkg/device/nvidia/...
  • go vet ./pkg/device/nvidia/...
  • go test -short --race ./pkg/device/... ./pkg/scheduler/... — all 19 packages pass
  • hack/verify-license.sh
  • hack/verify-import-aliases.sh
  • hack/verify-staticcheck.sh (golangci-lint v2.8.0)
  • go test -bench runs without regression

Summary by CodeRabbit

  • Bug Fixes

    • Improved NVIDIA GPU device selection and fit validation, including card health gating, UUID constraints, time-slicing limits, quota/memory/core checks, exclusivity conflicts, compute exhaustion, and custom rule filtering.
    • More accurate and aggregated failure reason reporting on overall allocation failures.
    • Correct handling of edge cases such as empty device lists, zero-resource requests, topology fallback with partial selections, MIG multi-slice allocation, and core/memory clamping.
  • Tests

    • Added comprehensive unit tests for NVIDIA fit checks and allocation scenarios.
    • Added benchmarks for allocation fitting and per-card validation performance.

The 137-line Fit function in pkg/device/nvidia/device.go interleaved 13
distinct per-card failure checks in a single for-loop, making the
reasoning hard to follow and per-branch testing impractical.

This refactor extracts the per-card checks into a pipeline of small,
independently testable functions in fit_checks.go:

  checkCardHealth (runs in the Fit loop, before checkType)
  checkCardUUID
  checkCardTimeSlicing
  checkCardQuota
  checkCardMemory
  checkCardCore
  checkCardExclusive
  checkCardComputeExhausted
  checkCardCustomRule

Each returns the common.ReasonXxx string on failure or empty on pass.
runCardChecks walks the pipeline and short-circuits on the first
failure, preserving the original check order.

NUMA reset, checkType, Coresreq normalization and memreq computation
remain in the Fit loop because they carry cross-card or mutation
side-effects.

Zero breaking changes
---------------------
* Fit method signature is unchanged.
* device.Devices interface is unchanged.
* No exported symbols added or removed.
* All 13 pre-existing TestDevices_Fit cases and the four topology/
  NUMA/CustomFilterRule tests in device_test.go still pass unchanged.
* pkg/scheduler/score_test.go's indirect Fit invocations still pass.

Coverage
--------
* Fit: 70% -> 100%.
* All 11 check functions: 100%.
* New tests include regression guards for ordering pitfalls discovered
  during self-review (health precedence over type mismatch, MIG
  multi-slot selection via the i++ stay-loop, Coresreq>100 clamping).
* BenchmarkFit on 8 cards: ~2000 ns/op, 25 allocs/op.

Known trade-off (disclosed)
---------------------------
normalizeCoresreq now runs before UUID/TimeSlicing instead of after.
Because it is idempotent and reads no fields consumed by the UUID or
TimeSlicing checks, all 13 reason counts, the returned tmpDevs and the
reason string remain identical. The only observable difference is that
klog.ErrorS("core limit can't exceed 100") may fire once even when
every card fails UUID/TimeSlicing (original code would not fire at all
in that edge case). This is log-only; functional behavior is identical.

Signed-off-by: yxxhero <aiopsclub@163.com>
@hami-robot
hami-robot Bot requested a review from archlitchi July 18, 2026 10:28
@hami-robot

hami-robot Bot commented Jul 18, 2026

Copy link
Copy Markdown
Contributor

[APPROVALNOTIFIER] This PR is NOT APPROVED

This pull-request has been approved by: yxxhero
Once this PR has been reviewed and has the lgtm label, please assign dsfans2014 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 requested a review from ouyangluwei163 July 18, 2026 10:28
@github-actions github-actions Bot added the kind/enhancement New feature or request label Jul 18, 2026
@hami-robot hami-robot Bot added the size/XXL label Jul 18, 2026
@coderabbitai

coderabbitai Bot commented Jul 18, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

Fit now delegates NVIDIA per-card eligibility and resource calculations to an ordered check pipeline. New unit tests, integration tests, and benchmarks cover validation ordering, allocation behavior, failure reasons, edge cases, and performance scenarios.

Changes

NVIDIA fit pipeline

Layer / File(s) Summary
Card-check pipeline
pkg/device/nvidia/fit_checks.go
Adds ordered checks for health, UUIDs, time slicing, quotas, memory, cores, exclusivity, compute exhaustion, and custom rules, with memory and core request normalization.
Fit integration and allocation tracking
pkg/device/nvidia/device.go
Fit builds check context, delegates validation, records failure reasons, and uses computed memory in selected device state.
Pipeline and Fit behavior validation
pkg/device/nvidia/fit_checks_test.go, pkg/device/nvidia/fit_extra_test.go
Adds coverage for checks, short-circuit ordering, selection, reason aggregation, precedence, topology handling, and edge cases.
Fit performance benchmarks
pkg/device/nvidia/fit_bench_test.go
Adds benchmarks for single-card, multi-card, all-failing, and all-pass scenarios.

Estimated code review effort: 4 (Complex) | ~45 minutes

Sequence Diagram(s)

sequenceDiagram
  participant Request
  participant NvidiaGPUDevices
  participant runCardChecks
  participant CustomFilterRule
  Request->>NvidiaGPUDevices: Fit request and device usages
  NvidiaGPUDevices->>runCardChecks: Build cardCheckCtx
  runCardChecks->>CustomFilterRule: Evaluate custom card rule
  CustomFilterRule-->>runCardChecks: Pass or failure reason
  runCardChecks-->>NvidiaGPUDevices: First failed check or success
  NvidiaGPUDevices-->>Request: Selected devices and aggregate reason
Loading

Possibly related PRs

Suggested reviewers: ouyangluwei163, archlitchi

Poem

A bunny checks each card in line,
With memory crumbs and cores divine.
Bad cards hop out, good cards stay,
Reasons neatly stack away.
Benchmarks twitch their whiskers bright—
The fit pipeline runs just right!

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.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 accurately summarizes the main refactor: splitting NVIDIA Fit into a composable check pipeline.
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.

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Code Review

This pull request refactors the GPU allocation logic in pkg/device/nvidia/device.go by extracting card-specific checks into a pipeline of check functions in a new file pkg/device/nvidia/fit_checks.go, improving code maintainability. It also introduces comprehensive unit tests and benchmarks to verify the correctness and performance of the refactored checks. The review feedback suggests enhancing the robustness of the new functions in fit_checks.go by adding defensive nil checks for parameters like dev, ctx, ctx.pod, and ctx.request to prevent potential nil pointer dereferences.

Important

The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.

Comment on lines +78 to +84
func checkCardHealth(dev *device.DeviceUsage, pod *corev1.Pod) string {
if !dev.Health {
klog.V(5).InfoS(common.CardNotHealth, "pod", klog.KObj(pod), "device", dev.ID, "health", dev.Health)
return common.CardNotHealth
}
return ""
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

medium

To adhere to defensive programming best practices and prevent potential nil pointer dereferences, we should add a nil check for the dev parameter at the beginning of checkCardHealth.

func checkCardHealth(dev *device.DeviceUsage, pod *corev1.Pod) string {
	if dev == nil {
		return common.CardNotHealth
	}
	if !dev.Health {
		klog.V(5).InfoS(common.CardNotHealth, "pod", klog.KObj(pod), "device", dev.ID, "health", dev.Health)
		return common.CardNotHealth
	}
	return ""
}
References
  1. Enforce defensive programming by ensuring appropriate null/nil checks exist before object property accesses.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Intentionally not added. These four are unexported helpers whose only callers are:

  1. NvidiaGPUDevices.Fit — the production path. Inside the main loop dev := devices[i] cannot be nil unless the caller of Fit puts nil entries in the input slice (which would be a caller bug we want surfaced, not silently swallowed). ctx is constructed inline in the same loop, and ctx.pod / ctx.request are assigned from Fit's own parameters.
  2. The unit tests in fit_checks_test.go / fit_extra_test.go, which build the inputs explicitly and would not benefit from a nil guard either.

Adding nil checks on private helpers would convert caller bugs into silent failures (e.g. returning common.CardNotHealth for a nil dev would mislabel the failure mode and skew reason counters). It also does not match the prevailing defensive-programming style elsewhere in pkg/device (e.g. checkType, CustomFilterRule, AddResourceUsage all dereference their pointer parameters without nil guards).

If the maintainers prefer the defensive style project-wide, happy to revisit — but it would be a broader change across pkg/device, not specific to this refactor.

Comment on lines +52 to +59
func runCardChecks(dev *device.DeviceUsage, ctx *cardCheckCtx) string {
for _, check := range cardCheckPipeline {
if reason := check(dev, ctx); reason != "" {
return reason
}
}
return ""
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

medium

To enforce defensive programming and prevent potential nil pointer dereferences across the entire check pipeline, we should validate that dev, ctx, ctx.pod, and ctx.request are not nil before executing the checks.

func runCardChecks(dev *device.DeviceUsage, ctx *cardCheckCtx) string {
	if dev == nil || ctx == nil || ctx.pod == nil || ctx.request == nil {
		return "InvalidContext"
	}
	for _, check := range cardCheckPipeline {
		if reason := check(dev, ctx); reason != "" {
			return reason
		}
	}
	return ""
}
References
  1. Enforce defensive programming by ensuring appropriate null/nil checks exist before object property accesses.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Intentionally not added. These four are unexported helpers whose only callers are:

  1. NvidiaGPUDevices.Fit — the production path. Inside the main loop dev := devices[i] cannot be nil unless the caller of Fit puts nil entries in the input slice (which would be a caller bug we want surfaced, not silently swallowed). ctx is constructed inline in the same loop, and ctx.pod / ctx.request are assigned from Fit's own parameters.
  2. The unit tests in fit_checks_test.go / fit_extra_test.go, which build the inputs explicitly and would not benefit from a nil guard either.

Adding nil checks on private helpers would convert caller bugs into silent failures (e.g. returning common.CardNotHealth for a nil dev would mislabel the failure mode and skew reason counters). It also does not match the prevailing defensive-programming style elsewhere in pkg/device (e.g. checkType, CustomFilterRule, AddResourceUsage all dereference their pointer parameters without nil guards).

If the maintainers prefer the defensive style project-wide, happy to revisit — but it would be a broader change across pkg/device, not specific to this refactor.

Comment on lines +61 to +69
func computeMemreq(req device.ContainerDeviceRequest, dev *device.DeviceUsage) int32 {
if req.Memreq > 0 {
return req.Memreq
}
if req.MemPercentagereq != 101 && req.Memreq == 0 {
return dev.Totalmem * req.MemPercentagereq / 100
}
return 0
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

medium

To prevent potential nil pointer dereferences when dev is nil, we should add a guard check for dev != nil before accessing dev.Totalmem.

Suggested change
func computeMemreq(req device.ContainerDeviceRequest, dev *device.DeviceUsage) int32 {
if req.Memreq > 0 {
return req.Memreq
}
if req.MemPercentagereq != 101 && req.Memreq == 0 {
return dev.Totalmem * req.MemPercentagereq / 100
}
return 0
}
func computeMemreq(req device.ContainerDeviceRequest, dev *device.DeviceUsage) int32 {
if req.Memreq > 0 {
return req.Memreq
}
if req.MemPercentagereq != 101 && req.Memreq == 0 && dev != nil {
return dev.Totalmem * req.MemPercentagereq / 100
}
return 0
}
References
  1. Enforce defensive programming by ensuring appropriate null/nil checks exist before object property accesses.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Intentionally not added. These four are unexported helpers whose only callers are:

  1. NvidiaGPUDevices.Fit — the production path. Inside the main loop dev := devices[i] cannot be nil unless the caller of Fit puts nil entries in the input slice (which would be a caller bug we want surfaced, not silently swallowed). ctx is constructed inline in the same loop, and ctx.pod / ctx.request are assigned from Fit's own parameters.
  2. The unit tests in fit_checks_test.go / fit_extra_test.go, which build the inputs explicitly and would not benefit from a nil guard either.

Adding nil checks on private helpers would convert caller bugs into silent failures (e.g. returning common.CardNotHealth for a nil dev would mislabel the failure mode and skew reason counters). It also does not match the prevailing defensive-programming style elsewhere in pkg/device (e.g. checkType, CustomFilterRule, AddResourceUsage all dereference their pointer parameters without nil guards).

If the maintainers prefer the defensive style project-wide, happy to revisit — but it would be a broader change across pkg/device, not specific to this refactor.

Comment on lines +71 to +76
func normalizeCoresreq(req *device.ContainerDeviceRequest, pod *corev1.Pod, dev *device.DeviceUsage) {
if req.Coresreq > 100 {
klog.ErrorS(nil, "core limit can't exceed 100", "pod", klog.KObj(pod), "device", dev.ID)
req.Coresreq = 100
}
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

medium

To prevent potential nil pointer dereferences when req or dev is nil, we should add defensive checks at the beginning of normalizeCoresreq.

func normalizeCoresreq(req *device.ContainerDeviceRequest, pod *corev1.Pod, dev *device.DeviceUsage) {
	if req == nil {
		return
	}
	if req.Coresreq > 100 {
		deviceID := ""
		if dev != nil {
			deviceID = dev.ID
		}
		klog.ErrorS(nil, "core limit can't exceed 100", "pod", klog.KObj(pod), "device", deviceID)
		req.Coresreq = 100
	}
}
References
  1. Enforce defensive programming by ensuring appropriate null/nil checks exist before object property accesses.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Intentionally not added. These four are unexported helpers whose only callers are:

  1. NvidiaGPUDevices.Fit — the production path. Inside the main loop dev := devices[i] cannot be nil unless the caller of Fit puts nil entries in the input slice (which would be a caller bug we want surfaced, not silently swallowed). ctx is constructed inline in the same loop, and ctx.pod / ctx.request are assigned from Fit's own parameters.
  2. The unit tests in fit_checks_test.go / fit_extra_test.go, which build the inputs explicitly and would not benefit from a nil guard either.

Adding nil checks on private helpers would convert caller bugs into silent failures (e.g. returning common.CardNotHealth for a nil dev would mislabel the failure mode and skew reason counters). It also does not match the prevailing defensive-programming style elsewhere in pkg/device (e.g. checkType, CustomFilterRule, AddResourceUsage all dereference their pointer parameters without nil guards).

If the maintainers prefer the defensive style project-wide, happy to revisit — but it would be a broader change across pkg/device, not specific to this refactor.

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

🧹 Nitpick comments (2)
pkg/device/nvidia/fit_bench_test.go (2)

50-52: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Restore global state to prevent cross-test pollution.

registerBenchmarkDevice unconditionally replaces the global device.DevicesMap. While this works in isolated benchmark runs, it can cause unexpected state pollution and failures if other tests or benchmarks that rely on the original map run subsequently.

Consider accepting b *testing.B and using b.Cleanup() to restore the previous state.

♻️ Proposed refactor
-func registerBenchmarkDevice(nv *NvidiaGPUDevices) {
+func registerBenchmarkDevice(b *testing.B, nv *NvidiaGPUDevices) {
+	b.Helper()
+	old := device.DevicesMap
 	device.DevicesMap = map[string]device.Devices{NvidiaGPUDevice: nv}
+	b.Cleanup(func() {
+		device.DevicesMap = old
+	})
 }

(Note: If you apply this, you will need to update the four call sites to pass b: registerBenchmarkDevice(b, nv))

🤖 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/nvidia/fit_bench_test.go` around lines 50 - 52, Update
registerBenchmarkDevice to accept *testing.B, save the existing
device.DevicesMap before replacing it, and register b.Cleanup to restore that
original map after the benchmark. Update all four call sites to pass b when
invoking registerBenchmarkDevice.

65-67: 🚀 Performance & Scalability | 🔵 Trivial | ⚡ Quick win

Pre-allocate arguments outside the benchmark loop.

Allocating &device.NodeInfo{} and &device.PodDevices{} inside the b.N loop introduces garbage collection and allocation overhead, which skews the benchmark's measurement of Fit. Declare these variables outside the loop to ensure you are only measuring the target function's performance.

  • pkg/device/nvidia/fit_bench_test.go#L65-L67: Extract allocations to nodeInfo := &device.NodeInfo{} and podDevices := &device.PodDevices{} before the loop, and pass nodeInfo and podDevices inside the loop.
  • pkg/device/nvidia/fit_bench_test.go#L81-L83: Apply the same extraction outside the loop.
  • pkg/device/nvidia/fit_bench_test.go#L101-L103: Apply the same extraction outside the loop.
🤖 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/nvidia/fit_bench_test.go` around lines 65 - 67, Move the NodeInfo
and PodDevices allocations outside each benchmark loop in
pkg/device/nvidia/fit_bench_test.go at lines 65-67, 81-83, and 101-103, then
pass the reused variables to nv.Fit inside the respective loops. Apply the same
change at all three sites.
🤖 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/nvidia/fit_checks_test.go`:
- Around line 242-243: Preserve the package-global device.DevicesMap in all
affected tests by snapshotting its current value before overwriting it and
restoring that snapshot during cleanup. Apply this to both setup blocks in
pkg/device/nvidia/fit_checks_test.go at lines 242-243 and 261-262, and add
equivalent snapshot-and-restore cleanup in pkg/device/nvidia/fit_extra_test.go
at lines 72-73.

---

Nitpick comments:
In `@pkg/device/nvidia/fit_bench_test.go`:
- Around line 50-52: Update registerBenchmarkDevice to accept *testing.B, save
the existing device.DevicesMap before replacing it, and register b.Cleanup to
restore that original map after the benchmark. Update all four call sites to
pass b when invoking registerBenchmarkDevice.
- Around line 65-67: Move the NodeInfo and PodDevices allocations outside each
benchmark loop in pkg/device/nvidia/fit_bench_test.go at lines 65-67, 81-83, and
101-103, then pass the reused variables to nv.Fit inside the respective loops.
Apply the same change at all three sites.
🪄 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: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 8c1c577e-5d2f-4c39-8aa5-1399f283d1bf

📥 Commits

Reviewing files that changed from the base of the PR and between 125c8c6 and 64669b0.

📒 Files selected for processing (5)
  • pkg/device/nvidia/device.go
  • pkg/device/nvidia/fit_bench_test.go
  • pkg/device/nvidia/fit_checks.go
  • pkg/device/nvidia/fit_checks_test.go
  • pkg/device/nvidia/fit_extra_test.go

Comment thread pkg/device/nvidia/fit_checks_test.go Outdated
@codecov

codecov Bot commented Jul 18, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.

Flag Coverage Δ
unittests 61.37% <100.00%> (+0.22%) ⬆️

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

Files with missing lines Coverage Δ
pkg/device/nvidia/device.go 98.65% <100.00%> (+1.73%) ⬆️
pkg/device/nvidia/fit_checks.go 100.00% <100.00%> (ø)
🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

… PR review

Address CodeRabbit feedback on PR Project-HAMi#2090:

1. Snapshot-and-restore device.DevicesMap in three test setup blocks
   (fit_checks_test.go x2, fit_extra_test.go x1) instead of replacing
   it with nil on cleanup. This avoids order-dependent state leakage
   when other tests in the package have previously populated the map.

2. registerBenchmarkDevice now accepts *testing.B and registers
   b.Cleanup to restore the previous DevicesMap, mirroring the test
   isolation pattern.

3. Hoist nodeInfo and allocated out of the inner benchmark loops in
   BenchmarkFit_SingleCardFromEight / FourCardsFromEight / AllCardsFail
   since they are loop-invariant; this removes ~3 redundant allocations
   per iteration from the measured path.

Gemini code-assist suggested adding defensive nil checks to
checkCardHealth, runCardChecks, computeMemreq, and normalizeCoresreq.
These were intentionally not added: all four are unexported helpers
whose only callers are the Fit method (which constructs ctx in place
and iterates a non-nil []*DeviceUsage) and unit tests that build the
inputs explicitly. Adding nil guards on private helpers would silently
swallow caller bugs that should surface as panics, and does not match
HAMi's prevailing style elsewhere in pkg/device. Happy to revisit if
the maintainers prefer the defensive style.

Signed-off-by: yxxhero <aiopsclub@163.com>
@FouoF

FouoF commented Jul 20, 2026

Copy link
Copy Markdown
Contributor

@yxxhero The benchmark is useful, but I think we can make it more effective. Currently, it only reports execution time without comparing the results against a baseline, which makes it difficult to identify performance regressions.

It would be helpful to store benchmark results from the main branch and compare them with the results from each pull request, showing the percentage difference. We could also introduce a threshold—for example, flagging changes that cause a regression of more than 5%—to help contributors ensure that their changes do not introduce significant performance overhead.

The exact threshold and how to account for benchmark variability can be discussed and refined later.

Conflicts only in pkg/device/nvidia/device.go. Master added a new mutex
GPU scheduler policy (hami.io/gpu-scheduler-policy: mutex) that rejects
any GPU with dev.Used > 0. Resolution keeps the refactored check
pipeline in fit_checks.go and absorbs the new logic as a new pipeline
stage:

- device.go: add gpuPolicy/isMutex locals; thread isMutex into
  cardCheckCtx.
- fit_checks.go: add isMutex field to cardCheckCtx; add checkCardMutex
  (returns common.ExclusiveDeviceAllocateConflict when the policy is
  mutex and the card is in use) and insert it in cardCheckPipeline
  right after checkCardTimeSlicing to match master's evaluation order.
- fit_checks_test.go: add TestCheckCardMutex covering policy-on/policy-
  off and zero/non-zero Used.

Tests pass with -race. gofmt, goimports, license header, and
golangci-lint clean.

Signed-off-by: yxxhero <aiopsclub@163.com>

@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.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
pkg/device/nvidia/device.go (1)

838-841: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Use the slice length instead of the map length to count allocated devices.

tmpDevs is a map of strings to slices (map[string]device.ContainerDevices). Calling len(tmpDevs) returns the number of keys in the map (which is at most 1 for k.Type), rather than the number of devices actually allocated. This causes the reason count and log messages to incorrectly report 1 instead of the true count of allocated cards.

  • pkg/device/nvidia/device.go#L838-L841: Use len(tmpDevs[k.Type]) to correctly record and log the number of devices allocated when the request cannot be fully satisfied.
  • pkg/device/nvidia/device.go#L771-L772: Use len(tmpDevs[k.Type]) to correctly add the number of discarded devices to the NumaNotFit reason count.
🛠️ Proposed fixes for the map length evaluations

For pkg/device/nvidia/device.go#L838-L841:

-	if len(tmpDevs) > 0 {
-		reasons[common.AllocatedCardsInsufficientRequest] = len(tmpDevs)
-		klog.V(5).InfoS(common.AllocatedCardsInsufficientRequest, "pod", klog.KObj(pod), "request", originReq, "allocated", len(tmpDevs))
+	allocatedCount := len(tmpDevs[k.Type])
+	if allocatedCount > 0 {
+		reasons[common.AllocatedCardsInsufficientRequest] = allocatedCount
+		klog.V(5).InfoS(common.AllocatedCardsInsufficientRequest, "pod", klog.KObj(pod), "request", originReq, "allocated", allocatedCount)
	}

For pkg/device/nvidia/device.go#L771-L772:

			if k.Nums != originReq {
-				reasons[common.NumaNotFit] += len(tmpDevs)
+				reasons[common.NumaNotFit] += len(tmpDevs[k.Type])
				klog.V(5).InfoS(common.NumaNotFit, "pod", klog.KObj(pod), "device", dev.ID, "k.nums", k.Nums, "numa", numa, "prevnuma", prevnuma, "device numa", dev.Numa)
			}
🤖 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/nvidia/device.go` around lines 838 - 841, The allocated-device
counts use the map size instead of the device-slice size. In
pkg/device/nvidia/device.go lines 838-841, update the reason count and log field
in the insufficient-request handling to use len(tmpDevs[k.Type]); in lines
771-772, update the NumaNotFit discarded-device count to use the same slice
length.
🤖 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.

Outside diff comments:
In `@pkg/device/nvidia/device.go`:
- Around line 838-841: The allocated-device counts use the map size instead of
the device-slice size. In pkg/device/nvidia/device.go lines 838-841, update the
reason count and log field in the insufficient-request handling to use
len(tmpDevs[k.Type]); in lines 771-772, update the NumaNotFit discarded-device
count to use the same slice length.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: bfbd9fe2-6e46-4f8a-8912-bba274b8cadc

📥 Commits

Reviewing files that changed from the base of the PR and between 2270d84 and e7488ca.

📒 Files selected for processing (3)
  • pkg/device/nvidia/device.go
  • pkg/device/nvidia/fit_checks.go
  • pkg/device/nvidia/fit_checks_test.go
🚧 Files skipped from review as they are similar to previous changes (2)
  • pkg/device/nvidia/fit_checks.go
  • pkg/device/nvidia/fit_checks_test.go

}

func checkCardCustomRule(dev *device.DeviceUsage, ctx *cardCheckCtx) string {
if !ctx.nv.CustomFilterRule(ctx.allocated, *ctx.request, ctx.tmpDevsMap[ctx.deviceType], dev) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

note: this passes the mutated k copy instead of the original request, harmless now since customfilterrule only reads memreq which never changes here, but revisit if it ever needs nums or coresreq

@mesutoezdil

mesutoezdil commented Jul 21, 2026

Copy link
Copy Markdown
Contributor

Thx for PR @yxxhero! Reminder: Answers must be written by human being. 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."

}

func checkCardCustomRule(dev *device.DeviceUsage, ctx *cardCheckCtx) string {
if !ctx.nv.CustomFilterRule(ctx.allocated, *ctx.request, ctx.tmpDevsMap[ctx.deviceType], dev) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

the old code passed the original request to CustomFilterRule but this passes the mutated k with clamped Coresreq and decremented Nums, harmless today because only Memreq is read but it silently changes what a future rule would see in a pr that claims no behavior change.

@mesutoezdil

Copy link
Copy Markdown
Contributor

closed bcs of inactivity of pr owner, it can be reopened if necessary

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

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants