patrick-kidger / patrick-kidger/diffrax

Speeding Up Evaluation of Padded Time Series Using Events?

Open
#602 5 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

question
Dominant language
Python
Stars
2.1k
Forks
189
Avg merge
3d 18h
Merged PRs (30d)
1

Description

I am currently training an online NCDE on a set of time series that are irregularly sampled and have varying lengths. To batch these time series, I pad them with the last (t_{N_i}, x_{N_i}) pair such that each time series has the same number of observations N_max, where N_i and N_max are the number of observations in the i'th and most observed time series, respectively.

I need to evaluate the NCDE at each observation in a time series due to the online nature of the model. Since in most cases N_i << N_max, this results in a large amount of wasted calculation. My thinking was to use an Event to stop the integration early for each time series. I've implemented a MWE below, where I've replaced the NCDE with an ODE.


import jax
jax.config.update("jax_enable_x64", True)

import diffrax
import numpy as np
from time import time

def f(t, y, args):
    return -y

N = 50
step = 0.2
cols = np.arange(N+1) * step
rows = np.arange(N+1)[:, None]
ts = np.clip(cols, 0, rows)[1:]

@jax.jit
def solve_ode_with_event(ts, idx_max):

    def cond_fn(t, y, args, **kwargs):

        return t >= jax.lax.dynamic_index_in_dim(ts, idx_max, keepdims=False)

    term = diffrax.ODETerm(f)
    solver = diffrax.Tsit5()
    saveat = diffrax.SaveAt(ts=ts)
    event = diffrax.Event(cond_fn)

    y0 = 1
    solution = diffrax.diffeqsolve(
        term,
        solver,
        t0=ts[0],
        t1=ts[-1],
        dt0=0.1,
        y0=y0,
        saveat=saveat,
        event=event
    )

    return solution

@jax.jit
def solve_ode(ts):

    term = diffrax.ODETerm(f)
    solver = diffrax.Tsit5()
    saveat = diffrax.SaveAt(ts=ts)
    stepsize_controller = diffrax.PIDController(rtol=1e-3, atol=1e-6)

    y0 = 1
    solution = diffrax.diffeqsolve(
        term,
        solver,
        t0=ts[0],
        t1=ts[-1],
        dt0=0.1,
        y0=y0,
        saveat=saveat,
        stepsize_controller=stepsize_controller
    )

    return solution

_ = solve_ode(ts[0])
_ = solve_ode_with_event(ts[0], 5)

n_runs = int(1e4)

t_odes = np.zeros(N)
for i in range(N):
    print(i)
    t0 = time()
    for j in range(n_runs):
        _ = solve_ode(ts[i])
    t_ode = (time() - t0) / n_runs
    t_odes[i] = t_ode

t_events = np.zeros(N)
for i in range(N):
    t0 = time()
    for j in range(n_runs):
        _ = solve_ode_with_event(ts[i], i+1)
    t_event = (time() - t0) / n_runs
    t_events[i-1] = t_event

speed_ups = t_odes / t_events

Running this example, I find that the event-based approach is slower across the board compared to simply just running the diffeqsolve over the padded time series and does not seem to vary significantly with the choice of time to stop integration at.

As far as I understand, in the case where an event is detected then the remaining steps of the solver are calculated using the value of the ode at t_event to ensure static array shapes, after which these steps are masked out. I'd think the observed slow down when using Events is then simply due to the additional overhead of evaluating events at each step.

Is this understanding correct? Or would we be able to get speed ups using this approach for more costly ODE evaluations? This relates somewhat to #601 .

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start with the supplied MWE and compare the compiled behavior of diffrax.diffeqsolve with and without diffrax.Event across different stopping indices. Trace the event and padded-save paths, then establish whether event overhead or masked solver work dominates; done means a supported conclusion about the performance tradeoff, ideally with a reproducible benchmark.

Written by the indexing model from the issue text.

Assessment

Tech stack
python
Domain
performance
Issue type
Feature
Difficulty
5/5
Estimated time
Over a week
Activity status
Stale
Clarity
Needs clarification
Newbie friendliness
30/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.