Performance Implications of NVRTC vs NVCC Compilation in DeepGEMM
NVRTC reduces first-use compilation latency to 10–30 ms by compiling in-process, while NVCC incurs higher one-time costs for offline CUBIN generation, though both converge to <1 % runtime difference after caching.
DeepGEMM, the high-performance matrix-multiplication library from DeepSeek, supports dual compilation modes for its CUDA kernels. Understanding the performance implications of NVRTC versus NVCC compilation helps you optimize for latency-sensitive applications versus long-running training jobs.
How DeepGEMM Selects the Compiler
The selection logic resides in csrc/jit/compiler.hpp. At startup, the library reads the environment variable DG_JIT_USE_NVRTC. When set to 1, DeepGEMM uses NVRTC for just-in-time compilation; otherwise, it defaults to NVCC for offline compilation (lines 54–60).
Architecture of the Compilation Pipeline
Both paths share the same entry point: Compiler::build(). This method checks the persistent on-disk cache in .deep_gemm/cache/ before invoking the actual compiler (lines 4–10). If a cached CUBIN exists, the method returns immediately, eliminating compilation overhead for both NVCC and NVRTC.
Compilation Latency: One-Time Cost vs. Per-Kernel Overhead
The primary performance difference emerges during the initial compilation phase.
NVCC (Offline Compilation)
NVCC spawns an external process that parses the entire translation unit, runs host-side compilation, and generates a CUBIN file. In csrc/jit/compiler.hpp, the NVCCCompiler::compile method assembles a command line including --gpu-architecture=sm_<arch> and invokes the driver (lines 122–131). This process incurs significant filesystem I/O and synchronization overhead (atomic rename operations at lines 28–38), resulting in higher one-time latency.
NVRTC (JIT Compilation)
NVRTC operates entirely within the process. The NVRTCCompiler::compile routine creates an nvrtcProgram, compiles the kernel string, and extracts PTX or CUBIN directly in memory (lines 84–92). For typical DeepGEMM kernels, this JIT path completes in approximately 10–30 ms. Since NVRTC version 12.8, DeepGEMM automatically enables pre-compiled header (PCH) support to further reduce this latency (lines 66–76).
Caching Strategy and Binary Reuse
Both compilation modes leverage the same persistent cache implemented in csrc/jit/cache.hpp. The cache key incorporates the kernel signature, compiler version, and architecture flags.
- Cache Hit: When a valid CUBIN exists, both NVCC and NVRTC paths skip compilation entirely.
- Cache Miss: The compiler writes the generated CUBIN to
.deep_gemm/cache/using atomic rename operations to prevent corruption during concurrent writes (lines 28–38).
This unified caching mechanism ensures that after the first compilation—regardless of method—subsequent process launches incur zero compilation overhead.
Binary Portability and Architecture Targeting
NVCC supports embedding device code for multiple architectures simultaneously using -gencode flags. DeepGEMM’s NVCC path targets the exact GPU architecture via --gpu-architecture=sm_<arch> (lines 94–104), producing minimal binary size.
NVRTC generates PTX for the runtime-detected architecture (via device_runtime->get_arch). The CUDA driver translates this PTX to machine code at module load time. This adds a small runtime translation overhead, though in practice this is negligible compared to kernel execution time.
Runtime Execution Performance
Once compiled and cached, both NVCC-generated CUBINs and NVRTC-generated CUBINs execute with identical performance characteristics. The DeepGEMM source code confirms that the runtime difference between cached NVCC and NVRTC binaries is less than 1 % for most kernels. The execution path—memory bandwidth, occupancy, and instruction throughput—depends solely on the generated machine code, which is equivalent in both cases.
Practical Configuration Examples
Enabling NVRTC for Low-Latency Inference
Set the environment variable before importing the library to minimize first-use latency:
import os
os.environ["DG_JIT_USE_NVRTC"] = "1"
import deep_gemm
# First call compiles in ~10-30 ms
result = deep_gemm.gemm(A, B)
Verifying Cache Behavior
Inspect the persistent cache directory to confirm CUBIN reuse across both compilation modes:
ls -la ~/.deep_gemm/cache/
# Output: kernel.gemm$${NVCC12.9}... or kernel.gemm$${NVRTC12.8}...
Measuring Compilation Overhead
Benchmark the compilation latency for each path:
import time, os
# Test NVRTC path
os.environ["DG_JIT_USE_NVRTC"] = "1"
import deep_gemm
start = time.perf_counter()
deep_gemm.gemm(A, B) # Cold start
print(f"NVRTC first-use: {time.time() - start:.3f}s")
Summary
- NVRTC minimizes first-use compilation latency to 10–30 ms by compiling in-process, making it ideal for interactive workloads and short-lived processes.
- NVCC incurs higher one-time compilation costs due to external process spawning and filesystem I/O, but produces architecture-specific CUBINs without runtime PTX translation.
- Both paths share a unified persistent cache (
.deep_gemm/cache/), eliminating compilation overhead after the first successful build. - Runtime performance differs by less than 1 % between cached NVCC and NVRTC binaries, as both execute identical machine code.
Frequently Asked Questions
How do I switch between NVRTC and NVCC in DeepGEMM?
Set the environment variable DG_JIT_USE_NVRTC to 1 before importing the library. The value is read during initialization in csrc/jit/compiler.hpp (lines 54–60). If unspecified, DeepGEMM defaults to NVCC for offline compilation.
Does NVRTC produce slower GPU kernels than NVCC?
No. Once compiled and cached, both NVRTC and NVCC generate functionally identical CUBINs. The DeepGEMM implementation confirms that runtime performance differs by less than 1 %. NVRTC generates PTX that the driver translates to machine code, but this translation overhead is negligible compared to kernel execution time.
Where does DeepGEMM store compiled kernels?
Compiled kernels are stored in the persistent cache directory .deep_gemm/cache/ within the user's home directory. Both NVCC and NVRTC write CUBIN files to this location using atomic rename operations to prevent corruption during concurrent writes, as implemented in csrc/jit/compiler.hpp (lines 28–38).
Is NVRTC suitable for production training workloads?
Yes, but with specific considerations. NVRTC excels in scenarios requiring low first-use latency, such as interactive development or inference with dynamic shapes. For long-running training jobs where initialization overhead is amortized over many hours, NVCC offers slightly faster compilation of complex kernels and eliminates any PTX translation overhead, though the practical difference remains under 1 %.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →