Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Commit Adapter

Commit Adapter는 Commit Engine이 패킷 스트림을 DM에 쓰기 전에 해당 스트림에 요소별 변환을 적용합니다. Commit Adapter는 Tensor Unit의 출력 측에서 Fetch Adapter와 대칭을 이룹니다.

Adapter의 단계는 업스트림 텐서에 대한 전용 .commit_xxx(...) 메서드로 체인을 이루며, 체인은 항상 실제 DM 쓰기를 수행하는 .commit(...)으로 끝납니다. 트리밍은 필수 첫 단계입니다. .commit() / .commit_view()는 .commit_trim(...) 이후에만 호출할 수 있으므로 모든 commit은 먼저 트리밍됩니다(플릿 패딩은 이 단계에서 제거됩니다). 나머지 공개 단계는 선택적인 타입 캐스팅이며, 그다음부터 main 컨텍스트와 sub 컨텍스트가 갈라집니다. Generate Mode는 memset이 즉시 값 채우기에 내부적으로 사용하는 별도의 서브 컨텍스트 경로이며, TuTensor에서 체인으로 이어지지 않습니다.

  • Main 파이프라인: 트리밍 → 타입 캐스팅(선택적으로 ReLU 융합) → .commit().
  • Sub 우회 경로: Generate Mode는 업스트림 Tensor Unit 스트림을 소비하지 않고 memset을 위해 상수를 직접 씁니다.
연산MainSub
트리밍✅✅
타입 캐스팅(선택적 ReLU 융합)✅❌
Generate Mode(내부 memset 경로)❌✅

트리밍

Tensor Unit 파이프라인의 스트림 패킷은 항상 32바이트 플릿이지만(Collect Engine 참고), 플릿은 용량보다 적은 유효 요소를 담을 수 있으며 이 경우 뒤쪽 요소는 패딩으로 채워집니다. 플릿 전체를 그대로 쓰면 유효 영역 너머의 DM 바이트가 플릿의 패딩 값으로 덮어쓰입니다.

트리밍은 각 플릿의 앞쪽 valid_size개 요소만 DM에 쓰고 뒤쪽 패딩은 버림으로써 이 문제를 해결합니다. 컴파일러는 출력 텐서 매핑으로부터 valid_size를 도출합니다. 사용자가 이 값을 직접 설정하지는 않습니다. D[valid_size]는 8, 16, 24, 32바이트 중 하나여야 합니다(32는 트리밍하지 않음을 의미합니다). 트리밍은 지연 시간을 거의 추가하지 않습니다.

모든 커밋에 제거할 패딩이 있는 것은 아니지만, 트리밍은 Commit Adapter의 필수 첫 단계입니다. valid_size가 이미 32바이트이면 플릿 전체가 유효하므로 트리밍은 아무 작업도 하지 않습니다(no-op). 트리밍이 필수인 이유는 .commit()을 .commit_trim(...) 이후에만 호출할 수 있기 때문이며, 따라서 트리밍은 체인의 기점이 되고 타입 캐스팅(main)보다 먼저 실행됩니다.

// `D: MaterializableScalar` here (trim is the commit path's mandatory first stage) keeps i5/i9 uncommittable.
impl<
    'l,
    const T: Tu,
    P: CanApplyCommitTrim,
    D: MaterializableScalar,
    Chip: M,
    Cluster: M,
    Slice: M,
    Time: M,
    Packet: M,
    B: Backend,
> TuTensor<'l, T, P, D, Chip, Cluster, Slice, Time, Packet, B>
{
    /// Runs the Commit Adapter's trimming stage.
    ///
    /// Drops the trailing padding from each flit so DM stores only valid
    /// elements. `OutPacket` is the post-trim layout the kernel
    /// promises; the compiler derives the trim count from the input and
    /// output mappings.
    #[primitive(TuTensor::commit_trim)]
    pub fn commit_trim<OutPacket: M>(self) -> CommitTrimTensor<'l, T, D, Chip, Cluster, Slice, Time, OutPacket, B> {
        verify_commit_trim::<D, Packet, OutPacket>();
        // `transpose(false)` is type-system filler; real trim lowering lands with the backend wiring.
        CommitTrimTensor::new(self.device, self.inner.transpose(false))
    }
}

