দোভাষী ডিজাইন

ডেটা মডেল

StableHLO প্রোগ্রামগুলি হল টেনসর (এন-ডাইমেনশনাল অ্যারে) এর উপর গণনা, যা বর্তমান মডেলে Tensor ক্লাস ব্যবহার করে প্রয়োগ করা হয়। একটি Tensor অবজেক্টের জন্য অন্তর্নিহিত স্টোরেজ ক্লাস, detail::Buffer , একটি mlir::ShapedType টেনসরের সাথে একটি mlir::HeapAsmResourceBlob অবজেক্ট সঞ্চয় করে যা মেজর-থেকে-মাইনোরে সংলগ্ন বাইট অ্যারে হিসাবে টেনসর ডেটার একটি পরিবর্তনযোগ্য ব্লব উপস্থাপন করে। আদেশ detail::Buffer অবজেক্টগুলি মেমরি পরিচালনাকে সহজ করার জন্য রেফারেন্স-গণনা করা হয়।

একটি টেনসরের স্বতন্ত্র উপাদানগুলিকে Element ক্লাস ব্যবহার করে উপস্থাপন করা হয়, যা স্টোরেজের জন্য APInt , APFloat বা pair<APFloat,APFloat> ধারণ করে একটি বৈষম্যমূলক ইউনিয়ন ব্যবহার করে। শেষটি জটিল ধরনের উপাদান সংরক্ষণের জন্য ব্যবহৃত হয়।

Tensor স্বতন্ত্র উপাদানগুলির সাথে ইন্টারঅ্যাক্ট করার জন্য নিম্নলিখিত API রয়েছে:

  • Element Tensor::get(llvm::ArrayRef<int64_t> index) : Element অবজেক্ট হিসাবে মাল্টি-ডাইমেনশনাল ইনডেক্স index একটি পৃথক টেনসর উপাদান বের করতে।
  • void Tensor::set(llvm::ArrayRef<int64_t> index, Element element); : বহুমাত্রিক সূচক index একটি টেনসরে একটি Element বস্তুর element আপডেট করতে।

দোভাষী কিভাবে কাজ করে

দোভাষী এন্ট্রি ফাংশন হয়

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

যা নিম্নলিখিত কাজ করে:

  1. func SSA আর্গুমেন্ট এবং তাদের সম্পর্কিত রানটাইম Tensor মানগুলি ট্র্যাক করে, একটি প্রতীক টেবিল ম্যাপ ব্যবহার করে, args এ প্রদত্ত, M।
  2. SSACFG ক্রম অনুসারে func মধ্যে প্রতিটি অপশনের জন্য:
    • অপে eval আহ্বান করে। অপের প্রতিটি এসএসএ অপারেন্ডের জন্য, eval ইনভোকেশনের আর্গুমেন্ট হিসেবে M থেকে এর রানটাইম মান বের করুন।
    • অপের SSA ফলাফল(গুলি) এবং M-তে মূল্যায়ন করা মান ট্র্যাক করে।

(2) তে উল্লিখিত অপ-লেভেল eval অপের এক্সিকিউশন সিমেন্টিকস বাস্তবায়নের জন্য দায়ী। নিম্নলিখিত stablehlo::AddOp জন্য একটি উদাহরণ। উদাহরণে, lhs এবং rhs টেনসরের পৃথক উপাদানগুলিকে 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);
}

এই মুহুর্তে, আমরা দোভাষীকে ধ্রুবক ভাঁজ করার জন্য সক্রিয়ভাবে কাজ করছি না কারণ আমরা StableHLO-এর জন্য ফোল্ডার বাস্তবায়নের পরিকল্পনা করছি না। যাইহোক, ভবিষ্যতে, আমরা MHLO-তে ধ্রুবক ভাঁজ করার জন্য দোভাষীর সুবিধা নেওয়ার পরিকল্পনা করছি, এই সময়ে আমরা উপরের কোড স্নিপেটের এর্গোনমিক্সকে উন্নত করব (যেমন আমাদের একটি সহায়ক ফাংশন থাকতে পারে যা Tensor অবজেক্টে ধ্রুবক অপারেন্ড প্যাক করে এবং আনপ্যাক করে। Tensor ফলাফল OpFoldResult )।

StableHLO দোভাষী পরীক্ষা করা হচ্ছে

