こんにちは、イノベーションセンターの鈴ヶ嶺です。 本記事では、 NVIDIA Dynamo や vLLM などの LLM 推論フレームワーク向けに設計された高速・低遅延の抽象化転送ライブラリである NVIDIA Inference Xfer Library (NIXL) について解説します。 また、NVIDIA Dynamo に関してはこちらで解説していますので参考にしていただけると幸いです。 engineers.ntt.com まず、LLM 推論高速化(KV Cache)におけるメモリ転送の背景と課題をご紹介し、それを解決する NIXL の概要を説明します。 NIXL は Plugin により任意の転送方式を実装可能なアーキテクチャとなっています。実際に Custom Plugin を実装する方法についても紹介します。 背景と課題 NVIDIA Inference Xfer Library (NIXL) GPUDirect RDMA による VRAM to VRAM のデータ転送 GPUDirect Storage による VRAM to FILE のデータ転送 Custom Plugin の実装方法について まとめ 背景と課題 LLM の推論高速化は、コスト削減や低遅延な応答によるユーザビリティ向上といったニーズから、さまざまな改良が進められています。中でも「KV Cache」は、過去トークンに対する計算済みのキー・バリュー行列を保持し、次のトークン生成時に再計算を省略することで、推論速度を大幅に向上させる重要な技術です。 1 一方で、KV Cache が保持する状態はシーケンス長に比例して増加するため、メモリ消費も増大します。また、このキャッシュを複数の GPU やノード間で共有する際には、低遅延で転送できる仕組みが求められます。さらに、キャッシュの転送に使用するメモリやストレージの種類によって、最適なプロトコル( NVLink 、 GPUDirect Storage/RDMA など)が異なり、実装の複雑さが課題となります。 これらの課題を解決するため、多彩なメモリ・ストレージ、通信プロトコルを抽象化し、高速・低遅延な転送を可能とするライブラリが求められています。 NVIDIA Inference Xfer Library (NIXL) NVIDIA Inference Xfer Library(NIXL) は、LLM 推論フレームワーク向けに設計された高速・低遅延の転送ライブラリです。特徴として、VRAM や DRAM, FILE, Block, Object Storage など異種のメモリ・ストレージを統一的に抽象化する API を提供し、UCX(Unified Communication X) や GPUDirect Storage といった複数のバックエンドプラグインを動的に選択して最適な通信経路を自動的に構築します(下図参照)。通信方式は Plugin 形式で任意に拡張可能なため、独自のキャッシュシステムを構築可能です。 引用: https://github.com/ai-dynamo/nixl/blob/main/docs/nixl.md#overview 2025 年 5 月時点の対応 Plugin は以下になります。 cuda_gds DMA(Direct Memory Access)により、GPU Memory と Storage 間を高速転送する GPUDirect Storage(GDS)を使用するバックエンド NVMe SSD, NVMe-oF, NFS over RDMA, 分散ファイルシステム(DDN EXAScaler, VAST NFS, WekaFS)などで利用可能 mooncake LLM Serving platform の Kimi で利用される KV Cache System の Mooncake を使用するバックエンド posix libaio や liburing を使用した POSIX 準拠の I/O 処理をするバックエンド ucx 高帯域・低遅延通信の抽象化ライブラリである UCX を使用するバックエンド デフォルトではこの Plugin が設定されます ucx_mo UCX v1.18 では 1 つの UCX Context で複数 GPU をサポートしていないため、 Multi-Object (MO) UCX は GPU ごとに異なる UCX Worker を関連づける実装に改良している 2 NIXL はさまざまな LLM 推論フレームワークへの採用が進んでいます。次の図のように vLLM のサポートが 2025 年 4 月 11 日にアナウンスされました。 Shaping NIXL-based PD Disaggregation in vLLM V1 また、 SGLang でも以下のように NIXL 対応の PR がマージされました。 [PD] Add NIXL transfer backend #5477 NIXL の通信過程は次のようになります。 各ノードのエージェント初期化 VRAM, DRAM の登録 転送のためのメタデータの交換 データ転送 引用: https://github.com/ai-dynamo/nixl/blob/main/docs/nixl.md#example-procedure 次の章で実際に NIXL を実行します。 GPUDirect RDMA による VRAM to VRAM のデータ転送 ここでは NIXL の example を動作させてノード間で GPU メモリを UCX による GPUDirect RDMA を用いて転送します。NIXL は prebuild のものを pip install nixl でインストールできますが、今回は理解やカスタマイズのために自前で build したものを使用します。 事前に cuda や gdrcopy についてはインストールしてください。 まず初めに、UCX をインストールします。 wget https://github.com/openucx/ucx/releases/download/v1. 18 . 0 /ucx-1. 18 . 0 .tar.gz tar xzf ucx-1. 18 . 0 .tar.gz cd ucx-1. 18 . 0 ./configure \ --enable-shared \ --disable-static \ --disable-doxygen-doc \ --enable-optimizations \ --enable-cma \ --enable-devel-headers \ --with-cuda = /usr/local/cuda \ --with-verbs \ --with-dm \ --with-gdrcopy = /usr/ local \ --enable-mt \ --prefix = /opt/ucx-1. 18 . 0 make -j sudo make install # add ~/.bashrc export PATH =/opt/ucx-1. 18 . 0 /bin: $PATH export LD_LIBRARY_PATH =/opt/ucx-1. 18 . 0 /lib: $LD_LIBRARY_PATH export PKG_CONFIG_PATH =/opt/ucx-1. 18 . 0 /lib/pkgconfig: $PKG_CONFIG_PATH 次に NIXL をインストールして、example である blocking_send_recv_example.py を実行します。 target から torch.ones(10, dtype=torch.float32) のメモリを initiator に転送しています。 git clone https://github.com/ai-dynamo/nixl.git cd nixl git checkout 503fe5ccb86b5963b828ee5663672fcba66b92d2 python3 -m venv venv source venv/bin/activate pip install . cd examples/python # UCXにおける、RDMAのNICや通信方法を設置 export UCX_NET_DEVICES = [ RNIC Device ] export UCX_TLS =rc,cuda # target node ./blocking_send_recv_example.py --ip 0 . 0 . 0 . 0 --mode target --use_cuda 1 ## MD listener is listening on port 8888... ## Backend UCX was instantiated ## Initialized NIXL agent: initiator ## initiator Tensors: [tensor([0., 0., 0., 0., 0., 0., 0., 0., 0., 0.], device='cuda:0'), tensor([0., 0., 0., 0., 0., 0., 0., 0., 0., 0.], device='cuda:0')] ## Initiator sending to [Node A IP Address] ## Ready for transfer ## initiator Data verification passed - [tensor([1., 1., 1., 1., 1., 1., 1., 1., 1., 1.], device='cuda:0'), tensor([1., 1., 1., 1., 1., 1., 1., 1., 1., 1.], device='cuda:0')] ## Test Complete. # initiator node ./blocking_send_recv_example.py --ip [ Node A IP Address ] --mode initiator --use_cuda 1 ## MD listener is listening on port 5555... ## Backend UCX was instantiated ## Initialized NIXL agent: target ## target Tensors: [tensor([1., 1., 1., 1., 1., 1., 1., 1., 1., 1.], device='cuda:0'), tensor([1., 1., 1., 1., 1., 1., 1., 1., 1., 1.], device='cuda:0')] ## Waiting for transfer ## Test Complete. 実行すると、上記のようにノード間で GPU メモリが共有されたことを確認できます。 GPUDirect Storage による VRAM to FILE のデータ転送 ここでは GPUDirect Storage(GDS)を用いて GPU メモリを直接ストレージに転送するサンプルを作成して実行します。 DRAM から GDS でストレージに書き込む example である nixl_gds_example.py を参考に VRAM から GDS でストレージに書き込む次のスクリプトを作成します。 nixl_gds_example_vram_to_file.py import os import sys import torch import subprocess import nixl._utils as nixl_utils from nixl._api import nixl_agent, nixl_agent_config if __name__ == "__main__" : agent_config = nixl_agent_config(backends=[ "GDS" ]) nixl_agent1 = nixl_agent( "GDSTester" , agent_config) # init VRAM float32 1.0(0x0000803f) tensors = [torch.ones( 10 , dtype=torch.float32, device= 'cuda:0' )] agent1_vram_descs = nixl_agent1.register_memory(tensors) agent1_xfer_vram = agent1_vram_descs.trim() file_path = sys.argv[ 1 ] agent1_fd = os.open(file_path, os.O_RDWR | os.O_CREAT) assert agent1_fd >= 0 agent1_file_list = [( 0 , tensors[ 0 ].numel() * tensors[ 0 ].element_size(), agent1_fd, "b" )] agent1_file_descs = nixl_agent1.register_memory(agent1_file_list, "FILE" ) assert agent1_file_descs is not None agent1_xfer_files = agent1_file_descs.trim() xfer_handle_1 = nixl_agent1.initialize_xfer( "WRITE" , agent1_xfer_vram, agent1_xfer_files, "GDSTester" ) if not xfer_handle_1: print ( "Creating transfer failed." ) exit() state = nixl_agent1.transfer(xfer_handle_1) assert state != "ERR" done = False while not done: state = nixl_agent1.check_xfer_state(xfer_handle_1) if state == "ERR" : print ( "Transfer got to Error state." ) exit() elif state == "DONE" : done = True print ( "Initiator done" ) nixl_agent1.release_xfer_handle(xfer_handle_1) nixl_agent1.deregister_memory(agent1_vram_descs) nixl_agent1.deregister_memory(agent1_file_descs) os.close(agent1_fd) # check file binary p = subprocess.run([ "hexdump" , "-Cv" , file_path], stdout=subprocess.PIPE) print ( "$ hexdump -Cv" , file_path) print (p.stdout.decode()) 実行した様子が以下のようになります。 ファイル内容を見ると float32 1.0 の 0x0000803f が書き込まれていることが分かります。 > python nixl_gds_example_vram_to_file.py /path/to/gds-support-dir/ones.bin Backend GDS was instantiated Initialized NIXL agent: GDSTester Initiator done $ hexdump -Cv /path/to/gds-support-dir/ones.bin 00000000 00 00 80 3f 00 00 80 3f 00 00 80 3f 00 00 80 3f |...?...?...?...?| 00000010 00 00 80 3f 00 00 80 3f 00 00 80 3f 00 00 80 3f |...?...?...?...?| 00000020 00 00 80 3f 00 00 80 3f |...?...?| 00000028 Custom Plugin の実装方法について NIXL は Plugin により任意の転送方式を実装可能なアーキテクチャとなっています。 ここではローカルで DRAM と FILE による転送が可能なサンプルの Plugin を実装する方法について紹介します。 基本的には、 nixlBackendEngine を継承して、最低でも純粋仮想関数(例: virtual void f() = 0; ) 3 を実装することで新たな転送方法を追加できます。 プロジェクト構成以下のように nixl の plugins 配下に新たに local ディレクトリを作成して実装コードを置きます。 nixl ├── src │ ├── plugins │ │ ├── local │ │ │ ├── local_backend.cpp │ │ │ ├── local_backend.h │ │ │ ├── local_plugin.cpp │ │ │ └── meson.build │ │ ├── meson.build 新たに Plugin を追加するため、 nixl/src/plugins/meson.build に以下を追記します。 subdir('local') 次のように追加した Plugins を登録する実行を追加します。 Plugin の名前、追加のオプションパラメータ、サポート可能なメモリーの種類を設定します。 local_plugin.cpp #include "backend/backend_plugin.h" #include "local_backend.h" static const char * PLUGIN_NAME = "LOCAL" ; static const char * PLUGIN_VERSION = "0.1" ; static nixlBackendEngine* create_local_engine ( const nixlBackendInitParams* init_params) { return new nixlLocalEngine (init_params); } static void destroy_local_engine (nixlBackendEngine* engine) { delete engine; } static const char * get_plugin_name () { return PLUGIN_NAME; } static const char * get_plugin_version () { return PLUGIN_VERSION; } static nixl_b_params_t get_backend_options () { nixl_b_params_t params; return params; } // サポート可能なメモリーの種類 static nixl_mem_list_t get_backend_mems () { nixl_mem_list_t mems; mems. push_back (DRAM_SEG); mems. push_back (FILE_SEG); return mems; } static nixlBackendPlugin plugin = {NIXL_PLUGIN_API_VERSION, create_local_engine, destroy_local_engine, get_plugin_name, get_plugin_version, get_backend_options, get_backend_mems}; #ifdef STATIC_PLUGIN_LOCAL nixlBackendPlugin* createStaticLocalPlugin () { return &plugin; } #else extern "C" NIXL_PLUGIN_EXPORT nixlBackendPlugin* nixl_plugin_init () { return &plugin; } extern "C" NIXL_PLUGIN_EXPORT void nixl_plugin_fini () {} #endif 以降で実際に転送処理を行うロジックを実装します。 postXfer 関数でメモリアドレスやファイルデスクリプタが渡されるためそれらを用いて転送します。 例えば UCX の実装を参考にすると実転送は postXfer 関数で実行されています。 4 local_backend.h #ifndef __LOCAL_BACKEND_H #define __LOCAL_BACKEND_H #include <nixl.h> #include <nixl_types.h> #include <unistd.h> #include "backend/backend_engine.h" class nixlLocalEngine : public nixlBackendEngine { private : public : nixlLocalEngine ( const nixlBackendInitParams *init_params); ~ nixlLocalEngine (); // 通知機能をサポートするかどうか bool supportsNotif () const { return false ; } // 別プロセス、リモートノードへの転送をサポートするかどうか bool supportsRemote () const { return false ; } // 同一のプロセス、ノードへの転送をサポートするかどうか bool supportsLocal () const { return true ; } // 内部に処理のバックグランドスレッドを持つかどうか bool supportsProgTh () const { return false ; } // サポート可能なメモリーの種類 nixl_mem_list_t getSupportedMems () const { nixl_mem_list_t mems; mems. push_back (DRAM_SEG); mems. push_back (FILE_SEG); return mems; } nixl_status_t connect ( const std :: string &remote_agent) { return NIXL_SUCCESS; } nixl_status_t disconnect ( const std :: string &remote_agent) { return NIXL_SUCCESS; } nixl_status_t loadLocalMD (nixlBackendMD *input, nixlBackendMD *&output) { output = input; return NIXL_SUCCESS; } nixl_status_t unloadMD (nixlBackendMD *input) { return NIXL_SUCCESS; } nixl_status_t registerMem ( const nixlBlobDesc &mem, const nixl_mem_t &nixl_mem, nixlBackendMD *&out); nixl_status_t deregisterMem (nixlBackendMD *meta); nixl_status_t prepXfer ( const nixl_xfer_op_t &operation, const nixl_meta_dlist_t &local, const nixl_meta_dlist_t &remote, const std :: string &remote_agent, nixlBackendReqH *&handle, const nixl_opt_b_args_t *opt_args = nullptr ); nixl_status_t postXfer ( const nixl_xfer_op_t &operation, const nixl_meta_dlist_t &local, const nixl_meta_dlist_t &remote, const std :: string &remote_agent, nixlBackendReqH *&handle, const nixl_opt_b_args_t *opt_args = nullptr ); nixl_status_t checkXfer (nixlBackendReqH *handle); nixl_status_t releaseReqH (nixlBackendReqH *handle); }; #endif local_backend.cpp #include "local_backend.h" #include <string.h> #include <sys/stat.h> #include <iostream> nixlLocalEngine:: nixlLocalEngine ( const nixlBackendInitParams *init_params) : nixlBackendEngine (init_params) {} nixl_status_t nixlLocalEngine:: registerMem ( const nixlBlobDesc &mem, const nixl_mem_t &nixl_mem, nixlBackendMD *&out) { // 基本的にローカルでDRAM, FILEの転送をする際にはここで処理はしない形で設計 // 例えばRDMA(ib verbs)ではibv_reg_mrやGDSではファイルハンドラ登録がされることが望ましいと思われる // 別途ここでファイルハンドラなどのメタデータを設定し、prepXferやpostXferを利用するためにはnixlBackendMDを継承したクラスを引数のoutに設定する // 参考: https://github.com/ai-dynamo/nixl/blob/1c979f0999740e4b221d0b9b470efbac793ddcae/src/plugins/cuda_gds/gds_backend.cpp#L95 if (nixl_mem == FILE_SEG || nixl_mem == DRAM_SEG) return NIXL_SUCCESS; return NIXL_ERR_NOT_SUPPORTED; } nixl_status_t nixlLocalEngine:: deregisterMem (nixlBackendMD *meta) { return NIXL_SUCCESS; } nixl_status_t nixlLocalEngine:: prepXfer ( const nixl_xfer_op_t &operation, const nixl_meta_dlist_t &local, const nixl_meta_dlist_t &remote, const std :: string &remote_agent, nixlBackendReqH *&handle, const nixl_opt_b_args_t *opt_args) { // validation if ((local. descCount () != remote. descCount ()) || ((operation != NIXL_READ) && (operation != NIXL_WRITE))) { return NIXL_ERR_INVALID_PARAM; } return NIXL_SUCCESS; } nixl_status_t nixlLocalEngine:: postXfer ( const nixl_xfer_op_t &operation, const nixl_meta_dlist_t &local, const nixl_meta_dlist_t &remote, const std :: string &remote_agent, nixlBackendReqH *&handle, const nixl_opt_b_args_t *opt_args) { // DRAM, FILEの転送処理を実行する if (local. getType () == DRAM_SEG && remote. getType () == DRAM_SEG) { // dram to dram int cnt = local. descCount (); for ( int i = 0 ; i < cnt; i++) { void *dst_addr; void *src_addr; if (operation == NIXL_READ) { dst_addr = ( void *)local[i].addr; src_addr = ( void *)remote[i].addr; } else if (operation == NIXL_WRITE) { dst_addr = ( void *)remote[i].addr; src_addr = ( void *)local[i].addr; } memcpy (dst_addr, src_addr, local[i].len); } } else if (local. getType () == FILE_SEG && remote. getType () == FILE_SEG) { // file to file int cnt = local. descCount (); for ( int i = 0 ; i < cnt; i++) { int in_fd; int out_fd; if (operation == NIXL_READ) { in_fd = remote[i].devId; out_fd = local[i].devId; } else if (operation == NIXL_WRITE) { in_fd = local[i].devId; out_fd = remote[i].devId; } struct stat st; if ( fstat (in_fd, &st) < 0 ) { return NIXL_ERR_INVALID_PARAM; } if ( copy_file_range (in_fd, 0 , out_fd, 0 , st.st_size, 0 ) < 0 ) { return NIXL_ERR_INVALID_PARAM; } } } else if ((local. getType () == FILE_SEG && remote. getType () == DRAM_SEG && operation == NIXL_WRITE) or (local. getType () == DRAM_SEG && remote. getType () == FILE_SEG && operation == NIXL_READ)) { // file to dram int cnt = local. descCount (); for ( int i = 0 ; i < cnt; i++) { int fd = local. getType () == FILE_SEG ? local[i].devId : remote[i].devId; uintptr_t buf = local. getType () == FILE_SEG ? remote[i].addr : local[i].addr; size_t len = operation == NIXL_WRITE ? local[i].len : remote[i].len; if ( pread (fd, ( void *)buf, len, 0 ) < 0 ) { return NIXL_ERR_INVALID_PARAM; } } } else if ((local. getType () == DRAM_SEG && remote. getType () == FILE_SEG && operation == NIXL_WRITE) or (local. getType () == FILE_SEG && remote. getType () == DRAM_SEG && operation == NIXL_READ)) { // dram to file int cnt = local. descCount (); for ( int i = 0 ; i < cnt; i++) { int fd = local. getType () == FILE_SEG ? local[i].devId : remote[i].devId; uintptr_t buf = local. getType () == FILE_SEG ? remote[i].addr : local[i].addr; size_t len = operation == NIXL_WRITE ? local[i].len : remote[i].len; if ( pwrite (fd, ( void *)buf, len, 0 ) < 0 ) { return NIXL_ERR_UNKNOWN; } } } else { return NIXL_ERR_NOT_SUPPORTED; } return NIXL_SUCCESS; } nixl_status_t nixlLocalEngine:: checkXfer (nixlBackendReqH *handle) { // 非同期機能未サポートのため、即時でSUCCESSを返す // 非同期実装については以下参考 // https://github.com/ai-dynamo/nixl/tree/main/src/plugins/posix return NIXL_SUCCESS; } nixl_status_t nixlLocalEngine:: releaseReqH (nixlBackendReqH *handle) { return NIXL_SUCCESS; } nixlLocalEngine::~ nixlLocalEngine () {} 最後に追加した Plugin の meson を次のように記載します。 nixl/src/plugins/local/meson.build if 'LOCAL' in static_plugins local_backend_lib = static_library('LOCAL', 'local_backend.cpp', 'local_backend.h', 'local_plugin.cpp', dependencies: [nixl_infra, nixl_common_dep], include_directories: [nixl_inc_dirs, utils_inc_dirs], install: true, cpp_args: ['-fPIC'], name_prefix: 'libplugin_', install_dir: plugin_install_dir) else local_backend_lib = shared_library('LOCAL', 'local_backend.cpp', 'local_backend.h', 'local_plugin.cpp', dependencies: [nixl_infra, nixl_common_dep], include_directories: [nixl_inc_dirs, utils_inc_dirs], install: true, cpp_args: ['-fPIC'], name_prefix: 'libplugin_', install_dir: plugin_install_dir) if get_option('buildtype') == 'debug' run_command('sh', '-c', 'echo "LOCAL=' + local_backend_lib.full_path() + '" >> ' + plugin_build_dir + '/pluginlist', check: true ) endif endif local_backend_interface = declare_dependency(link_with: local_backend_lib) 追加後は次のようにインストールします。 cd nixl pip install . 追加した Plugin を検証するコードを次に記述しました。 backends として agent に今回追加した LOCAL を設定します。 nixl_local_example.py import os import sys import torch import subprocess from nixl._api import nixl_agent, nixl_agent_config if __name__ == "__main__" : agent_config = nixl_agent_config(backends=[ "LOCAL" ]) nixl_agent = nixl_agent( "LOCAL_TEST" , agent_config) t1 = [torch.ones( 10 , dtype=torch.float32) for _ in range ( 1 )] t1_reg_descs = nixl_agent.get_reg_descs(t1) t1_xfer_descs = nixl_agent.get_xfer_descs(t1) assert nixl_agent.register_memory(t1_reg_descs) is not None t2 = [torch.zeros( 10 , dtype=torch.float32) for _ in range ( 1 )] t2_reg_descs = nixl_agent.get_reg_descs(t2) t2_xfer_descs = nixl_agent.get_xfer_descs(t2) assert nixl_agent.register_memory(t2_reg_descs) is not None t3 = [torch.zeros( 10 , dtype=torch.float32) for _ in range ( 1 )] t3_reg_descs = nixl_agent.get_reg_descs(t3) t3_xfer_descs = nixl_agent.get_xfer_descs(t3) assert nixl_agent.register_memory(t3_reg_descs) is not None print ( "=====Init=====" ) print ( 't1:' , t1) print ( 't2:' , t2) print ( 't3:' , t3) print ( "=====Write t1 to FILE(/tmp/ones.bin)=====" ) fd = os.open( "/tmp/ones.bin" , os.O_RDWR | os.O_CREAT) file_list = [( 0 , t1[ 0 ].numel() * t1[ 0 ].element_size(), fd, "b" )] file_descs = nixl_agent.register_memory(file_list, "FILE" ) assert file_descs is not None file_xfer_files = file_descs.trim() xfer_handle_1 = nixl_agent.initialize_xfer( "WRITE" , t1_xfer_descs, file_xfer_files, "LOCAL_TEST" ) if not xfer_handle_1: print ( "Write t1 to FILE failed." , file =sys.stderr) exit(- 1 ) state = nixl_agent.transfer(xfer_handle_1) assert state == "DONE" # check file binary p = subprocess.run([ "hexdump" , "-Cv" , "/tmp/ones.bin" ], stdout=subprocess.PIPE) print ( "$ hexdump -Cv /tmp/ones.bin" ) print (p.stdout.decode()) print ( "=====Read t2 from FILE(/tmp/ones.bin)=====" ) xfer_handle_2 = nixl_agent.initialize_xfer( "READ" , t2_xfer_descs, file_xfer_files, "LOCAL_TEST" ) if not xfer_handle_2: print ( "Read t2 from FILE failed." , file =sys.stderr) exit(- 1 ) state = nixl_agent.transfer(xfer_handle_2) assert state == "DONE" print ( "t2:" , t2) print ( "=====Write t1 to t3=====" ) xfer_handle_3 = nixl_agent.initialize_xfer( "WRITE" , t1_xfer_descs, t3_xfer_descs, "LOCAL_TEST" ) if not xfer_handle_3: print ( "Read t2 from FILE failed." , file =sys.stderr) exit(- 1 ) state = nixl_agent.transfer(xfer_handle_3) assert state == "DONE" print ( 't3:' , t3) # cleanup nixl_agent.release_xfer_handle(xfer_handle_1) nixl_agent.release_xfer_handle(xfer_handle_2) nixl_agent.release_xfer_handle(xfer_handle_3) nixl_agent.deregister_memory(t1_reg_descs) nixl_agent.deregister_memory(t2_reg_descs) nixl_agent.deregister_memory(t3_reg_descs) nixl_agent.deregister_memory(file_descs) os.close(fd) 実行結果は次のようになります。 t1 , t2 , t3 の 3 つの torch.Tensor がそれぞれファイルに書き込み、読み込みやメモリ間の転送を実施している様子が分かります。 > python nixl_local_example.py Backend LOCAL was instantiated Initialized NIXL agent: LOCAL_TEST ===== Init = ==== t1: [ tensor( [ 1 ., 1 ., 1 ., 1 ., 1 ., 1 ., 1 ., 1 ., 1 ., 1 . ] ) ] t2: [ tensor( [ 0 ., 0 ., 0 ., 0 ., 0 ., 0 ., 0 ., 0 ., 0 ., 0 . ] ) ] t3: [ tensor( [ 0 ., 0 ., 0 ., 0 ., 0 ., 0 ., 0 ., 0 ., 0 ., 0 . ] ) ] ===== Write t1 to FILE ( /tmp/ones.bin ) ===== $ hexdump -Cv /tmp/ones.bin 00000000 00 00 80 3f 00 00 80 3f 00 00 80 3f 00 00 80 3f |...?...?...?...?| 00000010 00 00 80 3f 00 00 80 3f 00 00 80 3f 00 00 80 3f |...?...?...?...?| 00000020 00 00 80 3f 00 00 80 3f |...?...?| 00000028 ===== Read t2 from FILE ( /tmp/ones.bin ) ===== t2: [ tensor( [ 1 ., 1 ., 1 ., 1 ., 1 ., 1 ., 1 ., 1 ., 1 ., 1 . ] ) ] ===== Write t1 to t3 = ==== t3: [ tensor( [ 1 ., 1 ., 1 ., 1 ., 1 ., 1 ., 1 ., 1 ., 1 ., 1 . ] ) ] まとめ 本記事では、LLM 推論フレームワーク向けに設計された、高速かつ低遅延な抽象化転送ライブラリ「NVIDIA Inference Xfer Library(NIXL)」について解説しました。また、example の実行方法や Custom Plugin の実装方法についても紹介しました。 NIXL を導入することで、複数 GPU やマルチノード環境におけるレイテンシの削減やスケールアウトが容易になり、将来的には Plugin を活用した新しいストレージ技術や通信プロトコルへの対応も期待できます。 LLM テクニックの習得: 推論の最適化 - NVIDIA 技術ブログ ↩ https://github.com/ai-dynamo/nixl/commit/b0085154d2aa4347c332bb121293a77ab733a871 ↩ Abstract class - cppreference.com ↩ https://github.com/ai-dynamo/nixl/blob/503fe5ccb86b5963b828ee5663672fcba66b92d2/src/plugins/ucx/ucx_backend.cpp#L892-L898 ↩