Generating Performant 6G GPU-Accelerated Code From High-Level Programming Languages
NVIDIA Developer
0:00 Okay, thank you Emeka.
0:01 So this is a follow-up talk to Michael's
0:03 great talk on the Aerial Framework runtime.
0:06 So before I get started, just to remind people that this is available
0:11 on GitHub and we have the getting started tutorials.
0:15 Michael covered 1, 7, 8, and 9.
0:18 And in this talk I'm going to cover the middle tutorials
0:22 on Python and how to take Python down to performant GPU code.
0:27 So as Michael said the Aerial Framework has two environments.
0:31 This is the development environment
0:33 with the lowering toolchain and the runtime engine.
0:36 The runtime engine has the GPU plus a NIC and this is
0:40 needed to run the real-time applications with MAC and radio unit emulation.
0:45 And this is really built for your test beds and product.
0:48 Now the development is the part that I'll talk about now.
0:52 And this is available to anyone with a recent GPU,
0:57 and it's for compiling algorithms to GPU executable code
1:00 for the Aerial Framework runtime engine over here or for test
1:04 beds if you have your own GPU C++ pipeline
1:09 product trials or if you have a C++ simulator.
1:13 So radio access networks have been well studied for decades
1:17 and we understand them very well in simulations and theory.
1:21 But the real world is messy.
1:24 Wireless radio channels don't really capture—I mean the 3GPP
1:28 models don't really capture—the true richness of the world
1:31 and data traffic patterns that we use in 3GPP
1:35 standardization don't represent modern apps and generative AI data flows.
1:38 And of course in the real
1:40 world we have hardware imperfections and nonlinearities.
1:43 And many may not know this but UE implementations,
1:47 the actual mobile phones, they differ from one another.
1:51 So in practice there are lots of heuristics, assumptions,
1:55 and compromises that are used to get things working.
1:59 So we believe that AI will help drive the next innovation wave.
2:03 And later in the talk I'm going to dig deep
2:06 into an example where we have a classical channel estimator fused with AI.
2:11 Okay.
2:12 So, we're going to use a transformer inside the channel estimator to basically
2:18 predict the parameters of the channel filter
2:22 using that channel estimator to improve performance.
2:25 So, in a world where we have signal processing deeply fused with AI,
2:30 how do we take this type of code to product?
2:33 How do we make it run real time over the air?
2:37 And this is where the Aerial Framework comes in.
2:40 So to explain this idea, I'd like to use this graph.
2:44 So on the x-axis we have runtime latency.
2:47 So this is in milliseconds.
2:49 This line here indicates the magic one slot marker of 500 microseconds.
2:55 Over here this is slower.
2:57 This would be milliseconds or seconds.
3:00 And over here fast that we need for the over-the-air transmission.
3:05 On the y-axis we have the iteration speed of how quickly or how
3:10 easy it is to innovate your algorithms and AI to improve system performance.
3:14 So the top fast few days and at the bottom slow where it takes years.
3:21 So first up research simulators.
3:23 These are typically written in Python and MATLAB
3:25 because you need to iterate your ideas fast.
3:28 You need to prototype.
3:30 You need native integration of Torch and JAX to use AI.
3:35 And as far as I know,
3:37 there is no one simulator that covers the entire RAN stack.
3:40 Like, the RAN stack is just too complicated.
3:43 So, everyone has their own simulator that simulates a part of the stack.
3:48 So, the next up in the pipeline is test beds.
3:51 And this is where you take ideas from simulators
3:54 and you rewrite the algorithms in C++ or CUDA
3:58 to get performance that you need to go
4:00 over the air within this 500 microsecond slot time.
4:05 And finally we have the product implementations and this is
4:10 where you have extreme optimizations in C++ and CUDA
4:14 to get ultra low latencies for large workloads where you
4:19 have SLAs and very rigid uptime requirements for real world deployments.
4:26 So how does an idea go from research to test bed to product?
4:30 The way I like to think about it is we have researchers.
4:34 They come up with the algorithm and then they write what is like a paper IR,
4:39 a paper intermediate representation,
4:40 where they write the algorithm down on paper or in a PowerPoint slide and it
4:44 gets handed over to someone who implements
4:46 a more serious version for a test or product.
4:50 But what I find is when we get down to these ultra
4:56 optimized implementations that lots of the nuance around the algorithm
4:59 and why it was designed that way have been lost
5:02 and the algorithm and the platform becomes incredibly fixed and rigid.
5:06 So it becomes very slow to integrate new ideas and test out things.
5:11 So the Aerial runtime engine that Michael introduced has been built
5:16 on years of learnings from Aerial and GPU teams and it's
5:20 really built for product and test bed workloads and they
5:23 have done a wonderful job at separating between runtime and algorithms.
5:28 So the question is how do we use this now
5:32 to accelerate research and this is the point of the Aerial Framework,
5:37 is to take research code directly from Python and push it into test
5:42 beds where you can verify it where it matters and that's over the air.
5:48 So the vision is to connect research, test beds,
5:51 and product to help solve development challenges at real time and by real time
5:55 I mean microsecond latency applications and then
5:57 we want developers to develop on C’s hardware,
6:00 for example DGX Spark or an RTX graphics card,
6:03 and then take the same code and deploy it
6:07 to product to test their ideas for real over the air.
6:11 So we want our software developers to have
6:13 state-of-the-art tools for signal processing and AI.
6:16 We very much don't want RAN software development
6:19 to get left behind other areas like AI,
6:22 automotive, robotics and scientific computing.
6:24 So this is the point of the Aerial Framework.
6:28 So we built this for RAN engineers and researchers
6:30 and I thought I'd go through two workflow examples,
6:33 and starting off with a very simple finite impulse response filter.
6:38 So here we have the equation defining the filter.
6:42 X is the input sequence.
6:44 In the figure here, it's this gray line, very noisy gray line.
6:48 And we want to filter it with this kernel H.
6:51 And we filter by sliding the kernel H
6:54 over the sequence in time and computing the output, the smooth output Y.
6:59 In Python, this is very easy to write as my function here.
7:03 And in Aerial Framework,
7:04 what we do is we provide tools to compile this function to an executable where
7:11 this executable can be run directly in Python
7:14 to verify that the compiled code is correct.
7:17 So here we can say the blue code is what we expected and what we wanted.
7:21 And then we can take that same executable and put it into the Aerial
7:25 Framework runtime pipelines that Michael was
7:28 showing to go straight over the air.
7:31 So how does it work under the hood?
7:33 So we start with our algorithm at the top here,
7:36 our finite impulse response filter.
7:38 We write it in JAX and then in JAX
7:41 we export the function to something called StableHLO
7:45 and StableHLO is a text representation of this function
7:50 that provides an intermediate representation for the compilers below.
7:54 Here you can see that X is an input tensor.
7:58 This is our X and for this example I
8:02 pass a length 400 of type float32 into the function.
8:05 It gets traced out through the function body.
8:09 And here you can see the kernel, the H,
8:13 becomes a StableHLO constant that more or less gets baked into the program.
8:19 And at the very bottom we have the operation defining the convolution.
8:24 And after the convolution,
8:25 the output tensor Y is returned of the same size and data type as the input.
8:33 So this text function StableHLO gets passed to our backend,
8:38 the ML TensorRT compiler,
8:39 which then compiles it down to a TensorRT engine that gets
8:45 injected into the Aerial Framework runtime that Michael was showing.
8:49 Now in addition to having this path for native Python code,
8:53 we have a path for high performance handwritten CUDA code as well.
8:57 So for example, if you have FFT or Cholesky or a solve operation that you
9:04 have a high performance implementation—could be LDPC
9:08 as well—we build it into a shared
9:11 library and then we expose it to JAX and StableHLO via the custom operation
9:15 and this gets passed down to the compiler
9:18 and this mechanism basically tells the compiler,
9:21 hey don't worry about it when you get to this function,
9:25 call out to this shared library and use that.
9:27 And this call is quite efficient, it takes a few microseconds to do.
9:33 So just to recap Michael's talk quickly, the runtime,
9:37 the product runtime, has this concept of pipelines and modules.
9:43 The modules are where we inject
9:46 our handwritten CUDA or our compiled TensorRT components.
9:51 The modules are the building blocks of the pipeline
9:56 and the pipeline coordinates the routing of information between these modules.
10:01 Now from the Python developer’s perspective, and to be honest my perspective,
10:07 what happens here is that I can put anything
10:10 I want from Python into a module via this TensorRT.
10:15 The only rules I need to follow are that the input data
10:19 types and the output data types match exactly what the C++ pipeline expects.
10:24 Of course.
10:25 So if you have a tensor of shape 400 and float32,
10:29 then you should accept 400 and float32 here.
10:32 And then of course the function that I write
10:34 in Python must compile successfully to a TensorRT engine.
10:37 But apart from that I can put anything I want into this pipeline
10:42 and then it gets executed on the GPU without any host CPU involvement.
10:47 So Michael showed this whole stack that comes
10:50 with the first release of Aerial Framework.
10:53 So we have the RU emulator, the NIC,
10:55 the fronthaul, and then the PUSCH receiver pipeline.
10:58 And this is the part I'm going to focus on in my talk.
11:01 So here there are two parts.
11:03 There's a PUSCH inner receiver and the PUSCH outer receiver.
11:08 The PUSCH inner receiver is shown in green
11:11 and the PUSCH outer receiver is shown in blue.
11:14 Now we have done this PUSCH outer receiver as a CUDA C++ set
11:19 of three modules and the reason for doing that, well there are two reasons.
11:24 First, we had a very high performance existing
11:27 implementation of the LDPC decoder already from Aerial cuPHY
11:31 and we wanted to test the workflow of mixing
11:36 development in CUDA with development exploratory research from Python.
11:41 So this is why we chose these two.
11:44 The Python side takes the inner receiver.
11:47 So we have the channel H,
11:49 the noisy received symbols, complex valued, coming in here.
11:53 We do DMRS extraction, channel estimation, interpolation,
11:57 interference plus noise covariance matrix estimation,
12:00 a standard MMSE-IC equalizer and soft mapper.
12:04 All of this gets compiled through the TensorRT compiler to a TensorRT
12:09 engine that becomes the first module in the Aerial Framework pipeline.
12:13 And on my machine, this gets about 90 microseconds plus a bit.
12:18 And Michael was showing around 200 microseconds.
12:21 So that's where we are there.
12:23 And for reference, this is for 100 megahertz bandwidth,
12:27 four receive antennas, one UE and one layer.
12:30 We do expect that this will scale very well with the number
12:34 of UEs given how the batching works for each of these operators.
12:39 So first up I did draw this in blue here
12:42 and the reason for that is because we actually use a custom plugin.
12:46 But before getting to that custom plugin
12:48 I'll just quickly refer to this figure here.
12:51 This is the DMRS configuration that we use for the test.
12:55 It's a standard one where the DMRS is on symbol two.
12:59 The DMRS sequence is on every second subcarrier.
13:02 The blue is the data that's transmitted from the user.
13:06 The gray is muted pilots that are not used.
13:09 So to create the DMRS sequence, we need to generate some random numbers.
13:14 And the way of doing that is to do this via two Gold sequences.
13:19 And this Gold sequence is generated
13:22 by having two linear feedback shift registers, this one here and this one here.
13:27 These are very sequential algorithms.
13:29 And when our first effort in prototyping this in JAX,
13:34 it worked and it compiled through.
13:36 However, the latency wasn't where we were at.
13:38 So we decided to write this as a custom CUDA plugin
13:43 where these feedback shift registers
13:45 were implemented essentially as lookup tables.
13:47 But this is a good way to highlight the workflow
13:50 where you start in JAX and then you can later,
13:55 when things harden, you can move to CUDA hardened kernels.
14:01 The next block in the PUSCH pipeline is channel estimation.
14:04 And there are many many ways to do channel estimation.
14:07 And what we've chosen here is a simple reference estimator
14:11 that is easy to calibrate and benchmark with our partners.
14:14 And the core idea is that you get the noisy DMRS in on this side.
14:19 You perform a least-squares channel estimate
14:22 to essentially extract or remove the DMRS symbols.
14:25 Then we have the raw and noisy channel in the frequency domain.
14:31 We do this IFFT to go to the time domain or delay domain.
14:37 We compute a power delay profile of the channel.
14:40 And then what happens is you get something like this black curve here.
14:44 So this represents the taps or the delays of the channel.
14:48 And then to perform filtering we perform a simple
14:52 threshold filter here where anything above the threshold we consider
14:57 to be the real channel and anything below we
15:01 consider to be noise and we set it to zero.
15:04 This threshold is simply based on the estimated noise power plus standard
15:07 deviation and then we've tuned some
15:09 hyperparameters to make it work reasonably okay.
15:12 After the thresholding we have the FFT back to the frequency domain.
15:17 To complete the inner receiver there are three more blocks.
15:20 There's the estimate of the interference
15:22 plus noise covariance matrix and this is done in the usual way by reconstructing
15:28 the desired signal from the received DMRS.
15:32 We compute the residuals.
15:34 So this is the error from the received DMRS minus the desired.
15:39 So it's the error from your estimate to what you received
15:43 and the covariance is computed in the normal way using an outer product.
15:49 We have this little optimization in there where
15:52 we apply this RBLW shrinkage method which essentially
15:55 uses a convex combination of two covariance matrices
16:00 to improve the accuracy of the covariance estimate.
16:04 And finally we have the standard MMSE-IC equalizer.
16:07 And for this we use a Cholesky
16:10 factor inverse method to compute this equation here.
16:14 Okay.
16:14 As Michael showed, this is what you'll get in the Nsight
16:19 Systems profiling tool when you run the full pipeline.
16:22 And you can see here for my machine we have about 10 microseconds for the DMRS.
16:27 And this includes the frequency orthogonal cover codes.
16:29 It also includes a casting of the channel,
16:32 a very large casting from float16 to float32.
16:36 And this is necessary because for the first release,
16:40 our FFTs for the channel estimator work with float32,
16:44 but the pipeline itself works with float16.
16:47 So there are some performance gains here immediately to have,
16:51 where we get some new kernels.
16:53 After the channel estimation we have the covariance estimation,
16:57 shrinkage and some transposes to prepare for the MMSE-IC equalizer.
17:02 And as Michael discussed when we zoomed into these things we
17:04 see that there are quite a few scatter-gather operations which suggests
17:07 that the way we did the data layout for the MMSE-IC can
17:11 be improved and there are some performance gains to be had here.
17:16 But nonetheless, for the first go implementing PUSCH from Python we,
17:22 in stream mode, have 185 or 186 microseconds latency.
17:28 Okay, so we have the PUSCH receiver in Python.
17:31 And for me, this is wonderful because it
17:33 means you can really start having fun and experimenting.
17:37 So if we take the channel estimator block here and then you go
17:42 after the DMRS extraction and you just look at the channel estimation part.
17:46 So I explained before first up that we have this weighted threshold,
17:51 this simple weighted threshold filter,
17:52 but immediately you can say well let's start trying other ideas.
17:56 So you just now plug in a different Python function to do this.
17:59 So in the first release we have one based on some
18:02 physics principles of free energy that we came up with.
18:05 I'm going to show you next the AI enhanced filter and in the notebook when you
18:10 run through there you'll see there'll be a cell
18:13 where you can define your own channel filter.
18:15 We gave a working example to get you started,
18:17 but in there you can just cut and paste and put your own ideas in and have
18:21 your filter compiled down with the rest of the chain
18:25 fused together and then profiled with everything else.
18:29 So the workflow in the notebook is
18:31 that you write your channel estimators in JAX,
18:33 export it to StableHLO and then you use this ML TensorRT
18:38 compiler to generate the engine that gets injected into the runtime pipeline.
18:43 And then we evaluate the performance of the channel
18:47 estimators using the 3GPP CDL-C model from SA.
18:51 And in the notebook, similar to what Michael showed,
18:55 you will get to profile what your channel
19:00 estimator does in a real product runtime type environment.
19:06 Okay.
19:06 So at the start of the talk, I talked about this idea of fusing
19:09 at a very deep level AI and classical algorithms.
19:12 So this is one idea or this is one example of that.
19:17 So here we have the same chain for channel estimation as I showed before.
19:22 We have the least-squares channel estimate.
19:24 We do an IFFT to go to the delay
19:27 domain and then we compute the power delay profile.
19:30 And before we would take this power delay profile here and we just threshold it
19:35 based on some heuristics to compute the FFT
19:38 and then FFT back to the frequency domain.
19:40 But instead of doing this thresholding based on heuristics,
19:43 what we can do is well let's use a more sophisticated filter.
19:48 So here we picked at random a Tukey
19:51 filter that is defined by these equations here.
19:54 And this filter has two parameters that define it, alpha and tau.
19:59 You can see alpha and tau define the shape
20:02 and how this filter is applied, where it's applied.
20:05 So instead of us hard coding, like you would in a classical system,
20:10 alpha and tau, we defined a neural network,
20:13 a transformer, to basically predict good values of alpha and tau
20:17 so that when you apply the filter it works well.
20:22 So when you train this, now that the whole thing
20:24 is written in JAX it means that we can compute
20:27 the mean squared error loss—actually it's a normalized mean squared
20:30 error that we use—of the true channel from Sionna versus
20:34 the estimate we get from our pipeline and we can
20:37 pass gradients all the way back through this pipeline to basically
20:41 optimize the transformer in such a way that it picks
20:45 good alpha and tau values to minimize this mean squared error.
20:50 And when you run the notebook and you train the AI model,
20:55 unfortunately we couldn't ship the pre-trained AI model,
20:57 so you'll have to train that one yourself.
21:00 You'll get a curve like this in the notebook.
21:03 On the x-axis, you have the SNR in dB ranging from minus 20
21:08 to 10 and on the y-axis you have the normalized mean squared error in dB.
21:13 So what we want is to get low values of the mean squared error.
21:18 So the lower the better.
21:20 And we can use that more or less as a rough proxy
21:24 to the single user uplink throughput you would experience on your mobile phone.
21:28 So lower here—so the red curve which is the AI
21:31 model performs a few dB better than the green curve,
21:35 which is this free energy idea, and that performs better than the Tukey filter.
21:40 I forgot to say that each one of these dots
21:43 is one particular channel realization for one particular filter.
21:47 So this is great.
21:50 The AI model beat the other ones, right?
21:53 But now that you have this ability to profile
21:57 the actual whole pipeline running on the product runtime,
22:01 you can look beyond this performance in MSE.
22:04 You can look at how long does it take to execute this model.
22:10 So we can see that the AI model takes 250 microseconds.
22:14 If we go down to the free energy model, it takes 169 microseconds.
22:18 This is not just for the channel filter.
22:21 Remember this is for DMRS generation and extraction,
22:25 the channel filtering and the covariance estimation as well.
22:29 But the point here is that now we can, as a researcher,
22:34 look at the model and we can look at how
22:37 the GPU is using that or how it's being executed
22:39 on the GPU and we can tailor the model fused
22:44 with the classical algorithms to get the most out of the GPU here.
22:52 Okay.
22:52 Yes.
22:52 So these are the settings.
22:54 You can look through the notebooks to see exactly what was run here.
22:58 So let's go deep into how we do this.
23:02 So at the backend we use this ML TensorRT compiler and this is
23:07 another open source project from NVIDIA that's been recently open sourced.
23:12 This compiler is for inference acceleration only.
23:15 It is not for training.
23:17 So it's just to accelerate during inference, during runtime.
23:21 The ML TensorRT compiler is for StableHLO programs.
23:26 So anything that can be expressed in StableHLO it can compile.
23:29 So we have a JAX frontend but you could
23:32 also use a Torch frontend with the Torch-MLIR pass.
23:36 We can also use TensorFlow as well.
23:40 Modularity means it integrates with existing ML compiler stacks.
23:44 One important thing is it has a TensorRT dialect.
23:49 So if we go to the compiler here,
23:51 this is the frontend of the compiler and at the backend it has several backends.
23:56 One is the TensorRT compiler and the other
23:59 is the Tiled IR and CodeGen compilers.
24:01 The TensorRT compiler is our main path.
24:04 So we traverse this link here.
24:07 And what the ML TensorRT compiler does is it provides a transpiler,
24:12 a conversion from our StableHLO representation of the function
24:16 to something that the TensorRT compiler understands
24:19 and then the TensorRT compiler is responsible for generating
24:23 the engine that gets injected into the runtime.
24:27 Okay.
24:27 So when you start playing with this there are some important things to note.
24:31 So if an operation or a data type is not supported by this TensorRT
24:36 compiler then what happens is you fall back to Tiled IR and CodeGen.
24:41 Now this is LLVM CodeGen, so this is not fully open sourced yet,
24:48 but the compiler will produce artifacts that you can use.
24:53 They'll produce PTX modules.
24:55 We don't at the moment have the ability—we have
24:57 not yet supported them in the Aerial Framework runtime.
25:00 That'll come in 2026.
25:02 So when you write your function, you should aim to use data types and operations
25:08 that are supported by the TensorRT compiler backend.
25:10 And you can look at the TensorRT website to see what is available there.
25:15 As it stands now, about 80% coverage of JAX ops is what we have.
25:21 But importantly it does not support many linear algebra operations,
25:25 namely solve, Cholesky, inverses, and FFT.
25:28 But this is no problem.
25:30 So what we have done is we've implemented
25:34 CUDA optimized kernels for these functions that we need
25:39 here and these get compiled into a shared
25:42 library that is made available or exposed to JAX
25:46 as a custom operation as I mentioned before
25:49 and this gets passed down to the compiler as an opaque
25:53 plugin and the compiler then can use this opaque
25:56 plugin as a reference when the TensorRT engine runs.
26:02 Complex data types—because TensorRT does not support complex data types,
26:06 they have to go via CodeGen.
26:07 So you'll get PTX for those and they won't
26:09 be able to immediately go into the Aerial Framework runtime.
26:12 But of course you could use them in your own C++ runtime if you wanted to.
26:17 If you want to use this compiler in your own standalone C++ project,
26:22 how we use it is via the EmitC runtime.
26:24 And this is what generates the TensorRT engine plus the PTX modules
26:28 and the C++ source to help you run the TensorRT engine and the PTX modules.
26:36 However, for the Aerial Framework we chose
26:39 to use our own integrations for the TensorRT engine.
26:44 So just to summarize what we've done in the development environment side.
26:50 First, this is available to anyone with a recent GPU.
26:55 It allows you to take JAX code or PyTorch code—we focused on JAX for the first
27:02 release—to compile that down to GPU executable code
27:06 that gets executed in GPU pipelines without host involvement.
27:10 Because we're working with Python and we can fuse in our own custom operators,
27:17 kernels like the DMRS generation for example or LDPC,
27:21 we can fuse at a very deep level
27:25 AI and signal processing and this fusion happens not
27:28 only at the algorithm level but as we
27:30 compile it down to an engine that gets executed,
27:33 it gets lowered to a single engine that gets executed in the pipeline.
27:38 And we think this is really fun to use
27:42 or to compile and deploy ideas in product trials,
27:46 test beds, or if you want just your simulators to run faster.
27:52 So what we have is our JAX implementation
27:55 of the inner receiver with a few plugins.
27:59 It gets compiled through TensorRT.
28:01 How I did it was we profiled a lot using nsys.
28:05 We iterated and optimized as much as we could with the time we had and then we
28:12 injected it directly into the Aerial Framework runtime
28:17 engine here and ran the thing end to end.
28:23 So what's next?
28:24 Well, we'll continue working on this problem of lowering
28:29 Python and we'll experiment and share technologies around this.
28:33 The DSLs we're focusing on are JAX, PyTorch,
28:36 and CuTe Tile that was recently open sourced by NVIDIA.
28:41 And we're focusing of course on Aerial Framework’s runtime.
28:44 We'll continue to work with the ML TensorRT compiler alongside
28:49 the new ways of working with CuTe Tile and Tiled IR.
28:53 I'm very interested in the ability
28:55 to manually write and fuse signal processing kernels.
28:59 So if you have two heavy matrix operations in Python
29:04 and you want to write a kernel directly that's fused,
29:08 where you specify the GPU tile size and all the rest,
29:13 making that available in Python so you can quickly prototype and compile
29:16 it down to something that can be injected into the Aerial Framework runtime.
29:21 We'll be evaluating integration with Sionna via TensorFlow.
29:25 We'll continue to build up our performant kernels such as DMRS,
29:29 FFT, Cholesky, and so on.
29:31 And over the coming months,
29:33 we're going to complete the missing PHY pipelines to add
29:37 the standard-essential features needed to go over the air sometime in 2026.
29:41 And of course, we're going to optimize, optimize,
29:45 and optimize to see how fast this AI-native Python based framework can take us.
29:52 This is available on GitHub from today.
29:55 We'll share our progress regularly and it
30:00 will be wonderful to get feedback from you.
30:07 That's it.
30:09 Thank you.
30:11 Got lots of questions.
30:13 So we're going to give it a go to see how many
30:16 of those questions we can get through in the remaining time that we have.
30:22 So I think, let me attempt grouping the questions
30:26 so that we have some consistency in terms of answering.
30:30 So you can expect there are questions around the software,
30:33 there are questions around the hardware,
30:36 there are also questions about relationship with other NVIDIA tool sets.
30:41 So let me take the first one here and wrap it
30:46 around a few of the other questions that I'm seeing here.
30:49 What's the hardware required for a developer environment in this case?
30:54 So for the development side, any modern recent GPU.
30:59 I think the official thing is we've said compute capability 8 or above.
31:06 So I've tested this on the GH100, GH200, RTX 6000, and RTX 5090.
31:15 We haven't tested on Spark that much yet.
31:22 Okay.
31:22 Okay, it's good.
31:24 Thank you.
31:25 And let me read it directly from how it's
31:29 come in so that hopefully I don't begin to paraphrase.
31:34 Two questions around CUDA and C++ code.
31:38 I'll take the first one.
31:41 Is it possible to use this signal processing toolkit to, is it, sorry,
31:49 is it possible to use Aerial to generate C++ code that is compatible with cuBB?
31:58 Yeah, I mean there's no generation of the C++.
32:02 But what we did do, say for the DMRS example,
32:07 is to take the optimized DMRS CUDA kernel
32:11 from cuPHY and you can write any kernel you like.
32:17 So for example we wrote one for FFT and another for Cholesky inverse that is
32:25 written in CUDA C++ and you would compile
32:28 that down to that shared library I showed.
32:30 So at the moment there is no code generation for C++ CUDA,
32:34 but of course if you have your own toolchain, if you have your own framework,
32:41 generative AI framework or whatever to generate the CUDA kernels,
32:45 then of course you can have the kernels generated that way
32:50 and use them as plugins like we did for DMRS and FFT.
32:55 Okay, fantastic.
32:56 I've got another question here that's going into detail.
33:01 So I think what they're trying to understand here is a clarification of why,
33:07 you know, with the development language of choice of CUDA C++.
33:12 So the person: can you please explain once
33:15 again for the development language choice of CUDA C++
33:20 for PUSCH outer receiver versus a TensorRT engine for PUSCH
33:24 inner receiver and the reasons for making these choices.
33:30 Ah okay.
33:32 Yes.
33:33 So this was this choice.
33:36 So we chose Python for this inner receiver.
33:40 I didn't explain this so this is good.
33:43 Because there's a lot of research
33:45 and innovation going around AI-based channel estimators.
33:48 And in the first talk today from the Sionna team
33:53 they talked about their neural receiver that combines channel estimator,
33:59 interference covariance estimation and equalizer
34:01 all into one big neural network.
34:03 So there's a lot of research and exploratory stuff going around here.
34:07 So this is very good to have in Python to have flexibility.
34:11 On the backend here in the outer receiver,
34:15 the LDPC decoder is quite fixed, right?
34:19 In 5G and in 6G I guess it's going to be quite
34:21 similar and we had a very very high performant LDPC decoder already.
34:25 So to help us go over the air we took that implementation.
34:30 That was one part of the reason and the other was we wanted to test
34:34 this workflow of having a mix of CUDA
34:36 C++ and this Python in the same pipeline, right?
34:39 We had to build the framework around to make this work.
34:43 Okay.
34:44 Okay, thank you.
34:45 Another question here is asking for you to flesh out a bit more.
34:51 What are the benefits of using GPU compute for this?
34:57 The benefits of using GPU—yeah,
35:00 I mean for me it is this flexibility in the platform
35:07 and the software development tools that we have on top.
35:12 If you were to, I mean what we did here
35:15 is we basically took a Python implementation of the PUSCH receiver,
35:19 which is one of the most extreme uses in the RAN,
35:23 and we made it run almost at product level latencies.
35:27 And this was only possible because we have
35:31 this huge ecosystem of CUDA software to build on.
35:34 We didn't build this ML TensorRT compiler;
35:37 we worked a lot with their team but we didn't build it,
35:41 like these are some great guys behind
35:43 that building that and underneath that there
35:45 is the TensorRT compiler and then underneath
35:47 that you have all sorts of great developments.
35:51 So this is one advantage that is very hard
35:57 to overcome if you want to work on GPU platforms.
36:03 This is wonderful.
36:05 Okay.
36:05 Yeah.
36:06 And looking at the DMRS parameters that you didn't show here.
36:14 So how are those handled?
36:18 Right.
36:19 So here we have just the channel coming in here.
36:23 But of course there are many parameters needed
36:27 to set up this pipeline and these parameters can
36:30 be passed in as dynamic inputs or they
36:33 can be compiled as static inputs in the pipeline.
36:36 And in the notebook you'll see how to do that.
36:39 How to fix some parameters as dynamic that change from slot to slot
36:44 or have others fixed to static that are held constant in the engine itself.
36:52 Okay.
36:52 Okay.
36:53 I'm looking at questions here regarding data types.
36:57 So someone is asking, you know,
37:00 can we use complex data types or what type of data types are supported?
37:07 I mean the architecture of this pipeline, is that what's meant here?
37:12 I think so.
37:13 Correct me if that's not right.
37:16 So I think it's any directed acyclic graph that we can have here.
37:23 So here we keep just a, I guess,
37:25 a linear pipeline from one module to the next but the framework supports,
37:31 you can have more complicated structures here for sure.
37:38 Okay.
37:38 Okay.
37:39 And what part of this transmission
37:42 receiver pipeline was accelerated most drastically?
37:47 Um I mean we went from Python which takes,
37:55 I mean I think, I don't remember the numbers.
37:58 This maybe ran in half a second if you run it
38:01 in Python or something and we took it down to 190 microseconds.
38:05 So this is a big acceleration here.
38:08 This side has been optimized and optimized and optimized
38:12 by the Aerial team for the last number of years.
38:17 So this is already very highly performant but of course it's super accelerated.
38:24 I guess that explains some of the questions that have just been coming in now.
38:29 One of the questions is, you know, you're showing us the PUSCH receiver.
38:34 Any work on the PDSCH receiver as well?
38:38 Yes.
38:38 We'll have that one soon.
38:40 Yes, we need that one to go over the air.
38:44 So that's in the pipeline.
38:46 We just weren't ready.
38:47 We weren't ready for the first release with PDSCH.
38:50 I see.
38:50 Another one related to what you have on screen there about the LDPC decoder.
38:57 Is it open source?
38:59 How efficient is it?
39:04 I don't know the exact,
39:06 it's part of the Aerial SDK and I will leave it to someone
39:09 else to answer the exact open-source nature of the details of the LDPC decoder.
39:16 But it is very efficient.
39:19 Yes.
39:19 Okay.
39:20 Let me get more of the questions as well.
39:24 So somebody's asking us here,
39:27 can I use compiled Python with my own C++ simulator?
39:33 Yeah, absolutely.
39:34 And that's how I started.
39:37 So when we started, before we had this pipeline built,
39:41 we started prototyping this in Python and compiling it down.
39:46 And when you compile it with this compiler using the EmitC backend,
39:50 it will generate C++ host code for you
39:53 to show you how to stitch together the different artifacts,
39:56 the TensorRT engines, the PTX modules and so on, how to stitch them together.
40:01 And I had my own C++ pipeline testing this.
40:04 So of course if you have a C++ simulator with a GPU and you
40:10 want to offload some things to that, yes of course you can use this compiler.
40:17 You can use our helpers,
40:20 our wrappers for that compiler as standalone for your C++.
40:24 Yes.
40:25 Okay.
40:25 Let's talk a bit about JAX because I'm seeing questions here.
40:29 So why did you use JAX?
40:31 Well, answer that before I get to the next
40:34 question because the person had two questions,
40:37 well two different people asking about JAX.
40:39 So why did you use JAX?
40:40 Then I'll come to the next question on JAX.
40:43 First, for people who know me they know I like JAX very much.
40:48 So maybe there's a bias.
40:51 But the real reason was JAX has this ability—I mean we export
40:57 to StableHLO and here with the ML TensorRT compiler we get this nice flow
41:03 and this compiler handled complex numbers out of the box and it had a lot
41:10 broader support than what we could get
41:13 with PyTorch directly with the TensorRT backend.
41:16 So that was the original motivation for using JAX.
41:22 Then the second benefit came from having these custom ops registered with JAX.
41:27 So this is a really wonderful way that JAX was built that you can register
41:33 my DMRS plugin as a native operation in JAX and the Python user just writes,
41:38 I want DMRS here, but under the hood the compiler knows
41:42 how to handle that and go to the custom CUDA kernel.
41:46 That being said, we also like PyTorch very much and we are working
41:51 on a way of having a PUSCH receiver or a way of working with PyTorch directly.
41:57 And there are a number of different options
42:00 and we have some examples in the codebase already.
42:03 You can use the TensorRT API directly
42:06 to build up the layers of the TensorRT engine
42:11 directly with PyTorch or you can use PyTorch-MLIR
42:16 to go via StableHLO like we did with JAX.
42:20 Okay.
42:21 So is it only the, so what parts of the RAN pipeline can be JAX compiled?
42:30 Is it only the AI block or also some classical DSP kernels?
42:37 No, I mean the whole, all of this is JAX.
42:40 So the DMRS extraction, the channel estimator, the classical methods,
42:44 the AI method, the mixed method, this is all JAX.
42:49 This interference plus noise covariance matrix estimation, this is JAX.
42:53 The MMSE-IC equalizer is all JAX except
42:55 the custom call to the Cholesky factorization.
42:58 The soft mapper is all JAX.
43:01 So all these are classical algorithms and they're all written in JAX.
43:05 Okay, I'm trying to pull up all the questions around
43:09 PyTorch because I've seen a few of them come through.
43:16 Okay.
43:17 So can we use PyTorch?
43:20 Yes absolutely.
43:21 So when you use, the way to use PyTorch, there are two ways.
43:26 If you want to use it with the ML TensorRT compiler
43:30 then you use something called Torch-MLIR and you export your Torch
43:35 model to StableHLO and then you hand it over to this compiler
43:38 in the same way as what we do for JAX.
43:43 And I think we have an example in the codebase.
43:46 I should double check that.
43:48 But at least we did in testing where we had
43:52 a PyTorch function exported by MLIR in StableHLO and this compiler.
43:58 That's the first way.
44:02 The second way is to use the TensorRT interfaces directly from PyTorch.
44:12 That's also possible and we have an example in the code base how to do that.
44:18 Okay, let's talk about latency here.
44:22 How close is the receiver latency with respect
44:26 to handwritten CUDA from our cuPHY library?
44:31 So I mean we were somewhere around—this was stream mode—180 or so.
44:39 We think there's some improvements, we're around 150.
44:43 The handwritten optimized CUDA implementation in cuPHY on this machine
44:49 for this workload was around 50 or 60 microseconds.
44:54 So we're getting closer and closer.
44:57 But of course with a lot more flexibility.
45:02 Okay.
45:03 Okay.
45:03 I'm trying to see if I can pull up
45:06 just another latency question that I want to find.
45:09 Okay, I don't think I can find it, but it's one of those that we will
45:15 provide an answer after the event and come back.
45:19 Okay, another question on PUSCH receiver.
45:22 So, it says, can you introduce more AI algorithms used
45:26 to improve the performance of the PUSCH receiver except for channel estimation?
45:30 Yeah, of course.
45:32 I'm looking forward to working with the SA team,
45:35 for example, to do a full neural receiver.
45:37 So where we combine all of these together and build up the neural network,
45:43 whether it be convolution based or transformer based,
45:47 and then see how fast we can make it run on the GPU.
45:53 So yes, because this is written all
45:56 in JAX it means it's all differentiable which
45:59 means that anything that you can differentiate
46:01 is fair game for doing machine learning optimizations.
46:05 So if you can figure out a way to optimize the soft mapper,
46:10 yeah go for it and run gradient descent because it's differentiable.
46:14 Yeah.
46:14 Okay.
46:15 And could this model run efficiently on DUs with GPU/DPU acceleration?
46:24 Yes, that's our hope,
46:26 that you will be able to deploy some code to a test bed DU or a product DU,
46:34 try it out and see if it works.
46:37 And then maybe you can hand write some
46:40 things to harden it to get the ultimate performance.
46:43 Or maybe you want a machine learning model
46:46 in there that you want to be updated regularly,
46:48 in which case that would probably stay as a TensorRT engine and stay in Python.
46:54 Okay.
46:54 And maybe we close off with one final question.
46:59 Are you accepting contributions on GitHub?
47:02 I guess lots of people want to get their hands busy with this.
47:08 What's the plan?
47:10 We're not quite ready to accept contributions, but we'd love to get feedback.
47:19 And I guess we'll work towards that.
47:24 Okay.
47:24 Okay.
47:25 Let me see if there's any other one that I could—oh, yeah, another one.
47:33 Maybe hopefully we close out on that.
47:36 Can we use this with Sionna?
47:39 We're working towards that now.
47:41 Sionna is based on TensorFlow
47:44 and the compiler can compile TensorFlow to StableHLO.
47:48 Though we have to look at the specific operations.
47:52 Sionna is a very rich and wonderful library.
47:55 We have to look at the particular operations and data types they use to see
48:00 how practical it is to take a big block of Sionna and just drop it in.
48:05 We're working through those details.
48:08 Okay.
48:08 Okay.
48:09 I think we begin to wrap it up there.
48:13 So thank you very much, Roy, for walking us through all of that.