Practice lab · AI compiler · NPU

AI Compiler and NPU Practice

A self-contained study page for multiple-choice and open-ended practice on graph compilers, Tenstorrent-style NPU programming, memory planning, MLIR-style lowering, quantization, runtime scheduling, and small coding drills.

Offline HTML MCQ + open-ended Collapsible answers Public resources

How to use

Study flow
  1. Read each question and pick an answer before opening the card.
  2. Open the card to check the correct option and the short explanation.
  3. Use the expanded question bank for verbal rehearsal: answer first, then open the card.
  4. Use the coding drills as whiteboard practice: state assumptions, give complexity, test edge cases.

This page keeps public technical vendor, framework, and hardware names where they are useful for AI compiler and NPU study.

Mental model

An AI compiler turns a model graph into efficient execution on a target accelerator. The central job is not only choosing arithmetic instructions. The compiler must preserve graph semantics, select layouts, tile work into local memory, schedule data movement, fuse or split operators, choose quantization boundaries, and coordinate runtime execution.

Pipeline: framework graph → graph optimization → high-level IR → layout and memory planning → target lowering → runtime packets and kernels.
LevelMain informationGood optimizations here
GraphOperators, shapes, constants, dependenciesFusion, folding, dead-node removal, partitioning
Tensor IRLoops, tensors, indexing, affine structureTiling, loop ordering, vectorization, bufferization planning
Target IRMemory spaces, DMA, hardware intrinsics, launch shapeSRAM placement, double buffering, sharding, kernel selection
RuntimeCompiled programs, queues, synchronization, cache stateWarmup, program cache, async dispatch, profiling

Processor map

This is a public-study comparison of common processor labels used around AI systems. Exact architecture varies by vendor, so treat this as a mental model, not a hardware specification.

UnitPrimary roleStrengthsCommon AI useWatch-outs
CPU
Central Processing Unit
General-purpose control processor with strong single-thread and branch performance. Operating system work, orchestration, irregular control flow, low-latency decisions, scalar code. Pre/post-processing, graph orchestration, fallback ops, tokenization, small dynamic tasks. Lower throughput and energy efficiency for large dense tensor math than specialized accelerators.
GPU
Graphics Processing Unit
High-throughput parallel processor with many lightweight threads. Massive data parallelism, dense tensor math, training, rendering, large batched workloads. Deep learning training, large inference batches, CUDA-style custom kernels, transformer matmuls. Needs enough parallel work to stay full; host launch overhead and memory movement still matter.
VPU
Vision Processing Unit
Vision-oriented low-power accelerator, often for edge computer-vision and DNN inference. Camera/vision pipelines, low-power edge inference, image processing, embedded deployment. Object detection, classification, face/person detection, surveillance, retail, transportation, smart cameras. Usually narrower than a GPU; best when the workload matches supported vision/DNN operators.
NPU
Neural Processing Unit
Neural-network accelerator optimized for AI inference or training primitives. Matrix multiply, convolution, activation pipelines, quantized inference, power-efficient AI. On-device AI, image/audio/text inference, sustained AI workloads, CPU/GPU offload. Vendor-specific op support, layouts, memory hierarchy, quantization formats, and compiler stack.
LPU
Language Processing Unit
Groq-specific term for an inference architecture designed around predictable language-model execution. Compiler-planned execution, on-chip SRAM emphasis, deterministic scheduling, token-generation latency. LLM inference, especially latency-sensitive token generation. Not a generic standard like CPU/GPU. Treat it as a vendor architecture category unless another source defines it explicitly.
How to choose a target

Use the CPU for control-heavy or irregular work, the GPU for large parallel dense workloads, the VPU for low-power vision pipelines, the NPU for power-efficient neural-network acceleration, and an LPU-style system when the deployment is specifically optimized for predictable language-model inference.

Knowledge check. Which processor is usually best for branchy graph orchestration and unsupported fallback operators?core
  1. CPU
  2. Only NPU
  3. Only VPU
  4. Only LPU

Answer: A. CPUs are general-purpose and handle irregular control flow well. Accelerators can be faster for supported tensor regions, but unsupported or branch-heavy parts often return to CPU or a custom kernel path.

Knowledge check. Why do modern AI PCs often include CPU, GPU, and NPU together?core
  1. Because all three do exactly the same job.
  2. Because different workloads trade off latency, throughput, power, programmability, and operator support.
  3. Because NPUs replace operating systems.
  4. Because GPUs cannot run parallel code.

Answer: B. A runtime can place each task where it fits best: CPU for orchestration, GPU for high-throughput parallel work, and NPU for efficient sustained AI tasks.

Transformer basics

Transformers are the dominant architecture behind many modern LLMs. The original Transformer replaced recurrence and convolution with attention-based blocks, making sequence modeling more parallelizable during training.

Core idea: A token representation attends to other token representations. Attention builds context-aware vectors, then feed-forward layers transform each token independently.
ComponentWhat it doesCompiler / NPU relevance
TokenizationConverts text into token IDs consumed by the model.Usually CPU-side or preprocessing; affects sequence length.
EmbeddingMaps token IDs to dense vectors.Memory lookup plus layout choice.
Self-attentionUses Q, K, V projections so each token can mix information from other tokens.GEMMs, softmax, masking, KV cache, memory bandwidth.
MLP / feed-forwardPer-token dense layers, often the largest FLOP contributor.Large GEMMs, activation fusion, quantization.
LayerNorm / RMSNormNormalizes activations for stable computation.Reduction-heavy, often less matrix-engine-friendly than GEMM.
KV cacheStores past keys and values during autoregressive generation.Critical for token latency and memory footprint.
Scaled dot-product attention:

  Attention(Q, K, V) = softmax(Q * K^T / sqrt(d_k)) * V

Common LLM inference shape:

  prompt prefill:
    many tokens at once
    large GEMMs and attention matrices

  decode:
    one new token per step
    reuses KV cache
    latency and memory bandwidth become very important
Knowledge check. Why are GEMM and memory bandwidth central to Transformer inference?core
  1. Transformers have no matrix multiplications.
  2. Q/K/V projections, output projections, and MLP layers are dominated by matrix multiplications, while attention and KV cache stress memory movement.
  3. All Transformer work is scalar branching.
  4. KV cache removes all memory traffic.

Answer: B. Transformer blocks are mostly dense linear algebra plus memory-heavy attention/cache operations. That is why GPU, NPU, and LPU-style systems focus heavily on GEMM throughput, memory locality, and scheduling.

Knowledge check. What is the difference between prefill and decode in LLM inference?deep
  1. Prefill processes the input prompt, often with many tokens in parallel; decode generates new tokens step by step while reusing the KV cache.
  2. Prefill is training and decode is data loading.
  3. Decode ignores all previous tokens.
  4. Prefill only runs on CPUs.

Answer: A. Prefill tends to be throughput-friendly. Decode is often latency-sensitive because each new token depends on the previous generated token.

Tenstorrent stack

This section focuses on Tenstorrent-style accelerator concepts, the public software stack, TT-Metalium programming ideas, tiled tensors, circular buffers, explicit data movement, program caching, and fast dispatch.

Memory hook: Tenstorrent-style low-level programming is easiest to remember as reader → compute → writer. Reader kernels move tiles into local buffers, compute kernels consume local tiles, and writer kernels move output tiles onward.
Software stack snapshot

TT-BUDA / PyBuda was the older high-level AI/ML compiler path. It could import models from frameworks such as PyTorch, TensorFlow, ONNX, and TFLite, but the public repository is now archived and points users toward TT-Forge.

TT-Forge is the current MLIR-based compiler stack. Think of it as the model-import and graph-lowering path: frontends such as TT-XLA and TT-Forge-ONNX feed TT-MLIR, which lowers toward execution on Tenstorrent hardware.

TT-NN is the higher-level neural-network operation library with Python and C++ APIs. It is the practical layer for writing or porting model code with optimized operations without manually writing every low-level kernel.

TT-Metalium is the low-level SDK and programming model. It exposes host programs, device kernels, circular buffers, explicit memory movement, NoC behavior, and Tensix compute resources for custom performance work.

TT-Lang is useful to watch as a kernel/programming-language layer for expressing Tenstorrent-style operations, dataflow buffers, simulation, and lower-level tutorials such as elementwise and matmul examples.

LayerMain roleWhen to study it
TT-ForgeCurrent high-level compiler path from framework graphs toward Tenstorrent execution.Model import, graph lowering, unsupported ops, compiler diagnostics, model bring-up.
TT-MLIRMLIR-based compiler infrastructure and backend used inside the compiler flow.Dialect design, lowering passes, layout constraints, runtime/codegen boundaries.
TT-NNHigh-level neural-network op library built on TT-Metalium.Porting model layers, using optimized ops, profiling tensors/ops, adding new operations.
TT-MetaliumLow-level SDK for host/device programs and custom kernels.Reader/compute/writer kernels, circular buffers, NoC movement, core grids, SRAM pressure.
TT-LangKernel/programming-language layer with simulator and tutorials.Learning dataflow-buffer concepts and experimenting with operation-level implementations.
TT-BUDALegacy high-level compiler stack; useful historically, but archived.Understanding older Tenstorrent terminology and why current material points to TT-Forge.
ConceptStudy meaningCompiler angle
Tensix-style core gridMany local compute nodes with nearby memory and data-movement capabilityPlacement, sharding, synchronization, and load balance matter.
Tile layoutTensors are processed in hardware-friendly blocks rather than arbitrary scalar orderPadding, layout conversion, and tile-size selection become backend decisions.
Circular buffersLocal producer-consumer queues between data movement and computeBuffer depth controls overlap and SRAM pressure.
TT-Metalium-style kernelsExplicit host programs plus device kernels for movement and computeThe compiler/runtime must emit launch configs, runtime args, and synchronization.
Kernel build and dispatch lifecycle

In TT-Metalium, device-side kernel source is typically C++ compiled just-in-time for the programmable RISC-V processors in Tenstorrent cores. DeepWiki describes the JIT build system as translating kernel source into RISC-V binaries using the SFPI toolchain, then linking ELF images with architecture/core-specific settings. The runtime tracks whether program binaries have been written to device DRAM and uses command queues plus dispatch kernels to issue writes, synchronization, launch messages, and go signals.