ইন্টারপ্রেটার ইনপুট হিসাবে নেয় (A) একটি StableHLO প্রোগ্রাম, এবং (B) ডেটা মানগুলি প্রোগ্রামে খাওয়ানোর জন্য, এবং আউটপুট ডেটা মান তৈরি করে, যা ব্যবহারকারী-প্রদত্ত প্রত্যাশিত ডেটা মানগুলির সাথে মিলে যায়। stablehlo.constant অপারেশন ব্যবহার করে প্রোগ্রামেই ডাটা মান (B) হার্ড-কোড করা হয়। দোভাষী ইনপুট প্রোগ্রাম মূল্যায়ন করে। পরীক্ষার অধীনে অপের আউটপুট (গুলি) চেকের মাধ্যমে পরীক্ষা করা হয় (যেমন check.expect_eq , check.expect_almost_eq ), নীচে দেখানো হিসাবে। check.expect_eq এবং check.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 ( কোড ) প্রোগ্রামটি পার্স করার জন্য দায়ী, ফাংশন গঠনকারী ক্রিয়াকলাপ সহ প্রতিটি ফাংশন ব্যাখ্যা করে। আমাদের একটি ডেডিকেটেড টেস্ট-স্যুট রয়েছে, প্রতিটি StableHLO Op-এর জন্য বিভিন্ন রানটাইম আচরণের ব্যায়াম করার জন্য বেশ কয়েকটি পরীক্ষা নিয়ে গঠিত। পরীক্ষা এখানে পাওয়া যাবে.

পরীক্ষার নির্দেশিকা

(G1) আমাদের কি প্রতিটি অপশনের জন্য সমস্ত সমর্থিত প্রকারের জন্য পরীক্ষা করতে হবে?

আমরা সিদ্ধান্ত নিতে নিম্নলিখিত নিয়মগুলির সংমিশ্রণ ব্যবহার করতে পারি:

  1. একটি অপ প্রয়োগ করার সময়, যদি একটি নির্দিষ্ট প্রকারকে পরিচালনা করার জন্য সংশ্লিষ্ট eval ফাংশনে কোড থাকে, তাহলে সেই প্রকারটি কভার করার জন্য পরীক্ষা(গুলি) থাকা অপরিহার্য। একটি উদাহরণ হিসাবে, add অপের জন্য, পূর্ণসংখ্যা, বুলিয়ান, ফ্লোটিং-পয়েন্ট এবং জটিল প্রকারগুলি পরিচালনা করার জন্য একচেটিয়া কোড রয়েছে এবং তাই আমাদের প্রতিটি ধরণের ধরণের জন্য একটি পরীক্ষা প্রয়োজন।

  2. যদি অনুরূপ eval ফাংশনে এক ধরনের সেট একইভাবে পরিচালনা করা হয়, তাহলে এই সমস্ত ধরণের জন্য একটি একক পরীক্ষা যথেষ্ট হওয়া উচিত। একটি উদাহরণ হিসাবে, add অপের জন্য, পূর্ণসংখ্যার প্রকারের সমস্ত রূপ ( si4 , u4 , si8 , u8 এবং তাই) llvm::APInt API ব্যবহার করে একইভাবে পরিচালনা করা হয়, এবং তাই আমরা সেই ভেরিয়েন্টগুলির প্রতিটির জন্য পরীক্ষা যোগ করা এড়িয়ে যেতে পারি, এবং পরিবর্তে একটি একক প্রতিনিধি পরীক্ষা যোগ করুন। প্রতিনিধি নির্বাচনের ক্ষেত্রে অস্পষ্টতা এড়াতে, আমাদের নিম্নলিখিত নির্দেশিকাগুলি ব্যবহার করা উচিত:

    • যদি সমস্ত প্রকার, একইভাবে পরিচালনা করা হয়, একই আদিম প্রকার থাকে (অর্থাৎ, যদি সবগুলি পূর্ণসংখ্যা, বা ভাসমান-বিন্দু, বা জটিল প্রকার হয়), তাহলে সর্বাধিক বিট-প্রস্থ সহ একটি বেছে নিন।
    • যদি সমস্ত প্রকার, একইভাবে পরিচালনা করা হয়, আদিম প্রকারের মিশ্রণ থাকে, তাহলে পছন্দের ক্রমানুসারে নিম্নোক্ত আদিম প্রকারের একটি বেছে নিন: পূর্ণসংখ্যা, ভাসমান-বিন্দু, বুলিয়ান, জটিল।

(G2) একজন অপের আচরণ কভার করার জন্য প্রয়োজনীয় পরীক্ষার সংখ্যার বিষয়ে আমরা কীভাবে সিদ্ধান্ত নেব?

লক্ষ্য হল ন্যূনতম সংখ্যক পরীক্ষার সাথে op (অর্থাৎ বাস্তবায়নের সমস্ত কোণার ক্ষেত্রে) জন্য দোভাষীর যুক্তিকে ব্যাপকভাবে কভার করা। রক্ষণাবেক্ষণের জন্য পরীক্ষার সংখ্যা কম করা গুরুত্বপূর্ণ। আমাদের যত কম পরীক্ষা আছে, সেগুলি পর্যালোচনা করা এবং সেগুলি ব্যাপকভাবে অপশনটিকে কভার করে তা নিশ্চিত করা তত সহজ। ফলস্বরূপ, আমরা আশা করি যে বেশিরভাগ সহজ অপারেশনের শেষ হবে মাত্র একটি পরীক্ষা। যদি কোনো সঙ্গত কারণে ব্যাপক কভারেজ অব্যবহারিক হয়, তাহলে >= 90% এ থামানো ভালো। পুল অনুরোধ পর্যালোচনার সময় এটি কেস-বাই-কেস ভিত্তিতে সিদ্ধান্ত নেওয়া হবে।

(G3) দোভাষী পরিকাঠামোর জন্য পরীক্ষা যোগ করার বিষয়ে কীভাবে?

দোভাষী পরিকাঠামো বেশিরভাগই সহজবোধ্য এবং আমাদের বিশ্বাসের ভিত্তিতে যোগ করা যেতে পারে। একমাত্র অ-তুচ্ছ অংশ হল কিভাবে বিভিন্ন প্রকারকে প্যাক করা হয় এবং অন্তর্নিহিত ইন্টারপ্রেটার স্টোরেজ থেকে আনপ্যাক করা হয়। (G1) যেমন আলোচনা করা হয়েছে, আমরা কেবলমাত্র সেই ধরনের অপ পরীক্ষা করব যা ভিন্নভাবে পরিচালনা করা হয়। এর সাথে এটা সম্ভব যে প্যাকিং/আন-প্যাকিং কোড, পূর্ণসংখ্যা/ফ্লোটিং-পয়েন্ট প্রকারের বিভিন্ন রূপের সাথে সম্পর্কিত, পরীক্ষার সময় সম্পূর্ণরূপে কভার নাও হতে পারে। সম্পূর্ণ কভারেজ নিশ্চিত করার জন্য, আমরা constant মতো একটি অপশন বেছে নিতে পারি যা সমস্ত StableHLO উপাদান প্রকারকে সমর্থন করে এবং সম্পূর্ণ পরীক্ষা লিখতে পারে।

(G4) যদি একটি অপের বাস্তবায়ন অন্যান্য অপ্সের উপর নির্ভর করে, তাহলে আমাদের কি পরবর্তীটির জন্য পরীক্ষা লিখতে হবে?

না। উদাহরণস্বরূপ, batch_norm_grad এর বাস্তবায়ন divide , subtract , multiply এবং অন্যান্যের উপর ভিত্তি করে করা যেতে পারে। পূর্বের পরীক্ষা করার সময় আমাদের পরবর্তী অপারেশনগুলি পরীক্ষা করা এড়াতে হবে।

(G5) বাস্তবায়ন-সংজ্ঞায়িত/অনির্ধারিত আচরণ অনুশীলন করার জন্য আমাদের কি পরীক্ষা লিখতে হবে?

আমাদের পরীক্ষা লেখা উচিত নয় যা অপের বাস্তবায়ন-সংজ্ঞায়িত বা অনির্ধারিত আচরণ অনুশীলন করে। বাস্তবায়ন-সংজ্ঞায়িত আচরণ অনুশীলনকারী পরীক্ষাগুলি দোভাষীর স্থানীয় আচরণ প্রদর্শন করে যা সাধারণীকরণ করা উচিত নয়। অনির্ধারিত আচরণ অনুশীলনকারী পরীক্ষাগুলি অপের আচরণ বোঝার দিকে অবদান রাখে না।

(G6) ফ্লোটিং-পয়েন্ট প্রকারের জন্য পরীক্ষা লেখার সময়, প্রত্যাশিত ফলাফলটি চেকগুলিতে নির্দিষ্ট করা প্রয়োজন কী?

