인터프리터 설계

데이터 모델

StableHLO 프로그램은 텐서에 대한 계산 (n차원 배열)는 현재 모델에서 클래스 Tensor. Tensor 객체의 기본 스토리지 클래스 detail::Buffer: 텐서의 mlir::ShapedType를 텐서의 변경 가능한 blob을 나타내는 mlir::HeapAsmResourceBlob 객체 연속 바이트 배열로 배열된 데이터를 주에서 부순입니다. detail::Buffer 객체는 메모리 관리를 간소화하기 위해 참조로 계산됩니다.

텐서의 개별 요소는 Element 클래스를 사용하여 표현됩니다. APInt, APFloat 또는 중 하나를 보유하는 차별된 공용체 사용 pair<APFloat,APFloat>: 스토리지 마지막 항목은 요소를 저장하고 사용할 수 있습니다

Tensor에는 개별 요소와 상호작용하는 다음 API가 있습니다.

  • Element Tensor::get(llvm::ArrayRef<int64_t> index): 다차원 색인 index의 개별 텐서 요소 Element 객체를 지정합니다.
  • void Tensor::set(llvm::ArrayRef<int64_t> index, Element element);: Element 객체 element를 다차원에서 텐서로 업데이트 색인 index.

통역 모드 작동 방식

인터프리터의 엔트리 함수는

SmallVector<Tensor> eval(func::FuncOp func, ArrayRef<Tensor> args);

이는 다음 작업을 실행합니다.

  1. func의 SSA 인수 및 연결된 런타임 Tensor를 추적합니다. 기호 테이블 맵 M을 사용하여 args에 제공된 값
  2. func 내의 각 작업에 대해 SSACFG 순서로 다음을 실행합니다. <ph type="x-smartling-placeholder">
      </ph>
    • 작업에서 eval를 호출합니다. 오퍼레이션의 각 SSA 피연산자에 대해 M의 런타임 값이 eval 호출에 대한 인수로 제공될 수 있습니다.
    • 작업의 SSA 결과와 M 단위로 평가된 값을 추적합니다.

(2)에서 언급된 작업 수준 eval는 다음을 구현합니다. 작업의 실행 의미 체계입니다. 다음은 stablehlo::AddOp의 예입니다. 이 예에서 lhsrhs 텐서의 개별 요소는 쌍입니다. Element 객체로 추출된 후 추가됩니다. 이러한 추가의 결과, Element 객체로, 최종 result 텐서에 저장됩니다.

Tensor eval(AddOp op, const Tensor &lhs, const Tensor &rhs) {
  Tensor result(op.getType());

  for (auto it = result.index_begin(); it != result.index_end(); ++it)
    result.set(*it, lhs.get(*it) + rhs.get(*it));

  return result;
}

전반적으로 인터프리터의 디자인은 eval 함수를 구현해야 합니다. StableHLO의 참조 구현 역할을 합니다. 예를 들어 eval를 템플릿 함수로 정의하고 요소 유형으로 매개변수화 다양한 요소 유형이 처리되는 방식에 대한 세부정보를 캡슐화합니다. Element::operator+eval의 구현을 간소화합니다.

일정한 접기를 위한 인터프리터 사용

인터프리터 메커니즘을 사용하여 상수 피연산자로 연산을 접을 수 있습니다. 값으로 사용됩니다. 다음 코드 스니펫은 부동 소수점 유형 피연산자가 있는 접기 stablehlo::AddOp의 경우 다음과 같습니다.

OpFoldResult AddOp::fold(FoldAdaptor adaptor) {
  auto attrs = adaptor.getOperands();
  DenseElementsAttr lhsData = dyn_cast<DenseElementsAttr>(attrs[0]);
  DenseElementsAttr rhsData = dyn_cast<DenseElementsAttr>(attrs[1]);
  if (!lhsData || !rhsData) return {};

  auto lhs = Tensor(lhsData);
  auto rhs = Tensor(rhsData);
  auto result = eval(*this, lhs, rhs);

  SmallVector<APFloat> values;
  for (auto i = 0; i < result.getNumElements(); ++i) {
    Element element = result.get(i);
    values.push_back(cast<FloatAttr>(element.getValue()).getValue());
  }

  return DenseElementsAttr::get(result.getType(), values);
}

