Skip to content

Releases: thanos/ex_systolic

v0.2.0

03 May 13:24

Choose a tag to compare

ExSystolic v0.2.0 Release Notes

Release date: 2025-05-03

ExSystolic is a BEAM-native systolic array simulator -- clocked spatial dataflow with explicit ticks, links, and processing elements. This release adds parallel execution, a pluggable topology abstraction, and proven cross-backend determinism.

Highlights

Parallel Backend

The new ExSystolic.Backend.Partitioned splits arrays into rectangular tiles and dispatches tile computations in parallel. Two dispatch strategies are available:

  • :tasks (default) -- uses Task.Supervisor.async_stream/4 with ordered: true. One supervised task per tile per tick.
  • :pool -- uses the :systolic_pool Poolex worker pool. Reuses long-lived GenServer workers, eliminating per-tick task spawn overhead.
# Default (Task.Supervisor)
Clock.run(array, ticks: 10, backend: :partitioned)

# With tile sizing
Clock.run(array, ticks: 10, backend: :partitioned, tile_rows: 4, tile_cols: 4)

# Poolex pool dispatch
Clock.run(array, ticks: 10, backend: :partitioned, dispatch: :pool)

Proven Determinism

Both backends follow the same 6-step Bulk Synchronous Parallel (BSP) contract:

  1. INJECT -- push external inputs into boundary links
  2. READ -- drain all link buffers
  3. EXECUTE -- run PE step functions (parallelised across tiles in partitioned backend)
  4. COLLECT -- gather PE outputs
  5. WRITE -- write outputs into link buffers
  6. RECORD -- append trace events (sorted by {tick, coord})

Conformance tests verify that interpreted and partitioned backends produce identical PE states and trace events for the same inputs. The partitioned backend's parallel execution cannot observe cross-tile writes within the same tick.

Pluggable Space Topology

The ExSystolic.Space behaviour now includes a links/2 callback. Space modules produce both boundary and internal links for a given direction, and Array.connect/2 delegates entirely to the space module. This eliminates the Grid2D special-case that leaked boundary-link behaviour.

Shared Link Operations

ExSystolic.Backend.LinkOps centralises inject_streams/2, drain_links/2, and write_pe_outputs/2 -- previously triple-duplicated across Clock, Interpreted, and Partitioned. One source of truth, one place for bugs.

New Modules

Module Purpose
ExSystolic.Backend.Partitioned Tile-based parallel backend
ExSystolic.Backend.PoolexWorker GenServer worker for pool dispatch
ExSystolic.Backend.LinkOps Shared link buffer operations
ExSystolic.Tile Tile data structure
ExSystolic.TilePartitioner Array-to-tile partitioning
ExSystolic.Application Supervision tree (TaskSupervisor + Poolex pool)

New APIs

  • Array.result_map/1 -- returns coord => state for any Space (not just Grid2D)
  • Array.input/3 -- accepts any atom port; raises on duplicate {coord, port} keys
  • PE.value(input, default) -- coerces :empty/nil to a default value
  • PE.present?(input) -- returns false for :empty/nil
  • Space.links(opts, direction) -- produces boundary + internal links for a direction

Bug Fixes

  • Trace event ordering was non-deterministic in partitioned backend
  • Link write/2 and read/1 doctests never executed
  • result_matrix/1 silently returned nil cells for non-Grid2D spaces
  • Array.input/3 silently overwrote duplicate streams
  • Range syntax incompatible with Elixir 1.20-rc
  • PoolexWorker.handle_call timeout risk on large tiles
  • Dialyzer opaque type violation in TilePartitioner

Quality Metrics

Metric v0.1.0 v0.2.0
Tests 95 185
Doctests 12 34
Coverage 95.4% 98.4%
Dialyzer errors 0 0
Credo issues 0 0
Doc warnings N/A 0

Breaking Changes

  • result_matrix/1 now raises ArgumentError for non-Grid2D spaces (previously returned matrix with nil cells). Use result_map/1 instead.
  • Array.input/3 now raises ArgumentError on duplicate {coord, port} keys (previously silently overwrote).
  • Clock.run/2 now raises ArgumentError for unknown backends (previously CaseClauseError).
  • Interpreted.read_inputs/2 has been deleted (was @doc false dead code).
  • Tile.boundary_inputs field has been removed from the Tile struct.
  • The Space behaviour now requires a links/2 callback.

Known Limitations

  • Trace.events list grows unbounded (no max_events or sampling)
  • Link mutation still uses List.replace_at (O(n)); indexed map storage not yet implemented
  • Latency > 1 links are not yet implemented
  • No sparse data support
  • No semi-ring abstraction

Upgrade Guide

  1. Update dependency: {:ex_systolic, "~> 0.2.0"}
  2. If you implemented a custom ExSystolic.Space, add a links/2 callback. For inspiration, see ExSystolic.Space.Grid2D.
  3. If you called Interpreted.read_inputs/2, switch to LinkOps.drain_links/2.
  4. If you accessed Tile.boundary_inputs, it has been removed; boundary links are managed globally by the partitioned backend.