概要
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等の特定の処理が遅いなどの欠点も散見されましたが、確かに処理は速く、トータルでは期待大という印象でした。 引き続き最新バージョンでの動作検証もできるように進めていこうと思っています。