A comprehensive web-based visualization tool for GPU performance profiling. Analyze PyTorch profiler traces and NVIDIA Nsight Compute (.ncu-rep) reports entirely in the browser β no server, no install required.
π Live Demo: https://kapilsharma.dev/perfessor/
- Multiple Trace Support: Load and switch between multiple trace files
- Gzip Support: Upload compressed
.json.gzfiles for smaller transfers - Large File Handling: Process files up to 1GB with chunked reading and Web Workers
- Behavior Match: Precisely matches torch-tb-profiler metrics and filtering
- Summary cards: total duration, event count, unique operators, GPU kernels
- GPU information: device name, memory, compute capability
- GPU utilization gauge and step time breakdown chart
- Automated performance recommendations and bottleneck detection
- Tensor Core utilization statistics
- Exact operator filtering matching torch-tb-profiler
- Sortable device/host self/total durations, calls, percentage breakdown
- Real-time debounced search and virtualized scrolling for 1000+ operators
- Interactive detail panel per operator; CSV export
- Weighted averages by duration matching torch-tb-profiler exactly
- Tensor Core detection ("TC" badges), occupancy, blocks per SM
- Interactive detail panel per kernel; search and CSV export
- Embedded Perfetto timeline with thread/stream lanes, zoom/pan, flow events
- Open in new window or download the trace file
- Allocation/deallocation tracking and memory usage over time
- Client-side parsing:
.ncu-repbinary files parsed entirely in the browser β no server needed - Kernel browser: Searchable and filterable sidebar with kernel type, grid/block dimensions, duration
- 10 analysis sections: All major NCU sections rendered with charts and metric tables
- Optimization hints: OPT and INF hints surfaced per kernel and in a global summary
- CSV export: Export metric tables from any section
- Kernel list with type classification (GEMM, Conv, Reduce, Elementwise, Softmax, Attention, Memory, etc.)
- Colored type badges, grid/block dimension tags, duration
- Real-time search by kernel name or type
- Summary β Key metrics (duration, compute/memory throughput, SM busy, occupancy, IPC) + all optimization hints
- GPU Speed of Light β Throughput bars (compute, memory, L1, L2) + roofline visualization
- Compute Workload β SM busy, issue slots busy, IPC metrics
- Memory Workload β Memory hierarchy (L1βL2βDRAM) with hit/miss rates, transaction breakdown
- Launch Statistics β Grid/block size, registers, shared memory, waves per SM
- Occupancy β Theoretical vs achieved, block limit factors (registers, shared mem, warps)
- Scheduler Statistics β Eligible warp distribution, issued/active warps
- Warp State Statistics β Stall reason breakdown (memory, execution dependency, sync, etc.)
- Instruction Statistics β Per-opcode instruction mix
- Source Counters β Per-source-line performance counters (when available)
- OPT (orange): Actionable optimization suggestions
- INF (blue): Informational context
- Global summary table aggregates hints across all kernels, sortable by type and section
- Node.js 18+ and npm
git clone <your-repo-url>
cd perfessor
npm install
npm run devThe application will be available at http://localhost:5173
npm run build
npm run previewThe app is configured to deploy to GitHub Pages on every push to main via GitHub Actions:
- Builds to the
docs/folder - Uploads the build artifact
- Deploys to GitHub Pages
To enable: go to repository Settings β Pages β set Source to "GitHub Actions".
import torch
import torch.nn as nn
import torch.profiler as profiler
model = nn.Sequential(
nn.Linear(10, 100),
nn.ReLU(),
nn.Linear(100, 10)
).cuda()
x = torch.randn(32, 10).cuda()
with profiler.profile(
activities=[
profiler.ProfilerActivity.CPU,
profiler.ProfilerActivity.CUDA,
],
record_shapes=True,
profile_memory=True,
with_stack=True,
) as prof:
model(x)
prof.export_chrome_trace("trace.json")π More info: PyTorch Profiler Recipe
schedule parameter to profile fewer steps, or split the trace into smaller segments.
- Drag and drop
.json,.pt.trace.json, or.json.gzfiles - Navigate views with icon tabs or keyboard shortcuts:
1β Overview2β Operators3β Kernels4β Trace5β Memory
# Profile with full kernel metrics
ncu --set full -o report ./your_cuda_application
# Profile specific kernels
ncu --kernel-name "myKernel" --set full -o report ./app
# Profile a Python script
ncu --set full -o report python train.pyThis produces a report.ncu-rep file.
π More info: NCU CLI Reference
- Drag and drop your
.ncu-repfile (or click "Add File") - The kernel sidebar populates automatically
- Click any kernel to open its full metric view
- Switch tabs to explore different analysis sections
- Hover metric cards for inline descriptions
| Component | Technology |
|---|---|
| UI Framework | React 19 |
| Build Tool | Vite 7.3 |
| State Management | Zustand |
| PyTorch Charts | Recharts 3.7 |
| NCU Charts | Chart.js 4.5 |
| Tables | TanStack Table v8 + Virtual v3 |
| Utilities | D3 Scale/Color |
| Trace Viewer | Perfetto UI |
| NCU Parsing | Custom protobuf decoder (no external deps) |
src/
βββ App.jsx # Mode switcher (PyTorch Trace β NCU Report)
βββ components/
β βββ FileUploader.jsx # Landing page, routes files by type
β βββ AddTraceButton.jsx # Add additional files + help popup
β βββ TraceViewer.jsx # PyTorch trace tab navigation
β βββ TraceSelector.jsx # Trace file switcher sidebar
β βββ UpdateBanner.jsx # Auto version check banner
β βββ overview/ # PyTorch Overview view
β βββ operator/ # PyTorch Operators view
β βββ kernel/ # PyTorch Kernels view
β βββ trace/ # Perfetto timeline view
β βββ memory/ # Memory events view
β βββ ncu/
β βββ NcuView.jsx # NCU layout (sidebar + detail)
β βββ NcuKernelSidebar.jsx # Kernel list with search/filter
β βββ NcuKernelDetail.jsx # Tab bar and section routing
β βββ NcuChart.jsx # React wrapper for Chart.js (useRef/useEffect)
β βββ NcuShared.jsx # MetricsTable, HintBox, MetricCard
β βββ tabs/ # One file per analysis section tab
βββ store/
β βββ traceStore.js # PyTorch trace state (Zustand)
β βββ ncuStore.js # NCU report state (Zustand)
βββ utils/
βββ traceDataProcessor.js # PyTorch trace parsing and aggregation
βββ ncuParser.js # .ncu-rep binary protobuf parser
βββ ncuHelpers.js # Metric formatting helpers
βββ ncuCharts.js # Chart.js chart factories
βββ ncuMetricDescriptions.js # Metric tooltip text
βββ recommendationsEngine.js # PyTorch performance hints
βββ eventClassifier.js
βββ memoryTracker.js
βββ formatters.js
- File upload & validation (type, size, gzip detection)
- Automatic gzip decompression (DecompressionStream API)
- Chunked reading in 10MB segments with progress tracking
- Web Worker background processing
- JSON parsing and event conversion (B/E β X events)
- Metadata extraction (GPU info, process/thread names)
- O(n) hierarchy building and self-time calculation
- Operator aggregation (exact torch-tb-profiler logic)
- Kernel analysis (weighted averages, Tensor Core detection)
- Step time breakdown and recommendations generation
The .ncu-rep file is NVIDIA's proprietary binary format based on Protocol Buffers v2. Perfessor parses it entirely client-side using a hand-written wire format decoder with no external protobuf library.
Official docs: Nsight Compute Documentation
flowchart TD
FILE["π .ncu-rep File"]
FILE --> MAGIC["Magic Header<br/>0x4E 0x56 0x52 0x00<br/>'NVR\0' β 4 bytes"]
FILE --> FHDR["FileHeader<br/>ββββββββββββββ<br/>4-byte LE size<br/>+ protobuf payload<br/>ββββββββββββββ<br/>Field 1: Version (uint32)"]
FILE --> BLOCKS["Blocks (repeated)"]
BLOCKS --> BLK0["Block 0"]
BLOCKS --> BLK1["Block 1"]
BLOCKS --> BLKN["Block N..."]
BLK0 --> BHDR["BlockHeader<br/>ββββββββββββββ<br/>4-byte LE size<br/>+ protobuf payload<br/>ββββββββββββββ<br/>Field 1: NumSources<br/>Field 2: NumResults<br/>Field 3: SessionDetails<br/>Field 4: StringTable<br/>Field 5: PayloadSize<br/>Field 7: NumRangeResults"]
BLK0 --> PAYLOAD["Payload (PayloadSize bytes)"]
PAYLOAD --> SOURCES["Source Entries Γ NumSources<br/>ββββββββββββββ<br/>4-byte LE size<br/>+ ProfileSource protobuf"]
PAYLOAD --> RESULTS["Profile Results Γ NumResults<br/>ββββββββββββββ<br/>4-byte LE size<br/>+ ProfileResult protobuf<br/>β¬ KERNEL DATA"]
PAYLOAD --> RANGES["Range Results Γ NumRangeResults<br/>ββββββββββββββ<br/>4-byte LE size<br/>+ RangeResult protobuf"]
RESULTS --> PR["ProfileResult<br/>ββββββββββββββ<br/>Field 5: mangled name<br/>Field 6: function name<br/>Field 7: demangled name<br/>Field 10: grid (Uint64x3)<br/>Field 11: block (Uint64x3)<br/>Field 13: MetricResults[]<br/>Field 17: Sections[]<br/>Field 19: RuleResults[]<br/>Field 22: contextId<br/>Field 23: streamId"]
PR --> MR["ProfileMetricResult<br/>ββββββββββββββ<br/>Field 1: NameId β StringTable[i]<br/>Field 2: MetricValue"]
PR --> SEC["ProfilerSection<br/>ββββββββββββββ<br/>Field 1: identifier<br/>Field 2: displayName<br/>Field 3: order<br/>Field 4: Header β metrics[]"]
PR --> RR["RuleResult<br/>ββββββββββββββ<br/>Field 1: identifier<br/>Field 2: displayName<br/>Field 3: Body β messages[]<br/>Field 4: sectionIdentifier"]
MR --> MV["ProfileMetricValue (oneof)<br/>ββββββββββββββ<br/>Field 1: string (LENGTH_DELIMITED)<br/>Field 2: float (FIXED32)<br/>Field 3: double (FIXED64)<br/>Field 4: uint32 (VARINT)<br/>Field 5: uint64 (VARINT)"]
RR --> RM["RuleResultMessage<br/>ββββββββββββββ<br/>Field 1: message text<br/>Field 2: type<br/>1 = INF (blue)<br/>4 = OPT (orange)"]
BHDR --> ST["StringTable<br/>ββββββββββββββ<br/>'Duration'<br/>'SM Busy'<br/>'L1 Hit Rate'<br/>...<br/>(carried across blocks)"]
MR -. "NameId resolves via" .-> ST
style MAGIC fill:#1e3a5f,stroke:#3b82f6
style FHDR fill:#1e3a5f,stroke:#3b82f6
style BHDR fill:#1e3a5f,stroke:#3b82f6
style PAYLOAD fill:#1a3a2a,stroke:#22c55e
style RESULTS fill:#2d1f3f,stroke:#a855f7
style PR fill:#2d1f3f,stroke:#a855f7
style MR fill:#3a2a1a,stroke:#f59e0b
style MV fill:#3a2a1a,stroke:#f59e0b
style SEC fill:#3a2a1a,stroke:#f59e0b
style RR fill:#3a1a1a,stroke:#ef4444
style RM fill:#3a1a1a,stroke:#ef4444
style ST fill:#1a3a3a,stroke:#06b6d4
| Wire Type | Value | Encoding | Used For |
|---|---|---|---|
| VARINT | 0 | Variable-length, 7 bits per byte | int32, int64, uint32, uint64, bool, enum |
| FIXED64 | 1 | 8 bytes, little-endian | fixed64, double |
| LENGTH_DELIMITED | 2 | varint length + bytes | string, bytes, nested messages, packed repeated |
| FIXED32 | 5 | 4 bytes, little-endian | fixed32, float |
Varint example: 300 β 0xAC 0x02 β last 7 bits of each byte form the value; MSB=1 means more bytes follow.
Located in your NCU installation at $NSIGHT_COMPUTE_ROOT/extras/FileFormat/*.proto:
ProfilerReport.protoβ FileHeader, BlockHeader, top-level structureProfilerResultsCommon.protoβ ProfileMetricValue, ProfileMetricResult, Uint64x3ProfilerSection.protoβ ProfilerSection, ProfilerSectionMetric, ProfilerSectionHeaderProfilerStringTable.protoβ String table for metric name deduplication
Note: The file format can change between NCU versions without notice.
| Field # | Type | Description |
|---|---|---|
| 5 | string | Mangled kernel name |
| 6 | string | Short function name |
| 7 | string | Demangled (human-readable) kernel name |
| 10 | Uint64x3 | Grid dimensions (X, Y, Z) |
| 11 | Uint64x3 | Block dimensions (X, Y, Z) |
| 12 | repeated SourceLine | SASS/PTX source lines (removed in NCU 2025+) |
| 13 | repeated ProfileMetricResult | Collected performance metrics |
| 17 | repeated ProfilerSection | Organized metric sections |
| 19 | repeated RuleResult | Optimization hints and warnings |
| 22 | uint32 | CUDA context ID |
| 23 | uint32 | CUDA stream ID |
Each metric value is a oneof with five possible types:
| Field # | Type | Wire Type |
|---|---|---|
| 1 | string | LENGTH_DELIMITED |
| 2 | float | FIXED32 |
| 3 | double | FIXED64 |
| 4 | uint32 | VARINT |
| 5 | uint64 | VARINT |
The parser checks fields 1β5 in order and returns the first non-null value.
Metric names can be 50+ characters and repeat thousands of times. NCU deduplicates them via a per-block string table:
BlockHeader.StringTable: ["Duration", "SM Busy", "L1 Hit Rate", ...]
ProfileMetricResult:
NameId: 2 β resolves to "L1 Hit Rate"
MetricValue: { double: 87.5 }
String tables persist across blocks β if a block has an empty table it reuses the previous block's table. This allows later blocks to reference earlier string tables.
Device information is stored as metrics in the first ProfileResult, not in a dedicated structure:
device__attribute_display_nameβ GPU model namedevice__attribute_compute_capability_major/minorβ SM versiondevice__attribute_multiprocessor_countβ Number of SMsdevice__attribute_global_memory_sizeβ Total VRAMdevice__attribute_l2_cache_sizeβ L2 cache sizedevice__attribute_gpu_core_clock_rateβ Core clockdevice__attribute_memory_clock_rateβ Memory clockdevice__attribute_max_threads_per_blockβ Block limitsdevice__attribute_max_shared_memory_per_blockβ Shared mem limitdevice__attribute_max_registers_per_blockβ Register limit
message RuleResult {
string Identifier = 1; // e.g., "SOL_DRAM_Bound"
string DisplayName = 2; // e.g., "Memory Bound"
RuleResultBody Body = 3; // Contains the hint messages
string SectionIdentifier = 4; // Which section this belongs to
}
message RuleResultMessage {
string Message = 1; // The hint text
int32 Type = 2; // 0=None, 1=Info, 2=Warn, 3=Error, 4=Optimization
}Perfessor surfaces Type 4 as OPT (orange) and Type 1 as INF (blue).
The parser applies name-pattern heuristics to pick units and formatting:
| Pattern | Unit | Example |
|---|---|---|
.pct, _pct, pct_of_peak |
% | sm__throughput.avg.pct_of_peak_sustained_elapsed β "45.2%" |
time_duration |
ns/us/ms/s | sm__duration.avg β "1.23 ms" |
clock_rate, frequency |
Hz/MHz/GHz | device__attribute_gpu_core_clock_rate β "1.41 GHz" |
_bytes, _size |
B/KB/MB/GB | device__attribute_global_memory_size β "16.00 GB" |
per_cycle |
inst/cycle | sm__inst_executed.avg.per_cycle_active β "2.45 inst/cycle" |
warp |
warp | sm__warps_active.avg β "32.5 warp" |
thread_count |
thread | launch__thread_count β "256 thread" |
Key decisions in src/utils/ncuParser.js:
- No protobuf library β hand-written wire format decoder keeps bundle size minimal
- BigInt throughout β faithful 64-bit integer handling for timestamps and counters
- Streaming block-by-block β processes files without loading everything into memory at once
- String table carry-forward β correctly handles multi-block files where later blocks omit the table
| Issue | Detection | Suggestion |
|---|---|---|
| DataLoader bottleneck | DataLoader time > 10% of total | Increase num_workers, enable pin_memory=True |
| GPU underutilization | GPU utilization < 50% | Increase batch size, use mixed precision |
| High communication overhead | Comms > 20% (distributed) | Use gradient accumulation, optimize network topology |
| Tensor Core underutilization | <50% eligible kernels use TCs | Use torch.cuda.amp for mixed precision |
| Memory inefficiency | High memory fragmentation | Use memory-efficient training techniques |
- Web Worker Processing: Heavy PyTorch trace computation runs off the main thread
- Chunked File Reading: 10MB chunks prevent browser freezing on large files
- Gzip Support: Automatic decompression via DecompressionStream API
- O(n) Algorithms: Optimized hierarchy building and self-time calculation
- Lazy Loading: NCU viewer loaded on-demand with
React.lazy() - Virtualized Tables: Only visible rows rendered (TanStack Virtual)
- Debounced Search: 300ms debounce for smooth filtering
1β5: Switch between PyTorch Trace viewsCtrl+F: Focus search (when available)Esc: Close detail panels
- Chrome/Edge 90+
- Firefox 88+
- Safari 14+
- PyTorch Kineto TensorBoard Plugin
- PyTorch Profiler Tutorial
- PyTorch Profiler Recipe
- Chrome Trace Event Format
- Nsight Compute Homepage
- Nsight Compute Documentation
- Profiling Guide
- NCU CLI Reference
- Python Report Interface
Contributions welcome!
Note: This is an unofficial tool not affiliated with NVIDIA or Meta/PyTorch. For official support, use NVIDIA Nsight Compute or PyTorch's built-in profiling tools.












