LeetLLM
My PlanLearnGlossaryTracksPracticeBlog
LeetLLM

Your go-to resource for mastering AI & LLM systems.

Product

  • Learn
  • Glossary
  • Tracks
  • Practice
  • Blog
  • RSS

Legal

  • Terms of Service
  • Privacy Policy

© 2026 LeetLLM. All rights reserved.

All Topics
Your Progress
0%

0 of 192 articles completed

🛠️Computing Foundations0/9
Git, Shell, Linux for AIDocker for Reproducible AIPython for AI EngineeringNumPy and Tensor ShapesCUDA for ML TrainingMPS & Metal for ML on MacData Structures for AISQL and Data ModelingAlgorithms for ML Engineers
📊Math & Statistics0/9
Gradients and BackpropVectors, Matrices & TensorsLinear Algebra for MLAdam, Momentum, SchedulersProbability for Machine LearningBayesian Inference from EvidenceStatistics and UncertaintyDistributions and SamplingHypothesis Tests, Intervals, and pass@k
📚Preparation & Prerequisites0/13
Neural Networks from ScratchCNNs from ScratchTraining & BackpropagationSoftmax, Cross-Entropy & OptimizationRNNs, LSTMs, GRUs, and Sequence ModelingAutoencoders and VAEsThe Transformer Architecture End-to-EndLanguage Modeling & Next TokensFrom GPT to Modern LLMsPrompt Engineering FundamentalsCalling LLM APIs in ProductionFirst AI App End-to-EndThe LLM Lifecycle
🧮ML Algorithms & Evaluation0/15
Linear Regression from ScratchLogistic Regression and MetricsDecision Trees, Forests, and BoostingReinforcement Learning BasicsValidation and LeakageStatistical Learning and GeneralizationConformal Prediction and UncertaintyClustering and PCAProbabilistic Generative ModelsCore Retrieval AlgorithmsDecoding AlgorithmsExperiment Design and A/B TestingCausal Inference and CounterfactualsPyTorch Training LoopsDataset Pipelines and Data Quality
📦Production ML Systems0/6
Feature Engineering for Production MLBatch and Streaming Feature PipelinesGradient Boosted Trees in ProductionRanking and Recommendation SystemsForecasting and Anomaly DetectionMonitoring Predictive Models
🧪Core LLM Foundations0/8
The Bitter Lesson & ComputeBPE, WordPiece, and SentencePieceStatic to Contextual EmbeddingsPerplexity & Model EvaluationFile Ingestion for AIChunking StrategiesLLM Benchmarks & LimitationsInstruction Tuning & Chat Templates
🧰Applied LLM Engineering0/25
Dimensionality Reduction for EmbeddingsCoT, ToT & Self-Consistency PromptingFunction Calling & Tool UseMCP & Tool Protocol StandardsContext EngineeringPrompt Injection DefenseResponsible AI GovernancePrivacy-Preserving Machine LearningData Labeling and Human FeedbackEvaluating AI AgentsProduction RAG PipelinesHybrid Search: Dense + SparseReranking and Cross-Encoders for RAGRAG Evaluation for Reliable AnswersLLM-as-a-Judge EvaluationBias & Fairness in LLMsHallucination Detection & MitigationLLM Observability & MonitoringExperiment Tracking with MLflow and W&BPrompt Optimization with DSPyModel Versioning & DeploymentSemantic Caching & Cost OptimizationLLM Cost Engineering & Token EconomicsModel Gateways, Routing, and FallbacksDesign an Automated Support Agent
🎓Portfolio Capstones0/9
Capstone: Delivery ETA PredictionCapstone: Product RankingCapstone: Demand ForecastingCapstone: Image Damage ClassifierCapstone: Production ML PipelineCapstone: Document QACapstone: Eval DashboardCapstone: Fine-Tuned ClassifierCapstone: Reproducible ML Study
🧠Transformer Deep Dives0/8
Sentence Embeddings & Contrastive LossEmbedding Similarity & QuantizationScaled Dot-Product AttentionVision Transformers and Image EncodersPositional Encoding: RoPE & ALiBiLayer Normalization: Pre-LN vs Post-LNMechanistic InterpretabilityDecoding Strategies: Greedy to Nucleus
🧬Advanced Training & Adaptation0/17
Scaling Laws & Compute-Optimal TrainingPre-training Data at ScaleBuild GPT from Scratch LabJAX for PyTorch ResearchersContinued Pretraining for Domain ShiftSynthetic Data PipelinesSupervised Fine-Tuning PipelineMixed Precision TrainingDistributed Training: FSDP & ZeROLoRA & Parameter-Efficient TuningTraining Run OperationsReward Modeling from Preference DataRLHF & DPO AlignmentConstitutional AI & Red TeamingRLVR & Verifiable RewardsKnowledge Distillation for LLMsModel Merging and Weight Interpolation
🤖Advanced Agents & Retrieval0/16
Vector DB Internals: HNSW & IVFAdvanced RAG: HyDE & Self-RAGGraphRAG & Knowledge GraphsRAG Security & Access ControlStructured Output GenerationReAct & Plan-and-ExecuteGuardrails & Safety FiltersCode Generation & SandboxingComputer-Use / GUI / Browser AgentsHuman-in-the-Loop Agent ArchitectureAI Coding Workflow with AgentsAgent Memory & PersistenceAgent Failure & RecoveryRecursive Language Models (RLM)Multi-Agent OrchestrationCapstone: Production Agent
⚡Inference & Production Scale0/27
Inference: TTFT, TPS & KV CacheMulti-Query & Grouped-Query AttentionKV Cache & PagedAttentionPrefix Caching and Prompt CachingFlashAttention & Memory EfficiencyContinuous Batching & SchedulingScaling LLM InferenceModel Parallelism for LLM InferenceAccelerator Architecture Field GuideCorrect GPU Profiling and BenchmarkingGPU Kernel Performance Engineering LabGPU Compilers and Kernel LanguagesGEMM From Naive CUDA to Tensor CoresLow-Precision GPU ProgrammingModel Quantization: GPTQ, AWQ & GGUFLocal LLM DeploymentSLM Specialization & Edge DeploymentSpeculative DecodingLong Context Window ManagementMixture of Experts ArchitectureMamba & State Space ModelsReasoning & Test-Time ComputeAdvanced MLOps & DevOps for AIGPU Serving & AutoscalingDistributed Inference Data PlaneServing Benchmark EngineeringA/B Testing for LLMs
🏗️System Design Capstones0/9
Content Moderation SystemCode Completion SystemMulti-Tenant LLM PlatformLLM-Powered Search EngineVision-Language Models & CLIPMultimodal LLM ArchitectureDiffusion Models: Images & TextReal-Time Voice AI AgentReasoning Agent System Design
🎤AI Lab Interviewing0/4
AI Lab Coding Interview: Python SystemsAI Lab System Design InterviewAI Lab Behavioral InterviewAI Lab Technical Presentation
🔬Project Deep Dives0/17
Deep Dive - vLLMDeep Dive - SkyRLDeep Dive - FlashAttentionDeep Dive - FlashInferDeep Dive - DeepGEMMDeep Dive - NCCLDeep Dive - MegatronDeep Dive - DeepSpeedDeep Dive - RayDeep Dive - MLflowDeep Dive - PyTorchDeep Dive - TransformersDeep Dive - SGLangDeep Dive - slimeDeep Dive - DeepEPDeep Dive - TinkerDeep Dive - Light-PEFT
Back to Topics
LearnInference & Production ScaleGPU Compilers and Kernel Languages
🚀HardInference Optimization

