Add macOS to CI pipeline, restore HATPackage to accept a file path instead of a directory path (#13)

* restored hat package definition as a hat file + library

* [nfc] pydoc update

* update test condition

* replace test data files with runtime generation

* install test dependencies

* update whl test

* fix folder name

* export run_benchmark, support spaces in strings

* fixed add_functions to expect a single hat file

Co-authored-by: Lisa Ong <onglisa@microsoft.com>
This commit is contained in:
Lisa Ong 2022-01-13 08:31:26 +08:00 коммит произвёл GitHub
Родитель d2b3132a24
Коммит 9146a032b1
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 4AEE18F83AFDEB23
12 изменённых файлов: 70 добавлений и 204 удалений

18
.github/workflows/ci.yml поставляемый
Просмотреть файл

@ -12,12 +12,7 @@ jobs:
fail-fast: false
matrix:
python-version: ["3.7", "3.8", "3.9"]
os: ['windows-latest', 'ubuntu-latest']
include:
- os: 'windows-latest'
test-dir: 'tools/test/data/windows'
- os: 'ubuntu-latest'
test-dir: 'tools/test/data/linux'
os: ['windows-latest', 'ubuntu-latest', 'macos-latest']
runs-on: ${{ matrix.os }}
steps:
@ -35,7 +30,10 @@ jobs:
python -m pip install --upgrade pip
python -m pip install -r tools/requirements.txt
- name: Unittest
run: python -m unittest discover tools/test
run: |
python -m pip install -r tools/test/requirements.txt
python -m pip uninstall hatlib
python -m unittest discover tools/test
- name: Build whl
run: |
python -m pip install build
@ -44,8 +42,8 @@ jobs:
shell: pwsh
run: |
$WHL = Get-ChildItem -Path dist -Filter "*.whl" | %{$_.FullName}
python -m pip install $WHL
python -m pip install --force-reinstall $WHL
- name: Test whl
run: |
cd ${{ matrix.test-dir }}
hatlib.hat_to_dynamic optimized_matmul.hat optimized_matmul.test.hat
cd test_acccgen
hatlib.benchmark_hat BenchmarkHATPackage_test_benchmark.hat

8
.gitignore поставляемый
Просмотреть файл

@ -356,9 +356,9 @@ MigrationBackup/
build/
results.csv
# test data
tools/test/data/
# setuptools
dist/
*.egg-info/
*.egg-info/
# test
test_acccgen

Просмотреть файл

@ -2,3 +2,4 @@ from .hat import *
from .hat_file import *
from .hat_package import *
from .hat_to_dynamic import *
from .benchmark_hat_package import run_benchmark

Просмотреть файл

@ -8,30 +8,23 @@ import os
class HATPackage:
def __init__(self, hat_file_path):
"""A HAT Package is defined to be a HAT file and corresponding object file, located in the same directory.
The object file is specified in the HAT file's link_target attribute.
The same object file can be referenced by many HAT files.
"""A HAT Package is defined to be a HAT file and corresponding binary file, located in the same directory.
The binary file is specified in the HAT file's link_target attribute.
The same binary file can be referenced by many HAT files.
Many HAT packages can exist in the same directory.
An instance of HATPackage is created by giving HATPackage the file path to the .hat file."""
self.path = Path(dirpath).resolve()
assert self.path.is_dir()
self.name = os.path.basename(hat_file_path)
self.hat_file_path = hat_file_path
self.hat_file = HATFile.Deserialize(hat_file_path)
self.name = self.path.name
self.hat_files = [HATFile.Deserialize(hat_file_path) for hat_file_path in self.path.glob("*.hat")]
# Find all referenced link targets and ensure they are also part of the package
self.link_targets = []
for hat_file in self.hat_files:
link_target_path = self.path / hat_file.dependencies.link_target
if not os.path.isfile(link_target_path):
raise ValueError(f"HAT file {hat_file.path} references link_target {hat_file.dependencies.link_target} which is not part of the HAT package at {self.path}")
self.link_targets.append(link_target_path)
self.link_target = self.hat_file.dependencies.link_target
self.link_target_path = os.path.join(os.path.split(self.hat_file_path)[0], self.hat_file.dependencies.link_target)
if not os.path.isfile(self.link_target_path):
raise ValueError(f"HAT file {self.hat_file_path} references link_target {self.hat_file.dependencies.link_target} which is not found in same directory as HAT file (expecting it to be in {os.path.split(self.hat_file_path)[0]}")
self.functions = self.hat_file.functions
def get_functions(self):
functions = []
for hat_file in self.hat_files:
functions += hat_file.functions
return functions
return self.hat_file.functions
def get_functions_for_target(self, os: str, arch: str, required_extensions:list = []):
all_functions = self.get_functions()

Просмотреть файл

@ -47,7 +47,7 @@ def linux_create_dynamic_package(input_hat_binary_path, output_hat_path, hat_des
# create new HAT binary
prefix, _ = os.path.splitext(output_hat_path)
output_hat_binary_path = prefix + ".so"
os.system(f"g++ -shared -fPIC -o {output_hat_binary_path} {input_hat_binary_path}")
os.system(f'g++ -shared -fPIC -o "{output_hat_binary_path}" "{input_hat_binary_path}"')
# create new HAT file
hat_description["dependencies"]["link_target"] = os.path.basename(output_hat_binary_path)
@ -100,7 +100,7 @@ def windows_create_dynamic_package(input_hat_binary_path, output_hat_path, hat_d
function_descriptions = hat_description["functions"]
function_names = list(function_descriptions.keys())
linker_command_line = "link.exe -dll -FORCE:MULTIPLE -EXPORT:{} -out:out.dll dllmain.obj {}".format(" -EXPORT:".join(function_names), input_hat_binary_path)
linker_command_line = 'link.exe -dll -FORCE:MULTIPLE -EXPORT:{} -out:out.dll dllmain.obj "{}"'.format(' -EXPORT:'.join(function_names), input_hat_binary_path)
os.system(linker_command_line)
shutil.copyfile("out.dll", output_hat_binary_path)

Просмотреть файл

@ -1,74 +0,0 @@
#ifndef __optimized_matmul__
#define __optimized_matmul__
#ifdef TOML
[description]
author = ""
version = ""
license_url = ""
[functions]
[functions.optimized_matmul_py_66985f63]
name = 'optimized_matmul_py_66985f63'
description = ''
calling_convention = "cdecl"
arguments = [{name = '', description = '', logical_type = "affine_array", declared_type = 'float*', element_type = 'float', usage = "input_output", shape = [ 784, 128 ], affine_map = [ 128, 1 ], affine_offset = 0}, {name = '', description = '', logical_type = "affine_array", declared_type = 'float*', element_type = 'float', usage = "input_output", shape = [ 128, 512 ], affine_map = [ 512, 1 ], affine_offset = 0}, {name = '', description = '', logical_type = "affine_array", declared_type = 'float*', element_type = 'float', usage = "input_output", shape = [ 784, 512 ], affine_map = [ 512, 1 ], affine_offset = 0}]
return = {name = '', description = '', logical_type = "void", declared_type = 'void', element_type = 'void', usage = "output"}
[target]
[target.required]
os = "linux"
[target.required.CPU]
architecture = "x86_64"
extensions = ["+sse2", "-tsxldtrk", "+cx16", "+sahf", "-tbm", "-avx512ifma", "-sha", "-gfni", "-fma4", "-vpclmulqdq", "+prfchw", "+bmi2", "-cldemote", "+fsgsbase", "-ptwrite", "-amx-tile", "-uintr", "+popcnt", "-widekl", "+aes", "-avx512bitalg", "-movdiri", "+xsaves", "-avx512er", "-avxvnni", "-avx512vnni", "-amx-bf16", "-avx512vpopcntdq", "-pconfig", "+clwb", "+avx512f", "+xsavec", "-clzero", "-pku", "+mmx", "-lwp", "-rdpid", "-xop", "+rdseed", "-waitpkg", "-kl", "-movdir64b", "-sse4a", "+avx512bw", "+clflushopt", "+xsave", "-avx512vbmi2", "+64bit", "+avx512vl", "-serialize", "-hreset", "+invpcid", "+avx512cd", "+avx", "-vaes", "-avx512bf16", "+cx8", "+fma", "+rtm", "+bmi", "-enqcmd", "+rdrnd", "-mwaitx", "+sse4.1", "+sse4.2", "+avx2", "+fxsr", "-wbnoinvd", "+sse", "+lzcnt", "+pclmul", "-prefetchwt1", "+f16c", "+ssse3", "-sgx", "-shstk", "+cmov", "-avx512vbmi", "-amx-int8", "+movbe", "-avx512vp2intersect", "+xsaveopt", "+avx512dq", "+adx", "-avx512pf", "+sse"]
[dependencies]
link_target = "optimized_matmul.o"
deploy_files = []
dynamic = []
[compiled_with]
compiler = ''
flags = ''
crt = ''
libraries = []
[declaration]
code = '''
#endif // __TOML__
//
// Header for Accera library optimized_matmul
//
#include <stdint.h>
#if defined(__cplusplus)
extern "C"
{
#endif // defined(__cplusplus)
//
// Functions
//
void optimized_matmul_py_66985f63(float*, float*, float*);
#ifndef __optimized_matmul_py_DEFINED__
#define __optimized_matmul_py_DEFINED__
void (*optimized_matmul_py)(float*, float*, float*) = optimized_matmul_py_66985f63;
#endif
#if defined(__cplusplus)
} // extern "C"
#endif // defined(__cplusplus)
#ifdef __TOML__
'''
#endif // TOML
#endif // __optimized_matmul__

Двоичные данные
tools/test/data/linux/optimized_matmul.o

Двоичный файл не отображается.

Просмотреть файл

@ -1,74 +0,0 @@
#ifndef __optimized_matmul__
#define __optimized_matmul__
#ifdef TOML
[description]
author = ""
version = ""
license_url = ""
[functions]
[functions.optimized_matmul_py_5a7abfb9]
name = 'optimized_matmul_py_5a7abfb9'
description = ''
calling_convention = "cdecl"
arguments = [{name = '', description = '', logical_type = "affine_array", declared_type = 'float*', element_type = 'float', usage = "input_output", shape = [ 784, 128 ], affine_map = [ 128, 1 ], affine_offset = 0}, {name = '', description = '', logical_type = "affine_array", declared_type = 'float*', element_type = 'float', usage = "input_output", shape = [ 128, 512 ], affine_map = [ 512, 1 ], affine_offset = 0}, {name = '', description = '', logical_type = "affine_array", declared_type = 'float*', element_type = 'float', usage = "input_output", shape = [ 784, 512 ], affine_map = [ 512, 1 ], affine_offset = 0}]
return = {name = '', description = '', logical_type = "void", declared_type = 'void', element_type = 'void', usage = "output"}
[target]
[target.required]
os = "windows"
[target.required.CPU]
architecture = "x86_64"
extensions = ["+sse2", "-tsxldtrk", "+cx16", "+sahf", "-tbm", "-avx512ifma", "-sha", "-gfni", "-fma4", "-vpclmulqdq", "+prfchw", "+bmi2", "-cldemote", "+fsgsbase", "-ptwrite", "-amx-tile", "-uintr", "+popcnt", "-widekl", "+aes", "-avx512bitalg", "-movdiri", "+xsaves", "-avx512er", "-avxvnni", "-avx512vnni", "-amx-bf16", "-avx512vpopcntdq", "-pconfig", "+clwb", "+avx512f", "+xsavec", "-clzero", "-pku", "+mmx", "-lwp", "-rdpid", "-xop", "+rdseed", "-waitpkg", "-kl", "-movdir64b", "-sse4a", "+avx512bw", "+clflushopt", "+xsave", "-avx512vbmi2", "+64bit", "+avx512vl", "-serialize", "-hreset", "+invpcid", "+avx512cd", "+avx", "-vaes", "-avx512bf16", "+cx8", "+fma", "+rtm", "+bmi", "-enqcmd", "+rdrnd", "-mwaitx", "+sse4.1", "+sse4.2", "+avx2", "+fxsr", "-wbnoinvd", "+sse", "+lzcnt", "+pclmul", "-prefetchwt1", "+f16c", "+ssse3", "-sgx", "-shstk", "+cmov", "-avx512vbmi", "-amx-int8", "+movbe", "-avx512vp2intersect", "+xsaveopt", "+avx512dq", "+adx", "-avx512pf", "+sse"]
[dependencies]
link_target = "optimized_matmul.obj"
deploy_files = []
dynamic = []
[compiled_with]
compiler = ''
flags = ''
crt = ''
libraries = []
[declaration]
code = '''
#endif // __TOML__
//
// Header for Accera library optimized_matmul
//
#include <stdint.h>
#if defined(__cplusplus)
extern "C"
{
#endif // defined(__cplusplus)
//
// Functions
//
void optimized_matmul_py_5a7abfb9(float*, float*, float*);
#ifndef __optimized_matmul_py_DEFINED__
#define __optimized_matmul_py_DEFINED__
void (*optimized_matmul_py)(float*, float*, float*) = optimized_matmul_py_5a7abfb9;
#endif
#if defined(__cplusplus)
} // extern "C"
#endif // defined(__cplusplus)
#ifdef __TOML__
'''
#endif // TOML
#endif // __optimized_matmul__

Двоичные данные
tools/test/data/windows/optimized_matmul.obj

Двоичный файл не отображается.

Просмотреть файл

@ -0,0 +1 @@
accera

Просмотреть файл

@ -2,7 +2,7 @@
#!/usr/bin/env python3
import unittest
import sys, os
from pathlib import Path
import accera as acc
sys.path.append(os.path.join(os.path.dirname(__file__), ".."))
@ -10,12 +10,23 @@ from benchmark_hat_package import run_benchmark
from hat_to_dynamic import get_platform
class BenchmarkHATPackage_test(unittest.TestCase):
def setUp(self):
self.hatfile_path = Path(os.path.dirname(__file__)) / "data" / get_platform().lower() / "optimized_matmul.hat"
@unittest.skipUnless(get_platform().lower() == "windows", "Flaky on non-windows")
def test_benchmark(self):
run_benchmark(self.hatfile_path, store_in_hat=False, batch_size=2, min_time_in_sec=1, input_sets_minimum_size_MB=1)
A = acc.Array(role=acc.Array.Role.INPUT, shape=(256, 256))
B = acc.Array(role=acc.Array.Role.INPUT, shape=(256, 256))
C = acc.Array(role=acc.Array.Role.INPUT_OUTPUT, shape=(256, 256))
nest = acc.Nest(shape=(256, 256, 256))
i, j, k = nest.get_indices()
@nest.iteration_logic
def _():
C[i, j] += A[i, k] * B[k, j]
package = acc.Package()
package.add_function(nest, args=(A, B, C), base_name="test_function")
package.build(name="BenchmarkHATPackage_test_benchmark", output_dir="test_acccgen")
run_benchmark("test_acccgen/BenchmarkHATPackage_test_benchmark.hat", store_in_hat=False, batch_size=2, min_time_in_sec=1, input_sets_minimum_size_MB=1)
if __name__ == '__main__':
unittest.main()

Просмотреть файл

@ -1,7 +1,7 @@
#!/usr/bin/env python3
import unittest
import sys, os
from pathlib import Path
import accera as acc
sys.path.append(os.path.join(os.path.dirname(__file__), ".."))
@ -9,30 +9,40 @@ from hat import load
from hat_to_dynamic import get_platform, create_dynamic_package
class HAT_test(unittest.TestCase):
def setUp(self):
self.hatfile_path = Path(os.path.dirname(__file__)) / "data" / get_platform().lower() / "optimized_matmul.hat"
self.dyn_hatfile_path = Path(os.path.dirname(__file__)) / "data" / get_platform().lower() / "optimized_matmul.HAT_test.hat"
@unittest.skipUnless(get_platform().lower() == "windows", "Flaky on non-windows")
def test_load(self):
import numpy as np
import accera as acc
create_dynamic_package(self.hatfile_path, self.dyn_hatfile_path)
package = load(self.dyn_hatfile_path)
# Generate a HAT package
A = acc.Array(role=acc.Array.Role.INPUT, shape=(16, 16))
B = acc.Array(role=acc.Array.Role.INPUT_OUTPUT, shape=(16, 16))
nest = acc.Nest(shape=(16, 16))
i, j = nest.get_indices()
@nest.iteration_logic
def _():
B[i, j] += A[i, j]
package = acc.Package()
package.add_function(nest, args=(A, B), base_name="test_function")
package.build(name="HAT_test_load", output_dir="test_acccgen")
create_dynamic_package("test_acccgen/HAT_test_load.hat", "test_acccgen/HAT_test_load.dyn.hat")
package = load("test_acccgen/HAT_test_load.dyn.hat")
for name in package.names:
print(name)
# create numpy arguments with the correct shape and dtype
A = np.random.rand(784, 128).astype(np.float32)
B = np.random.rand(128, 512).astype(np.float32)
C = np.random.rand(784, 512).astype(np.float32)
C_ref = C + A @ B
A = np.random.rand(16, 16).astype(np.float32)
B = np.random.rand(16, 16).astype(np.float32)
B_ref = B + A
# call the function
name = package.names[0]
optimized_matmul = package[name]
optimized_matmul(A, B, C)
test_function = package[name]
test_function(A, B)
# check for correctness
np.testing.assert_allclose(C, C_ref)
np.testing.assert_allclose(B, B_ref)