.commit_trim::<OutPacket>()은 트리밍 후의 패킷을 선언하고, 이어서 체인된 .commit(...)이 트리밍된 스트림에 대해 DM 쓰기를 수행합니다. 두 동작은 완전히 분리되어 있습니다.

#![feature(adt_const_params)]
extern crate furiosa_opt_std;
use furiosa_opt_std::prelude::*;
axes![M = 4, K = 2, W = 8, N = 16, J = 64];

fn commit_trim_i8_padding<'l, const T: Tu>(
    input: CastTensor<'l, T, i8, m![1], m![1], m![1], m![M, K], m![W # 32]>,
) -> CommitTrimTensor<'l, T, i8, m![1], m![1], m![1], m![M, K], m![W]> {
    // 8 valid i8 out of 32 padded; OutPacket drops the `# 32` padding.
    input.commit_trim::<m![W]>()
}

fn commit_trim_f32_non_padding<'l, const T: Tu>(
    input: ContractTensor<'l, T, f32, m![1], m![1], m![1], m![M, K], m![W]>,
) -> CommitTrimTensor<'l, T, f32, m![1], m![1], m![1], m![M, K], m![W = 4]> {
    // 4 valid f32 out of 8; OutPacket resizes `W` to 4.
    input.commit_trim::<m![W = 4]>()
}

fn commit_trim_bf16_with_transpose<'l, const T: Tu>(
    input: CastTensor<'l, T, bf16, m![1], m![1], m![1], m![M, K], m![N]>,
) -> CommitTrimTensor<'l, T, bf16, m![1], m![1], m![1], m![M, K], m![N = 8]> {
    // 8 valid bf16 out of 16; OutPacket resizes `N` to 8.
    input.commit_trim::<m![N = 8]>()
}

fn commit_trim_i4_no_trim<'l, const T: Tu>(
    input: CastTensor<'l, T, i4, m![1], m![1], m![1], m![M, K], m![J]>,
) -> CommitTrimTensor<'l, T, i4, m![1], m![1], m![1], m![M, K], m![J]> {
    // No trimming; `OutPacket == Packet`.
    input.commit_trim::<m![J]>()
}

let mut device = Device::new(Topology { chips: 1, pes: 8 }).unwrap();
let a: CastTensor<'_, _, i8, m![1], m![1], m![1], m![M, K], m![W # 32]> = CastTensor::new(&mut device.main, Tensor::zero());
let _o = commit_trim_i8_padding(a);
let b: ContractTensor<'_, _, f32, m![1], m![1], m![1], m![M, K], m![W]> = ContractTensor::new(&mut device.main, Tensor::zero());
let _o = commit_trim_f32_non_padding(b);
let c: CastTensor<'_, _, bf16, m![1], m![1], m![1], m![M, K], m![N]> = CastTensor::new(&mut device.main, Tensor::zero());
let _o = commit_trim_bf16_with_transpose(c);
let d: CastTensor<'_, _, i4, m![1], m![1], m![1], m![M, K], m![J]> = CastTensor::new(&mut device.main, Tensor::zero());
let _o = commit_trim_i4_no_trim(d);

타입 캐스팅

타입 캐스팅은 커밋 경로에서 f32 데이터를 bf16 형식으로 변환하며, 필요에 따라 같은 패스에 ReLU 활성화를 융합할 수 있습니다. Tensor Unit 파이프라인에서 대부분의 타입 변환은 Cast Engine이 처리합니다. Commit Adapter 타입 캐스팅은 메인 컨텍스트의 축약을 서브 컨텍스트의 Vector Engine 작업과 병렬로 실행하는 한 가지 특정한 경우를 위해 존재합니다. Cast Engine은 Vector Engine 위에 위치하므로 변환하는 동안 Vector Engine을 점유합니다. 메인 컨텍스트가 f32 → bf16 변환을 Cast Engine으로 수행하면 Vector Engine이 사용 중이 되어 서브 컨텍스트를 병렬로 실행할 수 없습니다. 대신 변환을 Commit Adapter를 거치도록 하면 Vector Engine을 서브 컨텍스트가 사용할 수 있도록 비워 둘 수 있습니다. 서브 컨텍스트 자체는 타입 캐스팅을 지원하지 않습니다(위의 지원 매트릭스와 일치합니다).