GPU Compilers and Kernel Languages

Trace a GPU kernel from CUDA C++ or a tile language through compiler IR, PTX, cubin, and native instructions, then choose an authoring model by control, portability, and inspectable evidence.

21 min read
Learning path
Step 147 of 192 in the full curriculum
GPU Kernel Performance Engineering LabGEMM From Naive CUDA to Tensor Cores

Personalize this lesson

Adapt explanations and teaching visuals to your background and preferred voice.

Your vector-add kernel passed every correctness check yesterday. Today the same source takes 300 milliseconds on its first call, or fails on a newer GPU with "no kernel image is available." Reading the source again won't explain either symptom. The GPU never executes CUDA C++ or Python directly.

The accelerator field guide mapped execution and memory owners across hardware. The kernel performance lab then treated memory traffic, launch geometry, occupancy, and numerical checks as evidence. Keep those habits. Here we follow the compiler artifacts between source and hardware, because each boundary owns a different class of bugs.

One addition, several representations

Start with one operation: for every valid index iii, write outi=xi+yiout_i = x_i + y_iouti​=xi​+yi​. CUDA C++ expresses the work from one thread's point of view. The launch supplies many threads.

vector_add.cu
1#include <cuda_runtime.h> 2 3#include <algorithm> 4#include <cmath> 5#include <cstdlib> 6#include <iostream> 7#include <vector> 8 9void check(cudaError_t status) { 10 if (status != cudaSuccess) { 11 std::cerr << cudaGetErrorString(status) << "\n"; 12 std::exit(1); 13 } 14} 15 16__global__ void add_kernel( 17 const float* x, 18 const float* y, 19 float* out, 20 int n 21) { 22 int i = blockIdx.x * blockDim.x + threadIdx.x; 23 if (i < n) { 24 out[i] = x[i] + y[i]; 25 } 26} 27 28void launch_add(const float* x, const float* y, float* out, int n) { 29 constexpr int threads = 256; 30 int blocks = (n + threads - 1) / threads; 31 add_kernel<<<blocks, threads>>>(x, y, out, n); 32} 33 34int main() { 35 constexpr int n = 1000; 36 std::vector<float> x(n, 1.25F); 37 std::vector<float> y(n, 2.75F); 38 std::vector<float> out(n); 39 40 float *d_x, *d_y, *d_out; 41 check(cudaMalloc(&d_x, n * sizeof(float))); 42 check(cudaMalloc(&d_y, n * sizeof(float))); 43 check(cudaMalloc(&d_out, n * sizeof(float))); 44 check(cudaMemcpy(d_x, x.data(), n * sizeof(float), cudaMemcpyHostToDevice)); 45 check(cudaMemcpy(d_y, y.data(), n * sizeof(float), cudaMemcpyHostToDevice)); 46 47 launch_add(d_x, d_y, d_out, n); 48 check(cudaGetLastError()); 49 check(cudaDeviceSynchronize()); 50 check(cudaMemcpy(out.data(), d_out, n * sizeof(float), cudaMemcpyDeviceToHost)); 51 52 check(cudaFree(d_x)); 53 check(cudaFree(d_y)); 54 check(cudaFree(d_out)); 55 56 float max_abs_error = 0.0F; 57 for (float value : out) { 58 max_abs_error = std::max(max_abs_error, std::abs(value - 4.0F)); 59 } 60 61 bool correct = max_abs_error == 0.0F; 62 std::cout << (correct ? "PASS" : "FAIL") 63 << " max_abs_error=" << max_abs_error << "\n"; 64 return correct ? 0 : 1; 65}