현재 Google은 인터프리터를 영어로 통합하기 위한 작업을 활발하게 하고 있지 않습니다. StableHLO에 폴더를 구현할 계획이 없기 때문에 계속 접히게 됩니다. 그러나 향후에는 인터프리터를 활용하여 코드 스니펫의 인체공학을 개선할 것입니다. 예를 들어 상수 피연산자를 Tensor 객체를 만들고 Tensor 결과를 OpFoldResult로 압축해제합니다.

StableHLO 인터프리터 테스트

인터프리터는 입력 (A) StableHLO 프로그램 및 (B) 데이터 값을 프로그램에 피드하고 출력 데이터 값을 생성하여 사용자가 제공한 예상 데이터 값과 비교합니다. 데이터 값 (B)는 stablehlo.constant 작업을 사용하여 프로그램 자체에 하드 코딩됩니다. 이 인터프리터가 입력 프로그램을 평가합니다 테스트 중인 작업의 출력 다음과 같이 검사 (예: check.expect_eq, check.expect_almost_eq)를 통해 확인됩니다. 확인할 수 있습니다. check.expect_eqcheck.expect_eq_const는 비트 단위로 확인합니다. 지원되는 모든 유형과 check.expect_almost_eq가 같음 check.expect_almost_eq_const는 허용 오차 내에서 거의 동등한지 확인합니다. 테스트 가이드라인 (G6)에 설명되어 있습니다.

// CHECK-LABEL: Evaluated results of function: add_op_test_ui4
func.func @add_op_test_ui4() {
  %0 = stablehlo.constant dense<[0, 2]> : tensor<2xui4>
  %1 = stablehlo.constant dense<[15, 3]> : tensor<2xui4>
  %2 = stablehlo.add %0, %1 : tensor<2xui4>
  check.expect_eq_const %2, [15, 5] : tensor<2xui4>
  func.return
}

테스트 유틸리티 stablehlo-translate --interpret (코드) 는 프로그램을 파싱하고, 함수입니다. Google Cloud에는 각 StableHLO 작업에 대해 다양한 런타임 동작을 실행하는 여러 테스트의 예시입니다. 테스트는 여기에서 확인할 수 있습니다.

검사 가이드라인

(G1) 모든 작업에서 지원되는 모든 유형을 테스트해야 하나요?

다음과 같은 규칙을 조합하여 사용할 수 있습니다.

  1. 작업을 구현하는 동안 상응하는 eval에 코드가 있는 경우 함수를 사용하여 특정 유형을 처리해야 하는 경우, 테스트를 살펴봤습니다 예를 들어 add 작업의 경우 배타적 코드가 있습니다. 정수, 부울, 부동 소수점, 복합 유형을 처리해야 하므로 각 유형 카테고리에 하나의 테스트가 필요합니다.

  2. 유형 집합이 상응하는 eval 함수에서 균일하게 처리되는 경우 이러한 모든 유형에 대한 단일 테스트로 충분합니다. 예를 들어 add 작업의 경우 정수 유형의 모든 변형 (si4, u4, si8, u8) 등)는 llvm::APInt API를 사용하여 동일하게 처리되므로 각 변형에 테스트를 추가하고 대신 단일 대표 테스트입니다. 담당자 선택 시 모호함을 피하기 위해 다음 가이드라인을 따라야 합니다.

    • 균일하게 처리되는 모든 유형이 동일한 기본 유형을 갖는 경우 (즉, 모두 정수, 부동 소수점 또는 복합 유형인 경우) 최대 비트 너비가 있는 것을 선택하세요.
    • 균일하게 처리되는 모든 유형에 기본 유형이 혼합된 경우 다음 원시 유형을 기준으로 내림차순으로 선택합니다. 환경설정: 정수, 부동 소수점, 부울, 복잡도

(G2) 오퍼레이션을 처리하는 데 필요한 테스트 수는 어떻게 결정하나요? 무엇인가요?

