Conversation
c140368 to
447758e
Compare
56c4143 to
96f01ec
Compare
| import flydsl.compiler as flyc | ||
| import flydsl.expr as fx | ||
| from flydsl.expr import const_expr, gpu, range_constexpr, rocdl | ||
| from flydsl.expr import const_expr, gpu, ktrace, range_constexpr, rocdl |
There was a problem hiding this comment.
add an examples instead of chaning production kernels.
| return max(int(env.ktrace.buffer_bytes) // _ktrace.RECORD_BYTES - 1, 1) | ||
|
|
||
|
|
||
| _BUFFER = None |
There was a problem hiding this comment.
global buffer for all trace data?
| **Status: unstable.** The API, record format and output schema may change in any minor | ||
| release. See [`api_stability.md`](api_stability.md). | ||
|
|
||
| **Hardware: gfx942 (CDNA3) and gfx950 (CDNA4) only.** Other targets raise at trace time. |
There was a problem hiding this comment.
not only for cnda3, 4. There are 5, rdna...
There was a problem hiding this comment.
This commit focuses on API and architecture design. Support for other architectures will be developed and tested separately, and submitted as standalone commits once validated.
|
|
||
| ## Limitations | ||
|
|
||
| - gfx942/gfx950 only. |
There was a problem hiding this comment.
This commit focuses on API and architecture design. Support for other architectures will be developed and tested separately, and submitted as standalone commits once validated.
| return False | ||
|
|
||
|
|
||
| def _invalidate_ktrace_records() -> None: |
There was a problem hiding this comment.
Done — the three helpers that only touch ktrace's own state (invalidate_records, restore_names, require_matching_capacity) moved to ktrace.py; the rest stay in jit_function.py since they build MLIR attributes or pack the launch ABI. Also reverted the production GEMM in favour of examples/07-ktrace_wave_timeline.py, and moved the API to flydsl.expr.experimental.ktrace.
|
|
||
|
|
||
| @dsl_loc_tracing | ||
| def s_memrealtime(): |
There was a problem hiding this comment.
no need to expose so many apis in this file. and also set '_' for internal only
There was a problem hiding this comment.
Done
Removed them — s_memrealtime, s_getreg_hw_id and s_getreg_xcc_id had no callers anywhere in the tree, since the conversion pass emits llvm.amdgcn.s.memrealtime / s.getreg itself after the dialect migration. Deleted along with their two private helpers and the test file that only covered them, which also drops three names from fx.rocdl.*.
| _BUFFER = None | ||
|
|
||
|
|
||
| def ensure_buffer(): |
There was a problem hiding this comment.
not safe to in this way.
There was a problem hiding this comment.
Done
The consequence reproduces: four threads reaching a first traced launch together each call hipMalloc, so three buffers leak for the life of the process and every record written into them is never read — trace data lost silently. Fixed with a double-checked lock following the existing pattern in compiler/jit_executor.py (the common already-allocated path still takes no lock), plus a regression test confirmed to fail with assert 4 == 1 when the lock is removed rather than passing vacuously.
| @@ -0,0 +1,443 @@ | |||
| # SPDX-License-Identifier: Apache-2.0 | |||
| # Copyright (c) 2025 FlyDSL Project Contributors | |||
There was a problem hiding this comment.
Done — it's now flydsl.expr.experimental.ktrace, so the import path itself marks the feature as unstable rather than relying on the absence of all. Graduating it later becomes a visible move out of experimental/ instead of a one-line change.
| _hip = None | ||
|
|
||
|
|
||
| def _hip_lib(): |
ae834af to
4ecc5b1
Compare
| kernel entry, where each wave claims its whole slot range at once; per event the | ||
| cost is a timestamp, an uncontended bump of that wave's own counter, and the | ||
| record store. | ||
| """ |
There was a problem hiding this comment.
I like keeping a high-level name here. range_push / mark / collect
is the right author-facing surface — the same annotations should work on
another GPU later without rewriting kernels. Iluvatar can do an in-kernel
phase timeline too, and so can NV; the cheap way to get there is a
portable API plus a per-backend lowering, which is already the reason this
is a dialect instead of inlined stores.
The part that would make that follow-up hard is that this module currently
ships the CDNA record as if it were the API: xcc_id, wave slots,
s_memrealtime, HIP ktrace_buffer, and convert-fly-ktrace-to-rocdl
are all visible from the public entry points. A second backend then has
to either fake XCD topology or break the 32-byte layout.
Would you consider splitting it, even if gfx942/gfx950 stays the only
implemented target in this PR?
- Keep
fx.experimental.ktrace(andfly_ktrace.*) as the annotation
contract: names, push/pop vs start/end, payload, hostcollect/summary. - Treat clock source, workgroup identity, buffer allocation, and the 32-byte
encoding as backend details behindFlyKtraceToROCDL/ the HIP runtime.
Then a later FlyKtraceToIXDL (or NV) only has to implement those details.
Kernel authors keep the same high-level names, which is the point of not
calling this fx.rocdl.ktrace.
There was a problem hiding this comment.
This is still an experimental API, so the focus for now is getting the frontend surface and its stability right. Worth noting that most of it is already backend-agnostic — 6 of the 8 constants (the five KIND_* and UNPAIRED_SLOT) are consumed by the portable decoder in utils/ktrace_trace.py; only RECORD_BYTES and EVENTS_PER_WAVE are ROCm-shaped.
| imported by the package ``__init__`` -- doing so would pull ``flydsl.expr`` into | ||
| every ``import flydsl.runtime``, which today needs none of it. | ||
| """ | ||
|
|
There was a problem hiding this comment.
Related to the API split: could the extra kernel argument and the
ktrace compile hint be a backend hook rather than always-on in the
shared JIT path?
ROCm can keep today's HIP buffer + convert-fly-ktrace-to-rocdl. Other
targets would register their own buffer + conversion, or fail closed with
"ktrace is not implemented here" instead of compiling the annotations
away. That way FLYDSL_KTRACE_ENABLE=1 stays a high-level switch, and
enabling it on a backend that has no lowering is loud rather than an
empty Perfetto file.
There was a problem hiding this comment.
This is already fail-closed, and loudly — the check is in the conversion pass, so an unsupported arch fails at compile time rather than producing an empty trace. On a gfx1100 module:
$ fly-opt --convert-fly-ktrace-to-rocdl unsupported_arch.mlir
error: ktrace: in-kernel timestamps are not supported on target arch 'gfx1100';
supported: gfx942 (CDNA3) and gfx950 (CDNA4)
There's no path where tracing silently no-ops.
Kernel authors annotate phases with fx.experimental.ktrace -- mark, range_push/range_pop and range_start/range_end; each annotation records a timestamped 32-byte event that a post-processing step renders as a Perfetto timeline. The annotations are MLIR ops, not inlined code. The frontend emits one fly_ktrace op per call and convert-fly-ktrace-to-rocdl expands it into the record-writing form. That is the point of the change: keeping the annotation in the IR lets a pass reason about a trace event instead of an atomic and six stores. The pass runs just before convert-fly-to-rocdl, forced from both sides -- it emits fly.to_llvm_ptr, which only that pass lowers, and scf.if, which must survive until convert-scf-to-cf. Disabled by default: without FLYDSL_KTRACE_ENABLE=1, or the ktrace compile hint, an annotated kernel compiles byte-identically to an unannotated one. The expansion's invariants -- one contended atomic per wave, the record written by the first active lane, unsigned bounds compares -- are documented where they are emitted, in lib/Conversion/FlyKtraceToROCDL.
4ecc5b1 to
9e64d4f
Compare
Adds
fx.ktrace: in-kernel wave tracing for gfx942/gfx950. Annotate phases in akernel, run it, and get a Perfetto timeline of the phases you named with the
nesting you wrote -- which is what ATT, working from instructions after the fact,
cannot give.
Each annotation writes a timestamped 32-byte record into a device buffer, which
the host reads back and renders. Measured at ~40 ns per event on MI355X.
Why a dialect rather than emitting the records in the frontend
The frontend could build the record directly -- leader guard, slot claim, bounds
checks, stores -- and that is the smaller change. The problem is what any later
pass then sees: an atomic and six stores, with no op meaning "this is one trace
event". Keeping that meaning in the IR is what makes pass-level work possible at
all -- folding adjacent events, dropping a range that collapses, deciding in the
compiler rather than only at the Python
_enabled()check.The cost is one dialect (6 ops and a token type) plus one pass.
Key points
convert-fly-ktrace-to-rocdlruns just beforeconvert-fly-to-rocdl,forced from both sides: it emits
fly.to_llvm_ptr, which only that passlowers, and
scf.if, which must survive toconvert-scf-to-cf.entry block; per event only an uncontended bump of that wave's own counter. An
unguarded claim runs on all 64 lanes and exhausts the buffer 64x early.
in-place replacement: retyping a use does not retype an
scf.for's result orbody argument, and the loop-carried range is the case the token form exists
for -- the common path, not a corner.
environment, so one compilation cannot disagree with itself about the buffer it
was built against. Event ids are assigned by the pass and published as
fly_ktrace.event_names.assumed: no
fly_ktraceop, no atomic, no trace-buffer parameter.range_end,a buffer smaller than the bound compiled against it, a wave past its 255-event
budget: each yields a plausible timeline rather than an error, so each is
rejected loudly instead.
Testing
test_gemm_a16w16_gfx950.py -k acc_small_mwith tracing on: 20 passed, same as with it offUntraced compiles were checked to produce a byte-identical pass pipeline.
Notes for review
gfx942/gfx950 only; a non-CDNA target is refused with a diagnostic naming ktrace.
Known limits, documented rather than papered over: one device per process, graph
replay is not traced correctly, and both ends of a range must sit under the same
guard.
docs/ktrace_guide.mdcovers the API, the settings, the cost model(including where the ~40 ns does not hold), and a "How it is implemented"
section pointing at the dialect and the pass.