<?xml version="1.0" encoding="utf-8"?> 
<feed xmlns="http://www.w3.org/2005/Atom" xml:lang="en-us">
    <generator uri="https://gohugo.io/" version="0.141.0">Hugo</generator><title type="html"><![CDATA[Cuda on Blog]]></title>
    
    
    
            <link href="https://blog.scientific-python.org/tags/cuda/" rel="alternate" type="text/html" title="html" />
            <link href="https://blog.scientific-python.org/tags/cuda/atom.xml" rel="self" type="application/atom" title="atom" />
    <updated>2026-08-15T11:34:12+00:00</updated>
    
    
    
    
        <id>https://blog.scientific-python.org/tags/cuda/</id>
    
        
        <entry>
            <title type="html"><![CDATA[Rewriting Awkward Array's GPU kernels in Python with NVIDIA's cuda.compute]]></title>
            <link href="https://blog.scientific-python.org/awkward/rewriting-gpu-kernels-cuda-compute/?utm_source=atom_feed" rel="alternate" type="text/html" />
            
            
                <id>https://blog.scientific-python.org/awkward/rewriting-gpu-kernels-cuda-compute/</id>
            
            
            <published>2026-08-14T00:00:00+00:00</published>
            <updated>2026-08-14T00:00:00+00:00</updated>
            
            
            <content type="html"><![CDATA[<blockquote>How the Awkward Array and NVIDIA teams replaced thousands of lines of hand-written CUDA C++ with Python built on cuda.compute — ending up with less code that runs faster.</blockquote><p><em>Thousands of lines of hand-written CUDA C++, now Python. Less code, and it runs faster.</em></p>
<p>A single collision event in a particle detector holds a variable number of particles, each with a variable number of measurements:</p>

<div class="highlight">
  <pre>[[1.1, 2.2, 3.3], [], [4.4, 5.5]]</pre>
</div>

<p><a href="https://awkward-array.org/">Awkward Array</a> is a Python library for manipulating nested, variable-length (&ldquo;ragged&rdquo;) data like this with NumPy-like idioms. It stores that data flat, as one <code>content</code> buffer holding every value contiguously plus an <code>offsets</code> array marking where each sublist begins and ends:</p>

<div class="highlight">
  <pre>content:  [1.1, 2.2, 3.3, 4.4, 5.5]
offsets:  [0, 3, 3, 5]        # the empty middle list spans no elements</pre>
</div>

<p>Nothing is wasted on padding, but <em>every</em> operation must then be written in terms of those two buffers rather than a simple shape. That is where dense GPU tensor frameworks stop helping: they want rectangles. For several years, Awkward&rsquo;s answer was a dictionary of hand-written CUDA C++ kernels compiled at runtime with CuPy.</p>
<p>The Awkward Array and NVIDIA teams have now rebuilt that layer on <a href="https://nvidia.github.io/cccl/unstable/python/compute/index.html"><code>cuda.compute</code></a>, which brings the CUDA C++ parallel-algorithm libraries CUB and Thrust (the reductions, scans, and sorts that power production GPU software) into Python as ordinary callables. Instead of writing a kernel, the backend now composes ones that already exist. Four results stand out.</p>
<h2 id="1-the-gpu-code-is-python-now">1. The GPU code is Python now<a class="headerlink" href="#1-the-gpu-code-is-python-now" title="Link to this heading">#</a></h2>
<p><code>ak.min</code>, the minimum over each ragged sublist, took <em>three</em> kernel launches in CUDA C++: initialize a scratch buffer, reduce within each block using shared memory and explicit thread synchronization, and copy the result out. Each communicated with the next through global memory.</p>
<p>It is now a single call to a library primitive:</p>


<div class="highlight">
  <pre class="chroma"><code><span class="line"><span class="cl"><span class="kn">import</span> <span class="nn">cupy</span> <span class="k">as</span> <span class="nn">cp</span><span class="o">,</span> <span class="nn">numpy</span> <span class="k">as</span> <span class="nn">np</span>
</span></span><span class="line"><span class="cl"><span class="kn">from</span> <span class="nn">cuda.compute</span> <span class="kn">import</span> <span class="n">OpKind</span><span class="p">,</span> <span class="n">segmented_reduce</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">def</span> <span class="nf">awkward_reduce_min</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">    <span class="n">toptr</span><span class="p">:</span> <span class="n">cp</span><span class="o">.</span><span class="n">ndarray</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="n">fromptr</span><span class="p">:</span> <span class="n">cp</span><span class="o">.</span><span class="n">ndarray</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="n">offsets</span><span class="p">:</span> <span class="n">cp</span><span class="o">.</span><span class="n">ndarray</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="n">outlength</span><span class="p">:</span> <span class="nb">int</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="n">identity</span><span class="p">:</span> <span class="nb">float</span><span class="p">,</span>
</span></span><span class="line"><span class="cl"><span class="p">)</span> <span class="o">-&gt;</span> <span class="kc">None</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="n">toptr</span><span class="p">[:</span><span class="n">outlength</span><span class="p">]</span> <span class="o">=</span> <span class="n">identity</span>
</span></span><span class="line"><span class="cl">    <span class="n">segmented_reduce</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">        <span class="n">fromptr</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="n">toptr</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="n">offsets</span><span class="p">[:</span><span class="o">-</span><span class="mi">1</span><span class="p">],</span>
</span></span><span class="line"><span class="cl">        <span class="n">offsets</span><span class="p">[</span><span class="mi">1</span><span class="p">:],</span>
</span></span><span class="line"><span class="cl">        <span class="n">OpKind</span><span class="o">.</span><span class="n">MINIMUM</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="n">np</span><span class="o">.</span><span class="n">asarray</span><span class="p">(</span><span class="n">identity</span><span class="p">,</span> <span class="n">dtype</span><span class="o">=</span><span class="n">fromptr</span><span class="o">.</span><span class="n">dtype</span><span class="p">),</span>
</span></span><span class="line"><span class="cl">        <span class="n">outlength</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="p">)</span></span></span></code></pre>
</div>
<p><code>segmented_reduce</code> performs one reduction per segment, and <code>offsets[:-1]</code> and <code>offsets[1:]</code> hand it the start and end of each sublist directly. No CUDA C++, no synchronization, no scratch buffers: the library handles all of it.</p>
<p>Awkward&rsquo;s user-facing API is unchanged. Underneath, 2.10.0 routes <strong>106 of its 133 GPU kernels (80%)</strong> through <code>cuda.compute</code>, up from none in 2.8.11. Every reduction and the sort now run through it with no hand-written implementation remaining. The 27 still in CUDA C++ handle structural work like jagged indexing, padding, and validity checks. Their control flow depends on the ragged layout itself, which fits the segmented primitives less naturally. That migration is ongoing. Counting only code that must actually be maintained, the switch to <code>cuda.compute</code> has so far cut hand-written CUDA C++ from <strong>8,288 lines to 2,170 — a 74% reduction</strong>.</p>
<h2 id="2-the-abstraction-made-it-faster">2. The abstraction made it faster<a class="headerlink" href="#2-the-abstraction-made-it-faster" title="Link to this heading">#</a></h2>
<p>We might expect to pay something for the abstraction.</p>
<p>Over 5,000,000 ragged sublists, <code>ak.argmin</code> takes <strong>0.96 ms per call</strong> as a hand-written CUDA kernel and <strong>0.39 ms</strong> through <code>cuda.compute</code>: <strong>2.5x faster, with identical output</strong>.</p>
<p>That speedup is inherited rather than hand-tuned, which is exactly the point of building on CUB: segmented reductions are difficult to write well by hand, and CUB&rsquo;s have been tuned per architecture for years. Awkward gets that tuning now, and the next architecture&rsquo;s when it ships, without changing its own code.</p>
<h2 id="3-a-whole-physics-formula-can-collapse-into-one-kernel">3. A whole physics formula can collapse into one kernel<a class="headerlink" href="#3-a-whole-physics-formula-can-collapse-into-one-kernel" title="Link to this heading">#</a></h2>
<p>The first two results came from the migration. For this one, a user drops down to <code>cuda.compute</code> and composes the primitives by hand.</p>
<p>Awkward evaluates eagerly: every operation returns a real array, so a chain of them writes an intermediate to global memory at each step and reads it back at the next. <code>cuda.compute</code> algorithms instead accept <strong>iterators</strong> that are evaluated lazily as the algorithm runs, letting many logical steps ride along inside a single pass. That is <a href="https://developer.nvidia.com/blog/kernel-fusion-in-nvidia-cuda-optimizing-memory-traffic-and-launch-overhead/">kernel fusion</a>, and it saves both the memory traffic and the launch overhead.</p>
<h3 id="example-di-muon-invariant-mass">Example: di-muon invariant mass<a class="headerlink" href="#example-di-muon-invariant-mass" title="Link to this heading">#</a></h3>
<p>The opposite-sign di-muon invariant mass, a standard reconstruction in particle physics, combines a few measured quantities for every pair of particles in an event. In Awkward, it is one line:</p>