At source level, you can inspect the bounds check and the index formula. You can't yet prove which machine instructions were emitted, how many registers they require, or whether a deployed binary contains code for the target GPU.

NVIDIA's offline compiler driver, nvcc, coordinates separate host and device compilation. For device code, the documented path reaches Parallel Thread Execution (PTX), a versioned virtual instruction set architecture (ISA). ptxas then assembles PTX into a CUDA binary, called a cubin, for a particular streaming multiprocessor target such as sm_90. A cubin is an ELF file containing encoded GPU instructions plus symbols, relocations, and resource metadata.[1]Reference 1NVIDIA CUDA Compiler Driver NVCChttps://docs.nvidia.com/cuda/cuda-compiler-driver-nvcc/[2]Reference 2CUDA Binary Utilitieshttps://docs.nvidia.com/cuda/cuda-binary-utilities/

Engineers commonly call the human-readable native instruction listing SASS. SASS isn't another portable source format in this workflow. It's a disassembly of target-specific instructions stored in a cubin.

There can be an earlier compiler layer. NVVM intermediate representation (IR) is NVIDIA's GPU-oriented subset and extension of LLVM IR. Language frontends can generate NVVM IR, and libNVVM compiles compatible NVVM IR into PTX.[3]Reference 3NVVM IR Specificationhttps://docs.nvidia.com/cuda/nvvm-ir-spec/[4]Reference 4libNVVM APIhttps://docs.nvidia.com/cuda/libnvvm-api/ That makes this conceptual path useful:

source→LLVM/NVVM IR→PTX→cubin with native code\text{source} \rightarrow \text{LLVM/NVVM IR} \rightarrow \text{PTX} \rightarrow \text{cubin with native code}source→LLVM/NVVM IR→PTX→cubin with native code

But don't turn a conceptual compiler layer into a deployment promise. nvcc publicly documents source-to-PTX and PTX-to-cubin stages; it doesn't promise a stable, human-readable NVVM file for every build. Another frontend may use different IRs before it reaches PTX, and a non-NVIDIA backend won't target PTX at all.

GPU compilation path from CUDA C++ or a kernel DSL through an optional LLVM or NVVM compiler representation, forward-compatible NVIDIA PTX, ptxas, and a target-specific cubin containing native instructions. A lower comparison shows that a runtime can load a compatible bundled cubin or JIT-compile embedded PTX and cache the generated cubin.
Follow the portability boundary: compatible PTX can be JIT-compiled for newer NVIDIA targets, while a cubin contains native instructions for one target architecture and loads only where binary-compatibility rules permit.

You find an FFMA instruction in nvdisasm output. Which layer has supplied the strongest evidence that this native operation exists?

Answer

The target cubin. nvdisasm reads encoded machine instructions from a cubin and renders them as SASS. A source expression or PTX instruction can suggest an FFMA, but a later compiler stage may still fuse, split, remove, or replace it.

nvcc builds a package, not one universal binary

A CUDA translation unit may contain CPU host code and GPU device code. nvcc separates those paths, sends the host path to a supported C++ compiler, compiles device code for requested virtual and real architectures, and embeds device images into the host object. The container of one or more device images is a fatbinary, usually shortened to fatbin.[1]Reference 1NVIDIA CUDA Compiler Driver NVCChttps://docs.nvidia.com/cuda/cuda-compiler-driver-nvcc/

The distinction between compute_90 and sm_90 records intent:

Target spellingArtifact meaningRuntime consequence
compute_90PTX using the virtual feature set associated with compute capability 9.0Driver may just-in-time compile it for a compatible current or later GPU
sm_90Cubin containing native code assembled for the 9.0 hardware targetCompatible GPU can load it without PTX code generation
Both in a fatbinNative code for known deployment plus PTX fallbackLoader prefers compatible binary and retains a forward-compatibility path