StageWhat happensWhy it matters
Kernel sourceKernels can come from file paths or inline source; compile-time args and defines specialize the kernel.Changing source, defines, or compile-time args should invalidate stale binaries.
Generated filesThe JIT emits helper files such as kernel_includes.hpp, defines_generated.h, and Metal 2.0 binding headers.Generated accessors connect source code to dataflow buffers, semaphores, and runtime/named arguments.
CompileThe build environment selects the SFPI RISC-V compiler, commonly riscv-tt-elf-g++, from runtime/sfpi or /opt/tenstorrent/sfpi.Compiler version, flags, includes, and defines are part of the build cache identity.
LinkObjects are linked into ELF binaries using a linker script selected through the HAL/JIT build query.The linker script controls text/data/stack placement and per-processor memory limits.
Load and dispatchThe program command sequence stages binaries and commands; fast dispatch kernels process command pages and send go signals to workers.Program launch overhead, binary transfer, runtime arguments, and synchronization affect first-run and steady-state timing.
Host program / TTNN op
        |
        v
Create Program + Kernels + RT args + CBs
        |
        v
JIT generate headers/defines
        |
        v
riscv-tt-elf-g++ compile -> .o
        |
        v
link with processor linker script -> .elf kernel binary
        |
        v
write binaries/commands to device memory
        |
        v
dispatch kernels send launch/go signals to target cores

TT-Metalium basics

Q31. In a TT-Metalium-style program, what are the reader, compute, and writer kernels responsible for?core
  1. The reader compiles source code, the compute kernel allocates host memory, and the writer trains the model.
  2. The reader moves input tiles into local buffers, the compute kernel operates on those tiles, and the writer drains output tiles.
  3. All three kernels perform the same dense matrix multiply.
  4. The writer always runs before the reader.

Answer: B. This split makes data movement explicit. It is useful for understanding why compiler backends care so much about tile order, buffer depth, and synchronization.

DRAM or peer core
      |
      v
reader kernel  ->  input circular buffer  ->  compute kernel
                                                    |
                                                    v
DRAM or peer core  <- output circular buffer <- writer kernel
Q32. What does a circular buffer represent in this programming model?memory
  1. A global database of compiled models.
  2. A local FIFO-like contract between a producer kernel and a consumer kernel.
  3. A replacement for graph-level optimization.
  4. A way to make every tensor shape static.

Answer: B. Calls that reserve, push, wait, and pop buffer slots express ownership. The same idea generalizes to many NPUs: keep local memory full enough to overlap movement and compute, but not so full that SRAM is wasted.

Q33. Why do tile shape and page size matter for Tenstorrent-style tensor execution?memory
  1. They only affect file naming.
  2. They determine how tensor data maps to hardware-friendly blocks, local memory, and data movement transactions.
  3. They remove the need for quantization.
  4. They are chosen randomly by the operating system.

Answer: B. A backend may need to pad dimensions, choose tiled layouts, and align work to hardware tile sizes. Poor layout choices can cause extra movement or underutilized compute.

Q34. What is sharding in an NPU memory-layout context?memory
  1. Deleting part of a tensor to reduce accuracy.
  2. Splitting a tensor across multiple cores or local memories so each core owns a slice.
  3. Converting every operator to a CPU fallback.
  4. A synonym for constant folding.

Answer: B. Sharding can improve locality and parallelism. The tradeoff is that later operators may require a different layout, creating resharding cost.

Q35. What does resharding usually cost?memory
  1. Nothing; it is only metadata.
  2. Data movement, synchronization, and sometimes temporary buffers.
  3. Only a source-code comment.
  4. It always reduces peak memory.

Answer: B. Resharding can be necessary, but it is often pure overhead. A good layout plan tries to minimize avoidable layout conversions across adjacent operators.

Runtime and compiler stack

Q36. How should program cache and warmup be explained in accelerator benchmarking?core
  1. The first run is always the only valid number.
  2. Warmup may populate compiled programs, kernel configs, memory allocations, or runtime caches; steady-state latency should be measured separately.
  3. Program caching changes the model's numerical output.
  4. Warmup is only relevant for web browsers.

Answer: B. Tenstorrent-style program caching is one concrete example of a broader runtime idea: avoid paying setup costs repeatedly when shapes and programs are reused.

Q37. What is fast dispatch trying to reduce?deep
  1. Numerical precision.
  2. Host-side launch overhead and gaps between device work items.
  3. The number of model parameters.
  4. The need for graph correctness.

Answer: B. Fast dispatch is about keeping the device fed with work. It pairs naturally with command queues, prefetching, and careful synchronization.

Q38. Why are TT-Forge and older TT-BUDA-style high-level compiler paths useful to study even when focusing on low-level kernels?deep
  1. It proves low-level kernels are unnecessary for performance work.
  2. It shows the full vertical path from model import and graph lowering down to tile layout, scheduling, and runtime execution.
  3. It replaces all memory planning with Python syntax.
  4. It only teaches frontend UI design.

Answer: B. A strong NPU mental model connects both ends: high-level graph decisions and low-level hardware execution. TT-BUDA is mainly historical now; TT-Forge is the current MLIR-based compiler stack to follow for model bring-up and lowering.

JIT 1. What is the main job of TT-Metalium's JIT build system?core
  1. Train the model weights on the host CPU.
  2. Compile device-side C++ kernel code into RISC-V binaries for programmable Tenstorrent cores.
  3. Convert every tensor to JSON.
  4. Replace all graph scheduling decisions with CSS layout.

Answer: B. The JIT build system specializes kernels using source, compile-time arguments, defines, target core type, and runtime options, then produces device binaries used by the runtime.

JIT 2. What is the role of a linker script in this kernel build flow?deep
  1. It controls where text, data, stack, and related sections are placed for the target processor and memory map.
  2. It chooses the best batch size for training.
  3. It calibrates INT8 activation ranges.
  4. It stores the Python package version only.

Answer: A. Linker scripts are low-level layout contracts. In a device kernel flow, they matter because BRISC, NCRISC, TRISC, Ethernet, firmware, and kernel images have different memory regions and size limits.

JIT 3. Why does TT-Metalium hash kernel source, compiler settings, and build parameters?core
  1. To encrypt model weights.
  2. To change the model's mathematical graph on every run.
  3. To decide whether cached objects and ELF binaries are still valid or must be rebuilt.
  4. To remove the need for runtime arguments.

Answer: C. JIT caching is useful only if stale binaries are not reused after source, flags, defines, linker scripts, or compiler versions change. Hashing captures those dependencies.

JIT 4. What does a program binary status such as NotSent, InFlight, or Committed track?memory
  1. Whether model accuracy is high enough.
  2. Whether kernel binaries have not been sent, are being written, or are confirmed in device DRAM.
  3. Whether a source file was formatted with clang-format.
  4. Whether all tensors are stored in FP32.

Answer: B. Before device cores can execute a program, the runtime has to stage the needed binaries and command data. Tracking binary status prevents redundant writes and clarifies first-run overhead.

JIT 5. In fast dispatch, what do the prefetch and dispatcher kernels help do?deep
  1. They delete command queues after every operation.
  2. They process command pages on device, coordinate writes/synchronization, and trigger worker execution with go signals.
  3. They replace all compute kernels with host-side Python loops.
  4. They are only used for HTML rendering.

Answer: B. Fast dispatch is a runtime mechanism for low-latency submission. The point is to keep work moving through command queues and dispatch cores so worker cores spend less time waiting for host intervention.

Memory, core-grid, and GEMM

Q39. Which circular-buffer operation order best describes producer-consumer ownership?memory
  1. Producer reserves a slot, writes data, pushes it; consumer waits, reads data, then pops it.
  2. Consumer pops first, producer writes later, and both assume the slot is valid.
  3. Producer and consumer write the same slot at the same time.
  4. Circular buffers are only metadata and do not express ownership.

Answer: A. The important idea is ownership transfer. A producer should not overwrite a slot that has not been consumed, and a consumer should not read a slot that has not been produced.

Producer side:
  reserve_back()     // claim free space
  write tile data
  push_back()        // publish tile to consumer

Consumer side:
  wait_front()       // wait until tile exists
  read tile data
  pop_front()        // release slot back to producer
Q40. For double buffering with circular buffers, what does buffer depth buy you?memory
  1. It changes matrix multiplication into convolution.
  2. It lets movement and compute overlap by keeping one tile active while another tile is being filled or drained.
  3. It removes all synchronization from the program.
  4. It makes DRAM unnecessary.

Answer: B. A depth of two is the classic ping-pong case. Deeper buffers can smooth jitter and support longer pipelines, but each extra slot consumes scarce local memory.

Depth-2 circular buffer:

time 0: reader fills slot 0
time 1: compute consumes slot 0, reader fills slot 1
time 2: compute consumes slot 1, reader fills slot 0
time 3: compute consumes slot 0, reader fills slot 1

Good when:
  load_time <= compute_time

If load_time > compute_time:
  compute still stalls, even with ping-pong buffering
Q41. What makes an operator a good fit for a core-grid NPU mapping?core
  1. It has one scalar operation and no parallel work.
  2. It has enough regular parallelism, supported layout/dtype, local-memory fit, and low communication relative to compute.
  3. It requires dynamic branches for every element.
  4. It forces every tensor through host memory between layers.

Answer: B. Dense matmul, convolution, and elementwise chains often map well. Branchy, serial, or globally synchronized work may need a custom strategy, partial offload, or fallback.

Core-grid fit checklist:

1. Is the op supported by available kernels or intrinsics?
2. Does it expose enough tiles to fill the core grid?
3. Do active tiles fit each core's local memory?
4. Can input/output layouts be reused by neighboring ops?
5. Is communication small compared with useful compute?
6. Are dependencies ready, or will this op stall the graph?
Q42. For GEMM C[M,N] = A[M,K] x B[K,N], what are the basic compute and tile-memory calculations?deep
  1. Total MACs are M + N + K, and local memory does not matter.
  2. Total MACs are M*N*K; a tile needs storage for A[Mt,Kt], B[Kt,Nt], and C[Mt,Nt].
  3. Only the output matrix size matters.
  4. The K dimension is ignored after tiling.

Answer: B. GEMM is the cleanest place to practice NPU arithmetic. The K dimension is reduced across multiple K tiles, so partial sums for C must stay in accumulators or local memory until the full K loop completes.

Whole GEMM:
  A[M,K] x B[K,N] = C[M,N]
  MACs  = M * N * K
  FLOPs = 2 * M * N * K   // if multiply and add count separately

One tiled step:
  A_tile[Mt,Kt]
  B_tile[Kt,Nt]
  C_tile[Mt,Nt]

Single-buffer local bytes:
  bytes(A_tile) + bytes(B_tile) + bytes(C_tile)

