PyTorch カスタム演算

2026/06/02 0:39

PyTorch カスタム演算

RSS: https://news.ycombinator.com/rss

要約

Japanese Translation:

開発者は、PyTorch の新しい

torch.export
機能を利用することで、カスタム C++ および CUDA 演算を含む複雑な AI モデルをスタンドアロンのバイナリとしてシームレスに展開できるようになりました。カスタム関数は CPU/CUDA ディスパッチをサポートするために
TORCH_LIBRARY_IMPL
に登録され、カスタムクラスは
forward()
メソッドを有し、
torch::CustomClassHolder
で定義され、
TORCH_LIBRARY
を介して登録されます。実装コードは共有ライブラリ(例:
libidentity_conv_ops.so
)にコンパイルされ、Python では
torch.ops.load_library
を使用してロードされます。
torch.compile
および
torch.export
との互換性を確保するためには、ネイティブ C++/CUDA コードを実行せずにシンボルトレーシングを可能にするために、
@register_fake_class
および
@torch.library.register_fake
を使用して抽象的な「偽」バージョンを登録する必要があります。代表例となる 4 レヤーの
IdentityModel
では、ネイティブ演算子と共に C++/CUDA カスタムクラス (
IdentityConvCustomClass
) および C++/CUDA カスタムオペレータ (
identity_conv_op
) が共存します。エクスポートされた際、モデルは AOTInductor パッケージ(
.pt2
)を生成し、
torch._inductor.aoti_compile_and_package
を使用してコンパイルおよびパッケージ化できます。Python 推論では
.pt2
パッケージを直接ロードし、必要に応じて
IDENTITY_CONV_OPS_LIB
環境変数を指定してカスタムオペレータライブラリのパスを指定することができます。C++ 推論では
torch::inductor::AOTIModelPackageLoader
を使用してモデルを読み込み、共有ライブラリは
dlopen
を使用してロードし、元の Python トレーニングコードの変更なく、断片化されたパイプラインの管理を行わずに、入出力間のビット単位同一の振る舞いを達成します。

本文

PyTorch カスタムオペレーションと AOTInductor で動作させる方法

2026 年 5 月 10 日に公開された記事で、C++ と CUDA で実装した PyTorch カスタムオペレーション(関数)およびカスタムクラスの具体的な実装例を踏まえ、AOTInductor コンパイルと推論プログラムでの利用方法を紹介する。

PyTorch のカスタムオペレーションとは、

C++
および
CUDA
で実装されたクラスまたは関数を指し、Python および C++ の両方の推論環境で使用可能です。

概要:恒等畳み込みの実装例

本記事は、単純な**「恒等演算(input == output)」による畳み込み**を実際に作成し、以下の手順で扱うことを示します。

  • C++ と CUDA で実装するカスタムオペレーションの構築
  • 登録・ロード処理を通じた PyTorch モデルへの統合
  • torch.compile
    および
    torch.export
    との完全な互換性確保
  • Python および純粋な C++ 推論プログラムでの実行

1. PyTorch カスタム関数の実装(C++ / CUDA)

カスタム関数はステートレスであり、パラメータを持たせません。CPU および CUDA の両方の実装を提供し、

TORCH_LIBRARY_IMPL
マクロを使用して登録します。PyTorch は入力テンソルのデバイスに基づいて自動的に処理を振り分けます。

実装コード (
custom_ops.cpp
)

以下のコードは、GPU/ CPU で動作する「恒等演算」のロジックを定義しています。

// custom_ops.cpp
#include <torch/extension.h>
#include <cuda_runtime.h>

