Skip to content

Commit 2c8b0b1

Browse files
committed
test(cuda_core): drop the Windows address-space hole report
It was there to work out why the reservations sometimes failed. Runs with bottom-up randomization disabled answered that -- 12 of 12 sessions clean against 7 of 12 with it on -- so the VirtualQuery walk, the hole-capacity maths and their tests have served their purpose and go. Keeps the reservations themselves, the cuMemAddressReserve measurement that works on both platforms, and the single end-of-run report.
1 parent 597e68a commit 2c8b0b1

3 files changed

Lines changed: 3 additions & 232 deletions

File tree

cuda_core/tests/helpers/va_reservation.py

Lines changed: 3 additions & 91 deletions
Original file line numberDiff line numberDiff line change
@@ -30,21 +30,10 @@
3030
from __future__ import annotations
3131

3232
import os
33-
import sys
3433
import time
3534

3635
from cuda.bindings import driver
3736

38-
if sys.platform == "win32":
39-
from helpers import win_address_space
40-
else:
41-
# No Linux counterpart on purpose; see helpers/win_address_space.py.
42-
win_address_space = None
43-
44-
# Holes smaller than this are noise in the layout dump.
45-
LAYOUT_MIN_HOLE = 1024 * 1024 * 1024
46-
LAYOUT_MAX_HOLES = 6
47-
4837
MIB = 1024 * 1024
4938
GIB = 1024 * MIB
5039

@@ -141,44 +130,6 @@ def format_bytes(value: int | None) -> str:
141130
return f"{value / GIB:.2f} GiB"
142131

143132

