GPU版pandasとされるcuDFをgoogle-colabで試してみた

概要

pandasのdataframeは有用ですが、データ量が多くなっていると計算速度に不満を感じるようになってきます。調べると、GPUでpandasライクにdataframeを取り扱えるcuDFがあるとのことでGoogle Colabolatoryを使って試行してみました。
最新バージョンは0.12ですが、エラーが出て動かすことが出来なかったので、動作を確認できた0.6.1を試行しました。

なお、cuDFとはRAPIDS社が提供するApache2.0ライセンスのツールの1つのようです。 他にはcuML(機械学習)等もあるとのことで、注目していくべき企業のように思われます。
https://rapids.ai/about.html

インストール

環境はGoogle colabolatory(以降colab)を使うことを前提に、cuDFをインストールするまでの過程を整理していきます。なお、colabは接続するタイミングによってGPUの種類が変わったり、インスタンス内のファイル構成が変わるといった性質があるらしく、これによりインストールにはかなり苦労する可能性が生じていることが分かりました。

version 0.12(インストール失敗)

まずは、現時点の最新版であるversion 0.12のインストールにトライしました。このためのコードは以下のURLにRAPIDS社が提供するノートブックが置かれていたので、これをそのまま実行しました。
https://colab.research.google.com/drive/1rY7Ln6rEE1pOlfSHCYOVaqt8OvDO35J0

実行すると、 # Install RAPIDSの処理で以下のようなエラーが出ました。ファイルが見つからないというエラーなのですが、!findで検索すると下記名称のファイルは存在するという結果であり、一旦他の情報を調べることにしました。

FileNotFoundError(2, "No such file or directory: '/usr/local/bin/python3.6'")

version 0.8(インストール失敗)

調べると以下の開発者ブログに行き着きました。
Run RAPIDS on Google Colab — For Free
ここにはversion 0.8のインストールコードがあり、これを試してみました。
https://colab.research.google.com/drive/1bEuSOlAc4bJvLm7Qj3xayr37ILkC4a1X

ここでは以下のようなエラーが出て動作しませんでした。

pynvml.NVMLError_DriverNotLoaded: Driver Not Loaded

version 0.6.1(なんとかインストール成功)

さらに調べると別の開発者ブログに行き着きました。
Rapids on google colab
ここには0.6.1のインストールコードが記載されていました。

!nvidia-smi
!nvcc -V
!python -V; pip -V

!pip install cudf-cuda100
!find / -name librmm.so* 
!cp /usr/local/lib/python3.6/dist-packages/librmm.so . 

import os
os.environ['NUMBAPRO_NVVM']='/usr/local/cuda-10.0/nvvm/lib64/libnvvm.so'
os.environ['NUMBAPRO_LIBDEVICE']='/usr/local/cuda-10.0/nvvm/libdevice'

その後動作チェック用のコードとして以下を実行しました。

import cudf
df = cudf.DataFrame()
df['key'] = [0, 1, 2, 3, 4]
df['val'] = [float(i + 10) for i in range(5)]  # insert column
print(df)
print(dir(df))

実行すると、順に以下3つのエラーが発生しました。

librmm.soが見つからないエラー

!cp~の部分で以下のようなエラーが出ました。

cannot load library 'librmm.so'

調べると、その時自分が接続したcolabではdist-packagesの下ではなくsite-packagesの下にlibrmm.soがあったためでした。ですので、以下のように書き換えたところこのエラーは出なくなりました。(なお、ほとんどのケースではdist-packagesでした。)

#!cp /usr/local/lib/python3.6/dist-packages/librmm.so .
!cp /usr/local/lib/python3.6/site-packages/librmm.so .

cudaのバージョン指定が間違っているエラー

df_cu = cudf.from_pandas(df)の部分で以下のエラーが出ました。

OSError: library nvvm not found
NvvmSupportError: libNVVM cannot be found. Do `conda install cudatoolkit`:
library nvvm not found