Double-buffer A/B, keep one C accumulator:
  2 * (bytes(A_tile) + bytes(B_tile)) + bytes(C_tile)
Example:
  M=N=K=1024
  Mt=Nt=Kt=32
  input dtype = FP16, accumulator = FP32

One K tile:
  A_tile = 32*32*2 bytes = 2048
  B_tile = 32*32*2 bytes = 2048
  C_tile = 32*32*4 bytes = 4096

Single-buffer local memory = 8192 bytes
Double-buffer A/B          = 12288 bytes

One output tile over full K:
  MACs = 32*32*1024 = 1,048,576

Whole GEMM:
  MACs  = 1024*1024*1024 = 1.07B
  FLOPs = 2.15B

TTNN operation practice

Q43. In TTNN-style convolution, why can some 1x1 convolutions be treated like matmul?core
  1. Because convolution and matmul always have identical padding behavior.
  2. Because a 1x1, stride-1 convolution without padding or dilation reduces to a per-position channel projection.
  3. Because the input height and width are ignored.
  4. Because all convolution weights are scalar constants.

Answer: B. A 1x1 convolution mixes channels at each spatial position. The backend can view the activation volume as rows and weights as a matrix, so the operation becomes GEMM-like when stride, padding, and dilation do not require sliding-window halo logic.

Q44. Which TTNN convolution sharding choice is most natural for large spatial images?memory
  1. Height sharding, because work can be split across the N*H*W spatial dimension.
  2. Width sharding, because it always removes all communication.
  3. No sharding, because convolution cannot use multiple cores.
  4. Only batch sharding, because channels cannot be tiled.

Answer: A. Height sharding distributes the activation rows or spatial positions across cores. Width sharding is more channel-oriented and can be attractive for smaller images with many channels. Block sharding uses a 2D split when both dimensions can benefit.

Q45. Why does convolution weight preparation matter on tiled accelerators?memory
  1. It changes trained model accuracy by retraining the weights.
  2. It converts weights into hardware-friendly tiled and aligned layouts before execution.
  3. It removes the need for input activation tiles.
  4. It makes every convolution depthwise.

Answer: B. The mathematical weights are the same, but their physical layout can be transformed for tiled matrix engines, sharding schemes, alignment rules, and multicast/reuse patterns. Good preparation reduces runtime layout work and improves data reuse.

Q46. What extra transformation does transposed convolution usually require compared with ordinary convolution?deep
  1. It never uses weights.
  2. It often flips or prepares weights differently and expands the input geometry, such as inserting zeros for stride greater than one.
  3. It always runs only on the CPU.
  4. It converts the operation into softmax.

Answer: B. Transposed convolution is not just "normal convolution backward" in implementation terms. Backends must account for output-size formulas, output padding, transformed weights, and expanded spatial positions before mapping the work to convolution or matmul-like kernels.

Q47. What does a matmul program configuration primarily control in TTNN-style execution?core
  1. The Python package name used to import the operation.
  2. The core-grid strategy, sub-blocking, reuse/multicast behavior, and memory layout assumptions.
  3. The model's loss function.
  4. Whether matrix dimensions need to match.

Answer: B. Different matmul factories target different tradeoffs: basic multicore distribution, optimized reuse, 1D or 2D multicast, DRAM-sharded inputs, and batched/sharded cases. The compiler question is how to choose a program shape that fits both the tensor shape and the hardware grid.

Q48. In a linear layer, when can bias or activation fusion fall back to a separate operation?deep
  1. Never; bias and activation fusion are always legal.
  2. When the bias shape, batched weight/bias case, dtype, or selected program config cannot support the fused path.
  3. Only when the input tensor is all zeros.
  4. Only when the output is smaller than one tile.

Answer: B. A linear layer is usually matmul plus optional bias and activation. Fusing the epilogue saves memory traffic, but legality depends on layout, shape, and kernel support. A robust compiler keeps a correct unfused fallback such as matmul followed by add or activation.

Q49. What is the main idea of Flash Attention or streaming SDPA?memory
  1. Materialize the full attention score matrix in DRAM before softmax.
  2. Compute attention in chunks while maintaining online softmax state, avoiding the full S x S matrix.
  3. Replace all matmuls with scalar loops.
  4. Remove the value tensor from attention.

Answer: B. SDPA computes softmax(QK^T / sqrt(d))V. Streaming implementations keep running normalization statistics so the backend can process blocks of keys/values and reduce memory pressure instead of storing every attention score.

Q50. How do prefill and decode attention differ for LLM inference?deep
  1. Prefill handles one new token, while decode handles the whole prompt.
  2. Prefill processes the prompt sequence in larger parallel chunks; decode processes a new token against the growing KV cache.
  3. Decode never reads keys or values.
  4. Prefill cannot use parallel hardware.

Answer: B. Prefill has more sequence-level parallelism. Decode is latency-sensitive because each generated token depends on previous tokens, and the new query attends over cached keys and values. That is why decode paths often need careful KV-cache layout, paging, and reduction strategy.

Q51. Why is paged KV cache useful in transformer serving?memory
  1. It makes every sequence exactly the same length.
  2. It lets the runtime manage non-contiguous KV blocks, reducing wasted memory and supporting dynamic sequence growth.
  3. It removes the need for attention masks.
  4. It stores Q, K, and V only in CPU registers.

Answer: B. LLM serving has variable-length requests. Paging the KV cache makes allocation more flexible, but it adds address translation and block-table work that the attention kernel or runtime must handle efficiently.

Q52. What problem do ring or distributed attention variants try to solve?deep
  1. They make attention independent of sequence length.
  2. They partition attention work across devices or core groups and pass partial state so long sequences or large models can run.
  3. They replace softmax with convolution.
  4. They are only a logging feature.

Answer: B. Distributed attention must preserve the same mathematical result while splitting Q/K/V blocks across hardware. The hard part is carrying partial softmax state, reductions, and communication without losing numerical stability or stalling compute.

Collective communication

Q53. In collective communication, what does all_gather do?core
  1. Each device deletes its local shard.
  2. Each device gathers shards from all devices, so the gathered dimension grows by the number of participating devices.
  3. Only device 0 receives the result.
  4. It performs a local activation function.

Answer: B. If each device owns a shard along dimension dim, an all-gather builds a larger tensor containing every shard. This is common after tensor-parallel operations that need the full activation for the next stage.

Q54. How do reduce_scatter and all_reduce differ?deep
  1. reduce_scatter broadcasts full results to all devices, while all_reduce deletes them.
  2. reduce_scatter reduces across devices and leaves each device with one shard; all_reduce leaves every device with the full reduced result.
  3. They are identical names for matrix multiplication.
  4. Neither operation communicates between devices.

Answer: B. A common all-reduce implementation is reduce-scatter followed by all-gather. Reduce-scatter is useful when the next computation can stay sharded; all-reduce is useful when every device needs the complete reduced tensor.

Q55. Why does CCL topology matter for multi-device Tenstorrent execution?memory
  1. Topology only changes variable names.
  2. Ring, line, mesh, or fabric paths affect hop count, buffering, synchronization, and overlap with compute.
  3. Topology removes all need for tensor sharding.
  4. Topology is only relevant to CSS layout.

Answer: B. Collectives are data movement algorithms. On multi-chip systems, the route and active links determine how much buffering is needed, how reductions are staged, and whether communication can overlap with matmul, attention, or layernorm work.

Graph optimization

Q1. What is the main performance reason to fuse adjacent operators?core
  1. It always reduces the number of mathematical operations.
  2. It avoids writing and rereading intermediate tensors from slower memory.
  3. It removes the need for shape inference.
  4. It makes every operator legal for every accelerator.

Answer: B. Fusion usually keeps intermediate values in registers or local SRAM, reducing DRAM traffic and dispatch overhead. The math may be the same.

Q2. Which pattern is usually a strong fusion candidate on an NPU?core
  1. MatMul -> BiasAdd -> Requantize -> ReLU
  2. Sort -> NonMaximumSuppression -> TopK
  3. RandomUniform -> Dropout during inference
  4. A producer with many consumers and different layouts

Answer: A. Bias, requantization, and activation are common epilogue operations that can often run while the output tile is still local.

Q3. What is the difference between folding and fusion?core
  1. Folding is for CPUs; fusion is for NPUs.
  2. They are identical words for combining operators.
  3. Folding removes or absorbs work before runtime; fusion combines runtime work into one kernel or region.
  4. Folding requires quantization; fusion requires floating point.

Answer: C. Constant folding and batch-normalization folding can delete runtime work. Fusion still performs work at runtime but avoids materializing intermediates.

Q4. When should legal fusion still be rejected?deep
  1. Never; legal fusion is always faster.
  2. When the fused working set exceeds local memory or destroys reuse for other consumers.
  3. Only when the graph has no constants.
  4. Only when the model is already quantized.

Answer: B. Fusion is a cost-model decision. A larger fused region can increase SRAM pressure, cause spills, force worse tiling, or require recomputation for another branch.

Q5. In graph partitioning, what does an unsupported operator usually create?core
  1. A guarantee that the whole model cannot run.
  2. A boundary between accelerator regions and fallback regions.
  3. A reason to quantize every tensor to 4 bits.
  4. A way to remove all layout conversions.

Answer: B. A compiler/runtime can partition supported subgraphs to the accelerator and route unsupported pieces to another engine, paying careful attention to copies and layout conversion.

Q6. Why is a topological order required before many graph optimizations?core
  1. It sorts operators by tensor size.
  2. It proves all operators are elementwise.
  3. It gives an execution order where each producer appears before its consumers.
  4. It replaces memory planning.

Answer: C. Many passes need a valid dependency order for scheduling, liveness, memory reuse, and cycle detection.

Tiling and memory

Q7. What is the main reason to tile a large matrix multiplication?memory
  1. To change the mathematical result.
  2. To make active input, weight, and output blocks fit in fast local memory.
  3. To avoid accumulation.
  4. To remove the need for a scheduler.

Answer: B. Tiling increases locality. Good tiles fit the scratchpad, align with hardware tile shapes, and allow reuse before data returns to DRAM.

Q8. What does double buffering try to overlap?memory
  1. Two independent model graphs.
  2. Data transfer for the next tile with compute on the current tile.
  3. Two compiler frontends.
  4. Two different quantization schemes.

Answer: B. Ping-pong buffers let DMA fill one buffer while compute consumes the other, hiding transfer latency when transfer time is less than or close to compute time.

Q9. Which condition is most important for hiding DMA latency completely?memory
  1. The graph must have only one operator.
  2. Per-tile transfer time should not exceed per-tile compute time.
  3. The model must be FP32.
  4. All tensors must be stored interleaved.

