AI Infra Interviews logo
CUDA, Triton & Kernel Engineering / 03
mediumNewNVIDIA

Given the addresses each thread in a warp touched, classify the access pattern: coalesced, strided or random. Write the classifier.

Thirty-two addresses per warp instruction, thousands of instructions: say what the pattern is and how many sectors it cost. The address-delta test for contiguous and strided, the sector count that measures the damage, the code that does both, and the edge cases (misalignment, inactive lanes, mixed widths).

Updated Sep 2026 · Grounded in real AI infrastructure interview loops and written to a senior-engineer editorial bar, with every number worked and every diagram hand-built.

TL;DR: For each warp-level memory instruction you have up to 32 addresses (some lanes inactive) and an access width. Two computations answer everything. First, sectors touched: map each active address to its 32-byte sector (addr // 32), take the set, and the size is the number of memory transactions; the useful bytes are active lanes × width; efficiency is useful ÷ (sectors × 32). Second, the pattern: sort active addresses by lane, take consecutive deltas; if all deltas equal the width it is contiguous (coalesced); if all deltas are equal to some other constant it is strided with that stride; otherwise random. Report both, because the label alone misleads: a contiguous access that is misaligned costs 5 sectors instead of 4, and a "random" access whose addresses all fall in two sectors is cheap. The classifier is 30 lines of Python; the edge cases are inactive lanes (skip them, do not count deltas across them), 8- and 16-byte accesses (the width changes the ideal sector count), and instructions where lanes hit the same address (broadcast, one sector).

How to approach it

Restate the input format and what you will output. Compute sectors first because it is pattern-independent. Then the delta test with the tolerance rules. Write the code. Then walk three examples through it and discuss the edge cases and what a summary over a whole trace looks like.

A strong answer

A typical situation: the interviewer pastes a trace of a few warp instructions with 32 hex addresses each and asks for the pattern of each, then asks the candidate to code it and to say which instruction is the most expensive.

The two computations:

input per instruction: width (4, 8 or 16 bytes), a list of 32 entries: address or None (inactive lane)
sectors: S = |{ a // 32 : a in active }|; also lines L = |{ a // 128 }|
useful bytes U = |active| × width; fetched bytes F = S × 32; efficiency E = U / F
ideal sectors for a fully active warp: 32 × width / 32 = width (4 for 4-byte, 8 for 8-byte, 16 for 16-byte)

pattern from deltas: take active (lane, addr) pairs in lane order; d_k = addr_{k+1} − addr_k over adjacent active lanes
  all d_k == width           → contiguous (and check alignment: addr_0 % 32 == 0 → aligned, else misaligned)
  all d_k == c, c != width   → strided, stride c bytes (c / width elements); note c < width means overlapping,
                                c == 0 means broadcast (all lanes same address, 1 sector)
  else                       → irregular; report S so the cost is still known

sanity: a contiguous 32-lane load of 4-byte elements touches 128 B = 4 sectors; the same warp
        strided by 32 elements touches 32 sectors, 8x the traffic for the same 128 B of useful data

The code:

from dataclasses import dataclass

@dataclass
class Result:
    pattern: str        # "contiguous" | "strided" | "broadcast" | "irregular"
    stride_bytes: int | None
    sectors: int
    lines: int
    useful_bytes: int
    efficiency: float
    aligned: bool

def classify(addrs: list[int | None], width: int) -> Result:
    active = [(lane, a) for lane, a in enumerate(addrs) if a is not None]
    if not active:
        return Result("empty", None, 0, 0, 0, 0.0, True)
    sectors = {a // 32 for _, a in active}
    lines = {a // 128 for _, a in active}
    useful = len(active) * width
    eff = useful / (len(sectors) * 32)
    aligned = (active[0][1] % 32) == 0

    deltas = [active[k + 1][1] - active[k][1] for k in range(len(active) - 1)]
    if not deltas:                                  # one active lane
        return Result("contiguous", width, len(sectors), len(lines), useful, eff, aligned)
    if all(d == 0 for d in deltas):
        return Result("broadcast", 0, len(sectors), len(lines), useful, eff, aligned)
    if all(d == width for d in deltas):
        return Result("contiguous", width, len(sectors), len(lines), useful, eff, aligned)
    if all(d == deltas[0] for d in deltas):
        return Result("strided", deltas[0], len(sectors), len(lines), useful, eff, aligned)
    return Result("irregular", None, len(sectors), len(lines), useful, eff, aligned)

def summarize(trace: list[tuple[int, list[int | None]]]) -> dict:
    """trace: [(width, addrs), ...]; returns totals and the worst instruction."""
    total_sectors = total_useful = 0
    worst = None
    for idx, (width, addrs) in enumerate(trace):
        r = classify(addrs, width)
        total_sectors += r.sectors
        total_useful += r.useful_bytes
        if worst is None or r.sectors > worst[1].sectors:
            worst = (idx, r)
    return {"sectors": total_sectors, "useful_bytes": total_useful,
            "efficiency": total_useful / (32 * total_sectors) if total_sectors else 0.0,
            "worst_instruction": worst}

Three examples through it:

1. width 4, addrs = [0x1000 + 4t for t in 0..31]
   deltas all 4 → contiguous; sectors {0x80..0x83} = 4; useful 128; efficiency 1.0; aligned (0x1000 % 32 == 0)
2. width 4, addrs = [0x1000 + 128t]        (column of a 32-float-wide row-major matrix)
   deltas all 128 → strided, 128 bytes = 32 elements; sectors 32; useful 128; efficiency 0.125
3. width 4, addrs = [0x1004 + 4t]          (contiguous but misaligned by 4 bytes)
   contiguous; sectors: 0x1004..0x1080 spans sectors 0x80..0x84 → 5; efficiency 128/160 = 0.8; aligned False
4. width 4, addrs random within one 128-byte line
   irregular by deltas; sectors ≤ 4; efficiency up to 1.0 → the label says irregular and the cost says cheap,
   which is why both are reported

Memory Coalescing is the model the classifier implements; Memory Coalescing and How to See It (the companion question) has the profiler's version of the same numbers.

Edge cases the interviewer will raise, and the handling:

inactive lanes: excluded from both computations; deltas are between adjacent active lanes only (a gap of one inactive
  lane between contiguous addresses shows a delta of 2 × width; the strict rule calls it strided; a tolerant rule
  accepts deltas of width × (lane gap) as contiguous; state which you chose)
widths: 8-byte and 16-byte accesses have ideal sector counts of 8 and 16; efficiency is the fair comparison across widths
partial sectors at the ends: a contiguous but misaligned access touches one extra sector; the classifier reports it
  through the sector count and the aligned flag
same address across lanes (broadcast): one sector; common for reading a scalar from global memory; cheap
stores: same arithmetic; partially written sectors cost a read-modify-write at L2, so efficiency understates the cost
multiple instructions to one line: the L1 cache may serve the second from the first's fill; the classifier counts per
  instruction and a cache model is a separate step; say so rather than over-claim
trace scale: millions of instructions; the summary streams and keeps running totals plus a top-k of expensive
  instructions by sectors, not a list of every result

Profiling with Nsight is where the same numbers come from in practice (sectors per request), and the classifier is what the profiler computes from hardware counters; the interview version exists to check the candidate can derive it.

THE ADDRESS-DELTA TEST, 32 ADDRESSES all Δ = width contiguous 4 sectors all Δ = c, c ≠ width strided by c up to 32 all Δ = 0 broadcast: every lane the same 1 sector anything else irregular report the count Thirty seconds of arithmetic names the pattern, and no hypothesis is needed before it. Ask for the sector count rather than the classification. It is harder and it is the useful number.

The reversal condition: if the trace records only the first lane's address per instruction (some tools do), the sector computation is impossible and the pattern must be inferred from consecutive instructions in a loop; the candidate should ask what the trace contains before writing code.

What interviewers probe next

  • "Which instruction should the engineer fix first?" The one with the most sectors weighted by how many times it executes; the summary's worst-instruction plus an execution count from the trace.
  • "How do you tell a coalesced access of a 2D tile from a strided one?" Within one warp instruction, a 32 × 4-byte row is contiguous; the tile shape shows across instructions. The classifier is per instruction by design.
  • "What about shared memory?" A different model (banks, not sectors); the same delta logic with a 4-byte bank width and modulo 32 finds bank conflicts, which is a good follow-up to offer.
  • "Can you do it in the kernel itself?" Yes, with warp intrinsics: each lane shares its address via shuffles and lane 0 computes the sector set; useful for instrumentation builds.

Common mistakes

  • Labeling by deltas only and never computing sectors, so a misaligned contiguous access reads as free.
  • Counting deltas across inactive lanes and mislabeling a masked contiguous access as strided.
  • Assuming 4-byte width for every instruction.
  • A per-instruction list as the output for a trace with millions of entries.

Key takeaways

  • Sectors touched is the cost; efficiency = useful bytes ÷ (sectors × 32); compute it first.
  • Pattern from lane-ordered deltas between active lanes: equal to width is contiguous, constant otherwise is strided, else irregular; zero is broadcast.
  • Handle inactive lanes, access width, alignment and broadcast explicitly.
  • Summarize with totals and the most expensive instructions, not a list.
That one was free — and so are 10 answers per topic without an account. Signing in doubles that to 20, opens the Plus lessons in the courses, and remembers which topics you keep getting wrong.no card · Google sign-in · nothing to cancel
HOW DID IT GO?
0
READING SIGNED OUT

Signing in doubles your free answers, from 10 to 20 per topic, and the site starts remembering you: mastery per topic, bookmarks, and a next-focus recommendation. Free, no card.

Sign in free

The concepts behind this question

Ranked by how closely each one overlaps this question's topic, so the first card is the thing to read if the answer above moved too fast.

Core
Kernels & CompilersSign in
Memory CoalescingA warp's 32 threads issue one memory request together, and the hardware serves it in 32-byte sectors. Coalescing is arranging addresses so those sectors are full of bytes the warp will use. It decides whether a bandwidth-bound kernel moves at the HBM rate or at an eighth of it, and it is the pattern NVIDIA's trace-classification interview question tests.
Advanced
💻 Coding for Infra🔒 Premium
Batching Queues and BackpressureWrite a request batcher is the coding round's version of the serving engine's scheduler: requests arrive one at a time, the GPU wants them in groups, and the batcher decides when a group is full enough to send without holding anyone too long or accepting more than it can hold. The two knobs are the maximum batch size and the maximum wait, the invariant is a bounded queue, and the follow-ups (priorities, cost-aware batching, cancellation, bounded in-flight batches) are the ideas the real engines carry. This page implements the batcher in asyncio, derives what each knob buys, and walks the follow-ups.
Advanced
💻 Coding for Infra🔒 Premium
Interval Merging and Utilization LogsGiven busy intervals per GPU, when was the whole cluster idle? What was the utilization per hour from a log of start and stop events? Which jobs overlapped? These are the interval problems of the infrastructure coding screen, and they share one tool: sort the endpoints and sweep. The sweep line turns every variant into a single pass with a counter, the sort is the only thing that costs more than linear time, and the edge cases (touching intervals, zero-length events, an unterminated start) are where candidates lose the round. This page works the standard problem and its relatives with code, tests and the complexity derivation.
Foundational
💻 Coding for Infra
The GPU Credit Scheduler PatternThe most widely reported coding problem in AI infrastructure loops is a small scheduler: accounts hold credits, jobs arrive with a cost and a priority, and you must decide which jobs run, in what order, without letting any account overspend, then extend it under follow-ups (refunds, reservations, concurrency limits, fairness). It is not a trick question; it is a test of whether you can model state cleanly, pick the right data structures, keep invariants under mutation, and talk about complexity while typing. This page works the problem from the first line to the fourth follow-up, with the code, the invariants, and the derivations.
UP NEXT ON YOUR JOURNEY
FEDITOR'S NOTE

Scored on computing sectors touched as the primary measure, on detecting stride from address deltas with tolerance for inactive lanes, on handling misalignment and access width, and on reporting efficiency rather than a label alone.

DISCUSSION · 0

No comments yet — be the first to share your approach.