condaでtoolkitをインストールせよという指摘ですが、colabにはcondaが入っていませんし、そもそもnvvmってパスを通していたはずなので、改めて確認したところ、colabのcudaのバージョンが10.0ではなく10.1であったことが分かりました。そこで以下のように書き換えたらこのエラーは発生しなくなりました。

#os.environ['NUMBAPRO_NVVM']='/usr/local/cuda-10.0/nvvm/lib64/libnvvm.so'
#os.environ['NUMBAPRO_LIBDEVICE']='/usr/local/cuda-10.0/nvvm/libdevice'
os.environ['NUMBAPRO_NVVM']='/usr/local/cuda-10.1/nvvm/lib64/libnvvm.so'
os.environ['NUMBAPRO_LIBDEVICE']='/usr/local/cuda-10.1/nvvm/libdevice'

pandasのバージョンがサポートされていないエラー

最後にdir(df)の部分でエラーが出ました。

AttributeError: module 'pandas.compat' has no attribute 'string_types'

どんなエラーがよくわかりませんが、調べるとpandasのバージョンが0.25以上だと動作しないという指摘を見つけました。
https://github.com/rapidsai/cudf/issues/2656

そこで推奨通り以下のコードを追加してpandasを0.24.2にバージョンダウンしたところ、動作するようになりました。

!pip install pandas==0.24.2

コードまとめ

最終的に以下のコードでcuDFをインストールすると動作しました。

!nvidia-smi
!nvcc -V
!python -V; pip -V

!pip install cudf-cuda100
!find / -name librmm.so* 
#!cp /usr/local/lib/python3.6/site-packages/librmm.so .
!cp /usr/local/lib/python3.6/dist-packages/librmm.so . 

import os
#os.environ['NUMBAPRO_NVVM']='/usr/local/cuda-10.0/nvvm/lib64/libnvvm.so'
#os.environ['NUMBAPRO_LIBDEVICE']='/usr/local/cuda-10.0/nvvm/libdevice'
os.environ['NUMBAPRO_NVVM']='/usr/local/cuda-10.1/nvvm/lib64/libnvvm.so'
os.environ['NUMBAPRO_LIBDEVICE']='/usr/local/cuda-10.1/nvvm/libdevice'

!pip install pandas==0.24.2

試行結果

メンバ比較

pandasの機能がどの程度網羅されているかを確認しました。

ライブラリ直下(pd)のメンバ

どうやらライブラリ直下(pd.で選べるメンバ)はほとんど実装されていないことが分かりました。

#pandasにもcudfにもあるメンバ
'DataFrame', 'Index', 'Series', '__builtins__', '__cached__',
       '__doc__', '__file__', '__loader__', '__name__', '__package__',
       '__path__', '__spec__', '__version__', '_version', 'concat', 'io',
       'melt', 'merge', 'read_csv', 'read_feather', 'read_hdf',
       'read_json', 'read_parquet'
#pandasにはあるがcudfにはないメンバ
'Categorical', 'CategoricalDtype', 'CategoricalIndex',
       'DateOffset', 'DatetimeIndex', 'DatetimeTZDtype', 'ExcelFile',
       'ExcelWriter', 'Float64Index', 'Grouper', 'HDFStore', 'IndexSlice',
       'Int16Dtype', 'Int32Dtype', 'Int64Dtype', 'Int64Index',
       'Int8Dtype', 'Interval', 'IntervalDtype', 'IntervalIndex',
       'MultiIndex', 'NaT', 'NamedAgg', 'Panel', 'Period', 'PeriodDtype',
       'PeriodIndex', 'RangeIndex', 'SparseArray', 'SparseDataFrame',
       'SparseDtype', 'SparseSeries', 'Timedelta', 'TimedeltaIndex',
       'Timestamp', 'UInt16Dtype', 'UInt32Dtype', 'UInt64Dtype',
       'UInt64Index', 'UInt8Dtype', '__docformat__', '__git_version__',
       '_config', '_hashtable', '_lib', '_libs', '_np_version_under1p14',
       '_np_version_under1p15', '_np_version_under1p16',
       '_np_version_under1p17', '_tslib', '_typing', 'api', 'array',
       'arrays', 'bdate_range', 'compat', 'core', 'crosstab', 'cut',
       'date_range', 'datetime', 'describe_option', 'errors', 'eval',
       'factorize', 'get_dummies', 'get_option', 'infer_freq',
       'interval_range', 'isna', 'isnull', 'lreshape', 'merge_asof',
       'merge_ordered', 'notna', 'notnull', 'np', 'offsets',
       'option_context', 'options', 'pandas', 'period_range', 'pivot',
       'pivot_table', 'plotting', 'qcut', 'read_clipboard', 'read_excel',
       'read_fwf', 'read_gbq', 'read_html', 'read_msgpack', 'read_pickle',
       'read_sas', 'read_spss', 'read_sql', 'read_sql_query',
       'read_sql_table', 'read_stata', 'read_table', 'reset_option',
       'set_eng_float_format', 'set_option', 'show_versions', 'test',
       'testing', 'timedelta_range', 'to_datetime', 'to_msgpack',
       'to_numeric', 'to_pickle', 'to_timedelta', 'tseries', 'unique',
       'util', 'value_counts', 'wide_to_long'

データフレーム(dataframe)のメンバ

これも大多数が実装されていないようです。meanやsumすらないのはちょっと驚きです。meanをしたいときはseriesにする必要があるようです。

#pandasにもcudfにもあるメンバ
'T', '__add__', '__class__', '__copy__', '__deepcopy__',
       '__delattr__', '__delitem__', '__dict__', '__dir__', '__div__',
       '__doc__', '__eq__', '__floordiv__', '__format__', '__ge__',
       '__getattr__', '__getattribute__', '__getitem__', '__gt__',
       '__hash__', '__init__', '__init_subclass__', '__iter__', '__le__',
       '__len__', '__lt__', '__module__', '__mul__', '__ne__', '__new__',
       '__pow__', '__radd__', '__reduce__', '__reduce_ex__', '__repr__',
       '__rfloordiv__', '__rmul__', '__rsub__', '__rtruediv__',
       '__setattr__', '__setitem__', '__sizeof__', '__str__', '__sub__',
       '__subclasshook__', '__truediv__', '__weakref__', 'as_matrix',
       'assign', 'columns', 'copy', 'drop', 'dtypes', 'empty', 'fillna',
       'from_records', 'groupby', 'head', 'iloc', 'index', 'iteritems',
       'join', 'key', 'loc', 'mask', 'merge', 'nlargest', 'nsmallest',
       'quantile', 'query', 'rename', 'replace', 'reset_index',
       'select_dtypes', 'set_index', 'shape', 'sort_index', 'sort_values',
       'tail', 'take', 'to_feather', 'to_hdf', 'to_json', 'to_parquet',
       'to_records', 'to_string', 'transpose', 'val'
#pandasにはあるがcudfにはないメンバ
'_AXIS_ALIASES', '_AXIS_IALIASES', '_AXIS_LEN', '_AXIS_NAMES',
       '_AXIS_NUMBERS', '_AXIS_ORDERS', '_AXIS_REVERSED',
       '_AXIS_SLICEMAP', '__abs__', '__and__', '__array__',
       '__array_priority__', '__array_wrap__', '__bool__', '__bytes__',
       '__contains__', '__finalize__', '__getstate__', '__iadd__',
       '__iand__', '__ifloordiv__', '__imod__', '__imul__', '__invert__',
       '__ior__', '__ipow__', '__isub__', '__itruediv__', '__ixor__',
       '__matmul__', '__mod__', '__neg__', '__nonzero__', '__or__',
       '__pos__', '__rand__', '__rdiv__', '__rmatmul__', '__rmod__',
       '__ror__', '__round__', '__rpow__', '__rxor__', '__setstate__',
       '__unicode__', '__xor__', '_accessors', '_add_numeric_operations',
       '_add_series_only_operations',
       '_add_series_or_dataframe_operations', '_agg_by_level',
       '_agg_examples_doc', '_agg_summary_and_see_also_doc', '_aggregate',
       '_aggregate_multiple_funcs', '_align_frame', '_align_series',
       '_box_col_values', '_box_item_values', '_builtin_table',
       '_check_inplace_setting', '_check_is_chained_assignment_possible',
       '_check_label_or_level_ambiguity', '_check_percentile',
       '_check_setitem_copy', '_clear_item_cache', '_clip_with_one_bound',
       '_clip_with_scalar', '_combine_const', '_combine_frame',
       '_combine_match_columns', '_combine_match_index', '_consolidate',
       '_consolidate_inplace', '_construct_axes_dict',
       '_construct_axes_dict_for_slice', '_construct_axes_dict_from',
       '_construct_axes_from_arguments', '_constructor',
       '_constructor_expanddim', '_constructor_sliced', '_convert',
       '_count_level', '_create_indexer', '_cython_table',
       '_deprecations', '_dir_additions', '_dir_deletions', '_drop_axis',
       '_drop_labels_or_levels', '_ensure_valid_index', '_expand_axes',
       '_find_valid_index', '_from_arrays', '_from_axes', '_get_agg_axis',
       '_get_axis', '_get_axis_name', '_get_axis_number',
       '_get_axis_resolvers', '_get_block_manager_axis', '_get_bool_data',
       '_get_cacher', '_get_index_resolvers', '_get_item_cache',
       '_get_label_or_level_values', '_get_numeric_data', '_get_value',
       '_get_values', '_getitem_bool_array', '_getitem_frame',
       '_getitem_multilevel', '_gotitem', '_iget_item_cache',
       '_indexed_same', '_info_axis', '_info_axis_name',
       '_info_axis_number', '_info_repr', '_init_mgr', '_internal_names',
       '_internal_names_set', '_is_builtin_func', '_is_cached',
       '_is_copy', '_is_cython_func', '_is_datelike_mixed_type',
       '_is_homogeneous_type', '_is_label_or_level_reference',
       '_is_label_reference', '_is_level_reference', '_is_mixed_type',
       '_is_numeric_mixed_type', '_is_view', '_ix', '_ixs',
       '_join_compat', '_maybe_cache_changed', '_maybe_update_cacher',
       '_metadata', '_needs_reindex_multi', '_obj_with_exclusions',
       '_protect_consolidate', '_reduce', '_reindex_axes',
       '_reindex_columns', '_reindex_index', '_reindex_multi',
       '_reindex_with_indexers', '_repr_data_resource_',
       '_repr_fits_horizontal_', '_repr_fits_vertical_', '_repr_html_',
       '_repr_latex_', '_reset_cache', '_reset_cacher',
       '_sanitize_column', '_selected_obj', '_selection',
       '_selection_list', '_selection_name', '_series', '_set_as_cached',
       '_set_axis', '_set_axis_name', '_set_is_copy', '_set_item',
       '_set_value', '_setitem_array', '_setitem_frame', '_setitem_slice',
       '_setup_axes', '_shallow_copy', '_slice', '_stat_axis',
       '_stat_axis_name', '_stat_axis_number', '_take',
       '_to_dict_of_blocks', '_try_aggregate_string_function', '_typ',
       '_unpickle_frame_compat', '_unpickle_matrix_compat',
       '_update_inplace', '_validate_dtype', '_values', '_where', '_xs',
       'abs', 'add', 'add_prefix', 'add_suffix', 'agg', 'aggregate',
       'align', 'all', 'any', 'append', 'apply', 'applymap', 'asfreq',
       'asof', 'astype', 'at', 'at_time', 'axes', 'between_time', 'bfill',
       'bool', 'boxplot', 'clip', 'clip_lower', 'clip_upper', 'combine',
       'combine_first', 'compound', 'corr', 'corrwith', 'count', 'cov',
       'cummax', 'cummin', 'cumprod', 'cumsum', 'describe', 'diff', 'div',
       'divide', 'dot', 'drop_duplicates', 'droplevel', 'dropna',
       'duplicated', 'eq', 'equals', 'eval', 'ewm', 'expanding', 'ffill',
       'filter', 'first', 'first_valid_index', 'floordiv', 'from_dict',
       'ftypes', 'ge', 'get', 'get_dtype_counts', 'get_ftype_counts',
       'get_values', 'gt', 'hist', 'iat', 'idxmax', 'idxmin',
       'infer_objects', 'info', 'insert', 'interpolate', 'isin', 'isna',
       'isnull', 'items', 'iterrows', 'itertuples', 'ix', 'keys', 'kurt',
       'kurtosis', 'last', 'last_valid_index', 'le', 'lookup', 'lt',
       'mad', 'max', 'mean', 'median', 'melt', 'memory_usage', 'min',
       'mod', 'mode', 'mul', 'multiply', 'ndim', 'ne', 'notna', 'notnull',
       'nunique', 'pct_change', 'pipe', 'pivot', 'pivot_table', 'plot',
       'pop', 'pow', 'prod', 'product', 'radd', 'rank', 'rdiv', 'reindex',
       'reindex_axis', 'reindex_like', 'rename_axis', 'reorder_levels',
       'resample', 'rfloordiv', 'rmod', 'rmul', 'rolling', 'round',
       'rpow', 'rsub', 'rtruediv', 'sample', 'select', 'sem', 'set_axis',
       'shift', 'size', 'skew', 'slice_shift', 'squeeze', 'stack', 'std',
       'style', 'sub', 'subtract', 'sum', 'swapaxes', 'swaplevel',
       'timetuple', 'to_clipboard', 'to_csv', 'to_dense', 'to_dict',
       'to_excel', 'to_gbq', 'to_html', 'to_latex', 'to_msgpack',
       'to_numpy', 'to_panel', 'to_period', 'to_pickle', 'to_sparse',
       'to_sql', 'to_stata', 'to_timestamp', 'to_xarray', 'transform',
       'truediv', 'truncate', 'tshift', 'tz_convert', 'tz_localize',
       'unstack', 'update', 'values', 'var', 'where', 'xs'