Answer: B. If transfer takes longer than compute, the compute engine will eventually wait. Larger tiles, fusion, better layout, or more bandwidth may help.

Q10. What does liveness analysis tell a memory planner?memory
  1. The exact floating-point accuracy of every tensor.
  2. Which tensors are alive at the same time and therefore cannot reuse the same buffer.
  3. Which compiler frontend imported the model.
  4. Which activation function is fastest.

Answer: B. Non-overlapping live intervals can reuse storage. Overlapping intervals need distinct storage unless recomputation or in-place transformation is legal.

Q11. What is resharding?memory
  1. Compressing weights into INT8.
  2. Replacing an unsupported op with a CPU op.
  3. Converting a tensor from one physical shard layout to another.
  4. Removing unused graph nodes.

Answer: C. Resharding can be necessary when adjacent operators prefer different layouts, but it can be expensive because it moves data between cores or memory regions.

Q12. If tensor dimensions are not multiples of the hardware tile shape, what is a common backend strategy?memory
  1. Reject the model.
  2. Use padding, masks, or specialized edge kernels for ragged tiles.
  3. Change the model weights randomly.
  4. Disable all fusion in the graph.

Answer: B. Hardware often prefers fixed tile shapes. The compiler handles ragged edges through padding, masks, cleanup kernels, or a mix of those options.

IR, MLIR, and TVM

Q13. What is an MLIR dialect?core
  1. A CPU assembly language only.
  2. A namespace of operations, types, and attributes for a particular abstraction level or domain.
  3. A model accuracy metric.
  4. A runtime scheduler queue.

Answer: B. Dialects let multiple abstraction levels coexist, such as tensor operations, structured loops, vector operations, and target-specific operations.

Q14. Why use progressive lowering instead of lowering directly to a low-level IR?core
  1. It makes compilation slower on purpose.
  2. It preserves useful structure long enough to run the right optimization at the right level.
  3. It avoids the need for legality checks.
  4. It prevents code generation.

Answer: B. Fusion is easier while tensors and operators are visible. DMA insertion and memory-space decisions belong later, when buffers and target details are explicit.

Q15. In MLIR-style programs, what is the practical difference between tensor and memref?deep
  1. There is no difference.
  2. tensor is an immutable value; memref describes concrete mutable storage, layout, and memory space.
  3. tensor is only for CPUs; memref is only for GPUs.
  4. tensor stores source code tokens.

Answer: B. Bufferization converts value semantics into explicit storage, which is when SRAM, DRAM, in-place updates, and DMA become natural to represent.

Q16. What does TVM's compute/schedule separation mean?core
  1. The algorithm describes what to compute; the schedule describes how to map it to hardware.
  2. The compiler and runtime must be written in separate languages.
  3. Quantization must run before graph import.
  4. Every model must have a hand-written kernel.

Answer: A. The same matrix multiply definition can be scheduled differently for CPU caches, GPU threads, or NPU tiles and local memory.

Compute = what:
  C[i,j] = sum_k A[i,k] * B[k,j]

Schedule = how:
  split i,j,k into tiles
  reorder loops for reuse
  cache_read A/B into SRAM
  cache_write C partials
  tensorize inner loop to a hardware matmul primitive

Same math, different hardware mapping.
Q17. Which TVM schedule primitive maps an inner loop nest to a hardware intrinsic?deep
  1. split
  2. compute_at
  3. tensorize
  4. inline

Answer: C. Tensorization replaces a recognized loop pattern with a target intrinsic such as a fixed-shape matrix multiply instruction.

Q18. Why is LLVM IR often too low-level for early NPU optimizations?deep
  1. LLVM IR cannot represent integers.
  2. Tensor, layout, loop, and operator intent may already be lost as scalar loads, stores, and branches.
  3. LLVM IR has no control flow.
  4. LLVM IR can only target one processor.

Answer: B. NPU optimizations need high-level structure. Once lowered too far, recovering fusion legality, tiling structure, and memory hierarchy intent is difficult.

Quantization

Data type mental model

Lower precision is useful when the model is limited by memory bandwidth, cache capacity, interconnect traffic, or matrix-engine throughput. The tradeoff is always numerical: smaller formats save bytes and energy, but they reduce range, precision, or both.

TypeGood forMain caveat
FP32 / float32Reference accuracy, debugging, numerically sensitive reductions, calibration baselines.Large memory footprint and bandwidth cost; often overkill for inference.
BF16 / bfloat16Training or inference when wide dynamic range is helpful and tensor hardware supports it.Less mantissa precision than FP32, but similar exponent range.
BFLOAT8_B / block float 8Compact tiled tensor execution when values in a block have compatible magnitude.Block-floating formats share exponent information across a group, so small values can lose precision when mixed with large values.
BFLOAT4_B / block float 4More aggressive memory and bandwidth reduction for tolerant tensors, often weights or selected activations.Even less precision; format support can be operation- and layout-dependent.
INT8 / UINT8Production inference on hardware with integer dot-product support; common for conv, matmul, and edge deployment.Needs scale/zero-point handling, calibration or QAT, wider accumulators, and requantization.
INT4 / FP4-style formatsWeight compression and LLM memory-bandwidth reduction when accuracy survives.Usually needs group scales, mixed precision, or QAT; activation quantization is harder than weight-only quantization.
1-bit / binaryResearch or special hardware paths where weights and/or activations can be represented as signs.Not a drop-in replacement: accuracy, training method, hardware support, and operator coverage dominate.
Tenstorrent-specific note

TTNN exposes floating-point tensor formats such as FLOAT32, BFLOAT16, BFLOAT8_B, and BFLOAT4_B for many tiled tensor paths. Some APIs support integer dtypes for conversion or elementwise work, but high-performance matmul/linear dtype support should be checked per operation instead of assuming every low-bit format is accepted everywhere.

Qualcomm AIMET for ONNX

AIMET, sometimes mistyped as AMET, is Qualcomm's AI Model Efficiency Toolkit. For ONNX, a common post-training quantization path is: start with a clean ONNX model, simplify/check it, create an aimet_onnx.QuantizationSimModel, run representative calibration data through compute_encodings, evaluate the simulated quantized model, then export model/encoding files or produce an ONNX QDQ graph with to_onnx_qdq(). If the starting point is an AIMET Torch QuantizationSimModel, aimet_torch.onnx.export can export ONNX with QuantizeLinear and DequantizeLinear nodes embedded.

StepPurposePractical check
1. Baseline ONNXMake sure the floating-point ONNX model is valid before quantization.Run ONNX shape checks and compare baseline ONNX Runtime accuracy.
2. Build QuantSimWrap the graph with simulated quantizers, often using INT8 for weights and activations.Select a config that matches the intended backend, such as a default or Qualcomm HTP-oriented config when available.
3. CalibrateUse compute_encodings to observe representative inputs and derive quantization ranges.Use data that matches real deployment; too few or biased samples can create bad scales.
4. ValidateCompare floating-point and quantized outputs before deployment conversion.If accuracy drops, inspect sensitive layers, outliers, unsupported ops, and mixed-precision candidates.
5. ExportUse export() for model plus encodings or to_onnx_qdq() for QuantizeLinear/DequantizeLinear nodes embedded in ONNX.Confirm whether the downstream runtime/compiler expects QDQ nodes or separate encoding files.
// Pseudocode only; check the installed AIMET version for exact imports.
onnx_model = onnx.load("model.onnx")
sim = aimet_onnx.QuantizationSimModel(
    onnx_model,
    param_type="int8",
    activation_type="int8",
    quant_scheme="min_max",
    config_file="default"
)

sim.compute_encodings(calibration_inputs)
quantized_onnx = sim.to_onnx_qdq()
onnx.save(quantized_onnx, "model.int8.qdq.onnx")
Q19. What does affine INT8 quantization usually store for a tensor?core
  1. A scale and often a zero-point.
  2. A source-code line number.
  3. A topological sort index only.
  4. A branch predictor state.

Answer: A. A common mapping is q = round(x / scale) + zero_point. The scale and zero-point connect real values to integer values.

Q20. Which setup is common for INT8 convolution inference?core
  1. Per-channel activation scales and random weight scales.
  2. Per-tensor activation quantization and per-output-channel weight quantization.
  3. Only FP64 activations.
  4. No bias scaling.

Answer: B. Per-channel weight scales improve accuracy with manageable hardware cost. Per-channel activation scales are harder because a single accumulation would mix scales.

Q21. During quantized convolution, what type commonly holds the accumulated sum?core
  1. INT4
  2. INT8
  3. INT32
  4. A string

Answer: C. Many INT8 products are summed, so the accumulator needs a wider type. The result is later requantized back to the output precision.

Q22. What is requantization?core
  1. Changing a graph from a DAG to a cyclic graph.
  2. Rescaling an integer accumulator to the next layer's integer output format.
  3. Removing all batch dimensions.
  4. Converting source code to tokens.

Answer: B. A typical flow multiplies by a fixed-point scale ratio, shifts, rounds, clamps, and writes INT8 or another target format.

Q23. If PTQ causes a large accuracy drop, what is a good first debugging method?deep
  1. Immediately rewrite the whole runtime.
  2. Compare floating-point and quantized outputs layer by layer to locate the first major divergence.
  3. Remove all calibration data.
  4. Force every tensor to use the same scale.

Answer: B. Layer-wise comparison with metrics such as cosine similarity or signal-to-quantization-noise ratio localizes the damage before trying fixes.

Q24. When might QAT be preferred over PTQ?deep
  1. When there is no training data and no accuracy target.
  2. When calibration-only quantization cannot meet accuracy, especially at low bit widths or sensitive layers.
  3. When the model has only one constant.
  4. When all tensors are already exact integers.

Answer: B. Quantization-aware training simulates quantization during training so the model can adapt to rounding and clipping effects.

DType 1. Why is INT8 often a strong inference format?core
  1. It has exactly the same numerical range and precision as FP32.
  2. It reduces model and activation bytes, improves cache/bandwidth behavior, and can use efficient integer dot-product hardware.
  3. It removes the need for calibration.
  4. It guarantees no accuracy loss for every model.

Answer: B. INT8 is attractive because FP32 values use 4 bytes while INT8 values use 1 byte. That can reduce memory traffic and model size, and many CPUs, GPUs, DSPs, and NPUs have efficient INT8 dot-product paths. Accuracy depends on scale choice, calibration, operator coverage, and whether sensitive layers stay in higher precision.

DType 2. What is the key difference between INT8 quantization and block-floating formats such as BFLOAT8_B?deep
  1. There is no difference; both always use one global scale for the whole model.
  2. INT8 typically maps real values through scale/zero-point arithmetic; block-floating formats keep floating-like values but share exponent information across a block.
  3. Block-floating formats cannot be tiled.
  4. INT8 never needs accumulation.

Answer: B. INT8 quantization is usually explicit affine mapping plus integer accumulation and requantization. Block-floating types compress floating-point-like values by sharing exponent context across a group, which can be efficient but can lose small values when the block contains a very large value.

DType 3. Why can BFLOAT8_B or BFLOAT4_B be risky for high-variance values?memory
  1. Because they store every value as a string.
  2. Because a shared block exponent can be dominated by large-magnitude values, causing small values in the same block to lose precision or flush toward zero.
  3. Because they always use FP64 internally.
  4. Because they forbid tiled layout.

Answer: B. Block-floating formats are best when values grouped together have similar magnitude. If one value sets a very large shared exponent, neighboring small values have fewer effective bits and may become numerically useless for operations such as reciprocal, normalization, or softmax-adjacent math.

DType 4. Why does quantized matmul commonly accumulate into a wider type?core
  1. Because low-bit multiplication cannot produce overflow.
  2. Because many low-precision products are summed across K, and the sum needs more range than one product.
  3. Because the output must always be FP64.
  4. Because zero-points are illegal.

Answer: B. Even if inputs are INT8, a dot product sums many products. Wider accumulators such as INT32 preserve range during the reduction. The backend then requantizes, clamps, or casts the accumulator to the next tensor format.

DType 5. How can 1-bit neural networks still work at all?deep
  1. They secretly store every weight as FP32 at inference time.
  2. They constrain weights and/or activations to signs such as +1/-1, then rely on training methods, scaling, and bitwise-friendly computation.
  3. They remove nonlinearities and attention entirely.
  4. They are guaranteed to match full precision on all tasks.

Answer: B. Binary neural networks are a real research direction: the forward path can use signs and bit operations, while training usually needs tricks such as straight-through estimators or quantization-aware training. They are not automatically a production win unless the model, accuracy target, and hardware kernels are designed for binary execution.

DType 6. What is a good mixed-precision rule of thumb for compiler backends?deep
  1. Force every tensor to the smallest available type.
  2. Use low precision where error is tolerated and bandwidth dominates; keep sensitive reductions, normalization, logits, or outlier-heavy tensors in higher precision.
  3. Use FP32 only for model weights and 1-bit for everything else.
  4. Ignore operator support and insert casts later.

Answer: B. Real deployments are usually mixed precision. Good compilers reason about dtype legality, cast placement, fusion boundaries, calibration data, per-layer error, and whether the hardware has a fast path for that dtype.

AIMET 1. What is the correct public name of Qualcomm's AMET-like quantization toolkit?core
  1. AMET, the Automatic Matrix Engine Toolkit.
  2. AIMET, the AI Model Efficiency Toolkit.
  3. QDQ-Only Compiler.
  4. ONNX Runtime itself.

Answer: B. Qualcomm's public toolkit is AIMET. It provides quantization and compression workflows for trained models, including ONNX and PyTorch-oriented paths in current documentation.

AIMET 2. In AIMET-ONNX, what does compute_encodings primarily do?core
  1. Compile CUDA kernels from Python source.
  2. Run calibration data through the simulated quantized model to compute quantization ranges/encodings.
  3. Delete every QuantizeLinear node from the final model.
  4. Convert ONNX graphs into HTML.

Answer: B. Calibration observes representative tensors so the simulator can assign encodings such as ranges/scales for quantizers before accuracy evaluation and export.

AIMET 3. What does exporting an ONNX QDQ model usually mean?deep
  1. The graph contains QuantizeLinear and DequantizeLinear nodes to mark quantized boundaries.
  2. The graph has no visible quantization information.
  3. The model has been converted into a training-only checkpoint.
  4. All weights are stored as plain text.

Answer: A. QDQ form makes quantization explicit in the ONNX graph. Downstream tools can use those nodes to preserve intended quantization boundaries or lower to backend-specific integer execution.

AIMET 4. Why validate the floating-point ONNX model before AIMET quantization?deep
  1. Because quantization can fix every model conversion error automatically.
  2. Because calibration data is unnecessary if the baseline runs.
  3. It separates ONNX export/runtime problems from quantization-induced accuracy loss.
  4. Because AIMET only accepts models with no inputs.

Answer: C. If the FP32 ONNX baseline is already wrong, quantization debugging becomes confusing. Establish a correct baseline, then compare simulated quantized outputs against it.

AIMET 5. What should guide the AIMET quantization config choice?deep
  1. The alphabetic order of model layer names.
  2. The target runtime/compiler constraints, supported ops, precision rules, and deployment hardware.
  3. A requirement to quantize every tensor to 1 bit.
  4. The HTML page theme color.

Answer: B. A config should reflect what the target backend can actually run. For edge deployment, that can include backend-specific constraints such as supported activation/weight dtypes, per-channel rules, and which ops must remain higher precision.

AIMET 6. If quantization simulation starts in AIMET Torch, what is the ONNX export path for QDQ?deep
  1. Save the Python object directly as an ONNX graph.
  2. Use aimet_torch.onnx.export to export a model with QuantizeLinear and DequantizeLinear nodes embedded.
  3. Remove all fake-quantization operators and ignore encodings.
  4. Convert the model to CSV first.

Answer: B. AIMET's Torch ONNX export API is designed for exporting a QuantizationSimModel to ONNX with QDQ nodes in the graph, which gives downstream ONNX tooling explicit quantization boundaries.

Runtime and hardware

Q25. Why does program caching reduce inference latency after warmup?core
  1. It improves model accuracy.
  2. It avoids repeating program creation, compilation, or setup for shapes and kernels already seen.
  3. It makes DRAM as fast as SRAM.
  4. It removes all synchronization.

Answer: B. The first run may allocate buffers, compile or instantiate kernels, and fill caches. Steady-state latency should be measured after warmup.

Q26. What does a roofline-style analysis help classify?core
  1. Whether performance is mostly compute-bound or bandwidth-bound.
  2. Whether a graph has comments.
  3. Whether source code uses tabs.
  4. The exact number of frontend dialects.

Answer: A. Compare achieved operations per second, bytes moved, and hardware limits. The fix differs for compute utilization problems versus memory traffic problems.

Q27. A model misses its latency target by 2x. What is the best first step?core
  1. Change every tile size randomly.
  2. Profile per-layer timelines and classify the bottleneck before changing the compiler.
  3. Assume the hardware is defective.
  4. Disable all graph optimizations.

Answer: B. Measure first. Look for compute stalls, DMA stalls, layout conversions, fallback boundaries, dispatch overhead, or a small set of slow layers.

Q28. What is an async command queue useful for in accelerator runtimes?deep
  1. Converting all tensors to source code.
  2. Letting the host enqueue work while device-side scheduling and execution continue independently.
  3. Preventing overlapping execution.
  4. Making every operation blocking by default.

Answer: B. Async dispatch can reduce host overhead and enable overlap, but correctness depends on explicit dependencies and synchronization.

Q29. Why can detector post-processing be awkward on an NPU?deep
  1. It is always just one dense matrix multiply.
  2. Steps such as Top-K and NMS are branchy, data-dependent, and less regular than dense tensor kernels.
  3. It never uses memory.
  4. It cannot run on any processor.

Answer: B. Some accelerators handle dense math well but struggle with irregular control flow. Common strategies include candidate pruning, partial offload, custom kernels, or CPU fallback.

Q30. What is the safest way to report benchmark latency?core
  1. Only report the first run, including all one-time setup.
  2. Separate cold-start latency from warmed steady-state latency, and describe batch size, shape, precision, and hardware settings.
  3. Hide layout conversion costs.
  4. Report only theoretical peak TOPS.

Answer: B. Reproducible performance reporting needs context. Cold-start and steady-state numbers answer different questions.

Interview question bank

Open-ended practice questions for AI compiler, NPU backend, runtime, and ML systems interviews.

How to practice: Give a 60 to 120 second answer before opening each card. A strong answer usually has four parts: definition, why it matters, concrete example, and tradeoff.

Graph optimization and fusion

QB1. What is operator fusion, and why is it often the highest-impact NPU graph optimization?core

Answer: Operator fusion combines adjacent runtime operations into one kernel or scheduled region so intermediate tensors do not need to be written to DRAM and read back. For many inference workloads, memory traffic and dispatch overhead dominate arithmetic, so fusion can be a large latency win.

Example: Conv -> BatchNorm -> ReLU can become one Conv-like kernel with folded BatchNorm parameters and a fused activation epilogue.

Before:
DRAM -> Conv -> DRAM -> BN -> DRAM -> ReLU -> DRAM

After:
DRAM -> [Conv + folded BN + ReLU epilogue] -> DRAM

Tradeoff: Fusion increases the live working set. If the fused region no longer fits local memory, it can spill or force smaller tiles and become slower.

QB2. What is the difference between fusion and folding?core

Answer: Fusion combines runtime work. Folding removes or absorbs work before runtime. Constant folding precomputes subgraphs whose inputs are constants. Layer folding absorbs a simple operation into a neighboring operation, such as folding BatchNorm into Conv weights and bias.

Interview phrasing: Fusion keeps data local at runtime. Folding deletes or absorbs work before runtime. Both reduce data movement, but by different mechanisms.

BN(y) = gamma * (y - mean) / sqrt(var + eps) + beta
y = Conv(x) = W*x + b

W_folded = W * gamma / sqrt(var + eps)
b_folded = (b - mean) * gamma / sqrt(var + eps) + beta
QB3. Explain TVM or Relay-style fusion categories and how they guide NPU fusion rules.deep

Answer: A useful mental model is: injective elementwise operations fuse freely; reductions can absorb simple producers or consumers if the schedule is legal; complex-out-fusible operations such as convolution or matmul can fuse epilogues; opaque operations such as sort and NMS usually form fusion boundaries.

injective -> injective          usually fuse
reduction -> injective          maybe fuse epilogue
matmul/conv -> bias/requant     strong NPU candidate
sort/NMS/dynamic control flow    often stop fusion

Compiler angle: Fusion rules should be target-parameterized. They need legality checks, local-memory checks, layout checks, precision-boundary checks, and cost modeling.

QB4. When would you deliberately avoid fusion even when it is legal?deep

