NCA-ADS domain - 12% of the exam

Foundations of Accelerated Data Science

Foundations of Accelerated Data Science is 12% of the NCA-ADS exam. These are the objectives it covers, each with practice questions, with every answer explained.

The domain in numbers

40
Practice questions
5
Objectives
8
Easy
24
Medium
8
Hard

Where people go wrong

  • Inventing a cause for cuDF's small-table slowdown

    The wrong answers explain a slow cuDF run on a tiny table by inventing a cause: an automatic switch to the CPU below a row threshold, memory spilling, or missing group-by support. None of these happens. cuDF runs on the GPU at any size, and fixed costs such as kernel launch and the host-device copy dominate when there is little work to parallelise. The GPU pulls ahead once the data is large enough for parallel work to outweigh those costs.

    See it in a sample question
  • Treating a CPU library step as free in a GPU pipeline

    The wrong answers route a GPU-resident DataFrame through pandas, NumPy, disk, or joblib and call the result harmless, or even a fix for transfer cost. Each of those runs on host memory, so the data is copied off the device and must be copied back for the next cuDF, cuML, or XGBoost stage. Keeping data resident means passing the cuDF DataFrame directly between libraries, for example into cuML or into a DMatrix through the CUDA array interface.

    See it in a sample question
  • Reversing CPU and GPU workload placement

    The wrong answers swap the two processors, or put everything on one. They give branch-heavy, sequential, or low-volume latency-bound work such as a parser or decision-tree traversal to the GPU, and uniform element-wise arithmetic or dense linear algebra to the CPU. GPU threads run in warps, where divergent branches serialise, so the GPU suits one operation across many independent elements. The CPU suits strong single-thread, low-latency, branching work.

    See it in a sample question
  • Expecting dask_cudf to execute eagerly

    The wrong answers treat dask_cudf like plain cuDF. They expect chained transformations to run at once, or blame a silent fallback or host-resident data when no GPU work appears. dask_cudf builds a lazy task graph over partitions and runs it only when compute, persist, or head is called. That is why each consumer of an unpersisted result re-triggers the whole graph, and why persisting once avoids the recomputation.

    See it in a sample question
  • Assuming a sort or more partitions removes the shuffle

    The wrong answers treat the cross-worker traffic in a distributed group-by or join as avoidable by pre-sorting, adding partitions, or computing on one client. Rows sharing a key sit in different partitions, so they must be co-located by key, and a distributed sort is itself a full shuffle. When one side of a join is small, broadcasting it to every worker lets each large partition join locally, so the large table never crosses the interconnect.

    See it in a sample question

Objectives in this domain

Sample questions from this domain

Free sampleFoundations of Accelerated Data Sciencemedium

A data scientist loads a 500-row CSV into a cuDF DataFrame, applies a single group-by aggregation, and finds the operation is slower than the equivalent pandas operation. What is the most likely explanation?

  • AcuDF does not support group-by aggregations on small DataFrames and falls back silently to a CPU-based path.
  • BGroup-by aggregations require sorting as a prerequisite, and sorting is a serial algorithm that GPUs cannot accelerate compared to a modern CPU core.
  • CThe GPU's clock speed is lower than a modern CPU's single-core clock speed, so any single-threaded aggregation step will always be slower on the GPU.
  • DThe host-to-device and device-to-host data transfers dominate the total wall-clock time when the dataset is small, erasing any parallelism benefit from the GPU kernel itself. Correct
Explain why host-device transfer overhead can make GPU acceleration slower than CPU processing for small datasets. GPUs deliver throughput advantages through massive parallelism, but every GPU operation requires data to travel from host (CPU) memory to device (GPU) memory and back. This PCIe transfer has a largely fixed latency cost. For a 500-row dataset the useful compute work is tiny, so the transfer overhead is proportionally dominant and the net wall-clock time exceeds that of an equivalent in-process pandas operation. The GPU advantage only emerges when the dataset is large enough that parallelism savings outweigh transfer cost.

Why A is wrong: cuDF does support group-by aggregations regardless of DataFrame size; there is no automatic silent fallback that would explain the slowdown as a capability gap.

Why B is wrong: Sorting is not strictly required for all group-by strategies, and GPU-based radix and merge sorts can be faster than CPU sorts for larger data; this reasoning does not explain the small-data slowdown.

Why C is wrong: While GPUs do have lower per-core clock speeds, cuDF group-by operations are data-parallel and use thousands of CUDA cores simultaneously; raw clock speed comparison is not the correct explanation for this scenario.

