-
Notifications
You must be signed in to change notification settings - Fork 87
Add a low-cost tracing subsystem #836
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
Open
Changes from all commits
Commits
Show all changes
9 commits
Select commit
Hold shift + click to select a range
c47d0e6
Add a low-cost tracing subsystem
vchuravy 580a8e1
Add `KernelAbstractions.@profile`
vchuravy 36ef5dd
Scope `@profile` to the tasks of its expression
vchuravy f1d1f3b
Annotate `KernelAbstractions.@spawn` with a profiler range
vchuravy e97d80b
Make annotations free when tracing is off
vchuravy 0562142
Make tracing cheaper when it is on
vchuravy aec082a
Make the range of a traced kernel launch cheaper
vchuravy e84c3ad
Compile the traced launch's helpers once rather than per kernel
vchuravy d6c0967
Trace kernel launches through a dynamic dispatch
vchuravy File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change | ||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,114 @@ | ||||||||||||||||||
| # Profiling | ||||||||||||||||||
|
|
||||||||||||||||||
| KernelAbstractions can put named ranges on the timeline of a tracing profiler, such as | ||||||||||||||||||
| NVIDIA Nsight Systems or Intel VTune, so that you can see which part of your program a | ||||||||||||||||||
| stretch of kernels belongs to. Annotations are cheap when no profiler is listening: a | ||||||||||||||||||
| single atomic load, and the label isn't even built. | ||||||||||||||||||
|
|
||||||||||||||||||
| ## Annotating code | ||||||||||||||||||
|
|
||||||||||||||||||
| Wrap code in [`@profiling_range`](@ref): | ||||||||||||||||||
|
|
||||||||||||||||||
| ```julia | ||||||||||||||||||
| @profiling_range "volume integral" begin | ||||||||||||||||||
| volume_integral!(du, u, backend) | ||||||||||||||||||
| end | ||||||||||||||||||
| ``` | ||||||||||||||||||
|
|
||||||||||||||||||
| Ranges can be grouped with a `domain`, which maps to an NVTX or ITT domain: | ||||||||||||||||||
|
|
||||||||||||||||||
| ```julia | ||||||||||||||||||
| @profiling_range "time step $i" domain = "Trixi" begin | ||||||||||||||||||
| step!(integrator) | ||||||||||||||||||
| end | ||||||||||||||||||
| ``` | ||||||||||||||||||
|
|
||||||||||||||||||
| Kernel launches are annotated with the kernel's name automatically. For an instantaneous | ||||||||||||||||||
| event, use [`profiling_mark`](@ref), and for ranges that don't follow the structure of the | ||||||||||||||||||
| code, [`profiling_range_start`](@ref KernelAbstractions.profiling_range_start) and | ||||||||||||||||||
| [`profiling_range_end`](@ref KernelAbstractions.profiling_range_end). | ||||||||||||||||||
|
|
||||||||||||||||||
| ## Built-in profiler | ||||||||||||||||||
|
|
||||||||||||||||||
| To see where time goes without an external profiler, run code under | ||||||||||||||||||
| [`KernelAbstractions.@profile`](@ref KernelAbstractions.@profile). It records the ranges | ||||||||||||||||||
| and kernel launches of an expression, and summarizes them: | ||||||||||||||||||
|
|
||||||||||||||||||
| ```julia-repl | ||||||||||||||||||
| julia> KernelAbstractions.@profile for i in 1:10 | ||||||||||||||||||
| @profiling_range "step" begin | ||||||||||||||||||
| mul2(backend)(A; ndrange = length(A)) | ||||||||||||||||||
| add(backend)(A, B; ndrange = length(A)) | ||||||||||||||||||
| end | ||||||||||||||||||
| end | ||||||||||||||||||
| Profiled 6.04 ms, recording 30 ranges. | ||||||||||||||||||
|
|
||||||||||||||||||
| Time (%) Total time Calls Avg time Min time Max time Name | ||||||||||||||||||
| ──────── ────────── ───── ──────── ──────── ──────── ──── | ||||||||||||||||||
| 92.6 % 5.59 ms 10 559 µs 298 µs 2.9 ms step | ||||||||||||||||||
| 57.6 % 3.48 ms 10 348 µs 109 µs 2.48 ms mul2 | ||||||||||||||||||
| 37.6 % 2.27 ms 10 227 µs 179 µs 400 µs add | ||||||||||||||||||
| ``` | ||||||||||||||||||
|
|
||||||||||||||||||
| Kernel launches synchronize their backend while profiling, so that their ranges measure the | ||||||||||||||||||
| kernel rather than its launch; pass `synchronize = false` to measure launches. Pass | ||||||||||||||||||
| `trace = true` to list every range in order instead. The first call of a kernel includes | ||||||||||||||||||
| its compilation, so profile a warmed-up run. | ||||||||||||||||||
|
|
||||||||||||||||||
| `@profile` records the task running the expression and the tasks it spawns, e.g. with | ||||||||||||||||||
| [`KernelAbstractions.@spawn`](@ref KernelAbstractions.@spawn), but not other tasks, so | ||||||||||||||||||
| profiles can run concurrently. Wait for spawned tasks within the expression, e.g. with | ||||||||||||||||||
| `@sync`, as what they record after it returns is lost. | ||||||||||||||||||
|
|
||||||||||||||||||
| ## Profilers | ||||||||||||||||||
|
|
||||||||||||||||||
| Ranges are recorded on the host threads of the process, which is how NVTX, ITT and | ||||||||||||||||||
| roctx work: it is the profiler that attributes the device work launched within a range to | ||||||||||||||||||
| it. So which profiler records the ranges depends on what the process runs under, not on the | ||||||||||||||||||
| backend: running the CPU backend under Nsight Systems gives NVTX ranges, and a GPU backend | ||||||||||||||||||
| under VTune gives ITT tasks. | ||||||||||||||||||
|
Comment on lines
+65
to
+69
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||||||||||||||
|
|
||||||||||||||||||
| Ranges go to every registered [`Tracer`](@ref KernelAbstractions.Tracer). These come with | ||||||||||||||||||
| KernelAbstractions, and only register themselves when their profiler is attached: | ||||||||||||||||||
|
|
||||||||||||||||||
| - **Nsight Systems**: load [NVTX.jl](https://github.com/JuliaGPU/NVTX.jl) (CUDA.jl loads it | ||||||||||||||||||
| too) and run under `nsys profile --trace=nvtx,...`. | ||||||||||||||||||
| - **rocprof**: load [AMDGPU.jl](https://github.com/JuliaGPU/AMDGPU.jl) and run under | ||||||||||||||||||
| `rocprofv3 --marker-trace` (or the legacy `rocprof --roctx-trace`). Ranges are recorded | ||||||||||||||||||
| with roctx, which has no domains, so a `domain` other than `"KernelAbstractions"` prefixes | ||||||||||||||||||
| the label. | ||||||||||||||||||
| - **Intel VTune**: load [IntelITT.jl](https://github.com/JuliaPerf/IntelITT.jl) and run | ||||||||||||||||||
| under VTune. | ||||||||||||||||||
| - **NVTXT**, a text format that Nsight Systems imports, to trace without a profiler: set | ||||||||||||||||||
| `JULIA_KA_NVTXT=1` to write `ka-<pid>.nvtxt` to the working directory, or set it to a | ||||||||||||||||||
| path, in which `%p` is replaced by the process id. Then | ||||||||||||||||||
| ```sh | ||||||||||||||||||
| ImportNvtxt --cmd create --nvtxt ka-1234.nvtxt -o report.nsys-rep | ||||||||||||||||||
| ``` | ||||||||||||||||||
| To trace only part of a program, register an [`NVTXTTracer`](@ref | ||||||||||||||||||
| KernelAbstractions.NVTXTTracer) yourself: | ||||||||||||||||||
| ```julia | ||||||||||||||||||
| tracer = KernelAbstractions.register_tracer!(KernelAbstractions.NVTXTTracer("trace.nvtxt")) | ||||||||||||||||||
| run_simulation() | ||||||||||||||||||
| KernelAbstractions.unregister_tracer!(tracer) | ||||||||||||||||||
| close(tracer) | ||||||||||||||||||
| ``` | ||||||||||||||||||
|
|
||||||||||||||||||
| Other profilers are supported by subtyping | ||||||||||||||||||
| [`Tracer`](@ref KernelAbstractions.Tracer). | ||||||||||||||||||
|
|
||||||||||||||||||
| ## API | ||||||||||||||||||
|
|
||||||||||||||||||
| ```@docs | ||||||||||||||||||
| @profiling_range | ||||||||||||||||||
| profiling_mark | ||||||||||||||||||
| KernelAbstractions.@profile | ||||||||||||||||||
| KernelAbstractions.ProfileResults | ||||||||||||||||||
| KernelAbstractions.profiling_active | ||||||||||||||||||
| KernelAbstractions.profiling_range_start | ||||||||||||||||||
| KernelAbstractions.profiling_range_end | ||||||||||||||||||
| KernelAbstractions.Tracer | ||||||||||||||||||
| KernelAbstractions.register_tracer! | ||||||||||||||||||
| KernelAbstractions.unregister_tracer! | ||||||||||||||||||
| KernelAbstractions.NVTXTTracer | ||||||||||||||||||
| ``` | ||||||||||||||||||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,35 @@ | ||
| module IntelITTExt | ||
|
|
||
| import KernelAbstractions as KA | ||
| import IntelITT | ||
|
|
||
| # forwards ranges to Intel VTune as ITT tasks, one ITT domain per domain | ||
| struct ITTTracer <: KA.Tracer | ||
| domains::IdDict{Symbol, IntelITT.Domain} | ||
| lock::ReentrantLock | ||
| end | ||
| ITTTracer() = ITTTracer(IdDict{Symbol, IntelITT.Domain}(), ReentrantLock()) | ||
|
|
||
| domain(tracer::ITTTracer, name::Symbol) = | ||
| @lock tracer.lock get!(() -> IntelITT.Domain(String(name)), tracer.domains, name) | ||
|
|
||
| function KA.trace_range_start(tracer::ITTTracer, label, domain_name) | ||
| # overlapped tasks may end on another thread, and needn't nest | ||
| task = IntelITT.Task(domain(tracer, domain_name), String(label)) | ||
| IntelITT.start(task) | ||
| return task | ||
| end | ||
|
|
||
| KA.trace_range_end(::ITTTracer, task::IntelITT.Task) = (IntelITT.stop(task); nothing) | ||
|
|
||
| const TRACER = Ref{ITTTracer}() | ||
|
|
||
| function __init__() | ||
| # only under a collector, as otherwise every annotation would be wasted work | ||
| if IntelITT.isactive() | ||
| TRACER[] = KA.register_tracer!(ITTTracer()) | ||
| end | ||
| return | ||
| end | ||
|
|
||
| end # module |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,53 @@ | ||
| module NVTXExt | ||
|
|
||
| import KernelAbstractions as KA | ||
| import NVTX | ||
|
|
||
| # forwards ranges to Nsight Systems as NVTX ranges, one NVTX domain per domain. NVTX ranges | ||
| # annotate host threads; Nsight Systems projects the GPU work launched within them itself. | ||
| struct NVTXDomain | ||
| domain::NVTX.Domain | ||
| # `Symbol` labels are fixed in the code, so they are registered with NVTX once, which | ||
| # makes recording them cheaper. Other labels are passed as they are. | ||
| strings::IdDict{Symbol, NVTX.StringHandle} | ||
| end | ||
|
|
||
| struct NVTXTracer <: KA.Tracer | ||
| domains::IdDict{Symbol, NVTXDomain} | ||
| lock::ReentrantLock | ||
| end | ||
| NVTXTracer() = NVTXTracer(IdDict{Symbol, NVTXDomain}(), ReentrantLock()) | ||
|
|
||
| function domain(tracer::NVTXTracer, name::Symbol) | ||
| return @lock tracer.lock get!(tracer.domains, name) do | ||
| NVTXDomain(NVTX.Domain(String(name)), IdDict{Symbol, NVTX.StringHandle}()) | ||
| end | ||
| end | ||
|
|
||
| message(::NVTXTracer, d::NVTXDomain, label::String) = label | ||
| message(tracer::NVTXTracer, d::NVTXDomain, label::Symbol) = | ||
| @lock tracer.lock get!(() -> NVTX.StringHandle(d.domain, String(label)), d.strings, label) | ||
|
|
||
| # process ranges, rather than push/pop, since they may end on another thread | ||
| function KA.trace_range_start(tracer::NVTXTracer, label, domain_name) | ||
| d = domain(tracer, domain_name) | ||
| return NVTX.range_start(d.domain; message = message(tracer, d, label)) | ||
| end | ||
| KA.trace_range_end(::NVTXTracer, id::NVTX.RangeId) = (NVTX.range_end(id); nothing) | ||
| function KA.trace_mark(tracer::NVTXTracer, label, domain_name) | ||
| d = domain(tracer, domain_name) | ||
| NVTX.mark(d.domain; message = message(tracer, d, label)) | ||
| return nothing | ||
| end | ||
|
|
||
| const TRACER = Ref{NVTXTracer}() | ||
|
|
||
| function __init__() | ||
| # only under Nsight, as otherwise every annotation would be wasted work | ||
| if NVTX.isactive() | ||
| TRACER[] = KA.register_tracer!(NVTXTracer()) | ||
| end | ||
| return | ||
| end | ||
|
|
||
| end # module |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,77 @@ | ||
| module ROCTXExt | ||
|
|
||
| import KernelAbstractions as KA | ||
| import AMDGPU | ||
| using Base.Libc: Libdl | ||
|
|
||
| # forwards ranges to rocprof as roctx ranges. like NVTX, roctx annotates host threads, and | ||
| # rocprof attributes the GPU work launched within them. roctx has no domains, so ranges in a | ||
| # domain other than `:KernelAbstractions` are prefixed with it. | ||
| struct ROCTXTracer <: KA.Tracer | ||
| range_start::Ptr{Cvoid} | ||
| range_stop::Ptr{Cvoid} | ||
| mark::Ptr{Cvoid} | ||
| end | ||
|
|
||
| """ | ||
| ROCTXTracer(library::AbstractString) | ||
|
|
||
| A tracer that calls the roctx API in `library`, i.e. `librocprofiler-sdk-roctx` for | ||
| `rocprofv3`, or `libroctx64` for the legacy `rocprof`. | ||
| """ | ||
| function ROCTXTracer(library::AbstractString) | ||
| handle = Libdl.dlopen(library) | ||
| return ROCTXTracer( | ||
| Libdl.dlsym(handle, :roctxRangeStartA), Libdl.dlsym(handle, :roctxRangeStop), | ||
| Libdl.dlsym(handle, :roctxMarkA) | ||
| ) | ||
| end | ||
|
|
||
| # a `Symbol` is passed to C as its name, without allocating | ||
| roctx_message(label, domain) = domain === KA.DEFAULT_DOMAIN ? label : string(domain, ": ", label) | ||
|
|
||
| # process ranges, rather than push/pop, since they may end on another thread | ||
| KA.trace_range_start(tracer::ROCTXTracer, label, domain) = | ||
| ccall(tracer.range_start, UInt64, (Cstring,), roctx_message(label, domain)) | ||
| KA.trace_range_end(tracer::ROCTXTracer, id::UInt64) = | ||
| (ccall(tracer.range_stop, Cvoid, (UInt64,), id); nothing) | ||
| KA.trace_mark(tracer::ROCTXTracer, label, domain) = | ||
| (ccall(tracer.mark, Cvoid, (Cstring,), roctx_message(label, domain)); nothing) | ||
|
|
||
| # rocprofv3 loads its tool through rocprofiler-register, and only intercepts the roctx of | ||
| # the rocprofiler-sdk; the legacy rocprof loads its tool into HSA, and intercepts libroctx64 | ||
| function roctx_libraries() | ||
| if haskey(ENV, "ROCP_TOOL_LIBRARIES") | ||
| return ["librocprofiler-sdk-roctx", "libroctx64"] | ||
| elseif haskey(ENV, "HSA_TOOLS_LIB") | ||
| return ["libroctx64", "librocprofiler-sdk-roctx"] | ||
| else | ||
| return String[] | ||
| end | ||
| end | ||
|
|
||
| function rocm_libdir() | ||
| rocm_path = try | ||
| AMDGPU.ROCmDiscovery.find_roc_path() | ||
| catch | ||
| get(ENV, "ROCM_PATH", "/opt/rocm") | ||
| end | ||
| return joinpath(rocm_path, "lib") | ||
| end | ||
|
|
||
| const TRACER = Ref{ROCTXTracer}() | ||
|
|
||
| function __init__() | ||
| # only under rocprof, as otherwise every annotation would be wasted work | ||
| names = roctx_libraries() | ||
| isempty(names) && return | ||
| library = Libdl.find_library(names, [rocm_libdir()]) | ||
| if isempty(library) | ||
| @warn "Running under rocprof, but roctx wasn't found; KernelAbstractions' ranges won't be recorded" names | ||
| return | ||
| end | ||
| TRACER[] = KA.register_tracer!(ROCTXTracer(library)) | ||
| return | ||
| end | ||
|
|
||
| end # module |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.