Google Summer of Code 2026 completed. A summer well spent writing GPU kernels.
This is more of a journey post than a completion post. So make sure you stick till the end.
On 30th April, 2026… actually, wait, on 29th April, I started seeing a lot of hullabaloo in my club's GSoC group, where all the participants who were trying for GSoC were discussing this JSON thing. There was a glitch on GSoC’s site where, in a JSON response under the program section, a few people were able to see a program. Mine was, of course, my first choice: The Julia Language. I was skeptical about it. Of the many who applied, only a handful could see it. So of course I lost sleep that day. Whether I would get into the programme was the question of the night.
The next day, I officially got into Google Summer of Code. Of course I was overjoyed but a bigger question started bugging me. Will I be able to complete it? The reason was that I was trying to hit a bar well beyond my current level. At that time, I was tinkering here and there with HIP kernels and was fascinated by them. I was also trying to find my way around Julia and its ecosystem when I stumbled upon JuliaGPU.
For context, I have two GPUs in my desktop. One is an RX 9060 XT, and the other is an RTX 3060 Ti. JuliaGPU was vendor-agnostic. This sold me on JuliaGPU. I started writing small kernels for it. Gradually, I started exploring JuliaGPU’s codebase to learn the magic. One of my very first contributions to JuliaGPU was a minor bug fix in GPUArrays.jl. I slowly got the hang of it and started contributing more. CuTile.jl was gaining traction by then. So, out of sheer curiosity, I cloned it and picked up an issue. I had no idea how tile-based programming worked. Great, “seekhlenge.” The issue I picked was well beyond my level but I gave it a shot. That was the first time I slid into the DMs of my now-GSoC mentor, Tim Besard. He clarified some things. But when you don’t know how to fly and your commander says, “Cadet, blip the throttle,” you’re left thinking, “Oh, I don’t know where the throttle is. AI probably knows.” I made AI my copilot. When the GSoC season came, I saw that JuliaGPU had a project titled Improving GPU stack portability for JuliaGPU. Well I thought of it as a win-win situation. Getting paid to work on what you love? Most welcome. So, I applied.
The proposal has its own different story. In Point Blank (the club I belong to), seniors generally review proposals before they are sent to mentors or submitted finally. I made a proposal that was 80 pages long. Imagine 80 pages. It had everything from what's the current condition of the project to what changes will be made, including the code snippets which I thought were relevant then. Then I showed this to my senior. His literal reaction was "You need to shorten it warna koi nahi padhega 😭". Then the process of cramming started and finally the proposal was reduced to some 36 pages. I showed it to my seniors and got it reviewed. I sent it to the mentor, Tim Besard. He reviewed it and steered the direction. Of course I was trying to land without landing gears. After understanding what the community wanted, I tailored it to those needs and submitted it on March 27th.
After acceptance, during the community-bonding period, I had two meetings with Tim and Christian (my co-mentor). Both were about how the project worked and the direction we were heading in. During this time I was also interning at Indian Institute of Technology, Kanpur as a compiler research intern. Since I was based in Bengaluru and attended a college where I couldn't miss internal exams, I was frequently flying back and forth. Hall 13 (IIT Kanpur) was my second home then. So I managed to work on both. Prof. Amey Karkare and my GSoC mentors were understanding, so I managed to make both commitments work.
The technical part
My proposal covered six parallel GPU algorithms that needed to be implemented or ported from vendor-specific implementations to a vendor-agnostic stack. reverse, accumulate, findall, mapreducedim, sort, rand.
Let me clarify the difference between vendor-specific and vendor-agnostic implementations. Different GPU vendors have different programming models and libraries, like CUDA for NVIDIA and HIP for AMD. While the underlying algorithms might be the same, their implementations often differ. JuliaGPU aims to abstract away these differences, allowing developers to write GPU code that works across supported hardware. However, higher-level operations like sort, accumulate, and mapreducedim can still depend on vendor-specific implementations. My project focused on bridging this gap by implementing or porting these parallel algorithms into a vendor-agnostic stack, making JuliaGPU more portable and extensible across GPU backends.
So, as per my mentor's recommendation, I started with mapreduce. My first major task was extending it to support reductions along arbitrary dimensions of multidimensional arrays, making its behaviour more consistent with Julia's Base implementation. This turned out to be much more involved than simply writing a reduction kernel. My initial approach relied heavily on Cartesian indexing, but Tim's reviews pushed me to investigate its performance costs, benchmark against existing implementations, and rethink the indexing strategy. I eventually introduced stride-based fast paths for dense and strided arrays, while retaining a generic fallback for other layouts. I also worked on handling tuple dimensions, duplicate and out-of-bounds dimensions, type-changing reductions, and multiple input arrays. The process involved several rounds of profiling, debugging, and reworking the implementation to avoid performance regressions across different workloads. It was my first real lesson in GPU kernel engineering: getting the algorithm correct is only half the job. Making it efficient without sacrificing generality is where things get interesting. You can check out the exact PRs and improvements in juliagpu.org.
Then came sort. This turned into a project of its own. AcceleratedKernels already had a merge sort, so the challenge wasn't simply implementing another sorting algorithm. It was figuring out which algorithm works best for which workload. I started by adding an opt-in, stable LSD RadixSort for integer and floating-point types, then worked on optimising its histogram and scatter stages. Tim's reviews pushed me to tighten floating-point ordering, handle edge cases like NaNs and signed zero correctly, and make the implementation behave consistently with Julia's Base sorting API. Next, I extended sort, sortperm, and their in-place variants to support sorting along arbitrary dimensions. That meant making the existing merge-sort machinery operate on independent slices rather than treating the entire array as one flat sequence. But merge sort wasn't ideal for short slices, so I added BitonicSort, which can sort small slices within a workgroup using local memory. Finally, I worked on segmented RadixSort to bring radix sorting to dimensional operations too. Each algorithm had different strengths, and the goal was to make them coexist in a portable implementation. The results were worth it: on an NVIDIA RTX 5080, RadixSort reduced the time to sort 128 million Int32 elements from 83 ms to 39 ms. For short slices, BitonicSort reduced sorting time from 1.73 ms to 44 μs on a representative workload. This was where I started thinking that GPU optimisation isn't about finding one clever algorithm. It's about understanding the workload, the memory hierarchy, and the trade-offs well enough to choose the right algorithm for the job.
Beyond mapreduce and sorting, the other PRs focused on filling gaps in the shared Julia GPU stack and making the existing kernels faster. reverse was one of the missing primitives, so I implemented reverse and reverse!, then extended them to support reversing along specific dimensions. I also added findall using stream compaction, combining a prefix scan with a scatter step to efficiently collect the indices that satisfy a predicate without moving the computation back to the CPU. For accumulate, which powers operations like cumsum and cumprod, I worked on improving the parallel scan implementation, including processing multiple items per thread to reduce overhead and improve throughput on large arrays. The work also involved fixing subtle correctness issues in the Decoupled Lookback scan algorithm, where blocks communicate intermediate results and correctness depends on proper device-wide memory ordering. On the reduction side, I optimised block-level loads and added support for processing multiple items per thread, while extending the implementation to handle more input shapes and dimensions. I also generalised map and map! to accept multiple source arrays, allowing element-wise operations across several inputs using the same portable kernel infrastructure. I added vectorised, by-block loads for reductions and introduced items_per_thread tuning for both reductions and scans, letting each thread process multiple elements to reduce overhead. I also fixed a subtle portability issue in the Decoupled Lookback scan by adding the device-scope memory fence needed to ensure blocks observe each other's intermediate results correctly across backends. All this work went into the breaking release of AcceleratedKernels.jl 0.5.0 (release).
Too much of tech stuff. Ufff...
The community
Both of my mentors were amazing. We had stand-ups every Wednesday, and they were fun. I will still be sticking with the community even after GSoC. Julia has one of the strongest communities, but it is pretty much nonexistent in India. I gave a talk at IndiaFOSS 2026 on how JuliaGPU works at a surface level. You can check my talk here.
So, how do you crack Google Summer of Code?
So, now a question... How do you crack Google Summer of Code?
No need for tutorial videos, no need for cheat sheets, and no need for any extraordinary acts. Just honest contributions, exchanges with mentors, and a great community or seniors who can help you are all you need.
Happy contributing!

Top comments (0)