google-research / google-research/tabfm

PyTorch backend silently accepts shapes/activations the JAX backend rejects (three cases)

Open
#90 0 comments 0 reactions 0 assignees View on GitHub
Dominant language
Python
Stars
2.6k
Forks
270
Avg merge
1d 7h
Merged PRs (30d)
1

Description

The two backends are meant to agree — `convert_and_upload.py` even runs a `verify_parity` step — but the PyTorch side accepts several inputs the JAX side rejects, and does so silently. Three concrete cases, all reproduced on `b15593e4c1111ddb5f4f30dd2957df2edbaa04ca` in a clean container.

**1. `d_model` not divisible by `nhead`**

JAX raises (`tabfm/src/jax/model.py:1148-1151`):

```python
if d_model % nhead != 0:
raise ValueError(f'd_model ({d_model}) must be divisible by nhead ({nhead})')
```

PyTorch just truncates (`tabfm/src/pytorch/model.py:102`): `self.nhead, self.hd = nhead, d_model // nhead`.

```
[torch embed_dim=7 nhead=2 (not divisible)] NO ERROR -> nhead=2 hd=3 -> nhead*hd=6 vs d_model=7
```

The model builds and runs while quietly dropping a dimension.

**2. RoPE dimension**

JAX asserts `dim >= 2` (`jax/model.py:250`). PyTorch's `RoPE.__init__` (`pytorch/model.py:84-87`) has no guard:

```
[Torch RoPE dim=1] NO ERROR -> (1,)
[Torch RoPE dim=0] NO ERROR -> (0,)
[Torch RoPE dim=2 (control)] NO ERROR -> (1,)
```

`dim=0` yields an empty frequency buffer, so the rotation silently becomes a no-op. Note also that `dim=1` and `dim=2` both produce shape `(1,)`.

**3. The supported activation sets differ**

JAX accepts `{relu, gelu, swiglu}` and raises a `ValueError` naming the valid options (`jax/model.py:1153-1155`). PyTorch's `get_activation` (`pytorch/model.py:41-47`) accepts `{relu, gelu, silu}` and raises a bare `KeyError`:

```
[Torch get_activation('silu')] NO ERROR -> silu
[Torch get_activation('swiglu')] KeyError: 'swiglu'
[Torch get_activation('nosuch')] KeyError: 'nosuch'
```

So `silu` works on one backend only, and an unknown name gives a message that does not say what was expected. (`swiglu` is handled earlier via the `self.swiglu` flag, so it works in practice through `MLP` — it is `get_activation` itself that disagrees.)

None of these is urgent, and the default TabFM shapes avoid all three. They matter because the failure mode is silence: a user who mis-specifies a shape gets a subtly wrong model from PyTorch and a clear error from JAX.

Disclosure: I used an AI assistant to help find these. I ran the reproductions myself.

Contributor guide

Open the contributing guide

Research direction

Start with the referenced validation and activation code in tabfm/src/pytorch/model.py, then compare it with the corresponding checks in tabfm/src/jax/model.py. Run the three reproductions in a clean container; done means PyTorch rejects invalid d_model/nhead and RoPE dimensions consistently and reports activation choices in line with the JAX backend.

Written by the indexing model from the issue text.

Assessment

Tech stack
python, pytorch
Domain
backend, machine-learning
Issue type
Bug
Difficulty
3/5
Estimated time
1-2 days
Activity status
Quiet
Clarity
Clearly specified
Newbie friendliness
70/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.