nvmath-pythonにおけるユニバーサルスパーステンソルによるスパース深層学習の簡素化
NVIDIAはnvmath-pythonライブラリにUniversal Sparse Tensor(UST)を追加し、スパース構造とメモリレイアウトを分離することで、スパース深層学習の開発効率と実装柔軟性を向上させた。
キーポイント
USTによるスパース性とメモリレイアウトの分離
テンソルの構造的なスパース性(非ゼロ要素の配置パターン)を、物理的なメモリ配置から独立させる設計を採用し、モデル構造の変更に伴うメモリ再構成コストを削減。
nvmath-python APIの提供
Python開発者が複雑な手動メモリ管理やカスタムカーネル実装を行わずとも、標準的なPythonワークフローから高パフォーマンスなスパース演算を直接呼び出せる環境を整備。
開発者体験と実装ワークフローの簡素化
従来のスパースモデル実装で必要だったメモリレイアウトの最適化作業を抽象化し、プロトタイピングから本番デプロイまでの開発サイクルを加速させることを目的としている。
影響分析・編集コメントを表示
影響分析
本機能は、スパースモデルの実装における「構造設計」と「メモリ最適化」の強結合を解消し、開発者の実装負担を大幅に軽減する。これにより、大規模言語モデルや推薦システム分野でのスパース技術の普及が加速し、NVIDIAエコシステム内のPython開発者基盤とハードウェア活用効率がさらに強化される。
編集コメント
開発者の実装負荷を軽減するアーキテクチャ設計は、スパースAIの実用化において極めて重要な一歩である。今後は周辺ライブラリとの連携状況とベンチマーク結果に注目したい。
nvmath-pythonにおけるUniversal Sparse Tensor(UST)でスパースディープラーニングを簡素化する
以前の投稿で、Universal Sparse Tensor(UST)を紹介しました。これにより、開発者はテンソルのスパース性(sparsity)をメモリレイアウトから切り離すことができ、より高い柔軟性とパフォーマンスを実現できます。私たちは、スパース科学計算およびディープラーニングアプリケーションの高速化に向けて、USTをnvmath-python v0.9.0に統合したことを発表できることを嬉しく思います。
本投稿では、USTの主要な機能、実装の詳細、パフォーマンス概要をウォークスルー形式で解説します。主な内容は以下の通りです:
ゼロコスト相互運用性(Zero-cost interoperability):PyTorch、SciPy、CuPyとの間でデータ移動を伴わない変換。
カスタムフォーマット(Custom formats):新たなスパース性スキームを定義可能。
多態演算(Polymorphic operations):スパース性非依存の関数が自動的に最適化されたカーネル(kernels)を使用するか、カスタムなスパースコードを生成します。これにより、新しいフォーマットの手動コーディングが不要になります。
PyTorchインジェクション(PyTorch injection):既存のPyTorchモデルにUSTのパフォーマンスメリットを簡単に組み込み可能。
透過的キャッシング(Transparent caching):JIT/LTOの再コンパイルや計画見直しを回避し、同じ操作の後の繰り返し実行にわたってオーバーヘッドを分散(amortize)します。
テンソルフォーマットDSL
USTは、一般的な(COO、CSR、CSC、BSRなど)および比較的珍しいスパーステンソルストレージフォーマットを、シンプルなドメイン固有言語(domain-specific language, DSL)で記述します。例えば、CSCフォーマットは以下のように記述されます。
(i, j) -> (j : dense, i : compressed) # CSC
このDSLをプログラミング言語に統合するには、さまざまなトレードオフを伴う設計判断が必要です。
例えばC++ライブラリMatXでは、DSLは型とテンプレート化を通じて純粋に実装されており、特定のストレージフォーマットに対して興味深いコンパイル時(compile-time)最適化を可能にします。以下のディスパッチメソッドfoo()では、フォーマット固有のテストがコンパイル時に評価されるため、関連する分岐のみがコンパイルされパッケージ化されます。これによりバイナリサイズは小さくなりますが、コンパイル時間はわずかに長くなります。
template foo(TensorType a) { if constexpr (TensorType::Format::isCOO()) { ... dispatch to COO code ... } else if constexpr (TensorType::Format::isCSR()) { ... dispatch to CSR code ... } else ...}
nvmath-pythonでは、よりPythonらしいDSLの提示方法を採用しており、フォーマットオブジェクトを検査する際の柔軟性を多少犠牲にしてパフォーマンスを確保しています。上記のCSCフォーマットは、例えば以下のように表現されます。
i, j = ( Dimension(dimension_name="i"), Dimension(dimension_name="j") )CSC = TensorFormat( [i, j], {j: LevelFormat.DENSE, i: LevelFormat.COMPRESSED} )
これらのオブジェクトの大きな利点は、すべてをランタイム(runtime)で動的に構築できることです(文字列からのパースを含む)。ただし、フォーマット固有のタスクを実行するには、ランタイムで実際のコンテンツを検査する必要があります。このような判断は一般的にパフォーマンスクリティカルなパスの外側で行われるため、汎用性のために多少のトレードオフを行うのは許容できる選択だと考えられます。
nvmath-pythonの汎用疎テンソル(Universal Sparse Tensor、UST)実装は、PyTorch、SciPy、CuPy、NumPyのテンソルとの相互運用性を提供します。変換はほぼゼロコストであり、COO、CSR、CSC、BSR、BSC、DIAなどの密(dense)または疎(sparse)フォーマットをUSTオブジェクトに変換したり元に戻したりする際に、データ移動やコピーが行われません。代わりに、USTオブジェクトは元のデータ構造のストレージバッファ(storage buffers)を参照します。
例えば、COO形式のSciPy疎行列からnvmath-pythonのUSTオブジェクトへの変換では、行、列、値配列を使用して、USTの位置、座標、値配列を構成します。
row = np.array([0, 0, 1, 1, 2, 3], dtype=np.int32)col = np.array([0, 1, 1, 3, 2, 3], dtype=np.int32)val = np.array([1, 2, 3, 4, 5, 6], dtype=np.float32)coo = sps.coo_array((val, (row, col)), shape=(4, 8)) ust = Tensor.from_package(coo)
この変換は、以下に示すようにCOOのドメイン固有言語(Domain Specific Language、DSL)を自動的に生成します。他のフォーマットを変換すると、それに対応するフォーマットのDSLが生成されます。
TensorFormat( [i, j] -> {i: (, ), j: } )
USTオブジェクトは、以下のように元のパッケージ内の疎テンソルに変換されます。これも構成要素のバッファへの参照を渡すだけのゼロコスト変換です。
coo = ust.to_package()
一般的なフォーマット以外の形式
開発者はDSLを使用して独自の新規な疎フォーマットを定義することもできます。例えば、以下のコードは、PyTorchでネイティブにサポートされていない密なPyTorchテンソルを、2ビットデルタ圧縮フォーマット(2-bit delta-compressed format)に変換します。
A = torch.tensor([[1, 0, 0, 0, 0, 0, 0, 2], [0, 0, 3, 4, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0, 5]], dtype=torch.float64)U = Tensor.from_package(A) delta_format = TensorFormat([i, j], {i: LevelFormat.DENSE, j: (LevelFormat.DELTA, 2)}) S = U.convert(tensor_format=delta_format)
これにより、図1に示すような疎なUSTストレージが生成されます。各座標は直接の列インデックス(column index)ではなく、次の格納要素までの距離を使用します。この距離が3を超えた場合(2ビットで最大値)、パディング(padding)が必要だったことに注意してください(赤色で示された0)。
Figure 1. Tensor in 2-bit delta-compressed format
印刷および描画ユーティリティ
デバッグ、テスト、nvmath-pythonのUST実装への習熟を支援するために、さまざまなユーティリティが提供されています。まず、ダッダーメソッド(dunder method)__repr__ は、テンソルのストレージ内容を印刷するために使用できる曖昧さのない文字列表現(string representation)を提供します。
A = torch.arange(4 * 5).reshape(4, 5).cuda().to_sparse_csr()U = Tensor.from_package(A)print(U)
このスニペット(snippet)は、以下の出力を生成します。これには、値(int64)、USTストレージ内の位置および座標配列(これもint64)に使用される型、デバイス(cuda)、次元およびレベルの範囲(dimension and level extents)、格納データ、ストレージに使用されるバイト数の概要(5*8+19*8+19*8)、および疎性(sparsity)((1 – 19 / 20) x 100%)が表示されます。
---- 疎テンソル形式 (Sparse Tensor Format) : [i, j] -> (i: , j: )
devices : cuda
dim : [4, 5]
lvl : [4, 5]
nse : 19
pos[1] : [0, 4, 9, 14, 19] #5
crd[1] : [1, 2, 3, 4, 0, 1, 2, 3, 4, 0, 1, 2, 3, 4, 0, 1, 2, 3, 4] #19
values : [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19] #19
data : 344 bytes
sparsity : 5.00%
draw_storage()メソッドとdraw()メソッドは、テンソルストレージ(任意の次元数)またはテンソル内容(3次元まで対応)の画像を生成します。前者はFigure 1の生成に使用されました。前述のUST(Universal Sparse Tensor)例に対して後者のメソッドを呼び出すと、Figure 2に示す画像が得られます。ここで灰色は保存された非ゼロ要素(この場合はほぼ全要素)を示しています。
Figure 2. draw()によって生成されたテンソル画像
これらのメソッドは非常に大きなサイズにスケールしないため、比較的小さい例に対してのみUSTを表示します。より大きなテンソルの場合、draw_raw()メソッド(2Dまたは3D)を使用して、Figure 3にSuiteSparseライブラリから取得した行列の例を示すように、テンソルの非ゼロ構造のみを可視化できます。
Figure 3. draw_raw()によって生成されたPARSEC_SiO2_SiO2の非ゼロ構造
ポリモーフィック演算(Polymorphic operations)
USTのテンソル形式DSL(Domain-Specific Language)により、開発者はテンソルの疎性に集中できます。ただし、テンソルのストレージを定義するのは要件の一部に過ぎません。開発者は実行する演算も指定できます。
nvmath-pythonのUST実装により、開発者はmatmul()やsolve()のような疎性非依存演算(sparsity-agnostic operations)を使用して計算を定義できます(テンソルインデックス表記などの代替手段とは異なります)。このアプローチが強力な理由は、システムが演算子の形式を調査して実行パスを決定するためです。つまり、一般的な形式には最適化された手書きのライブラリまたはカーネルにディスパッチし、最適化されたソリューションが存在しない場合は自動疎コード生成(automatic sparse code generation)に依存します。
この設計により、標準形式には既存のライブラリソリューションを使用して高いパフォーマンスを達成しつつ、明示的な手動コーディングなしで新しい疎形式を使用できます。matmul()演算の計画と実行の構文を以下に示します。nvmath-pythonの規約に従い、この演算は計画フェーズ(ディスパッチかJIT LTOコード生成の選択、将来の再利用のためにキャッシュ、および必要なパラメータの設定を決定するフェーズ)と実行フェーズ(実際の演算を実行するフェーズ)に分割できます。
with Matmul(a, b, c, beta=1.0) as mm:
mm.plan()
mm.execute()このアプローチにより、同じmatmul()呼び出しの多数の実行にわたって初期計画コストを均等化できます。これは、例えば反復型疎ソルバー(iterative sparse solvers)やディープラーニングにおける(剪定された)線形層において、非常に効果的です。
PyTorchへのUST注入(UST injection into PyTorch)
PyTorchの疎テンソル(sparse tensor)とnvmath-python間の相互運用性により、開発者はPyTorchモデル内で新たな疎格納スキーム(sparse storage scheme)を実験できます。強力ではあるものの、AI研究者は、はるかに優れたパフォーマンスが得られる場合であっても、線形層(linear layer)内で実行されるGEMM演算(GEMM operation)を上記のUST多態演算(polymorphic operation)に置き換えるためにモデルコードを書き直すことを躊躇します。
USTのnvmath-python実装は、元のモデルコードを書き換えずに既存のモデルにUSTを「注入」する方法を提供します。これは、USTをtorch.Tensorのサブクラスでラップし、USTの多態演算に対するラッパー(wrapper)を提供することで実現されます。一般的なPyTorchテンソル演算(例:dot, mv, mm, linear)のラッパーを提供することで、研究者は内部動作を深く理解することなくUSTを利用できます。
これらのラッパーは、既にあるJIT/LTOルックアップ(JIT/LTO lookup)の上に追加のキャッシュ層(cache layer)を追加し、類似の演算に対して計画されたMatMulインスタンス(MatMul instance)を再利用可能にすることで、その後のランタイムを実行フェーズのみへと削減します。
reformat_model()メソッドも利用可能であり、すべての線形層の重みを反復処理し、ユーザー定義関数によって各重み行列を検査し、必要に応じて剪定(prune)および疎化(sparsify)(またはスキップ)することを可能にします。
weights = torchvision.models.get_model_weights(model_name).DEFAULT
model = torchvision.models.get_model(model_name, weights=weights)
model.to(device)
model.eval()
...
reformat_model(model, func=reformat)
...
with torch.inference_mode():
prediction = model(batch)ユーザー提供の再フォーマットメソッドの一般的な形式を以下に示します。Noneを返すことは、重み行列を変更せずにそのままにする信号です。これにより、変更が適用されたすべての線形層の適用において、推論はUSTを使用するようになります。
def reformat(weight):
... inspect (possibly even prune weight), then decide on storage ...
if (...):
return TorchUST.from_torch(weight.to_sparse_csr())
return Noneパフォーマンスの例(Performance examples)
まず、Matrix Marketから取得した1,489,752 x 1,489,752の行列atmosmodlにおいて、ネイティブにサポートされる形式(COO, CSR, DIA)に対するCuPyの@演算(@-operation)、COOおよびCSRに対するPyTorchのmv()、そしてUSTのmatmul()を用いたSpMV(Sparse Matrix-Vector multiplication)のパフォーマンスを比較します。後者については、繰り返し乗算を行うアプリケーションにとって公平な比較となるよう、execute()のランタイム(runtime)のみを測定します(CuPyもPyTorchもプランニングセットアップ(planning setup)を提供していないため)。得られたランタイムをFigure 4に示します(縦軸は対数スケールを使用)。
Figure 4. SpMVのランタイム(us単位):CuPy/PyTorchとUSTの使用時
このシナリオにおいて、UST(Universal Sparse Tensor)は1.1倍から444倍の高速化を達成しました。CuPyおよびPyTorchのCOO(Coordinate Format)実装の性能が低いことが指摘されています。CSR(Compressed Sparse Row)形式の場合、すべてのバージョンはcuSPARSE実装を利用しており、USTは計画フェーズの再利用から恩恵を受けています。DIA(Diagonal format)形式で見られる高速化は、CuPyがまずCSRへ変換する手法(およびPyTorchにDIA形式が存在しないこと)とは対照的に、USTが専用カーネルを使用していることに起因します。
2番目の実験は、MACKO: Sparse Matrix-Vector Multiplication for Low Sparsityという論文に基づいています。この論文は、ディープラーニングにおける単一トークン推論の重要なステップであるSpMV(Sparse Matrix-Vector Multiplication)演算の効率的な実装を提供しています。著者はMACKO形式を提案しており、これは図1に示されるデルタ圧縮形式(delta-compressed format)本質的なものであり、50%の不規則スパース性(unstructured sparsity)以上において密実装よりも高速化を実現する、印象的なNVIDIA CUDA実装を提供しています。
nvmath-python USTはルックアップメカニズム(lookup mechanism)に新しい手書きカーネルを組み込む簡単な方法を提供しているため、私たちはデルタ圧縮形式のバックエンドカーネル実装(backend kernel implementation)として彼らの実装を使用する実験を行いました。図5は、0%スパース(密)から100%スパース(ゼロ)まで変化する8192×8192の一様ランダムスパース行列に対する、密実装(GEMV)、SPMVのcuSPARSE実装、MACKO UST、および元のMACKO実装の実行時間を比較しています。パディングが各行の末尾で停止するため、MACKO USTはわずかに優れたパフォーマンスを示します(もちろん、元のMACKOも容易にこの最適化を組み込むことができます)。
図5. 行列のスパース度合いに応じた行列-ベクトル演算の実行時間(us単位)
詳しくはこちら
nvmath-python USTの実装についてさらに深く理解するには、nvmath-pythonのオンラインドキュメントを参照してください。
原文を表示
In a previous post, we introduced the Universal Sparse Tensor (UST), enabling developers to decouple a tensor’s sparsity from its memory layout for greater flexibility and performance. We’re excited to announce the integration of the UST into nvmath-python v0.9.0 to accelerate sparse scientific and deep learning applications.
This post provides a walkthrough of key UST features, implementation details, and performance overview, including:
Zero-cost interoperability: Data-movement-free conversion with PyTorch, SciPy, and CuPy.
Custom formats: Define novel sparsity schemes.
Polymorphic operations: Sparsity-agnostic functions automatically use optimized kernels or generate custom sparse code—eliminating the need for manual coding of new formats.
PyTorch injection: Easily inject UST performance benefits into existing PyTorch models.
Transparent caching: Avoid JIT/LTO recompilation and replanning—amortizing overhead over subsequent repeated execution of the same operation.
Tensor format DSL
The UST describes common (e.g., COO, CSR, CSC, and BSR) and less-common sparse tensor storage formats using a simple domain-specific language (DSL). For example, a CSC format is described as follows.
(i, j) -> (j : dense, i : compressed) # CSC
Integrating this DSL within a programming language requires design decisions with various trade-offs.
In the C++ library MatX, for instance, the DSL is purely implemented through types and templating, which enables interesting compile-time optimizations for specific storage formats. In the following dispatching method foo(), for example, format-specific tests can be evaluated at compile-time, which means that only the relevant branch is compiled and shipped, trading smaller binary sizes for slightly higher compile times.
template<TensorType> foo(TensorType a) { if constexpr (TensorType::Format::isCOO()) { ... dispatch to COO code ... } else if constexpr (TensorType::Format::isCSR()) { ... dispatch to CSR code ... } else ...}
In nvmath-python, we adopt a more Pythonic way of presenting the DSL, trading runtime flexibility for some performance when inspecting the format objects. The CSC format shown above, for instance, is expressed as follows.
i, j = ( Dimension(dimension_name="i"), Dimension(dimension_name="j") )CSC = TensorFormat( [i, j], {j: LevelFormat.DENSE, i: LevelFormat.COMPRESSED} )
A major advantage of these objects is that everything can be constructed dynamically at runtime (including parsing from strings). Performing format-specific tasks, however, requires inspecting the actual contents at runtime. Since such decisions generally happen outside performance-critical paths, trading off for generality seems an acceptable choice.
Interoperability with PyTorch, SciPy, CuPy
The nvmath-python UST implementation provides interoperability with tensors of PyTorch, SciPy, CuPy, and NumPy. The conversion is mostly zero-cost, which means that converting dense or sparse formats like COO, CSR, CSC, BSR, BSC, and DIA to a UST object or back is done without data movement or copying. Instead, the UST object references the storage buffers of the original data structure.
For example, the following conversion from a SciPy sparse matrix in COO format to an nvmath-python UST object uses the row, column, and values array to form the positions, coordinates, and values array of the UST.
row = np.array([0, 0, 1, 1, 2, 3], dtype=np.int32)col = np.array([0, 1, 1, 3, 2, 3], dtype=np.int32)val = np.array([1, 2, 3, 4, 5, 6], dtype=np.float32)coo = sps.coo_array((val, (row, col)), shape=(4, 8)) ust = Tensor.from_package(coo)
This conversion automatically generates the DSL for COO, illustrated below. Converting other formats will result in the corresponding DSL for those formats.
TensorFormat( [i, j] -> {i: (<LevelFormat.COMPRESSED>, <LevelProperty.NONUNIQUE>), j: <LevelFormat.SINGLETON>} )
The UST object is converted back to a sparse tensor in the originating package as follows. This too is a zero-cost conversion by simply passing references to constituent buffers.
coo = ust.to_package()
Less common formats
Developers can also define their own novel sparsity format using the DSL. For example, the following code converts a dense PyTorch tensor into a 2-bit delta-compressed format (similar to CSR, but using runtime differences for the coordinates), not natively supported in PyTorch.
A = torch.tensor([[1, 0, 0, 0, 0, 0, 0, 2], [0, 0, 3, 4, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0, 5]], dtype=torch.float64)U = Tensor.from_package(A) delta_format = TensorFormat([i, j], {i: LevelFormat.DENSE, j: (LevelFormat.DELTA, 2)}) S = U.convert(tensor_format=delta_format)
This yields the sparse UST storage shown in Figure 1, where each coordinate uses a distance to the next stored element rather than a direct column index. Note that padding was required (the zeros shown in red) for cases where that distance would exceed 3 (maximum using 2 bits).
Figure 1. Tensor in 2-bit delta-compressed format
Printing and drawing utilities
Various utilities are provided to help users debug, test, and become familiar with the nvmath-python UST implementation. First, the dunder method __repr__ provides an unambiguous string representation that can be used to print tensor storage contents.
A = torch.arange(4 * 5).reshape(4, 5).cuda().to_sparse_csr()U = Tensor.from_package(A)print(U)
This snippet yields the following output, which shows the types used for the values (int64), positions and coordinate arrays in the UST storage (also int64), the device (cuda), the dimension and level extents, the stored data, a summary of the number of bytes used for storage (5*8+19*8+19*8), and the sparsity ((1 – 19 / 20) x 100%).
---- Sparse Tensor<VAL=int64,POS=int64,CRD=int64,DIM=2,LVL=2>format : [i, j] -> (i: <LevelFormat.DENSE>, j: <LevelFormat.COMPRESSED>)device : cudadim : [4, 5]lvl : [4, 5]nse : 19pos[1] : [0, 4, 9, 14, 19] #5crd[1] : [1, 2, 3, 4, 0, 1, 2, 3, 4, 0, 1, 2, 3, 4, 0, 1, 2, 3, 4] #19values : [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19] #19data : 344 bytessparsity : 5.00%----
The methods draw_storage() and draw() generate an image of the tensor storage (any dimensionality) or tensor contents (supported up to 3D). The former method was used to generate Figure 1. Calling the latter method on the previous UST example yields the image shown in Figure 2, where the grey color denotes stored nonzero elements (almost all elements in this case).
Figure 2. Tensor image generated by draw()
Both methods show the UST for relatively small examples since they don’t scale to very large sizes. For larger tensors, the draw_raw() method can be used (2D or 3D) to get a visualization of just the nonzero structure of the tensor, as illustrated in Figure 3 for a matrix taken from the SuiteSparse library.
Figure 3. Nonzero structure of PARSEC_SiO2_SiO2 generated by draw_raw()
Polymorphic operations
The tensor format DSL of the UST enables developers to focus on tensor sparsity. Defining the tensors’ storage is only part of the requirement, however. Developers can also specify the operations to be performed.
The nvmath-python UST implementation enables developers to define computations using sparsity-agnostic operations like matmul() and solve() (unlike alternatives such as tensor index notation). This approach is powerful because the system inspects the operand formats to determine the execution path: either dispatching to an optimized, handwritten library or kernel for common formats or relying on automatic sparse code generation when no optimized solution exists.
This design enables the use of novel sparse formats without explicit manual coding, while still achieving high performance by using existing library solutions for standard formats. The syntax for planning and performing a matmul() operation is shown below. Following nvmath-python conventions, the operation can be split into a planning phase (deciding between dispatch or JIT LTO code generation, cached for future re-use, as well as setting up all required parameters) and an execution phase (performing the actual operation).
with Matmul(a, b, c, beta=1.0) as mm: mm.plan() mm.execute()
This approach amortizes the initial planning cost over many executions of the same matmul() invocation, which really pays off in, for instance, iterative sparse solvers, or (pruned) linear layers in deep learning.
UST injection into PyTorch
The interoperability between PyTorch sparse tensors and nvmath-python enables developers to experiment with novel sparse storage schemes inside PyTorch models. Although powerful, AI researchers are reluctant to rewrite their model code to replace GEMM operations performed inside linear layers into the UST polymorphic operations shown above, even if this yields much better performance.
The nvmath-python implementation of the UST provides a way to “inject” the UST into existing models without rewriting the original model code. This is achieved by wrapping the UST into a subclass of torch.Tensor and providing wrappers to polymorphic operations of the UST. By providing wrappers for common PyTorch tensor operations (e.g., dot, mv, mm, and linear), researchers can use the UST without learning much of the internal workings.
These wrappers also add another caching layer (on top of the already cached JIT/LTO lookup) for reuse of planned MatMul instances for similar operations, reducing subsequent runtime to just the execution phase.
A reformat_model() method is also available, iterating through the weights of all linear layers and enabling a user-defined function to inspect, potentially prune, and sparsify (or skip) each weight matrix
weights = torchvision.models.get_model_weights(model_name).DEFAULTmodel = torchvision.models.get_model(model_name, weights=weights)model.to(device)model.eval()...reformat_model(model, func=reformat)... with torch.inference_mode(): prediction = model(batch)
The general form of the user-provided reformatting method is shown below. Returning None signals leaving the weight matrix unchanged. After this, inference will use the UST for all linear layer applications that have been modified.
def reformat(weight): ... inspect (possibly even prune weight), then decide on storage ... if (...) return TorchUST.from_torch(weight.to_sparse_csr()) return None}
Performance examples
First, we compare SpMV performance using CuPy’s @-operation across natively supported formats (COO, CSR, and DIA), along with PyTorch mv() for COO and CSR against UST’s matmul() on the 1,489,752 x 1,48,9752 matrix atmosmodl from the Matrix Market. For the latter, we measure the execute() runtime only, which is a fair comparison for applications with repeated multiplication (since neither CuPy nor PyTorch provides a planning setup). The resulting runtimes are shown in Figure 4 (using a logarithmic scale for the vertical axis).
Figure 4. Runtime (in us) of SpMV using CuPy/PyTorch and UST
UST achieved speedups ranging from 1.1 to 444 in this scenario. The poor performance of the CuPy and PyTorch COO implementation is noted. For the CSR format, all versions utilize the cuSPARSE implementation, with UST benefiting from reusing the planning phase. The speedup seen with the DIA format is due to UST’s use of a specialized kernel, in contrast to CuPy’s method of first converting to CSR (and PyTorch’s lack of DIA).
The second experiment is based on the publication MACKO: Sparse Matrix-Vector Multiplication for Low Sparsity, which provides an efficient implementation of the SpMV operation, an important step for single-token inference in deep learning. The authors introduce the MACKO format, which is essentially the delta-compressed format of Figure 1, and provide an impressive NVIDIA CUDA implementation that already delivers speedups over dense implementations for 50% unstructured sparsity and up.
Since nvmath-python UST provides an easy way to incorporate new hand-written kernels in the lookup mechanism, we experimented with using their implementation as a backend kernel implementation for the delta compressed format. Figure 5 compares the runtime of a dense implementation (GEMV), the cuSPARSE implementation of SPMV, the MACKO UST, and original MACKO implementation for 8192×8192 uniform random sparse matrices, varying from 0% sparse (dense) to 100% sparse (zero). MACKO UST performs slightly better since padding stops at the end of each row (an optimization that the original MACKO could easily incorporate as well, of course).
Figure 5. Runtime (in us) of matrix-vector for varying sparsities of the matrix
Learn more
For a deeper understanding of the nvmath-python UST implementation, please refer to the nvmath-python online documentation.
関連記事
NVIDIA Nemotron 3 Ultra が長時間実行型エージェントの推論を高速化・効率化
NVIDIA は、長時間実行型エージェントが推論を行い、文脈を維持し、ツールを活用して効率的に動作するための新モデル「Nemotron 3 Ultra」を発表した。これにより、単発チャットボットから複雑なタスクをこなすエージェントへの進化が加速する。
マイクロソフトと NVIDIA の新ツールを用いて Windows PC でパーソナル AI エージェントを構築する
マイクロソフトと NVIDIA は、Windows PC 上でパーソナル AI エージェントを構築するための新ツールを提供した。これにより開発者はローカル環境で効率的にエージェントを設計・実装できる。
Hermes エージェントと NVIDIA NemoClaw を活用し、より高速かつ安全な研究のために自己進化型エージェントをデプロイする
NVIDIA は、データ合成や意思決定支援に強力なツールである AI エージェントの課題解決のため、Hermes エージェントと自社の NemoClaw プラットフォームを組み合わせた手法を発表した。これにより、研究プロセスの高速化とセキュリティ強化を実現できる。