Top-down reading of Wu et al., CVPR 2024

Point Transformer V3 하향식 해부

CNN까지만 아는 사람이 Point Transformer V3(PTv3)를 수식과 코드 수준까지 이해하기 위한 자료입니다. 완성된 모델을 먼저 보고, 그 모델이 서 있는 부품들로 내려가며, 각 부품을 낳은 논문을 CNN의 언어로 다시 설명합니다. 마지막에는 실제 텐서 shape을 따라가며 처음의 그림으로 되돌아옵니다.

기준 논문 Point Transformer V3: Simpler, Faster, Stronger (arXiv 2312.10035) 기준 코드 Pointcept · PT-v3m1 전제 지식 CNN, 역전파, 기본 선형대수

읽는 법

왜 하향식인가

상향식으로 읽으면(PointNet → 어텐션 → Swin → PTv1 → PTv2 → PTv3) 각 논문을 읽는 동안 “이게 결국 어디에 쓰이는지”를 모른 채 세부를 외우게 됩니다. 이 문서는 반대로 갑니다. 먼저 PTv3의 완성된 구조를 모르는 단어가 있어도 괜찮다는 전제로 한 번 훑고, 각 단어가 왜 필요한지를 질문으로 남긴 채 한 단계씩 내려갑니다. 모든 선행 논문 절은 마지막에 “PTv3로 되돌아가기” 상자로 끝나며, 그 논문의 어떤 조각이 PTv3의 어느 줄에 남아 있는지 짚습니다.

  1. LEVEL 0전체
    PTv3가 푸는 문제, 주장, 결과, 전체 구조. 여기서는 용어를 “정의”하지 않고 “위치”만 잡습니다.
  2. LEVEL 1부품
    PTv3를 다섯 부품으로 분해하고 forward 흐름을 따라갑니다. 각 부품이 어떤 선행 지식을 요구하는지 목록을 만듭니다.
  3. LEVEL 2선행 지식
    열두 개의 절. 각 절은 한 논문(또는 한 주제)을 CNN 사용자 관점에서 상세히 설명합니다. 수식, 직관, 한계, 그리고 PTv3에 남은 흔적.
  4. LEVEL 3재조립
    텐서 shape으로 블록 하나를 완주하고, 풀링 코드를 줄 단위로 읽고, 논문의 설계 결정(ablation)을 이해한 상태에서 다시 봅니다.
CNN 사용자에게

이 문서 전체를 관통하는 비유는 하나입니다. 합성곱은 “고정된 커널 가중치 × 격자 위 이웃”이고, 어텐션은 “입력이 스스로 계산한 가중치 × 임의의 이웃 집합”입니다. 포인트 클라우드에서는 “격자 위 이웃”이 존재하지 않기 때문에, 3D 인식의 역사는 결국 “이웃을 어떻게 정의하고, 가중치를 어떻게 만들 것인가”를 두고 벌어진 선택의 역사입니다. PTv3는 그 선택을 “이웃 = 직렬화된 순서에서 인접한 1024개, 가중치 = 표준 dot-product 어텐션”으로 극단적으로 단순화한 모델입니다.

LEVEL 0완성된 모델을 먼저 본다

PTv3 한눈에

푸는 문제

입력은 포인트 클라우드입니다. 3D 스캐너(RGB-D 카메라, LiDAR)가 뱉어낸 점의 집합으로, 각 점은 좌표 (x, y, z)와 부가 특징(색상 RGB, 법선, 반사 강도 등)을 갖습니다. 한 장면은 보통 수만에서 수백만 개의 점입니다. 대표 과제는 의미 분할(semantic segmentation) — 모든 점에 “의자, 바닥, 벽…” 같은 클래스를 붙이는 것 — 이고, 그 외에 인스턴스 분할, 물체 검출, 사전학습된 백본으로서의 전이 등에 쓰입니다.

이미지와 결정적으로 다른 점 두 가지: 점들이 격자 위에 놓여 있지 않고(좌표가 실수, 밀도가 불균일), 점의 순서에 의미가 없습니다(집합). 이 두 성질 때문에 2D 합성곱을 그대로 옮길 수 없습니다. Level 2의 첫 절에서 자세히 다룹니다.

논문의 주장 한 문장

“정교한 설계보다 규모(scale)가 성능을 더 크게 좌우한다. 그러니 정확도를 조금 희생하더라도 효율을 극단적으로 높여 모델과 데이터를 키우자.” 부제 Simpler, Faster, Stronger가 이 순서로 배열된 이유입니다. 구체적으로 PTv3는 전작 PTv2에서 다음을 제거했습니다.

  • kNN 이웃 탐색직렬화(serialization): 점들을 공간 채움 곡선(Z-order, Hilbert)을 따라 한 줄로 세우고, 그 줄에서 인접한 점들을 이웃으로 삼습니다. 이웃 탐색이 정렬 한 번으로 바뀝니다.
  • 벡터 어텐션(채널마다 다른 가중치) → 표준 스칼라 dot-product 멀티헤드 어텐션: FlashAttention 같은 고도로 최적화된 커널을 그대로 쓸 수 있게 됩니다.
  • 상대 위치 인코딩(RPE)xCPE: 어텐션 앞에 희소 합성곱 한 층을 두어 위치 정보를 “암묵적으로” 주입합니다.
  • 격자 풀링(grid pooling)직렬화 풀링: 직렬화 코드의 비트를 오른쪽으로 밀어 병합 그룹을 만듭니다. 별도의 격자 해시가 필요 없습니다.

그 결과, 논문 보고 기준으로 PTv2 대비 추론 속도 3.3배, 메모리 10.2배 절감, 한 점이 한 번의 어텐션에서 보는 이웃 수(수용장)는 16 → 1024로 늘었습니다. 파라미터는 12.8M → 46.2M으로 오히려 커졌는데도 그렇습니다.

결과 (논문 Table 기준, mIoU)

벤치마크종류PTv2PTv3PTv3 + PPT
ScanNet v2 val실내, 20클래스75.477.578.6
ScanNet200 val실내, 200클래스30.235.236.0
S3DIS Area 5실내71.673.474.7
nuScenes val실외 LiDAR80.280.481.2
SemanticKITTI val실외 LiDAR70.375.576.9

PPT(Point Prompt Training)는 여러 데이터셋을 함께 학습하는 기법으로 Level 2 마지막 절에서 다룹니다. 표의 소수점 수치는 논문 표에서 옮긴 것이며, 재현 환경에 따라 ±0.5 정도 달라집니다.

전체 구조

CNN 사용자에게 가장 익숙한 형태로 말하면 PTv3는 U-Net입니다. 인코더가 다섯 단계(stage)로 점점 점을 줄이며 채널을 늘리고, 디코더가 네 단계로 다시 점을 늘리며 스킵 연결로 인코더 특징을 더합니다. 다만 각 단계 안의 “합성곱 블록”이 “직렬화 어텐션 블록”으로 바뀌어 있고, “풀링”이 “직렬화 풀링”으로 바뀌어 있습니다.

PointN×6 serializationsparsifyEmbedding enc02 × 32 enc12 × 64 enc22 × 128 enc36 × 256 enc42 × 512 SerializedPooling ×4 (stride 2) dec32 × 256 dec22 × 128 dec12 × 64 dec02 × 64 SerializedUnpooling ×4 + 스킵 연결 (점선) seg head64→20 “2 × 32” = 블록 2개, 채널 32. 헤드 수는 채널/16 (2,4,8,16,32). 패치 크기 1024.
ScanNet 설정의 PTv3 (PT-v3m1). 인코더 깊이 (2,2,2,6,2), 채널 (32,64,128,256,512), 디코더 깊이 (2,2,2,2), 채널 (64,64,128,256). Swin과 같은 “세 번째 단계를 가장 깊게” 관행을 따릅니다. 코드: pointcept/models/point_transformer_v3/point_transformer_v3m1_base.py, 설정: configs/scannet/semseg-pt-v3m1-0-base.py.

블록 하나의 모양

모든 인코더·디코더 단계는 같은 Block을 반복합니다. 순서는 xCPE → LayerNorm → 직렬화 어텐션 → LayerNorm → MLP이고 세 곳 모두 잔차 연결이 있습니다. ResNet의 basic block을 아는 사람에게는 “conv-BN-ReLU-conv-BN + skip”의 자리에 “norm-attention + skip, norm-MLP + skip”이 들어간 형태(pre-norm Transformer 블록)에, 맨 앞에 3×3×3 희소 합성곱 잔차 하나가 추가된 구조입니다.

# Block.forward — 실제 코드 축약
shortcut = point.feat
point = self.cpe(point)                 # SubMConv3d(3³) → Linear → LayerNorm
point.feat = shortcut + point.feat      # xCPE 잔차

shortcut = point.feat
point = self.norm1(point)               # pre-norm
point = self.drop_path(self.attn(point))# SerializedAttention (패치 1024)
point.feat = shortcut + point.feat

shortcut = point.feat
point = self.norm2(point)
point = self.drop_path(self.mlp(point)) # Linear(C→4C) → GELU → Linear(4C→C)
point.feat = shortcut + point.feat
여기서 남는 질문 (Level 1·2가 답한다)
LEVEL 1부품으로 분해한다

다섯 개의 부품

PTv3를 아래 다섯 부품으로 나누면, 각 부품이 요구하는 선행 지식이 겹치지 않게 정리됩니다. 오른쪽 열이 Level 2에서 읽을 절입니다.