Answer: Avoid fusion when the fused region exceeds SRAM, forces worse tile shapes, breaks double buffering, uses incompatible layouts or engines, or harms reuse for another consumer. A producer with multiple consumers is especially tricky because fusion into one branch may force recomputation or materialization anyway.

Rule of thumb: Legal fusion is not automatically profitable fusion. The cost model should compare saved DRAM traffic and dispatch overhead against added SRAM pressure, recompute, and scheduling constraints.

Tiling and memory planning

QB5. Operands do not fit in SRAM. How do you choose tile sizes?memory

Answer: Tile the iteration space so active inputs, weights, and partial outputs fit in local memory. For matmul, choose Mt, Nt, and Kt so A_tile, B_tile, and C_tile fit the SRAM budget, with extra room for ping-pong buffers and alignment to hardware tile shapes.

C[M,N] += A[M,K] * B[K,N]

Required local memory roughly:
  A[Mt,Kt] + B[Kt,Nt] + C[Mt,Nt] + buffering overhead <= SRAM budget

Tradeoff: Larger tiles improve reuse and arithmetic intensity, but can reduce parallelism or exceed local memory. Smaller tiles fit easily, but may move too much data.

QB6. How does an NPU compiler hide DMA latency?memory

Answer: Use double buffering or software pipelining. While compute consumes tile i, DMA loads tile i+1 and possibly stores tile i-1. The compiler generates the prologue, steady-state loop, epilogue, and synchronization.

Time ---->

DMA load:   T0 | T1 | T2 | T3
Compute:      | T0 | T1 | T2 | T3
Store:           | T0 | T1 | T2

Condition: Transfer is hidden when per-tile DMA time is less than or equal to per-tile compute time. Otherwise the kernel is bandwidth-bound.

Double-buffer loop shape:

Prologue:
  load T0

Steady state:
  for tile i:
    load T(i+1) into the free buffer
    compute T(i) from the active buffer
    store T(i-1) if output is ready
    swap active/free buffers

Epilogue:
  compute final loaded tile
  store final outputs
QB6a. How do circular buffers implement producer-consumer synchronization?memory

Answer: A circular buffer is a bounded local queue. The producer owns free slots, reserves one, writes tile data, and publishes it. The consumer waits for a published slot, reads the tile, and releases the slot. The synchronization prevents overwrite-before-read and read-before-write hazards.

Producer kernel:
  reserve_back(cb, 1)
  ptr = get_write_ptr(cb)
  write tile to ptr
  push_back(cb, 1)

Compute kernel:
  wait_front(cb, 1)
  ptr = get_read_ptr(cb)
  consume tile from ptr
  pop_front(cb, 1)

Ownership:
  free slot  - producer may write
  full slot  - consumer may read
  popped     - slot returns to producer

Compiler angle: Buffer depth is a scheduling knob. Depth 1 is simple but often stalls. Depth 2 enables ping-pong overlap. Larger depths can absorb jitter but consume more SRAM.

QB6b. How do you decide whether double buffering is worth the local-memory cost?memory

Answer: Compare the saved stall time against the extra SRAM used by the second buffer. Double buffering is valuable when DMA and compute can overlap and the extra buffer does not force worse tile sizes, spills, or lower occupancy.

Useful overlap:
  load_time <= compute_time
  extra SRAM does not shrink tile too much
  synchronization overhead is small

May not help:
  load_time >> compute_time
  second buffer forces smaller tiles
  op is already compute-bound with no DMA stalls
  next op requires immediate layout conversion anyway

Interview answer: "I would inspect the timeline. If compute has DMA gaps, I try ping-pong buffers. If the timeline is already compute-bound, I spend effort on tile shape, tensorization, or arithmetic utilization instead."

QB7. Given tensor live ranges, how do you minimize peak memory?memory

Answer: Run liveness analysis over the scheduled graph. Tensors with non-overlapping lifetimes can reuse the same buffer. Tensors whose lifetimes overlap need separate buffers unless in-place update, aliasing, or recomputation is legal.

op index:  1  2  3  4  5  6
A live:    [===========]
B live:       [====]
C live:          [=========]
D live:                [====]

A and D can reuse storage.
A and C cannot reuse storage.

Compiler angle: Scheduling and memory planning interact. Reordering operations can shorten or lengthen live ranges, so a memory-aware scheduler can reduce peak SRAM.

QB8. What is the difference between DRAM, L1/SRAM, interleaved layout, and sharded layout?memory

Answer: DRAM versus L1/SRAM says where data lives. DRAM is large and slower; L1/SRAM is small and fast near the compute core. Interleaved versus sharded says how data is distributed. Interleaved spreads pages in a general-purpose way; sharded splits a tensor across cores or local memories so each core owns a slice.

Practical answer: Keep hot tiles and fused intermediates in L1/SRAM when possible. Use sharding when it improves locality and parallelism. Avoid unnecessary resharding because it costs movement and synchronization.

MLIR, TVM, and compiler internals

QB9. What is an MLIR dialect, and what is progressive lowering?core

Answer: A dialect is a namespace of operations, types, and attributes for one abstraction level or domain. Progressive lowering means moving gradually from high-level model semantics to lower-level loops, buffers, memory spaces, and target operations.

PyTorch / ONNX / StableHLO
  -> tensor or linalg-style IR
  -> bufferization to memref
  -> affine / scf / vector loops
  -> target NPU dialect
  -> runtime packet, binary, or host launch

Key idea: Run each optimization where the right information is still visible. Fuse while operators are visible; insert DMA when buffers and memory spaces are visible.

QB10. Explain tensor versus memref and what bufferization does.deep

Answer: A tensor is an immutable SSA value with no concrete storage location. It is ideal for algebraic rewrites because the compiler can treat values functionally. A memref is a concrete mutable buffer reference with shape, layout, strides, and memory-space information. Bufferization converts tensor programs into buffer programs, choosing where copies are required and where buffers can be reused in place.

Destination-passing style: Many structured ops expose an output or destination operand. Bufferization can use that destination as the physical storage for the result when there is no read-after-write conflict. If overwriting would destroy a value still needed later, the compiler inserts a copy.

Before bufferization: value semantics

  %y = linalg.matmul %a, %b : tensor<...>
  %z = linalg.relu %y       : tensor<...>

No concrete storage is promised yet.
%y and %z are immutable SSA values.

After bufferization: concrete storage

  %A = memref<..., space=DRAM>
  %B = memref<..., space=DRAM>
  %Y = memref<..., space=SRAM>

  dma DRAM -> SRAM for A/B tiles
  matmul into %Y
  relu in-place on %Y if legal
  dma SRAM -> DRAM for final output

tensor = value semantics
memref = concrete storage + layout + memory space

NPU angle: After bufferization, the compiler can attach DRAM or SRAM memory spaces, insert DMA, plan allocation, reason about in-place updates, and decide whether an intermediate can stay local across fused operations.

Why bufferization matters for an NPU:

High-level tensor IR:
  good for fusion, folding, shape reasoning, algebraic rewrites

Memref / buffer IR:
  good for local memory allocation
  good for DRAM vs SRAM placement
  good for DMA insertion
  good for alias and in-place-update decisions

Too early:
  memory details can block useful tensor-level rewrites

Too late:
  the compiler may miss chances to plan SRAM and data movement
QB11. How does MLIR pattern rewriting work, and why not lower directly to LLVM for an NPU?deep

Answer: Pattern rewriting matches IR patterns and replaces them with canonical, optimized, or lower-level forms. A pass can mark operations legal or illegal and apply rewrite patterns to reach a target dialect. Lowering directly to LLVM is usually too early for NPU work because tensor, loop, layout, and memory-hierarchy information may be lost as scalar loads and stores.

Example: transpose(transpose(x)) can canonicalize to x. A target pass might rewrite a tiled matmul loop into an NPU matrix intrinsic.

QB12. Explain TVM's compute/schedule separation.core

Answer: TVM inherits the Halide-style split between compute and schedule. The compute definition says what tensor expression is being computed. The schedule says how to execute that same math on a target. This lets one algorithm retarget to CPU caches, GPU threads, or NPU tiles without rewriting the mathematical definition.

Key schedule primitives: split and reorder for tiling and loop permutation; fuse for combining loops; vectorize, unroll, and parallel for hardware parallelism; cache_read and cache_write for staging data into faster memory; compute_at for producer-consumer locality; and tensorize for mapping an inner loop nest to a hardware intrinsic.

Compute:
  C[i,j] = sum_k A[i,k] * B[k,j]

Schedule:
  tile loops
  cache A and B into SRAM
  reorder for reuse
  tensorize inner tile to hardware matmul
TVM split, more explicitly:

Compute = what
  for i, j:
    C[i,j] = sum_k A[i,k] * B[k,j]

Schedule = how
  split i into outer_i, inner_i
  split j into outer_j, inner_j
  split k into outer_k, inner_k
  reorder loops to maximize reuse
  cache_read A and B into local memory
  cache_write C partial sums
  tensorize inner_i/inner_j/inner_k to an NPU matmul primitive

Same math:
  CPU schedule  - cache blocking + SIMD + threads
  GPU schedule  - blocks + warps + shared memory
  NPU schedule  - tiles + SRAM + DMA + hardware matmul

NPU angle: The schedule is where hardware mapping becomes explicit. It chooses tile sizes, loop order, local-memory staging, double buffering, tensorization, and sometimes sharding. This is why compute/schedule separation is a useful mental model for NPU compiler backends.

QB13. Compare AutoTVM, Ansor, and MetaSchedule. Where does tensorize fit?deep

Answer: AutoTVM tunes parameters inside hand-written schedule templates. Ansor generates schedule sketches and searches them with a learned cost model. MetaSchedule generalizes scheduling around TensorIR traces and composable rules. tensorize maps an inner loop pattern to a declared hardware intrinsic, which is critical for fixed-function NPU tiles.

Tradeoff: Search-based scheduling can be expensive. Product compilers often combine analytical constraints, templates, profiling, and cached tuning results.

Quantization

QB14. Explain symmetric versus asymmetric and per-tensor versus per-channel quantization.core

Answer: Symmetric quantization maps real zero to integer zero using scale only. Asymmetric quantization adds a zero-point. Per-tensor quantization uses one scale for the whole tensor. Per-channel quantization uses separate scales, commonly per output channel for weights.

Common recipe: Asymmetric per-tensor activations plus symmetric per-channel weights. This balances accuracy and hardware simplicity.

QB15. Walk through quantized convolution including requantization.deep

