공간 차원과 시간 차원
HostTensor<D, E>는 단일 매핑으로 물리적 표현을 완전히 나타냅니다. 디바이스 텐서는 매핑을 여러 전용 차원에 나누어 담습니다:
- 공간 차원:
Chip,Cluster,Slice는 하드웨어 계층 전반에 데이터를 분산합니다. 스트림 텐서에서는Packet이 각 시간 반복 내 병렬 전달의 크기를 추가로 정합니다. - 시간 차원:
Time은 스트림 텐서에서 전달 반복의 순서를 정합니다.
공간 차원
하드웨어 계층의 각 공간 수준은 텐서 타입에서 고유한 타입 매개변수를 가지며, 이를 통해 공간 병렬성이 가능해집니다. 각 수준의 모든 유닛은 동일한 매핑을 공유한다고 가정합니다.
아래 표기법은 물리적 차원을 가리키지만 공개 타입이나 생성자는 아닙니다. 공개 텐서 API와 뷰는 스토리지 모듈에 문서화되어 있습니다.
extern crate furiosa_opt_std;
use furiosa_opt_std::prelude::*;
use std::marker::PhantomData;
// Assumed throughout this page.
axes![A = 8, B = 512];
// HBM tensors
struct HbmTensor<D: Scalar, Chip: M, Element: M> {
/* ... */
_marker: PhantomData<(D, Chip, Element)>,
}
// SRAM tensors
// DM (Data Memory), TRF (Tensor Register File), and VRF (Vector Register File)
struct DmTensor<D: Scalar, Chip: M, Cluster: M, Slice: M, Element: M> {
/* ... */
_marker: PhantomData<(D, Chip, Cluster, Slice, Element)>,
}
struct TrfTensor<D: Scalar, Chip: M, Cluster: M, Slice: M, Lane: M, Element: M> {
/* ... */
_marker: PhantomData<(D, Chip, Cluster, Slice, Lane, Element)>,
}
struct VrfTensor<D: Scalar, Chip: M, Cluster: M, Slice: M, Element: M> {
/* ... */
_marker: PhantomData<(D, Chip, Cluster, Slice, Element)>,
}
HBM 텐서는 공간 병렬성을 위해 데이터를 여러 Chip에 분산합니다. 각 Chip은 자신이 맡은 데이터 부분을 동시에 처리합니다. 예를 들어, HbmTensor<bf16, m![A], m![B]>는 8 × 512 = 4096개의 요소를 8개의 Chip에 Chip당 512개씩 분산합니다. i번째 Chip의 j번째 요소는 텐서 인덱스 i![A: i, B: j]를 저장합니다.
SRAM 텐서 타입은 더 세분화된 병렬성을 위해 Cluster 및 Slice 차원을 추가합니다. TrfTensor에는 추가로 TRF 데이터를 Slice당 8개 레인에 분산하는 Lane 차원이 있습니다. 자세한 내용은 Contraction Engine을 참조하십시오.
HBM 텐서는 구체적인 주소를 가집니다. DM, TRF, VRF 텐서는 백엔드로부터 주소를 할당받을 수 있습니다. 호스트 텐서는 호스트 측 값으로 남습니다. 매핑 매개변수는 물리적 표현을 결정하며, 주소는 스토리지가 별도로 소유합니다.
제약 조건
-
Chip,Cluster,Slice크기: 하드웨어 개수와 정확히 일치해야 합니다:단위 개수 제약 조건 패딩 예시 Chip시스템에 따라 다름 Chip::SIZE == NUM_CHIPSm![1 # NUM_CHIPS]ClusterChip당 2개 Cluster::SIZE == 2m![1 # 2]Slice256 / Cluster Slice::SIZE == 256m![X / N # 256]커널이 하드웨어가 제공하는 것보다 적은 수의 유닛을 사용할 때는 어떤 차원이든
#로 패딩할 수 있습니다. 예를 들어type Cluster = m![1 # 2]는 활성 Cluster 1개와 패딩 전용 Cluster 1개를 사용하여 Chip당 Cluster 2개라는 하드웨어 요구 사항을 충족합니다.Note
런타임은 Chip 단위(
#[device(chip = N)])로 동작하므로 Chip이나 Cluster의 일부만 사용하는 것은 아직 지원되지 않습니다. 이 제한은 향후 릴리스에서 완화될 수 있습니다. -
Element크기:Element::SIZE * size_of::<D>()는 유닛당 SRAM 용량을 초과해서는 안 되며, 이 용량은 텐서 타입에 따라 다릅니다:타입 단위 제약 조건 DmTensor512KB / Slice Element::SIZE * size_of::<D>() <= 512KBTrfTensor8KB / Lane Lane::SIZE <= 8,Element::SIZE * size_of::<D>() <= 8KBVrfTensor8KB / Slice Element::SIZE * size_of::<D>() <= 8KB -
Element정렬: 디바이스 저장소 API는 주소에 정렬 제약 조건을 부과합니다. 모든 텐서에 하나의 주소 규칙이 적용된다고 가정하지 말고, 계층별 생성자와 백엔드 검사를 확인하십시오.
시간 차원
TuTensor는 Tensor Unit을 통해 스트림으로 흐르는 텐서 데이터를 나타냅니다. SRAM 타입과 동일한 Chip, Cluster, Slice 차원을 유지하며, 스트리밍을 위해 Time과 Packet을 추가합니다. Time은 시간 차원으로, 전달 반복의 순서를 정합니다. 공간 차원과 달리 Time에는 하드웨어가 부과하는 크기 제한이 없으며, 처리할 데이터의 양에 따라 커집니다. Packet은 각 Slice가 시간 반복마다 받는 요소 수를 결정하는 추가 공간 차원입니다.
#![allow(unused)]
fn main() {
#![feature(adt_const_params)]
extern crate furiosa_opt_std;
use furiosa_opt_std::prelude::*;
use std::marker::ConstParamTy;
use std::marker::PhantomData;
axes![N = 4, C = 64, H = 32, W = 32];
/// Pipeline stage.
/// `Vector` is intentionally absent: the Vector Engine uses a separate typestate
/// (`VectorBranchTensor` and friends) that tracks branch, ALU, and other Vector-specific state.
/// `Commit` is intentionally absent: once the Commit Engine writes results back to DM,
/// the data is at rest and the type becomes `DmTensor`, not `TuTensor`.
#[derive(PartialEq, Eq, ConstParamTy)]
enum Position {
Begin, // After the start of the pipeline
Fetch, // After the Fetch Engine
Switch, // After the Switch Engine
Collect, // After the Collect Engine
Contraction, // After the Contraction Engine
Reduce, // After the Reduce Engine
Cast, // After the Cast Engine
Transpose, // After the Transpose Engine
}
struct TuTensor<
'l, // Lifetime tied to the Tensor Unit context
const P: Position,
D: Scalar,
Chip: M,
Cluster: M,
Slice: M,
Time: M,
Packet: M,
> {
/* ... */
_marker: PhantomData<&'l (D, Chip, Cluster, Slice, Time, Packet)>,
}
type T<'l> = TuTensor<
'l,
{ Position::Fetch }, // Fetch Engine's output
bf16,
m![1], // Chip: single chip
m![1], // Cluster: single cluster
m![C / 2], // Slice: distribute 64 channels across 32 slices
m![N, H, W], // Time: iterate over batch (N) and spatial (H, W) dimensions
m![C % 2], // Packet: 2 channels per cycle
>;
}
타입 T는 전체 형태가 \(\{N=4, C=64, H=32, W=32\}\)인 텐서를 32개 Slice에 걸쳐 스트리밍합니다(Slice::SIZE = m![C / 2]::SIZE = 32). Time 차원(m![N, H, W])의 크기는 4 * 32 * 32 = 4096이며, 이는 시간 반복이 4,096회 있다는 뜻입니다. 각 시간 반복마다 Packet 차원 m![C % 2]는 각 Slice에 채널 2개를 전달합니다. 32개 Slice가 병렬로 동작하므로, 각 시간 반복은 총 32 * 2 = 64개 채널을 처리합니다.
NCHW 표현 추적
NCHW 예제에서는 배치를 네 개의 Chip에 분산하는 HBM 표현부터 시작해 표현이 어떻게 바뀌는지 추적할 수 있습니다:
#![allow(unused)]
fn main() {
extern crate furiosa_opt_std;
use furiosa_opt_std::prelude::*;
axes![N = 4, C = 64, H = 32, W = 32];
type Hbm = HbmTensor<bf16, m![N], m![C, H, W]>;
}
이를 활성 Chip 하나와 채널 두 개당 요소 Slice 하나로 구성된 DM으로 옮깁니다:
#![allow(unused)]
fn main() {
extern crate furiosa_opt_std;
use furiosa_opt_std::prelude::*;
axes![N = 4, C = 64, H = 32, W = 32];
type Dm = DmTensor<bf16, m![1], m![1], m![C / 2], m![N, H, W, C % 2]>;
}
위의 스트림 타입 T는 같은 채널 분할을 사용하지만, N, H, W는 Time에 두고 각 Slice의 두 채널은 Packet에 둡니다.
Index (N=1, C=17, H=3, W=5)를 추적해 봅시다:
- HBM은 Chip
1을 선택하며, 요소 오프셋은17 * 32 * 32 + 3 * 32 + 5 = 17,509입니다. - DM은 Slice
17 / 2 = 8을 선택하고, 요소 매핑은N=1, H=3, W=5와 패킷 위치17 % 2 = 1을 선택합니다. - 스트림은 Slice
8에서Time = 1 * 32 * 32 + 3 * 32 + 5 = 1,125와Packet = 1을 선택합니다.
값의 물리적 좌표는 단계마다 바뀌지만, 그 Index는 (1, 17, 3, 5)로 유지됩니다. 이렇게 매핑에서 표현으로 이어지는 경로를 추적할 수 있습니다. 텐서가 값을 가진다는 것이 무엇을 뜻하는지는 텐서 의미론에서 정의합니다.