PTX offers forward compatibility, not backward compatibility. PTX generated for compute_90 may be JIT-compiled for a later target, but it can't run on an older GPU that lacks the required feature set. Cubin compatibility is narrower: NVIDIA documents compatibility within some major compute-capability families, and not across major versions.[1]Reference 1NVIDIA CUDA Compiler Driver NVCChttps://docs.nvidia.com/cuda/cuda-compiler-driver-nvcc/

The runtime flow branches after packaging:

Diagram showing 1. Host launches kernel, 2. Compatible cubin present?, 3a. Load target code, and 3b. Compatible PTX present?.
1. Host launches kernel, 2. Compatible cubin present?, 3a. Load target code, and 3b. Compatible PTX present?.

This branch explains two otherwise confusing observations. A cold process may pay compilation or lazy-loading cost before the first kernel. A package may run on the build machine yet fail on another GPU because neither a compatible cubin nor usable PTX was embedded.

Produce artifacts you can inspect

Compile the CUDA source for one native Hopper target and keep PTX for future targets. --keep asks nvcc to retain supported intermediate files, while the two --generate-code entries package both sm_90 cubin and compute_90 PTX.

terminal
1mkdir -p build/keep 2 3nvcc -O3 -lineinfo \ 4 --generate-code arch=compute_90,code=sm_90 \ 5 --generate-code arch=compute_90,code=compute_90 \ 6 --keep --keep-dir build/keep \ 7 vector_add.cu -o build/vector_add

Create standalone PTX and cubin files as well. Keeping both makes the boundary explicit and gives nvdisasm a cubin it can read directly.

terminal
1nvcc -O3 --ptx \ 2 --gpu-architecture=compute_90 \ 3 vector_add.cu -o build/vector_add.compute_90.ptx 4 5nvcc -O3 -lineinfo --cubin \ 6 --generate-code arch=compute_90,code=sm_90 \ 7 vector_add.cu -o build/vector_add.sm_90.cubin

Now inspect the package before judging performance. cuobjdump accepts a standalone cubin or a host executable, object, library, or external fatbin. nvdisasm accepts standalone cubins and adds richer control-flow and register-liveness views.[2]Reference 2CUDA Binary Utilitieshttps://docs.nvidia.com/cuda/cuda-binary-utilities/

terminal
1# What did the host executable package? 2cuobjdump --list-elf build/vector_add 3cuobjdump --list-ptx build/vector_add 4 5# Read virtual and native instruction forms. 6cuobjdump --dump-ptx build/vector_add > build/embedded.ptx 7cuobjdump --dump-sass build/vector_add > build/embedded.sass 8 9# Check per-kernel registers, local memory, shared memory, and stack use. 10cuobjdump --dump-resource-usage build/vector_add 11 12# Read one standalone cubin with source lines. 13nvdisasm --print-code \ 14 --print-line-info \ 15 build/vector_add.sm_90.cubin > build/vector_add.lines.sass 16 17# Generate a separate register-liveness view. 18nvdisasm --print-code \ 19 --print-life-ranges \ 20 build/vector_add.sm_90.cubin > build/vector_add.liveness.sass

Read these outputs as a chain of evidence:

  1. PTX header: Check .version, .target, address size, and kernel entry name. A mismatched .target can explain a deployment failure before launch.
  2. PTX body: Look for global loads, bounds predication, arithmetic, and the store. This confirms virtual operations, not final scheduling.
  3. Resource report: Record registers, static shared memory, local memory, and stack per kernel. Local memory can indicate spills, although not every local-memory use is a spill.
  4. SASS listing: Confirm native loads, arithmetic, stores, predicates, and architecture-specific instructions. Compare against compiler flags and source line info.
  5. Profiler trace: Measure executed instructions, memory behavior, and time on the target GPU. Static disassembly can't tell you which path dominates wall time.

⚠️ Common mistake: A shorter SASS listing isn't automatically faster. Instruction latency, issue rate, dependency chains, memory transactions, occupancy, and input-dependent control flow still decide runtime behavior.

Keep an artifact manifest

A benchmark result becomes much easier to reproduce when its compiler state travels beside it. Record at least these fields:

kernel-artifact-manifest.json
1{ 2 "source_revision": "git-sha", 3 "compiler": "nvcc", 4 "toolkit_version": "capture nvcc --version", 5 "driver_version": "capture nvidia-smi", 6 "virtual_target": "compute_90", 7 "native_targets": ["sm_90"], 8 "compile_flags": ["-O3", "-lineinfo"], 9 "embedded_images": ["compute_90 PTX", "sm_90 cubin"], 10 "kernel": "add_kernel", 11 "correctness": "max_abs_error=0 for deterministic FP32 fixture" 12}

Hash the source, compile options, target, compiler version, and any compile-time constants into a JIT cache key. Shape-specialized kernel systems also need shape, stride, data type, alignment, and algorithmic mode when those values change generated code. Caching only by function name can silently load a valid cubin for the wrong contract.

JIT is part of request latency

Just-in-time (JIT) compilation moves work from build time to runtime. NVIDIA's runtime compiler, NVRTC, accepts CUDA C++ strings and can emit PTX, cubin, CUDA Tile IR, or link-time optimization IR depending on target and options. The CUDA Driver API can load the result, while nvJitLink can combine PTX or link-time IR and produce a linked cubin.[5]Reference 5NVRTChttps://docs.nvidia.com/cuda/nvrtc/

