-
Notifications
You must be signed in to change notification settings - Fork 6
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Refactor Model Zoo #166
Refactor Model Zoo #166
Changes from 19 commits
f3ebd0e
1a1308c
b3d60dd
e7913fd
11279a9
4fdd527
bbe3a81
0632b40
789a7ba
dd9053b
2841633
f2ba5ad
055168b
9ef6644
1339aac
8ed435a
105e280
bcd8f03
b850c47
dc0aecc
cad487e
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
# This pip constraints file is for the reproducibility of model accuracies | ||
opencv-python-headless==4.8.0.76 | ||
torch==2.0.1 | ||
torchvision==0.15.2 | ||
numpy==1.25.2 |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,10 +1,10 @@ | ||
from furiosa.models.vision import EfficientNetB0 | ||
from furiosa.runtime import session | ||
from furiosa.runtime.sync import create_runner | ||
|
||
image = "tests/assets/cat.jpg" | ||
|
||
effnetb0 = EfficientNetB0.load() | ||
with session.create(effnetb0.enf) as sess: | ||
effnetb0 = EfficientNetB0() | ||
with create_runner(effnetb0.model_source()) as runner: | ||
inputs, _ = effnetb0.preprocess(image) | ||
outputs = sess.run(inputs).numpy() | ||
outputs = runner.run(inputs) | ||
effnetb0.postprocess(outputs) |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,10 +1,10 @@ | ||
from furiosa.models.vision import EfficientNetV2s | ||
from furiosa.runtime import session | ||
from furiosa.runtime.sync import create_runner | ||
|
||
image = "tests/assets/cat.jpg" | ||
|
||
effnetv2s = EfficientNetV2s.load() | ||
with session.create(effnetv2s.enf) as sess: | ||
effnetv2s = EfficientNetV2s() | ||
with create_runner(effnetv2s.model_source()) as runner: | ||
inputs, _ = effnetv2s.preprocess(image) | ||
outputs = sess.run(inputs).numpy() | ||
outputs = runner.run(inputs) | ||
effnetv2s.postprocess(outputs) |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,4 +1,4 @@ | ||
from furiosa.models.types import Model | ||
from furiosa.models.vision import ResNet50 | ||
|
||
model: Model = ResNet50.load() | ||
model: Model = ResNet50() |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,10 +1,10 @@ | ||
from furiosa.models.vision import ResNet50 | ||
from furiosa.runtime import session | ||
from furiosa.runtime.sync import create_runner | ||
|
||
image = "tests/assets/cat.jpg" | ||
|
||
resnet50 = ResNet50.load() | ||
with session.create(resnet50.enf) as sess: | ||
resnet50 = ResNet50() | ||
with create_runner(resnet50.model_source()) as runner: | ||
inputs, _ = resnet50.preprocess(image) | ||
outputs = sess.run(inputs).numpy() | ||
outputs = runner.run(inputs) | ||
resnet50.postprocess(outputs) |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,10 +1,10 @@ | ||
from furiosa.models.vision import SSDMobileNet | ||
from furiosa.runtime import session | ||
from furiosa.runtime.sync import create_runner | ||
|
||
image = ["tests/assets/cat.jpg"] | ||
|
||
mobilenet = SSDMobileNet.load() | ||
with session.create(mobilenet.enf) as sess: | ||
mobilenet = SSDMobileNet() | ||
with create_runner(mobilenet.model_source()) as create_runner: | ||
inputs, contexts = mobilenet.preprocess(image) | ||
outputs = sess.run(inputs).numpy() | ||
outputs = create_runner.run(inputs) | ||
mobilenet.postprocess(outputs, contexts[0]) |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,10 +1,10 @@ | ||
from furiosa.models.vision import SSDMobileNet | ||
from furiosa.runtime import session | ||
from furiosa.runtime.sync import create_runner | ||
|
||
image = ["tests/assets/cat.jpg"] | ||
|
||
mobilenet = SSDMobileNet.load(use_native=True) | ||
with session.create(mobilenet.enf) as sess: | ||
mobilenet = SSDMobileNet(postprocessor_type="Rust") | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Platform 이 strEnum 이기도 하고, 사용자가 편하게 문자열로 선택하면 좋을 것 같아서 그렇게 했습니다. 사실 strict 하게 typing 하자면 Platform만 주는 것이 맞긴 한데요, 모델 주는 사용자 편의성이 중요한 프로젝트인 것 같아서 임의로 그렇게 했습니다. 아님 혹시 더 좋은 방법이 있을까요? |
||
with create_runner(mobilenet.model_source()) as runner: | ||
inputs, contexts = mobilenet.preprocess(image) | ||
outputs = sess.run(inputs).numpy() | ||
outputs = runner.run(inputs) | ||
mobilenet.postprocess(outputs, contexts[0]) |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,22 +1,20 @@ | ||
import yaml | ||
|
||
from furiosa.models.vision import SSDMobileNet | ||
from furiosa.quantizer import quantize | ||
from furiosa.runtime import session | ||
from furiosa.runtime.sync import create_runner | ||
|
||
compiler_config = {"lower_tabulated_dequantize": True} | ||
|
||
image = ["tests/assets/cat.jpg"] | ||
|
||
mobilenet = SSDMobileNet.load() | ||
onnx_model: bytes = mobilenet.source | ||
calib_range: dict = yaml.full_load(mobilenet.calib_yaml) | ||
mobilenet = SSDMobileNet() | ||
onnx_model: bytes = mobilenet.origin | ||
calib_range: dict = mobilenet.tensor_name_to_range | ||
|
||
# See https://furiosa-ai.github.io/docs/latest/en/api/python/furiosa.quantizer.html#furiosa.quantizer.quantize | ||
# for more details | ||
dfg = quantize(onnx_model, calib_range, with_quantize=False) | ||
quantized_onnx = quantize(onnx_model, calib_range) | ||
|
||
with session.create(dfg, compiler_config=compiler_config) as sess: | ||
inputs, contexts = mobilenet.preprocess(image) | ||
outputs = sess.run(inputs).numpy() | ||
with create_runner(quantized_onnx, compiler_config=compiler_config) as runner: | ||
inputs, contexts = mobilenet.preprocess(image, skip_quantize=False) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 이 커멘트는 가볍게 드리는 의견입니다. 다른 분들도 견해를 들려주시면 좋을 것 같습니다. 기존의 저는 모델의 입력 텐서 타입이 float인지 int(uint)인지가 중요하지 않을까, 하고 생각이 들었는데요, There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. cad487e 에서 with_scaling이라는 파라미터로 바꾸었고 설명을 추가했습니다. 구두로 논의 나눈 내용이라 자세히 적지는 않겠습니다. 감사합니다 There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
이 PR 이 크기 때문에 이 관련해서는 별도 PR 로 다루면 좋을 것 같기는 합니다만, 조금 설명을 듣고 싶은데요. with_scaling 이라는 이름이 여전히 모호한 느낌이 있는데요. 제가 인용한 병찬님이 말씀 처럼 |
||
outputs = runner.run(inputs) | ||
mobilenet.postprocess(outputs, contexts[0]) |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,9 +1,9 @@ | ||
from furiosa.models.vision import SSDResNet34 | ||
from furiosa.runtime import session | ||
from furiosa.runtime.sync import create_runner | ||
|
||
resnet34 = SSDResNet34.load() | ||
resnet34 = SSDResNet34(postprocessor_type="Python") | ||
|
||
with session.create(resnet34.enf) as sess: | ||
with create_runner(resnet34.model_source()) as runner: | ||
image, contexts = resnet34.preprocess(["tests/assets/cat.jpg"]) | ||
output = sess.run(image).numpy() | ||
output = runner.run(image) | ||
resnet34.postprocess(output, contexts=contexts) |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,9 +1,9 @@ | ||
from furiosa.models.vision import SSDResNet34 | ||
from furiosa.runtime import session | ||
from furiosa.runtime.sync import create_runner | ||
|
||
resnet34 = SSDResNet34.load(use_native=True) | ||
resnet34 = SSDResNet34(postprocessor_type="Rust") | ||
|
||
with session.create(resnet34.enf) as sess: | ||
with create_runner(resnet34.model_source()) as runner: | ||
image, contexts = resnet34.preprocess(["tests/assets/cat.jpg"]) | ||
output = sess.run(image).numpy() | ||
output = runner.run(image) | ||
resnet34.postprocessor(output, contexts=contexts[0]) |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,5 +1,5 @@ | ||
"""Furiosa Models""" | ||
from . import errors, vision | ||
|
||
__version__ = "0.9.0.dev0" | ||
__version__ = "0.10.0.dev0" | ||
__all__ = ["errors", "vision"] |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,161 @@ | ||
import logging | ||
import os | ||
from pathlib import Path | ||
from typing import TYPE_CHECKING, Collection, Optional, Tuple, Union | ||
|
||
if TYPE_CHECKING: | ||
from .types import Platform | ||
|
||
import requests | ||
import yaml | ||
|
||
from . import errors | ||
|
||
EXT_CALIB_YAML = "calib_range.yaml" | ||
EXT_ONNX = "onnx" | ||
DATA_DIRECTORY_BASE = Path(__file__).parent / "data" | ||
CACHE_DIRECTORY_BASE = Path( | ||
os.getenv( | ||
"FURIOSA_MODELS_CACHE_HOME", | ||
os.path.join(os.getenv("XDG_CACHE_HOME", Path.home() / ".cache"), "furiosa/models"), | ||
) | ||
) | ||
DVC_PUBLIC_HTTP_ENDPOINT = ( | ||
"https://furiosa-public-artifacts.s3-accelerate.amazonaws.com/furiosa-artifacts/" | ||
) | ||
|
||
module_logger = logging.getLogger(__name__) | ||
|
||
|
||
def get_version_info() -> Optional[str]: | ||
from furiosa.tools.compiler.api import VersionInfo | ||
|
||
version_info = VersionInfo() | ||
return f"{version_info.version}_{version_info.git_hash}" | ||
|
||
|
||
def find_dvc_cache_directory(path: Path) -> Optional[Path]: | ||
if path is None or path == path.parent: | ||
return None | ||
if (path / ".dvc").is_dir(): | ||
return path / ".dvc" / "cache" | ||
return find_dvc_cache_directory(path.parent) | ||
|
||
|
||
def parse_dvc_file(file_path: Path) -> Tuple[str, str, int]: | ||
info_dict = yaml.safe_load(open(f"{file_path}.dvc").read())["outs"][0] | ||
md5sum = info_dict["md5"] | ||
return md5sum[:2], md5sum[2:], info_dict["size"] | ||
|
||
|
||
def get_from_url(path: str, uri: Path, is_legacy_path: bool = False) -> bytes: | ||
url = f"{DVC_PUBLIC_HTTP_ENDPOINT}{path}" | ||
module_logger.debug(f"Fetching from remote: {url}") | ||
with requests.get(url) as resp: | ||
if resp.status_code != 200: | ||
if not is_legacy_path: | ||
# New dvc now stores data into /files/md5 | ||
return get_from_url(f"files/md5/{path}", uri, True) | ||
raise errors.NotFoundInDVCRemote(uri, path) | ||
data = resp.content | ||
caching_path = CACHE_DIRECTORY_BASE / get_version_info() / (uri.name) | ||
module_logger.debug(f"caching to {caching_path}") | ||
caching_path.parent.mkdir(parents=True, exist_ok=True) | ||
with open(caching_path, mode="wb") as f: | ||
f.write(data) | ||
return data | ||
|
||
|
||
class ArtifactResolver: | ||
def __init__(self, uri: Union[str, Path]): | ||
self.uri = Path(uri) | ||
# Note: DVC_REPO is to locate local DVC directory not remote git repository | ||
self.dvc_cache_path = os.environ.get("DVC_REPO", find_dvc_cache_directory(Path.cwd())) | ||
if self.dvc_cache_path is not None: | ||
self.dvc_cache_path = Path(self.dvc_cache_path) | ||
if self.dvc_cache_path.is_symlink(): | ||
self.dvc_cache_path = self.dvc_cache_path.readlink() | ||
module_logger.debug(f"Found DVC cache directory: {self.dvc_cache_path}") | ||
|
||
def _read(self, directory: str, filename: str) -> bytes: | ||
# Try to find local cached file | ||
local_cache_path = CACHE_DIRECTORY_BASE / get_version_info() / (self.uri.name) | ||
if local_cache_path.exists(): | ||
module_logger.debug(f"Local cache exists: {local_cache_path}") | ||
with open(local_cache_path, mode="rb") as f: | ||
return f.read() | ||
|
||
# Try to find real file along with DVC file (no DVC) | ||
if Path(self.uri).exists(): | ||
module_logger.debug(f"Local file exists: {self.uri}") | ||
with open(self.uri, mode="rb") as f: | ||
return f.read() | ||
|
||
module_logger.debug(f"{self.uri} not exists, resolving DVC") | ||
if self.dvc_cache_path is not None: | ||
cached: Path = self.dvc_cache_path / directory / filename | ||
if cached.exists(): | ||
module_logger.debug(f"DVC cache hit: {cached}") | ||
with open(cached, mode="rb") as f: | ||
return f.read() | ||
else: | ||
module_logger.debug(f"DVC cache directory exists, but not having {self.uri}") | ||
|
||
# Fetching from remote | ||
return get_from_url(f"{directory}/{filename}", self.uri) | ||
|
||
def read(self) -> bytes: | ||
directory, filename, size = parse_dvc_file(self.uri) | ||
data = self._read(directory, filename) | ||
assert len(data) == size | ||
return data | ||
|
||
|
||
def resolve_artifact(src_name: str, full_path: Path) -> bytes: | ||
try: | ||
return ArtifactResolver(full_path).read() | ||
except Exception as e: | ||
raise errors.ArtifactNotFound(f"{src_name}:{full_path}") from e | ||
|
||
|
||
def resolve_source(src_name: str, extension: str) -> bytes: | ||
full_path = next((DATA_DIRECTORY_BASE / src_name).glob(f'*.{extension}.dvc')) | ||
# Remove `.dvc` suffix | ||
full_path = full_path.with_suffix('') | ||
return resolve_artifact(src_name, full_path) | ||
|
||
|
||
def resolve_model_source(src_name: str, num_pe: int = 2) -> bytes: | ||
version_info = get_version_info() | ||
if version_info is None: | ||
raise errors.VersionInfoNotFound() | ||
generated_path_base = DATA_DIRECTORY_BASE / f"generated/{version_info}" | ||
if not generated_path_base.exists(): | ||
module_logger.warning("ENF does not exist. Trying to generate from source..") | ||
|
||
try: | ||
import onnx | ||
import yaml | ||
|
||
from furiosa.quantizer import ModelEditor, TensorType, get_pure_input_names, quantize | ||
except ImportError: | ||
raise errors.ExtraPackageRequired() | ||
module_logger.warning(f"Returning quantized ONNX for {src_name}") | ||
onnx_model = onnx.load_from_string(resolve_source(src_name, EXT_ONNX)) | ||
calib_range = yaml.full_load(resolve_source(src_name, EXT_CALIB_YAML)) | ||
editor = ModelEditor(onnx_model) | ||
for input_name in get_pure_input_names(onnx_model): | ||
editor.convert_input_type(input_name, TensorType.UINT8) | ||
return quantize(onnx_model, calib_range) | ||
file_name = f'{src_name}_warboy_{num_pe}pe.enf' | ||
return resolve_artifact(src_name, generated_path_base / file_name) | ||
|
||
|
||
def validate_postprocessor_type( | ||
postprocessor_type: Union[str, "Platform"], postprocessor_map: Collection["Platform"] | ||
): | ||
if postprocessor_type not in postprocessor_map: | ||
raise ValueError( | ||
f"Not supported postprocessor type: {postprocessor_type}, " | ||
f"Available choices: {', '.join(postprocessor_map)}" | ||
) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
다른 코드들과 일관성을 맞추면 좋을 것 같습니다.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
dc0aecc 에서 반영했습니다.