144-
def free_holes() -> list[tuple[int, int]] | None:
145-
"""Unallocated holes as ``(size, base)``, largest first. None off Windows."""
146-
if win_address_space is None:
147-
return None
148-
return win_address_space.free_regions(LAYOUT_MIN_HOLE)
149-
150-
151-
def pool_capacity(holes, pool_bytes) -> int | None:
152-
"""How many driver pools the free holes could hold between them.
153-
154-
This is the number that decides the outcome, and it is neither the largest
155-
hole nor the count of holes that clear one pool. A reservation has to fit
156-
within a single hole, but a hole twice the size takes two -- the driver
157-
packs them from its low end, so a lone 800 GiB hole hosts both pools just
158-
as well as two 400 GiB ones. Summing each hole's capacity covers both.
159-
"""
160-
if holes is None or not pool_bytes:
161-
return None
162-
return sum(size // pool_bytes for size, _base in holes)
163-
164-
165-
def layout_lines(holes, pool_bytes, label, detail: bool = True) -> list[str]:
166-
"""Render the hole structure. Base addresses show whether it is randomized."""
167-
if holes is None:
168-
return []
169-
capacity = pool_capacity(holes, pool_bytes)
170-
headline = f"free holes {label}: {len(holes)} >= {format_bytes(LAYOUT_MIN_HOLE)}"
171-
if capacity is not None:
172-
headline += f", room for {capacity} pool(s) of {format_bytes(pool_bytes)}"
173-
out = [headline]
174-
if detail:
175-
for size, base in holes[:LAYOUT_MAX_HOLES]:
176-
out.append(f" {format_bytes(size):>14} @ {base:#018x}")
177-
if len(holes) > LAYOUT_MAX_HOLES:
178-
out.append(f" ... and {len(holes) - LAYOUT_MAX_HOLES} smaller")
179-
return out
180-
181-
182133
class Reservation:
183134
"""One driver-managed pool that has to be materialized."""
184135

@@ -260,16 +211,7 @@ class ReservationReport:
260211
"""What the early reservations cost, for the terminal."""
261212

262213
def __init__(
263-
self,
264-
device_name,
265-
device_memory,
266-
before,
267-
after,
268-
reservations,
269-
measured,
270-
seconds=0.0,
271-
unsupported=False,
272-
holes_before=None,
214+
self, device_name, device_memory, before, after, reservations, measured, seconds=0.0, unsupported=False
273215
):
274216
self.device_name = device_name
275217
self.device_memory = device_memory
@@ -279,7 +221,6 @@ def __init__(
279221
self.measured = measured
280222
self.seconds = seconds
281223
self.unsupported = unsupported
282-
self.holes_before = holes_before
283224

284225
@property
285226
def failed(self) -> list[Reservation]:
@@ -310,24 +251,13 @@ def lines(self) -> list[str]:
310251
# carve its reservations out of a region other than the largest
311252
# hole, in which case the largest hole does not move at all.
312253
change = None if self.before is None or self.after is None else self.before - self.after
313-
if change is None:
314-
note = ""
315-
elif change > 0:
316-
note = f" (largest hole shrank by {format_bytes(change)})"
317-
else:
318-
note = " (largest hole unchanged)"
319-
out.append(f"largest reservable range after: {format_bytes(self.after)}{note}")
254+
out.append(f"largest reservable range after: {format_bytes(self.after)}")
320255
pool_bytes = self.pool_reservation_bytes
321256
if pool_bytes and self.after is not None:
322257
out.append(
323258
f"remaining headroom: {self.after // pool_bytes} more pool-sized "
324259
f"({format_bytes(pool_bytes)}) reservations [{self.seconds:.1f}s measuring]"
325260
)
326-
# Just the counts here. They are what makes a successful session
327-
# comparable with a failed one, since the hole count is what decides the
328-
# outcome. The addresses behind them are only worth printing when a
329-
# reservation is actually refused; see build_failure_message.
330-
out += layout_lines(self.holes_before, self.pool_reservation_bytes, "at session start", detail=False)
331261
return out
332262

333263

@@ -349,14 +279,6 @@ def build_failure_message(report: ReservationReport) -> str:
349279
for item in report.failed:
350280
lines.append(f" {item.name} ({item.detail}): {item.error}")
351281

352-
# Each pool needs a hole of its own, so the hole structure -- not the total
353-
# free -- is what decides this. Included here because it is the first thing
354-
# anyone diagnosing a refusal will want.
355-
layout = layout_lines(report.holes_before, pool_bytes, "at session start")
356-
if layout:
357-
lines.append("")
358-
lines += [f" {line}" for line in layout]
359-
360282
return "\n".join(lines)
361283

362284

@@ -379,7 +301,6 @@ def reserve_driver_pools(device, measure: bool = True) -> ReservationReport:
379301
started = time.perf_counter()
380302
before = largest_reservable() if measured else None
381303
elapsed = time.perf_counter() - started
382-
holes_before = free_holes()
383304

384305
reservations = reservations_for(device)
385306
for item in reservations:
@@ -388,13 +309,4 @@ def reserve_driver_pools(device, measure: bool = True) -> ReservationReport:
388309
started = time.perf_counter()
389310
after = largest_reservable() if measured else None
390311
elapsed += time.perf_counter() - started
391-
return ReservationReport(
392-
device.name,
393-
device_memory,
394-
before,
395-
after,
396-
reservations,
397-
measured,
398-
elapsed,
399-
holes_before=holes_before,
400-
)
312+
return ReservationReport(device.name, device_memory, before, after, reservations, measured, elapsed)

cuda_core/tests/helpers/win_address_space.py

Lines changed: 0 additions & 74 deletions
This file was deleted.

cuda_core/tests/test_helpers.py

Lines changed: 0 additions & 67 deletions
Original file line numberDiff line numberDiff line change
@@ -167,73 +167,6 @@ def test_failure_message_names_the_pool_and_the_size_it_needed():
167167
assert "ZeroDivisionError" in message
168168

169169

170-
@pytest.mark.agent_authored(model="claude-opus-5")
171-
def test_pool_capacity_counts_two_pools_in_one_large_hole():
172-
# A reservation must fit within a single hole, but one hole twice the size
173-
# takes both pools -- the driver packs them from its low end. Counting only
174-
# the holes that clear one pool would call this a failure.
175-
pool = 358 * va_reservation.GIB
176-
one_big_hole = [(800 * va_reservation.GIB, 0x1000)]
177-
two_holes = [(400 * va_reservation.GIB, 0x1000), (400 * va_reservation.GIB, 0x2000)]
178-
179-
assert va_reservation.pool_capacity(one_big_hole, pool) == 2
180-
assert va_reservation.pool_capacity(two_holes, pool) == 2
181-
182-
183-
@pytest.mark.agent_authored(model="claude-opus-5")
184-
def test_pool_capacity_ignores_holes_that_cannot_take_a_whole_pool():
185-
# Free space that is merely plentiful does not help; it has to be
186-
# contiguous. This is the shape that fails on the affected machine.
187-
pool = 358 * va_reservation.GIB
188-
lopsided = [(700 * va_reservation.GIB, 0x1000), (300 * va_reservation.GIB, 0x2000)]
189-
190-
assert va_reservation.pool_capacity(lopsided, pool) == 1
191-
192-
193-
@pytest.mark.agent_authored(model="claude-opus-5")
194-
def test_layout_lines_are_empty_off_windows():
195-
# free_holes() returns None where VirtualQuery is unavailable; the report
196-
# must simply omit the section rather than fail.
197-
assert va_reservation.layout_lines(None, 358 * va_reservation.GIB, "before") == []
198-
assert va_reservation.pool_capacity(None, 358 * va_reservation.GIB) is None
199-
200-
201-
@pytest.mark.agent_authored(model="claude-opus-5")
202-
def test_neither_report_mentions_holes_off_windows(monkeypatch):
203-
# The hole layout comes from VirtualQuery, so it is Windows-only. Everything
204-
# else is CUDA APIs and must still be reported. Simulates the non-win32
205-
# branch of the guarded import in va_reservation.
206-
monkeypatch.setattr(va_reservation, "win_address_space", None)
207-
good = va_reservation.Reservation("default device mempool", "cuDeviceGetMemPool", lambda: None)
208-
bad = va_reservation.Reservation("graph memory pool", "cuGraphAddMemAllocNode", lambda: 1 / 0)
209-
good.run()
210-
bad.run()
211-
holes = va_reservation.free_holes()
212-
gib = va_reservation.GIB
213-
214-
report = va_reservation.ReservationReport(
215-
"dev", 25650855936, 900 * gib, 800 * gib, [good, bad], True, holes_before=holes
216-
)
217-
218-
assert holes is None
219-
assert "free holes" not in "\n".join(report.lines())
220-
message = va_reservation.build_failure_message(report)
221-
assert "free holes" not in message
222-
assert "graph memory pool" in message # the rest of the report survives
223-
224-
225-
@pytest.mark.agent_authored(model="claude-opus-5")
226-
def test_layout_lines_report_base_addresses():
227-
# The bases are the point: comparing them across launches shows whether the
228-
# layout is being randomized.
229-
holes = [(500 * va_reservation.GIB, 0x2200000000)]
230-
231-
text = "\n".join(va_reservation.layout_lines(holes, 358 * va_reservation.GIB, "before"))
232-
233-
assert "room for 1 pool(s)" in text
234-
assert "0x0000002200000000" in text
235-
236-
237170
@pytest.mark.agent_authored(model="claude-opus-5")
238171
def test_report_lines_cover_both_driver_pools():
239172
ok_pools = [

0 commit comments

Comments
 (0)