Answer: INT8 activations and INT8 weights multiply and accumulate into INT32. Bias is stored or converted using the product of activation scale and weight scale. The INT32 accumulator is then requantized to the output integer type using a fixed-point multiplier, shift, rounding, saturation, and output zero-point.

int8 activation * int8 weight
        |
        v
int32 accumulator + int32 bias
        |
        v
fixed-point rescale + clamp
        |
        v
int8 output
QB16. A quantized model loses accuracy. How do you debug it?deep

Answer: Compare floating-point and quantized outputs layer by layer to find the first major divergence. Inspect activation ranges, calibration data, clipping, outlier channels, depthwise convolutions, concat boundaries, layernorm, softmax, and GELU. Try better calibration, per-channel weights, bias correction, cross-layer equalization, mixed precision, or QAT if PTQ plateaus.

Hardware mapping and runtime

QB17. Compare weight-stationary, input-stationary, and output-stationary dataflows.core

Answer: Weight-stationary keeps weights local and streams activations. Input-stationary keeps input activations local and streams weights. Output-stationary keeps partial sums local until reduction completes. The best choice depends on reuse, local memory capacity, bandwidth, and accumulator pressure.

Compiler angle: Dataflow choice drives tile shapes, loop order, buffer allocation, and which operand is reused most aggressively.

QB17a. How do you select which ready operators or tiles should run on a core grid?core

Answer: Start with correctness: only schedule operators whose dependencies are ready. Then rank ready work by grid fit, locality, memory pressure, and downstream layout reuse. Dense regular work with many independent tiles can fill the grid; small serial work may waste cores or become a synchronization bottleneck.

Ready set from graph scheduler:
  ready = {op | all producers have completed}

Core-grid placement score:
  enough parallel tiles?
  each tile fits local SRAM?
  supported dtype/layout/kernel?
  output layout useful for consumers?
  communication small compared with compute?
  balanced work across cores?
  deterministic tie-breaker for reproducibility?

Example: A large GEMM with hundreds of output tiles is a good grid candidate. A final scalar threshold, a tiny reshape, or branch-heavy NMS may run on fewer cores, on a vector side path, as a custom kernel, or as final fallback.

QB17b. How do you map a GEMM tile to a core grid and estimate waves?deep

Answer: Split the output matrix C[M,N] into Mt x Nt output tiles. Each output tile needs all K blocks, so each core assigned a C tile loops over Kt chunks and accumulates partial sums. If the number of C tiles exceeds the number of cores, execution proceeds in waves.

GEMM:
  A[M,K] x B[K,N] = C[M,N]

Output tile grid:
  tiles_M = ceil(M / Mt)
  tiles_N = ceil(N / Nt)
  output_tiles = tiles_M * tiles_N

Core grid:
  cores = grid_rows * grid_cols
  waves = ceil(output_tiles / cores)
Example:
  M=N=K=1024
  Mt=Nt=32
  core grid = 8 x 8 = 64 cores

Output tiles:
  tiles_M = 1024/32 = 32
  tiles_N = 1024/32 = 32
  output_tiles = 1024

Waves:
  waves = 1024 / 64 = 16

Each output tile:
  K loop count = 1024/32 = 32 K-blocks
  MACs = 32*32*1024 = 1,048,576

Compiler angle: The scheduler also needs to choose output sharding, K-block order, local-memory allocation, double buffering for A/B tiles, and whether C partial sums stay in registers, accumulator memory, or SRAM.

QB17c. What are the basic GEMM memory and arithmetic calculations for an NPU tile?deep

Answer: For C[M,N] = A[M,K] x B[K,N], total MACs are M*N*K. If multiply and add are counted separately, FLOPs are 2*M*N*K. A local step needs an A tile, a B tile, and a C partial-sum tile. With double buffering, A and B often need two slots each, while C stays resident until the K reduction completes.

Tile memory:
  A_tile bytes = Mt * Kt * bytes(A)
  B_tile bytes = Kt * Nt * bytes(B)
  C_tile bytes = Mt * Nt * bytes(accumulator)

Single-buffer:
  A_tile + B_tile + C_tile

Double-buffer A and B:
  2 * (A_tile + B_tile) + C_tile
Concrete example:
  Mt=Nt=Kt=32
  A/B = FP16 = 2 bytes
  accumulator C = FP32 = 4 bytes

  A_tile = 32*32*2 = 2048 bytes
  B_tile = 32*32*2 = 2048 bytes
  C_tile = 32*32*4 = 4096 bytes

  single-buffer = 8192 bytes
  double-buffer A/B = 12288 bytes

Question to ask:
  Does 12 KB fit per core after circular buffers,
  runtime args, alignment, and other intermediates?
QB18. How do you map a 2D convolution onto a matmul engine?deep

Answer: One method is im2col: lower sliding windows into a matrix and use GEMM. It reuses optimized matmul hardware but may materialize a large expanded matrix. Another method is direct convolution lowering: tile the convolution loops and feed the matmul engine without fully materializing im2col.

Tradeoff: im2col is simple and matmul-friendly. Direct lowering saves memory but needs more specialized scheduling and indexing.

QB19. How does a backend select hardware intrinsics and handle shapes that do not match hardware granularity?deep

Answer: The backend matches loop patterns or tensor operations to hardware intrinsics with required tile sizes, layouts, dtypes, and alignment. If shapes do not match, it can pad, mask, split into main and cleanup tiles, or choose a fallback kernel. Cost modeling decides whether padding waste is acceptable.

QB20. An NPU supports only part of a model. How should compiler and runtime execute the whole graph?core

Answer: Partition the graph into supported accelerator subgraphs and fallback subgraphs for CPU, GPU, or another engine. Insert layout conversions and synchronization only at boundaries. Try to keep unsupported operators at the edges or group them to avoid repeated device crossings.

Risk: A graph that is 80 percent supported by operator count can still be slow if the unsupported 20 percent sits between many accelerator regions and forces repeated copies.

QB21. Design a runtime for CPU + GPU + NPU execution with shared memory.deep

Answer: Represent execution as a dependency graph over command queues. Each engine has its own queue. The runtime tracks buffer ownership, memory visibility, events, and synchronization. It should support async enqueue, shape-specialized program caches, profiling, fallback paths, and deterministic error handling.

Key concern: Shared memory does not remove synchronization. The runtime still needs ordering, cache coherency rules, and explicit waits where engines exchange data.

QB22. A model misses its latency target by 2x. How do you triage?core

Answer: Profile first. Classify the bottleneck as compute-bound, bandwidth-bound, dispatch-bound, fallback-bound, layout-conversion-bound, or synchronization-bound. Compare achieved performance with hardware rooflines. Inspect worst layers, generated kernels, tile shapes, memory movement, and fallback boundaries.

Fix examples: Fusion and layout planning for memory traffic, better tiling for compute utilization, program cache or command-queue replay for dispatch overhead, and custom kernels for expensive fallback operators.

QB22a. Explain program cache, warmup, and steady-state latency across accelerator runtimes.core

Answer: Many runtimes cache prepared execution artifacts: device programs, engines, timing choices, JIT kernels, captured launch graphs, or compiled model blobs. Warmup populates these caches and allocations. Steady-state latency measures repeated execution after setup costs are mostly paid.

Examples: Tenstorrent program cache, TensorRT engines and timing cache, CUDA Graph replay, OpenVINO model cache, and ONNX Runtime provider engine caches all express the same broad idea.

QB22b. What are fast dispatch and prefetch in a command-queue accelerator runtime?deep

Answer: Fast dispatch reduces host-side launch overhead by letting the host enqueue work while device-side dispatch logic drives execution. Prefetch stages command or data payloads so the dispatcher can keep kernels fed. The goal is fewer gaps between device work items and better overlap between host, dispatch, DMA, and compute.

Large inputs, custom ops, and post-processing

QB23. Large multi-camera plus LiDAR input is too big. How do you make it NPU-friendly?deep

Answer: Avoid materializing huge intermediates. Process per-camera or per-spatial tile, shard the BEV or feature grid across cores, fuse preprocessing where possible, and use streaming pipelines. Keep each core's active tile in local memory and synchronize only where cross-view fusion or global reduction requires it.

Compiler angle: This is a layout and scheduling problem as much as a model problem. The key is bounding working sets and minimizing large-format layout conversions.

QB24. In PointPillars-style workloads, why can large voxel or pillar inputs be hard for an NPU?deep

Answer: Pillar workloads can be sparse, irregular, and shape-variable. Dense matrix engines like regular tiles, but sparse point clouds create gather/scatter, masking, sorting, and dynamic-count problems. A deployment-friendly path often bounds the number of pillars and points per pillar, pads to fixed shapes, and separates irregular preprocessing from dense backbone execution.

QB25. How would you compile or support deformable convolution on an NPU?deep

Answer: First check whether the target has a custom DeformConv operator. If not, lower it to tiled loops: compute shifted sampling coordinates, handle boundaries, bilinear-sample four neighbors, optionally apply modulation masks, multiply by weights, and reduce. Keep offset and mask tiles local and avoid materializing a huge im2col buffer.

Tradeoff: DeformConv is irregular compared with normal convolution. It may need a custom kernel, a GPU/CPU fallback, or a model rewrite if latency is critical.

QB26. How do you make Top-K and NMS work well for detector post-processing?core

Answer: Reduce candidates before NMS. Decode boxes and scores, apply a confidence threshold, run parallel local Top-K per shard, merge to a bounded global Top-K, then run NMS on the smaller set. Use fixed-size outputs with a valid-count or mask.

NPU angle: Top-K can use partial selection or compare networks. NMS is branchy and data-dependent, so it may be a custom kernel, vector-engine kernel, or final CPU fallback. Avoid CPU fallback in the middle of an otherwise accelerated graph.

Coding drills

These are practical graph, tiling, and liveness drills for whiteboard-style practice.

Drill 1. Topological sort of a compute graphcore

Prompt: Given n operations and adjacency list adj[u] containing consumers of u, return a valid execution order. Return an empty list if the graph has a cycle.

vector<int> topoOrder(int n, const vector<vector<int>>& adj) {
    vector<int> indeg(n, 0), order;
    for (const auto& nexts : adj)
        for (int v : nexts) ++indeg[v];

    queue<int> ready;
    for (int i = 0; i < n; ++i)
        if (indeg[i] == 0) ready.push(i);

    while (!ready.empty()) {
        int u = ready.front();
        ready.pop();
        order.push_back(u);
        for (int v : adj[u])
            if (--indeg[v] == 0) ready.push(v);
    }

    return order.size() == static_cast<size_t>(n) ? order : vector<int>{};
}