namespace {

// CUDA カーネル:並列処理で要素ごとのコピーを実行
template<typename scalar_t>
__global__ void identity_kernel(const scalar_t* input, scalar_t* output,
                                const int64_t* shape_dev, const int64_t* strides_dev,
                                int ndim, int numel) {
    int idx = blockIdx.x * blockDim.x + threadIdx.x;
    if (idx < numel) {
        // 恒等演算:入力値をそのまま出力にコピー
        output[idx] = input[idx];
    }
}

// CPU 用実装:テンソルをクローン
torch::Tensor identity_conv_cpu_impl(const torch::Tensor& input){
    TORCH_CHECK(!input.is_cuda(), "identity_conv_cpu_impl: input must be a CPU tensor");
    return input.clone();
}

// CUDA 用実装
torch::Tensor identity_conv_cuda_impl(const torch::Tensor& input){
    TORCH_CHECK(input.is_cuda(), "identity_conv_cuda_impl: input must be a CUDA tensor");
    
    auto output = torch::empty_like(input);
    const int64_t numel = input.numel();
    if (numel == 0) return output;
        
    const int ndim = input.dim();
    // 形状とストライドをデバイス上へコピー(カーネル引数用)
    const auto opts = torch::TensorOptions().dtype(torch::kInt64).device(input.device());
    const auto shape_dev = torch::tensor(
        std::vector<int64_t>(input.sizes().begin(), input.sizes().end()), opts);
    const auto strides_dev = torch::tensor(
        std::vector<int64_t>(input.strides().begin(), input.strides().end()), opts);
    
    constexpr int kThreads = 256;
    const int blocks = static_cast<int>((numel + kThreads - 1) / kThreads);
    
    AT_DISPATCH_FLOATING_TYPES_AND2(
        at::ScalarType::Half, at::ScalarType::BFloat16, input.scalar_type(),
        "identity_conv_cuda_impl",
        [&]() {
            identity_kernel<scalar_t><<<blocks, kThreads>>>(
                input.data_ptr<scalar_t>(), output.data_ptr<scalar_t>(),
                shape_dev.data_ptr<int64_t>(), strides_dev.data_ptr<int64_t>(),
                ndim, numel);
        });
    
    C10_CUDA_KERNEL_LAUNCH_CHECK();
    return output;
}

// 登録処理:デバイス種別ごとにマクロを使用
TORCH_LIBRARY_IMPL(my_ops, CUDA, m){
    m.impl("identity_conv_op", identity_conv_cuda_impl);
}

TORCH_LIBRARY_IMPL(my_ops, CPU, m){
    m.impl("identity_conv_op", identity_conv_cpu_impl);
}

} // namespace

2. PyTorch カスタムクラスの実装(C++)

パラメータや

forward()
メソッドを持つカスタムクラスは、ステートレスな関数とは異なります。以下の手順で実装します。

アプローチ

  • C++ で
    torch::CustomClassHolder
    を継承してクラスを定義
  • TORCH_LIBRARY
    マクロを使用して登録
  • Python からの呼び出し(PyTorch グラフ)に対応させる

実装コード (
custom_class.cpp
)

// custom_class.cpp
#include <torch/extension.h>

struct IdentityConvClass : torch::CustomClassHolder{
    int64_t channels_;
    // コンストラクタ:パラメータを保持
    explicit IdentityConvClass(int64_t channels) : channels_(channels) {}
    
    // フォワード処理:デバイス検知付き
    torch::Tensor forward(const torch::Tensor& x) {
        return x.is_cuda() ? identity_conv_cuda_impl(x)
                           : identity_conv_cpu_impl(x);
    }
    
    int64_t get_channels() const { return channels_; }
};

// custom_class_registration.cpp
#include <torch/extension.h>
#include <c10/core/TensorImpl.h>

TORCH_LIBRARY(my_ops, m){
            // クラスの登録
            m.class_<IdentityConvClass>("IdentityConvClass")
        .def(torch::init<int64_t>()) // コンストラクタバインディング
        .def("forward", &IdentityConvClass::forward) // メソッドバインディング
        .def("get_channels", &IdentityConvClass::get_channels)
        .def("__obj_flatten__", [](const c10::intrusive_ptr<IdentityConvClass>& self){
                 return std::make_tuple(std::make_tuple("channels", self->channels_));
             })
        // Pickle シリアライザの登録(保存・復元用)
        .def_pickle(
            [](const c10::intrusive_ptr<IdentityConvClass>& self) -> int64_t { return self->channels_; },
            [](int64_t channels) -> c10::intrusive_ptr<IdentityConvClass> 
            { return c10::make_intrusive<IdentityConvClass>(channels); });
        
        // シンプルな関数のバインディングも定義可能
        m.def("identity_conv_op(Tensor x) -> Tensor");
}

3. Python 環境でのセットアップとファイク登録

C++ 側をビルドし、共有ライブラリ(

libidentity_conv_ops.so
)を作成した後、Python でロードします。特に重要なのは、
torch.export
および
torch.compile
との互換性
のため、「疑似的」なバージョン(Abstract/ Fake version)を登録する手順です。

共有ライブラリのロードとファイク実装 (
custom_ops.py
)

# custom_ops.py
import os
import torch
from torch.library import default_lib

# 共有ライブラリのパスを設定
_default_lib = os.path.join(
    os.path.dirname(os.path.abspath(__file__)), "..", "ext",
    "libidentity_conv_ops.so")
