Skip to content

BC и GAIL падали на любой машине с GPU - #45

Merged
DenisDrobyshev merged 2 commits into
mainfrom
fix/imitation-on-a-gpu
Sep 20, 2026
Merged

DenisDrobyshev merged 2 commits into
mainfrom
fix/imitation-on-a-gpu

Conversation

@DenisDrobyshev

Copy link
Copy Markdown
Member
agent = BC(env)                       # device="auto" -> cuda
data  = collect_expert_dataset(...)   # device="cpu", всегда
agent.train(data)                     # RuntimeError на первом же батче
RuntimeError: Expected all tensors to be on the same device, but got mat1
is on cpu, different from other tensors on cuda:0

Это ровно то, как BC используется в README. И это никогда не работало на машине с GPU — а для библиотеки обучения с подкреплением это большинство машин.

Ничего придумывать не пришлось

ReplayBatch.to() уже существует, и его докстринг — это описание данного бага:

Lets an agent train from a dataset stored on a different device (for example a CPU-collected offline dataset feeding a CUDA agent) without a device mismatch.

CQL, IQL, TD3BC и DiffusionPolicy — все зовут его на той же строке, где сэмплят:

batch = dataset.sample(self.batch_size).to(self.device)

У decisionrl.imitation было два места, где сэмплят, и ни одно этого не делало. Теперь делают оба, ровно как четыре соседа.

У GAIL случай тоньше: свой policy-датасет он строит уже на self.device, так что «извне» приходит только экспертная сторона — и приходит всегда, потому что она каллерская, собранная collect_expert_dataset, у которого "cpu" захардкожен дефолтом и сказать иначе нечем.

Почему этого никто не видел

test_bc_predicts_and_round_trips, test_bc_imitates_expert и test_gail_imitates_expert падают на любой машине с GPU и проходят на каждом раннере CI, потому что у раннеров GPU нет.

Библиотека была зелёной ровно столько, сколько её проверяли только там, где баг невозможен.

Поэтому регрессионным тестам GPU тоже не нужен

Два из трёх проверяют шаг, а не падение — что каждый батч проходит через .to(self.device), — подглядывая за собственным to у батча. На раннере перенос — no-op, но его отсутствие всё равно роняет тест. Это и есть нужное свойство:

$ CUDA_VISIBLE_DEVICES=-1 pytest tests/test_imitation.py -q      # на НЕисправленном исходнике
FAILED test_bc_moves_every_batch_to_the_actors_device
FAILED test_gail_moves_the_expert_batch_to_the_discriminators_device
  Right contains 2 more items, first extra item: device(type='cpu')

То есть этот баг был бы пойман в CI, без всякого GPU.

Третий тест утверждает падение целиком и пропускается без CUDA — и говорит в докстринге, что CI его запустить не может, потому что именно необъявленность этой разницы и есть вся история.

Измерено

до после
test_imitation.py на CUDA-машине 3 failed, 2 passed 5 passed
test_imitation.py на CPU — 7 passed, 1 skipped
test_offline.py, test_diffusion.py passed passed (их не трогал — они были правы)

ruff check чист.

DenisDrobyshev and others added 2 commits September 20, 2026 21:44
    agent = BC(env)                       # device="auto" -> cuda
    data  = collect_expert_dataset(...)   # device="cpu", always
    agent.train(data)                     # RuntimeError on the first batch

    RuntimeError: Expected all tensors to be on the same device, but got mat1
    is on cpu, different from other tensors on cuda:0

That is the way the README uses BC, and it has never worked on a machine with a
GPU -- which, for a reinforcement learning library, is most of them.

Nothing here needed designing. `ReplayBatch.to()` exists, and its docstring is
this bug: "Lets an agent train from a dataset stored on a different device (for
example a CPU-collected offline dataset feeding a CUDA agent) without a device
mismatch." CQL, IQL, TD3BC and DiffusionPolicy all call it on the line they
sample. `decisionrl.imitation` had two sample sites and neither did. Both now
do, exactly as the four siblings do.

GAIL's is the subtler of the two: it builds its policy dataset on `self.device`
already, so only the expert side arrives from elsewhere -- and it always does,
because it is the caller's, built by `collect_expert_dataset`, which is a "cpu"
default with no way to say otherwise.

Why nobody saw it: `test_bc_predicts_and_round_trips`, `test_bc_imitates_expert`
and `test_gail_imitates_expert` fail on any GPU machine and pass on every CI
runner, because the runners have none. The library was green for as long as it
was only ever tested where the bug cannot happen.

So the regression tests do not need a GPU either. Two of them check the step
rather than the crash -- that each batch is routed through `.to(self.device)` --
by spying on the batch's own `to`. On a runner the move is a no-op, but its
absence still fails, which is the property that matters: on the unfixed source
with `CUDA_VISIBLE_DEVICES=-1`, both fail with `moved == []`. The third asserts
the crash end to end and skips without CUDA, and says in its docstring that CI
cannot run it, because that difference going unstated is the whole story here.

Measured on a CUDA machine: `tests/test_imitation.py` was 3 failed, 2 passed
before and is 5 passed after. On CPU it is 7 passed, 1 skipped. `test_offline.py`
and `test_diffusion.py` are unchanged either way -- they were already correct.
@DenisDrobyshev
DenisDrobyshev merged commit 4214712 into main Sep 20, 2026
11 checks passed
@DenisDrobyshev
DenisDrobyshev deleted the fix/imitation-on-a-gpu branch September 20, 2026 19:54
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant