FPGA開発日記

カテゴリ別記事インデックス https://msyksphinz.github.io/github_pages , English Version https://fpgadevdiary.hatenadiary.com/

"Digital Design with Chisel"が正式発行

f:id:msyksphinz:20190901171450p:plain:w200
"Digital Design with Chisel"の表紙

前々からChiselのメーリングリストに流れていた、Chiselの入門者的な書籍、"Digital Design with Chisel"が最終正式版になってついに発売されたらしい。

Amazonでも入手が可能だがpdfは無料公開されており、GitHubからすべての原稿とソースコードがダウンロードできる。太っ腹である。

github.com

Amazonのペーパーバックでわずか10ドルである。

www.amazon.com

コンパイル済みpdf版。

http://www.imm.dtu.dk/~masca/chisel-book.pdf

GitHubリポジトリをダウンロードしてさっそくpdfをビルドしてみた。環境としてはUbuntu 18.04 Windows Subsystem on Linuxを使ったのだが、pdfを作り上げるためにChiselの環境が丸まる必要であったことと、使っていたScalaのバージョンが古くて新しいものをインストールする必要があった。

sudo apt remove scala-library scala
wget www.scala-lang.org/files/archive/scala-2.12.6.deb
sudo dpkg -i scala-2.12.6.deb
git clone https://github.com/schoeberl/chisel-book.git
cd chisel-book
make

中身は結構しっかり書いてある。

  1. Introduction
  2. Basic Components
  3. Build Process and Testing
  4. Components
  5. Combinational Building Blocks
  6. Sequential Building Blocks
  7. Finite-State Machines
  8. Communicating State Machines
  9. Hardware Generators
  10. Example Designs
  11. Design of a Processor
  12. Contributing to Chisel

これ、日本語化してみても面白いかもな。勉強がてらに、少しやってみようかな。

GitHubでCoremark-PROがオープンソース化されたので試してみる

CPUのベンチマークといえば様々なものがあるが、有名なところといえば

など様々なものが存在する。SPECは有料だが、EEMBCはCoremarkに限って無料となっている。しかし今回、Coremarkに加えて、mlmark(Machine Learning benchMARK)およびCoremark-PROがオープンソースとして公開されてみたので触ってみたい。まずはCoremark-PROから試行していく。

github.com

Coremark-PROとは、Coremarkの後継として開発されたベンチマークプログラムセットだ。これまでCoremarkは無料で使用することができたが、Coremark-PROはベータ版のみ無料、本物は有料となっていた。今回はCoremark-PROも無料となり、オープンソースとして公開されている。

Ceremarkは単一のプログラム(といっても複数のベンチマークプログラムをmain()内で呼び出しているだけであるが)、Coremark-PROの場合は複数のプログラムから構成されている。

github.com

Coremark-PROのGitHubディレクトリを眺めているとbenchmarksディレクトリの中は複数のベンチマークから構成されていることが分かる。

f:id:msyksphinz:20190831164054p:plain
Coremark-PROのベンチマークプログラムのディレクトリ構成

Coremark-PROをx86マシンで動かす

では、まずはx86ベンチマークプログラムを動かしてみよう。使用しているのは私のSurface Laptop 2ノートPCである。

git clone https://github.com/eembc/coremark-pro.git

とりあえず手元のマシンで動作するかどうかを確認した。ビルドには以下のコマンドを使用する。

make TARGET=linux64 build

上記のコマンドで、以下のbuilds/linux64/gcc64/binにバイナリファイルが生成されていることが分かる。

$ tree builds/linux64/gcc64/bin
builds/linux64/gcc64/bin
├── cjpeg-rose7-preset.exe
├── core.exe
├── data
├── linear_alg-mid-100x100-sp.exe
├── loops-all-mid-10k-sp.exe
├── nnet_test.exe
├── parser-125k.exe
├── radix2-big-64k.exe
├── sha-test.exe
└── zip-test.exe

ベンチマークプログラムを走らせるためには、以下のようにコマンドを実行する。

make TARGET=linux64 XCMD='-c4' certify-all

ベンチマークプログラムを走らせた結果は、以下のようになった。

WORKLOAD RESULTS TABLE

                                                 MultiCore SingleCore
