pymc-devs / pymc-devs/pytensor

scan_push_out_seq doesn't push a sequence-dependent Blockwise out of the loop

Open
#2,357 1 comment 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

enhancement graph rewriting performance scan
Dominant language
Python
Stars
644
Forks
208
Avg merge
2d 14h
Merged PRs (30d)
16

Description

scan_push_out_seq only relocates Elemwise and DimShuffle, so a Blockwise applied per timestep — the input projection of any RNN, but equally any batched solve or cholesky — stays in the loop as N small ops instead of one batched one. (scan_push_out_dot1 is a different shape, a sit-sot accumulator read at [-1], and doesn't fire here.)

import timeit

import numpy as np
import pytensor
import pytensor.tensor as pt

from pytensor.scan import scan
from pytensor.scan.op import Scan

floatX = pytensor.config.floatX
W = pytensor.shared(np.random.default_rng(0).normal(size=(256, 256)).astype(floatX))
X = pt.tensor("X", shape=(200, 64, 256))
h0 = pt.zeros((64, 256), dtype=floatX)
X_np = np.random.default_rng(1).normal(size=(200, 64, 256)).astype(floatX)


def build(step, sequence=X):
    out = scan(step, sequences=[sequence], outputs_info=[h0], return_updates=False)
    return pytensor.function([X], out)


def inner_ops(fn):
    [node] = [n for n in fn.maker.fgraph.apply_nodes if isinstance(n.op, Scan)]
    return [str(n.op) for n in node.op.fgraph.apply_nodes]


def ms(fn):
    fn(X_np)
    return min(timeit.repeat(lambda: fn(X_np), number=5, repeat=5)) / 5 * 1e3


print(inner_ops(build(lambda x_t, h: pt.tanh(pt.exp(x_t) + h))))
# ['Composite{tanh((i0 + i1))}']         exp is gone, applied to the whole sequence at once

inside = build(lambda x_t, h: pt.tanh(x_t @ W + h))
print(inner_ops(inside))
# ['Dot', 'Composite{tanh((i0 + i1))}']  the dot stays, running 200 small matmuls

hoisted = build(lambda p_t, h: pt.tanh(p_t + h), sequence=X @ W)
print(f"{ms(inside):.1f} ms inside vs {ms(hoisted):.1f} ms hoisted by hand")
# ~24 ms inside vs ~21 ms hoisted by hand

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 by reproducing the example and reading scan_push_out_seq alongside scan_push_out_dot1, then inspect the Scan inner graph and its Blockwise nodes. Done means a sequence-dependent Blockwise operation is moved out of the loop as one batched operation, while the existing loop behavior remains correct and the reported inside-versus-hoisted performance gap is addressed.

Written by the indexing model from the issue text.

Assessment

Tech stack
python
Domain
compilers, performance
Issue type
Feature
Difficulty
3/5
Estimated time
1-2 days
Activity status
Quiet
Clarity
Mostly clear
Newbie friendliness
58/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.