작업의 인터프리터 로직을 포괄적으로 다루는 것이 목표입니다. (즉, 구현의 모든 특수한 사례)을 실행할 수 있습니다. 유지관리를 위해 테스트 수를 최소화하는 것이 중요합니다. 테스트 횟수 감소 보다 쉽게 검토하고 게시할 수 있도록 작업을 종합적으로 다룹니다. 따라서 대부분의 간단한 테스트는 하나의 테스트만 거치게 됩니다 적절한 이유에서 종합적이었다면 실용성이 떨어지는 경우 90% 이상에서 멈추는 것이 좋습니다. 결정될 예정임 pull 요청을 검토하는 동안

(G3) 통역 인프라 테스트를 추가해 보시겠어요?

통역 인프라는 대체로 간단하며 신뢰 기반입니다 간단하지 않은 유일한 부분은 다양한 유형이 기본 인터프리터 저장소에서 압축해제됩니다 (G1)에서 논의한 것처럼 다르게 처리되는 작업 유형만 테스트합니다. 다음으로 바꿉니다. 해당하는 다른 포장/압축 해제 코드가 정수/부동 소수점 유형의 변형은 있습니다. 전체 적용 범위를 보장하기 위해 다음과 같은 constant 작업을 선택할 수 있습니다. 모든 StableHLO 요소 유형을 지원하고 철저한 테스트를 작성합니다.

(G4) 한 오퍼레이션 구현이 다른 오퍼레이션에 의존하는 경우 어떻게 해야 할까요?

아니요. 예를 들어 batch_norm_grad의 구현은 다음을 기반으로 할 수 있습니다. divide, subtract, multiply 외 후자 테스트는 피해야 함 테스트하게 됩니다

(G5) 구현 정의 / 정의되지 않음을 실행하는 테스트를 작성해야 하는가 무엇일까요?

구현 정의 또는 작동하지 않습니다. 구현 정의 동작 연습 테스트 통역사가 현지 동작을 보여줌으로써 일반화했습니다. 정의되지 않은 동작을 실행하는 테스트는 다음에 기여하지 않음 이해하는 것이 중요합니다.

(G6) 부동 소수점 유형에 대한 테스트를 작성하는 동안 확인에 지정해야 하는가?

기본 연산 (더하기, 빼기, 곱하기, 나누기 및 정사각형), IEEE 사양을 따르는 구현은 수학적으로 정확한 결과에서 0.5 ULP 이내의 반올림된 결과입니다. 그렇긴 하지만 우리는 이러한 연산에서 나올 것으로 예상되는 결과를 알 수 있습니다. 그러나 초월 함수에는 작동하지 않을 수 있습니다. (sine, cosine 등) 정밀도 보장 구현을 정의합니다 (근거).

현재 구현에서는 '일률적인' 공차 값을 0.0001로 설정합니다. 다음 예에서는 위의 허용 오차가 어떻게 작동하는지 보여줍니다.

func.func @check_tolerance() {
  %0 = stablehlo.constant dense<0.2> : tensor<f32>

  // The following check succeeds as %0 is almost equal to the provided
  // constant modulo the tolerance, mentioned above.
  check.expect_almost_eq_const %0, dense<0.19999> : tensor<f32>

  // The following check fails as %0 is not bitwise equal to the provided
  // constant.
  check.expect_eq_const %0, dense<0.19999> : tensor<f32>

  func.return
}

이는 StableHLO 작업의 수치적 정확성을 테스트하는 첫 번째 단계일 뿐입니다. 현재 이는 StableHLO 사양에서 과소 지정된 영역으로, 지속적인 노력 #1156 Google의 StableHLO 사용 경험과 Google의 피드백을 통해 있습니다. 작업이 진행됨에 따라 인프라를 업데이트할 예정입니다. 변경할 수 있습니다

(G7) 테스트의 코딩 스타일에 관한 의견이 있으신가요?

  1. 기본값 대신 입력/출력의 실제 이름을 사용해야 합니다. SSA 값 (예: %0, %1 등)
  2. 테스트에서 예쁘게 인쇄된 형식이 있는지 확인합니다.

(G8) 사양에 이미 제공된 예시를 포함해야 하나요? 예 (테스트의 완전성을 위해)