commit_cast와 commit_cast_relu는 모드가 있는 하나의 변환이 아니라 서로 별개의 하드웨어 변환이므로 별도의 메서드로 나뉘어 있습니다. ReLU에는 실행 시점에 선택할 수 있는 독립된 단계가 없으며, 축소 캐스트와 융합된 형태로만 존재합니다.

impl<
    'l,
    const T: Tu,
    P: CanApplyCommitCast,
    D: MaterializableScalar,
    Chip: M,
    Cluster: M,
    Slice: M,
    Time: M,
    Packet: M,
    B: Backend,
> TuTensor<'l, T, P, D, Chip, Cluster, Slice, Time, Packet, B>
{
    /// Runs the Commit Adapter's type-casting stage.
    ///
    /// Folds the `f32` → `bf16` cast into the commit path, leaving the
    /// [Cast Engine](crate::engine::cast) free for sub-context Vector Engine
    /// work. See [`CommitCast`](crate::prelude::CommitCast) for why this is the
    /// only conversion, and [`commit_cast_relu`](Self::commit_cast_relu) for the
    /// variant that fuses a ReLU.
    #[primitive(TuTensor::commit_cast)]
    pub fn commit_cast<OutD: Scalar>(self) -> CommitCastTensor<'l, T, OutD, Chip, Cluster, Slice, Time, Packet, B>
    where
        D: CommitCast<OutD>,
    {
        verify_commit_cast::<Packet>();
        CommitCastTensor::new(self.device, self.inner.map(|v| v.cast()))
    }

    /// The same cast with a ReLU fused in, clamping negative values to zero.
    ///
    /// A separate method rather than an argument because the two are separate
    /// hardware conversions (`CommitF32ToBf16` and `CommitF32ToBf16Relu`), and
    /// ReLU has no standalone stage to select at run time.
    #[primitive(TuTensor::commit_cast_relu)]
    pub fn commit_cast_relu<OutD: Scalar>(self) -> CommitCastTensor<'l, T, OutD, Chip, Cluster, Slice, Time, Packet, B>
    where
        D: CommitCast<OutD>,
    {
        verify_commit_cast::<Packet>();
        CommitCastTensor::new(self.device, self.inner.map(|v| v.cast_relu()))
    }
}
#![feature(adt_const_params)]
extern crate furiosa_opt_std;
use furiosa_opt_std::prelude::*;
axes![N = 4, C = 3, H = 4, W = 8];

fn commit_cast_example<'l, const T: Tu>(
    input: ContractTensor<'l, T, f32, m![1], m![1], m![1], m![N, C, H], m![W]>,
) -> CommitCastTensor<'l, T, bf16, m![1], m![1], m![1], m![N, C, H], m![W]> {
    // Cast f32 to bf16 (values preserved), no activation. A real main
    // commit runs `.commit_trim()` first, then `.commit(...)` after.
    // W = 8 f32 elements (32 bytes) stays 8 bf16 elements (16 bytes).
    input.commit_cast::<bf16>()
}

fn commit_cast_relu_example<'l, const T: Tu>(
    input: ContractTensor<'l, T, f32, m![1], m![1], m![1], m![N, C, H], m![W]>,
) -> CommitCastTensor<'l, T, bf16, m![1], m![1], m![1], m![N, C, H], m![W]> {
    // f32 -> bf16 with a fused ReLU: negative values clamped to zero.
    // e.g. [-5.0, -0.1, 0.0, 3.7] -> [0.0, 0.0, 0.0, 3.7]
    input.commit_cast_relu::<bf16>()
}

원문 © FuriosaAI · Programming Tensor Contraction Processors · Apache License 2.0 · NOTICE
yeokja 한국어 번역 · Anthropic 사의 claude-sonnet-5 모델을 활용하여 번역되었으며 학습을 모두 비허용한 상태로 작업하였습니다 · 이 문서는 원문을 수정한 비공식 번역본이며 Apache License 2.0으로 제공합니다.