blob: a5c09a00a722224932ad61ffb994ddf99d5d1ece [file] [log] [blame]
# Copyright (c) Meta Platforms, Inc. and affiliates.
# All rights reserved.
#
# This source code is licensed under the BSD-style license found in the
# LICENSE file in the root directory of this source tree.
# pyre-strict
import random
from typing import Any, Dict, List, Tuple, Union
import executorch.exir as exir
import torch
from executorch.bundled_program.config import BundledConfig
from executorch.exir import CaptureConfig
from executorch.exir.schema import Program
# @manual=fbsource//third-party/pypi/typing-extensions:typing-extensions
from typing_extensions import TypeAlias
# A hacky integer to deal with a mismatch between execution plan and complier.
#
# Execution plan supports multiple types of inputs, like Tensor, Int, etc,
# rather than only Tensor. However, compiler only supports Tensor as input type.
# All other inputs will remain the same as default value in the model, which
# means during model execution, each function will use the preset default value
# for non-tensor inputs, rather than the one we manually set. However, eager
# model supports multiple types of inputs.
#
# In order to show that bundled program can support multiple input types while
# executorch model can generate the same output as eager model, we hackily set
# all Int inputs in Bundled Program and default int inputs in model as a same
# value, called DEFAULT_INT_INPUT.
#
# TODO(gasoonjia): track the situation. Stop supporting multiple input types in
# bundled program if execution plan stops supporting it, or remove this hacky
# method if compiler can support multiple input types
DEFAULT_INT_INPUT = 2
# Alias type for all datas model needs for single execution.
InputValues: TypeAlias = List[Union[torch.Tensor, int]]
# Alias type for all datas model generates per execution.
OutputValues: TypeAlias = List[torch.Tensor]
class MISOModel(torch.nn.Module):
"""An example model with Multi-Input Single-Output"""
def __init__(self) -> None:
super().__init__()
self.a: torch.Tensor = 3 * torch.ones(2, 2, dtype=torch.int32)
self.b: torch.Tensor = 2 * torch.ones(2, 2, dtype=torch.int32)
def forward(
self, x: torch.Tensor, q: torch.Tensor, a: int = DEFAULT_INT_INPUT
) -> torch.Tensor:
z = x.clone()
torch.mul(self.a, x, out=z)
y = x.clone()
torch.add(z, self.b, alpha=a, out=y)
torch.add(y, q, out=y)
return y
def get_rand_input_values(
n_tensors: int,
sizes: List[List[int]],
n_int: int,
dtype: torch.dtype,
n_sets_per_plan_test: int,
n_execution_plan_tests: int,
) -> List[List[InputValues]]:
return [
[
[(torch.rand(*sizes[i]) - 0.5).to(dtype) for i in range(n_tensors)]
+ [DEFAULT_INT_INPUT for _ in range(n_int)]
for _ in range(n_sets_per_plan_test)
]
for _ in range(n_execution_plan_tests)
]
def get_rand_output_values(
n_tensors: int,
sizes: List[List[int]],
dtype: torch.dtype,
n_sets_per_plan_test: int,
n_execution_plan_tests: int,
) -> List[List[OutputValues]]:
return [
[
[(torch.rand(*sizes[i]) - 0.5).to(dtype) for i in range(n_tensors)]
for _ in range(n_sets_per_plan_test)
]
for _ in range(n_execution_plan_tests)
]
def get_rand_attachement(n_kv_pairs: int = -1) -> Dict[str, Any]:
"""Helper function to generate random bundled attachments."""
if n_kv_pairs == -1:
n_kv_pairs = random.randint(0, 10)
# This list cotains differnet possible values for generated random attachment.
# They are in differnet types on purpose, which demonstrate bundle program
# supports multiple types of attachment values. It is worth noting that it is
# just a hacky method to generate random bundled attachments. It does not mean
# bundled program can only support listed values.
rand_attachment_vals = [
b"BUNDLED_VALUE_IN_BYTES", # random bytes array
"BUNDLED_VALUE_IN_STR", # random strinĂ¥g
1.2345, # random float
True, # random bool
1, # random int
]
return {
"BUNDLED_ATTACHMENT_KEY_{}".format(i): random.choice(rand_attachment_vals)
for i in range(n_kv_pairs)
}
# TODO(T143955558): make n_int and metadatas as its input;
def get_random_config(
n_model_inputs: int,
model_input_sizes: List[List[int]],
n_model_outputs: int,
model_output_sizes: List[List[int]],
dtype: torch.dtype,
n_sets_per_plan_test: int,
n_execution_plan_tests: int,
) -> Tuple[
List[List[InputValues]],
List[List[OutputValues]],
List[Dict[str, Any]],
Dict[str, Any],
BundledConfig,
]:
"""Helper function to generate config filled with random inputs and expected outputs.
The return type of rand inputs is a List[List[InputValues]]. The inner list of
InputValues represents all test sets for single execution plan, while the outer list
is for multiple execution plans.
Same for rand_expected_outputs.
"""
rand_inputs = get_rand_input_values(
n_tensors=n_model_inputs,
sizes=model_input_sizes,
n_int=1,
dtype=dtype,
n_sets_per_plan_test=n_sets_per_plan_test,
n_execution_plan_tests=n_execution_plan_tests,
)
rand_expected_outputs = get_rand_output_values(
n_tensors=n_model_outputs,
sizes=model_output_sizes,
dtype=dtype,
n_sets_per_plan_test=n_sets_per_plan_test,
n_execution_plan_tests=n_execution_plan_tests,
)
rand_metadatas = [get_rand_attachement() for _ in range(n_execution_plan_tests)]
rand_attachment = get_rand_attachement()
return (
rand_inputs,
rand_expected_outputs,
rand_metadatas,
rand_attachment,
BundledConfig(
rand_inputs, rand_expected_outputs, rand_metadatas, **rand_attachment
),
)
def get_random_config_with_eager_model(
eager_model: torch.nn.Module,
n_model_inputs: int,
model_input_sizes: List[List[int]],
dtype: torch.dtype,
n_sets_per_plan_test: int,
n_execution_plan_tests: int,
) -> Tuple[List[List[InputValues]], BundledConfig]:
"""Generate config filled with random inputs for each inference method given eager model
The details of return type is the same as get_random_config_with_rand_io_lists.
NOTE: Right now we do not support multiple inference methods per eager model. To simulate
generating exepected output for different inference functions, we infer the same method
multiple times.
TODO(T143752810): Update the hacky method after we support multiple inference methods.
"""
inputs = get_rand_input_values(
n_tensors=n_model_inputs,
sizes=model_input_sizes,
n_int=1,
dtype=dtype,
n_sets_per_plan_test=n_sets_per_plan_test,
n_execution_plan_tests=n_execution_plan_tests,
)
expected_outputs = [
[[eager_model(*x)] for x in inputs[i]] for i in range(n_execution_plan_tests)
]
metadatas = [get_rand_attachement() for _ in range(n_execution_plan_tests)]
attachment = get_rand_attachement()
return inputs, BundledConfig(inputs, expected_outputs, metadatas, **attachment)
def get_common_program() -> Tuple[Program, BundledConfig]:
"""Helper function to generate a sample BundledProgram with its config."""
eager_model = MISOModel()
# Trace to FX Graph.
capture_input = (
(torch.rand(2, 2) - 0.5).to(dtype=torch.int32),
(torch.rand(2, 2) - 0.5).to(dtype=torch.int32),
DEFAULT_INT_INPUT,
)
program = (
exir.capture(eager_model, capture_input, CaptureConfig())
.to_edge()
.to_executorch()
.program
)
_, bundled_config = get_random_config_with_eager_model(
eager_model=eager_model,
n_model_inputs=2,
model_input_sizes=[[2, 2], [2, 2]],
dtype=torch.int32,
n_sets_per_plan_test=10,
n_execution_plan_tests=len(program.execution_plan),
)
return program, bundled_config