<div class="highlight">
  <pre class="chroma"><code><span class="line"><span class="cl"><span class="n">mu1</span><span class="p">,</span> <span class="n">mu2</span> <span class="o">=</span> <span class="n">ak</span><span class="o">.</span><span class="n">unzip</span><span class="p">(</span><span class="n">ak</span><span class="o">.</span><span class="n">combinations</span><span class="p">(</span><span class="n">muons</span><span class="p">,</span> <span class="mi">2</span><span class="p">))</span>
</span></span><span class="line"><span class="cl"><span class="n">mass</span> <span class="o">=</span> <span class="n">np</span><span class="o">.</span><span class="n">sqrt</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">    <span class="mi">2</span> <span class="o">*</span> <span class="n">mu1</span><span class="o">.</span><span class="n">pt</span> <span class="o">*</span> <span class="n">mu2</span><span class="o">.</span><span class="n">pt</span> <span class="o">*</span> <span class="p">(</span><span class="n">np</span><span class="o">.</span><span class="n">cosh</span><span class="p">(</span><span class="n">mu1</span><span class="o">.</span><span class="n">eta</span> <span class="o">-</span> <span class="n">mu2</span><span class="o">.</span><span class="n">eta</span><span class="p">)</span> <span class="o">-</span> <span class="n">np</span><span class="o">.</span><span class="n">cos</span><span class="p">(</span><span class="n">mu1</span><span class="o">.</span><span class="n">phi</span> <span class="o">-</span> <span class="n">mu2</span><span class="o">.</span><span class="n">phi</span><span class="p">))</span>
</span></span><span class="line"><span class="cl"><span class="p">)</span></span></span></code></pre>
</div>
<p>Evaluated step by step, that chain of arithmetic and trigonometric operations becomes one or more kernels per step, with every intermediate written out as a full-length array and read back.</p>
<p>Written by hand as one <code>cuda.compute</code> call, the whole formula becomes a single operator: a <code>gpu_struct</code> keeps each particle&rsquo;s fields together, a <code>ZipIterator</code> combines them, and a <code>PermutationIterator</code> produces each pair on demand. The operator sees one complete pair at a time, and no intermediate is ever built.</p>
<p>Over all such pairs in a CMS open-data sample:</p>
<table>
  <thead>
      <tr>
          <th></th>
          <th><strong>kernel launches</strong></th>
          <th><strong>memory operations</strong></th>
          <th><strong>GPU time</strong></th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>step by step</td>
          <td>88</td>
          <td>212</td>
          <td>25.7 ms</td>
      </tr>
      <tr>
          <td>fused into one call</td>
          <td><strong>1</strong></td>
          <td>45</td>
          <td><strong>10.2 ms</strong></td>
      </tr>
  </tbody>