Workload Name                                     (iter/s)   (iter/s)    Scaling
----------------------------------------------- ---------- ---------- ----------
cjpeg-rose7-preset                                  357.14     119.05       3.00
core                                                  5.40       1.87       2.89
linear_alg-mid-100x100-sp                           400.00     169.49       2.36
loops-all-mid-10k-sp                                 19.96       6.02       3.32
nnet_test                                            16.89       7.27       2.32
parser-125k                                          50.00      19.23       2.60
radix2-big-64k                                     1543.21     449.84       3.43
sha-test                                            344.83     158.73       2.17
zip-test                                            222.22     111.11       2.00

MARK RESULTS TABLE

Mark Name                                        MultiCore SingleCore    Scaling
----------------------------------------------- ---------- ---------- ----------
CoreMark-PRO                                      12068.25    4581.69       2.63

RISC-Vへのポーティングを試みる

Coremark-PROをRISC-Vへの移植するためには、どのようにすれば良いのだろうか?

内部を調査していると、Coremarkの内部に複数のMakefileが入っており、これらを改造すればよいように思われる。以下のファイルを新たに追加してみた。中身は、

util/make/riscv64-unknown-elf-gcc.mak   # gcc.makからコピーして作成
util/make/riscv64.mak                   # linux64.makからコピーして作成

まずは、以下のようにしてビルドを実行する。以下のようにしてエラーが発生した。

/home/msyksphinz/riscv64/lib/gcc/riscv64-unknown-elf/7.2.0/../../../../riscv64-unknown-elf/bin/ld: cannot find -lpthread
/home/msyksphinz/riscv64/lib/gcc/riscv64-unknown-elf/7.2.0/../../../../riscv64-unknown-elf/bin/ld: cannot find -lrt

いろいろ調査してみたが、どうも上手く行かない。libpthread, librtが存在しているRISC-V GCCパッケージは無いかな?

  • riscv64-unknown-linux-gnuを使用してみると以下のエラーが発生した。
al_file.c:(.text+0xb2): undefined reference to `_impure_ptr'
/home/msyksphinz/riscv64-ctng-linux/lib/gcc/riscv64-unknown-linux-gnu/8.3.0/../../../../riscv64-unknown-linux-gnu/bin/ld: al_file.c:(.text+0xb6): 
/home/msyksphinz/riscv64-ctng-linux/lib/gcc/riscv64-unknown-linux-gnu/8.3.0/../../../../riscv64-unknown-linux-gnu/bin/ld: al_file.c:(.text+0xba): undefined reference to `__srget_r'
collect2: error: ld returned 1 exit status

うーん、単純なpthreadのプログラムは動くんだが。。。 どうにかしてpthreadのコードを削除できないかな?

AWSのPythonインタフェースboto3を使用してEC2インスタンスにジョブを流し込む

f:id:msyksphinz:20190818215623p:plain

AWSコマンドラインから扱う方法についていろいろ勉強している。 最終的にはローカルマシンで実行している処理をAWSに流し込めるようになりたいが、そのためにはまずはPythonインタフェースであるboto3から、コマンドを流し込んで実行できなければならない。 boto3からAWSLinuxを操作するための方法について調査した。

最終的に実現したいことは、ローカルマシンのLLVMリポジトリAWSに転送し、ビルドして、その結果を回収してくること。

という訳で、

  1. AWS上でEC2インスタンスを立ち上げ、Amazon Linux 2をブートする
  2. EC2インスタンス上で必要なツールをインストールし、LLVMをビルドする環境を整える。
  3. LLVMリポジトリとビルドディレクトリをS3からダウンロードする。
  4. ビルドを実行する
  5. 結果をS3に書き戻す

という手順をローカルマシンからboto3を経由して実行したい。 このフローを作成した。

  1. AWS EC2インスタンスの立ち上げ