부품PTv3에서의 구현요구하는 선행 지식
① 입력 표현Point 딕셔너리: coord, grid_coord(복셀 정수 좌표), feat, offset/batch. 데이터 파이프라인의 GridSample(grid_size=0.02)가 0.02 m 복셀당 한 점만 남김.3D 데이터와 CNN의 한계, PointNet++(샘플링 계보), 희소 합성곱(복셀 표현)
② 직렬화Point.serialization(): grid_coord를 Z-order/Hilbert 코드로 바꾸고 argsort. 네 가지 순서(z, z-trans, hilbert, hilbert-trans)를 한꺼번에 만들어 블록마다 번갈아 사용.공간 채움 곡선, OctFormer / FlatFormer
③ 블록xCPE(SubMConv3d) + SerializedAttention(패치 1024, FlashAttention varlen) + MLP. Pre-norm, LayerNorm, GELU, DropPath.Transformer, ViT/Swin, PTv1/v2, 위치 인코딩, FlashAttention, 정규화
④ 풀링 / 언풀링SerializedPooling: 코드를 3비트 오른쪽 시프트 → 같은 코드끼리 uniquesegment_csr(max). SerializedUnpooling: 저장해 둔 pooling_inverse로 되돌리고 스킵을 더함.PointNet++(계층 구조), PTv2(격자 풀링), 공간 채움 곡선(비트 구조)
⑤ 학습 레시피AdamW(lr 0.006, wd 0.05, 블록은 lr 0.0006), OneCycle, 800 epoch, CE + Lovasz 손실, 강한 증강(회전·스케일·색상 지터·SphereCrop 102,400점), 선택적으로 PPT 다중 데이터셋.정규화·정칙화·손실, PPT

forward 흐름

모델의 forward는 놀랄 만큼 짧습니다. 이 네 줄의 각 단어가 이후 모든 절의 제목이 됩니다.

def forward(self, data_dict):
    point = Point(data_dict)
    point.serialization(order=self.order, shuffle_orders=self.shuffle_orders)  # ② 직렬화
    point.sparsify()                                                           # ① spconv 텐서 준비
    point = self.embedding(point)   # SubMConv3d(k=5) → BN → GELU
    point = self.enc(point)         # [SerializedPooling] + Block × depth, 5단계
    point = self.dec(point)         # SerializedUnpooling + Block × 2, 4단계
    return point

Pointaddict.Dict를 상속한 딕셔너리로, 배치 전체의 점을 한 줄로 이어 붙인 텐서로 들고 다닙니다. 이미지처럼 (B, C, H, W)로 쌓을 수 없기 때문에(장면마다 점 개수가 다름) feat(N_total, C)이고, 어느 점이 어느 장면 소속인지는 batch(길이 N의 정수 배열) 또는 offset(장면별 누적 끝 인덱스)으로 표현합니다. 이 “패킹된 배치” 관행은 PointNet++ 이래 모든 포인트 클라우드 라이브러리가 공유하며, FlashAttention의 varlen 인터페이스와 정확히 맞물립니다.

직렬화가 만들어 두는 세 텐서
  • serialized_code (K, N): 순서 종류 K개(기본 4)에 대한 64비트 정수 코드. 상위 비트에 배치 인덱스, 하위 3·depth비트에 공간 코드.
  • serialized_order (K, N): argsort(code). “곡선을 따라 i번째로 오는 점의 원래 인덱스”.
  • serialized_inverse (K, N): 그 역순열. “원래 j번째 점이 곡선에서 몇 번째인가”. 어텐션 후 결과를 원래 순서로 되돌릴 때 사용.

어텐션 블록은 feat[order]로 줄을 세우고, 1024개씩 잘라 패치를 만들고, 패치 안에서만 어텐션을 한 뒤 feat[inverse]로 되돌립니다. 블록 iorder_index = i % K를 써서 순서 종류를 번갈아 쓰고, 매 forward마다 순서 목록 자체가 무작위로 섞입니다(shuffle_orders).

LEVEL 2각 부품을 낳은 논문으로 내려간다

선행 지식 열두 절

순서는 “PTv3를 읽는 데 필요한 순서”이지 발표 연도순이 아닙니다. 각 절은 문제 → 핵심 아이디어 → 수식·알고리즘 → 한계 → PTv3로 되돌아가기의 순서로 씁니다.

2.1

3D 데이터와 CNN의 한계

CNN이 이미지에서 강력한 이유를 세 가지 성질로 쪼개 보면, 포인트 클라우드에서 무엇이 깨지는지가 보입니다.

CNN이 기대는 성질이미지포인트 클라우드
정규 격자(regular grid)픽셀은 정수 좌표. “왼쪽 위 이웃”이 항상 정의됨.실수 좌표. 이웃은 반경/개수로 정의해 주어야 함. 밀도가 센서 거리에 따라 수십 배 차이.
밀집(dense)모든 픽셀에 값이 있음. 메모리 = H·W·C.공간의 99% 이상이 비어 있음. 3D 격자로 만들면 메모리·연산이 빈 공간에 낭비됨(2 cm 복셀로 10 m 방 = 1.25억 셀).
순서 고정픽셀 배열 순서가 의미를 가짐(래스터 순서).점의 저장 순서는 임의. 순서를 바꿔도 같은 장면 → 모델은 순열 불변이어야 함.

초기 시도들은 이 셋 중 하나를 억지로 복원했습니다. 복셀화 + 3D CNN(VoxNet, 2015)은 격자를 복원하지만 밀집 텐서 때문에 해상도가 32³ 정도에 묶였습니다. 다시점 투영(MVCNN, 2015)은 2D CNN을 재활용하지만 가림과 기하 정보 손실이 큽니다. 그래서 두 갈래가 살아남았습니다.

  • 점 기반(point-based): 격자를 포기하고 집합 위에서 직접 연산. PointNet → PointNet++ → KPConv, PointConv → Point Transformer v1/v2. “이웃”을 kNN이나 반경 탐색으로 매번 계산.
  • 복셀 기반(voxel-based)이되 희소(sparse): 격자를 유지하되 점이 있는 셀만 저장·연산. SSCN → MinkowskiNet → SpConv. 합성곱을 거의 그대로 씀.

PTv3는 흥미롭게도 두 갈래를 모두 씁니다. 데이터는 복셀화(GridSample)되어 정수 grid_coord를 갖고, 위치 인코딩과 stem은 희소 합성곱이며, 주 연산은 점 기반 어텐션입니다. 다만 kNN은 쓰지 않고, 정수 격자 좌표가 있다는 사실을 이용해 정렬 한 번으로 이웃을 정의합니다.

PTv3로 되돌아가기

GridSample(grid_size=0.02)는 학습 시 복셀당 점 하나를 무작위로 남기고, 테스트 시 모든 점을 남기되 복셀 단위로 예측을 되돌립니다(return_inverse). 이 단계에서 만들어진 grid_coord가 직렬화(2.7), 희소 합성곱(2.3), 풀링(Level 3) 모두의 공통 입력입니다. 즉 PTv3는 “정수 좌표를 가진 희소 복셀 집합”을 입력으로 보는 모델입니다.

2.2

PointNet과 PointNet++: 집합 위의 신경망

논문
Qi et al., PointNet: Deep Learning on Point Sets for 3D Classification and Segmentation, CVPR 2017 (arXiv 1612.00593) · Qi et al., PointNet++: Deep Hierarchical Feature Learning on Point Sets in a Metric Space, NeurIPS 2017 (arXiv 1706.02413)
PTv3에 남은 것
패킹 배치 관행, 계층적(단계별) 다운샘플링이라는 골격, “점별 MLP + 집계”라는 연산 단위

PointNet: 순열 불변을 만드는 가장 단순한 방법

점 집합 {x1, …, xn}에 대한 함수가 순서에 무관하려면, 각 점을 독립적으로 변환한 뒤 대칭 함수로 합치면 됩니다.

f({x1,…,xn}) ≈ γ( MAXi h(xi) )

h는 모든 점에 같은 가중치를 공유하는 MLP(코드로는 1×1 합성곱), MAX는 채널별 최댓값, γ는 다시 MLP입니다. CNN 사용자에게: 1×1 conv만 있고 3×3 conv가 없는 네트워크라고 보면 됩니다. 이웃 정보를 전혀 섞지 않으므로 지역 구조(모서리, 곡면)를 배우지 못하는 것이 근본 한계였습니다.

PointNet++: “이웃”과 “계층”을 도입

CNN의 3×3 conv + 풀링 계층을 흉내 내기 위해 Set Abstraction이라는 층을 만들었습니다. 한 층은 세 단계입니다.

  1. 샘플링: Farthest Point Sampling(FPS)으로 N개 중 N'개의 중심점을 고릅니다. 매번 “지금까지 뽑힌 점들에서 가장 먼 점”을 뽑아 공간을 고르게 덮습니다. 비용 O(N·N').
  2. 그룹화: 각 중심점 주변 반경 r 안(ball query) 또는 kNN으로 이웃을 모읍니다. 이것이 “커널이 덮는 영역”에 해당합니다.
  3. PointNet: 각 그룹 안에서 상대 좌표 xj − xi를 특징에 붙여 mini-PointNet(MLP + MAX)을 적용해 중심점 하나의 특징으로 요약합니다.

분할을 위해서는 반대 방향의 Feature Propagation 층이 있습니다. 거친 단계의 특징을 원래 점으로 되돌릴 때 거리 역수 가중 보간(가장 가까운 3점)을 쓰고, 같은 해상도의 인코더 특징을 이어 붙입니다(스킵 연결). 즉 PointNet++는 이미 포인트 클라우드용 U-Net이었고, 이후 모든 점 기반 백본(PTv1, v2, v3 포함)이 이 인코더-디코더 골격을 물려받았습니다.

CNN과 대응

FPS ≈ stride-2 풀링의 “어느 위치를 남길지” 결정. ball query ≈ 커널 창. mini-PointNet ≈ 커널 가중치 곱 + 합 — 다만 이웃마다 다른 가중치를 주는 대신 상대 좌표를 입력으로 넣고 같은 MLP를 쓴다는 점이 다릅니다. 이 “가중치를 위치별로 두지 못한다”는 약점을 메우려는 시도가 KPConv(커널 점을 3D 공간에 배치)와, 이 문서의 주인공인 어텐션(가중치를 입력에서 계산)입니다.

PTv3로 되돌아가기

PTv3는 FPS도 ball query도 kNN도 쓰지 않습니다. 하지만 “5단계 인코더에서 점을 줄이며 채널을 늘리고, 디코더에서 스킵으로 되돌린다”는 골격은 그대로입니다. FPS 대신 격자 병합(2.6의 PTv2 grid pooling → PTv3 serialized pooling)이, 보간 대신 병합 시 저장해 둔 소속 인덱스로 복사(pooling_inverse)가 쓰입니다. 후자는 “최근접 이웃 업샘플링”과 같은 의미입니다.

2.3

희소 합성곱: 빈 공간을 건너뛰는 3D CNN

논문
Graham & van der Maaten, Submanifold Sparse Convolutional Networks, 2017 (arXiv 1706.01307); Graham et al., 3D Semantic Segmentation with Submanifold Sparse Convolutional Networks, CVPR 2018 · Choy et al., 4D Spatio-Temporal ConvNets: Minkowski Convolutional Neural Networks, CVPR 2019 (arXiv 1904.08755) · 구현: spconv
PTv3에 남은 것
stem(SubMConv3d, k=5), 모든 블록의 xCPE(SubMConv3d, k=3), Point.sparsify()

문제

2 cm 복셀로 방 하나를 밀집 3D 텐서로 만들면 셀의 99% 이상이 비어 있고, 밀집 3D conv는 이 빈 셀에도 연산을 합니다. 더 나쁜 것은 빈 공간의 “번짐”입니다. 일반 conv는 빈 셀이라도 이웃에 값이 있으면 출력을 만들어내므로, 층을 거칠수록 표면이 두꺼워지고 희소성이 사라집니다.

핵심 아이디어: 활성 셀의 집합만 저장하고, 두 종류의 합성곱을 구분

입력을 (좌표 리스트, 특징 리스트)의 쌍으로 저장합니다. (N, 4) 정수 좌표(배치 인덱스 + xyz)와 (N, C) 특징 — 바로 PTv3의 Point 구조입니다. 합성곱은 두 가지로 나뉩니다.

  • Submanifold sparse convolution (SubMConv): 출력 좌표 집합 = 입력 좌표 집합. 각 활성 셀에서만 출력을 계산하고, 커널 창 안의 활성 이웃만 곱해 더합니다. 희소성이 층을 거쳐도 그대로 유지됩니다. 3×3 conv with padding=same의 희소 판.
  • Regular sparse convolution (SparseConv): 커널이 닿는 모든 셀에 출력을 만듭니다(팽창). stride 2와 함께 다운샘플링용으로 씁니다.

계산은 rulebook으로 합니다. 커널 오프셋 k ∈ {−1,0,1}³(27개)마다 “입력 인덱스 i의 좌표 + k가 활성 셀 j인 쌍 (i, j)”의 목록을 해시 테이블로 만듭니다. 그 뒤 오프셋별로 Wk ∈ ℝCin×Cout을 gather된 특징에 곱해 scatter-add합니다.

yj = b + Σk∈𝒦 Σ(i,j)∈Rk Wk xi

수학적으로는 밀집 3D conv와 동일하고(빈 셀 = 0), 단지 0끼리의 곱을 건너뛸 뿐입니다. spconv의 indice_key는 같은 좌표 집합을 쓰는 여러 층이 rulebook을 한 번만 만들고 공유하게 하는 캐시 키입니다.

CNN과 대응

SubMConv3d(kernel 3) = 익숙한 3×3×3 conv, padding=1. 단, “이웃이 없으면 0”이라는 zero padding이 커널 창 안 어디서나 일어난다고 생각하면 됩니다. BatchNorm도 (N, C) 특징 위에서 BatchNorm1d로 그대로 작동합니다(PTv3 stem과 풀링의 norm이 BN인 이유).

PTv3로 되돌아가기

Point.sparsify()(batch, grid_coord)featspconv.SparseConvTensor를 만듭니다. 이 텐서는 두 곳에서 쓰입니다.

  • Embedding stem: SubMConv3d(6→32, kernel 5) + BN + GELU. 5³ 커널이라 첫 층부터 반경 2복셀의 지역 문맥을 봅니다.
  • xCPE: 각 블록 맨 앞의 SubMConv3d(C→C, kernel 3, indice_key=f"stage{s}"). 같은 단계의 블록들이 rulebook을 공유합니다. 왜 이것이 “위치 인코딩”인지는 2.9에서 다룹니다.

블록 끝에서 point.sparse_conv_feat.replace_feature(point.feat)로 어텐션 결과를 희소 텐서에 다시 써 넣는 줄이 이 연결 고리입니다. 풀링 후에는 좌표 집합이 바뀌므로 SerializedPooling이 끝에서 point.sparsify()를 다시 호출합니다.

2.4

Transformer와 셀프 어텐션: 입력이 스스로 가중치를 만드는 층

논문
Vaswani et al., Attention Is All You Need, NeurIPS 2017 (arXiv 1706.03762) · Ba et al., Layer Normalization, 2016 (arXiv 1607.06450) · Xiong et al., On Layer Normalization in the Transformer Architecture, ICML 2020 (pre-norm)
PTv3에 남은 것
SerializedAttention의 QKV 투영, scaled dot-product, 멀티헤드, MLP, pre-norm 잔차 블록

합성곱을 다시 쓰기

1D 신호에서 3-tap 합성곱은 출력 yi = w−1xi−1 + w0xi + w1xi+1입니다. 일반화하면 “이웃 집합 𝒩(i)에 대해 yi = Σj∈𝒩(i) wij · v(xj)”이고, 합성곱은 가중치 wij가 상대 위치 j − i만의 함수이고 학습 후 고정이라는 선택입니다. 셀프 어텐션은 같은 틀에서 두 가지를 바꿉니다.

  • 가중치 wij입력 쌍 (xi, xj)의 유사도에서 계산합니다. 학습되는 것은 “유사도를 재는 방법”(투영 행렬)이지 가중치 자체가 아닙니다.
  • 이웃 집합 𝒩(i)를 격자 창이 아니라 임의의 토큰 집합(문장 전체, 패치 전체)으로 둡니다.

수식

토큰 행렬 X ∈ ℝn×C에서 세 개의 선형 투영으로 질의(query), 키(key), 값(value)을 만듭니다.

Q = XWQ, K = XWK, V = XWV (각각 ℝn×d)
Attention(Q,K,V) = softmax( QK / √d ) V

풀어 쓰면 yi = Σj aij vj, aij = exp(qi·kj/√d) / Σj' exp(qi·kj'/√d). 즉 각 토큰이 다른 모든 토큰과의 내적으로 “얼마나 참고할지”를 정하고, 그 비율로 값을 평균합니다. √d로 나누는 이유는 내적의 분산이 d에 비례해 커져 softmax가 포화(거의 one-hot)되는 것을 막기 위해서입니다. 코드의 self.scale = (C // H) ** -0.5가 이것입니다.

멀티헤드: 채널 CH개의 헤드로 나눠(d = C/H) 각각 독립적으로 어텐션을 계산한 뒤 이어 붙이고 출력 투영 WO를 곱합니다. 헤드마다 다른 “유사도의 기준”을 배울 수 있게 하는 장치로, 합성곱의 여러 출력 채널이 여러 필터를 배우는 것과 비슷합니다. PTv3는 헤드당 16채널(C/H = 16)을 모든 단계에서 유지합니다.

비용: QKn×n 행렬입니다. 시간·메모리 모두 O(n²). 10만 점 장면 전체에 그냥 적용하면 1010 원소 — 불가능합니다. 이것이 이후 모든 “창(window)”, “패치”, “이웃 제한” 설계의 출발점이며, PTv3의 “패치 1024”도 n을 1024로 제한하는 선택입니다(그래도 1024² ≈ 106N/1024번 — FlashAttention 없이는 여전히 무겁습니다. 2.10 참조).

블록의 나머지: FFN, 잔차, LayerNorm

어텐션은 토큰을 섞는 층이고 채널별 비선형 변환은 하지 않으므로, 뒤에 위치별 MLP(FFN)가 옵니다: Linear(C→4C) → GELU → Linear(4C→C). 코드의 MLPmlp_ratio=4입니다. 둘 다 잔차로 감싸며, 정규화는 LayerNorm(각 토큰의 채널 방향 평균·분산으로 정규화)을 씁니다. BatchNorm과 달리 배치 통계가 필요 없어 “장면마다 점 수가 다르고 배치가 작은” 상황에 안정적입니다. 원논문은 잔차를 더한 에 norm을 두는 post-norm이었지만, 깊은 모델의 학습 안정성 때문에 지금은 pre-norm(x + f(LN(x)))이 표준이고 PTv3도 pre_norm=True입니다.

왜 위치 정보가 따로 필요한가

어텐션 수식 어디에도 “ji의 왼쪽인지 오른쪽인지”가 없습니다. 토큰을 섞어 넣어도 출력이 같이 섞일 뿐 값은 변하지 않습니다(순열 등변). 이것은 집합 처리에 좋은 성질이지만, 기하를 배워야 하는 3D에서는 치명적입니다: 두 점이 1 cm 떨어졌는지 1 m 떨어졌는지 모델이 알 수 없습니다. 그래서 위치 인코딩이 반드시 필요하고, 그 방법이 2.9의 주제입니다.

PTv3로 되돌아가기

SerializedAttention의 비-Flash 경로는 이 절의 수식 그대로입니다: qkv = Linear(C→3C), (N', K, 3, H, C/H)로 reshape, attn = (q·scale) @ kᵀ, softmax, @ v, 그리고 proj. 여기서 N' = N/K는 패치 수, K = 1024는 패치 크기입니다. PTv1/v2가 썼던 “벡터 어텐션”(2.6)과 달리 표준 수식을 한 글자도 바꾸지 않은 것이 PTv3의 의도적 선택이고, 그 덕에 Flash 경로로 갈아탈 수 있습니다.

2.5

ViT와 Swin Transformer: 어텐션을 “창”에 가두기

논문
Dosovitskiy et al., An Image is Worth 16×16 Words, ICLR 2021 (arXiv 2010.11929) · Liu et al., Swin Transformer: Hierarchical Vision Transformer using Shifted Windows, ICCV 2021 (arXiv 2103.14030)
PTv3에 남은 것
계층 구조(단계마다 해상도 ½, 채널 2배, 세 번째 단계 최심), 창 어텐션이라는 발상, 창 사이 정보 교환이 필요하다는 문제 인식, stochastic depth·AdamW 학습 레시피

ViT: 이미지를 토큰 열로

ViT는 이미지를 16×16 픽셀 패치로 잘라 각 패치를 선형 투영해 토큰으로 만들고(224² 이미지 → 196 토큰), 학습 가능한 절대 위치 임베딩을 더한 뒤 표준 Transformer 인코더에 넣습니다. 합성곱의 귀납 편향(지역성, 이동 등변성)을 버리는 대신 대규모 데이터에서 더 잘 스케일된다는 것을 보였습니다. PTv3 논문이 인용하는 “규모가 설계를 이긴다”는 명제의 원형입니다. 하지만 토큰 수가 196개인 분류에서는 괜찮아도, 분할처럼 고해상도가 필요한 과제에서 O(n²)은 곧바로 문제가 됩니다.

Swin: 창 어텐션 + 이동 창 + 계층

Swin은 세 가지를 도입해 Transformer를 CNN 백본처럼 쓸 수 있게 했습니다.

  1. 창 어텐션(W-MSA): 특징 맵을 M×M(=7×7) 창으로 나누고 창 안에서만 어텐션. 비용이 O(n·M²)으로 선형이 됩니다. 창 안의 상대 위치 (Δx, Δy) ∈ [−M+1, M−1]²마다 학습 가능한 스칼라 편향 B를 두어 softmax(QK/√d + B)V로 계산합니다 — 상대 위치 편향(relative position bias, RPE).
  2. 이동 창(SW-MSA): 창 안에서만 섞으면 창 경계를 넘는 정보가 없으므로, 다음 블록에서 창 격자를 (M/2, M/2)만큼 밀어서 다른 분할을 씁니다. 두 블록을 거치면 모든 이웃이 연결됩니다. 구현은 cyclic shift + 마스크.
  3. 패치 병합(patch merging): 단계가 바뀔 때 2×2 이웃 토큰을 이어 붙여(4C) 선형층으로 2C로 줄입니다. 해상도 ½, 채널 2배 — ResNet의 stage와 같은 피라미드. 깊이 배분 (2, 2, 6, 2)도 Swin-T의 것입니다.
Swin 블록 ℓ: 창 Swin 블록 ℓ+1: 이동 창 PTv3: 곡선 위 연속 구간이 패치 패치 (8점 예시)
창을 정의하는 두 방법. Swin은 격자를 M×M으로 자르고 다음 블록에서 격자를 민다. PTv3는 공간 채움 곡선으로 점을 한 줄로 세운 뒤 연속 K개를 패치로 삼는다. 곡선이 지역성을 보존하므로 이 패치는 “대체로 공간적으로 뭉쳐 있는 점들의 집합”이 된다. 창 사이 교환은 이동 창 대신 다른 곡선(순서)으로 바꿔 타는 것으로 해결한다.

3D로의 직역 시도: Stratified Transformer(2022)와 Swin3D(2023)는 Swin의 창을 3D 복셀 창으로 옮겼습니다. 하지만 3D에서는 창 안의 점 수가 들쭉날쭉해서(빈 창, 넘치는 창) 패딩과 마스킹 비용이 크고, 상대 위치 편향 테이블도 3D라 훨씬 큽니다. PTv3는 이 문제를 “창을 공간이 아니라 순서로 정의해 항상 정확히 K개가 들어가게” 만드는 방식으로 우회합니다.

PTv3로 되돌아가기

PTv3 논문은 창 사이 교환(patch interaction)을 위해 네 가지를 실험했습니다: shift dilation(간격을 두고 뽑기), shift patch(패치 경계 밀기 — Swin의 이동 창과 같은 발상), shift order(블록마다 다른 순서), shuffle order(순서 목록을 무작위로 섞은 뒤 블록마다 번갈아 사용). 마지막이 가장 좋았고 코드의 기본값입니다. Swin의 RPE는 RPE 클래스로 구현되어 있지만(enable_rpe) 기본은 꺼져 있고, 대신 xCPE를 씁니다(2.9). 계층 설정 (2,2,2,6,2)/채널 2배 규칙은 Swin의 관행을 3D 5단계로 늘린 것입니다.

2.6

Point Transformer v1 / v2: 포인트 클라우드용 어텐션의 직계 조상

논문
Zhao et al., Point Transformer, ICCV 2021 (arXiv 2012.09164) · Wu et al., Point Transformer V2: Grouped Vector Attention and Partition-based Pooling, NeurIPS 2022 (arXiv 2210.05666)
PTv3에 남은 것
U-Net 골격, 격자 기반 풀링의 발상(→ 직렬화 풀링), Pointcept 코드베이스. 반대로 버린 것: kNN, 벡터 어텐션, 명시적 상대 위치 인코딩

PTv1: 벡터 어텐션과 kNN 이웃

PTv1은 PointNet++의 Set Abstraction 자리에 어텐션을 넣었습니다. 각 점 i에 대해 kNN(k = 16) 이웃 𝒩(i)를 구하고, 다음을 계산합니다.

yi = Σj∈𝒩(i) ρ( γ( φ(xi) − ψ(xj) + δij ) ) ⊙ ( α(xj) + δij ), δij = θ(pipj)

2.4의 표준 어텐션과 세 가지가 다릅니다.

  • 뺄셈 관계: q·k 내적 대신 q − k.
  • 벡터 어텐션: 가중치가 스칼라 aij가 아니라 채널 차원의 벡터입니다. γ(MLP)가 C차원 가중치를 만들고 ρ(softmax)를 이웃 축으로 취한 뒤 값과 원소별 곱(⊙). 채널마다 다른 이웃을 볼 수 있어 표현력이 높습니다.
  • 위치 인코딩 δij: 상대 좌표 pi − pj를 MLP θ에 통과시켜 가중치 계산과 값 양쪽에 더합니다. 3D 기하를 직접 넣는 가장 자연스러운 방법입니다.

다운샘플링(TransitionDown)은 FPS + kNN 풀링, 업샘플링은 보간으로 PointNet++와 같습니다. 문제는 비용입니다. kNN은 매 단계 O(N·N')(또는 공간 자료구조로도 무시 못 할 상수), 벡터 어텐션은 이웃마다 C차원 MLP를 돌리므로 메모리가 N·k·C, 그리고 이런 맞춤 연산은 표준 GEMM/FlashAttention 커널로 가속되지 않습니다.

PTv2: 그룹 벡터 어텐션과 격자 풀링

PTv2는 두 가지를 손봤습니다.

  • Grouped Vector Attention (GVA): 벡터 가중치를 C채널 전부 만들지 않고 g개 그룹(예: 8)마다 하나씩만 만들어 그룹 안에서 공유. 멀티헤드 스칼라 어텐션(g = H, 그룹 안 공유)과 완전 벡터 어텐션(g = C) 사이의 절충으로, 파라미터와 과적합을 줄였습니다. 여기에 position encoding multiplier — 위치 인코딩을 더하기만 하지 않고 곱하기(δmul ⊙ ψ(xj) + δbias)로도 넣어 기하 정보를 강화했습니다.
  • Partition-based (grid) pooling: FPS + kNN 대신 공간을 균일 격자로 나누고 같은 셀의 점들을 max로 병합. 셀 중심이 새 점이 됩니다. FPS의 순차적 비용이 사라지고, 병합 소속이 명시적이라 언풀링은 단순 복사(unpooling by index)로 됩니다.

PTv2는 ScanNet 75.4 mIoU로 당시 SOTA였지만, 논문의 표현을 빌리면 “정교하지만 느린” 모델이었습니다. 추론 한 장면에 PTv3의 3.3배 시간, 10배 메모리.

PTv3로 되돌아가기 — 무엇을 왜 버렸나
PTv2PTv3이유
kNN(k=16) 이웃직렬화 패치(K=1024)이웃 탐색 비용 제거, 수용장 64배
그룹 벡터 어텐션표준 멀티헤드 dot-productFlashAttention 사용 가능, 메모리 O(N)
상대 좌표 MLP + multiplierxCPE(희소 conv 잔차)이웃 쌍 수에 비례하는 위치 계산 제거, 정확도 유지
격자 풀링(해시)직렬화 풀링(비트 시프트)이미 있는 코드 재활용, 정렬 상태 유지

즉 PTv3는 PTv2의 “격자 풀링” 아이디어를 극단까지 밀어붙여 어텐션 이웃까지 격자(의 직렬화)로 정의한 모델이라고 요약할 수 있습니다.

2.7

공간 채움 곡선: 3D 격자를 한 줄로 세우는 법

출처
Morton, A computer oriented geodetic data base and a new technique in file sequencing, IBM 1966 (Z-order) · Hilbert, Über die stetige Abbildung einer Linie auf ein Flächenstück, 1891 · Skilling, Programming the Hilbert curve, 2004 (Pointcept의 hilbert.py가 따르는 알고리즘)
PTv3에 남은 것
Point.serialization(), serialization/z_order.py, serialization/hilbert.py, 네 가지 order

문제

“한 점의 이웃”을 정렬 한 번으로 얻고 싶습니다. 1D라면 좌표로 정렬하면 이웃이 인접합니다. 3D 정수 격자 (x, y, z) ∈ [0, 2D를 하나의 정수로 바꾸되, 3D에서 가까운 셀이 1D에서도 가깝도록(지역성 보존) 하는 사상이 필요합니다. 그런 사상을 공간 채움 곡선이라 부릅니다.

Z-order (Morton) 곡선: 비트 끼워넣기

좌표의 비트를 번갈아 끼워 넣습니다. x = xD−1…x1x0, y, z도 같은 식으로 쓰면

key(x,y,z) = xD−1yD−1zD−1x1y1z1 x0y0z0 (2진수)

예: x = 5 = 1012, y = 3 = 0112, z = 01 0 0 · 0 1 0 · 1 1 0 = 1000101102 = 278. 왜 지역성이 보존되는가: 상위 비트가 먼저 오므로, 상위 3m비트가 같다는 것은 두 셀이 같은 크기 2D−m의 정육면체(옥트리 노드)에 속한다는 뜻입니다. 정렬하면 같은 옥트리 노드의 셀들이 연속 구간에 모입니다. 이름의 Z는 2D에서 곡선이 그리는 “Z” 모양에서 왔고, 이 재귀적 Z 패턴이 옥트리의 깊이 우선 순회와 정확히 일치합니다.

# z_order.py — xyz2key 핵심 (OCNN에서 가져옴)
key = 0
for i in range(depth):
    mask = 1 << i
    key |= ((x & mask) << (2*i + 2)) | ((y & mask) << (2*i + 1)) | ((z & mask) << (2*i + 0))
# i번째 비트를 3i+2, 3i+1, 3i 자리로 보낸다. 실제 코드는 8비트 LUT로 가속.

약점: 곡선이 “점프”합니다. 2D에서 셀 (1,0) 다음은 (0,1)이 아니라 — Z의 대각선 — 그리고 사분면이 바뀔 때는 멀리 뜁니다. 그래서 1D에서 인접한 두 셀이 3D에서는 멀 수 있습니다(반대 방향, 즉 3D에서 가까운데 1D에서 먼 경우도 경계에서 발생).

Hilbert 곡선: 점프 없는 채움

Hilbert 곡선은 연속한 두 코드가 항상 3D에서 인접한 셀이 되도록 재귀 패턴을 회전·반사해 이어 붙인 곡선입니다. 지역성이 Z-order보다 좋다는 것이 정량적으로 알려져 있고(예: 곡선상 거리 대비 공간 거리의 상한이 더 작음), 대신 계산이 복잡합니다. Pointcept의 hilbert.py는 Skilling(2004)의 Gray-code 기반 정수 알고리즘을 텐서 연산으로 옮긴 것으로, 좌표 비트를 위에서부터 훑으며 각 단계의 사분면(3D는 팔분면) 진입 방향에 따라 하위 비트를 뒤집거나 교환합니다.

8×8 격자 위의 Z-order(왼쪽)와 Hilbert(오른쪽). 같은 색 구간은 곡선상 연속 16셀 = “패치”. Z-order는 사분면 경계에서 긴 점프가 있고, Hilbert는 항상 이웃으로만 이동한다. 두 곡선 모두 패치가 공간적으로 뭉쳐 있지만 어떤 셀이 같은 패치에 묶이는지는 다르다 — 이것이 순서를 번갈아 쓰면 창 경계가 달라지는 이유다. (JS로 실제 계산해 그린 것.)

“-trans” 변형과 배치 인코딩

z-trans, hilbert-transgrid_coord[:, [1, 0, 2]]xy를 바꿔 넣은 것입니다. 곡선의 진행 방향이 바뀌므로 같은 곡선이라도 패치 경계가 달라집니다. 네 순서(z, z-trans, hilbert, hilbert-trans)를 한꺼번에 계산해 (4, N) 코드 텐서를 만듭니다. 배치는 코드의 상위 비트에 넣습니다: code = batch << (depth*3) | code. 그러면 한 번의 argsort로 “장면별로 묶인 뒤 장면 안에서 곡선 순서”가 되고, 어텐션 패치가 장면 경계를 넘지 않게 됩니다(offset과 패딩 로직이 이를 보장). depthgrid_coord.max()+1의 비트 길이로 자동 결정되며 최대 16(48비트 + 배치 비트 ≤ 63비트).

CNN과 대응

이미지의 래스터 순서(행 우선으로 픽셀을 나열)도 공간 채움 곡선입니다 — 지역성이 나쁜 곡선일 뿐입니다(한 행 끝에서 다음 행 처음으로 점프, 위아래 이웃은 W만큼 떨어짐). 3×3 conv가 “래스터 순서의 이웃”이 아니라 “2D 격자의 이웃”을 쓰는 이유가 그것입니다. PTv3는 반대로 “곡선이 충분히 좋다면 곡선상 이웃으로도 대부분의 공간 이웃을 덮을 수 있고, 나머지는 곡선을 여러 개 쓰면 된다”는 데 베팅합니다.

PTv3로 되돌아가기

논문의 ablation(Table 6)은 순서 조합이 늘수록 성능이 오르고(z 단독 < z+z-trans < 넷 다), Hilbert가 Z보다 근소하게 좋으며, 순서를 섞어 쓰는 shuffle이 추가 이득을 준다고 보고합니다. 직관: 한 곡선의 패치 경계에서 잘린 이웃을 다른 곡선이 이어 줍니다. 코드는 매 forward마다 torch.randperm으로 순서 목록을 섞고(학습·추론 모두 — self.training 분기가 없음), 블록 iorder_index = i % 4를 씁니다.

2.8

OctFormer와 FlatFormer: “직렬화 후 창 어텐션”의 선례

논문
Wang, OctFormer: Octree-based Transformers for 3D Point Clouds, SIGGRAPH 2023 (arXiv 2305.03045) · Liu et al., FlatFormer: Flattened Window Attention for Efficient Point Cloud Transformer, CVPR 2023 (arXiv 2301.08739)
PTv3에 남은 것
“정렬해서 고정 크기로 자른다”는 핵심 발상, Z-order LUT 코드(OCNN), CPE의 채택

PTv3는 직렬화 어텐션을 처음 제안한 논문이 아닙니다. 논문 스스로 두 선행 연구를 “point cloud serialization”의 대표로 꼽고, 자신의 기여를 그 발상을 일반화·단순화하고 규모를 키운 것으로 위치시킵니다.

OctFormer (2023)

점을 옥트리에 넣고(OCNN 라이브러리), 옥트리 리프의 Z-order(= 옥트리 셔플 키) 순서로 정렬한 뒤, 연속 K개(논문 기본 26 → 확장 실험에서 더 크게)를 한 창으로 삼아 어텐션합니다. 창 경계를 넘는 정보 교환을 위해 “팽창(dilated) 옥트리 어텐션” — 정렬된 열에서 d칸 간격으로 뽑아 창을 만드는 방식 — 을 씁니다. 위치 인코딩은 옥트리 합성곱을 이용한 CPE(2.9). 옥트리는 다운샘플링(부모 노드로 병합)을 자연스럽게 제공하지만, 옥트리 자료구조 구축 자체가 비용이고 Z-order 하나에 묶여 있습니다.

FlatFormer (2023)

실외 LiDAR 검출을 위해 “창 어텐션의 창을 동일한 점 수로 만들자”고 제안했습니다. 점을 창 좌표로 정렬한 뒤 순서대로 동일 크기 그룹으로 자릅니다(flattened window). 공간 창은 크기가 제각각이라 패딩이 필요하지만, 이렇게 하면 모든 그룹이 같은 길이라 GPU 배치 처리에 완벽히 맞고 패딩이 없습니다. 교대 블록에서는 정렬 축(x-major/y-major)을 바꿔 그룹 경계를 달리 합니다 — PTv3의 “trans” 순서 교대와 같은 발상입니다.

PTv3로 되돌아가기
  • OctFormer로부터: Z-order 직렬화, 정렬열 위의 고정 크기 창, CPE. 대신 옥트리 구조를 버리고 코드의 비트 연산만으로 정렬(2.7)과 풀링(Level 3)을 해결했습니다. 코드의 z_order.py 헤더가 OCNN 저작권인 이유입니다.
  • FlatFormer로부터: “동일 크기 그룹” 관행과 정렬 축 교대. PTv3는 이를 Hilbert까지 포함한 네 순서 + 무작위 셔플로 확장했습니다.
  • PTv3의 독자 기여: 창 크기를 1024까지 키워도 성능이 계속 오른다는 발견(OctFormer는 수십 단위), xCPE, 직렬화 풀링, 그리고 “이 모든 것을 단순하게 만들었더니 스케일이 열린다”는 메시지.
2.9

위치 인코딩: 절대 PE, RPE, CPE, 그리고 xCPE

논문
Vaswani et al. 2017 (sinusoidal 절대 PE) · Shaw et al., Self-Attention with Relative Position Representations, NAACL 2018 · Liu et al. 2021 (Swin의 relative position bias) · Chu et al., Conditional Positional Encodings for Vision Transformers, 2021/ICLR 2023 (arXiv 2102.10882) · Wang 2023 (OctFormer의 3D CPE)
PTv3에 남은 것
Block.cpe(xCPE), 선택적 RPE 클래스

2.4에서 본 대로 어텐션은 위치를 모릅니다. 위치를 넣는 방법은 크게 셋이고, 3D에서는 각각의 비용이 크게 다릅니다.

절대 위치 인코딩 (APE)

각 토큰의 절대 좌표를 벡터로 바꿔 입력 특징에 더합니다. 원 Transformer는 사인·코사인 함수(PE(pos, 2i) = sin(pos / 100002i/d)), ViT는 학습 테이블. 3D 점의 절대 좌표는 장면마다 원점이 다르고 회전 증강까지 하므로 절대 좌표 자체는 거의 의미가 없습니다. 다만 (x,y,z)를 입력 특징의 일부로 넣는 것(PTv3의 in_channels 6 = 색상 3 + 법선 3, 좌표는 별도로 다룸)은 흔한 관행입니다.

상대 위치 인코딩 (RPE)

어텐션 로짓에 두 토큰의 상대 위치에 의존하는 항을 더합니다: aij ∝ exp(qi·kj/√d + b(pi − pj)). Swin은 b(2M−1)² 크기의 테이블로 두고, PTv1/v2는 θ(pi − pj)를 MLP로 계산해 값에도 더합니다. 이동 등변성을 정확히 갖는 장점이 있지만, 토큰 쌍 수 N·K에 비례하는 추가 계산과 메모리가 들고, N×K×K 편향 행렬을 로짓에 더해야 하므로 FlashAttention의 “로짓 행렬을 절대 메모리에 쓰지 않는다”는 전제와 충돌합니다. PTv3 코드의 RPE 클래스는 Swin 방식의 3D 판(축별 테이블 3개를 더함, pos_bnd로 클램프)이고, enable_flash=True일 때는 assert로 금지됩니다.

조건부 위치 인코딩 (CPE)

CPVT(Chu et al.)의 관찰: 제로 패딩이 있는 합성곱은 이미 절대 위치를 알고 있다(경계에서 패딩을 보는 정도로 위치를 추정할 수 있음)는 사실과, 합성곱이 이동 등변이라는 사실을 합치면 — 토큰 특징 맵에 depthwise 3×3 conv 한 층을 잔차로 끼워 넣는 것만으로 위치 정보가 충분히 주입됩니다. 이를 Positional Encoding Generator(PEG)라 부르며, ① 입력 길이에 무관하고(테이블 크기 제한 없음) ② 위치 정보가 “주변 특징에 조건부”이며 ③ 비용이 O(N)입니다. CNN 사용자에게는 당연해 보일 수 있습니다 — 합성곱은 원래 위치를 안다 — 하지만 “Transformer에 conv 한 층만 넣어 PE를 대신한다”는 것이 이 논문의 기여입니다. OctFormer가 이를 옥트리 합성곱으로 3D에 옮겼습니다.

xCPE: 희소 합성곱 + 스킵

PTv3는 CPE를 3×3×3 submanifold sparse conv로 구현하되, 뒤에 Linear + LayerNorm을 붙이고 잔차 연결로 감싼 것을 xCPE(enhanced CPE)라 부릅니다. 논문 ablation에서 순수 CPE보다 근소하게 높고 RPE와 대등하거나 나은 정확도를 훨씬 적은 비용으로 얻습니다. 잔차 덕분에 이 층은 “위치 정보를 더하는” 층이 되어 어텐션이 원래 특징과 위치가 섞인 특징을 모두 볼 수 있습니다.

self.cpe = PointSequential(
    spconv.SubMConv3d(C, C, kernel_size=3, bias=True, indice_key=cpe_indice_key),
    nn.Linear(C, C),
    norm_layer(C),      # LayerNorm
)
# forward: point.feat = point.feat + cpe(point).feat
CNN 관점의 해석

PTv3 블록은 사실 “3×3×3 conv 잔차 블록 하나 + 어텐션 블록 하나”의 결합입니다. 지역적·기하적 정보(모서리, 법선 변화)는 conv가, 넓은 문맥(같은 물체의 반대편, 1024점 범위)은 어텐션이 담당하는 분업으로 읽으면 됩니다. 실제로 논문은 xCPE 없이 어텐션만 쓰면 성능이 크게 떨어진다고 보고합니다 — 어텐션이 “어디”를 전혀 모르기 때문입니다.

2.10

FlashAttention: 패치 1024를 가능하게 한 시스템 논문

논문
Dao et al., FlashAttention: Fast and Memory-Efficient Exact Attention with IO-Awareness, NeurIPS 2022 (arXiv 2205.14135) · Dao, FlashAttention-2, 2023 (arXiv 2307.08691)
PTv3에 남은 것
flash_attn.flash_attn_varlen_qkvpacked_func 호출과 그 입력을 만드는 get_padding_and_inverse

문제

표준 어텐션 구현은 S = QK(n×n)를 GPU의 큰 메모리(HBM)에 쓰고, softmax를 위해 다시 읽고, P = softmax(S)를 쓰고, PV를 위해 다시 읽습니다. n = 1024, 헤드 32, 패치 100개면 P 하나가 100·32·1024² · 2바이트 ≈ 6.7 GB(fp16), 역전파를 위해 저장까지 해야 합니다. 병목은 연산이 아니라 메모리 읽기·쓰기입니다.

핵심 아이디어: 타일링 + 온라인 softmax + 재계산

  1. 타일링: Q, K, V를 SRAM(GPU의 작은 고속 메모리)에 들어가는 블록으로 나누고, 블록 쌍마다 S의 조각을 계산해 곧바로 소비합니다. SP 전체를 HBM에 쓰는 일이 없습니다.
  2. 온라인 softmax: softmax는 행 전체의 최댓값과 합이 필요하지만, 블록을 순서대로 처리하며 “지금까지의 최댓값 m과 합 ”을 유지하고 새 블록이 오면 이전 누적값을 emold − mnew로 보정합니다. 결과는 정확히(근사 없이) 같습니다.
  3. 역전파 재계산: P를 저장하지 않고 역전파 때 Q, K와 저장해 둔 (m, ℓ)로 다시 계산합니다. 연산은 늘지만 메모리 왕복이 줄어 전체는 더 빠릅니다.

결과: 메모리 O(n²) → O(n), 속도 2~4배. FlashAttention-2는 병렬화 축과 워프 분배를 손봐 A100에서 이론 FLOPs의 70% 수준까지 끌어올렸습니다.

varlen 인터페이스와 PTv3의 패딩 로직

PTv3가 쓰는 flash_attn_varlen_qkvpacked_func(qkv, cu_seqlens, max_seqlen, …)는 여러 시퀀스를 한 줄로 이어 붙인 텐서와 각 시퀀스의 시작 오프셋 배열 cu_seqlens를 받습니다. 시퀀스 = 어텐션 패치이므로 PTv3는 cu_seqlens = [0, 1024, 2048, …]를 넘기면 됩니다. 문제는 장면의 점 수가 1024의 배수가 아니라는 점이고, get_padding_and_inverse가 이를 처리합니다.

  • 장면의 점 수 nb가 패치 크기 K보다 크면 ⌈nb/K⌉·K로 올림한 길이의 pad 인덱스 배열을 만듭니다. 마지막 불완전 패치의 빈 자리는 직전 패치의 같은 위치 점들을 복사해 채웁니다(0 벡터가 아니라 실제 점을 재사용 — 마스크가 필요 없고 softmax 분포가 왜곡되지 않음).
  • nb ≤ K이면 패딩 없이 그 장면 전체를 길이 nb의 시퀀스 하나로 넘깁니다(varlen이 가변 길이를 허용하므로).
  • unpad는 원래 점 인덱스 → 패딩된 위치의 사상으로, 어텐션 출력에서 원래 점만 골라 inverse로 되돌릴 때 씁니다. 복사된 점의 출력은 버려집니다.

Flash 경로는 bf16(지원 시) 또는 fp16으로 계산하고 결과를 원래 dtype으로 되돌립니다. 이것이 enable_flash=True일 때 upcast_attention/softmax를 끄도록 assert하는 이유입니다.

PTv3로 되돌아가기

논문 Table 5에서 패치 크기를 16 → 1024로 키우면 성능이 계속 오르고, 4096에서 포화·감소합니다. 이 실험 자체가 FlashAttention 없이는 메모리 때문에 불가능했습니다. 논문이 보고하는 “PTv2 대비 10.2배 메모리 절감”의 상당 부분은 벡터 어텐션(N·k·C 메모리)을 버리고 Flash 커널을 쓴 데서 옵니다. 반대로 말하면 PTv3의 설계 결정 대부분은 “표준 어텐션 커널에 태울 수 있는가”라는 기준으로 내려졌다고 읽을 수 있습니다.

2.11

정규화·정칙화·손실: 블록 주변의 작은 부품들

논문
Hendrycks & Gimpel, Gaussian Error Linear Units, 2016 (arXiv 1606.08415) · Huang et al., Deep Networks with Stochastic Depth, ECCV 2016 (arXiv 1603.09382) · Loshchilov & Hutter, Decoupled Weight Decay Regularization, ICLR 2019 (arXiv 1711.05101) · Smith & Topin, Super-Convergence (OneCycle), 2017 · Berman et al., The Lovász-Softmax Loss, CVPR 2018 (arXiv 1705.08790)
부품CNN 사용자가 아는 것PTv3에서
LayerNormBatchNorm: 배치·공간 축으로 채널별 통계토큰 하나의 채널 축으로 정규화. 배치 구성과 무관 → 점 수가 제각각인 배치에 안정적. 어텐션 블록과 xCPE에 사용. stem·풀링·언풀링은 BatchNorm(eps=1e-3, momentum=0.01) — 희소 conv 관행.
GELUReLUx·Φ(x)(정규분포 CDF). 0 근처에서 매끄러운 ReLU. Transformer 표준.
DropPath (stochastic depth)Dropout: 뉴런을 끔잔차 브랜치 전체를 샘플 단위로 확률 p로 끕니다(x + f(x)x). 깊이 정칙화. PTv3는 p를 0 → 0.3까지 블록 순서대로 선형 증가(torch.linspace).
AdamWSGD + momentum + L2Adam에서 weight decay를 기울기가 아니라 가중치에 직접 적용(decoupled). lr 0.006, wd 0.05; 단 block 파라미터는 lr 0.0006(param_dicts) — 어텐션 블록은 낮은 lr, stem/풀링/헤드는 높은 lr.
OneCyclestep decaylr을 짧게 올렸다가 코사인으로 내리는 스케줄. 800 epoch.
CrossEntropy + LovászCELovász-Softmax는 IoU(Jaccard)의 볼록 확장을 직접 최소화합니다. mIoU로 평가하는 분할에서 CE만 쓸 때보다 소수 클래스 IoU가 오릅니다. 두 손실을 1:1로 더함.

데이터 증강도 “레시피”의 일부입니다. z축 무작위 회전, 스케일 0.9~1.1, 축 뒤집기, 좌표 지터, 색상 대비·이동·지터·색조 변화(chromatic auto contrast / translation / jitter / hue-saturation), 그리고 학습 시 최대 102,400점을 구(sphere)로 잘라 내는 SphereCrop. 이런 증강이 PPT의 “규모”와 함께 PTv3의 성능을 실제로 떠받칩니다.

2.12

Point Prompt Training과 스케일링 원리

논문
Wu et al., Towards Large-scale 3D Representation Learning with Multi-dataset Point Prompt Training, CVPR 2024 (arXiv 2308.09718) · 후속: Wu et al., Sonata: Self-Supervised Learning of Reliable Point Representations, CVPR 2025 (arXiv 2503.16429)
PTv3에 남은 것
pdnorm_* 옵션, PDNorm 클래스, “+PPT” 결과 열, 그리고 논문의 동기 자체

왜 이 논문이 PTv3보다 먼저 와야 하는가

PTv3의 서론은 “PPT에서 관찰한 스케일링 원리에서 출발했다”고 씁니다. PPT의 관찰: 3D 데이터셋은 개별로는 작지만(ScanNet 1,513 장면), 여러 데이터셋을 함께 학습시키면 — 그냥 섞으면 도메인 차이(센서, 실내/실외, 라벨 체계) 때문에 오히려 나빠지는 negative transfer가 일어나지만 — 도메인을 조건으로 알려 주면 각 데이터셋 단독 학습보다 모두에서 좋아집니다. 즉 3D에서도 데이터 규모가 성능을 밀어 올리며, 병목은 그 규모를 감당할 백본의 효율입니다. PTv3는 그 병목을 풀기 위해 설계된 백본입니다.

PDNorm: 데이터셋 조건부 정규화

가장 단순하고 효과적이었던 장치는 정규화 층의 affine 파라미터를 데이터셋마다 따로 두는 것입니다. 코드의 PDNormconditions=("ScanNet","S3DIS","Structured3D")마다 별도의 BatchNorm1d/LayerNorm을 만들고 point.condition에 따라 골라 씁니다(decouple=True). 선택적으로 adaptive=True면 텍스트 임베딩 등 문맥 벡터에서 scale/shift를 예측해 곱·더합니다(AdaLN 방식). 파라미터 증가는 미미하고, 백본의 나머지 가중치는 모든 데이터셋이 공유합니다.

언어 유도 카테고리 정렬

데이터셋마다 클래스 이름과 개수가 다르므로 분류 헤드를 공유할 수 없습니다. PPT는 클래스 이름을 CLIP 텍스트 인코더로 임베딩하고, 점 특징을 그 임베딩과의 코사인 유사도로 분류합니다. 그러면 “chair”(ScanNet)와 “chair”(S3DIS)가 같은 출력 뉴런을 공유하고, 비슷한 개념끼리 자연스럽게 가까워집니다.

PTv3로 되돌아가기

Level 0 표의 “+PPT” 열은 ScanNet + S3DIS + Structured3D(실내) 또는 nuScenes + SemanticKITTI + Waymo(실외)를 PDNorm으로 함께 학습한 결과입니다. 모든 벤치마크에서 +1 mIoU 안팎이 오릅니다. 이 문서 기준 코드에서는 pdnorm_bn / pdnorm_ln = Truepdnorm_conditions로 켭니다. 이후 Sonata(m2)는 라벨 없는 자기지도 학습으로, Utonia(m3)는 더 큰 규모로 같은 백본을 확장하며, 둘 다 이 저장소의 point_transformer_v3m2_sonata.py, point_transformer_v3m3_utonia.py에 있습니다. “백본을 단순하게 만들면 데이터를 키울 수 있다”는 PTv3의 명제가 실제로 실행된 결과들입니다.

LEVEL 3다시 위로 올라가 조립한다

블록 하나를 shape으로 따라가기

ScanNet 장면 하나(배치 크기 1)를 가정합니다. 2 cm 복셀화 후 N = 100,000점, enc0 단계(C = 32, H = 2, K = 1024)의 블록 하나를 지나갑니다.

단계텐서shape비고
입력point.feat(100000, 32)패킹 배치. offset = [100000]
xCPESubMConv3d 출력(100000, 32)좌표 집합 불변. rulebook은 indice_key="stage0"로 블록 간 공유
패딩pad(100352,)⌈100000/1024⌉ = 98 패치 → 100352. 마지막 352개 슬롯은 직전 패치 점 복사
QKVqkv[order](100352, 96)Linear(32→96) 후 곡선 순서로 재배열
Flash 입력reshape(100352, 3, 2, 16)(토큰, qkv, 헤드, 헤드차원). cu_seqlens = [0, 1024, …, 100352] 길이 99
어텐션(개념상) QK98 × 2 × (1024 × 1024)Flash는 이 행렬을 메모리에 만들지 않음. 비-Flash 경로에서는 (98, 2, 1024, 1024) fp32 = 0.8 GB
출력feat[inverse](100000, 32)원래 순서로 복원, 복사본 폐기, proj Linear(32→32)
MLPfc1 → GELU → fc2(100000, 128) → (100000, 32)mlp_ratio = 4

다음 블록은 order_index = 1이므로 serialized_order[1](예: z-trans)로 다시 줄을 섭니다. 같은 점이 다른 1024개와 패치를 이루므로, 두 블록을 지나면 한 점의 실효 수용장은 두 곡선의 패치 합집합이 됩니다. 이것이 Swin의 이동 창과 같은 역할입니다.

직렬화 풀링 해부

SerializedPooling(stride=2)는 “정수 격자를 2배 거칠게 만들고 같은 셀의 점을 합친다”를 정렬과 비트 연산만으로 수행합니다. 2.7에서 본 Z-order의 성질 — 상위 3m비트가 같으면 같은 옥트리 노드 — 이 여기서 쓰입니다.

pooling_depth = 1                                # stride 2 → 1단계, 4 → 2단계
code = point.serialized_code >> pooling_depth * 3 # 하위 3비트 제거 = 2×2×2 셀 병합. 배치 비트는 상위라 안전
code_, cluster, counts = torch.unique(code[0], sorted=True, return_inverse=True, return_counts=True)
# cluster[i] = 점 i가 속한 거친 셀의 번호 (0..M-1). counts = 셀당 점 수
_, indices = torch.sort(cluster)                 # 셀 번호순으로 점을 모음
idx_ptr = cat([0, cumsum(counts)])               # CSR 경계
head_indices = indices[idx_ptr[:-1]]             # 셀별 대표 점 하나

feat  = segment_csr(self.proj(point.feat)[indices], idx_ptr, reduce="max")   # (M, C_out)
coord = segment_csr(point.coord[indices], idx_ptr, reduce="mean")           # 셀 평균 좌표
grid_coord = point.grid_coord[head_indices] >> pooling_depth                # 정수 좌표도 ½
code  = code[:, head_indices]; order = argsort(code); inverse = …           # 4개 순서 모두 갱신
serialized_depth -= pooling_depth
point_dict["pooling_inverse"] = cluster          # 언풀링용: 거친 점 → 원래 점 복사
point_dict["pooling_parent"]  = point            # 스킵 연결용: 원래 해상도 Point 통째로 보관

세 가지를 눈여겨볼 만합니다.

  • code[0]만으로 클러스터링합니다. 어떤 순서든 하위 3비트를 지운 결과는 같은 2×2×2 셀을 가리키므로(Z-order는 정확히, Hilbert도 팔분면 구조는 동일) 하나로 충분합니다. 나머지 순서의 코드는 대표 점의 것을 그대로 물려받아 argsort만 다시 합니다 — 전체 직렬화를 재계산하지 않습니다.
  • 특징은 max, 좌표는 mean. PTv2 grid pooling과 동일한 선택입니다. proj(Linear Cin → Cout)가 먼저 적용되어 채널을 두 배로 늘립니다.
  • 언풀링은 인덱싱 한 줄: parent.feat = proj_skip(parent.feat) + proj(point.feat)[inverse]. 거친 점의 특징을 소속 원래 점 모두에 복사하고(최근접 이웃 업샘플링), 같은 해상도의 인코더 특징을 더합니다(U-Net 원본의 concat 대신 add).

다섯 단계를 지나면 grid_coord는 16배(= 32 cm 복셀) 거칠어지고 점 수는 대략 수백~수천 개가 됩니다. 이 단계에서 패치 1024는 사실상 장면 전체에 대한 전역 어텐션입니다.

설계 결정과 실험이 말해 주는 것

논문 Section 5.3의 ablation을 “왜 그렇게 정했는가”의 형태로 요약합니다. 수치의 정확한 값보다 방향이 중요합니다.

결정실험 결과의 방향이 문서의 근거 절
순서: z + z-trans + hilbert + hilbert-trans순서가 많을수록 좋고 Hilbert ≥ Z. 셔플이 추가 이득.2.7
패치 상호작용: shuffle ordershift dilation / shift patch / shift order보다 shuffle order가 가장 좋음. 여러 방법을 섞어도 더 오르지 않음.2.5
패치 크기 102416 → 1024까지 단조 증가, 4096에서 포화. 큰 패치가 kNN 16의 소수용장을 압도.2.10
위치 인코딩: xCPEAPE < RPE ≈ CPE < xCPE(정확도). 비용은 RPE가 가장 큼. 인코딩 없음은 크게 하락.2.9
풀링: 직렬화 풀링grid pooling과 정확도 동등, 구현 단순·빠름.Level 3
어텐션: 표준 dot-product + Flash벡터 어텐션 대비 정확도 손실 거의 없음, 속도·메모리 대폭 개선.2.4, 2.6
한 단락 요약

PTv3는 “포인트 클라우드를 정수 격자로 복셀화하고, 공간 채움 곡선으로 한 줄로 세운 뒤, 1024개씩 잘라 표준 멀티헤드 어텐션을 적용하되, 어텐션 앞에 3×3×3 희소 합성곱 잔차로 기하를 주입하고, 블록마다 곡선을 바꿔 타 창 경계를 지우며, 코드의 비트를 밀어 U-Net 계층을 만드는” 모델입니다. 이 문장의 모든 절이 위의 어떤 논문에서 왔는지 말할 수 있다면 PTv3를 이해한 것입니다.

논문 읽기 순서 제안

이 문서를 한 번 읽은 뒤 원논문을 읽을 때의 권장 순서입니다. 하향식 이해를 유지하기 위해 PTv3를 먼저, 두 번 읽습니다.

  1. 1훑기
    PTv3 본문 Sec. 1, 3, Fig. 2–4만. 용어는 이 문서의 Level 0·1과 대조하며.
  2. 2어텐션
    Attention Is All You Need Sec. 3 → Swin Sec. 3 (창, 이동 창, RPE, 패치 병합). 여기서 2.4·2.5 상자로 돌아와 PTv3와 차이를 확인.
  3. 33D 계보
    PointNet++ Sec. 3 → Submanifold Sparse Conv Sec. 2–3 → PTv1 Sec. 3–4 → PTv2 Sec. 3–4. PTv2는 Pointcept 코드(pointcept/models/point_transformer_v2)와 함께.
  4. 4직렬화
    OctFormer Sec. 3 → FlatFormer Sec. 3. 그 후 Pointcept serialization/z_order.py, hilbert.py를 실제로 실행해 8×8×8 격자의 코드를 눈으로 확인.
  5. 5시스템
    FlashAttention Sec. 3(알고리즘 1, 2) → CPVT Sec. 3. 이 둘이 PTv3 설계의 “제약 조건”이었음을 확인.
  6. 6정독
    PTv3 전체를 다시, 이번엔 Sec. 4(설계 세부)와 Sec. 5.3(ablation)을 코드와 나란히. 마지막으로 PPT로 “+PPT” 열과 스케일링 동기를 닫음.

참고 문헌

  1. Wu, X. et al. Point Transformer V3: Simpler, Faster, Stronger. CVPR 2024. arXiv:2312.10035 · 코드 Pointcept/PointTransformerV3
  2. Wu, X. et al. Point Transformer V2: Grouped Vector Attention and Partition-based Pooling. NeurIPS 2022. arXiv:2210.05666
  3. Zhao, H. et al. Point Transformer. ICCV 2021. arXiv:2012.09164
  4. Qi, C. R. et al. PointNet. CVPR 2017. arXiv:1612.00593 · PointNet++. NeurIPS 2017. arXiv:1706.02413
  5. Graham, B., van der Maaten, L. Submanifold Sparse Convolutional Networks. 2017. arXiv:1706.01307 · Graham, B. et al. 3D Semantic Segmentation with Submanifold Sparse Convolutional Networks. CVPR 2018
  6. Choy, C. et al. 4D Spatio-Temporal ConvNets: Minkowski Convolutional Neural Networks. CVPR 2019. arXiv:1904.08755
  7. Vaswani, A. et al. Attention Is All You Need. NeurIPS 2017. arXiv:1706.03762
  8. Dosovitskiy, A. et al. An Image is Worth 16×16 Words. ICLR 2021. arXiv:2010.11929
  9. Liu, Z. et al. Swin Transformer. ICCV 2021. arXiv:2103.14030
  10. Lai, X. et al. Stratified Transformer for 3D Point Cloud Segmentation. CVPR 2022. arXiv:2203.14508 · Yang, Y.-Q. et al. Swin3D. 2023. arXiv:2304.06906
  11. Wang, P.-S. OctFormer: Octree-based Transformers for 3D Point Clouds. SIGGRAPH 2023. arXiv:2305.03045
  12. Liu, Z. et al. FlatFormer: Flattened Window Attention for Efficient Point Cloud Transformer. CVPR 2023. arXiv:2301.08739
  13. Chu, X. et al. Conditional Positional Encodings for Vision Transformers. ICLR 2023. arXiv:2102.10882
  14. Shaw, P. et al. Self-Attention with Relative Position Representations. NAACL 2018. arXiv:1803.02155
  15. Dao, T. et al. FlashAttention. NeurIPS 2022. arXiv:2205.14135 · Dao, T. FlashAttention-2. 2023. arXiv:2307.08691
  16. Morton, G. M. A Computer Oriented Geodetic Data Base and a New Technique in File Sequencing. IBM, 1966 · Skilling, J. Programming the Hilbert curve. AIP Conf. Proc. 2004
  17. Ba, J. L. et al. Layer Normalization. 2016. arXiv:1607.06450 · Xiong, R. et al. On Layer Normalization in the Transformer Architecture. ICML 2020. arXiv:2002.04745
  18. Huang, G. et al. Deep Networks with Stochastic Depth. ECCV 2016. arXiv:1603.09382
  19. Loshchilov, I., Hutter, F. Decoupled Weight Decay Regularization. ICLR 2019. arXiv:1711.05101
  20. Berman, M. et al. The Lovász-Softmax Loss. CVPR 2018. arXiv:1705.08790
  21. Wu, X. et al. Towards Large-scale 3D Representation Learning with Multi-dataset Point Prompt Training. CVPR 2024. arXiv:2308.09718
  22. Wu, X. et al. Sonata: Self-Supervised Learning of Reliable Point Representations. CVPR 2025. arXiv:2503.16429
  23. Dai, A. et al. ScanNet: Richly-annotated 3D Reconstructions of Indoor Scenes. CVPR 2017. arXiv:1702.04405

코드 인용은 이 문서 작성 시점의 Pointcept main 브랜치(point_transformer_v3m1_base.py, utils/structure.py, utils/serialization/, configs/scannet/semseg-pt-v3m1-0-base.py) 기준입니다. 논문 수치는 원문 표에서 옮겼으며 소수점 단위의 오기가 있을 수 있으니 인용 시 원문을 확인하십시오.