Why D is correct: GPU acceleration carries a fixed overhead for copying data across the PCIe bus. On a 500-row dataset the transfer cost exceeds the compute savings, making the GPU path net slower than an in-process pandas call.

Free sampleFoundations of Accelerated Data Sciencemedium

An engineer profiles a RAPIDS workflow in which a cuDF DataFrame is built on the GPU, several transformations run on the device, and then a single column is passed into a pure-Python loop that calls a NumPy routine row by row before the result is written back into the cuDF DataFrame. The two cuDF stages are fast, but the middle step dominates runtime. What best explains why the NumPy loop is the slow part of an otherwise GPU-resident pipeline?

  • AThe column must be copied from GPU memory to host memory for NumPy to process it on the CPU, and the per-row Python loop runs serially on the host before the result is copied back, so device-to-host transfer and serial CPU execution dominate. Correct
  • BNumPy automatically dispatches array operations to the GPU, but the per-row Python loop prevents it from batching them, so the device is underutilised.
  • CcuDF transformations are lazy and only execute when the NumPy step forces evaluation, so the loop is charged with the cost of the earlier GPU work.
  • DThe NumPy routine is GPU-accelerated but uses a different CUDA context from cuDF, so the pipeline pays a context-switch penalty on every row.
Dropping into a CPU library mid-pipeline forces host-device copies and serial execution that erase the benefit of staying GPU-resident. cuDF holds data in GPU memory, whereas NumPy operates on host memory using the CPU. Routing a column through a NumPy loop forces a copy off the device, serial row-by-row execution on the CPU, and a copy back, so the cost is dominated by host-device data movement and the loss of GPU parallelism rather than by the arithmetic itself.

Why A is correct: cuDF data lives in GPU memory while NumPy runs on the host CPU, so feeding the column to NumPy forces a device-to-host copy, serial per-row Python execution on the CPU, and a host-to-device copy back; these transfers and the loss of parallelism are exactly why the middle step dominates an otherwise GPU-resident pipeline.

Why B is wrong: NumPy is a CPU library and does not dispatch to the GPU at all, so this misstates how NumPy works; the slowdown is not unbatched GPU calls but the move of data off the device and back.

Why C is wrong: cuDF operations execute eagerly rather than building a deferred graph, so this borrows Dask-style laziness that does not apply; the real cost is host-device transfer plus serial Python execution, not deferred GPU work being triggered late.

Why D is wrong: NumPy does not use CUDA at all, so there is no second CUDA context and no per-row context switch; this is a plausible-sounding low-level explanation that misidentifies a CPU library as a GPU one.

Free sampleFoundations of Accelerated Data Sciencemedium

A data scientist benchmarks two stages of a workflow on identical hardware. Stage one applies the same arithmetic normalisation to every one of 300 million floating-point values in a column. Stage two walks a recursive parser that reads one record, decides which of dozens of branches to follow based on the previous record's result, then reads the next. Which mapping of stages to processing units best reflects the strengths of each architecture?

  • ARun stage one on the GPU and stage two on the CPU, because uniform element-wise arithmetic is data-parallel while the serial branch-dependent parser needs strong single-thread, low-latency execution. Correct
  • BRun both stages on the GPU, because once data is resident in device memory every operation benefits equally from the higher core count regardless of its control flow.
  • CRun stage one on the CPU and stage two on the GPU, because branch-heavy parsing maps naturally to the GPU's large instruction cache.
  • DRun both stages on the CPU, because mixing devices forces a host-to-device copy whose latency outweighs any throughput gain on either stage.
Match data-parallel uniform workloads to the GPU and serial branch-dependent workloads to the CPU. GPUs deliver throughput by running one instruction across many independent data elements at once, which fits element-wise arithmetic; a recursive parser whose every step depends on the previous result is serial and benefits from the CPU's superior single-thread latency and branch prediction.

Why A is correct: Correct: the GPU excels at applying one operation across millions of independent elements in parallel, whereas the CPU's high single-thread performance and branch prediction suit an inherently sequential, control-flow-heavy task.

Why B is wrong: Tempting because keeping data GPU-resident avoids transfer cost, but a sequential branch-dependent parser cannot be parallelised across cores, so the GPU's thousands of lanes sit idle while one path is followed.

Why C is wrong: This inverts the correct assignment; the GPU has comparatively small per-core caches and weak single-thread branch handling, so a serial parser runs poorly there while the uniform arithmetic that suits the GPU is wrongly placed on the CPU.