_lib_path = os.path.abspath(
    os.environ.get("IDENTITY_CONV_OPS_LIB", _default_lib))

# ライブラリの読み込み
torch.ops.load_library(_lib_path)

# 1. カスタムクラスのファイク登録
# torch.export がこのクラスをグラフとして扱うために必須
from torch._library.fake_class_registry import register_fake_class

@register_fake_class("my_ops::IdentityConvClass")
class FakeIdentityConvClass:
    """torch.export 中の抽象的な対応物"""
    def __init__(self, channels: int) -> None:
        self.channels_ = channels
    
    def __obj_flatten__(self):
        return (("channels", self.channels_), )
    
    @classmethod
    def __obj_unflatten__(cls, flat):
        return cls(dict(flat)["channels"])
    
    def forward(self, x: torch.Tensor) -> torch.Tensor:
        # 抽象実装:出力サイズは入力と同じ
        return torch.empty_like(x)
    
    def get_channels(self) -> int:
        return self.channels_

# 2. カスタム関数のファイク登録
@torch.library.register_fake("my_ops::identity_conv_op")
def _identity_conv_op_fake(x: torch.Tensor) -> torch.Tensor:
    """torch.export/FakeTensor 追跡用抽象実装"""
    return torch.empty_like(x)

identity_conv_op = torch.ops.my_ops.identity_conv_op

4. PyTorch モデルの構築(
IdentityModel

C++/CUDA を介したカスタムクラス・関数と、ネイティブ PyTorch オペレーターのミックスでモデルを作成します。

モデル定義 (
model.py
)

# model.py
import torch
import torch.nn as nn
from custom_ops import identity_conv_op

class IdentityConv(nn.Module):
    """ネイトヴ PyTorch で実装(kernel_size=1)"""
    def __init__(self, channels: int) -> None:
        super().__init__()
        self.conv = nn.Conv2d(channels, channels, kernel_size=(1, 1), stride=(1, 1), 
                              padding=(0, 0), bias=False)
        self.conv.weight.data = torch.ones(channels, 1, 1, 1)
        self.conv.weight.requires_grad = False
        
    def forward(self, x: torch.Tensor) -> torch.Tensor:
        return self.conv(x)

class IdentityConvCustomClass(nn.Module):
    """torch.classes で登録されたカスタムクラスを使用"""
    def __init__(self, channels: int) -> None:
        super().__init__()
        self.obj = torch.classes.my_ops.IdentityConvClass(channels)
        
    def forward(self, x: torch.Tensor) -> torch.Tensor:
        return self.obj.forward(x)

class IdentityConvCustomOp(nn.Module):
    """torch.library.custom_op で登録された関数を使用"""
    def __init__(self, channels: int) -> None:
        super().__init__()
        self.channels = channels
        
    def forward(self, x: torch.Tensor) -> torch.Tensor:
        return identity_conv_op(x)

class IdentityModel(nn.Module):
    """AOTInductor デモ用:4 レイヤ構成"""
    def __init__(self, channels: int) -> None:
        super().__init__()
        self.layer1 = IdentityConv(channels)        # ネイティブ
        self.layer2 = IdentityConvCustomClass(channels) # カスタムクラス
        self.layer3 = IdentityConvCustomOp(channels)  # カスタム関数
        self.layer4 = IdentityConv(channels)        # ネイティブ
        
    def forward(self, x: torch.Tensor) -> torch.Tensor:
        x = self.layer1(x)
        x = self.layer2(x)
        x = self.layer3(x)
        x = self.layer4(x)
        return x

def create_model(channels: int = 3) -> IdentityModel:
    return IdentityModel(channels=channels).cuda().eval()

5.
torch.export
と AOTInductor コンパイル

PyTorch モデルをエクスポートし、AOTInductor でコンパイルしてパッケージ化(

.pt2
)します。このステップでは、前述の「ファイク登録」が正しく実行されていることが重要です。

エクスポートとコンパイル (
export_compile.py
)

# export_compile.py
import sys
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))

import torch
from model import create_model

CHANNELS = 3
BATCH_SIZE = 1
HEIGHT = 224
WIDTH = 224
PACKAGE_PATH = "./artifacts/model.pt2"

