텐서 이동
Furiosa-opt 커널은 Furiosa NPU용으로 컴파일되는 Rust 함수입니다. 이 장에서는 커널이 호스트 메모리, HBM, DM, Tensor Unit 사이에서 텐서 데이터를 전송하는 방법을 설명합니다. 빠른 시작 커널 설계에서는 완전한 커널을 통해 이러한 계층을 소개합니다. 전송 예제를 읽기 전에 이동 표에서 출발지와 목적지를 고르십시오. Sequencer에서는 공통 루프 모델을 설명합니다. 각 엔진 페이지에서는 구체적인 경계를 하나씩 문서화합니다.
이동 경로 선택
이동 표는 현재 값에서 목적지로 가는 연산을 선택합니다. 이 표는 각 공개 경계에 대한 연산과 컨텍스트를 제공합니다.
| 현재 값 | 목적지 | 연산과 컨텍스트 |
|---|---|---|
HostTensor | HbmTensor | Device::pdma와 함께 사용하는 HostTensor::to_hbm(비동기). DMA Engine을 참조하십시오. |
HbmTensor | HostTensor | Device::pdma와 함께 사용하는 HbmTensor::to_host(비동기). DMA Engine을 참조하십시오. |
HbmTensor | DmTensor | Device::tdma와 함께 사용하는 HbmTensor::to_dm. DMA Engine을 참조하십시오. |
DmTensor | HbmTensor | Device::tdma와 함께 사용하는 DmTensor::to_hbm. DMA Engine을 참조하십시오. |
HbmTensor | HbmTensor | 호출에 필요한 DMA 컨텍스트와 함께 사용하는 HbmTensor::to_hbm. DMA Engine을 참조하십시오. |
DmTensor | DmTensor | Device::tdma와 함께 사용하는 DmTensor::to_dm 또는 Tensor Unit을 거치는 fetch/commit 왕복. DMA Engine과 Commit Engine을 참조하십시오. |
DmTensorView | Tensor Unit 스트림 | TuContext::begin 후 fetch |
| Tensor Unit 스트림 | DmTensor | commit, 또는 기존 가변 뷰에는 commit_view |
복사 가능한 엔드투엔드 전송
이 페이지는 이동 경계, 컨텍스트 소유권, 수명 규칙을 정의합니다. 예제 전송은 HBM 텐서를 확장하고 재정렬한 뒤 HBM으로 되돌려 보냅니다. 디바이스 함수는 HBM의 데이터를 DM에 단계적으로 적재하고, 스트림을 fetch하고, 이를 캐스팅하여 collect한 뒤 DM에 commit하고, 결과를 다시 HBM으로 전송합니다. 호스트 테스트는 호스트에서 HBM으로, HBM에서 호스트로 가는 I/O만 대기하며 재정렬된 결과를 검사합니다.
예제 전송은 다음 순서로 읽고 변경하십시오.
| 단계 | 선언된 선택 |
|---|---|
| 입력 | A = 4096, B = 8, 입력 요소 타입은 i8, 입력 HBM 매핑은 m![1], m![A, B]입니다. |
| HBM→DM | DM의 Cluster는 m![1 # 2], Slice는 m![A / 16], Element는 m![A / 8 % 2, A % 8, B]입니다. |
| Fetch | OutTime은 m![A / 8 % 2]이고 OutPacket은 m![A % 8, B]입니다. |
| 계산과 Collect | 스트림은 i8을 i32로 캐스팅하고, 이어서 Collect는 Time에 m![A / 8 % 2, A % 8]을, Packet에 m![B]를 사용합니다. |
| Commit→DM→HBM | commit_trim::<m![B]>()는 DM 텐서를 m![A, B]로 기록하고, 이어서 to_hbm은 DMA 재배치를 통해 HbmTensor<i32, m![1], m![B, A]>를 반환합니다. |
소스의 정렬 단언은 유지하면서 여기에 선언된 축, 매핑 또는 요소 타입만 변경하십시오. 호스트 테스트는 pdma를 통해 to_hbm과 to_host를 대기한 다음, 예상한 B-major 결과를 단언합니다.
#![expect(clippy::type_complexity)]
use furiosa_opt_std::prelude::*;
axes![A = 4096, B = 8];
type Chip = m![1];
type Cluster = m![1 # 2];
#[device(chip = 1)]
pub fn fetch_commit_simple(
device: &mut Device,
input: &HbmTensor<i8, m![1], m![A, B]>,
) -> HbmTensor<i32, m![1], m![B, A]> {
// Element's innermost axis must match the source's innermost (B, stride 1) so that the
// DMA tail contains the full B axis (8 × i8 = 8 bytes), satisfying min_align = 8.
let input_dm = input.to_dm::<Cluster, m![A / 16], m![A / 8 % 2, A % 8, B]>(&mut device.tdma);
let fetch_and_commit_tensor: DmTensor<i32, Chip, Cluster, m![A / 16], m![A / 8 % 2, A % 8, B]> = device
.main
.begin(input_dm.view())
.fetch::<m![A / 8 % 2], m![A % 8, B]>()
.fetch_cast::<i32>()
.collect::<m![A / 8 % 2, A % 8], m![B]>()
.commit_trim::<m![B]>()
.commit();
fetch_and_commit_tensor.to_hbm(&mut device.tdma)
}
use furiosa_opt_examples::fetch_commit::fetch_commit_simple;
use furiosa_opt_std::prelude::*;
#[tokio::test]
async fn test_fetch_commit_simple_host() {
use furiosa_opt_examples::fetch_commit::{A, B};
let mut device = Device::new(fetch_commit_simple.topology()).unwrap();
// Create input tensor with shape (A=4096)(B=8).
let input = HostTensor::<i8, m![A, B]>::from_vec((0..32768).map(|x| x as i8).collect::<Vec<_>>())
.to_hbm::<m![1], m![A, B]>(&mut device.pdma)
.await
.unwrap();
// Call the device function.
let output = launch(fetch_commit_simple, (&mut device, &input)).await.unwrap();
let mut expected = vec![0i32; 4096 * 8];
let mut idx = 0;
for b in 0..8 {
for a in 0..4096 {
expected[idx] = ((a * 8 + b) as i8) as i32;
idx += 1;
}
}
assert_eq!(
output.to_host::<m![B, A]>(&mut device.pdma).await.unwrap().into_inner(),
Tensor::<_, m![B, A], CurrentBackend>::from_vec(expected)
);
}
포함된 소스는 아래에서 설명하는 순서 규칙과 수명 규칙을 모두 보여 주는 완전한 예제입니다. 이 장에서는 이 전송을 읽기 쉬운 HBM↔DM 및 수명 튜토리얼로 남겨 두며, 상세한 패킷 및 성능 비교는 링크된 Tensor Unit I/O 패턴에서 다룹니다.
타입 수준의 Chip, Cluster, Slice, Time, Element 매핑은 각 값의 저장 방식과 스트림 동작을 정의합니다. DMA 호출은 타입 매개변수로 지정된 매핑을 갖는 대상을 생성합니다. 이 호출은 소스를 변경하지 않습니다. Fetch와 commit은 Tensor Unit 파이프라인으로 들어가고 나오는 인계 지점입니다.
첫 파이프라인에서는 HBM 입력을 DM에 스테이징하고 begin(...).fetch(...)를 호출한 다음, 계산 단계를 실행하고 스트림을 DM에 커밋한 뒤 결과를 다시 HBM으로 전송하십시오. 사례 연구: Tensor Unit I/O에서 구체적인 매핑과 소스 코드로 이 순서를 보여 줍니다.
전송 순서 유지
Device::new(Topology { chips, pes })는 Chip을 열며 launch가 실행되는 대상입니다. 이 디바이스는 서로 독립적인 main 및 sub Tensor Unit 컨텍스트와 tdma 및 pdma DMA 컨텍스트를 가집니다. 각 연산에는 그에 맞는 가변 컨텍스트를 전달하십시오. Rust의 차용(borrow)은 Tensor Unit 스트림이 소스 뷰를 살아 있게 유지하도록 강제합니다. begin은 DmTensorView를 차용하며, 스트림의 수명은 그 뷰보다 길 수 없습니다. Fetch는 BeginTensor를 소비하여 스트림을 생성합니다. fetch_cast나 collect 같은 어댑터는 스트림을 소비하고 다음 단계를 반환합니다. Commit은 최종 스트림을 소비하거나, 주어진 DmTensorViewMut에 쓰면서 소비합니다.
호스트 전송인 to_hbm과 to_host는 비동기 I/O이므로 반드시 await해야 합니다. 커널 측의 to_dm, to_hbm, fetch, commit 연산은 각자 선택된 컨텍스트에 디바이스 명령을 큐에 넣습니다. 이를 감싸는 커널 또는 백엔드 제출이 완료될 때까지 소스 핸들과 대상 핸들을 살아 있게 유지하십시오. 가변 뷰는 대상 영역을 의도적으로 덮어쓸 때만 사용하십시오. 대상을 생성하는 전송이나 커밋이 완료되기 전에는 그 대상을 읽지 마십시오. 또한 순서 간선(ordering edge) 없이 별칭 뷰에 대한 쓰기를 겹치게 하지 마십시오.
엔진 경계
- Fetch는 Slice당 하나의 Sequencer로 DM을 읽고 Tensor Unit용 패킷 스트림을 내보냅니다. 출력 매핑은 스트림의
Time축과Packet축을 선택합니다. 선택된 차원에 브로드캐스트가 포함된 경우 축 리프팅으로 축을Time에서Chip,Cluster또는Slice로 옮길 수도 있습니다. - Commit은 Tensor Unit 스트림을 DM에 씁니다.
Element매핑은 대상 레이아웃을 선택하며, 쓰는 동안 스트림 축을 전치할 수 있습니다. - DMA는 메모리 간 이동을 위해 읽기 Sequencer와 쓰기 Sequencer를 짝지어 사용합니다. 지원되는 공개 경로는 HBM에서 HBM, HBM에서 DM, DM에서 HBM, DM에서 DM입니다. 간접 gather/scatter API는 인덱스 단위에 대해 별도의 계약을 가집니다. 스케일되지 않은 gather는 원시 행 위치를 담은
DmTensor인덱스를 사용하며, 스케일되지 않은 scatter는 현재 구현되어 있지 않습니다. SPM에는 공개 텐서 타입이 없습니다.
따라서 Tensor Unit 파이프라인은 다음과 같습니다.
flowchart LR
H[Host] <-->|PCIe DMA| B[HBM]
B <-->|Tensor DMA| D[DM]
D -->|begin + fetch| F[Tensor Unit stream]
F -->|commit| D
F --> A[Adapters / switch / collect]
A --> C[Compute]
C -->|commit| D
Collect는 Contraction 작업이나 Vector 작업을 위해 스트림 피연산자를 TRF 또는 VRF 레지스터 파일에 배치할 수 있습니다. 이러한 계산 경계는 텐서 계산에서 다룹니다.
이동 튜닝
모든 이동은 수학적 텐서 이동입니다. 논리적 값은 보존되지만 대상 매핑은 바뀔 수 있습니다. 컴파일러는 두 매핑으로부터 Sequencer 스트라이드를 도출합니다. 성능을 튜닝하기 전에 엔진 제약 조건을 지키십시오. DM Cluster는 1 또는 2여야 하고, DM Slice는 64, 128 또는 256이어야 하며, DMA 패킷 꼬리는 API가 검사하는 요소 크기 정렬을 만족해야 합니다. Fetch와 Commit도 각자의 매핑별 Sequencer 규칙을 검증합니다. 잘못된 매핑은 컴파일이나 명령 검증 중에 실패합니다.
트래픽을 Cluster, Slice, DM 뱅크, HBM 채널 전반에 분산하는 연속적인 패킷 레이아웃과 매핑을 우선적으로 사용하십시오. 패킷이 작거나 스트라이드가 있으면 Sequencer 접근 횟수가 늘어납니다. DM 뱅크 패턴이 충돌하면 DMA가 기아 상태에 빠질 수 있습니다. 뱅크 기아 규칙, HBM 인터리빙, 패킷 크기 트레이드오프는 메모리 성능을 참조하십시오.
완전한 엔드투엔드 이동 패턴은 사례 연구: Tensor Unit I/O를 참조하십시오. 간접 HBM 행 이동은 DMA gather와 scatter를 참조하십시오.
함께 보기
furiosa-opt-std rustdoc에 릴리스 API가 문서화되어 있습니다. 연산별 계약은 위의 엔진 페이지를 참조하고, 유효한 이동 계획에 오버랩 측정이나 해저드 진단이 필요할 때는 스케줄링과 튜닝을 참조하십시오.