Complexity: O(V + E). Explain: Kahn's algorithm repeatedly schedules ready nodes whose dependencies are done. Fewer than n emitted nodes means a cycle.

Drill 2. Group simple fusable chainscore

Prompt: Fuse u into v when both are elementwise and u has exactly one consumer. Return groups.

struct DSU {
    vector<int> p;
    DSU(int n) : p(n) { iota(p.begin(), p.end(), 0); }
    int find(int x) { return p[x] == x ? x : p[x] = find(p[x]); }
    void unite(int a, int b) { p[find(a)] = find(b); }
};

vector<vector<int>> fuseChains(
    int n,
    const vector<pair<int, int>>& edges,
    const vector<bool>& isElementwise
) {
    vector<int> outdeg(n, 0);
    for (auto [u, v] : edges) ++outdeg[u];

    DSU dsu(n);
    for (auto [u, v] : edges) {
        if (isElementwise[u] && isElementwise[v] && outdeg[u] == 1) {
            dsu.unite(u, v);
        }
    }

    map<int, vector<int>> grouped;
    for (int i = 0; i < n; ++i) grouped[dsu.find(i)].push_back(i);

    vector<vector<int>> result;
    for (auto& [root, members] : grouped) result.push_back(members);
    return result;
}

Explain: The single-consumer check avoids accidentally forcing recomputation or materialization for branches. A real compiler also checks memory capacity, layout, precision, and kernel support.

Drill 3. Cover a 2D tensor with ragged edge tilesmemory

Prompt: Cover an H x W tensor using tile size TH x TW. Emit each tile origin and clamped size.

struct Tile {
    int row;
    int col;
    int height;
    int width;
};

vector<Tile> tileGrid(int H, int W, int TH, int TW) {
    vector<Tile> tiles;
    for (int r = 0; r < H; r += TH) {
        for (int c = 0; c < W; c += TW) {
            tiles.push_back({r, c, min(TH, H - r), min(TW, W - c)});
        }
    }
    return tiles;
}

Explain: The edge tile is often smaller than the preferred hardware tile. Production backends may clamp, pad, mask, or use cleanup kernels.

Drill 4. Peak live tensors by sweep linememory

Prompt: Each tensor is alive across inclusive interval [start, end]. Return the maximum number alive at once.

int peakLive(const vector<pair<int, int>>& intervals) {
    vector<pair<int, int>> events;
    for (auto [start, end] : intervals) {
        events.push_back({start, +1});
        events.push_back({end + 1, -1});
    }

    sort(events.begin(), events.end());

    int current = 0;
    int peak = 0;
    for (auto [time, delta] : events) {
        current += delta;
        peak = max(peak, current);
    }
    return peak;
}

Complexity: O(k log k). Explain: Weight each event by tensor bytes instead of 1 to estimate peak memory pressure. Scheduling order changes live intervals, so scheduling and memory planning interact.

Pattern picker
  • Order respecting dependencies: topological sort.
  • Group or merge connected items: union-find or graph traversal.
  • Peak overlap or active intervals: sweep line.
  • Tiles, blocks, or windows: nested loops with clamped edges.
  • Shortest unweighted path: BFS.
  • Reachability or all paths: DFS/backtracking.

Resources

Reading links for the main topics in this page. These links open external documentation, papers, or repositories when clicked.

Tenstorrent TT-Metalium: TT-Metalium documentation for the programming model, kernels, memory movement, circular buffers, and tiled execution concepts.
Tenstorrent code: tt-metal GitHub repository for examples, APIs, runtime code, and issue discussions around TT-Metalium.
Tenstorrent software stack: Software stack overview for the official relationship among TT-Forge, TT-NN, and TT-Metalium.
TT-Forge: TT-Forge documentation for the current MLIR-based compiler stack, frontends, TT-MLIR, and model bring-up path.
TT-MLIR: TT-MLIR documentation for Tenstorrent's MLIR-based compiler infrastructure and backend concepts.
TT-NN: TT-NN documentation for high-level tensor creation, layouts, neural-network operations, profiling, and model-porting examples.
TTNN data types: Tensor data type notes, to_dtype, and linear dtype support for checking layout and dtype legality per operation.
TT-Lang: TT-Lang documentation for operation-level tutorials, dataflow buffers, functional simulation, and kernel-language concepts.
TT-BUDA: Archived TT-BUDA repository for historical context on the previous high-level compiler stack and PyBuda terminology.
DeepWiki tt-metal map: tt-metal DeepWiki overview for a searchable map of TT-Metalium, TTNN, operation implementations, tests, and repository structure.
DeepWiki kernel build path: JIT build system, Program and Kernel System, Fast Dispatch, and main linker script for studying kernel compilation, ELF linking, binary staging, and dispatch.
DeepWiki TTNN ops: Convolution, matmul/linear, attention/transformer, and collective communication pages used as a code-index reading path.
MLIR: MLIR documentation for dialects, operations, passes, lowering, and compiler infrastructure patterns.
MLIR tutorial: Toy tutorial for a hands-on walk through building and lowering a small language with MLIR.
Apache TVM: TVM documentation for tensor IR, scheduling, compilation flows, Relay, TensorIR, and deployment ideas.
ONNX Runtime: ONNX Runtime documentation for execution providers, graph optimizations, quantization, and model deployment.
INT8 quantization: ONNX Runtime quantization guide and Google AI Edge post-training quantization for INT8 workflows, calibration, QDQ, and deployment tradeoffs.
Qualcomm AIMET: Qualcomm AIMET overview, AIMET GitHub repository, AIMET-ONNX QuantSim API, and AIMET Torch ONNX QDQ export for Qualcomm-backed quantization workflows.
Binary neural networks: Binarized Neural Networks for the classic 1-bit weights/activations idea and why binary inference needs special training and hardware-aware kernels.
Runtime performance: ONNX Runtime performance guide for profiling, threading, execution providers, graph optimization levels, and tuning knobs.
CUDA and GPU: CUDA C Programming Guide for GPU execution hierarchy, memory spaces, synchronization, and performance mental models.
NPU on Windows: Microsoft NPU device guidance for understanding how NPUs fit into AI PC application deployment.
OpenVINO NPU: OpenVINO NPU device documentation for inference-device configuration, precision support, and practical NPU deployment notes.
Intel Movidius VPU: Intel Movidius VPU page for a vendor example of low-power vision acceleration hardware.
Groq LPU: Groq LPU architecture overview for reading about an inference system designed around predictable language-model execution.
Transformer paper: Attention Is All You Need for the original Transformer architecture: self-attention, multi-head attention, positional encoding, and encoder-decoder structure.
Transformer practice: Hugging Face Transformers documentation for model families, tokenizer/model usage, pipelines, and common LLM workflow vocabulary.

Glossary

NPU: Neural Processing Unit, an accelerator for tensor-heavy inference or training workloads.
IR: Intermediate Representation, the compiler's internal form of a program or model.
DAG: Directed Acyclic Graph. Model compute graphs are usually expected to be DAGs for scheduling.
SRAM/L1: Small fast local memory near compute units; much smaller than DRAM.
DMA: Direct Memory Access, hardware-assisted data movement between memory regions.
Fusion: Combining adjacent runtime operations to reduce intermediate memory traffic.
Folding: Removing or absorbing work before runtime, such as constant folding.
QDQ: Quantize/Dequantize nodes that mark quantized boundaries in a graph.
PTQ: Post-Training Quantization using calibration without retraining.
QAT: Quantization-Aware Training, where training simulates quantization effects.
AIMET: Qualcomm AI Model Efficiency Toolkit for model compression, quantization simulation, calibration, and export workflows.
FP32: 32-bit floating point; useful as a reference format and for numerically sensitive work, but expensive in memory bandwidth.
BF16: 16-bit brain floating point; keeps a wide exponent range with fewer mantissa bits than FP32.
BFLOAT8_B: Tenstorrent block-floating 8-bit-style format where values in a block share exponent context.
BFLOAT4_B: More compact Tenstorrent block-floating format with lower precision and operation/layout-dependent support.
INT8: 8-bit integer quantized format, usually paired with scale and zero-point metadata and wider accumulation.
Binary neural network: Network style that constrains weights and/or activations to signs such as +1/-1 for bitwise-friendly inference.
NMS: Non-Maximum Suppression, a detector post-processing step that removes overlapping boxes.
Warmup: Initial runs used to populate caches and allocate resources before steady-state timing.
Tenstorrent: Public NPU hardware/software ecosystem useful for studying tiled execution, explicit data movement, and compiler/runtime boundaries.
TT-Metalium: Low-level programming model centered on host programs, device kernels, circular buffers, and explicit memory movement.
SFPI toolchain: Tenstorrent's specialized RISC-V compiler/toolchain path used by the JIT build system for device kernels and SFPU-oriented code.
Linker script: Low-level build file that places text, data, stack, debug, and metadata sections into target device memory regions when producing ELF binaries.
Program binary status: Runtime state for whether a compiled kernel binary has not been sent, is in flight, or is committed in device memory.
Tensix: Tenstorrent compute-core concept often discussed as a grid of local-memory compute nodes.
TT-Forge: Current Tenstorrent MLIR-based compiler stack for bringing high-level models toward Tenstorrent hardware execution.
TT-NN: Higher-level Python and C++ neural-network operation library built on TT-Metalium.
TT-MLIR: MLIR-based compiler infrastructure used for Tenstorrent hardware lowering and backend work.
TT-Lang: Operation-level language and tooling for expressing Tenstorrent-style computation and dataflow concepts.
TT-BUDA: Legacy Tenstorrent high-level compiler stack; useful for historical terminology, but archived in favor of TT-Forge.
SDPA: Scaled Dot-Product Attention, usually computed as softmax(QK^T / sqrt(d))V.
KV cache: Stored key and value tensors from previous tokens, reused during autoregressive decode.
CCL: Collective communication library or layer for multi-device tensor movement and reductions.
All-gather: A collective where every device gathers shards from all participating devices.
Reduce-scatter: A collective where devices reduce data across peers and each device keeps a shard of the reduced result.
All-reduce: A collective where devices reduce data across peers and every device receives the full reduced result.
Circular buffer: Local FIFO-style producer-consumer buffer used to overlap data movement and compute.
Double buffering: Ping-pong buffering where one tile is consumed while another tile is loaded or stored.
Core grid: A 2D set of accelerator cores; compilers map tiles or shards onto the grid and execute them in waves if work exceeds core count.
GEMM: General matrix multiply, usually C[M,N] = A[M,K] x B[K,N]; total MACs are M*N*K.