<?xml version='1.0' encoding='UTF-8' standalone='no'?>
<doxygen xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="compound.xsd" version="1.9.6" xml:lang="en-US">
  <compounddef id="kmeans_cudaflow" kind="page">
    <compoundname>kmeans_cudaflow</compoundname>
    <title>k-means Clustering (cudaFlow)</title>
    <tableofcontents>
      <tocsect>
        <name>Define the k-means Kernels</name>
        <reference>kmeans_cudaflow_1DefineTheKMeansKernels</reference>
    </tocsect>
      <tocsect>
        <name>Define the k-means cudaFlow</name>
        <reference>kmeans_cudaflow_1DefineTheKMeanscudaFlow</reference>
    </tocsect>
      <tocsect>
        <name>Benchmarking</name>
        <reference>kmeans_cudaflow_1KMeanscudaFlowBenchmarking</reference>
    </tocsect>
    </tableofcontents>
    <briefdescription>
    </briefdescription>
    <detaileddescription>
<para>Following up on <ref refid="kmeans" kindref="compound">k-means Clustering</ref>, this page studies how to accelerate a k-means workload on a GPU using <ref refid="classtf_1_1cudaFlow" kindref="compound">tf::cudaFlow</ref>.</para>
<sect1 id="kmeans_cudaflow_1DefineTheKMeansKernels">
<title>Define the k-means Kernels</title>
<para>Recall that the k-means algorithm has the following steps:</para>
<para><itemizedlist>
<listitem>
<para>Step 1: initialize k random centroids </para>
</listitem>
<listitem>
<para>Step 2: for every data point, find the nearest centroid (L2 distance or other measurements) and assign the point to it </para>
</listitem>
<listitem>
<para>Step 3: for every centroid, move the centroid to the average of the points assigned to that centroid </para>
</listitem>
<listitem>
<para>Step 4: go to Step 2 until converged (no more changes in the last few iterations) or maximum iterations reached </para>
</listitem>
</itemizedlist>
</para>
<para>We observe Step 2 and Step 3 of the algorithm are parallelizable across individual points for use to harness the power of GPU:</para>
<para><orderedlist>
<listitem>
<para>for every data point, find the nearest centroid (L2 distance or other measurements) and assign the point to it </para>
</listitem>
<listitem>
<para>for every centroid, move the centroid to the average of the points assigned to that centroid. </para>
</listitem>
</orderedlist>
</para>
<para>At a fine-grained level, we request one GPU thread to work on one point for Step 2 and one GPU thread to work on one centroid for Step 3.</para>
<para><programlisting filename=".cpp"><codeline><highlight class="comment">//<sp/>px/py:<sp/>2D<sp/>points</highlight><highlight class="normal"></highlight></codeline>
<codeline><highlight class="normal"></highlight><highlight class="comment">//<sp/>N:<sp/>number<sp/>of<sp/>points</highlight><highlight class="normal"></highlight></codeline>
<codeline><highlight class="normal"></highlight><highlight class="comment">//<sp/>mx/my:<sp/>centroids</highlight><highlight class="normal"></highlight></codeline>
<codeline><highlight class="normal"></highlight><highlight class="comment">//<sp/>K:<sp/>number<sp/>of<sp/>clusters</highlight><highlight class="normal"></highlight></codeline>
<codeline><highlight class="normal"></highlight><highlight class="comment">//<sp/>sx/sy/c:<sp/>storage<sp/>to<sp/>compute<sp/>the<sp/>average</highlight><highlight class="normal"></highlight></codeline>
<codeline><highlight class="normal">__global__<sp/></highlight><highlight class="keywordtype">void</highlight><highlight class="normal"><sp/>assign_clusters(</highlight></codeline>
<codeline><highlight class="normal"><sp/><sp/></highlight><highlight class="keywordtype">float</highlight><highlight class="normal">*<sp/>px,<sp/></highlight><highlight class="keywordtype">float</highlight><highlight class="normal">*<sp/>py,<sp/></highlight><highlight class="keywordtype">int</highlight><highlight class="normal"><sp/>N,<sp/></highlight></codeline>
<codeline><highlight class="normal"><sp/><sp/></highlight><highlight class="keywordtype">float</highlight><highlight class="normal">*<sp/>mx,<sp/></highlight><highlight class="keywordtype">float</highlight><highlight class="normal">*<sp/>my,<sp/></highlight><highlight class="keywordtype">float</highlight><highlight class="normal">*<sp/>sx,<sp/></highlight><highlight class="keywordtype">float</highlight><highlight class="normal">*<sp/>sy,<sp/></highlight><highlight class="keywordtype">int</highlight><highlight class="normal"><sp/>K,<sp/></highlight><highlight class="keywordtype">int</highlight><highlight class="normal">*<sp/>c</highlight></codeline>
<codeline><highlight class="normal">)<sp/>{</highlight></codeline>
<codeline><highlight class="normal"><sp/><sp/></highlight><highlight class="keyword">const</highlight><highlight class="normal"><sp/></highlight><highlight class="keywordtype">int</highlight><highlight class="normal"><sp/>index<sp/>=<sp/>blockIdx.x<sp/>*<sp/>blockDim.x<sp/>+<sp/>threadIdx.x;</highlight></codeline>
<codeline><highlight class="normal"></highlight></codeline>
<codeline><highlight class="normal"><sp/><sp/></highlight><highlight class="keywordflow">if</highlight><highlight class="normal"><sp/>(index<sp/>&gt;=<sp/>N)<sp/>{</highlight></codeline>
<codeline><highlight class="normal"><sp/><sp/><sp/><sp/></highlight><highlight class="keywordflow">return</highlight><highlight class="normal">;</highlight></codeline>
<codeline><highlight class="normal"><sp/><sp/>}</highlight></codeline>
<codeline><highlight class="normal"></highlight></codeline>
<codeline><highlight class="normal"><sp/><sp/></highlight><highlight class="comment">//<sp/>Make<sp/>global<sp/>loads<sp/>once.</highlight><highlight class="normal"></highlight></codeline>
<codeline><highlight class="normal"><sp/><sp/></highlight><highlight class="keywordtype">float</highlight><highlight class="normal"><sp/>x<sp/>=<sp/>px[index];</highlight></codeline>
<codeline><highlight class="normal"><sp/><sp/></highlight><highlight class="keywordtype">float</highlight><highlight class="normal"><sp/>y<sp/>=<sp/>py[index];</highlight></codeline>
<codeline><highlight class="normal"></highlight></codeline>
<codeline><highlight class="normal"><sp/><sp/></highlight><highlight class="keywordtype">float</highlight><highlight class="normal"><sp/>best_dance<sp/>=<sp/>FLT_MAX;</highlight></codeline>
<codeline><highlight class="normal"><sp/><sp/></highlight><highlight class="keywordtype">int</highlight><highlight class="normal"><sp/>best_k<sp/>=<sp/>0;</highlight></codeline>
<codeline><highlight class="normal"><sp/><sp/></highlight><highlight class="keywordflow">for</highlight><highlight class="normal"><sp/>(</highlight><highlight class="keywordtype">int</highlight><highlight class="normal"><sp/>k<sp/>=<sp/>0;<sp/>k<sp/>&lt;<sp/>K;<sp/>++k)<sp/>{</highlight></codeline>
<codeline><highlight class="normal"><sp/><sp/><sp/><sp/></highlight><highlight class="keywordtype">float</highlight><highlight class="normal"><sp/>d<sp/>=<sp/>L2(x,<sp/>y,<sp/>mx[k],<sp/>my[k]);</highlight></codeline>
<codeline><highlight class="normal"><sp/><sp/><sp/><sp/></highlight><highlight class="keywordflow">if</highlight><highlight class="normal"><sp/>(d<sp/>&lt;<sp/>best_d)<sp/>{</highlight></codeline>
<codeline><highlight class="normal"><sp/><sp/><sp/><sp/><sp/><sp/>best_d<sp/>=<sp/>d;</highlight></codeline>
<codeline><highlight class="normal"><sp/><sp/><sp/><sp/><sp/><sp/>best_k<sp/>=<sp/>k;</highlight></codeline>
<codeline><highlight class="normal"><sp/><sp/><sp/><sp/>}<sp/><sp/><sp/></highlight></codeline>
<codeline><highlight class="normal"><sp/><sp/>}</highlight></codeline>
<codeline><highlight class="normal"></highlight></codeline>
<codeline><highlight class="normal"><sp/><sp/>atomicAdd(&amp;sx[best_k],<sp/>x);<sp/></highlight></codeline>
<codeline><highlight class="normal"><sp/><sp/>atomicAdd(&amp;sy[best_k],<sp/>y);<sp/></highlight></codeline>
<codeline><highlight class="normal"><sp/><sp/>atomicAdd(&amp;c<sp/>[best_k],<sp/>1);<sp/></highlight></codeline>
<codeline><highlight class="normal">}</highlight></codeline>
<codeline><highlight class="normal"></highlight></codeline>
<codeline><highlight class="normal"></highlight><highlight class="comment">//<sp/>mx/my:<sp/>centroids,<sp/>sx/sy/c:<sp/>storage<sp/>to<sp/>compute<sp/>the<sp/>average</highlight><highlight class="normal"></highlight></codeline>
<codeline><highlight class="normal">__global__<sp/></highlight><highlight class="keywordtype">void</highlight><highlight class="normal"><sp/>compute_new_means(</highlight></codeline>
<codeline><highlight class="normal"><sp/><sp/></highlight><highlight class="keywordtype">float</highlight><highlight class="normal">*<sp/>mx,<sp/></highlight><highlight class="keywordtype">float</highlight><highlight class="normal">*<sp/>my,<sp/></highlight><highlight class="keywordtype">float</highlight><highlight class="normal">*<sp/>sx,<sp/></highlight><highlight class="keywordtype">float</highlight><highlight class="normal">*<sp/>sy,<sp/></highlight><highlight class="keywordtype">int</highlight><highlight class="normal">*<sp/>c</highlight></codeline>
<codeline><highlight class="normal">)<sp/>{</highlight></codeline>
<codeline><highlight class="normal"><sp/><sp/></highlight><highlight class="keywordtype">int</highlight><highlight class="normal"><sp/>k<sp/>=<sp/>threadIdx.x;</highlight></codeline>
<codeline><highlight class="normal"><sp/><sp/></highlight><highlight class="keywordtype">int</highlight><highlight class="normal"><sp/><ref refid="cpp/algorithm/count" kindref="compound" external="/Users/twhuang/Code/taskflow/doxygen/cppreference-doxygen-web.tag.xml">count</ref><sp/>=<sp/><ref refid="cpp/algorithm/max" kindref="compound" external="/Users/twhuang/Code/taskflow/doxygen/cppreference-doxygen-web.tag.xml">max</ref>(1,<sp/>c[k]);<sp/><sp/></highlight><highlight class="comment">//<sp/>turn<sp/>0/0<sp/>to<sp/>0/1</highlight><highlight class="normal"></highlight></codeline>
<codeline><highlight class="normal"><sp/><sp/>mx[k]<sp/>=<sp/>sx[k]<sp/>/<sp/><ref refid="cpp/algorithm/count" kindref="compound" external="/Users/twhuang/Code/taskflow/doxygen/cppreference-doxygen-web.tag.xml">count</ref>;</highlight></codeline>
<codeline><highlight class="normal"><sp/><sp/>my[k]<sp/>=<sp/>sy[k]<sp/>/<sp/><ref refid="cpp/algorithm/count" kindref="compound" external="/Users/twhuang/Code/taskflow/doxygen/cppreference-doxygen-web.tag.xml">count</ref>;</highlight></codeline>
<codeline><highlight class="normal">}</highlight></codeline>
</programlisting></para>
<para>When we recompute the cluster centroids to be the mean of all points assigned to a particular centroid, multiple GPU threads may access the sum arrays, <computeroutput>sx</computeroutput> and <computeroutput>sy</computeroutput>, and the count array, <computeroutput>c</computeroutput>. To avoid data race, we use a simple <computeroutput>atomicAdd</computeroutput> method.</para>
</sect1>
<sect1 id="kmeans_cudaflow_1DefineTheKMeanscudaFlow">
<title>Define the k-means cudaFlow</title>
<para>Based on the two kernels, we can define the cudaFlow for the k-means workload below:</para>
<para><programlisting filename=".cpp"><codeline><highlight class="comment">//<sp/>N:<sp/>number<sp/>of<sp/>points</highlight><highlight class="normal"></highlight></codeline>
<codeline><highlight class="normal"></highlight><highlight class="comment">//<sp/>K:<sp/>number<sp/>of<sp/>clusters</highlight><highlight class="normal"></highlight></codeline>
<codeline><highlight class="normal"></highlight><highlight class="comment">//<sp/>M:<sp/>number<sp/>of<sp/>iterations</highlight><highlight class="normal"></highlight></codeline>
<codeline><highlight class="normal"></highlight><highlight class="comment">//<sp/>px/py:<sp/>2D<sp/>point<sp/>vector<sp/></highlight><highlight class="normal"></highlight></codeline>
<codeline><highlight class="normal"></highlight><highlight class="keywordtype">void</highlight><highlight class="normal"><sp/>kmeans_gpu(</highlight></codeline>
<codeline><highlight class="normal"><sp/><sp/></highlight><highlight class="keywordtype">int</highlight><highlight class="normal"><sp/>N,<sp/></highlight><highlight class="keywordtype">int</highlight><highlight class="normal"><sp/>K,<sp/></highlight><highlight class="keywordtype">int</highlight><highlight class="normal"><sp/>M,<sp/>cconst<sp/><ref refid="cpp/container/vector" kindref="compound" external="/Users/twhuang/Code/taskflow/doxygen/cppreference-doxygen-web.tag.xml">std::vector&lt;float&gt;</ref>&amp;<sp/>px,<sp/></highlight><highlight class="keyword">const</highlight><highlight class="normal"><sp/><ref refid="cpp/container/vector" kindref="compound" external="/Users/twhuang/Code/taskflow/doxygen/cppreference-doxygen-web.tag.xml">std::vector&lt;float&gt;</ref>&amp;<sp/>py</highlight></codeline>
<codeline><highlight class="normal">)<sp/>{</highlight></codeline>
<codeline><highlight class="normal"><sp/><sp/><ref refid="cpp/container/vector" kindref="compound" external="/Users/twhuang/Code/taskflow/doxygen/cppreference-doxygen-web.tag.xml">std::vector&lt;float&gt;</ref><sp/>h_mx,<sp/>h_my;</highlight></codeline>
<codeline><highlight class="normal"><sp/><sp/></highlight><highlight class="keywordtype">float</highlight><highlight class="normal"><sp/>*d_px,<sp/>*d_py,<sp/>*d_mx,<sp/>*d_my,<sp/>*d_sx,<sp/>*d_sy,<sp/>*d_c;</highlight></codeline>
<codeline><highlight class="normal"></highlight></codeline>
<codeline><highlight class="normal"><sp/><sp/></highlight><highlight class="keywordflow">for</highlight><highlight class="normal">(</highlight><highlight class="keywordtype">int</highlight><highlight class="normal"><sp/>i=0;<sp/>i&lt;K;<sp/>++i)<sp/>{</highlight></codeline>
<codeline><highlight class="normal"><sp/><sp/><sp/><sp/>h_mx.push_back(h_px[i]);</highlight></codeline>
<codeline><highlight class="normal"><sp/><sp/><sp/><sp/>h_my.push_back(h_py[i]);</highlight></codeline>
<codeline><highlight class="normal"><sp/><sp/>}</highlight></codeline>
<codeline><highlight class="normal"></highlight></codeline>
<codeline><highlight class="normal"><sp/><sp/></highlight><highlight class="comment">//<sp/>create<sp/>a<sp/>taskflow<sp/>graph</highlight><highlight class="normal"></highlight></codeline>
<codeline><highlight class="normal"><sp/><sp/><ref refid="classtf_1_1Executor" kindref="compound">tf::Executor</ref><sp/>executor;</highlight></codeline>
<codeline><highlight class="normal"><sp/><sp/><ref refid="classtf_1_1Taskflow" kindref="compound">tf::Taskflow</ref><sp/>taskflow(</highlight><highlight class="stringliteral">&quot;K-Means&quot;</highlight><highlight class="normal">);</highlight></codeline>
<codeline><highlight class="normal"></highlight></codeline>
<codeline><highlight class="normal"><sp/><sp/></highlight><highlight class="keyword">auto</highlight><highlight class="normal"><sp/>allocate_px<sp/>=<sp/>taskflow.emplace([&amp;](){</highlight></codeline>
<codeline><highlight class="normal"><sp/><sp/><sp/><sp/>TF_CHECK_CUDA(cudaMalloc(&amp;d_px,<sp/>N*</highlight><highlight class="keyword">sizeof</highlight><highlight class="normal">(</highlight><highlight class="keywordtype">float</highlight><highlight class="normal">)),<sp/></highlight><highlight class="stringliteral">&quot;failed<sp/>to<sp/>allocate<sp/>d_px&quot;</highlight><highlight class="normal">);</highlight></codeline>
<codeline><highlight class="normal"><sp/><sp/>}).name(</highlight><highlight class="stringliteral">&quot;allocate_px&quot;</highlight><highlight class="normal">);</highlight></codeline>
<codeline><highlight class="normal"></highlight></codeline>
<codeline><highlight class="normal"><sp/><sp/></highlight><highlight class="keyword">auto</highlight><highlight class="normal"><sp/>allocate_py<sp/>=<sp/>taskflow.emplace([&amp;](){</highlight></codeline>
<codeline><highlight class="normal"><sp/><sp/><sp/><sp/>TF_CHECK_CUDA(cudaMalloc(&amp;d_py,<sp/>N*</highlight><highlight class="keyword">sizeof</highlight><highlight class="normal">(</highlight><highlight class="keywordtype">float</highlight><highlight class="normal">)),<sp/></highlight><highlight class="stringliteral">&quot;failed<sp/>to<sp/>allocate<sp/>d_py&quot;</highlight><highlight class="normal">);</highlight></codeline>
<codeline><highlight class="normal"><sp/><sp/>}).name(</highlight><highlight class="stringliteral">&quot;allocate_py&quot;</highlight><highlight class="normal">);</highlight></codeline>
<codeline><highlight class="normal"></highlight></codeline>
<codeline><highlight class="normal"><sp/><sp/></highlight><highlight class="keyword">auto</highlight><highlight class="normal"><sp/>allocate_mx<sp/>=<sp/>taskflow.emplace([&amp;](){</highlight></codeline>
<codeline><highlight class="normal"><sp/><sp/><sp/><sp/>TF_CHECK_CUDA(cudaMalloc(&amp;d_mx,<sp/>K*</highlight><highlight class="keyword">sizeof</highlight><highlight class="normal">(</highlight><highlight class="keywordtype">float</highlight><highlight class="normal">)),<sp/></highlight><highlight class="stringliteral">&quot;failed<sp/>to<sp/>allocate<sp/>d_mx&quot;</highlight><highlight class="normal">);</highlight></codeline>
<codeline><highlight class="normal"><sp/><sp/>}).name(</highlight><highlight class="stringliteral">&quot;allocate_mx&quot;</highlight><highlight class="normal">);</highlight></codeline>
<codeline><highlight class="normal"></highlight></codeline>
<codeline><highlight class="normal"><sp/><sp/></highlight><highlight class="keyword">auto</highlight><highlight class="normal"><sp/>allocate_my<sp/>=<sp/>taskflow.emplace([&amp;](){</highlight></codeline>
<codeline><highlight class="normal"><sp/><sp/><sp/><sp/>TF_CHECK_CUDA(cudaMalloc(&amp;d_my,<sp/>K*</highlight><highlight class="keyword">sizeof</highlight><highlight class="normal">(</highlight><highlight class="keywordtype">float</highlight><highlight class="normal">)),<sp/></highlight><highlight class="stringliteral">&quot;failed<sp/>to<sp/>allocate<sp/>d_my&quot;</highlight><highlight class="normal">);</highlight></codeline>
<codeline><highlight class="normal"><sp/><sp/>}).name(</highlight><highlight class="stringliteral">&quot;allocate_my&quot;</highlight><highlight class="normal">);</highlight></codeline>
<codeline><highlight class="normal"></highlight></codeline>
<codeline><highlight class="normal"><sp/><sp/></highlight><highlight class="keyword">auto</highlight><highlight class="normal"><sp/>allocate_sx<sp/>=<sp/>taskflow.emplace([&amp;](){</highlight></codeline>
<codeline><highlight class="normal"><sp/><sp/><sp/><sp/>TF_CHECK_CUDA(cudaMalloc(&amp;d_sx,<sp/>K*</highlight><highlight class="keyword">sizeof</highlight><highlight class="normal">(</highlight><highlight class="keywordtype">float</highlight><highlight class="normal">)),<sp/></highlight><highlight class="stringliteral">&quot;failed<sp/>to<sp/>allocate<sp/>d_sx&quot;</highlight><highlight class="normal">);</highlight></codeline>
<codeline><highlight class="normal"><sp/><sp/>}).name(</highlight><highlight class="stringliteral">&quot;allocate_sx&quot;</highlight><highlight class="normal">);</highlight></codeline>
<codeline><highlight class="normal"></highlight></codeline>
<codeline><highlight class="normal"><sp/><sp/></highlight><highlight class="keyword">auto</highlight><highlight class="normal"><sp/>allocate_sy<sp/>=<sp/>taskflow.emplace([&amp;](){</highlight></codeline>
<codeline><highlight class="normal"><sp/><sp/><sp/><sp/>TF_CHECK_CUDA(cudaMalloc(&amp;d_sy,<sp/>K*</highlight><highlight class="keyword">sizeof</highlight><highlight class="normal">(</highlight><highlight class="keywordtype">float</highlight><highlight class="normal">)),<sp/></highlight><highlight class="stringliteral">&quot;failed<sp/>to<sp/>allocate<sp/>d_sy&quot;</highlight><highlight class="normal">);</highlight></codeline>
<codeline><highlight class="normal"><sp/><sp/>}).name(</highlight><highlight class="stringliteral">&quot;allocate_sy&quot;</highlight><highlight class="normal">);</highlight></codeline>
<codeline><highlight class="normal"><sp/><sp/></highlight><highlight class="keyword">auto</highlight><highlight class="normal"><sp/>allocate_c<sp/>=<sp/>taskflow.emplace([&amp;](){</highlight></codeline>
<codeline><highlight class="normal"><sp/><sp/><sp/><sp/>TF_CHECK_CUDA(cudaMalloc(&amp;d_c,<sp/>K*</highlight><highlight class="keyword">sizeof</highlight><highlight class="normal">(</highlight><highlight class="keywordtype">float</highlight><highlight class="normal">)),<sp/></highlight><highlight class="stringliteral">&quot;failed<sp/>to<sp/>allocate<sp/>dc&quot;</highlight><highlight class="normal">);</highlight></codeline>
<codeline><highlight class="normal"><sp/><sp/>}).name(</highlight><highlight class="stringliteral">&quot;allocate_c&quot;</highlight><highlight class="normal">);</highlight></codeline>
<codeline><highlight class="normal"></highlight></codeline>
<codeline><highlight class="normal"><sp/><sp/></highlight><highlight class="keyword">auto</highlight><highlight class="normal"><sp/>h2d<sp/>=<sp/>taskflow.emplace([&amp;](){</highlight></codeline>
<codeline><highlight class="normal"><sp/><sp/><sp/><sp/>cudaMemcpy(d_px,<sp/>h_px.data(),<sp/>N*</highlight><highlight class="keyword">sizeof</highlight><highlight class="normal">(</highlight><highlight class="keywordtype">float</highlight><highlight class="normal">),<sp/>cudaMemcpyDefault);</highlight></codeline>
<codeline><highlight class="normal"><sp/><sp/><sp/><sp/>cudaMemcpy(d_py,<sp/>h_py.data(),<sp/>N*</highlight><highlight class="keyword">sizeof</highlight><highlight class="normal">(</highlight><highlight class="keywordtype">float</highlight><highlight class="normal">),<sp/>cudaMemcpyDefault);</highlight></codeline>
<codeline><highlight class="normal"><sp/><sp/><sp/><sp/>cudaMemcpy(d_mx,<sp/>h_mx.data(),<sp/>K*</highlight><highlight class="keyword">sizeof</highlight><highlight class="normal">(</highlight><highlight class="keywordtype">float</highlight><highlight class="normal">),<sp/>cudaMemcpyDefault);</highlight></codeline>
<codeline><highlight class="normal"><sp/><sp/><sp/><sp/>cudaMemcpy(d_my,<sp/>h_my.data(),<sp/>K*</highlight><highlight class="keyword">sizeof</highlight><highlight class="normal">(</highlight><highlight class="keywordtype">float</highlight><highlight class="normal">),<sp/>cudaMemcpyDefault);</highlight></codeline>
<codeline><highlight class="normal"><sp/><sp/>}).name(</highlight><highlight class="stringliteral">&quot;h2d&quot;</highlight><highlight class="normal">);</highlight></codeline>
<codeline><highlight class="normal"></highlight></codeline>
<codeline><highlight class="normal"><sp/><sp/></highlight><highlight class="keyword">auto</highlight><highlight class="normal"><sp/>kmeans<sp/>=<sp/>taskflow.emplace([&amp;](){</highlight></codeline>
<codeline><highlight class="normal"></highlight></codeline>
<codeline><highlight class="normal"><sp/><sp/><sp/><sp/><ref refid="classtf_1_1cudaFlow" kindref="compound">tf::cudaFlow</ref><sp/>cf;</highlight></codeline>
<codeline><highlight class="normal"></highlight></codeline>
<codeline><highlight class="normal"><sp/><sp/><sp/><sp/></highlight><highlight class="keyword">auto</highlight><highlight class="normal"><sp/>zero_c<sp/>=<sp/>cf.<ref refid="classtf_1_1cudaFlow_1a40172fac4464f6d805f75921ea3c2a3b" kindref="member">zero</ref>(d_c,<sp/>K).<ref refid="classtf_1_1cudaTask_1ab81b4f71a44af8d61758524f0c274962" kindref="member">name</ref>(</highlight><highlight class="stringliteral">&quot;zero_c&quot;</highlight><highlight class="normal">);</highlight></codeline>
<codeline><highlight class="normal"><sp/><sp/><sp/><sp/></highlight><highlight class="keyword">auto</highlight><highlight class="normal"><sp/>zero_sx<sp/>=<sp/>cf.<ref refid="classtf_1_1cudaFlow_1a40172fac4464f6d805f75921ea3c2a3b" kindref="member">zero</ref>(d_sx,<sp/>K).<ref refid="classtf_1_1cudaTask_1ab81b4f71a44af8d61758524f0c274962" kindref="member">name</ref>(</highlight><highlight class="stringliteral">&quot;zero_sx&quot;</highlight><highlight class="normal">);</highlight></codeline>
<codeline><highlight class="normal"><sp/><sp/><sp/><sp/></highlight><highlight class="keyword">auto</highlight><highlight class="normal"><sp/>zero_sy<sp/>=<sp/>cf.<ref refid="classtf_1_1cudaFlow_1a40172fac4464f6d805f75921ea3c2a3b" kindref="member">zero</ref>(d_sy,<sp/>K).<ref refid="classtf_1_1cudaTask_1ab81b4f71a44af8d61758524f0c274962" kindref="member">name</ref>(</highlight><highlight class="stringliteral">&quot;zero_sy&quot;</highlight><highlight class="normal">);</highlight></codeline>
<codeline><highlight class="normal"></highlight></codeline>
<codeline><highlight class="normal"><sp/><sp/><sp/><sp/></highlight><highlight class="keyword">auto</highlight><highlight class="normal"><sp/>cluster<sp/>=<sp/>cf.<ref refid="classtf_1_1cudaFlow_1a68f666503d13a7b80fb7399fb2f0c153" kindref="member">kernel</ref>(</highlight></codeline>
<codeline><highlight class="normal"><sp/><sp/><sp/><sp/><sp/><sp/>(N+512-1)<sp/>/<sp/>512,<sp/>512,<sp/>0,</highlight></codeline>
<codeline><highlight class="normal"><sp/><sp/><sp/><sp/><sp/><sp/>assign_clusters,<sp/>d_px,<sp/>d_py,<sp/>N,<sp/>d_mx,<sp/>d_my,<sp/>d_sx,<sp/>d_sy,<sp/>K,<sp/>d_c</highlight></codeline>
<codeline><highlight class="normal"><sp/><sp/><sp/><sp/>).<ref refid="classtf_1_1cudaTask_1ab81b4f71a44af8d61758524f0c274962" kindref="member">name</ref>(</highlight><highlight class="stringliteral">&quot;cluster&quot;</highlight><highlight class="normal">);</highlight></codeline>
<codeline><highlight class="normal"></highlight></codeline>
<codeline><highlight class="normal"><sp/><sp/><sp/><sp/></highlight><highlight class="keyword">auto</highlight><highlight class="normal"><sp/>new_centroid<sp/>=<sp/>cf.<ref refid="classtf_1_1cudaFlow_1a68f666503d13a7b80fb7399fb2f0c153" kindref="member">kernel</ref>(</highlight></codeline>
<codeline><highlight class="normal"><sp/><sp/><sp/><sp/><sp/><sp/>1,<sp/>K,<sp/>0,</highlight></codeline>
<codeline><highlight class="normal"><sp/><sp/><sp/><sp/><sp/><sp/>compute_new_means,<sp/>d_mx,<sp/>d_my,<sp/>d_sx,<sp/>d_sy,<sp/>d_c</highlight></codeline>
<codeline><highlight class="normal"><sp/><sp/><sp/><sp/>).<ref refid="classtf_1_1cudaTask_1ab81b4f71a44af8d61758524f0c274962" kindref="member">name</ref>(</highlight><highlight class="stringliteral">&quot;new_centroid&quot;</highlight><highlight class="normal">);</highlight></codeline>
<codeline><highlight class="normal"></highlight></codeline>
<codeline><highlight class="normal"><sp/><sp/><sp/><sp/>cluster.<ref refid="classtf_1_1cudaTask_1abdd68287ec4dff4216af34d1db44d1b4" kindref="member">precede</ref>(new_centroid)</highlight></codeline>
<codeline><highlight class="normal"><sp/><sp/><sp/><sp/><sp/><sp/><sp/><sp/><sp/><sp/><sp/>.<ref refid="classtf_1_1cudaTask_1a4a9ca1a34bac47e4c9b04eb4fb2f7775" kindref="member">succeed</ref>(zero_c,<sp/>zero_sx,<sp/>zero_sy);</highlight></codeline>
<codeline><highlight class="normal"></highlight></codeline>
<codeline><highlight class="normal"><sp/><sp/><sp/><sp/></highlight><highlight class="comment">//<sp/>Repeat<sp/>the<sp/>execution<sp/>for<sp/>M<sp/>times</highlight><highlight class="normal"></highlight></codeline>
<codeline><highlight class="normal"><sp/><sp/><sp/><sp/><ref refid="classtf_1_1cudaStream" kindref="compound">tf::cudaStream</ref><sp/>stream;</highlight></codeline>
<codeline><highlight class="normal"><sp/><sp/><sp/><sp/></highlight><highlight class="keywordflow">for</highlight><highlight class="normal">(</highlight><highlight class="keywordtype">int</highlight><highlight class="normal"><sp/>i=0;<sp/>i&lt;M;<sp/>i++)<sp/>{</highlight></codeline>
<codeline><highlight class="normal"><sp/><sp/><sp/><sp/><sp/><sp/>cf.<ref refid="classtf_1_1cudaFlow_1ae6810f7de27e5a347331aacfce67bea1" kindref="member">run</ref>(stream);</highlight></codeline>
<codeline><highlight class="normal"><sp/><sp/><sp/><sp/>}</highlight></codeline>
<codeline><highlight class="normal"><sp/><sp/><sp/><sp/>stream.<ref refid="classtf_1_1cudaStream_1a1a81d6005e8d60ad082dba2303a8aa30" kindref="member">synchronize</ref>();</highlight></codeline>
<codeline><highlight class="normal"><sp/><sp/>}).name(</highlight><highlight class="stringliteral">&quot;update_means&quot;</highlight><highlight class="normal">);</highlight></codeline>
<codeline><highlight class="normal"></highlight></codeline>
<codeline><highlight class="normal"><sp/><sp/></highlight><highlight class="keyword">auto</highlight><highlight class="normal"><sp/>stop<sp/>=<sp/>taskflow.emplace([&amp;](){</highlight></codeline>
<codeline><highlight class="normal"><sp/><sp/><sp/><sp/>cudaMemcpy(h_mx.data(),<sp/>d_mx,<sp/>K*</highlight><highlight class="keyword">sizeof</highlight><highlight class="normal">(</highlight><highlight class="keywordtype">float</highlight><highlight class="normal">),<sp/>cudaMemcpyDefault);</highlight></codeline>
<codeline><highlight class="normal"><sp/><sp/><sp/><sp/>cudaMemcpy(h_my.data(),<sp/>d_my,<sp/>K*</highlight><highlight class="keyword">sizeof</highlight><highlight class="normal">(</highlight><highlight class="keywordtype">float</highlight><highlight class="normal">),<sp/>cudaMemcpyDefault);</highlight></codeline>
<codeline><highlight class="normal"><sp/><sp/>}).name(</highlight><highlight class="stringliteral">&quot;d2h&quot;</highlight><highlight class="normal">);</highlight></codeline>
<codeline><highlight class="normal"></highlight></codeline>
<codeline><highlight class="normal"><sp/><sp/></highlight><highlight class="keyword">auto</highlight><highlight class="normal"><sp/><ref refid="cpp/memory/c/free" kindref="compound" external="/Users/twhuang/Code/taskflow/doxygen/cppreference-doxygen-web.tag.xml">free</ref><sp/>=<sp/>taskflow.emplace([&amp;](){</highlight></codeline>
<codeline><highlight class="normal"><sp/><sp/><sp/><sp/>cudaFree(d_px);</highlight></codeline>
<codeline><highlight class="normal"><sp/><sp/><sp/><sp/>cudaFree(d_py);</highlight></codeline>
<codeline><highlight class="normal"><sp/><sp/><sp/><sp/>cudaFree(d_mx);</highlight></codeline>
<codeline><highlight class="normal"><sp/><sp/><sp/><sp/>cudaFree(d_my);</highlight></codeline>
<codeline><highlight class="normal"><sp/><sp/><sp/><sp/>cudaFree(d_sx);</highlight></codeline>
<codeline><highlight class="normal"><sp/><sp/><sp/><sp/>cudaFree(d_sy);</highlight></codeline>
<codeline><highlight class="normal"><sp/><sp/><sp/><sp/>cudaFree(d_c);</highlight></codeline>
<codeline><highlight class="normal"><sp/><sp/>}).name(</highlight><highlight class="stringliteral">&quot;free&quot;</highlight><highlight class="normal">);</highlight></codeline>
<codeline><highlight class="normal"></highlight></codeline>
<codeline><highlight class="normal"><sp/><sp/></highlight><highlight class="comment">//<sp/>build<sp/>up<sp/>the<sp/>dependency</highlight><highlight class="normal"></highlight></codeline>
<codeline><highlight class="normal"><sp/><sp/>h2d.succeed(allocate_px,<sp/>allocate_py,<sp/>allocate_mx,<sp/>allocate_my);</highlight></codeline>
<codeline><highlight class="normal"></highlight></codeline>
<codeline><highlight class="normal"><sp/><sp/>kmeans.succeed(allocate_sx,<sp/>allocate_sy,<sp/>allocate_c,<sp/>h2d)</highlight></codeline>
<codeline><highlight class="normal"><sp/><sp/><sp/><sp/><sp/><sp/><sp/><sp/>.precede(stop);</highlight></codeline>
<codeline><highlight class="normal"></highlight></codeline>
<codeline><highlight class="normal"><sp/><sp/>stop.precede(free);</highlight></codeline>
<codeline><highlight class="normal"></highlight></codeline>
<codeline><highlight class="normal"><sp/><sp/></highlight><highlight class="comment">//<sp/>run<sp/>the<sp/>taskflow</highlight><highlight class="normal"></highlight></codeline>
<codeline><highlight class="normal"><sp/><sp/>executor.<ref refid="classtf_1_1Executor_1a519777f5783981d534e9e53b99712069" kindref="member">run</ref>(taskflow).wait();</highlight></codeline>
<codeline><highlight class="normal"></highlight></codeline>
<codeline><highlight class="normal"><sp/><sp/></highlight><highlight class="comment">//std::cout<sp/>&lt;&lt;<sp/>&quot;dumping<sp/>kmeans<sp/>graph<sp/>...\n&quot;;</highlight><highlight class="normal"></highlight></codeline>
<codeline><highlight class="normal"><sp/><sp/>taskflow.dump(<ref refid="cpp/io/basic_ostream" kindref="compound" external="/Users/twhuang/Code/taskflow/doxygen/cppreference-doxygen-web.tag.xml">std::cout</ref>);</highlight></codeline>
<codeline><highlight class="normal"><sp/><sp/></highlight><highlight class="keywordflow">return</highlight><highlight class="normal"><sp/>{h_mx,<sp/>h_my};</highlight></codeline>
<codeline><highlight class="normal">}</highlight></codeline>
</programlisting></para>
<para>The first dump before executing the taskflow produces the following diagram. The condition tasks introduces a cycle between itself and <computeroutput>update_means</computeroutput>. Each time it goes back to <computeroutput>update_means</computeroutput>, the cudaFlow is reconstructed with captured parameters in the closure and offloaded to the GPU.</para>
<para><dotfile name="kmeans_3.dot"></dotfile>
</para>
<para>The main cudaFlow task, <computeroutput>update_means</computeroutput>, must not run before all required data has settled down. It precedes a condition task that circles back to itself until we reach <computeroutput>M</computeroutput> iterations. When iteration completes, the condition task directs the execution path to the cudaFlow, <computeroutput>h2d</computeroutput>, to copy the results of clusters to <computeroutput>h_mx</computeroutput> and <computeroutput>h_my</computeroutput> and then deallocate all GPU memory.</para>
</sect1>
<sect1 id="kmeans_cudaflow_1KMeanscudaFlowBenchmarking">
<title>Benchmarking</title>
<para>We run three versions of k-means, sequential CPU, parallel CPUs, and one GPU, on a machine of 12 Intel i7-8700 CPUs at 3.20 GHz and a Nvidia RTX 2080 GPU using various numbers of 2D point counts and iterations.</para>
<para> <table rows="6" cols="6"><row>
<entry thead="yes" align='center'><para>N   </para>
</entry><entry thead="yes" align='center'><para>K   </para>
</entry><entry thead="yes" align='center'><para>M   </para>
</entry><entry thead="yes" align='center'><para>CPU Sequential   </para>
</entry><entry thead="yes" align='center'><para>CPU Parallel   </para>
</entry><entry thead="yes" align='center'><para>GPU    </para>
</entry></row>
<row>
<entry thead="no" align='center'><para>10   </para>
</entry><entry thead="no" align='center'><para>5   </para>
</entry><entry thead="no" align='center'><para>10   </para>
</entry><entry thead="no" align='center'><para>0.14 ms   </para>
</entry><entry thead="no" align='center'><para>77 ms   </para>
</entry><entry thead="no" align='center'><para>1 ms    </para>
</entry></row>
<row>
<entry thead="no" align='center'><para>100   </para>
</entry><entry thead="no" align='center'><para>10   </para>
</entry><entry thead="no" align='center'><para>100   </para>
</entry><entry thead="no" align='center'><para>0.56 ms   </para>
</entry><entry thead="no" align='center'><para>86 ms   </para>
</entry><entry thead="no" align='center'><para>7 ms    </para>
</entry></row>
<row>
<entry thead="no" align='center'><para>1000   </para>
</entry><entry thead="no" align='center'><para>10   </para>
</entry><entry thead="no" align='center'><para>1000   </para>
</entry><entry thead="no" align='center'><para>10 ms   </para>
</entry><entry thead="no" align='center'><para>98 ms   </para>
</entry><entry thead="no" align='center'><para>55 ms    </para>
</entry></row>
<row>
<entry thead="no" align='center'><para>10000   </para>
</entry><entry thead="no" align='center'><para>10   </para>
</entry><entry thead="no" align='center'><para>10000   </para>
</entry><entry thead="no" align='center'><para>1006 ms   </para>
</entry><entry thead="no" align='center'><para>713 ms   </para>
</entry><entry thead="no" align='center'><para>458 ms    </para>
</entry></row>
<row>
<entry thead="no" align='center'><para>100000   </para>
</entry><entry thead="no" align='center'><para>10   </para>
</entry><entry thead="no" align='center'><para>100000   </para>
</entry><entry thead="no" align='center'><para>102483 ms   </para>
</entry><entry thead="no" align='center'><para>49966 ms   </para>
</entry><entry thead="no" align='center'><para>7952 ms   </para>
</entry></row>
</table>
</para>
<para>When the number of points is larger than 10K, both parallel CPU and GPU implementations start to pick up the speed over than the sequential version. We can see that using the built-in predicate, tf::cudaFlow::offload_n, can avoid repetitively creating the graph over and over, resulting in two times faster than conditional tasking. </para>
</sect1>
    </detaileddescription>
    <location file="examples/kmeans_cudaflow.dox"/>
  </compounddef>
</doxygen>