def main() -> None:
    model = create_model(channels=CHANNELS)
    
    # [入力生成]
    x = torch.randn(BATCH_SIZE, CHANNELS, HEIGHT, WIDTH, device="cuda", dtype=torch.float32)
    
    # [動作確認:Eager モード]
    with torch.no_grad(): out = model(x)
    assert torch.equal(x, out), "Eager verification FAILED"

    # [エクスポート: torch.export]
    with torch.no_grad():
        exported_program = torch.export.export(model, (x,))
    
    print("Exported graph:")
    # 出力グラフのサマリー(確認用)
    # %call_torchbind : IdentityConvClass の呼び出しを表す
    # %identity_conv_op : Custom Op の呼び出しを表す

    # [コンパイル: AOTInductor]
    torch._inductor.aoti_compile_and_package(exported_program, package_path=PACKAGE_PATH)
    print("Compilation DONE")

if __name__ == "__main__": main()

注意: エクスポートされたグラフを確認すると、カスタムクラスは

torch.ops.higher_order.call_torchbind
、カスタム関数は
torch.ops.my_ops.identity_conv_op.default
として表記されます。


6. 推論の実行

コンパイルされたパッケージ(

.pt2
)を読み込んで推論を行います。PyTorch および 純粋な C++ 環境の両方で動作確認が可能です。

A. Python 推論 (
run_inference.py
)

# run_inference.py
import sys, os
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))

import torch
from torch._inductor import codecache

PACKAGE_PATH = sys.argv[1] if len(sys.argv) > 1 else "./artifacts/model.pt2"
OP_LIB_PATH = sys.argv[2] if len(sys.argv) > 2 else None

if OP_LIB_PATH is not None:
    os.environ["IDENTITY_CONV_OPS_LIB"] = OP_LIB_PATH

compiled_model = torch._inductor.aoti_load_package(PACKAGE_PATH)
x = torch.randn(1, 3, 224, 224, device="cuda", dtype=torch.float32)
with torch.no_grad(): output = compiled_model(x)
assert torch.equal(x, output[0]), "Bitwise mismatch!"

print("SUCCESS! Python inference verified.")

B. C++ 推論 (
run_inference.cpp
)

純粋な C++ 環境(pybind11 不要)でも動作します。

dlopen
を使用して共有ライブラリを動的にロードし、AOTInductor のローダを使用してモデルパッケージを開きます。

// run_inference.cpp
#include <cstdlib>
#include <iostream>
#include <stdexcept>
#include <dlfcn.h> 
#include <torch/torch.h>
#include <torch/csrc/inductor/aoti_package/model_package_loader.h>

static constexpr int64_t kBatchSize = 1;
static constexpr int64_t kChannels = 3;
static constexpr int64_t kHeight = 224;
static constexpr int64_t kWidth = 224;

int main(int argc, char* argv[]){
    if (argc < 3) {
        std::cerr << "Usage: ./run_inference <model.pt2> <libidentity_conv_ops.so>" << std::endl;
        return EXIT_FAILURE;
    }

    void* lib_handle = dlopen(argv[2], RTLD_NOW | RTLD_GLOBAL);
    if (!lib_handle) { throw std::runtime_error(std::string("dlopen failed: ") + dlerror()); }

    torch::inductor::AOTIModelPackageLoader loader(argv[1]);
    auto runner = loader.get_runner();

    auto options = torch::TensorOptions().dtype(torch::kFloat32).device(torch::kCUDA, 0);
    auto input = torch::randn({kBatchSize, kChannels, kHeight, kWidth}, options);

    std::vector<at::Tensor> inputs = {input};
    auto outputs = runner->run(inputs);
    const auto& output = outputs[0];

    bool passed = input.equal(output);
    
    // リソース解放
    dlclose(lib_handle);

    if (passed) std::cout << "SUCCESS! C++ inference verified." << std::endl;
    else { std::cerr << "Verification FAILED" << std::endl; return EXIT_FAILURE; }

    return EXIT_SUCCESS;
}

同じ日のほかのニュース

一覧に戻る →

2026/06/07 4:17

Ntsc-rs ~アナログテレビおよびVHS のアーティファクトを模倣するオープンソースのビデオエミュレーター~

## 日本語翻訳: ntsc-rs は、単純なカラーフィルターではなく、実際の送信符号化の原理に基づいた高度なアルゴリズムを使用して、ヴィンテージ NTSC テレビと VHS テープのアートファクトを本物らしく再現する無料のオープンソースツールです。マルチスレッドおよび SIMD 加速を実現した Rust で構築されており、超標準解像度でもリアルタイムで動画を処理し、ntscQT といった旧来のツールよりも優れたパフォーマンスを発揮します。そのアルゴリズムは composite-video-simulator、zhuker/ntsc、および ntscQT で開発されたものを採用しています。独立したアプリケーション、ウェブプラットフォーム、または Adobe After Effects、Premiere Pro、DaVinci Resolve、Hitfilm、Vegas(すべての OpenFX ソフトウェアと互換性あり)用のプラグインとして利用可能であり、歴史的正确性と現代の効率性を兼ね備えており、クリエイターがリアルなアナログの美学を現代的なワークフローに直接統合できるよう支援します。

