Skip to content

The unfed-seed restore runs once after the first solve, so a feed whose operand arrives in the second solve leaves a permanent phantom unknown beside its repair #939

Description

@khatchad

The dataflow's unfed-seed restore runs once, after the first solve, and a feed whose operand gains state only in the second solve then delivers its repair beside a restored ⊤ member that nothing can remove. The result is a value carrying {? of unknown, ? of float32} where the feed alone would have given {? of float32}: a phantom unknown, permanent under join. Its witness in this repository is expected to vanish when an upstream absence is repaired, and that disappearance will not be evidence that this is fixed.

The Mechanism

PythonTensorAnalysisEngine.performAnalysis solves the dataflow, then calls TensorTypeAnalysis.restoreUnfedSeeds, which puts a suppressed seed back on every fed destination whose state is still empty, then solves again so the restored members propagate. The restore is right for a feed that will never deliver: the destination would otherwise lose its tensor identity. It is wrong for a feed that delivers late: if the operand's state arrives during the second solve, the feed composes the real member, but the restored seed is already in the destination's state, and the transfer functions only add. The order of events decides the answer, and the restore has no way to tell "never" from "not yet".

Demonstrated

In the vendored gpt-2 fixture, with a temporary line in restoreUnfedSeeds (its unconditional entry line printed entered with 294 suppressed seeds, and 112 restores followed in the same run, so the counts below are measured): tf.split(x, 3, axis=2)'s result in MultiHeadAttention.call (value 16) and its three unpacked elements (values 30, 32, 34) had their {? of unknown} seeds restored in every context. The split declares a dtype-only feed over its input, the feed was installed in every context, and the input (c_attn's output, value 12) reads {? of float32} at the end. The post-feed state of the split result is {? of unknown}, {? of float32}: the feed delivered, after the restore. The operand was late because c_attn's own output was produced by seeds that were themselves restored at that same step (#938), so its state existed only in the second solve.

Why It Is Filed Now

Fixing that upstream absence gives the split's operand state at restore time, the phantom disappears, and the only witness for this timing flaw goes with it, without the flaw being fixed. The restore bites any operand that is late for any reason. The weight absence is merely what made this one late. So the record is made before the witness vanishes, and a later reading in which split_heads's parameter is clean should not be taken as this issue's closing condition. A closing condition that survives the upstream repair: a fixture in which a feed's operand is fed by a chain that becomes stateful only after a restore, reading no unknown member, or a restore that is re-checked after the second solve rather than committed after the first.

Where It Shows In Emitted Types

The split_heads parameter reads {? of unknown, ? of float32} in the vendored fixture, which is the two-member same-shape signature with one unknown dtype that #936 records for loop-carried values. Here there is no loop: the pair is manufactured by the restore.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions