Profiler: Add counters and recompute statistics - #9164
Draft
abadams wants to merge 97 commits into
Draft
Conversation
Adds per-instance counters (Realizations, points_required_at_realization /
_production / _root, points_computed) so the profile distinguishes
multiple appearances of the same Func and can diagnose recompute. Wires
the corresponding markers (declare_box_required_at_*, declare_inlined,
declare_stage) through ScheduleFunctions, BoundsInference, and Inline,
and consumes them in a new InjectCounters pass.
Adds GPU support: marker-billed counters track Funcs inside GPU kernels,
hoisting non-uniform contributions out via bounds_of_expr_in_scope (or
Let / Select / max as appropriate) and flagging the Func with
counters_approximated when it has to do so. Injects a halide_device_sync
at the end of every GPU kernel launch under -profile so kernel time
gets billed to the launching producer rather than the next blocking
host operation.
Splits per-Func time aggregation from counter aggregation: runs that
complete between two sampler ticks contribute to per-Func counters but
not to per-Func time, with a separate billed_runs field for the time
denominator.
Threads halide_copy_to_host/device synthetic instances into the timeline
view (parented to the producer they sit inside) instead of pulling them
into separate sections at the end, and counts each copy invocation via
a new halide_profiler_count_host_device_copy runtime helper.
Adds report rules:
- mid-Func host<->device bouncing (forgotten device schedule on an
update def)
- counters_approximated note (per-Func) and unaccounted-runs
summary (per-pipeline)
- sliding-window-failure / RoundUp / GuardWithIf via the
realization/production/root/computed counters
Adds test/generator/profiler_instances_{generator,aottest}.cpp covering
the per-instance machinery, recompute counters, the impure-condition
approximation path, and host/device copy synthetics (the GPU pieces are
gated on get_target().has_gpu_feature()).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Adds halide_profiler_func_kind (func / overhead / thread_idle / malloc /
free / copy_to_host / copy_to_device) and a buffer_func_id field on
halide_profiler_func_stats. The runtime gets the kind/buffer_func_id
arrays through halide_profiler_instance_start alongside the existing
names/parents/canonical_ids.
Replaces three sites that previously did the equivalent work by parsing
names or hardcoding bookkeeping slot indices:
- Filtering empty bookkeeping rows from the table (used `i < 4` index
checks)
- Skipping bookkeeping and copy synthetics in the rules loop (used
`idx < 4` plus strstr for the copy-name suffix)
- The "stages computing on different devices" rule looking for both
directions of copy of a given Func's buffer (used name-prefix match)
The expensive_free pipeline-level check now looks up the free slot by
kind rather than p->funcs[3]. JSON dump now emits kind, buffer_func_id,
and canonical_id. The dead suffix_cut argument on print_func_row /
emit_name (left over from the section-header refactor) is gone.
The IR-side *_id constants in Profiling.cpp stay (the bookkeeping slot
indices still matter for stack tracking and the set_current_func
emission) — they're no longer how the report identifies the slots.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
halide_profiler_instance_state already uses "instance" to mean one in-flight pipeline invocation; in src/Profiling.cpp the same word was also doing duty for "one row in the per-Func stats array" (one appearance of a Func in the schedule, distinguishing repeated inlining sites or separately-realized update defs). The two senses are unrelated and the collision was confusing. Renames the per-row sense to "entry" throughout Profiling.cpp and the test files: IdInfo -> EntryInfo id_info -> entry_info id_for_instance -> id_for_entry instance_map -> entry_map instances_by_name -> entries_by_name approximated_instances -> approximated_entries resolve_instance_id -> resolve_entry_id PreAllocateInstances -> PreAllocateEntries get_func_instance_id -> get_func_entry_id Test helper instances_of -> entries_of, plus the per-row scenario assertions and comments. The runtime API (halide_profiler_instance_*, the `instance` variable referring to halide_profiler_instance_state) keeps the "instance" word — it's where it belongs. The test pipeline's filename and registered Generator name stay (those drive the AOT build path). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Removes a "Clearing func stats" debug-level-0 print left over from earlier development. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…e_box
declare_box_touched is a real annotation that bounds inference's
box_touched analysis follows: its first arg has to be a
Variable<Handle>(func.name()) so passes that substitute on names of
in-scope buffers transform it correctly. The new
declare_box_required_at_{realization,production,root} intrinsics are
profiler-only markers whose first arg is just a label for the report
and must not be confused with an in-scope reference (so it's a
StringImm).
The declare_box helper in ScheduleFunctions.cpp now picks the right
shape for the first arg based on the intrinsic. Restores the
extern-stage handling (correctness/extern_producer,
correctness/extern_output_expansion,
generator_aot_nested_externs_root/_inner) that regressed when the
refactor uniformly switched all declare_box_* intrinsics to StringImm.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…d loops
The previous version widened *every* impure Call in a vectorized loop,
which broadcast halide_trace_helper / make_struct calls to vector
types and produced LLVM-level signature mismatches (the assertion in
CallInst::init complaining about a bad signature) — for instance, the
vectorize_inlined subtest of correctness/compute_with would assert
during codegen.
The widening was only ever needed for the profiler's counter markers,
which encode per-lane counter contributions in the intrinsic's
type.lanes() and so have to be widened to match the surrounding loop's
lane count even when their args don't reference any vectorized var.
Restrict the special case to those:
- declare_inlined (bills InlinedCalls per lane)
- declare_box_required_at_realization / _production / _root
(bills points_required_at_* per lane via box_total)
inline_marker is gone after resolve_inline_markers, and declare_stage
is idempotent (no lane-count dependence), so neither needs the widening.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
resolve_inline_markers walks the IR looking for inline_markers that need to be replaced with declare_inlined intrinsics. The expectation was that every chain of markers sits inside some Provide (the production being billed), but extern stages can have markers in their call args (e.g. nested_externs_root reads an Input scalar Func through inline_marker as part of the args to halide_copy_to_device / nested_externs_inner) — those have no surrounding Provide. BuildInlineGraph already strips the inline_marker intrinsics during its walk, so the rewritten Stmt is well-formed. The previous code asserted on the missing Provide name; now we just return the rewritten Stmt without emitting a declare_inlined. The inlined work still happens; the profiler simply doesn't bill it to an entry, which is the right call for extern-stage arg evaluation anyway. Fixes generator_aot_nested_externs_root and _inner under HL_TARGET=host-profile. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Inline.cpp wraps every inlined call site in an inline_marker so the profiler can later stamp down a declare_inlined for its surrounding Provide. Extern stages don't have a surrounding Provide — their ScheduleFunctions-emitted IR is a LetStmt whose value is the extern call. Without an anchor, resolve_inline_markers asserted when inline_markers appeared in extern call args (which happens whenever a Func is inlined into an extern stage's scalar args). Wrap each extern call's value in a new pure intrinsic extern_stage_marker(name, value) under -profile. BuildInlineGraph recognizes the wrapper and uses the extern stage's name as the billing target for any inline_markers inside it, exactly as it would for a Provide. resolve_inline_markers gains an Evaluate handler so a top-level Evaluate of an inline_marker-bearing expression is treated as its own subtree (mirroring the Provide and LetStmt cases). The inline_markers may live in any combination of CSE-hoisted LetStmts above the extern call or directly in its args; both paths now route through process_inlining_subtree. Test case in profiler_instances inlines a Func into an extern stage's scalar arg and asserts that the inlined entry's parent in the report is the extern stage. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The BoundsInference Inliner had keep_inlined_calls=true under -profile,
wrapping each inlined call site in return_second(call, body) so
boxes_required could still see the original call when recording the box
required for the inlined Func. The wrapper served as a no-op at runtime
(return_second discards the first arg), but the wrapped Halide call
referenced a Func with no buffer or producer — once storage flattening
turned it into a load, codegen would fail with "Name not in Scope".
The leak path wasn't limited to extern bounds-query args: any time
boxes_required walked an expression produced by the Inliner, the args
of the original Halide calls could end up in the resulting Box's min/max
intervals (e.g. f(f(x)) with f inlined → box of outer f contains the
inlined-form of inner f(x), wrappers and all), and those intervals get
baked into the `.s0.x.min` / `.s0.x.max` LetStmts that flow into
runtime IR.
Switch the wrapper to inline_marker(call, body) — same dual-role
semantics (boxes_required sees the call; bounds-of-expr returns body),
but typed for our purpose. Strip every inline_marker unconditionally
in a single pass right after BoundsInference finishes; by then the
marker has done its job and the inlined Func has no codegen role.
Teach the bounds-of-expr rule in Bounds.cpp to treat inline_marker
like return_second (collapsed into the same is_intrinsic({...}) check
alongside if_then_else, which already shared the rule).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…/Halide into abadams/profile_recompute
CSE-hoisted Lets inside a Provide can chain such that each let's RHS
references the previous let's variable. Walking such a chain, the
Variable visitor splices the referenced let's roots into the current
let's roots. With a vector container, multiple Variable uses of the
same prior let duplicate that let's entire roots vector each time, so
chains of "let tk = t_{k-1}*t_{k-1} + t_{k-1}" snowball as 3^N. This
exhausted RAM on a real lookup-table-heavy pipeline.
Switch the let_roots containers to std::set<int>, which is the right
shape anyway — the roots of a let are a set of inlining-graph node ids.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Under -profile every inlined Func gets a per-stage entry in the BoundsInference stages list so its recompute counters can be tracked. For a chain of N inlined Funcs with non-trivial bounds two distinct quadratic costs appeared: 1) The construction of N declare_box_required_at_root intrinsics each carried a copy of the bounds chain in its args (no sharing across declarations). For a chain of length N each declaration's expressions had size O(N-k), so total IR text was O(N^2) — exposing any downstream pass (and HL_DEBUG_CODEGEN=2 printing) to O(N^2) work even though the underlying Expr DAG was O(N) via refcount. 2) The boxes_required walk ran once per stage in the inference loop. For inlined consumers in a chain each walk visited an O(N)-sized shared suffix of the inlined IR, accumulating to O(N^2) total construction work in BoundsInference itself. Notably the `boxes` map produced for inlined consumers was immediately discarded — there's a `continue` before the "expand to producers" loop, so the per-stage walk was pure waste. Two changes: - Stage::define_bounds for inlined Funcs no longer emits the full declare_box_required_at_root in place. Instead it stashes the box in a side map keyed by Func name and stamps a single-arg marker (the intrinsic with just the StringImm name) at the same scope. A post-pass RewriteDeferredRootBoxMarkers walks the resulting Stmt, finds runs of consecutive markers in each Block, joint-CSEs the corresponding box expressions, peels outer Lets into LetStmts that wrap the block of declarations, and rewrites each marker in place to a full declaration referencing the lifted let-bound subexpressions. Net effect: O(N) IR text instead of O(N^2), and the joint CSE itself runs on the (linear) shared DAG. - In the main BoundsInference relationship-computing loop, move the `if (consumer.inlined) continue;` check above the per-stage boxes_required block. Inlined consumers' producer bounds are picked up transitively through the outermost (non-inlined) consumer's walk (inline_marker's args[0] carries the original Halide call), so the per-inlined-consumer walk is redundant. Skipping it removes the O(N) work-per-stage that summed to O(N^2) over the chain. Together these bring a chain of N=512 inlined Funcs with select-based bounds from 653 s under -profile to 14 s, and N=128 from 3.6 s to 0.9 s. The remaining time is mostly LLVM codegen on legitimately N-sized IR and isn't profile-specific. Also filter profiler_instances.rungen out of GENERATOR_BUILD_RUNGEN_TESTS since its aottest provides a test_extern_stage callback that rungen doesn't link. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Two more places in BoundsInference were doing O(N) work per stage and summing to O(N^2) across a chain of N inlined Funcs: - The pure-inlining loop ran inliner.do_inlining() on every stage's exprs, even inlined stages whose exprs are never consulted (the relationship-computing loop already skips them via continue). Each call does mutate() + common_subexpression_elimination(), and the CSE alone is O(unique nodes) per call. Skip inlined stages here. - Inliner::get_qualified_body, called recursively as the chain expands, ran CSE on the result at every level. The recursion shape means the level-k cache entry includes the fully-inlined chain from level k+1 downward; running CSE at each level walks the same shared sub-DAG over and over. Defer CSE to the public do_inlining entry point — once at the top of the recursion is sufficient. mutate() still recurses through visit(Call) into get_qualified_body for each inlined sub-call, so the chain still resolves. Combined with the existing marker-deferral and the consumer.inlined skip in the relationship loop, this brings the N=512 chain test from 653 s (pre-fixes) to 10 s under -profile. Some residual super-linear cost remains in the joint-CSE-on-bundle marker rewrite — CSE's use_map uses IRGraphDeepCompare which is O(structure size) per comparison, so deep chains still pay there — but it's tolerable now (~22% of compile time at N=512). That's a separate follow-up. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Describe what the code does without forensic notes about alternatives or scaling reasoning. The latter belongs in commit messages and PR descriptions, not the source. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
In SkipStages::visit(Select), the two branches' per-Func .used / .loaded predicates were combined as `(t_used && cond) || (f_used && !cond)`. When both branches contributed the same Expr -- which is exactly what happens when both branches read the same let-stashed FuncInfo from an outer let -- make_or could not recognise the And nodes as equivalent (they aren't same_as even when their operands are), so the predicate roughly doubled in size at every nested Select. A long chain of CSE'd lets where each let value contains a Select then drove the predicate size to 2^N, well past the point where allocating the IR is feasible. Combine the two branches with `select(cond, t, f)` instead, and add a make_select helper that collapses `select(c, X, X) -> X` and the constant-cond cases. When both branches contributed the same Expr, make_select drops the condition immediately and the chain stays linear. The new correctness test (many_inlined_selects.cpp) constructs a 500- element CSE'd let chain whose values each carry a Param<bool>-gated Select, then feeds the chain into a final Select. With the bug present this test would not terminate -- skip_stages would crash allocating ~2^500 IR nodes long before any reasonable timeout fired. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
When an id is only touched on one branch of the Select, the previous code passed an undefined Expr to a `combine` helper that then turned `undefined` into const_false and built a `select(cond, X, false)` -- which is just `X && cond` dressed up as a select. Call make_and directly in those cases and keep make_select for the both-branches case, where the `select(c, X, X) -> X` collapse is the whole point. Also factor the "merge into old" body into a small helper to remove the duplication. No behaviour change. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Lowercase the Func name, drop the unnecessary top-level select and output schedule, and make each chain entry depend on chain.back() so nothing gets eliminated as dead. The test still reproduces the pre-fix exponential blow-up (verified by reverting the fix: it times out at 30s on a 500-element chain). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Bounds inference for inlined Funcs can produce root-box bounds whose arithmetic doesn't fit in int32 -- e.g. an index expression of shape (c1 - c2) * c3 over a wide interval, where simplify materialises a signed_integer_overflow intrinsic for the offending product. Those markers feed the recompute-ratio report and are nice-to-have only; letting them reach codegen turns into a user_error and breaks the whole compile for what is otherwise a profiling-only stat. Add a small pre-pass in inject_profiling that walks the IR with a Scope of "poisoned" let-binding names (a binding is poisoned if its value transitively contains a signed_integer_overflow intrinsic or a reference to another poisoned binding) and rewrites any declare_box_required_at_root whose args touch the poison set to make_zero. The subsequent simplify() in lower then drops the now- dead lets. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Adds an inlined Func whose bounds-inferred interval doesn't fit in int32 -- shape (uint16 - uint16) * c -- and feeds it through a buffer index. Without the poison-drop pre-pass in inject_profiling the generator user_errors during codegen; with it the test compiles and the wide_scaled Func still shows up in the profiler report as an inlined entry (we just lose the root-box count for table16, which is the bug we worked around). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
CI flagged them as unused after the earlier check pruning. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Two padding issues that caused bus errors on 32-bit builds: - kind was a uint8_t followed by an int (buffer_func_id), which inserts 3 bytes of implicit padding. Use the enum type itself (an int) so the field is exactly 4 bytes with no implicit padding. - With a 4-byte pointer at the start (name), the uint64_t counter region would land at a 4-aligned but not 8-aligned offset on 32-bit x86 (the i386 ABI allows uint64_t to be 4-aligned). Atomic 64-bit ops on cmpxchg8b require 8-byte alignment, so this would bus-error. Move the non-counter fields (parent, canonical_id, kind, buffer_func_id) to precede `time`, and mark `time` explicitly with HALIDE_ATTRIBUTE_ALIGN(8) so the compiler always inserts the needed padding regardless of target. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
find_or_create_pipeline took const uint64_t *func_names and indexed it
with an 8-byte stride. Halide's IR-side make_struct of the per-Func
Handle pointers codegens as [N x ptr], so on 32-bit each entry is only
4 bytes — the runtime would read every other entry as garbage past the
first half of the array. Main avoided this by using
Allocate(Handle(), {num_funcs}) + Stores (Halide's Handle type is fixed
at 64 bits regardless of target), but this branch uses make_struct.
Change the runtime signature to const char *const *func_names so the
stride matches the actual pointer width emitted by make_struct.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Allocation-group buffers (created by FuseGPUThreadLoops when it fuses shared/heap allocations) have names that don't match any Func, so the previous code skipped tracking them — the runtime would have aborted on func_id == -1, and even if it didn't, the bytes would have been dropped on the floor. Mint an allocation-kind entry for those buffers and bill the memory_allocate/memory_free calls to it. Render the row as the participating Funcs (e.g. "f1$0.0,f2$0.1.buffer") by splitting on the "allocgroup__" tag and joining with commas. The rendering is a placeholder pending a follow-up PR that splits the size across the participants via the counter machinery; the TODO in id_for_entry sketches the plan. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
…pute_without_counters
Move per-Func num_allocs and memory_total accounting from halide_profiler_memory_allocate into halide_profiler_update_counters, so the updates can hoist out of inner loops. For GPU-fused allocations that have no Allocate node left, emit a declare_allocation intrinsic at the strip site in FuseGPUThreadLoops so the counters mutator can still bill the per-Func contribution. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
abadams
marked this pull request as draft
May 29, 2026 18:54
Codecov Report❌ Patch coverage is Additional details and impacted files@@ Coverage Diff @@
## main #9164 +/- ##
==========================================
+ Coverage 70.21% 70.29% +0.08%
==========================================
Files 255 255
Lines 78895 79242 +347
Branches 18865 18956 +91
==========================================
+ Hits 55397 55705 +308
- Misses 17832 17888 +56
+ Partials 5666 5649 -17 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
…hub.com/halide/Halide into abadams/profile_recompute_with_counters
…pute_with_counters
fuse_gpu_thread_loops still hoists shared, heap, and register allocations and normalizes GPU loop structure, and still computes the lifetime/offset fusion analysis where the barrier structure is intact. But instead of erasing the per-Func allocations and rewriting their loads and stores onto the fused buffer, it now emits a backing allocation plus one aliasing Allocate per Func, whose new_expr is a new offset_pointer(backing, offset) intrinsic. Loads and stores keep their original per-Func names. A new flatten pass in inject_gpu_offload folds each aliasing allocation's offset back into its loads and stores and drops the aliasing nodes, reproducing the flat representation the device backends expect. It runs after the conceptual stmt is captured, so the profiler and the conceptual stmt see per-Func allocation names and stores can be attributed to a specific Func. Supporting fixes: - offset_pointer is a non-pure Intrinsic so CSE/LICM won't lift it out of new_expr and escape the backing allocation's scope. - RemoveDeadAllocations traverses new_expr, so a backing allocation referenced only through aliasing new_exprs isn't dropped as dead. - PartitionLoops no longer moves a let inside an Allocate when the let is used in the allocation's new_expr or condition (evaluated in the outer scope), which previously left a dangling reference for dynamically-sized allocations. - The flatten pass re-derives load/store alignment from the offset, rather than keeping the original alignment across a possibly misaligned shift. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
LowerWarpShuffles treated any non-shared allocation above a lane loop as per-lane register storage, striping its size by the warp size and moving the Allocate inside the lane loop. With GPU allocation fusing now emitting per-Func aliasing allocations, a heap allocation used inside a gpu_lanes loop (e.g. apps/iir_blur) reaches this pass as a real Allocate node and was wrongly striped, which also separated it from its Free and left a dangling reference. Heap is global memory and is never warp-level storage, so skip it alongside GPUShared. On main no heap Allocate nodes reach this pass, so this is a no-op there. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The per-Func names now live on the aliasing allocations, so the backing
allocation no longer needs a name concatenating every fused Func. Name
it unique_name("shared_alloc") or unique_name("global_alloc") instead of
the "allocgroup__f1__f2__..." monster name. The single-shared-allocation
case still names the backing after its Func, since there are no aliasing
wrappers there.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…pute_with_counters
…ions' into abadams/profile_recompute_with_counters
The block-level shared/heap and register allocation extractors in fuse_gpu_thread_loops previously disambiguated same-named allocations (e.g. from unrolling a loop that holds an allocation) by suffixing each with a counter. That suffix looks like a tuple index and obscures the Func name the profiler needs for attribution. Instead, keep each Func's own name and coalesce same-named allocations with disjoint lifetimes into one allocation sized to the largest, reusing the storage. Since loads and stores keep their names, the shared/heap extractor's load/store visitors now only track liveness (plus the per-thread index striping for allocations inside thread loops), and the register extractor's load/store visitors are gone entirely. Both extractors coalesce via an operator() override that runs immediately after the mutation. Also drops now-dead code: the AllocGroup::name accumulation, the write-only register_allocations scope, and a redundant local init. Adds correctness coverage for the repeated-realization case in both shared/heap and register memory using tuple-valued Funcs. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…ions' into abadams/profile_recompute_with_counters
…pute_with_counters
- Profiling.cpp: sum each counter over a closing-out GPU loop symbolically instead of collapsing to max-value then multiplying by the full extent. For a footprint-guarded contribution like select(guard, k, 0) we clip the iteration count to solve_for_outer_interval(guard) (an over-approximation, so still a conservative upper bound), which stops the GPU recompute ratios from being grossly inflated (e.g. unsharp's gray drops from 3.98 to 2.36). - Simplify_LT.cpp: move the c0 < select(x, c1, c2) / select(x, c1, c2) < c0 folds out of the no_overflow block. They do no arithmetic, so they are valid and terminating for every type; the counter code above needs them for its unsigned (UInt64) counters. Covered by a new simplify test. - profiler_common.cpp: emit hoist_storage allocation entries before the producer entries at each level of the report tree, so a Func's allocation row appears ahead of the computation it feeds (matching IR order, which compute_with otherwise inverts). Allocation entries are leaves, so the tree-art stays correct. - InjectHostDevBufferCopies.cpp: fix a compiler segfault when a 0-dimensional device-only allocation is compiled with the profiler on - the device-size marker read op->extents[0] out of bounds. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
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
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
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.
This is build on #9157 so ignore it until that one is in. Opening it for CI coverage. This adds the system of cheap counters. Output now looks like this:
This lets you see that sliding window failed for outGPyramid 3 and 4. The recompute ratios are massive. Previously this was lost in the benchmarking noise.