Skip to content

fix(torch): densify ragged knn_search output in PointTransformer's knn_batch - #699

Open
dripston wants to merge 1 commit into
isl-org:mainfrom
dripston:fix/683-knn-batch-ragged-neighbors
Open

dripston wants to merge 1 commit into
isl-org:mainfrom
dripston:fix/683-knn-batch-ragged-neighbors

Conversation

@dripston

Copy link
Copy Markdown

Problem

Training PointTransformer on a point cloud (or a batch region) with fewer candidate points than k crashes:

RuntimeError: shape '[-1, 16]' is invalid for input of size 345

Root cause

open3d.ml.torch.ops.knn_search's own docstring states it "supports returning less than k neighbors if there are less than k points" and that its output format is "compatible with the radius_search and fixed_radius_search ops" — i.e. neighbors_index is ragged: its total length is not guaranteed to be a multiple of k, and neighbors_row_splits marks each query's actual sub-range.

knn_batch in ml3d/torch/models/point_transformer.py ignored this and called ans.neighbors_index.reshape(-1, k) directly. When every query has exactly k neighbors this happens to work; the moment any query has fewer, the reshape either raises (this issue) or, if the ragged total happens to still divide evenly by k, silently misaligns which neighbors belong to which query.

Fix

ml3d/torch/models/kpconv.py's batch_neighbors already solves the identical ragged-output problem for FixedRadiusSearch using open3d.ml.torch.ops.ragged_to_dense (values, row_splits, out_col_size, default_value). I applied the same op here, using neighbors_row_splits to densify to (num_queries, k).

One difference from kpconv.py's usage: knn_batch's result is used by queryandgroup to index directly into points/feat (points[idx.view(-1).long(), :]), so I can't reuse ragged_to_dense's out-of-range default_value (points.shape[0]) as the final padding — that would just trade the reshape crash for an IndexError. After densifying with that sentinel, padded slots are replaced with that query's own first (real) neighbor, which keeps every returned index valid and contributes a harmless, already-nearby duplicate to the fixed-size attention window instead of an arbitrary or out-of-range one. The one further edge case — a query with zero neighbors at all — falls back to point index 0. Distances get the same treatment, defaulting to 0.0 in that same all-empty case so no inf reaches the downstream attention/softmax.

Testing

  • Added test_pointtransformer_knn_batch_ragged_neighbors in tests/test_models_torch.py, using a 5-point cloud with k=16 (guaranteeing every query gets fewer than k neighbors, matching the issue's actual trigger condition) and asserting the returned idx/dist have the correct (n_points, k) shape with every index in [0, n_points).
  • Verified the padding logic in isolation with a pure-Python/torch reimplementation of ragged_to_dense (this environment has no compiled Open3D ML ops), covering: a full row (unaffected), a short row (padded with its own first neighbor), and a fully empty row (falls back to index 0) — all three produced correctly shaped, in-range output.
  • Confirmed via knn_search's own docstring (extracted from the open3d==0.20.0 wheel) that its ragged output shares neighbors_row_splits with FixedRadiusSearch's result, validating that the kpconv.py densification pattern applies unchanged.
  • python3 -m py_compile passes on both changed files.
  • Could not run the added test or the existing test_pointtransformer_torch here — this environment doesn't have the compiled open3d/open3d.ml.torch package with GPU ops. Please run pytest tests/test_models_torch.py -k pointtransformer in CI/a configured dev environment to confirm.

Fixes #683

🤖 Generated with Claude Code

…n_batch

knn_search (open3d.ml.torch.ops) documents that it returns fewer than k
neighbors per query whenever there are fewer than k candidate points --
its neighbors_index output is then ragged (not a multiple of k in
total length), and its neighbors_row_splits marks each query's
sub-range. knn_batch ignored this and called
ans.neighbors_index.reshape(-1, k) directly, which raises
"shape '[-1, 16]' is invalid for input of size N" (or silently
misaligns rows on a lucky divisor) whenever any query in a batch has
fewer than k neighbors -- e.g. a training config with widely spaced
points, or a point cloud smaller than k.

FixedRadiusSearch's ragged output hits the same shape elsewhere in
this repo (kpconv.py's batch_neighbors) and is already densified with
open3d.ml.torch.ops.ragged_to_dense using neighbors_row_splits; apply
the same op to knn_search's output here.

knn_batch's result is used by queryandgroup to index directly into
points/feat, so padding cannot use ragged_to_dense's out-of-range
default_value as-is (points.shape[0] would raise IndexError instead).
After densifying, replace padded slots with that query's own first
neighbor (a valid, already in-range index), and fall back to point 0
only for the pathological case of a query with zero neighbors at all.
Distances are padded the same way, defaulting to 0.0 in that same
all-empty edge case so no inf reaches downstream softmax/attention
weights.

Fixes isl-org#683

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
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.

Point transformer traning crashes if shape is not [n, 16]

1 participant