計算速度

それぞれで100万行100列のデータフレームを作成し、1列の平均を出す処理を実施したところ以下の結果でした。

#データ作成
nrow=1000000
ncol=100
df = pd.DataFrame(index=range(nrow), columns=range(ncol), data=pd.np.random.rand(nrow, ncol))
df_cu = cudf.from_pandas(df)
#pandas
%timeit df.iloc[:, 0].mean()
100 loops, best of 3: 6.7 ms per loop
#cuDF
%timeit df_cu.iloc[:, 0].mean()
1 loop, best of 3: 195 ms per loop

上記の結果をみると、pandasが6.7msで、cuDFは195msとcuDFの方が大幅に遅い結果になりました。おかしいな、と思ってみてみると、ilocに大きな時間を要していることが分かりました。

#cuDF
#なんと、ilocに195msの内の193msを使っていた
%timeit df_cu.iloc[:, 0]
1 loop, best of 3: 193 ms per loop

そこで、データを1億行1列にして同様の比較をしたところ、pandasが148msで、cuDFが10ms以下でしたので、cuDFが大幅に高速化していることが確認できました。他にも、versionは少し古そうですが、計算速度の試行は以下のサイト様等でも言及されていて、10倍~程度早くなっているという結果が示されています。
https://towardsdatascience.com/heres-how-you-can-speedup-pandas-with-cudf-and-gpus-9ddc1716d5f2
https://lab.m-field.co.jp/2019/04/26/gpu-cudf/

まとめ

pandasをGPUで動作させるcuDFについて、インストールできたversion 0.6.1を試行しました。印象としては、使うまでが大変なのと、まだ機能不足が多そうなのと、iloc等の特定の処理が遅いなどの欠点も散見されましたが、確かに処理は速く、トータルでは期待大という印象でした。 引き続き最新バージョンでの動作検証もできるように進めていこうと思っています。

コメントを残す

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です

CAPTCHA