প্রাথমিক ক্রিয়াকলাপগুলির জন্য (যোগ, বিয়োগ, গুণ, ভাগ এবং বর্গ), IEEE স্পেসিফিকেশন অনুসরণ করে একটি বাস্তবায়ন গাণিতিকভাবে সঠিক ফলাফলের 0.5 ULP-এর মধ্যে একটি বৃত্তাকার ফলাফল প্রদান করবে বলে আশা করা হচ্ছে। এটি বলেছে, আমরা নিরাপদে কল্পনা করতে পারি যে এই অপারেশনগুলি থেকে প্রত্যাশিত ফলাফলটি সর্বাধিক 1 ইউএলপি আলাদা হতে পারে। যাইহোক, এটি ট্রান্সসেন্ডেন্টাল ফাংশনগুলির ( 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 ops-এর সংখ্যাগত নির্ভুলতা পরীক্ষা করার প্রথম ধাপ। এই মুহুর্তে, এটি StableHLO স্পেকের একটি নিম্ন-নির্দিষ্ট এলাকা, এবং অনুশীলনে StableHLO ব্যবহার করার অভিজ্ঞতা এবং স্টেকহোল্ডারদের প্রতিক্রিয়ার ভিত্তিতে #1156 বের করার জন্য কাজ চলছে। এই কাজটি এগিয়ে যাওয়ার সাথে সাথে, আমরা সেই অনুযায়ী পরিকাঠামো আপডেট করব।

(G7) পরীক্ষার কোডিং-শৈলী সম্পর্কে কিছু?

  1. এসএসএ মানগুলিতে ডিফল্ট না করে ইনপুট/আউটপুটগুলির প্রকৃত নাম ব্যবহার করা নিশ্চিত করুন (যেমন %0, %1, ইত্যাদি)
  2. নিশ্চিত করুন যে পরীক্ষাগুলি সুন্দর-মুদ্রিত বিন্যাস ব্যবহার করে, যদি এটি বিদ্যমান থাকে।

(G8) আমরা কি ইতিমধ্যে স্পেস দেওয়া উদাহরণ অন্তর্ভুক্ত করা উচিত? হ্যাঁ (পরীক্ষার সম্পূর্ণতার জন্য)।

,

ডেটা মডেল

StableHLO প্রোগ্রামগুলি হল টেনসর (এন-ডাইমেনশনাল অ্যারে) এর উপর গণনা, যা বর্তমান মডেলে Tensor ক্লাস ব্যবহার করে প্রয়োগ করা হয়। একটি Tensor অবজেক্টের জন্য অন্তর্নিহিত স্টোরেজ ক্লাস, detail::Buffer , একটি mlir::ShapedType টেনসরের সাথে একটি mlir::HeapAsmResourceBlob অবজেক্ট সঞ্চয় করে যা মেজর-থেকে-মাইনোরে সংলগ্ন বাইট অ্যারে হিসাবে টেনসর ডেটার একটি পরিবর্তনযোগ্য ব্লব উপস্থাপন করে। আদেশ detail::Buffer অবজেক্টগুলি মেমরি পরিচালনাকে সহজ করার জন্য রেফারেন্স-গণনা করা হয়।

একটি টেনসরের স্বতন্ত্র উপাদানগুলিকে Element ক্লাস ব্যবহার করে উপস্থাপন করা হয়, যা স্টোরেজের জন্য APInt , APFloat বা pair<APFloat,APFloat> ধারণ করে একটি বৈষম্যমূলক ইউনিয়ন ব্যবহার করে। শেষটি জটিল ধরনের উপাদান সংরক্ষণের জন্য ব্যবহৃত হয়।

Tensor স্বতন্ত্র উপাদানগুলির সাথে ইন্টারঅ্যাক্ট করার জন্য নিম্নলিখিত API রয়েছে:

  • Element Tensor::get(llvm::ArrayRef<int64_t> index) : Element অবজেক্ট হিসাবে মাল্টি-ডাইমেনশনাল ইনডেক্স index একটি পৃথক টেনসর উপাদান বের করতে।
  • void Tensor::set(llvm::ArrayRef<int64_t> index, Element element); : বহুমাত্রিক সূচক index একটি টেনসরে একটি Element বস্তুর element আপডেট করতে।

দোভাষী কিভাবে কাজ করে

দোভাষী এন্ট্রি ফাংশন হয়

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

যা নিম্নলিখিত কাজ করে:

  1. func SSA আর্গুমেন্ট এবং তাদের সম্পর্কিত রানটাইম Tensor মানগুলি ট্র্যাক করে, একটি প্রতীক টেবিল ম্যাপ ব্যবহার করে, args এ প্রদত্ত, M।
  2. SSACFG ক্রম অনুসারে func মধ্যে প্রতিটি অপশনের জন্য:
    • অপে eval আহ্বান করে। অপের প্রতিটি এসএসএ অপারেন্ডের জন্য, eval ইনভোকেশনের আর্গুমেন্ট হিসেবে M থেকে এর রানটাইম মান বের করুন।
    • অপের SSA ফলাফল(গুলি) এবং M-তে মূল্যায়ন করা মান ট্র্যাক করে।

(2) তে উল্লিখিত অপ-লেভেল eval অপের এক্সিকিউশন সিমেন্টিকস বাস্তবায়নের জন্য দায়ী। নিম্নলিখিত stablehlo::AddOp জন্য একটি উদাহরণ। উদাহরণে, lhs এবং rhs টেনসরের পৃথক উপাদানগুলিকে 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);
}

এই মুহুর্তে, আমরা দোভাষীকে ধ্রুবক ভাঁজ করার জন্য সক্রিয়ভাবে কাজ করছি না কারণ আমরা StableHLO-এর জন্য ফোল্ডার বাস্তবায়নের পরিকল্পনা করছি না। যাইহোক, ভবিষ্যতে, আমরা MHLO-তে ধ্রুবক ভাঁজ করার জন্য দোভাষীর সুবিধা নেওয়ার পরিকল্পনা করছি, এই সময়ে আমরা উপরের কোড স্নিপেটের এর্গোনমিক্সকে উন্নত করব (যেমন আমাদের একটি সহায়ক ফাংশন থাকতে পারে যা Tensor অবজেক্টে ধ্রুবক অপারেন্ড প্যাক করে এবং আনপ্যাক করে। Tensor ফলাফল OpFoldResult )।

StableHLO দোভাষী পরীক্ষা করা হচ্ছে

ইন্টারপ্রেটার ইনপুট হিসাবে নেয় (A) একটি StableHLO প্রোগ্রাম, এবং (B) ডেটা মানগুলি প্রোগ্রামে খাওয়ানোর জন্য, এবং আউটপুট ডেটা মান তৈরি করে, যা ব্যবহারকারী-প্রদত্ত প্রত্যাশিত ডেটা মানগুলির সাথে মিলে যায়। stablehlo.constant অপারেশন ব্যবহার করে প্রোগ্রামেই ডাটা মান (B) হার্ড-কোড করা হয়। দোভাষী ইনপুট প্রোগ্রাম মূল্যায়ন করে। পরীক্ষার অধীনে অপের আউটপুট (গুলি) চেকের মাধ্যমে পরীক্ষা করা হয় (যেমন check.expect_eq , check.expect_almost_eq ), নীচে দেখানো হিসাবে। check.expect_eq এবং check.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 ( কোড ) প্রোগ্রামটি পার্স করার জন্য দায়ী, ফাংশন গঠনকারী ক্রিয়াকলাপ সহ প্রতিটি ফাংশন ব্যাখ্যা করে। আমাদের একটি ডেডিকেটেড টেস্ট-স্যুট রয়েছে, প্রতিটি StableHLO Op-এর জন্য বিভিন্ন রানটাইম আচরণের ব্যায়াম করার জন্য বেশ কয়েকটি পরীক্ষা নিয়ে গঠিত। পরীক্ষা এখানে পাওয়া যাবে.

পরীক্ষার নির্দেশিকা

(G1) আমাদের কি প্রতিটি অপশনের জন্য সমস্ত সমর্থিত প্রকারের জন্য পরীক্ষা করতে হবে?

আমরা সিদ্ধান্ত নিতে নিম্নলিখিত নিয়মগুলির সংমিশ্রণ ব্যবহার করতে পারি:

  1. একটি অপ প্রয়োগ করার সময়, যদি একটি নির্দিষ্ট প্রকারকে পরিচালনা করার জন্য সংশ্লিষ্ট eval ফাংশনে কোড থাকে, তাহলে সেই প্রকারটি কভার করার জন্য পরীক্ষা(গুলি) থাকা অপরিহার্য। একটি উদাহরণ হিসাবে, add অপের জন্য, পূর্ণসংখ্যা, বুলিয়ান, ফ্লোটিং-পয়েন্ট এবং জটিল প্রকারগুলি পরিচালনা করার জন্য একচেটিয়া কোড রয়েছে এবং তাই আমাদের প্রতিটি ধরণের ধরণের জন্য একটি পরীক্ষা প্রয়োজন।

  2. যদি অনুরূপ eval ফাংশনে এক ধরনের সেট একইভাবে পরিচালনা করা হয়, তাহলে এই সমস্ত ধরণের জন্য একটি একক পরীক্ষা যথেষ্ট হওয়া উচিত। একটি উদাহরণ হিসাবে, add অপের জন্য, পূর্ণসংখ্যার প্রকারের সমস্ত রূপ ( si4 , u4 , si8 , u8 এবং তাই) llvm::APInt API ব্যবহার করে একইভাবে পরিচালনা করা হয়, এবং তাই আমরা সেই ভেরিয়েন্টগুলির প্রতিটির জন্য পরীক্ষা যোগ করা এড়িয়ে যেতে পারি, এবং পরিবর্তে একটি একক প্রতিনিধি পরীক্ষা যোগ করুন। প্রতিনিধি নির্বাচনের ক্ষেত্রে অস্পষ্টতা এড়াতে, আমাদের নিম্নলিখিত নির্দেশিকাগুলি ব্যবহার করা উচিত:

    • যদি সমস্ত প্রকার, একইভাবে পরিচালনা করা হয়, একই আদিম প্রকার থাকে (অর্থাৎ, যদি সবগুলি পূর্ণসংখ্যা, বা ভাসমান-বিন্দু, বা জটিল প্রকার হয়), তাহলে সর্বাধিক বিট-প্রস্থ সহ একটি বেছে নিন।
    • যদি সমস্ত প্রকার, একইভাবে পরিচালনা করা হয়, আদিম প্রকারের মিশ্রণ থাকে, তাহলে পছন্দের ক্রমানুসারে নিম্নোক্ত আদিম প্রকারের একটি বেছে নিন: পূর্ণসংখ্যা, ভাসমান-বিন্দু, বুলিয়ান, জটিল।

(G2) একজন অপের আচরণ কভার করার জন্য প্রয়োজনীয় পরীক্ষার সংখ্যার বিষয়ে আমরা কীভাবে সিদ্ধান্ত নেব?

লক্ষ্য হল ন্যূনতম সংখ্যক পরীক্ষার সাথে op (অর্থাৎ বাস্তবায়নের সমস্ত কোণার ক্ষেত্রে) জন্য দোভাষীর যুক্তিকে ব্যাপকভাবে কভার করা। রক্ষণাবেক্ষণের জন্য পরীক্ষার সংখ্যা কম করা গুরুত্বপূর্ণ। আমাদের যত কম পরীক্ষা আছে, সেগুলি পর্যালোচনা করা এবং সেগুলি ব্যাপকভাবে অপশনটিকে কভার করে তা নিশ্চিত করা তত সহজ। ফলস্বরূপ, আমরা আশা করি যে বেশিরভাগ সহজ অপারেশনের শেষ হবে মাত্র একটি পরীক্ষা। যদি কোনো সঙ্গত কারণে ব্যাপক কভারেজ অব্যবহারিক হয়, তাহলে >= 90% এ থামানো ভালো। পুল অনুরোধ পর্যালোচনার সময় এটি কেস-বাই-কেস ভিত্তিতে সিদ্ধান্ত নেওয়া হবে।

(G3) দোভাষী পরিকাঠামোর জন্য পরীক্ষা যোগ করার বিষয়ে কীভাবে?

দোভাষী পরিকাঠামো বেশিরভাগই সহজবোধ্য এবং আমাদের বিশ্বাসের ভিত্তিতে যোগ করা যেতে পারে। একমাত্র অ-তুচ্ছ অংশ হল কিভাবে বিভিন্ন প্রকারকে প্যাক করা হয় এবং অন্তর্নিহিত ইন্টারপ্রেটার স্টোরেজ থেকে আনপ্যাক করা হয়। (G1) যেমন আলোচনা করা হয়েছে, আমরা কেবলমাত্র সেই ধরনের অপ পরীক্ষা করব যা ভিন্নভাবে পরিচালনা করা হয়। এর সাথে এটা সম্ভব যে প্যাকিং/আন-প্যাকিং কোড, পূর্ণসংখ্যা/ফ্লোটিং-পয়েন্ট প্রকারের বিভিন্ন রূপের সাথে সম্পর্কিত, পরীক্ষার সময় সম্পূর্ণরূপে কভার নাও হতে পারে। সম্পূর্ণ কভারেজ নিশ্চিত করার জন্য, আমরা constant মতো একটি অপশন বেছে নিতে পারি যা সমস্ত StableHLO উপাদান প্রকারকে সমর্থন করে এবং সম্পূর্ণ পরীক্ষা লিখতে পারে।