PTX loaded at runtime is compiled by the device driver's JIT compiler. Generated cubins enter a compute cache, and a driver upgrade invalidates that cache so the new JIT compiler can regenerate code.[6]Reference 6CUDA Programming Guide.https://docs.nvidia.com/cuda/cuda-programming-guide/

That runtime behavior creates an operational contract:

ContractEvidence to captureFailure when omitted
Compilation identitySource or IR hash, compiler version, flags, target, specialization valuesWrong artifact reused or rollout can't be reproduced
Load compatibilityGPU compute capability, driver version, PTX target, cubin targetsMissing image or unsupported PTX at module load
Cold-start budgetCompile time, module-load time, cache state, first launch timeFirst request violates latency objective while steady state looks healthy
CorrectnessReference outputs, tolerances, edge shapes, alignment and mask casesFast specialization returns wrong values on a boundary case
PerformanceWarmups, synchronization, target GPU, input shapes, precision, profiler evidenceJIT or lazy-load time is mistaken for kernel execution time

Use the driver's switches to test both deployment branches. The first command forces embedded PTX through JIT and disables its disk cache for the run. The second disables PTX JIT, so success requires a compatible embedded cubin.[7]Reference 7CUDA Environment Variableshttps://docs.nvidia.com/cuda/cuda-programming-guide/05-appendices/environment-variables.html

terminal
1CUDA_FORCE_PTX_JIT=1 CUDA_CACHE_DISABLE=1 ./build/vector_add 2CUDA_DISABLE_PTX_JIT=1 ./build/vector_add

Run each mode in a fresh process. Reset the environment afterward. These are packaging tests, not kernel benchmarks.

CUDA module loading is lazy by default in current documentation. First use can therefore include module or kernel loading even when a compatible cubin exists. Warm the exact kernel or use eager loading when a latency-sensitive service needs that work outside the request path.[7]Reference 7CUDA Environment Variableshttps://docs.nvidia.com/cuda/cuda-programming-guide/05-appendices/environment-variables.html

A service packages sm_90 cubin plus compute_90 PTX. Its first request on a future compatible GPU is slow, but later requests are fast. What should you separate in the trace?

Answer

Separate process initialization, module loading, PTX-to-cubin JIT, cache write, first kernel launch, and steady-state execution. A fast warm kernel doesn't disprove expensive compilation or loading on the first request.

Same kernel, different unit of thought

CUDA C++ asks you to describe one thread, then organize threads into blocks. Triton asks you to describe one blocked program instance operating on vectors of offsets. Both versions below compute the same masked vector add.

The Triton kernel forms a block of 256 indices, loads two blocks under a mask, adds them, and stores the result. The compiler maps that blocked program to GPU threads and memory operations.[8]Reference 8Triton Programming Guide: Introductionhttps://triton-lang.org/main/programming-guide/chapter-1/introduction.html

vector_add_triton.py
1import torch 2import triton 3import triton.language as tl 4 5@triton.jit 6def add_kernel(x_ptr, y_ptr, out_ptr, n, BLOCK: tl.constexpr): 7 offsets = tl.program_id(axis=0) * BLOCK + tl.arange(0, BLOCK) 8 mask = offsets < n 9 x = tl.load(x_ptr + offsets, mask=mask) 10 y = tl.load(y_ptr + offsets, mask=mask) 11 tl.store(out_ptr + offsets, x + y, mask=mask) 12 13def add(x: torch.Tensor, y: torch.Tensor) -> torch.Tensor: 14 out = torch.empty_like(x) 15 n = x.numel() 16 grid = (triton.cdiv(n, 256),) 17 add_kernel[grid](x, y, out, n, BLOCK=256) 18 return out

The arithmetic stayed fixed. Ownership changed:

QuestionCUDA C++ answerTriton answer
What does one source instance own?One scalar threadOne block of values
How is the global index formed?blockIdx, blockDim, threadIdxprogram_id plus arange
How are edge elements protected?Scalar branch or predicateVector mask on load and store
Who chooses thread mapping?Programmer chooses block size and per-thread workCompiler maps blocked program; author supplies block and launch meta-parameters
What evidence closes the loop?Cubin resource report, SASS, profiler, correctness testCompiler artifacts, generated target code, profiler, correctness test

Triton removes some thread-level bookkeeping. It doesn't remove the need to reason about coalescing, block size, occupancy, specialization, or numerical behavior. A blocked expression is an optimization hypothesis until generated artifacts and measurements support it.

Six authoring surfaces, six control contracts

A kernel language isn't a ranking from easy to powerful. Each surface chooses which decisions belong to author, compiler, library, and runtime.

CUDA C++

