ওভারভিউ
এই পৃষ্ঠাটি টেনসরফ্লোতে কম্পোজিট অপারেশনগুলিকে LiteRT-এ ফিউজড অপারেশনগুলিতে রূপান্তর করার জন্য প্রয়োজনীয় নকশা এবং পদক্ষেপগুলি বর্ণনা করে৷ এই অবকাঠামোটি সাধারণ উদ্দেশ্য এবং TensorFlow-এর যেকোনও কম্পোজিট অপারেশনকে LiteRT-এ সংশ্লিষ্ট ফিউজড অপারেশনে রূপান্তর করতে সহায়তা করে।
এই পরিকাঠামোর একটি উদাহরণ হল TensorFlow RNN অপারেশন ফিউশন to LiteRT, এখানে বিস্তারিতভাবে বলা হয়েছে।
ফিউজড অপারেশন কি
TensorFlow অপারেশনগুলি হয় আদিম অপস হতে পারে যেমন tf.add অথবা সেগুলি অন্যান্য আদিম অপারেশন যেমন tf.einsum থেকে তৈরি করা যেতে পারে। একটি আদিম অপারেশন টেনসরফ্লো গ্রাফে একটি একক নোড হিসাবে প্রদর্শিত হয় যখন একটি যৌগিক অপারেশন হল টেনসরফ্লো গ্রাফে নোডগুলির একটি সংগ্রহ। একটি যৌগিক ক্রিয়াকলাপ নির্বাহ করা তার প্রতিটি উপাদানের আদিম ক্রিয়াকলাপ সম্পাদন করার সমতুল্য।
একটি ফিউজড অপারেশন একটি একক অপারেশনের সাথে মিলে যায় যা সংশ্লিষ্ট যৌগিক ক্রিয়াকলাপের মধ্যে প্রতিটি আদিম অপারেশন দ্বারা সম্পাদিত সমস্ত গণনা সাবসুম করে।
ফিউজড অপারেশনের সুবিধা
সামগ্রিক গণনা অপ্টিমাইজ করে এবং মেমরি ফুটপ্রিন্ট হ্রাস করে তাদের অন্তর্নিহিত কার্নেল বাস্তবায়নের কর্মক্ষমতা সর্বাধিক করার জন্য ফিউজড অপারেশন বিদ্যমান। এটি খুবই মূল্যবান, বিশেষ করে কম লেটেন্সি ইনফারেন্স ওয়ার্কলোড এবং রিসোর্স সীমাবদ্ধ মোবাইল প্ল্যাটফর্মের জন্য।
মিশ্রিত ক্রিয়াকলাপগুলি কোয়ান্টাইজেশনের মতো জটিল রূপান্তরগুলিকে সংজ্ঞায়িত করার জন্য একটি উচ্চ স্তরের ইন্টারফেসও সরবরাহ করে, যা অন্যথায় আরও দানাদার স্তরে করা অসম্ভব বা খুব কঠিন হবে।
উপরে উল্লেখিত কারণে LiteRT-তে ফিউজড অপারেশনের অনেক উদাহরণ রয়েছে। এই ফিউজড অপারেশনগুলি সাধারণত সোর্স টেনসরফ্লো প্রোগ্রামের কম্পোজিট অপারেশনের সাথে মিলে যায়। TensorFlow-এর কম্পোজিট অপারেশনগুলির উদাহরণ যা LiteRT-তে একক ফিউজড অপারেশন হিসাবে প্রয়োগ করা হয় তার মধ্যে রয়েছে বিভিন্ন RNN অপারেশন যেমন Unidirectional এবং Bidirectional sequence LSTM, convolution (conv2d, bias add, relu), সম্পূর্ণ সংযুক্ত (matmul, bias add, relu) এবং আরও অনেক কিছু। LiteRT-এ, LSTM কোয়ান্টাইজেশন বর্তমানে শুধুমাত্র ফিউজড LSTM অপারেশনগুলিতে প্রয়োগ করা হয়।
ফিউজড অপারেশনের সাথে চ্যালেঞ্জ
টেনসরফ্লো থেকে কম্পোজিট অপারেশনগুলিকে LiteRT-এ ফিউজড অপারেশনে রূপান্তর করা একটি কঠিন সমস্যা। এটি কারণ:
যৌগিক ক্রিয়াকলাপগুলিকে টেনসরফ্লো গ্রাফে একটি সুনির্দিষ্ট সীমানা ছাড়াই আদিম ক্রিয়াকলাপের একটি সেট হিসাবে উপস্থাপন করা হয়। এই ধরনের একটি যৌগিক অপারেশনের সাথে সম্পর্কিত সাব-গ্রাফটি সনাক্ত করা (যেমন প্যাটার্ন ম্যাচিংয়ের মাধ্যমে) খুব চ্যালেঞ্জিং হতে পারে।
একটি ফিউজড LiteRT অপারেশনকে লক্ষ্য করে একাধিক TensorFlow বাস্তবায়ন হতে পারে। উদাহরণ স্বরূপ, TensorFlow (Keras, Babelfish/lingvo ইত্যাদি) তে অনেক LSTM বাস্তবায়ন রয়েছে এবং এগুলির প্রত্যেকটি বিভিন্ন আদিম ক্রিয়াকলাপের সমন্বয়ে গঠিত কিন্তু সেগুলিকে এখনও LiteRT-তে একই ফিউজড LSTM অপারেশনে রূপান্তর করা যেতে পারে।
যেমন, ফিউজড অপারেশনের রূপান্তর বেশ চ্যালেঞ্জিং প্রমাণিত হয়েছে।
কম্পোজিট অপ থেকে একটি TFLite কাস্টম অপারেশনে রূপান্তর করা (প্রস্তাবিত)
একটি tf.function
মধ্যে যৌগিক অপারেশন মোড়ানো
অনেক ক্ষেত্রে, মডেলের কিছু অংশ TFLite-এ একক অপারেশনে ম্যাপ করা যেতে পারে। নির্দিষ্ট ক্রিয়াকলাপগুলির জন্য একটি অপ্টিমাইজ করা বাস্তবায়ন লেখার সময় এটি কার্য সম্পাদনে সহায়তা করতে পারে। TFLite-এ একটি ফিউজড অপারেশন তৈরি করতে সক্ষম হওয়ার জন্য, গ্রাফের অংশটি চিহ্নিত করুন যা একটি ফিউজড অপারেশনকে প্রতিনিধিত্ব করে এবং এটিকে একটি tf.function- এ "experimental_implements" অ্যাট্রিবিউট সহ একটি tf.function
এ মুড়ে দিন, যার মান true
সহ tfl_fusable_op
বৈশিষ্ট্য রয়েছে। যদি কাস্টম অপারেশন বৈশিষ্ট্যগুলি গ্রহণ করে তবে সেগুলিকে একই "পরীক্ষামূলক_ইমপ্লিমেন্ট" এর অংশ হিসাবে পাস করুন।
উদাহরণ,
def get_implements_signature():
implements_signature = [
# 'name' will be used as a name for the operation.
'name: "my_custom_fused_op"',
# attr "tfl_fusable_op" is required to be set with true value.
'attr {key: "tfl_fusable_op" value { b: true } }',
# Example attribute "example_option" that the op accepts.
'attr {key: "example_option" value { i: %d } }' % 10
]
return ' '.join(implements_signature)
@tf.function(experimental_implements=get_implements_signature())
def my_custom_fused_op(input_1, input_2):
# An empty function that represents pre/post processing example that
# is not represented as part of the Tensorflow graph.
output_1 = tf.constant(0.0, dtype=tf.float32, name='first_output')
output_2 = tf.constant(0.0, dtype=tf.float32, name='second_output')
return output_1, output_2
class TestModel(tf.Module):
def __init__(self):
super(TestModel, self).__init__()
self.conv_1 = tf.keras.layers.Conv2D(filters=1, kernel_size=(3, 3))
self.conv_2 = tf.keras.layers.Conv2D(filters=1, kernel_size=(3, 3))
@tf.function(input_signature=[
tf.TensorSpec(shape=[1, 28, 28, 3], dtype=tf.float32),
tf.TensorSpec(shape=[1, 28, 28, 3], dtype=tf.float32),
])
def simple_eval(self, input_a, input_b):
return my_custom_fused_op(self.conv_1(input_a), self.conv_2(input_b))
মনে রাখবেন যে আপনাকে কনভার্টারে allow_custom_ops
সেট করতে হবে না কারণ tfl_fusable_op
বৈশিষ্ট্যটি ইতিমধ্যেই এটিকে বোঝায়।
কাস্টম অপ প্রয়োগ করুন এবং TFLite ইন্টারপ্রেটারের সাথে নিবন্ধন করুন
একটি TFLite কাস্টম অপারেশন হিসাবে আপনার ফিউজড অপারেশন বাস্তবায়ন করুন - নির্দেশাবলী দেখুন৷
উল্লেখ্য যে, op নিবন্ধন করার নামটি ইমপ্লিমেন্ট স্বাক্ষরে name
বৈশিষ্ট্যে উল্লেখিত নামের অনুরূপ হওয়া উচিত।
উদাহরণে অপের জন্য একটি উদাহরণ হল
TfLiteRegistration reg = {};
// This name must match the name specified in the implements signature.
static constexpr char kOpName[] = "my_custom_fused_op";
reg.custom_name = kOpName;
reg.prepare = [](TfLiteContext* context, TfLiteNode* node) -> TfLiteStatus {
// Add your code.
return kTfLiteOk;
};
reg.invoke = [](TfLiteContext* context, TfLiteNode* node) -> TfLiteStatus {
// Add your code.
return kTfLiteOk;
};
reg.builtin_code = kTfLiteCustom;
resolver->AddCustom(kOpName, ®);
কম্পোজিট থেকে ফিউজড অপারেশনে রূপান্তর করা (উন্নত)
টেনসরফ্লো কম্পোজিট অপারেশনগুলিকে LiteRT ফিউজড অপারেশনে রূপান্তর করার জন্য সামগ্রিক আর্কিটেকচার নীচে দেওয়া হল:
একটি tf.function
মধ্যে যৌগিক অপারেশন মোড়ানো
TensorFlow মডেলের সোর্স কোডে, Experimental_implements ফাংশন টীকা দিয়ে একটি tf.function- এ যৌগিক ক্রিয়াকলাপ সনাক্ত করুন এবং বিমূর্ত করুন। এম্বেডিং লুকআপের একটি উদাহরণ দেখুন। ফাংশন ইন্টারফেস সংজ্ঞায়িত করে এবং এর আর্গুমেন্টগুলি রূপান্তর যুক্তি প্রয়োগ করতে ব্যবহার করা উচিত।
রূপান্তর কোড লিখুন
রূপান্তর কোডটি implements
টীকা সহ ফাংশনের ইন্টারফেস অনুসারে লেখা হয়। এম্বেডিং লুকআপের জন্য একটি উদাহরণ ফিউশন দেখুন। ধারণাগতভাবে, রূপান্তর কোড এই ইন্টারফেসের যৌগিক বাস্তবায়নকে ফিউজডের সাথে প্রতিস্থাপন করে।
প্রস্তুতি-কম্পোজিট-ফাংশন পাসে, আপনার রূপান্তর কোডে প্লাগইন করুন।
আরও উন্নত ব্যবহারে, ফিউজড অপারেশনের অপারেন্ডগুলি বের করার জন্য যৌগিক অপারেশনের অপারেন্ডগুলির জটিল রূপান্তরগুলি বাস্তবায়ন করা সম্ভব। কেরাস এলএসটিএম দেখুন। একটি উদাহরণ হিসাবে রূপান্তর কোড।
LiteRT তে রূপান্তর করুন
LiteRT এ রূপান্তর করতে TFLiteConverter.from_saved_model API ব্যবহার করুন।
ফণা অধীনে
আমরা এখন LiteRT এ ফিউজড অপারেশনে রূপান্তর করার জন্য সামগ্রিক ডিজাইনের উচ্চ স্তরের বিবরণ বর্ণনা করি।
TensorFlow-এ কম্পোজিং অপারেশন
Experimental_implements ফাংশন অ্যাট্রিবিউটের সাথে tf.function- এর ব্যবহার ব্যবহারকারীদের TensorFlow আদিম ক্রিয়াকলাপগুলি ব্যবহার করে স্পষ্টভাবে নতুন ক্রিয়াকলাপ রচনা করতে এবং এর ফলে কম্পোজিট অপারেশন প্রয়োগ করা ইন্টারফেসটি নির্দিষ্ট করতে দেয়। এটি খুব দরকারী কারণ এটি প্রদান করে:
- অন্তর্নিহিত TensorFlow গ্রাফে যৌগিক অপারেশনের জন্য একটি সু-সংজ্ঞায়িত সীমানা।
- এই অপারেশনটি যে ইন্টারফেসটি প্রয়োগ করে তা স্পষ্টভাবে উল্লেখ করুন। tf.function এর আর্গুমেন্ট এই ইন্টারফেসের আর্গুমেন্টের সাথে মিলে যায়।
উদাহরণ হিসেবে, এম্বেডিং লুকআপ বাস্তবায়নের জন্য সংজ্ঞায়িত একটি যৌগিক অপারেশন বিবেচনা করা যাক। এটি LiteRT-এ একটি ফিউজড অপারেশনের মানচিত্র।
@tf.function(
experimental_implements="embedding_lookup")
def EmbFprop(embs, ids_vec):
"""Embedding forward prop.
Effectively, it computes:
num = size of ids_vec
rets = zeros([num, embedding dim])
for i in range(num):
rets[i, :] = embs[ids_vec[i], :]
return rets
Args:
embs: The embedding matrix.
ids_vec: A vector of int32 embedding ids.
Returns:
The result of embedding lookups. A matrix of shape
[num ids in ids_vec, embedding dims].
"""
num = tf.shape(ids_vec)[0]
rets = inplace_ops.empty([num] + emb_shape_suf, py_utils.FPropDtype(p))
def EmbFpropLoop(i, embs, ids_vec, rets):
# row_id = ids_vec[i]
row_id = tf.gather(ids_vec, i)
# row = embs[row_id]
row = tf.reshape(tf.gather(embs, row_id), [1] + emb_shape_suf)
# rets[i] = row
rets = inplace_ops.alias_inplace_update(rets, [i], row)
return embs, ids_vec, rets
_, _, rets = functional_ops.For(
start=0,
limit=num,
delta=1,
inputs=[embs, ids_vec, rets],
body=EmbFpropLoop,
rewrite_with_while=compiled)
if len(weight_shape) > 2:
rets = tf.reshape(rets, [num, symbolic.ToStatic(p.embedding_dim)])
return rets
মডেলগুলিকে tf.function- এর মাধ্যমে যৌগিক ক্রিয়াকলাপগুলিকে উপরে চিত্রিত করার মাধ্যমে, এই ধরনের ক্রিয়াকলাপগুলিকে ফিউজড LiteRT অপারেশনে সনাক্ত এবং রূপান্তর করার জন্য একটি সাধারণ অবকাঠামো তৈরি করা সম্ভব হয়।
LiteRT কনভার্টার প্রসারিত করা হচ্ছে
এই বছরের শুরুতে প্রকাশিত LiteRT কনভার্টারটি শুধুমাত্র টেনসরফ্লো মডেলগুলিকে তাদের সংশ্লিষ্ট ধ্রুবক মানগুলির সাথে প্রতিস্থাপিত সমস্ত ভেরিয়েবল সহ একটি গ্রাফ হিসাবে আমদানি করা সমর্থন করে৷ এটি অপারেশন ফিউশনের জন্য কাজ করে না কারণ এই ধরনের গ্রাফগুলিতে সমস্ত ফাংশন ইনলাইন থাকে যাতে ভেরিয়েবলগুলিকে ধ্রুবকগুলিতে পরিণত করা যায়।
রূপান্তর প্রক্রিয়া চলাকালীন experimental_implements
বৈশিষ্ট্যের সাথে tf.function-কে লিভারেজ করার জন্য, ফাংশনগুলিকে রূপান্তর প্রক্রিয়ার পরে সংরক্ষণ করতে হবে।
যেমন, আমরা কম্পোজিট অপারেশন ফিউশন ব্যবহারের ক্ষেত্রে সমর্থন করার জন্য কনভার্টারে টেনসরফ্লো মডেল আমদানি এবং রূপান্তর করার একটি নতুন কর্মপ্রবাহ বাস্তবায়ন করেছি। বিশেষত, যোগ করা নতুন বৈশিষ্ট্য হল:
- MLIR-এ TensorFlow সংরক্ষিত মডেল আমদানি করা হচ্ছে
- ফিউজ যৌগিক অপারেশন
- পরিবর্তনশীল পরিবর্তনশীলতা বিশ্লেষণ
- সমস্ত পঠনযোগ্য ভেরিয়েবল ফ্রিজ করুন
এটি আমাদের ফাংশন ইনলাইনিং এবং ভেরিয়েবল ফ্রিজিংয়ের পূর্বে যৌগিক ক্রিয়াকলাপের প্রতিনিধিত্বকারী ফাংশনগুলি ব্যবহার করে অপারেশন ফিউশন সম্পাদন করতে দেয়।
অপারেশন ফিউশন বাস্তবায়ন
এর আরো বিস্তারিতভাবে অপারেশন ফিউশন পাস তাকান. এই পাস নিম্নলিখিত কাজ করে:
- MLIR মডিউলের সমস্ত ফাংশন লুপ করুন।
- যদি একটি ফাংশনে tf._implements অ্যাট্রিবিউট থাকে, অ্যাট্রিবিউট মানের উপর ভিত্তি করে, উপযুক্ত অপারেশন ফিউশন ইউটিলিটি কল করে।
- অপারেশন ফিউশন ইউটিলিটি ফাংশনের অপারেন্ড এবং বৈশিষ্ট্যের উপর কাজ করে (যা রূপান্তরের জন্য ইন্টারফেস হিসাবে কাজ করে) এবং ফাংশনের বডিকে ফিউজড অপারেশন ধারণকারী একটি সমতুল্য ফাংশন বডি দিয়ে প্রতিস্থাপন করে।
- অনেক ক্ষেত্রে, প্রতিস্থাপিত শরীরে ফিউজড অপারেশন ছাড়া অন্য অপারেশন থাকবে। এগুলি ফিউজড অপারেশনের অপারেন্ডগুলি পাওয়ার জন্য ফাংশনের অপারেন্ডে কিছু স্ট্যাটিক রূপান্তরের সাথে মিলে যায়। যেহেতু এই কম্পিউটেশনগুলি সবগুলি অবিচ্ছিন্নভাবে ভাঁজ করা যেতে পারে, সেগুলি রপ্তানি করা ফ্ল্যাটবাফারে উপস্থিত থাকবে না যেখানে শুধুমাত্র ফিউজড অপারেশন থাকবে৷
এখানে পাস থেকে কোড স্নিপেট প্রধান কর্মপ্রবাহ দেখাচ্ছে:
void PrepareCompositeFunctionsPass::ConvertTFImplements(FuncOp func,
StringAttr attr) {
if (attr.getValue() == "embedding_lookup") {
func.eraseBody();
func.addEntryBlock();
// Convert the composite embedding_lookup function body to a
// TFLite fused embedding_lookup op.
ConvertEmbeddedLookupFunc convert_embedded_lookup(func);
if (failed(convert_embedded_lookup.VerifySignature())) {
return signalPassFailure();
}
convert_embedded_lookup.RewriteFunc();
} else if (attr.getValue() == mlir::TFL::kKerasLstm) {
func.eraseBody();
func.addEntryBlock();
OpBuilder builder(func.getBody());
if (failed(ConvertKerasLSTMLayer(func, &builder))) {
return signalPassFailure();
}
} else if (.....) /* Other fusions can plug in here */
}
এখানে কোড স্নিপেট এই যৌগিক ক্রিয়াকলাপটিকে একটি রূপান্তর ইন্টারফেস হিসাবে ফাংশনটিকে লিভারেজ করে LiteRT-তে একটি ফিউজড অপারেশনে ম্যাপিং দেখাচ্ছে।
void RewriteFunc() {
Value lookup = func_.getArgument(1);
Value value = func_.getArgument(0);
auto output_type = func_.getType().getResult(0);
OpBuilder builder(func_.getBody());
auto op = builder.create<mlir::TFL::EmbeddingLookupOp>(
func_.getLoc(), output_type, lookup, value);
builder.create<mlir::ReturnOp>(func_.getLoc(), op.getResult());
}