これにはboto3の`create_instanceを用いる。今回はSpotインスタンスを使用して料金を安く抑える作戦に出た。

blockDeviceMappings = [{
    "DeviceName": "/dev/xvda",
    "Ebs": {
        "SnapshotId": "snap-08a7cb489033af8c7",
        "DeleteOnTermination": True,
        "VolumeType": "gp2",
        'VolumeSize': 100
        }
    }]

def create_instance(instance_type):
    print("Launching EC2 ...")
    ec2_resource = boto3.resource('ec2')
    # tag_specification = [{'ResourceType': 'spot-instances-request'}, ]
    instance_market_options={
    'MarketType': 'spot',
    'SpotOptions': {
        'MaxPrice': '0.27',
        'SpotInstanceType': 'one-time',
    }
    }

    instances = ec2_resource.create_instances(ImageId='ami-0c3fd0f5d33134a76',
                                          MaxCount=1, MinCount=1,
                                          InstanceType=instance_type,
                                          IamInstanceProfile={'Name': 'SSM_Access'},
                                          InstanceMarketOptions=instance_market_options,
                                              BlockDeviceMappings=blockDeviceMappings,
    )
    time.sleep(5.0)


    instance = instances[0]
    return instance
  1. EC2インスタンス上で必要なツールをインストールし、LLVMをビルドする環境を整える。

Amazon Linux 2はcmakeのバージョンが古いため、cmakeだと手動でアップグレードする。これはcmakeを最新に置き換えたAMIを用意すれば省略できるだろうが、ここではとりあえずインストールから実行している。

    cmake_install_command_list = ['yum install -y gcc gcc-c++ ncurses-devel',
                                  'wget https://cmake.org/files/v3.10/cmake-3.10.0.tar.gz -O /tmp/cmake-3.10.0.tar.gz &&'
                                  'cd /tmp/ && tar xfz cmake-3.10.0.tar.gz &&'
                                  'cd /tmp/cmake-3.10.0 && ./bootstrap &> log &&'
                                  'make -j16 -C /tmp/cmake-3.10.0/ &&'
                                  'make -j16 install -C /tmp/cmake-3.10.0/ && '
                                  'whereis cmake',
    ]
    execute_command_list(instance.instance_id, cmake_install_command_list)
  1. LLVMリポジトリとビルドディレクトリをS3からダウンロードする。

これはAWSs3 syncコマンドを使った。boto3でs3とEC2インスタンス間でsyncする機能について調査したのだが、良く分からなかったのでそのままawsコマンドを流して実行した。

    command_list = ['yum install update',
                    'yum install -y clang',
                    'aws s3 sync s3://llvm-bucket/build-myriscvx /home/ec2-user/build-myriscvx',
                    'aws s3 sync s3://llvm-bucket/llvm-myriscvx /home/ec2-user/llvm-myriscvx',
                    'aws s3 sync s3://llvm-bucket/myriscvx-tests /home/ec2-user/myriscvx-tests',
    ]
    execute_command_list(instance.instance_id, command_list)
  1. ビルドを実行する

これもmakeコマンドを流すだけである。

    command_list = ['cd /home/ec2-user/build-myriscvx && make -j16']
    execute_command_list(instance.instance_id, command_list)
  1. 結果をS3に書き戻す

ビルド結果をs3 syncコマンドでs3バケットに書き戻した。

    command_list = ['aws s3 sync /home/ec2-user/build-myriscvx s3://llvm-bucket/build-myriscvx']
    execute_command_list(instance.instance_id, command_list)

これをc5.4xlarge上で実行した。実行時間は大体30分程度であった。

問題点としては以下だと思う。

  • AWS EC2インスタンスの立ち上げに時間がかかる。これは短い時間で何度もビルドするような場合は一度立ち上げたEC2をTerminateせずに、再利用するという方法がある。
  • AWS EC2インスタンスとS3の同期に時間がかかる。これも一度立ち上げたEC2を短い間隔で再利用する場合には、Terminateしないという機能を実装すればよいと思う。

このあたりの機能を改良していく。

ソースコードは以下。

gist.github.com

AWSのPythonインタフェースboto3を使用してEC2インスタンスにコマンドを流し込む方法

f:id:msyksphinz:20190818215623p:plain

AWSコマンドラインから扱う方法についていろいろ勉強している。 最終的にはローカルマシンで実行している処理をAWSに流し込めるようになりたいが、そのためにはまずはPythonインタフェースであるboto3から、コマンドを流し込んで実行できなければならない。 boto3からAWSLinuxを操作するための方法について調査した。

boto3経由でAWS EC2インスタンスLinuxにコマンドを流し込むためには、ssmインタフェースというものを使用する。

boto3.amazonaws.com

SSMインタフェースを経由して、立ち上げているEC2インスタンスに対してコマンドを流し込む。インスタンスインスタンスIDを使用して識別する。

import boto3

def execute_command(instance_id, command):
    ssm_client = boto3.client('ssm')
    try:
        response = ssm_client.send_command(
        DocumentName="AWS-RunShellScript",
            Parameters={'commands': [command]},
            InstanceIds=[instance_id],
            TimeoutSeconds=3600,
        )
    except Exception as e:
        print("Error: During Executing EC2 Instance")
        print("message:{0}".format(e.message))

execute_command("インスタンスのID", "ls -lt /")

これでコマンドを発行できる。コマンドを発行すると、結果を回収しなければならない。そのためにはget_command_invocation()を使用する。 コマンドの状態を取得し、その状態output['Status']がまだ'InProgress'であれば処理実行中。そうでなければ結果が格納されている。

boto3.amazonaws.com

    command_id = response['Command']['CommandId']
    output = ssm_client.get_command_invocation(
        CommandId=command_id,
        InstanceId=instance_id,
    )
    while(output['Status'] == 'InProgress'):
        print("Time = {} : Status = {}".format(time_cnt * 5.0, output['Status']))

        time.sleep(5.0)
        output = ssm_client.get_command_invocation(
            CommandId=command_id,
            InstanceId=instance_id,
        )
        time_cnt += 1

例えば、以下のようなコードを実行してみよう。

command_list = ['ls -lt /',
                'df -h',
]
execute_command_list(instance.instance_id, command_list)

以下のように、実行結果を取得できる。

Executing ['ls -lt /', 'df -h'] ...
Output =
total 16
drwxrwxrwt   8 root root  172 Aug 23 10:58 tmp
drwxr-xr-x  27 root root  960 Aug 23 10:57 run
drwxr-xr-x  80 root root 8192 Aug 23 10:57 etc
drwxr-xr-x   4 root root   38 Aug 23 10:57 home
dr-xr-x---   3 root root  103 Aug 23 10:57 root
drwxr-xr-x  15 root root 2820 Aug 23 10:57 dev
drwxr-xr-x  19 root root  269 Aug 23 10:57 var
dr-xr-xr-x  13 root root    0 Aug 23 10:57 sys
dr-xr-xr-x 100 root root    0 Aug 23 10:57 proc
dr-xr-xr-x   4 root root 4096 Jun 18 22:24 boot
drwxr-xr-x   4 root root   27 Jun 18 22:24 opt
drwxr-xr-x  13 root root  155 Jun 18 22:23 usr
lrwxrwxrwx   1 root root    7 Jun 18 22:23 bin -> usr/bin
lrwxrwxrwx   1 root root    7 Jun 18 22:23 lib -> usr/lib
lrwxrwxrwx   1 root root    9 Jun 18 22:23 lib64 -> usr/lib64
lrwxrwxrwx   1 root root    8 Jun 18 22:23 sbin -> usr/sbin
drwxr-xr-x   2 root root    6 Jun 18 22:23 local
drwxr-xr-x   2 root root    6 Apr  9 19:57 media
drwxr-xr-x   2 root root    6 Apr  9 19:57 mnt
drwxr-xr-x   2 root root    6 Apr  9 19:57 srv
Filesystem      Size  Used Avail Use% Mounted on
devtmpfs        475M     0  475M   0% /dev
tmpfs           492M     0  492M   0% /dev/shm
tmpfs           492M  332K  492M   1% /run
tmpfs           492M     0  492M   0% /sys/fs/cgroup
/dev/xvda1      100G  1.3G   99G   2% /

ソースコードは以下。

gist.github.com

AWSコマンドラインインタフェースにてS3とデータを同期する方法

f:id:msyksphinz:20190818215623p:plain

AWSコマンドラインから扱う方法についていろいろ勉強している。 例えば、ローカルマシン上のファイルやディレクトリをS3にアップロードし、それをEC2インスタンスに転送する方法などコマンドラインで実現できるようになりたい。

ローカルディレクトリのファイルをS3にアップロードする方法について調査したのだが、PythonインタフェースのBoto3ではこれを扱う方法は無いらしい。 その代わりに、AWS CLIを使ってS3にアップロード・ダウンロードすることができる。

aws.amazon.com

www.saintsouth.net

#!/bin/bash

target_dirs=('/home/msyksphinz/work/llvm/llvm-myriscvx/' '/home/msyksphinz/work/llvm/myriscvx-tests/' '/home/msyksphinz/work/llvm/build-myriscvx')

for target_dir in ${target_dirs[*]}
do
    echo "Uploading " ${target_dir} " ..."
    aws s3 sync ${target_dir} s3://llvm-bucket/`basename ${target_dir}` --exclude ".git*"
done

aws s3 syncコマンドによりローカルディスクとS3のディスクを同期することができる。

EC2インスタンスをboto3を使って立ち上げてコマンドを実行するまでの流れ

https://i0.wp.com/python.gotrained.com/wp-content/uploads/2019/02/boto3.png?fit=300%2C300&ssl=1

boto3を使ってEC2のインスタンスを立ち上げるところまではできるようになった。次は、立ち上げたインスタンスに対してコマンドを発行して、EC2インスタンスに任意の操作を実行できるようにする。

boto3経由でEC2インスタンスを操作するためには、SSMという機能を使用する必要があるようだ。 SSMというのはAmazon Systems Manager Agentの略称らしく、AWSAPIを使用してEC2インスタンス内の制御を行うための環境らしい。ちなみに、Amazon Linux, Amazon Linux 2, Ubuntu 18.04 LTS, Ubuntu 16.04 LTSのAMIならば標準でインストールされている。

ここではAmazon Linux2を使うのでSSMは標準でインストールされているのだが、これを使うためにはSSMのRoleを有効にする必要があるようだ。 つまり、単純にboto3で、

instances = ec2_resouce.create_instances(ImageId='ami-0c3fd0f5d33134a76',
                                         MaxCount=1, MinCount=1,
                                         InstanceType='t2.micro',
)

としてAWSを立ち上げてコマンドを発行しても動作する訳ではないようだ。実際、何度もSendCommandを実行してエラーが帰ってきてしまい、なぜなのか原因が分からずしばらく悩んでしまった。

AWSのIAMのページで新しいロールの追加設定を行い、SSMに対するFull Accessを行うためのロールを追加した。

f:id:msyksphinz:20190818175844p:plain
AmazonSSMFullAccessのためのロールであるSSMAccessを追加する

そして、boto3でEC2インスタンスを作成する際にこのロールを追加する。

instances = ec2_resouce.create_instances(ImageId='ami-0c3fd0f5d33134a76',
                                         MaxCount=1, MinCount=1,
                                         # InstanceType='c5.4xlarge',
                                         InstanceType='t2.micro',
                                         IamInstanceProfile={'Name': 'SSM_Access'},
                                         InstanceMarketOptions=instance_market_options
)

これでSSMアクセス付きのインスタンスの作成が確認できた。SSMで制御できるインスタンスが存在しているかどうかは、AWSCLIコマンドで以下のようにして確認できる。

aws --region ap-northeast-1 ssm describe-instance-information --output text
INSTANCEINFORMATIONLIST 2.3.372.0       ip-172-31-14-230.ap-northeast-1.compute.internal        172.31.14.230   i-063a1f747b3dc1c74  False    1566119111.05   Online  Amazon Linux    Linux   2       EC2Instance
aws ssm describe-instance-information --instance-information-filter-list key=PingStatus,valueSet=Online
{
    "InstanceInformationList": [
        {
            "IsLatestVersion": false,
            "ComputerName": "ip-172-31-14-230.ap-northeast-1.compute.internal",
            "PingStatus": "Online",
            "InstanceId": "i-063a1f747b3dc1c74",
            "IPAddress": "172.31.14.230",
            "ResourceType": "EC2Instance",
            "AgentVersion": "2.3.372.0",
            "PlatformVersion": "2",
            "PlatformName": "Amazon Linux",
            "PlatformType": "Linux",
            "LastPingDateTime": 1566119111.049
        }
    ]
}

これでコマンドを流し込むことができるようになる。

ssm_client = boto3.client('ssm')
response = ssm_client.send_command(
    DocumentName="AWS-RunShellScript",
    Parameters={'commands': ['ls -lt /', 'df']},
    InstanceIds=[instance_id],
)

time.sleep(5.0)

command_id = response['Command']['CommandId']
output = ssm_client.get_command_invocation(
    CommandId=command_id,
    InstanceId=instance_id,
)
print("Output = \n{}\n".format(output['StandardOutputContent']))
print("Error  = \n{}\n".format(output['StandardErrorContent']))

time.sleep(5.0)としているのは、コマンドを実行してからしばらく待った方が安定するため。

結果として以下のような出力が得られた。コマンドはちゃんと実行されているらしい。

Launching EC2..
Instance ID = i-00d91f32fea43875f
Waiting EC2 Launch ...
EC2 Launch Finished ...
Output =
total 16
drwxr-xr-x  27 root root  960 Aug 18 09:15 run
drwxr-xr-x  80 root root 8192 Aug 18 09:14 etc
drwxrwxrwt   9 root root  301 Aug 18 09:14 tmp
drwxr-xr-x   4 root root   38 Aug 18 09:14 home
dr-xr-x---   3 root root  103 Aug 18 09:14 root
drwxr-xr-x  15 root root 2820 Aug 18 09:14 dev
drwxr-xr-x  19 root root  269 Aug 18 09:14 var
dr-xr-xr-x  13 root root    0 Aug 18 09:14 sys
dr-xr-xr-x 109 root root    0 Aug 18 09:14 proc
dr-xr-xr-x   4 root root 4096 Jun 18 22:24 boot
drwxr-xr-x   4 root root   27 Jun 18 22:24 opt
drwxr-xr-x  13 root root  155 Jun 18 22:23 usr
lrwxrwxrwx   1 root root    7 Jun 18 22:23 bin -> usr/bin
lrwxrwxrwx   1 root root    7 Jun 18 22:23 lib -> usr/lib
lrwxrwxrwx   1 root root    9 Jun 18 22:23 lib64 -> usr/lib64
lrwxrwxrwx   1 root root    8 Jun 18 22:23 sbin -> usr/sbin
drwxr-xr-x   2 root root    6 Jun 18 22:23 local
drwxr-xr-x   2 root root    6 Apr  9 19:57 media
drwxr-xr-x   2 root root    6 Apr  9 19:57 mnt
drwxr-xr-x   2 root root    6 Apr  9 19:57 srv
Filesystem     1K-blocks    Used Available Use% Mounted on
devtmpfs          485712       0    485712   0% /dev
tmpfs             503664       0    503664   0% /dev/shm
tmpfs             503664     348    503316   1% /run
tmpfs             503664       0    503664   0% /sys/fs/cgroup
/dev/xvda1       8376300 1244484   7131816  15% /


Error  =


Waiting EC2 Terminate ...
EC2 Terminate Finished ...

付録:ここまでのソースコード

#!/usr/bin/python3

import time
import boto3

print("Launching EC2..")
ec2_resouce = boto3.resource('ec2')
# tag_specification = [{'ResourceType': 'spot-instances-request'}, ]
instance_market_options={
    'MarketType': 'spot',
    'SpotOptions': {
        'MaxPrice': '0.27',
        'SpotInstanceType': 'one-time',
    }
}

instances = ec2_resouce.create_instances(ImageId='ami-0c3fd0f5d33134a76',
                                         MaxCount=1, MinCount=1,
                                         # InstanceType='c5.4xlarge',
                                         InstanceType='t2.micro',
                                         IamInstanceProfile={'Name': 'SSM_Access'},
                                         InstanceMarketOptions=instance_market_options,
                                         KeyName='msyksphinz_test',
)
instance = instances[0]
instance_id = instance.instance_id

print("Instance ID = {}".format(instance_id))

print("Waiting EC2 Launch ...")
instance.wait_until_running()
print("EC2 Launch Finished ...")

# print(instance.network_interfaces_attribute)

time.sleep(5.0)

ssm_client = boto3.client('ssm')
response = ssm_client.send_command(
    DocumentName="AWS-RunShellScript",
    # Parameters={'commands': ['sudo yum install -y clang']},
    Parameters={'commands': ['ls -lt /', 'df']},
    InstanceIds=[instance_id],
)

time.sleep(5.0)

command_id = response['Command']['CommandId']
output = ssm_client.get_command_invocation(
    CommandId=command_id,
    InstanceId=instance_id,
)
print("Output = \n{}\n".format(output['StandardOutputContent']))
print("Error  = \n{}\n".format(output['StandardErrorContent']))

instance.terminate()

print("Waiting EC2 Terminate ...")
# instance.wait_until_terminated()
print("EC2 Terminate Finished ...")

HotChips 2019で発表された世界最大のチップCerebras Wafer Scale Engineのホワイトペーパを読む

f:id:msyksphinz:20190826172825p:plain
https://www.cerebras.net ウェブサイトより引用。

HotChips 2019の様子がTwitterで流れてきた。やはり一番大きなインパクトがあったのはCerebrasのWafer Scale Engineではないだろうか。 性能、機能性、意味はとりあえずおいておいて、そのインパクトはかなり強力だった。

発表資料や論文など手に入らないかと探していると、いろいろと出てきた。

今回は、内容的に一番情報量の多そうなホワイトペーパを読んでみた。

結論からすると、Cerebras WSEの重要なポイントは以下の3つだ。

  1. 1チップにより多くのコアを詰め込む。
  2. 1チップにより多くのオンチップメモリを詰め込む。
  3. オフチップ通信をなるべく除去し、オンチップ通信に収める。

このアプローチは上手くいけば一応効果はありそうに思える。なぜならば小さなチップを組み合わせてオフチップ通信を行うならば、どうしても高速I/Oは必要で、そのためのIPを組み込むと面積だって馬鹿にならない。 実際発表資料を見ているとウェハレベルといいつつスクライブラインは見えるし、スクライブラインを超える通信は近距離ワイヤでつなぐらしい。 これだけのサイズのマスクなど製造できないだろうし、ワンショットで転写できる最大サイズでユニットを構成し、その間にスクライブラインを引いたのかな。

凄い技術だが、強力なブレークスルーにはならない気がする。上記のメリットに対して、組み立て、熱、ソフトウェア、制御、歩留まり、などの乗り越えなければならない問題が多すぎる。


Cerebras Wafer Scale Engine: An Introduction

1. 速度の必要性

ディープラーニングにおいて、計算速度は非常に重要であり、高速な計算機が必要となってくる。そのなかで、Google, Facebook, Baiduなどは学習時間がAIの長い学習時間がAIの進歩の足かせになっていると指摘している。 ここを解決するために、CerebrasはWafer Scale Engine(WSE)と呼ばれる超大規模なウェハレベルのチップを製造した。 このチップは46,225平方ミリメートルで、1.2兆個以上のトランジスタを内蔵している。 比較として、WSEは最大のグラフィック処理ユニットより56倍以上大きく、オンチップメモリは3,000倍、メモリ帯域幅は10,000倍以上を誇っている。

WSEは、最も複雑なAI作業にかかる時間を数か月から数分に短縮できる。

2. ディープラーニングのワークロード

まずはWSEを設計するにあたり、ディープラーニングのワークロードを定義する。ディープラーニングのベースとなるニューラルネットワークは、入力データからの出力データへ、パラメータを行列に掛け合わせるなどの、単純かつ高度に並列な操作から構成されている。 学習中は、データは入力と出力の間で双方向に流れる。学習中は出力ノードからエラーの情報が逆方向に伝搬され、パラメータの値が調整される。 ニューラルネットワークの学習に必要な時間は、このフィードバックループを介して入力を処理できる速度が重要となる。 入力がネットワーク内を高速で移動できるほど、単位時間あたりより多くの入力がループを介して処理することができ、ネットワークの学習が高速になる。

3. 性能の理解

ディープラーニングの学習時間を短縮する方法は、入力のフィードバックにかかる時間を短縮すること。つまり、計算速度を向上させる、そして通信速度を高速化させる、ということになる。

まず、計算時間の高速化は演算コアを増加させることにより達成できる。特に学習の場合は浮動小数点演算ユニットを増強することで計算を乞うおs区化させる。 次に、通信速度は帯域幅の増加、遅延の削減により達成できる。これにより、ループ内のステージの結果が後続のステージに迅速に伝達できる。通信速度の問題は非常に重要で、すでにAIの計算に必要なコアの数は、ダイに収めることのできる演算器の数を超えてしまっている。そこでNVIDIAはNVLinkと呼ばれるプロトコルを開発したり、を買収するなどして通信機能の強化を図ってきた。

今回のCerebrasのアプローチは、小さなチップを高速な通信方式を使って並べるのではなく、大きなウェハ上にチップを構築する。これにより以下のメリットがある。

  • 計算に使用できるコアの数が劇的に増加する。
  • チップ面積に余裕があるため、ローカルメモリなどの独自の記憶装置を配置する。
  • 通信はすべてオンダイ上で行う。オフチップ技術を使う必要がない。

(個人的な感想 : ただしオンダイとはいえ1ショットでは製造できないしスクライブラインがあるだろうから、結局ワイヤで接続する必要がありオンダイほどの通信性能は出ないのでは?)

このチップを設計するにあたり、「設計・製造・電力・冷却・通信・調整」などの問題が発生する。

4. Cerebrasアーキテクチャ

Cerebrasアーキテクチャの3つのキーを説明する。この3つのキーにより、WSEは高い性能を達成する。

  • コア
  • コアに近いメモリ
  • コア間帯域幅

4.1 シリコン領域が増えると、計算コアの面積が増える

CerebrasのWSEのコアはSLAと呼ばれている。コアはキャッシュを持たず、汎用命令(制御系命令)、線形代数命令・疎密度な線形代数命令などを備える。また、テンソル命令などを備える。

SLAの特徴としては、疎密度な線形代数に対する処理の最適化を実施している点である。疎密度な行列に対する最適化パスや命令を持っていることで性能を向上させ、消費電力を削減する。

4.2 オンチップメモリにより多くのシリコン面積を割く

ディープラーニングにおいてなぜオンチップメモリが重要なのかというと、モデルのパラメータ・アクティベーション・モデルの構成などを保持するため。このためのメモリをオンチップに持ち、高速に読み書きすることが必要になる。

これまでのチップでは大容量高速メモリは大部分がオフチップであり、HBMなどの技術を使っても非常に低速なままである。

そこで、CerebrasのWSEには18GBにもおよぶオンチップメモリを搭載し、9.6バイト(注:これはTypo?)のバンド幅を持っている。

4.3 シリコン上での高速ネットワーク : Swarm Fabric

Cerebras WSEの400,000のコアは、1秒あたり100Peta Bitの帯域幅を持つ2DメッシュのSwarm通信Fabricを介して接続されている。 Swarmは、各計算コアにハードウェアルーティングエンジンを内蔵し、レイテンシと帯域幅に最適化された短いワイヤでそれらを接続する。

また、このFabricはコンフィギャラブルで、ソフトウェアで制御することによりAIのトレーニング中に正確な通信を実現できる。

  1. Cerebras Wafer Scale Engineのソフトウェアスタック

WSEのソフトウェアスタックは、WSEの設計時からハードウェアと同時に開発された。 Cerebrasのグラフコンパイラは、TensorFlowやPyTorchなどのMLフレームワークとのシームレスなインタフェースを持っており、ニューラルネットワークのデータフローを抽出し、これらをマイクロコードプログラムに変換し、WSEのリソースにマッピングする。そしてSwarm Fabricを構成し、データパスを形成して性能を最大化させる。


と、良いことはいっぱい書いてあるが、すべてに同意できるわけではないし、16nmで歩留まりはどれくらいになるのだろう。

アーキテクチャとしては少し古臭く感じる。疎密度の線形代数とか、最先端のディープラーニングアルゴリズムではもう時代遅れなのでは?

どちらにしろ、彼らがどのようにしてこの困難なチップを実用化していくのか、とても楽しみだ。