CUDA C++ uses single instruction, multiple threads (SIMT) semantics. You control thread and block geometry, address calculations, synchronization, memory spaces, and low-level intrinsics. That directness fits irregular algorithms, architecture experiments, and cases where a higher layer hides a critical decision. Its portability surface is NVIDIA GPUs, with source, PTX, and fatbins handling generations inside that ecosystem.[6]Reference 6CUDA Programming Guide.https://docs.nvidia.com/cuda/cuda-programming-guide/

Evidence should reach the cubin. Keep ptxas resource output, disassemble native code, profile target hardware, and compare results against a trusted implementation.

Triton block programs

Triton is a Python-embedded domain-specific language (DSL) whose program instances operate on blocks. Its compiler performs block-level data-flow analysis and can automate coalescing, vectorization, shared-memory management, synchronization, and instruction selection.[8]Reference 8Triton Programming Guide: Introductionhttps://triton-lang.org/main/programming-guide/chapter-1/introduction.html Current project documentation lists NVIDIA and AMD GPU support, but backend support and generated code still vary by hardware and Triton release.[9]Reference 9Triton Language and Compilerhttps://github.com/triton-lang/triton

Triton fits custom fusion and dense or structured kernels where block ownership is natural. Inspect specialized IR and target assembly when available, because one Python function can compile into many shape, type, and meta-parameter variants.

CUTLASS and CuTe

CUTLASS supplies CUDA C++ templates for high-performance linear algebra. CuTe, used inside modern CUTLASS, models hierarchical tensor layouts, tensors, copy atoms, and matrix multiply-accumulate atoms. Its C++ surface keeps detailed control over thread-data mapping and architecture features. CuTe DSL brings the same low-level concepts into a Python JIT stack while preserving explicit memory, thread, and data hierarchy.[10]Reference 10CUTLASS and CuTe Documentationhttps://docs.nvidia.com/cutlass/latest/overview.html

Choose this family when matrix multiplication, convolution, attention building blocks, or unusual layouts need more architectural control than Triton exposes. Expect a steeper layout-algebra learning curve and a larger specialization space. Use CUTLASS profiler or an equivalent harness, inspect PTX and SASS, and keep compile-time policies in the benchmark manifest.

CUDA Tile

CUDA Tile changes CUDA's unit of thought from one SIMT thread to one block operating collectively on immutable multidimensional tiles. The compiler chooses the number of threads per block and maps tile operations onto registers, shared memory, tensor cores, and other hardware resources. SIMT and tile kernels can coexist in one application.[11]Reference 11Writing Tile Kernelshttps://docs.nvidia.com/cuda/cuda-programming-guide/02-basics/writing-tile-kernels.html

Tooling snapshot, verified August 29, 2026: NVIDIA's CUDA 13.3 documentation lists CUDA Tile in Python through cuda.tile and CUDA Tile C++ in the toolkit from 13.3 onward. Tile shapes must be compile-time powers of two in the documented model. Treat earlier toolkits and different language releases as separate environments, not compatible assumptions.[11]Reference 11Writing Tile Kernelshttps://docs.nvidia.com/cuda/cuda-programming-guide/02-basics/writing-tile-kernels.html

CUDA Tile offers source portability across NVIDIA generations by hiding thread mapping, not cross-vendor portability. Inspect Tile IR or emitted device images where supported, record compiler version, then profile on each architecture you claim to support.

Pallas

Pallas extends JAX with custom kernels for GPU and tensor processing unit (TPU) backends. Kernels use references (Refs) to memory, launch grids, block specifications (BlockSpecs), and backend-specific pipelining or hardware APIs. Current APIs embed a kernel in a larger JAX program through pl.kernel or a backend-specific kernel wrapper.[12]Reference 12Pallas: a JAX Kernel Languagehttps://docs.jax.dev/en/latest/pallas/

Pallas is useful when JAX composition, differentiation, or GPU and TPU integration matters. Shared concepts don't guarantee one source or tuning configuration performs equally across backends. JAX describes Pallas as under active development, and its quickstart still warns that the API is experimental. Mosaic GPU supports Hopper and newer GPUs; the older Triton backend is deprecated in JAX 0.11 and scheduled for removal.[12]Reference 12Pallas: a JAX Kernel Languagehttps://docs.jax.dev/en/latest/pallas/ Pin JAX and backend versions, test interpret mode where supported, inspect lowering, and profile each target.

NKI

Neuron Kernel Interface (NKI) targets AWS Trainium, Inferentia2, Trainium2, and Trainium3 NeuronCores. Its high-level nki.language API handles tensor indexing and placement, while lower-level nki.isa exposes hardware operations for computation, data movement, dynamic control flow, and cross-core communication. The documented execution pattern moves inputs from high-bandwidth memory (HBM) into the on-chip state buffer (SBUF), computes on NeuronCore engines, then stores outputs to HBM.[13]Reference 13About Neuron Kernel Interfacehttps://awsdocs-neuron.readthedocs-hosted.com/en/latest/nki/get-started/about/index.html

NKI fits workloads committed to AWS Neuron hardware that need custom operations or tighter control than framework compilation supplies. It isn't a CUDA portability layer. Keep Neuron compiler and instance type in the artifact record, use framework-level correctness comparisons, and collect Neuron profiler evidence.

The comparison is easier to use after each model has a concrete meaning:

SurfaceAuthor's main unitExplicit controlPortability boundaryMinimum convincing evidence
CUDA C++Thread, warp, blockHighest thread-level controlNVIDIA CUDA GPUsPTX, cubin resources, SASS, target profile
TritonBlocked programTile shape, loads, masks, launch meta-parametersSupported Triton GPU backendsSpecialization key, compiler IR or assembly, target profile
CUTLASS/CuTeHierarchical layout and hardware atomVery high layout, copy, pipeline, and MMA controlNVIDIA CUDA GPUsPolicy manifest, profiler, PTX/SASS, numeric check
CUDA TileBlock-level immutable tilesTile partition and operations; compiler owns threadsCUDA Tile-capable NVIDIA stackTile/compiler artifact, version pin, per-target profile
PallasGrid program over Refs and blocksMemory movement, block specs, backend pipeline APIsJAX GPU and TPU backends, with backend-specific constraintsJAX/backend pin, lowering evidence, per-target profile
NKITile over Neuron memory and enginesSBUF placement plus optional low-level ISA controlAWS Inferentia2 and Trainium familiesNeuron compiler record, profiler, framework comparison

A Triton kernel and a CuTe rewrite pass the same correctness fixture. Triton is faster on H100, while CuTe is faster on B200. What must the review artifact contain before either implementation becomes the default?

Answer

Keep both source revisions, compiler and backend versions, specialization keys, compile flags, native targets, correctness tolerances, cold and warm timings, emitted-code evidence, and per-target profiler results. Choose against the deployment GPU mix and latency objective. One architecture's timing or a PTX instruction count can't establish a portable winner.

Diagnose the first failing boundary

Compiler failures become manageable when you ask which representation last satisfied its contract.

SymptomLikely boundaryEvidence to collectNext action
Source compile rejects type, address space, or intrinsicSource to frontend IRFull compiler log, minimal source, language and compiler versionsReduce to smallest rejected construct; check supported language subset
ptxas rejects target or instructionPTX to cubinPTX header, ptxas log, toolkit version, requested smAlign PTX feature set, toolkit, and target architecture
"No kernel image" at load or launchFatbin selectionGPU compute capability, cuobjdump --list-elf, --list-ptxPackage compatible cubin or usable PTX fallback
Forced PTX JIT fails, normal mode worksDriver JIT pathEmbedded PTX, driver version, JIT error logFix PTX compatibility or driver floor; don't treat cubin success as forward-compatibility proof
First call is slow, warm calls are fastCompile or lazy-load pathFresh-process timeline, cache state, module-loading modePrecompile, warm exact variants, or move eager loading outside request path
Register count rises and throughput fallsIR or target-code optimizationOld and new resource reports, SASS, occupancy and stall profileFind specialization or code change that extended live ranges or caused spills
Edge shape returns wrong valuesSource mask or specialization contractFailing shape, strides, alignment, generated variant, reference outputFix boundary mask and include shape or stride in specialization key
nvdisasm rejects host executableInspection tool inputFile type and packaged image listUse cuobjdump on host file or extract cubin before nvdisasm

Don't skip directly from source to profiler. A profiler identifies an executed bottleneck, but it can't explain a missing image that never loaded. Disassembly proves emitted code, but it can't prove correct answers. Correctness tests prove values for their fixtures, but they can't establish speed. The evidence layers complement one another.

Choose by the decision you must own

Start at the highest abstraction that exposes the decision blocking your kernel:

  1. Use a framework or vendor library when an existing operation already meets correctness and performance needs.
  2. Use Triton when custom block structure, fusion, or masking is central and compiler-managed thread mapping is acceptable.
  3. Use Pallas when the kernel must compose naturally with JAX or span its GPU and TPU ecosystem.
  4. Use NKI when NeuronCore memory placement or engines are the target, not an afterthought.
  5. Use CUDA Tile when block-level tile semantics fit and NVIDIA's compiler should own intra-block threads.
  6. Use CUTLASS/CuTe when tensor layouts, asynchronous copies, and matrix hardware atoms need explicit control.
  7. Drop to CUDA C++ when per-thread behavior, synchronization, special instructions, or unsupported irregularity requires it.

That order isn't permanent. A prototype may begin in Triton, expose a compiler limitation, and move one hot kernel to CuTe or CUDA C++. Another kernel may move upward after a compiler learns the missing transformation.

Practice the full artifact path before moving on. Build the vector-add executable with an sm_90 cubin and compute_90 PTX, save its manifest and cuobjdump image inventory, then run forced-PTX and cubin-only checks in fresh processes. Capture the resource report, SASS, correctness output, and separate cold and warm timings in one review artifact. Rebuild the Triton version for at least two shapes and compare the same evidence so a language change keeps the fixture and measurement contract intact.

Mastery check

Complete the lesson

Mastery Check

Answer every question, then check your score. Score 75% or higher to mark this lesson complete.

1.A CUDA C++ expression contains x[i] + y[i]. Which artifact proves the final target binary contains a native floating-point add for that operation?

