Diagonal factor
cholesky factors the current diagonal tile.
COMPILER + PARALLEL RUNTIME
HT compiles operator calls and memory effects into a small symbolic program. At runtime, it resolves dependencies using actual addresses and schedules work within a fixed CPU and graph-state budget.
Explore execution ↓01 / THE WORKLOAD
Cholesky factorization takes a symmetric positive definite matrix A and finds a lower-triangular matrix L such that A = LLᵀ. The blocked algorithm partitions a matrix of width n into tiles of width b.
Each diagonal step factors one tile, solves the panel below it, then updates the trailing matrix. Independent tile operations can overlap. Operations that read or overwrite an earlier result must wait.
HT preserves those constraints while changing how work is represented and scheduled. It does not replace the numerical algorithms inside the operators.
cholesky factors the current diagonal tile.
trsm uses that diagonal to solve a tile below it.
madd updates a tile using two solved panel tiles.
Let m = n/b. With r tile rows remaining, one step issues 1 diagonal call, r panel calls and r(r+1)/2 updates. Sum over r = 0 … m−1:
N = m + m(m−1)/2 + m(m−1)(m+1)/6
= m(m+1)(m+2)/6
Grouping the innermost update loop leaves m diagonal tasks, m(m−1)/2 panel tasks and the same number of update groups: m² scheduled tasks.
These are exact counts for this source traversal. The compiler derives loops from source; the runtime does not contain a Cholesky count table.
At fixed n, halving b roughly multiplies call count by eight while dividing arithmetic per tile update by eight. Total leading-order arithmetic stays proportional to n³, but arguments, footprints, queues and dependency bookkeeping must be processed far more often. Grouping amortizes those costs at the price of less scheduling freedom.
02 / EXECUTION LAB
Compare a fully materialized graph with a limited live frontier. A group runs its update calls sequentially in one callback. The graph and timelines below show the same numerical work under each policy.
The starting example emphasizes registration overhead. HT-style selection chooses grouped streaming here and matches the explicit grouped policy. Reduce registration cost or change the graph and worker count to explore other tradeoffs.
Columns follow diagonal steps. Select a node or timeline bar to inspect its prerequisites. Arrows show read/write ordering at tile granularity.
Select a task to inspect its dependencies.
Horizontal gaps are idle time. Bar widths represent illustrative computation cost. The vertical marker follows the simulation clock.
Tile-level RAW, WAR and WAW dependencies, source-order registration, active-window backpressure and completion-driven release are explicit. Diagonal, panel and update calls cost 1, 2 and 3 time units. A group costs the sum of its calls. Registration costs the amount selected above per scheduled task, on a producer that can overlap worker execution.
The full graph pays all registration cost before execution and uses downstream task depth for priority. Streaming uses source-order ready selection. The demo’s automatic policy uses a 100-task cap to make the transition visible on a small graph; real HT uses 32K–64K tasks plus footprint, edge and argument guards. Factor three selects grouping above m > 3×workers.
These are teaching-model timings, not predicted Kunpeng performance. Cache locality, memory bandwidth, kernel-specific timing, caller participation, worker continuation, graph-reduction details and allocation costs are not simulated. Grouping changes task count, not the amount of arithmetic.
| Policy | Tasks | Peak live | Time units | Worker utilization |
|---|
03 / IMPLEMENTATION
LLVM extracts typed loop bounds, pointer expressions and operator calls. Automatic body inference inspects a separate copy of each available operator implementation, including supported helpers. It proves caller-visible effects and distinguishes private scratch from shared memory.
ABI checks, pointer provenance and execution-context checks reject unsupported calls. Source scalar helpers have a separate proof. The analysis bodies never enter executable numerical kernels. Opaque operators can use explicit trusted effect contracts.
The runtime reads and fingerprints the required metadata, then binds current dimensions and addresses. Dependencies follow actual bytes: different formal pointer arguments may alias. Read-after-write, write-after-read and write-after-write hazards all constrain execution.
A decoded-model cache saves repeat decoding. It still validates the complete file on every entry and stores neither an input-specific ready graph nor a matrix result.
| Mechanism | Implementation | Tradeoff / limit |
|---|---|---|
| Source preflight | Count supported tasks and arguments before graph allocation. Stop once enough is known to reject an oversized graph. | Unknown or saturated counts cannot justify exact reservation. |
| Packed graph storage | Reserve known node/argument sizes after the first task passes guards. Store arguments and successor lists contiguously. | Fine graphs still cost construction time and memory. |
| Transitive edge reduction | If W→R→V already orders two writers, omit redundant W→V after merging actual-address accesses. | Reachability must survive aliasing. Fewer edges do not guarantee lower wall time. |
| Priorities and continuation | Prefer deeper downstream paths. Continue a newly ready successor locally and publish other ready work. | Unweighted depth does not estimate numerical kernel cost. |
| Bounded source-loop groups | Compose a group’s effects from the fine model, then stream native loop callbacks through the live window. | Long groups can hide ready calls and create uneven worker loads. |
| Worker and shape reuse | Reuse thread teams and pointer-independent affine geometry. Bind current addresses separately. | Initial setup is timed. Reuse does not establish pointer disjointness. |
| Scalar stores and copying | Eligible fills avoid per-element queue traffic; the compact build enables a parallel-copy path. | Eligibility guards and source ordering remain necessary. |
A full graph exposes global lookahead but stores all tasks and dependencies. A bounded stream registers work in source order, waits when the live window fills, then releases capacity as tasks retire. “Ungrouped” does not mean “unbounded.” The older factor-three dispatcher selected between two compiled variants; it was not inherently a full-graph scheduler.
Current compact HT tries a fine graph under a checked task cap of min(65536, max(32768, 4096 × min(workers, affinity CPUs))). Other resource and geometry guards also apply. It then tries source-loop composition and finally bounded fine execution. Rejected graph attempts run no callbacks.
For n=2304, b=8, HT executes 4,022,880 numerical calls through 82,944 scheduled tasks: 288 diagonal calls, 41,328 panel calls and 41,328 update-loop groups. With 23 requested workers, the default native streaming window permits 92 live tasks. Neither the full call list nor all grouped tasks need to reside in the scheduler at once.
The current factorization model expands from 13 bytes to its complete 361-byte canonical form. Structural coding removes impossible choices and uses variable-length fields. Larger programs use larger files or general encodings. Thirteen bytes is an observed size, not a format limit.
Supported typed loops and memory effects are explicit. Unknown contexts, unsafe helpers and unsupported ABIs reject. Task/window bounds do not bound total RSS: matrices and exact-footprint fallback also consume memory. Matrices execute sequentially.
04 / MEASURED ON KUNPENG 920
Seven fresh-process repetitions on 24 physical CPUs, with strict memory binding to node 0. HT uses 23 background workers plus caller participation where supported. Serial and HT execute identical external numerical kernels. All outputs pass numerical verification and match serial bit for bit.
The 20-matrix batch and one-case large-input runs are separate campaigns. Timings include output allocation/copying, worker initialization, metadata loading, dependencies, scheduling, kernels and matrix writes. Input/output file operations and verification are outside compute timing. Process launch and dynamic loading appear in runner wall time.
| Case | n | b | Serial ms | HT ms | Serial / HT |
|---|
Separate per-case medians need not sum to the median batch total. Small cases can be slower under parallel execution because setup outweighs useful parallel work.
| n | b | Seed | Median s | Range s | Median peak RSS MiB |
|---|
No serial fallback occurred in these HT measurements. These results describe this implementation and shared-host environment, not a guarantee for every input or machine.
LLVM 22.1.8, GCC 10.3.1 headers. Execution source f56754c, test snapshot 9a8c789; later changes are documentation and publication cleanup. Full reference-enabled suites pass 66/66 on x86-64 and AArch64, with separate selected sanitizer checks.
Download current measurement summary (JSON) ↓