2026/06/07 3:35

Meta、AI チャットボットの悪用によるInstagramアカウントの乗っ取りが数千件あったと確認

## Japanese Translation: Metaは、ハッカーがAIチャットボットのバグを悪用し、Instagramのアカウント20,000個以上を乗っ取ったという重大なセキュリティ不備を公表しました。このシステムは、パスワードリセット用として設計されたものの、誤って認証コードを受け取るメールアドレスを登録済みのアカウント所有者に限定せず、任意のメールアドレスに入力した者に送信してしまっていました。これにより、無許可ユーザーが数日以内にして完全なアクセス権を引き渡すことができました。脆弱性は二段階認証を行っていないアカウントを狙い、4月中旬から最近まで活性していました。 この侵害事件は、人工知能(AI)に対する大幅な投資が行われている時期に発生しており、人員削減を踏まえ、イノベーションとセキュリティのバランスに関する懸念が浮上しています。Metaはこの乗っ取られたボットを停止し、コードを修正済みですが、同社は直接メッセージやプロフィール情報など機密データの暴露があったことを認めています。メイン州在住の30人が直接通知を受けたものの、同社は犯罪者がアクセスした個人データ総量について現在把握していないと確認しています。専門家は、この事件がAIの急速な展開に伴う重大なリスクを浮き彫りにしているとし、ユーザーに対して即時にパスワードのリセットと、認証済みチャネル経由での再認証を行ってアカウントのセキュリティを回復するよう求めており、またMetaは同様の再発を防ぐため、現在自社プラットフォーム上の他のチャットボットの調査を進めています。

2026/06/06 23:59

Zeroserve:eBPF を用いてスクリプト可能なゼロ設定 Web サーバー

## 日本語訳: ゼロサーブ(Zeroserve)は、最小限のセットアップで静的ファイルを配信できる軽量かつ高性能な HTTPS サーバーです。各サイトごとに単一のタールアーカイブのみを用いて動作を開始できます。改行・認証・レート制限・リバースプロキシなどの機能を実装するサンドボックス化されたミドルウェアとしてユーザー空間で動作する eBPF プログラムをサポートしています。パフォーマンスの主な特徴は、io_uring I/O インターフェースと即時コンパイル(Just-In-Time コンパイル)された eBPF スクリプトによる卓越したシングルスレッド速度です。Ryzen 7 3700X ベンチマークでは、小規模ファイルにおいて nginx を約 17% 上回り、Caddy よりも大幅に高性能であることが示されました。そのアーキテクチャはバイナリコードを共有する複数プロセスの並列実行によりスケールし、各インスタンスはシングルスレッドモデルの monoio ランタイムを使用します。BoringSSL を用いた高度な TLS 機能もサポートしており、TLS 1.3、暗号化された ClientHello(ECH)、SNI サーティフィケート選択、JA4 フィンガープリント、および ECH リレーモードを含みます。運用上の利点として、原子スワップ(SIGHUP)によるホットリロードやダウンタイムなしの瞬時の構成更新が可能です。eBPF プログラミングモデルは、ヘッダー操作やテンプレート置換を行うために共有されるパーリクエストメタデータマップを持つ、ファイル名順ソートされたスクリプトチェーンを使用します。ヘルパー関数はリクエスト検査・変異、暗号処理(SHA-256、HMAC、base64)、JSON 処理、トークンバケット方式のレート制限、AWS SigV4、および XChaCha20-Poly1305 クッキーを用いた OIDC ログインをカバーします。デフォルト設定では eBPF インスタンスあたりのメモリ使用量の上限が 256 KB で、他の接続を止めることを防ぐためにプリエンプト間隔は 2 ms に設定されています。この間隔を増やすことで動的レスポンスのスループットをさらに向上させることが可能です。リバースプロキシとしての性能では、プーリングされた io_uring コネクションを用いて小規模応答において最先端の速度を発揮し、大規模ボディにおいては nginx と競合するレベルのパフォーマンスを示します。全体として、このサーバーは Lua や Perl などの遅いインタプリターに依存せず、ユーザー空間内で直接低レイテンシーとより細かいセキュリティ制御を提供します。

PyTorch カスタム演算 | そっか~ニュース