Correct answer: The SASS disassembly of the target cubin

Source and PTX describe earlier representations that later passes can rewrite. Disassembling the target cubin reads the native instructions that were actually encoded for that architecture.

2.A build embeds only an sm_90 cubin and must run on a GPU with a new major compute capability. Which deployment property is missing?

Correct answer: Compatible PTX that the newer driver can JIT for its target

Cubins aren't binary compatible across major compute-capability versions. Embedded PTX supplies the forward path because a newer driver can compile compatible virtual code for the new target.

3.Which command path is valid when you want register liveness from a CUDA host executable?

Correct answer: Extract a cubin with cuobjdump, then pass that cubin to nvdisasm

cuobjdump accepts host binaries and can list or extract their embedded cubins. nvdisasm accepts standalone cubins and provides the richer register-liveness view.

4.A kernel takes 250 ms on its first call and 20 microseconds afterward. Which experiment best separates JIT and loading from execution?

Correct answer: Trace a fresh process with cache state recorded, then warm and time synchronized launches separately

Fresh-process tracing exposes compilation and module loading, while warmed synchronized launches isolate steady-state kernel time. Mixing the phases produces a misleading latency number.

5.For the vector-add examples, which ownership change correctly describes CUDA C++ versus Triton?

Correct answer: CUDA describes one GPU thread, while Triton describes one blocked program instance

CUDA's ordinary SIMT model starts from per-thread work and explicit launch geometry. Triton starts from vectorized blocks of values and lets its compiler map each program instance onto GPU threads.

6.A JAX team needs one custom operation on TPU and Hopper GPU, accepts an actively developed API, and wants it to compose with JAX transformations. Which surface fits best?

Correct answer: Pallas, with separate backend validation and tuning for GPU and TPU

Pallas integrates custom kernels with JAX and provides GPU and TPU backends. Its shared programming ideas don't erase backend-specific APIs, constraints, profiling, or tuning.

7.After a specialization change, register use rises and throughput drops. Which evidence set most directly tests a compiler-codegen cause?

Correct answer: Before-and-after cubin resource reports, target SASS, and profiler data

Register allocation happens in target compilation. Resource reports and SASS show the emitted change, while profiler data connects it to occupancy, stalls, and measured throughput.

8.Which JIT cache key is safest for a shape-specialized kernel whose layout and compiler flags change generated code?

Correct answer: Source or IR hash, compiler and flags, target, shape, strides, dtype, and codegen meta-parameters

Every value that can alter generated code or its validity belongs in compilation identity. Function-name-only and shape-only caches can return a well-formed artifact for the wrong layout, type, target, or algorithm.

8 questions remaining.

Next Step
Continue to GEMM From Naive CUDA to Tensor Cores

You can now trace source choices into PTX, cubin resources, and native instructions. Next, use that evidence while transforming one matrix multiplication from a correct scalar-thread baseline into tiled, pipelined Tensor Core code.

PreviousGPU Kernel Performance Engineering Lab
Share this article
XFacebookLinkedInBlueskyRedditHacker NewsEmail
References

NVIDIA CUDA Compiler Driver NVCC

NVIDIA · 2026 · Official documentation

https://docs.nvidia.com/cuda/cuda-compiler-driver-nvcc/

CUDA Binary Utilities

NVIDIA · 2026 · Official documentation

https://docs.nvidia.com/cuda/cuda-binary-utilities/

NVVM IR Specification

NVIDIA · 2026 · Official documentation

https://docs.nvidia.com/cuda/nvvm-ir-spec/

libNVVM API

NVIDIA · 2026 · Official documentation

https://docs.nvidia.com/cuda/libnvvm-api/

NVRTC

NVIDIA · 2026 · Official documentation

https://docs.nvidia.com/cuda/nvrtc/

CUDA Programming Guide.

NVIDIA · 2026

https://docs.nvidia.com/cuda/cuda-programming-guide/

CUDA Environment Variables

NVIDIA · 2026 · Official documentation

https://docs.nvidia.com/cuda/cuda-programming-guide/05-appendices/environment-variables.html

Triton Programming Guide: Introduction

Triton Project · 2026 · Official documentation

https://triton-lang.org/main/programming-guide/chapter-1/introduction.html

Triton Language and Compiler

Triton Project · 2026 · Official repository

https://github.com/triton-lang/triton

CUTLASS and CuTe Documentation

NVIDIA · 2026 · Official documentation

https://docs.nvidia.com/cutlass/latest/overview.html

Writing Tile Kernels

NVIDIA · 2026 · Official documentation

https://docs.nvidia.com/cuda/cuda-programming-guide/02-basics/writing-tile-kernels.html

Pallas: a JAX Kernel Language

JAX Authors · 2026 · Official documentation

https://docs.jax.dev/en/latest/pallas/

About Neuron Kernel Interface

Amazon Web Services · 2026 · Official documentation

https://awsdocs-neuron.readthedocs-hosted.com/en/latest/nki/get-started/about/index.html

Discussion

Questions and insights from fellow learners.

Discussion loads when you reach this section.