</table>
<p>2.5x faster, with nothing allocated in between.</p>
<h2 id="4-at-analysis-scale-the-gap-widens">4. At analysis scale, the gap widens<a class="headerlink" href="#4-at-analysis-scale-the-gap-widens" title="Link to this heading">#</a></h2>
<p>We ran the <a href="https://github.com/CoffeaTeam/coffea-benchmarks">ADL benchmark queries</a> (a standard set of physics-analysis tasks) on CMS 2012 open data, against released Awkward 2.8.11 on its hand-written CuPy backend, the last version before <code>cuda.compute</code>. For the two combinatoric queries below, the Awkward expression is <strong>unchanged</strong>; only the backend underneath differs, so the comparison isolates the migration itself.</p>
<p>Measured on the <strong>GPU compute stage</strong> at 100k, 1M, and 10M events, both speed up by margins that grow with the data:</p>
<ul>
<li>the di-muon reconstruction from section 3: <strong>60x → 422x → 3634x</strong></li>
<li>a second combinatoric query: <strong>45x rising to 250x</strong></li>
</ul>
<p><code>cuda.compute</code>&rsquo;s time stays approximately constant across those sizes while the hand-written implementation grows super-linearly, so the gap widens as data grows rather than closing.</p>
<h2 id="the-result-that-isnt-a-number">The result that isn&rsquo;t a number<a class="headerlink" href="#the-result-that-isnt-a-number" title="Link to this heading">#</a></h2>
<p>A stated goal of the Awkward Array project is to let physicists and data analysts write high-performance code in Python without GPU expertise. The old backend required contributors to understand CUDA thread hierarchies, atomics, and shared-memory behavior before they could add or fix a kernel. The new one asks for an ordinary Python function and a call to the right primitive. Domain scientists can read it, review it, and unit-test its logic without a GPU.</p>
<p>Awkward knows the problem. <code>cuda.compute</code> knows the hardware. The result is Python that&rsquo;s simpler and faster than the CUDA C++ it replaced.</p>
<p><em>Full methodology, per-query results, and the code-counting rules are in &ldquo;GPU-Accelerated Awkward Arrays with CUDA Python&rdquo; by Ashwin Srinath (NVIDIA) and Ianna Osborne (Princeton University), Proceedings of the 24th Python in Science Conference (SciPy 2026). All measurements were taken on an NVIDIA RTX PRO 6000 Blackwell Server Edition with CUDA 13.2, <code>cuda.compute</code> 1.1.0, and CuPy 14.1.1; every benchmark was run twice on independent machines, with structural counts identical and timings agreeing to within a few percent. Migration progress is tracked in <a href="https://github.com/scikit-hep/awkward/issues/3793">scikit-hep/awkward#3793</a>.</em></p>
<p><em>Much of the kernel migration was implemented by Maxym Naumchyk. Thanks also to the <code>cuda.compute</code> and CUB/Thrust developers at NVIDIA and to the Scikit-HEP community. This work was supported in part by NSF grants OAC-1450377, OAC-1836650, OAC-2103945, PHY-2121686, and PHY-2323298.</em></p>
]]></content>
            
                 
                    
                 
                    
                         
                        
                            
                             
                                <category scheme="taxonomy:Tags" term="awkward-array" label="Awkward Array" />
                             
                                <category scheme="taxonomy:Tags" term="gpu" label="GPU" />
                             
                                <category scheme="taxonomy:Tags" term="cuda" label="CUDA" />
                             
                                <category scheme="taxonomy:Tags" term="cuda.compute" label="cuda.compute" />
                             
                                <category scheme="taxonomy:Tags" term="scikit-hep" label="Scikit-HEP" />
                            
                        
                    
                
            
        </entry>
    
        
        <entry>
            <title type="html"><![CDATA[Automated tests with GPUs for your project]]></title>
            <link href="https://blog.scientific-python.org/scikit-learn/gpu-ci/?utm_source=atom_feed" rel="alternate" type="text/html" />
            
            
                <id>https://blog.scientific-python.org/scikit-learn/gpu-ci/</id>
            
            
            <published>2024-08-15T00:00:00+00:00</published>
            <updated>2024-08-15T00:00:00+00:00</updated>
            
            
            <content type="html"><![CDATA[<blockquote>Setting up CI with a GPU to test your code</blockquote><p>TL;DR: If you have GPU code in your project, setup a GitHub hosted GPU runner today.
It is fairly quick to do and will free you from having to run tests manually.</p>
<p>Writing automated tests for your code base and certainly for the more complex parts
of it has become as normal as brushing your teeth in the morning. Having a system
that automatically runs a project&rsquo;s tests for every Pull Request
is completely normal. However, until recently it was very complex and expensive
to setup a system that can run tests on a system with a GPU. This means that,
when dealing with GPU related code, we were thrown back into the dark ages where
you had to rely on manual testing.</p>
<p>In this blog post I will describe how we set up a GitHub Action based GPU runner
for the scikit-learn project and the things we learnt along the way. The goal is
to give you some additional information and details about the setup we now use.</p>
<ul>
<li><a href="/scikit-learn/gpu-ci/#larger-runners-with-gpus">Setting up larger runners for your project</a></li>
<li><a href="/scikit-learn/gpu-ci/#vm-image-contents">VM image contents and setup</a></li>
<li><a href="/scikit-learn/gpu-ci/#workflow-configuration">Workflow configuration</a></li>
<li><a href="/scikit-learn/gpu-ci/#bonus-material">Bonus material</a></li>
</ul>
<h2 id="larger-runners-with-gpus">Larger runners with GPUs<a class="headerlink" href="#larger-runners-with-gpus" title="Link to this heading">#</a></h2>
<p>All workflows for your GitHub project are executed on a
runner. Normally all your workflows run on the default runner, but you can have additional runners too. If you wanted
to you could host a runner yourself on your own infrastructure. Until now this
was the only way to get access to a runner with a GPU. However, hosting your
own runner is complicated and comes with pitfalls regarding security.</p>
<p>Since about April 2024 GitHub has made <a href="https://docs.github.com/en/actions/using-github-hosted-runners/about-larger-runners/about-larger-runners">larger runners with a
GPU</a> generally available.</p>
<p>To use these you will have to <a href="https://docs.github.com/en/billing/managing-your-github-billing-settings/adding-or-editing-a-payment-method#updating-your-organizations-payment-method">setup a credit card for your organisation</a>. Configure a spending limit so that you do not end up getting surprised
with a very large bill. For scikit-learn we currently use a limit of $50.</p>
<p>When <a href="https://github.com/organizations/YOUR_OWN_ORG_NAME/settings/actions/runners">adding a new GitHub hosted runner</a> make sure to select the &ldquo;Partner&rdquo; tab when
choosing the VM&rsquo;s image. You need to select the &ldquo;NVIDIA GPU-Optimized Image for AI and HPC&rdquo;
image in order to be able to choose the GPU runner later on.</p>
<p>The group the runner is assigned to can be configured to only allow particular repositories
and workflows to use the runner group. It makes sense to only enable the runner
group for the repository in which you plan to use it. Limiting which workflows your
runner will pick up requires an additional level of indirection in your workflow
setup, so I will not cover it in this blog post.</p>
<p>Name your runner group <code>cuda-gpu-runner-group</code> to match the name used in the examples
below.</p>
<h2 id="vm-image-contents">VM Image contents<a class="headerlink" href="#vm-image-contents" title="Link to this heading">#</a></h2>
<p>The GPU runner uses a disk image provided by NVIDIA. This means that there are
some differences to the image that the default runner uses.</p>
<p>The <code>gh</code> command-line utility is not installed by default. Keep this in mind
if you want to do things like removing a label from the Pull Request or
other such tasks.</p>
<p>The biggest difference to the standard image is that the GPU image contains
a conda installation, but the file permissions do not allow the workflow user
to modify the existing environment or create new environments. As a result
for scikit-learn we install conda a second time via miniforge. The conda environment is
created from a lockfile, so we do not need to run the dependency solver.</p>
<h2 id="workflow-configuration">Workflow configuration<a class="headerlink" href="#workflow-configuration" title="Link to this heading">#</a></h2>
<p>A key difference between the GPU runner and the default runner is that a project
has to pay for the time of the GPU runner. This means that you might want to
execute your GPU workflow only for some Pull Requests instead of all of them.</p>
<p>The GPU available in the runner is not very powerful, this means it is not
that attractive of a target for people who are looking to abuse free GPU resources.
Nevertheless, once in a while someone might try. Another reason to not run
the GPU workflow by default.</p>
<p>A nice way to deal with running the workflow only after some form of human review
is to use a label. To mark a Pull Request (PR) for execution on the GPU runner a
reviewer applies a particular label. Applying a label does not cause a notification
to be sent to all PR participants, unlike using a special comment to trigger the
workflow.
In the following example the <code>CUDA CI</code> label is used to mark a PR for execution and
the <code>runs-on</code> directive is used to select the GPU runner. This is a snippet from
<a href="https://github.com/scikit-learn/scikit-learn/blob/9d39f57399d6f1f7d8e8d4351dbc3e9244b98d28/.github/workflows/cuda-ci.yml">the full GPU workflow</a> used in the scikit-learn repository.</p>

<div class="highlight">
  <pre>name: CUDA GPU
on:
  pull_request:
    types:
      - labeled

jobs:
  tests:
    if: contains(github.event.pull_request.labels.*.name, &#39;CUDA CI&#39;)
    runs-on:
      group: cuda-gpu-runner-group
    steps:
      - uses: actions/setup-python@v5
        with:
          python-version: &#39;3.12.3&#39;
      - name: Checkout main repository
        uses: actions/checkout@v4
      ...</pre>
</div>

<p>In order to remove the label again we need a workflow with elevated
permissions. It needs to be able to edit a Pull Request. This privilege is not
available for workflows triggered from Pull Requests from forks. Instead
the workflow has to run in the context of the main repository and should only
do the minimum amount of work.</p>

<div class="highlight">
  <pre>on:
  # Using `pull_request_target` gives us the possibility to get a API token
  # with write permissions
  pull_request_target:
    types:
      - labeled

# In order to remove the &#34;CUDA CI&#34; label we need to have write permissions for PRs
permissions:
  pull-requests: write

jobs:
  label-remover:
    if: contains(github.event.pull_request.labels.*.name, &#39;CUDA CI&#39;)
    runs-on: ubuntu-20.04
    steps:
      - uses: actions-ecosystem/action-remove-labels@v1
        with:
          labels: CUDA CI</pre>
</div>

<p>This snippet is from the <a href="https://github.com/scikit-learn/scikit-learn/blob/9d39f57399d6f1f7d8e8d4351dbc3e9244b98d28/.github/workflows/cuda-label-remover.yml">label remover workflow</a>
we use in scikit-learn.</p>
<h2 id="bonus-material">Bonus Material<a class="headerlink" href="#bonus-material" title="Link to this heading">#</a></h2>
<p>For scikit-learn we have been using the GPU runner for about six weeks. So far we have stayed
below the $50 monthly spending limit we set. This includes some runs to debug the setup at the
start.</p>
<p>One of the scikit-learn contributors created a <a href="https://gist.github.com/EdAbati/ff3bdc06bafeb92452b3740686cc8d7c">Colab notebook that people can use to setup and run the scikit-learn test suite on Colab</a>. This is useful
for contributors who do not have easy access to a GPU. They can test their changes or debug
failures without having to wait for a maintainer to label the Pull Request. We plan to add
a workflow that comments on PRs with information on how to use this notebook to increase its
discoverability.</p>
<h2 id="conclusion">Conclusion<a class="headerlink" href="#conclusion" title="Link to this heading">#</a></h2>
<p>Overall it was not too difficult to setup the GPU runner. It took a little bit of fiddling to
deal with the differences in VM image content as well as a few iterations for how to setup
the workflow, given we wanted to manually trigger them.</p>
<p>The GPU runner has been reliably working and picking up work when requested. It saves us (the
maintainers) a lot of time, as we do not have to checkout a PR locally and run the tests
by hand.</p>
<p>The costs so far have been manageable and it has been worth spending the money as it removes
a repetitive and tedious manual task from the reviewing workflow. However, it does require
having the funds and a credit card.</p>]]></content>
            
                 
                    
                 
                    
                         
                        
                            
                             
                                <category scheme="taxonomy:Tags" term="scikit-learn" label="scikit-learn" />
                             
                                <category scheme="taxonomy:Tags" term="ci" label="ci" />
                             
                                <category scheme="taxonomy:Tags" term="gpu" label="gpu" />
                             
                                <category scheme="taxonomy:Tags" term="cuda" label="cuda" />
                            
                        
                    
                
            
        </entry>
    
</feed>