(G4) যদি একটি অপের বাস্তবায়ন অন্যান্য অপ্সের উপর নির্ভর করে, তাহলে আমাদের কি পরবর্তীটির জন্য পরীক্ষা লিখতে হবে?

না। উদাহরণস্বরূপ, batch_norm_grad এর বাস্তবায়ন divide , subtract , multiply এবং অন্যান্যের উপর ভিত্তি করে করা যেতে পারে। পূর্বের পরীক্ষা করার সময় আমাদের পরবর্তী অপারেশনগুলি পরীক্ষা করা এড়াতে হবে।

(G5) বাস্তবায়ন-সংজ্ঞায়িত/অনির্ধারিত আচরণ অনুশীলন করার জন্য আমাদের কি পরীক্ষা লিখতে হবে?

আমাদের পরীক্ষা লেখা উচিত নয় যা অপের বাস্তবায়ন-সংজ্ঞায়িত বা অনির্ধারিত আচরণ অনুশীলন করে। বাস্তবায়ন-সংজ্ঞায়িত আচরণ অনুশীলনকারী পরীক্ষাগুলি দোভাষীর স্থানীয় আচরণ প্রদর্শন করে যা সাধারণীকরণ করা উচিত নয়। অনির্ধারিত আচরণ অনুশীলনকারী পরীক্ষাগুলি অপের আচরণ বোঝার দিকে অবদান রাখে না।

(G6) ফ্লোটিং-পয়েন্ট প্রকারের জন্য পরীক্ষা লেখার সময়, প্রত্যাশিত ফলাফলটি চেকগুলিতে নির্দিষ্ট করা প্রয়োজন কী?

প্রাথমিক ক্রিয়াকলাপগুলির জন্য (যোগ, বিয়োগ, গুণ, ভাগ এবং বর্গ), IEEE স্পেসিফিকেশন অনুসরণ করে একটি বাস্তবায়ন গাণিতিকভাবে সঠিক ফলাফলের 0.5 ULP-এর মধ্যে একটি বৃত্তাকার ফলাফল প্রদান করবে বলে আশা করা হচ্ছে। এটি বলেছে, আমরা নিরাপদে কল্পনা করতে পারি যে এই অপারেশনগুলি থেকে প্রত্যাশিত ফলাফলটি সর্বাধিক 1 ইউএলপি আলাদা হতে পারে। যাইহোক, এটি ট্রান্সসেন্ডেন্টাল ফাংশনগুলির ( 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 ops-এর সংখ্যাগত নির্ভুলতা পরীক্ষা করার প্রথম ধাপ। এই মুহুর্তে, এটি StableHLO স্পেকের একটি নিম্ন-নির্দিষ্ট এলাকা, এবং অনুশীলনে StableHLO ব্যবহার করার অভিজ্ঞতা এবং স্টেকহোল্ডারদের প্রতিক্রিয়ার ভিত্তিতে #1156 বের করার জন্য কাজ চলছে। এই কাজটি এগিয়ে যাওয়ার সাথে সাথে, আমরা সেই অনুযায়ী পরিকাঠামো আপডেট করব।

(G7) পরীক্ষার কোডিং-শৈলী সম্পর্কে কিছু?

  1. এসএসএ মানগুলিতে ডিফল্ট না করে ইনপুট/আউটপুটগুলির প্রকৃত নাম ব্যবহার করা নিশ্চিত করুন (যেমন %0, %1, ইত্যাদি)
  2. নিশ্চিত করুন যে পরীক্ষাগুলি সুন্দর-মুদ্রিত বিন্যাস ব্যবহার করে, যদি এটি বিদ্যমান থাকে।

(G8) আমরা কি ইতিমধ্যে স্পেস দেওয়া উদাহরণ অন্তর্ভুক্ত করা উচিত? হ্যাঁ (পরীক্ষার সম্পূর্ণতার জন্য)।