このブログは、株式会社フィックスターズのエンジニアが、あらゆるテーマについて自由に書いているブログです。
全国のRISC-Vファンの方、こんにちは。
今日は、RISC-Vの”V”の本丸機能であるVector extension(ベクトル拡張)、略してRVVがどのようなものか、解説をしたいと思います。
(「なんでフィックスターズがRISC-V?」などは前回のRISC-V記事をご覧ください。なお、この記事も前回と同じく、勉強会にて発表した内容の加筆修正版です。)
RV32Vはまだ正式版(stable)ではありません。執筆時点の最新リリースバージョンはv0.7.1です。
現在もmasterにどんどん更新が入っています。
とはいえ、聞き及んだところによると、バイナリ表現などが決まっては居ないものの、レジスタや命令については概ね完成しているようです。
本記事では、執筆時点にRISC-V “V” Vector Extensionのページで確認した最新版である”0.8-draft-20190906″に準拠した解説をします。
また、この記事にはRISC-Vの仕様がかなり多く含まれている関係上、本家レポジトリと同じくクリエイティブ・コモンズ表示4.0国際ライセンスの元で利用許諾します。再利用時の帰属表示には、本家に加えて「株式会社フィックスターズ」を追加してください。
「ベクトル」と聞いてみなさん何を思い浮かべますか?
など色々あると思いますが、本記事で言う「ベクトル」とは、1命令で複数のデータを処理する方法の1つを指します。
複数の処理を並列実行するので、ソフトウェアの高速化には大変重要な概念です。
もしかして、ベクトル命令アーキテクチャについてご存じないですか?ではヘネパタ読んでください(『RISC-V原典』p.75より)。
しかし現代のIT技術者であってもベクトル命令が使える計算機使ったことある人の方が稀なので(『RISC-V原典』p.75より)、少し簡単に+あえて詳細には触れず雰囲気を知ってもらうぐらい紹介を以下にします。
なお、RISC-Vの解説記事であることから分かる通り、以降では前提として、話は命令セットアーキテクチャ(ISA)のことを指しており、ハードウェアの実装アーキテクチャの話ではないことに注意してください。
多くの方は「1命令で複数データを実行」と聞いて、x86, x64にあるSSEやAVXを思い浮かべることと思います。
これらの命令セットアーキテクチャを、RISC-Vの文脈では(特に『RISC-V原典』では)、「SIMDアーキテクチャ」と呼んでいます。
一方で、RISC-Vでは同様の「1命令で複数データを実行」する手法として、SIMDではなく「ベクトル」を採用しています。
ベクトルがSIMDと違う最大の点は、固定長でないことです。
SSEやAVXやNEONなど有名なSIMD命令は全て「どの命令が何bitか」決まっていますが、それだとハードウェアが進化して幅が増えたり変わったりするたびに新しい命令を作ることになり、ISAが不安定になります。
そのため、「ISAはシンプルかつ安定的であるべき」という思想のあるRISC-Vでは、SIMDではなくベクトルを使うことになりました。
この辺り、用語が分かりにくい上に色んな人や企業が色んな流儀で呼んでいるので混乱しやすいのですが、以降はとりあえずこのRVVの流儀に従って、このように区別することにします。
ちょっと話は脱線しますが、なぜ現代ではベクトルではなくSIMDが主流になっているのかを整理します。
一番大きい理由は、我々が普段使っているCPUの最大手であるIntelらがMMX, SSE, AVXとSIMD推しだからだと思われます。
ではなぜそれらのCPUでベクトルが採用されていないか?というと、当事者らに聞かないと本当のことは分かりませんが、1つの理由には、大きなベクトルレジスタではコンテキストスイッチのコストが高いことが挙げられるようです。
コンテキストスイッチの時には、現在のレジスタの値を全部保存して切り替える必要があり、長いレジスタ幅だとそこがかなり重くなってしまいます(『RISC-V原典』p.78)。
という事情から、ベクトルは本当の汎用計算機には向かず、それが普通のCPUを使った計算機には採用されていない一つの理由のようです。
一方、現代でも、主にHPC用途ではベクトル・アーキテクチャはまだ現役です(GPGPUのようなメニーコアSIMTにかなり圧されている気はしますが)。
例えば、NECのSX-Aurora TSUBASA(※ツバメではない)はつい最近発売された、代表的なベクトルアーキテクチャを採用したハードウェアですね。
あと、有名なところでは、ポスト京あらため富岳で使われるA64fxが採用するARM SVE(Scalable Vector Extension)という命令セットがベクトルとなっています。
※SVEがベクトルかSIMDかについては異論もあります
RISC-Vでも、ベクトル拡張を推進している人たちは、RISC-V総本山のUCバークレーの人に加えて、超並列RISC-Vコアを作ろうとしているEsperanto Technologiesの人がいたり、割とHPC向きの人が多いようです。
先に書いた通り、SIMDとの大きな違いは、1命令で実行できる長さが可変であることです。これはどう実現しているでしょうか?
簡単に想像がつくと思いますが、1命令で実行できる長さレジスタがあり、計算したい長さだけ演算が繰り返される命令です。つまり擬似コードでかけば
for(i=0; i<n; ++i)
{
a[i] *= 2.0;
}
という処理がベクトルでは
L=8; // 1命令で実行できる長さ、これはHW固有
for(i=0; i<n; i+=L)
{
a[i:i+L] *= 2.0 // L個同時にload/add/store命令
}
なります。SIMDとの違いは、Lが命令オペランドにあるのか、実行時に読み込むのかという違いとも言えますね。
しかし、この命令オペランドにあるかどうかは、命令デコーダー部に関わってくるので、ハードウェア的には大きな違いがあります。
また、これは本当にハードウェア的にもL個同時に実行されていることを必ずしも意味してはいません。
ハードウェア上は、中で1要素ずつL回反復が回っているかもしれませんし、L/2回ずつかもしれません。
しかし仮に同時実行でないとしても、各処理が独立していることが保証されているのできれいにパイプライン処理でき、実行時間の短縮(効率化)につながります。
という感じで、ハードウェアの実装と命令をうまく分離できているのがベクトル命令アーキテクチャの特徴です。
一方で、見て分かる通り、これでは実行レイテンシがどうなっているかが、命令だけでは決まりません。その辺りの予測困難さ(に加えて、先ほどのコンテキストスイッチの重さ)がSIMDと比較したときのトレードオフになります。
さて軽く復習したところで、本編です。
RISC-Vにおけるベクトル命令規格はRV32V(またはRV64V)で定義されています。命令は概ね
に分けられます。また、これらに加えて、ベクトル演算をするためのレジスタが用意されています。
RV32Vでは、普通のベクトル・アーキテクチャよりも更にISAを単純化するために大変工夫しています。
このベクトル拡張の策定・推進にはかなりの労力のかけられているようで、『RISC-V原典』にはRISC-VのVはVectorのVとも言える、とまで書いてあります。
ここが割と重要なRISC-Vの工夫なので、少し詳細に説明します。
まず、RV32Vに定義されているレジスタには
があります。最初のだけが汎用レジスタ、残りはシステム制御用レジスタ(CSR/Control and Status Registers)です。そして、後者3つは細かい設定を必要とする時にしか使わないので、本解説では割愛します。大事なのは前者3つです。
まず、ベクトルレジスタが32個ありますが、幅は実装依存です。これはVLEN
と呼ばれ、2ベキであることと、全てのレジスタが同じVLEN
であることのみが保証されています。
ただしv0
はゼロレジスタであり、常に0で、何を書いても0のままです(スカラーのx0
レジスタと同じです)。
vlレジスタには、実際に1命令で実行されるベクトル長、つまり、何個の要素が計算されるかを符号なし整数で示します。
vtypeレジスタは、以下のように、1要素の長さ等を各フィールドで表します
SEW
が2^(3+vsew)[bit]であることを表します(例えばb011なら1要素は64bit)。SEW
の最小値は8、最大値は1024になりますLMUL
が2^vlmul個であることと表します(例えばb10なら、指定されたレジスタから後ろ4個が使われる)。LMUL
に最小値は1、最大値は8になりますvlとvtypeレジスタによって、1命令で実行できる最大要素数VLMAX
がVLEN*LMUL/SEW
個であると決定されます。
このvlとvtypeレジスタは読み込み専用で、設定はレジスタに直接書くのではなく、vsetvli
またはvsetvl
命令で同時に指定します。
vsetvli rd rs1 vtypei
vsetvl rd rs1 rs2
AVL
ですここで注意しなければならないのは、rs1で指定した値が、そのまま計算で使用されるベクトル長vlになるわけではないということです。
簡単な例を出すと、32[bit]要素を128個演算したい(SEW
=32、AVL
=128)と指定すると、全部で32*128=4096[bit]必要になりますが、ベクトルレジスタの数は31個しかないのでベクトルレジスタの幅VLEN
が64[bit]の実装だった場合には、1命令で実行できる要素数vl
が128個になることはありません(レジスタ全部合わせて32*64=1984[bit]しかないので、4096[bit]は入らない)
指定された要素数AVL
から実際に計算されるvl
の値は実際には以下のように決まります
なお、AVLに0を指定すると、vlの値を変更することなく、rdに現在のvl値を取得できます。
少し複雑なので、例題を出します。
ここに、ベクトルレジスタの最大幅VLENが2048[bit]のハードウェアがあるとします。それに対して、64[bit]要素の配列をベクトル演算したい時には、
という感じになります。
このようにvlの決定方法が決められているのは、主にレジスタスピルやコンテキストスイッチのために退避・復元するレジスタ数などを決定しやすくするためです。
また、「VLMAXの2倍より小さい時」の特殊化が入っているのは、反復の最後の端数処理の時のためです。
例えば1命令の最大要素数(VLEN)=128個で残りの要素数(AVL)=140個の時に、最後の2回の反復で計算する要素数を、128個→12個と偏らせず70個→70個と均等分散させられます。
また、vtypeにはどんな値でも入れられるわけではなく、要素の長さSEWには制限があり、これをELEN
と呼び実装依存の固有値です(2ベキではある)。
例えば、スカラーのレジスタ幅(※RV32やRV64などのアドレス幅とは関係ない)であるXLENより長いデータ長はサポートしないことが多いと考えられるので、その場合ELEN
は自動的にXLENと等しくなることが多いと思います。
もし、SEWなどに実装でサポートされていない値を入れた場合、villフィールドに1が立ちます。
このような仕様の何が嬉しいかと言うと、ISAがかなり単純になることです。先述の通り、従来は、演算したい幅や要素幅をオペコードに入れていたため、それぞれに対して命令が定義され、結果的にデコーダーの負荷が高く、かつ将来の拡張性が低いものでした。
しかし、RV32Vなら、以下のようにとても簡単な命令列で、どんなハードウェアでも効率的にベクトル演算をすることができます。
# a0:配列の(残りの)要素数
loop:
vlsetvli t0, a0, e64,m4 # 64[bit]で4レジスタを使い、残りa0個全部を計算したいと入力するが、実際に計算される数vlはt0に返ってくる
### ここでベクトル演算などをする
sub a0, a0, t0 # 残りの要素数を計算する : a0 -= t0;
bnez a0, loop # 残りが0個になるまで繰り返す : if(a0 != 0) goto loop;
多くの命令では、オペランドにマスクvmを指定し、命令を実行する要素としない要素を選択できます。
マスクに用いるのは、各要素の最下位ビットのみで、1ならば実行され、0ならば実行されません。
vmにv0(ゼロレジスタ)を入れると1つも実行されないことになります。逆に、全部実行したい(ことのほうが多いと思いますが)場合は、アセンブラには何も指定しないでください(全てが1になります)。
また、普通のレジスタとマスクを区別するために、アセンブラ上ではvmのところに指定したレジスタには.tと書くことになっています
vlb.v v1, v2, v3.t
マスクに用いる値を用意するために、レジスタの最下位ビットだけを操作するマスク専用命令vm***があります。
vmand | vd = vs2 & vs1 |
---|---|
vmandnot | vd = vs2 & !vs1 |
vmnand | vd = !(vs2 & vs1) |
vmor | vd = vs2 | vs1 |
vmornot | vd = vs2 | !vs1 |
vmnor | vd = !(vs2 | vs1) |
vmxor | vd = vs2 ^ vs1 |
vmxnor | vd = !(vs2 ^ vs1) |
その他に、実行される要素(1)の数を数える(いわゆるポップカウント)や、最初の要素番号を調べる命令もあります
vmpopc.m rd, vs2, vm # vs2の各最下位ビットが1の数をrdに出力する
vmfirst.m rd, vs2, vm # vs2の最下位ビットが最初に1なっている要素番号をrdに出力する
# いずれも、vmで0になっている要素は無視される
また、「最初に1が立っているところ」に対して、「それより前を全て1にする」「自分も含めて1にする」「自分だけ1にする」という命令もあります
vmsbf.m vd, vs2, vm
vmsif.m vd, vs2, vm
vmsof.m vd, vs2, vm
# いずれも、vmで0になっている要素は無視される
命令の結果のvd/vs2 | 0 | 0 | 0 | 1 | 0 | 1 | 1 | 0 |
---|---|---|---|---|---|---|---|---|
vmsbf |
1 | 1 | 1 | 0 | 0 | 0 | 0 | 0 |
vmsif |
1 | 1 | 1 | 1 | 0 | 0 | 0 | 0 |
vmsof |
0 | 0 | 0 | 1 | 0 | 0 | 0 | 0 |
ベクトルレジスタに何かしらの値を持ってくるために、メモリから読み書きする必要があります。RV32Vでは、以下3種類のメモリアクセスが可能です
※vlがvector lengthのレジスタと、vector loadの読み込み命令の両方の意味が使われるので注意
これらは、機械語レベルでは1つのオペコード0000111
(load)と0100111
(store)が割り当てられており、フィールドの値を変えることで切り替えられます。
通常の連続アクセスは簡単で、指定したアドレスからvl個の要素をLMUL個のレジスタを使って読み込みます
vlb.v vd, (rs1), vm # rs1のアドレスから、vdへ、b(08bit)を、連続で、読み込む
vlh.v vd, (rs1), vm # rs1のアドレスから、vdへ、h(16bit)を、連続で、読み込む
vlw.v vd, (rs1), vm # rs1のアドレスから、vdへ、w(32bit)を、連続で、読み込む
vle.v vd, (rs1), vm # rs1のアドレスから、vdへ、vtypeレジスタ.SEWの要素サイズを、連続で、読み込む
vsb.v vs3, (rs1), vm # rs1のアドレスへ、vs3から、b(08bit)を、連続で、書き込む
vsh.v vs3, (rs1), vm # rs1のアドレスへ、vs3から、h(16bit)を、連続で、書き込む
vsw.v vs3, (rs1), vm # rs1のアドレスへ、vs3から、w(32bit)を、連続で、書き込む
vle.v vs3, (rs1), vm # rs1のアドレスへ、vtypeレジスタ.SEWの要素サイズを、連続で、書き込む
ストライドは、連続アクセスに加えて、rd2にストライド、つまり次の要素までの間隔が何バイトであるかを指定します(負またはゼロも可)
vlsb.v vd, (rs1), rs2, vm # rs1のアドレスから、vdへ、b(08bit)を、rs2[byte]間隔で、読み込む
vlsh.v vd, (rs1), rs2, vm # rs1のアドレスから、vdへ、h(16bit)を、rs2[byte]間隔で、読み込む
vlsw.v vd, (rs1), rs2, vm # rs1のアドレスから、vdへ、w(32bit)を、rs2[byte]間隔で、読み込む
vlse.v vd, (rs1), rs2, vm # rs1のアドレスから、vdへ、vtypeレジスタ.SEWの要素サイズを。rs2[byte]間隔で、読み込む
vssb.v vs3, (rs1), rs2, vm # rs1のアドレスへ、vs3から、b(08bit)を、rs2[byte]間隔で、書き込む
vssh.v vs3, (rs1), rs2, vm # rs1のアドレスへ、vs3から、h(16bit)を、rs2[byte]間隔で、書き込む
vssw.v vs3, (rs1), rs2, vm # rs1のアドレスへ、vs3から、w(32bit)を、rs2[byte]間隔で、書き込む
vlse.v vs3, (rs1), rs2, vm # rs1のアドレスへ、vs3から、vtypeレジスタ.SEWの要素サイズを、rs2[byte]間隔で、書き込む
インデックスアクセスは、固定されたストライドではなく、各要素が指定されたベクトルレジスタにある要素番号(オフセット)から取得されます
vlxb.v vd, (rs1), vs2, vm # rs1のアドレスから、vdへ、b(08bit)を、各々vs2進んだところから、読み込む
vlxh.v vd, (rs1), vs2, vm # rs1のアドレスから、vdへ、h(16bit)を、各々vs2進んだところから、読み込む
vlxw.v vd, (rs1), vs2, vm # rs1のアドレスから、vdへ、w(32bit)を、各々vs2進んだところから、読み込む
vlxe.v vd, (rs1), vs2, vm # rs1のアドレスから、vdへ、vtypeレジスタ.SEWの要素サイズを。各々vs2進んだところから、読み込む
vsxb.v vs3, (rs1), vs2, vm # rs1のアドレスへ、vs3から、b(08bit)を、各々vs2進んだところへ、書き込む
vsxh.v vs3, (rs1), vs2, vm # rs1のアドレスへ、vs3から、h(16bit)を、各々vs2進んだところへ、書き込む
vsxw.v vs3, (rs1), vs2, vm # rs1のアドレスへ、vs3から、w(32bit)を、各々vs2進んだところへ、書き込む
vlxe.v vs3, (rs1), vs2, vm # rs1のアドレスへ、vs3から、vtypeレジスタ.SEWの要素サイズを、各々vs2進んだところへ、書き込む
連続アクセスには、「0が取得されたらそこで読み書きを終了する」という読み込み命令(fault-only-first)があります。
vlbff.v vd, (rs1), vm # rs1のアドレスから、vdへ、b(08bit)を、0が取得されるまで連続で、読み込む
vlhff.v vd, (rs1), vm # rs1のアドレスから、vdへ、h(16bit)を、0が取得されるまで連続で、読み込む
vlwff.v vd, (rs1), vm # rs1のアドレスから、vdへ、w(32bit)を、0が取得されるまで連続で、読み込む
vleff.v vd, (rs1), vm # rs1のアドレスから、vdへ、vtypeレジスタ.SEWの要素サイズを、0が取得されるまで連続で、読み込む
この命令を実行後にvlレジスタを読みに行くと、実際に読み込まれた値が含まれています。
つまり、例えばvl=8の時にvd={10, 11, 12, 13, 14, 15, 16, 17}に対してvlffを発行した時、(rs1)に{1, 2, 3, 4, 5, 0, 0, 0}があったとすると、実際にはvd={1, 2, 3, 4, 5, 15, 16, 17}になり、vl=5になっています。
これは、whileループのようなもので、特に\0
終端の文字列を読み込む時などに有用です。
# a0に文字列のポインタが入っているとする
vsetvli x0, x0, b00000 # 最大長読み込む設定
vlbff.v vd, v1, (a0) # 文字列を読み込む
csrr a1, vl # 実際に何個読まれたかを取得する
なお、フラグ判定付きは連続アクセスにしかなく、ストライドやインデックスアクセスには用意されていません。これは、オーバーラン等のセキュリティーリスクが高すぎるためとのことです。将来的に拡張機能で入る可能性は否定されていないようです。
上記までは1要素ずつの読み書きでしたが、拡張機能として、複数要素の単位で読み書きする命令が提案されています。
これは、AoS(Array of Structures)のデータ構造を読み書きするのに重要で、特にストライドやインデックスアクセスに利用されます。
まだ提案されたばかり&拡張機能なので、詳しくは割愛します。
は、それぞれ、その形式での演算を実行します。
先述の通り、要素幅(何ビットか)はSEWで決まっているので、これらの違いは、その幅の数字を、どのように扱うかという点しか違いがありません。
どの形式においても、基本的な加減乗除はもちろん、対応するビット演算、根号、FMAなどもあります。
固定小数命令は、多くの演算は整数命令で代替できますが、以下のような命令が追加されます。
があります。
浮動小数命令の場合、IEEE754/2008には16,32,64,128bitしか規定がないので、SEWにはこのうちいずれか(かつ、実装固有値で最大幅であるELEN以下)でないと、不正命令例外になります。
また、いくつかの命令では、演算結果の幅を倍にしたり半分にしたりすることも可能で、特に加算・乗算においては、オーバーフロー等を防ぐことができます。
縮約(リダクション、reduction)は、vl個の値を1つにまとめるものです。
が定義されています。どの形式でも
vred***.vs vd, vs2, vs1, vm
という形になっており、***にsum, orなど具体的な演算名が入ります。この命令では、vdの0要素目に、vs1の0要素目とvs2全部の縮約結果が入ります。
vredsum.vs vd, vs2, vs1, vm
# vd[0] = sum(vs1[0], vs2[0], vs2[1], ..., vs2[vl-1])
演算結果を幅を倍にする命令や、浮動小数演算の場合は順番を保証するものとしない2種類が用意されています。
交換命令では、ベクトルレジスタ内の要素を並び替えたり移動させたりすることができます。
いずれも、移動先(vd)と元(vs2)を重ねると不正命令となります。どうしても重なる場合はちゃんとマスクしてください。
n個ずらして格納します
# 右にずらす。rs1/immより左にある値は変更されず、vl<VLMAXの時の末尾は0埋めされる
vslideup.vx vd, vs2, rs1, vm # vd[i+rs1] = vs2[i]
vslideup.vi vd, vs2, imm, vm # vd[i+imm] = vs2[i]
# 左にずらす。末尾は0となる
vslidedown.vx vd, vs2, rs1, vm # vd[i+rs1] = vs2[i] # vd[i] = vs2[i+rs1]
vslidedown.vi vd, vs2, imm, vm # vd[i+imm] = vs2[i] # vd[i] = vs2[i+imm]
また、1個だけずらし、末尾/先頭にはスカラーレジスタx0,x1,…から読み込むという命令もあります。
vslide1up.vx vd, vs2, rs1, vm # vd[i+1] = vs2[i] , vd[0]=x[rs1]
vslide1down.vx vd, vs2, rs1, vm # vd[i] = vs2[i+1], vd[vl-1]=x[rs1]
ベクトルレジスタにコピー元の要素番号を入力することができます
vrgather.vv vd, vs2, vs1, vm # vd[i] = vs2[vs1[i]]; # 各要素の番号が異なる
vrgather.vx vd, vs2, rs1, vm # vd[i] = vs2[rs1] # 1要素からコピー(いわゆる配信/broadcast)
vrgather.vi vd, vs2, imm, vm # vd[i] = vs2[imm] # 1要素(即値)
フラグの立っている要素のみを複製し、前に詰める命令です
vcompress.vm vd, vs2, vs1 # vs1が1の場所のvs2の値を、vdに前に詰める
例えば以下のようになります
vs1 | 0 | 1 | 0 | 1 | 1 | 0 | 1 | 1 |
vs2 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 |
vd | 2 | 4 | 5 | 7 | 8 | 0 | 0 | 0 |
ベクトルレジスタの特定要素をスカラーレジスタに移動させたり、元に戻したりできます
vmv.s.x vd, rs1 # vd[0] = rs1
vmv.x.s rd, vs2 # rd = vs[0]
vext.x.v rd, vs2, rs1 # rd = vs2[rs1]
vfmv.s.f vd, rs1 # vd[0] = rs1
vfmv.f.s rd, vs2 # rd = vs2[0]
最初に書いた通り、ベクトル命令に関する規格はまだ大きく変わっており、この内容も将来的に変更される可能性があります。ただし、基本的な考え方はあまり変わっていないはずで、教科書も副読本としては価値があると思います(が、なくなったり名前が変わったりしているものも多いので・・・)。
いつかベクトル命令が安定版になる日を待ち望んでいます!!
RISC-Vを学ぼうと思う方は、『RISC-V原典』を手に取り読み始める方が多いかもしれませんが、散々述べてきた通りベクトル拡張は日々変化しているので、書籍に書かれている内容から大きく変わっています。
以下、気づいたところを列挙しておきます(全部網羅したわけではありません)。
mvl
はなくなり、vlやVLENなどに変更されたvpi
はなくなり、汎用レジスタでマスクを取り扱うことになったvsetdcfg
やベクトル長を指定するsetvl
命令はなくなり、vsetvli
命令に統合され同時に指定することになったvselect
ではなくなり、vrgather
になった
コンピュータビジョンセミナーvol.2 開催のお知らせ - ニュース一覧 - 株式会社フィックスターズ in Realizing Self-Driving Cars with General-Purpose Processors 日本語版
[…] バージョンアップに伴い、オンラインセミナーを開催します。 本セミナーでは、...
【Docker】NVIDIA SDK Managerでエラー無く環境構築する【Jetson】 | マサキノート in NVIDIA SDK Manager on Dockerで快適なJetsonライフ
[…] 参考:https://proc-cpuinfo.fixstars.com/2019/06/nvidia-sdk-manager-on-docker/ […]...
Windowsカーネルドライバを自作してWinDbgで解析してみる① - かえるのほんだな in Windowsデバイスドライバの基本動作を確認する (1)
[…] 参考:Windowsデバイスドライバの基本動作を確認する (1) - Fixstars Tech Blog /proc/cpuinfo ...
2021年版G検定チートシート | エビワークス in ニューラルネットの共通フォーマット対決! NNEF vs ONNX
[…] ONNX(オニキス):Open Neural Network Exchange formatフレームワーク間のモデル変換ツー...
YOSHIFUJI Naoki in CUDAデバイスメモリもスマートポインタで管理したい
ありがとうございます。別に型にこだわる必要がないので、ユニバーサル参照を受けるよ...