Why D is wrong: Tempting because transfer overhead is real, but for 300 million independent arithmetic operations the GPU's throughput advantage dwarfs a single bulk copy, so confining everything to the CPU wastes the data-parallel stage.

Free sampleFoundations of Accelerated Data Sciencemedium

In an end-to-end pipeline, a single cleaned and feature-engineered Dask-cuDF DataFrame is consumed by three downstream stages within the same run: model training, a statistical report, and an export job. Profiling shows the expensive cleaning-and-feature step is being executed three times, once per downstream consumer, because each consumer triggers the lazy computation independently. Which change removes the redundant recomputation while keeping the three consumers separate?

  • ARun the three downstream consumers strictly one after another so the cleaning step is not under contention from concurrent access.
  • BIncrease the number of partitions in the cleaning step so each downstream computation has smaller chunks to process.
  • CMaterialise the cleaned and feature-engineered DataFrame once by persisting it into distributed GPU memory, then pass that single computed result to all three downstream consumers. Correct
  • DConvert the cleaning step to use single-GPU cuDF so its results are eagerly computed and therefore not recomputed by the consumers.
A shared expensive intermediate consumed by several stages should be materialised once by persisting it so downstream consumers reuse it instead of recomputing the lazy graph each time. Lazy evaluation re-evaluates the upstream graph every time a result is requested, so when several consumers each request the same intermediate the costly step runs once per consumer; persisting the result computes it a single time and caches it across workers for all consumers to share.

Why A is wrong: Serialising the consumers avoids concurrency, which sounds relevant, but each consumer still independently triggers the lazy computation, so the expensive step is recomputed three times regardless of ordering.

Why B is wrong: Smaller partitions can improve parallel balance, making this plausible, but partitioning does not stop the graph from being evaluated once per consumer, so the threefold recomputation remains.

Why C is correct: Persisting forces the lazy graph to execute a single time and holds the result in worker memory, so the three consumers read the same materialised DataFrame instead of each re-triggering the expensive upstream computation.

Why D is wrong: Eager single-GPU cuDF would compute once, which seems to fix the symptom, but it abandons the distributed scaling the workflow needs and the correct fix is to materialise the existing lazy result rather than abandon Dask.

Free sampleFoundations of Accelerated Data Sciencehard

A team joins two dask_cudf DataFrames every hour: a 2 TB fact table partitioned across a GPU cluster and a 40 MB reference table that fits easily on every worker. The join key has high cardinality, and profiling shows that a naive distributed merge spends most of its time shuffling the large fact table across the interconnect. Which adjustment most directly removes the expensive shuffle while keeping the join correct?

  • ARepartition both DataFrames into many more, smaller partitions so the shuffle is spread across additional tasks and the per-task transfer cost of the fact table drops.
  • BSort the fact table by the join key before merging so that matching rows become adjacent and the framework can skip the shuffle stage when it performs the distributed merge.
  • CBroadcast the small reference table to every worker so each fact-table partition joins against a local copy, which avoids shuffling the large fact table across the interconnect. Correct
  • DConvert the reference table to pandas and merge it on the client process after collecting the fact table with compute, keeping the join logic on a single coordinating node.
Use a broadcast join to avoid shuffling a large distributed table when one side is small enough to replicate to every worker. When one join side is tiny, replicating it to every worker lets each partition of the large table perform a purely local join. This broadcast strategy eliminates the key-based shuffle of the large table, which is the dominant cost in a naive distributed merge of a huge fact table against a small reference table.

Why A is wrong: Tempting because more partitions can balance load, but it still shuffles the full 2 TB table and adds scheduling overhead; it does not remove the shuffle, so it is wrong.

Why B is wrong: Tempting because sorting feels related to locality, but a distributed sort is itself a full shuffle and does not align partitions across the two collections; this trades one shuffle for another and is wrong.

Why C is correct: Correct: a broadcast join replicates the tiny table to all workers so each large partition joins locally, meaning the 2 TB table never moves across the interconnect.

Why D is wrong: Tempting as a simplification, but calling compute on a 2 TB table to merge on a single client exceeds memory and defeats distribution, so this is wrong.

Other domains in this exam

See also the NCA-ADS cert hub, the study guide, and the cheat sheet.

Examworthy is not affiliated with or endorsed by NVIDIA. Original, blueprint-aligned practice material only.