Seminar with Peter Fackeldey "Awkward Array: manipulating nested, variable-sized data..."
Watch on YouTubeVideo summary
Peter Fackeldey from Princeton's Research Software Institute presents Awkward Array, a specialized library designed to efficiently manipulate "jagged" data structures that are ubiquitous in high-energy physics experiments like those at CERN. Unlike traditional flat arrays, this tool utilizes offset-based memory layouts with starts and stops indices to store variable-length collections without copying data, enabling rapid processing of millions of particle collisions per second where the number of particles varies for each event. This efficient representation allows for advanced slicing and structural manipulations that prevent unnecessary loading from disk while supporting record arrays to group fields by semantic meaning, such as organizing electron properties within a dataset.
The library extends its utility through robust interoperability and performance optimizations, leveraging Numba JIT compilation to accelerate vectorized operations beyond the limits of pure Python loops. It supports multiple execution backends ranging from standard CPU implementations using NumPy and C++ to GPU acceleration via CUDA kernels similar to PyTorch, alongside a specialized Type Tracer backend for I/O optimization. To enhance code safety and readability in complex workflows, recent features include named axes that prevent invalid operations between incompatible dimensions, while distributed computing capabilities integrate with Dask to process massive datasets across clusters by handling data in manageable chunks.
Beyond particle physics, Awkward Array finds critical applications in diverse fields such as text analysis for managing variable sentence lengths, astronomy for tracking varying numbers of planets per solar system, and automotive fleet data processing. The architecture supports sophisticated I/O strategies like "Array Tracing," which delays computation to identify necessary columns before loading them, and experimental "Virtual Arrays" that use blueprints or forms to define shapes without immediate memory allocation; accessing specific fields in these virtual arrays triggers lazy evaluation via callables to fetch only the required data on demand. This approach ensures robustness against complex conditional logic while maintaining a balance between the flexibility of open-source development and the responsibility of avoiding breaking changes for hundreds of active users across scientific communities.
Read the full video transcript
So, I'm really really delighted to
welcome everyone who's here in person
and everyone who's connecting online,
the Berkeley Institute for Data Science.
My name is Kirsty Whiter. I'm the
executive director here. Um, one of the
projects that we house and support and
celebrate um inside of BIDS is the
scientific Python project and community.
who ran a developer summit in Seattle at
the beginning of May. And I was
delighted to get to meet Peter uh when
we were up there. Peter is a scientist
turned software developer, so PhD in
particle physics and now thinking about
solving problems with open-source
software. Um we had a lot of fun looking
at some GitHub statistics. He gets a A
minus in the automated statistics from
some app. Um, but I can promise you
because of the quality of his work that
he's about to tell you about, this is
absolutely an A plus. So, thank you
everyone um for coming along. This talk
is going to be a little bit longer than
our traditional um seminars. So, we're
going to run kind of probably all the
way up to the top of the hour and Peter
and and I are here for a little bit
longer afterwards. So, you are very
welcome to drop when and leave the room
whenever you need to. But you are also
very very welcome to stay um ask some
questions and we also invite comments
discussion uh beyond just questions. So
that's enough from me. Thank you so much
Peter and over to you. Thanks.
I hope you can hear me as well.
Um yeah so thank you so much for the
kind introduction and giving me the
opportunity to speak here. Um I want to
talk about the orray project uh today
and uh first I want to start uh with a
slide about me. So I'm currently a
postto um at Princeton and I'm working
in the RS project. I will explain this
in the next slide. Um my scientific
background is in high energy physics. So
I'm a member of the US collaboration
which is the second largest
collaboration at CERN since 9 years now.
Um and in case you haven't heard about
CMS, Atlas and CMS were the two big
experiments that found the expos.
Uh my PhD thesis was about pair
production. Um with the CMS experiment
um yeah but during that time I also
developed quite a software background.
Um so I'm programming in Python since
roughly 10 years and during the time of
my PhD up to the end of my PhD I
basically went from a a power user to a
contributor uh of the psychot domain
stack um to being a development
maintainer.
Um yeah and I personally have a passion
for number crunching software. Um so if
you want to talk to me about J feel
free.
Yeah. And a little bit about us. Um so
as I mentioned Psychab and Iris before
so Psychab is our community project our
domain stage on GitHub where we connect
all the um projects that that we develop
and then there's also IRS which is a
software institute funed by the National
Science Foundation and you can see on
the left on the bottom right many
institutes all over the US um are are
part of this.
this great
I want to give a little bit of an
introduction about high energy physics
because this is basically where came
from. Um and in high energy physics we
are interested in understanding the
fundamental building blocks of our
universe. And this is a lot about uh
particle interactions and what we
usually do for example at CERN or other
experiments is we shoot particles
against each other and take pictures and
that's how we probe these interactions
and um the analysis of these pictures is
basically in a very simplified way done
in three steps. So first uh we identify
from these
outcomes of the collision what is
interesting then we build combinatorics.
So we basically group together particles
and see where they originally come from
and then we reconstruct physical
properties like an invarant mass. And on
the uh left you can see a picture um
from the omega minus discovery and you
see a lot of white lines. These are all
the particles that came out of this
collision. Um, but as you can see, only
the green ones are of interest. So, we
need a way to isolate the interesting
ones. And then you can also see that
some of these particles decay into
others. Like for example, this gamma
here, this photon decays into E plus E
minus. Um, so we but we only see E plus
E minus in the detector. So what we do
is basically build a combinatorics of
both and reconstruct the problem and
then we do this with all these decay
products until we can reconstruct back
the particle that we're interested in
the omega minus and then we are for
example interested in the mass or some
other physical property. Uh
they actually did this manually uh in
the 60s. So they were looking at these
2D images uh and they analyzed by hand
more than 100,000 of these pictures and
if I got this correct this picture was
found here at Berkeley so to discover
the mines
nowadays um the experiments are big uh
yeah are much bigger um so I have this
accelerator roughly 100 meters
underground and this ring um has four
main detectors. It's CMS, LHCb, Atlas,
Alice and for example at CMS we measure
proton collisions and now we have like a
3D image of all the tracks you can see
on the top right but the analysis of
these collisions are still the same. So
we basically from all these yellow lines
we're interested in which ones are of
interest which ones are electrons for
example imons and then we have to build
combinations of those to find the
original
basically decay and then we reconstruct
those crypto
and this is what CMS and atlas did in
2012 uh for the discovery of the expos
and here this is reconstructed the bar m
of electrons or muons and electrons and
you can see in red there's this excess
of the HP um that matches the data
from a computational point of view
physics has um several challenges. So we
have to deal with a lot of data um like
our detectors they measure 40 million
collisions per second um and each
collision is of roughly one megaby of
data. So we have a data rate of 40
terabytes per second. Um
every event so every collision is
independent of the next one. Um which
gives us some nice factorization
opportunity.
Um every event produces a var number of
particles. So if you collide let's say
two protons there can be one electron or
two or even more coming out. Um so we
have to use this jacket data and um the
check measure collection of information
like momentum energy position. So we
want to represent our data basically as
arrays of stress and yeah as I said
before u one thing that we do quite
heavily is combining the products into
their parents
and this is very similar to joints in in
SD.
I want to focus on the first part of my
talk about this lower part because that
was really what a ray solves uh uh does.
So you can represent and manipulate
jacket data very efficiently.
So the open rate project um this
screenshot from pick up with me um was
mainly developed by the Iris project and
primarily by people who worked at
Princeton in the Pixie uh department and
the physics department. Um originally it
was started by Jim Pasti who changed
positions recently uh and angles uh Jana
Henry Andre and me who
now basically
development mainly the port of course we
have contributors from outside and also
from other institutes of the research
project
to give you um a direct preview of how
our library looks like. So um on the top
you can see how you can construct an
array. Oh sorry
um so this array has for example an
outer length of three and inside there
are records that we call them records
which are basically um yeah named
fields. Um and you can see that for
example this y here has first a length
of one and here it has two elements and
then it has three elements then four and
then five. So this is basically um
adding a jacket dimension in in an
array.
Um what we can do now in our array is
basically using number like expressions
to work with these arrays. So we can say
okay from this array array I want to
select all the fields so the field y and
then uh on the last dimension I slice
the first element away and then I take
the square root of this. If you do this
in equivalent Python, you would have to
write several nested for loops and track
the temporaries and intermediates. And
so
this is implemented using uh kernels and
slicing syntax. Um so we can basically
bring down the runtime the memory
footprint by quite a lot by just
comparing to run directly with primitive
Python code.
So in case you're not familiar with how
jagged arrays um can be represented um
on the left you can see how users would
write or would basically define an
output array or jet array um and on the
right you can see the memory layout. So
um let's take this example. So we have
again three um outer elements and each
of them have different lengths. So the
first one has three entries, the second
one has one entry and the last one has
two.
And uh we basically store uh the
following information in memory. So we
store a flat array of order contents. Um
and then we store also starts and stops
for example. And the starts and stops we
basically say where does the first uh
three elements start and where do they
stop? And with that information we can
basically construct an output or
generate.
Now we can also do some manipulations
for example as I show before removing uh
the first element in the last dimension.
So you can apply slicing syntax like
with no paras.
And um the output is then basically
slicing weight to 50 slicing weight five
slicing weight 40. And interestingly,
you can do that by just adding a plus
one to the start and just have a
basically a shrink mu on the data.
You can also do manipulation that is
basically passed down just to the
content. So it's not affecting the
structure. For example, taking the
square root of the energy. Um we still
have three elements in the first event,
one in the second and two in the third.
So we don't have to manipulate starts
and stops. The only thing that we can do
is basically route the numpy split.
But then there are also manipulations
um that for example sum all the energies
in in
um that require for so that's why we
have here the awkward do summation and
the one stands for it's implied on the x
as well. And what we do here is uh we're
basically summing um the elements in the
last dimension. So for example uh here
we would sum 50 + 100 + 30 280 5 and 40
+ 20 is 60 and um yeah what we do with
the content is summing it up. So we have
uh 180 50 and 60 as as the new content.
Um but for memory layout we do not need
starts and stops anymore because we
reduce the jacket dimension and now it's
a rectangular array. So
we basically don't have to track starts
and stops any
so basically these three types of
manipulations. So we have structural
manipulations
uh as I showed before this only needs
starts and stops or revers
offsets for example. Um and this is very
efficient because we actually don't need
data for this. We only know need to know
the offsets for the starts and stops. So
if we have some something that we need
to load from disk, you can actually skip
loading the data entirely because the
slides don't need it. Um so this allows
us to do some IO optimizations.
Um data manipulations um is exactly vice
versa. So it's independent of the
structure. Um it only needs the flat
contents of data. So what we can do here
is we can route the past number nine
kernels directly to the net data.
But then there are also these more
complex jacket manipulations where you
need structural information but also
need the data and for that we wrote
dedicated kernels. Um there's a or a CPP
library that is automatically shipped
with our library.
Then um as I showed you before we are
often interested in combining elements
from so basically
calculating combinations of values
inside our code array um and this is
similar to join SQL so let's take a
example here we have this array X and
then we calculate the ex the
combinations like all the two
combinations so we would do one two one
three and two three for the first one,
the second is empty and then we have uh
yeah quite some more for the last one
and you can visually uh look at this
basically calculating all the
combinations here. Um
by default like the combinations that
are duplicated are skipped but they can
be included of course and uh we can also
do this across various axis.
Then um we're also interested in doing a
cartisian product between two arrays.
For example, between all the muons and
electrons in our um uh events and for
this we have this uh function called
cartisian because it's a cartigian
product. Um and here we can basically do
a cartigian product between x and y
where you then do uh 1 a 2 a 3 a for the
first element. The second is empty
again. And then for the last one we have
4 C5 C 6 C 7.
Then uh I also said in the beginning
that we are usually interested in
grouping um uh things semantically
because uh this is just more comfortable
for us physicists to work with. So what
we do is basically we have this concept
of record arrays where we group arrays
into a meaningful structure. So we have
this outer array of events and this one
contains a jacket array of electrons and
these may have physical properties like
mass, energy, momentum and so on. So you
would have to read this as something
like events is a record array of
particle types like neons or electrons.
Uh and where each of these particles is
a record
different physical properties and these
fields uh electron and mass um they are
fixed length. They are typed and
possibly named. So you can also have a
record array that has no name. It's just
basically a triple
in high energy physics
events typically look like this. So um
one event is for example it has two
electrons which each has transverse
momentum pi which has positional
coordinates
um and then three neons with the same
properties. Um but in the second event
you may have uh
one less sorry two less neurons but then
you also have photons and these photons
may have also different properties like
it has some identification properties.
Um so it's highly reg
um it's not only we're not only having
this raggedness in the number of
basically particles but we also have
different um fields.
Yeah.
And having these record arrays allows us
uh to pretty generally u reconstruct
physical properties because our array
has this concept of behaviors and I
showed you before that these fields
can be named and if we know a name for
example energy and momentum uh we can
use this information oh this is energy
and this is momentum to calculate the
mass. So we can basically provide
automatically properties um based on
existing fields to calculate the
multiply and uh for physics uh these
quantities are abstracted into uh these
behaviors via the vector operation.
Um for more complex reconstructions
a direct provides um uh two more things.
So there's a lot of interoperability
with JIT compiled for loop expressions
with number, Julia and C++. Um and we do
have various conversions to ML
libraries. So you can convert to Python
projects.
I want to give you an example of how
this works with number. Not sure if uh
you're familiar with number but I hope
this gives a good impression. So um we
basically initialize an awkward array
and what we're now interested in is uh
summing over the field y in the last
dimension. So we want to have basically
an array that has the elements 10 0 25
21 because 1 + 1 + 2 + 1 + 2 + 3 is 10.
So what we do uh in with array
operations we basically say okay first
we select the field y then we sum over
the last axis and then we sum again over
the last a
basically eliminate
the the x's and then we end up with an
array of 10 0 25 and 21.
um
very familiar with array operations that
might be intuitive but you can imagine
this can become quite complex. Um so
what you can do is also writing for
loops just in Python. do we first
allocate. So we have this function here.
We first allocate an output array for
zeros the outer length of the array.
Then we loop over the records of this
dictionary X and Y. And then inside of
this loop we loop again over these
elements of Y. Then we sum them up
basically. And this is much more
expressive and probably easier to read
than the first base thinking of how the
Lo works. Uh and if you do this you end
up with the same output. The nice thing
here is that you can decorate this with
numberit and it gets just in time
compiled and will be uh super fast.
Um and this works because a registers
it's its own types as no number types.
Uh then we can also have a look at the
performance and um if you do a time it
on the time before this roughly takes
164
164 microconds.
Um and when you run the D kernel it's
it's much less more than a factor of 10.
Um of course the second invocation here
does not include the compiler. So for
the first invocation of this kernel it
takes a little bit little bit longer.
Yeah. So, Aquar provides all these
ingredients for energy physics that I
mentioned before. Um, you can do
slicing. So, you can isolate interesting
tracks or particles. Um, it does support
the combinatorics to find paricles.
Um, and then it provides various ways to
reconstruct physical properties.
But it also provides uh much more. So,
there are different array types, not
just jagged arrays. You also heard about
record arrays but there are some more.
Um there are different back ends. Um we
have quite a lot of high level map
operations. I just showed you some but
there are also like AK dome and so on.
Um and there are other features for
example there metadata handling XR
named dimensions and so on. I first want
to quickly go through different array
types. So typically users interact with
this highle array which is called ak.c
capital array and I showed you examples
before to initialize initialize this one
and usually users would only interact
with this array type because it is
supposed to look at like a not which is
something people are very familiar with.
um it supports a pretty duplicator
printing and inside of this printing it
uh contains a type information that
includes the shape and d type. So if you
look at the right there's this show
function and a type here it has an outer
link of four then a variable length
that's a jet dimension and then it has
basically two fields x and y where x is
of floor 64 and y has again a jacket
dimension
type in 64.
Um yeah, similar to XR these high level
arrays also do have this address
something that XR um introduced to the
arrays.
Um yeah and what you can do is uh you
can use no like indexing including fancy
indexing other or cut arrays. So you can
basically say uh
I want to index this the original array
this AK.num num is basically telling you
how many elements are on the last axis
and then I say exactly three and you can
see here um that I'm basically slicing
away all the y um fields that do not
have um uh the number of elements equals
to three.
The next level down are layouts and
these this is basically how we
internally store our code arrays. So
they have an XML like uh structure and
um each of the different types of these
layout arrays and uh they have different
purposes. So for example there's a list
offset array that one has offsets and
contents and this the jagged list that I
showed you before. Then there are record
arrays and record arrays have fields. So
there's a field X there's a field Y. Um,
and then on the the very bottom of the
spec, so to say, there are the lumpy
arrays, which are just that bad data.
And you can basically plug together any
um of these types and construct
arbitrary complex
arrays.
Um, and then the lowest level so-called
forms. And this is very the concept is
very similar to um it reminds me often
of pi trees from from Jaw
PyTorch for example um because here we
can basically decompose um a layout into
a blueprint which is just metadata and
the actual arrays that are at the bottom
of the leaves. And uh we can do this
with two buffers. And we have this
blueprint form um and the arrays. This
blueprint form is basically just JSON
like metadata to reconstruct the awkward
array and um the arrays that we uh
basically separated from the form is
just a mapping from unique keys for
example here node to data uh to an
array. And if we reconstructed this
chrome buffers, we're basically putting
node to data at a point where you find
the py node two and then we basically
plug together again um given a form of
the mapping
to an awkward array.
Yeah. So there are basically three
levels of uh awkward arrays. That's high
level the one that users interact with.
Um there are layouts that implement all
the jagged logic logic and instructs of
arrays and stuff like this. Um and there
are forms which basically separate the
metadata structure from the actual um
arrays
and yeah the letter two are basically
awkward internals. They are accessible
but usually not needed to to work with.
Um yeah the second part that array
provides a different back ends. Um and
so what's a backend for bucket array? It
basically has two purposes. Um a backend
manages memories for arrays and it also
implements jacket kernels the ones that
are not automatically there from from
you can basically with one line squeet.
So either in the constructor you can put
back equal something or there's this
function to change the back end of the
existing array.
At the default back end is uh called
CPU. The memory of arrays is managed by
numpy
and kernels are basically taken from
numpai as a CPG. Uh this one runs fully
on CPU as I said it's default back end
and awkward CDP implements additional
kernels that basically do not exist
because they have to work on checked uh
arrays.
The second back end
um is cuda. So n is managed um by cup
and um the cutters are implemented using
pup existing kernels or we extended
actually also with our own cooler
kernels. This runs fully on GPU. Um yeah
and as I said awkward provides check
kernels
um that are needed for for
I showed you before an example with
number
for CPU case you can use number cuda to
write custom that work together with
this
we also have a jacket uh this one can
run on CPU or GPU um that's chosen by
Jack because Jack manages the memory
Um the kernels are implemented they use
pulley J kernels and we have the
possibility to do this because they are
the so-called segment kernels. Um
segment kernels are basically you have a
reference array that's defining like
segments and then you can say okay the
first elements are all zeros and then it
sums basically these all zeros together
in in the in the original. Um we cannot
just compile this because it actually
requires us to know the shapes at
compile time. This is something that
cannot guarantee um but we can make use
of operative. So we can have the
gradients uh through a way using
operative functions.
Uh and then the last back end um this
may be a bit confusing now but I hope I
can clarify a bit later. So there's a
thing called type tracer. Uh and
basically we introduced this concept on
our own. So there's this type tracer
array that manages um the memory of
arrays and there's uh the type tracer
kernel library so to say. And the idea
is that type tracer arrays they are pure
poison objects. They do not hold any
numerical values but instead they just
um are basically duct typed arrays. So
they look and behave like arrays but
they don't have any values in there. Um
and uh yeah the set module as I said
implements basically a subset of all the
array API methods that we need to work
with array but yeah as I said more more
on those later.
Um the last thing I want to highlight
here because that was also
a big chunk of my work when I started at
Prince is adding named dimensions and
access
and uh named access uh have several
benefits. Um of course it's more
readable uh and understandable and you
can imagine that many supervisors in
high energy physics reach for this so
they can read their students code in a
better way. uh they lead to less errors
because you're not summing over wrong
axis because you're consciously saying
okay I want to sum over let's say all
the electrons
um and they can provide safety
opportunities. So what we can do is if
you do a binary operation that involve
like additions um that involve few
access with different names uh we can
throw an error because it's likely not
what was intended. Um, so for example,
you should not probably want to add
events and particles because it's just a
nonsensic
nonsense operation. Uh, there's also a
nice write up by covered in the PT
uh when named XR department. I can
recommend to read this. It's basically
motivating it from a computer vision
point of view. Um, and yeah, doing a
prototype in Python. Um but that was a
big inspiration to add them to a ring.
So the basics is basically you can add
named access directly to a constructor
of an or array. So you can so this array
here has two dimensions
and we can just use a tupil to annotate
the first and second one. So the first
would be then second particles. Uh you
can also use a dictionary and basically
map um a name to a dimension. So this
would be the one means the first
dimension, one means the second
dimension. Uh or you can use this uh
this function to add uh to an existing
array dimensions. The nice thing about
the dictionary notation is that you can
also pin a name to like minus one. So
you want to basically have the last
dimension is always called articles like
this. Um and then once they have these
named X's attached you can use uh these
names for for example for reduction. So
you can use AK do sum on this array and
you say I want to sum over the parts. Um
so what we do here is basically one so 1
+ 2 and 3 + 4 and we end up with an
array three seven. And you can see in
the representation it only has no events
there because we reduce basically the
particles dimension. It doesn't exist
anymore.
Um we do the fetch numpy operations
directly. So you know instead of ak sum
you can use np sum and will
automatically translate correctly to the
awkward one. And this one is also
working um the name axis.
Um if you sum for example over the
events axis so basically you say 1 + 3
and 2 + 4 which is four comma 6 then uh
the events axis is gone particles uh
access is left and now particles points
to zero because we reduce that
direction. So it's not just uh the
string is not just pointing to a fixed
integer. it will basically dynamically
adjust with the operations uh what the
dimension means stands for
um you can do uh of course you also have
to adjust the indexes when you're doing
indexing. So for example if you do
select certain elements of an array oh
sorry um you have to basically adjust
the name dimensions as well. So for
example, selecting um on the second axis
the zero element means we do not have
particles anymore. We just have events.
If we select on the first dimension we
do not have events but except we have
particles again. Um if you add new
dimensions like with non um you're
basically introducing a new outer
dimension in this case and this will
lead to events and particle will be
shifted by one because they dimension.
So then it's going to belong to the
second one as part.
Um now that we have names we can also
introduce a new syntax for indexing. Um
so what we can do is uh we can use a
dictionary for indexing. We can select
with the name uh the dimension that
um that you want to index and then here
the zero or you can put a slice there.
is the actual slice that is applied to
the direction.
Um, and you can also mix this with
position and named axis. So for example,
you can attach names to only certain
axis and others are not having any
names. So you can use a dictionary where
using integer for the number of
dimension and then the slice or the
index that you want to apply. Um but you
can also mix this or pause when you have
um so here for example I'm taking from
the first dimension the first element
and then from the particles dimension
I'm taking a slice of the basically zero
to first element and
yeah so if I do this here I'm basically
selecting the one because I'm selecting
the first group here the one comma rule
and then I'm slicing zero to one which
is basically retaining three
length one dimension uh by just taking
the element of one
and as I said before uh we can also use
some safety guards so let's cons these
two arrays here um where I'm attaching
um named ais of events and electrons and
then the second array y I'm attaching
events but now the second image has a
different name um
So if I add them
um this will give a value error because
the name axis are incompatible and it
says it got electrons for the positional
access of one uh which is
a bit conservative as a policy but it's
there for um basically providing some
security and people can opt out of this.
So once you attach an access you're not
bound to use them. You can also for this
alteration remove them and afterwards
attach them again if you like.
Yeah. So this basically um bring me to a
little summary of array and this first
big feature that I was working on. Um so
I told you about the basics of oral
arrays aboutics, the reconstruction of
physical properties and various other
features.
Um like the levels of abstraction we
have for arrays and different back ends.
Uh and yeah in the last few slides I I
showed you the new features. One thing I
want to add for indexes is that um this
is encoded throughout the whole code
base of a brain. but also work with
broadcasting um and various other things
you can imagine in order access
um and this is really a complete
implementation of named dimensions or
named axis.
Now uh for the second part uh of my talk
I want to talk a little bit about the
first uh two or three bullet points that
I skipped at the beginning that we have
to deal with large amounts of data. Um
and here we have to go to distributed
computing and I want to highlight a bit
how we facilitate this.
So uh for high energy physics analysis
we have um as a computational challenges
we have a lot of data uh usually an
analysis is working on roughly 10 to 100
terabytes uh I said before it's 40
terabyte per second of event rate or
data rate but this is already for this
10 to 100 after the collaboration the
collaborations do some central selection
processing
um And typically uh the collaboration
provides files of roughly 2 GB of size
compressed size. Um yeah so what we need
to do is basically to scale to clusters
that process these files or the data in
parallel because uh yeah usually you
cannot store 100 terabytes data on a
laptop and even if you could then um you
don't you do not have enough cost
and the second part uh that I want to
talk about in this scaling to
distributed clusters is uh usually these
files they are provided centrally from
in the operations. They contain roughly
one of 1,000 columns. So they have
physical quantities of
energy or mass of electrons. Um but
typically physicists only need a very
small subset of them like roughly order
100. Um and the memory of our clusters
is quite limited. So would be quite bad
if we load all the 1,00
columns but only use 100 because then
we're wasting
a factor of 10 um of memory.
So we need a mechanism for loading only
the required columns and this I call it
IO optimization. This is something I
want to talk about uh in a second this
part.
So now first about the scaling to a
cluster. Um I want to start again
thinking about uh how this works on a
single worker on a single machine before
scaling to whole field machines. So um
we have I showed you before how an event
in high energy physics looks like with
electrons neons photons for example. Um
what we're current what we're usually
doing is so we group them in arrays like
typically 100,000 because they roughly
fit into a 2 GB regime
and then that's what we call a chunk of
events and this is usually the unit of
data that's processed simultaneously on
a single worker. Uh physics analysis
looks like this roughly. So we basically
apply vectorzed array operations from
the original chunk or basically a
process chunk.
And here you can see uh event two is
masked out because we're not interested
in this one for example. And then the
two boxes they annotate basically
reconstructed some new properties that
were not existing before. Um and then uh
we're basically histogram uh for example
this new blue um
reconstructed uh uh information to a
histogram and arrive at yeah for example
x discovery and if you think about this
this is basically a map reduce scheme so
and there are solutions how to scale
this to clusters
and it's basic it's very simple so we
can just process various chunks
uh in parallel. So this map step is
basically in parallel in a in a new
dimension and then the reduction step is
is happening again from all the
different uh workers. And
here we're basically scaling in this
dimension with the number of workers and
typically we're reducing the output on
the client um in order to not have this
2 GB memory constraint of the cluster.
uh we did implement a library called D
awkward to make this uh automatically
because here we want to connect D which
is a P library to basically facilitate
this separately
uh and awkward array and D or allows you
to build chunk or partitioned uh awkward
collections. So basically
um you work with a huge task or
collection which internally has multiple
partitions of chunks. Um and what it
also allows you to do is build complex
tasks. So not just map and reduce but
also various granularity of uh the
actual operations that happen. Now one
example um sketch I want to show here
when you go from the very top the
original events to the histogram on the
right. This is a small uh extra the
actual sketch of the task. Um so these
are just the two layers but they have
more than few hundred ones. Um and here
we have for example electrons or sorry
letons on the very left. We select the
first one. We do this combinations with
the combinatorics that I show before. We
have two electrons. we select them
again. Then we access some properties of
them and check if they are equal and
stuff like this. So we can make uh each
part of our analysis part of the
hardware. Um but of course u wants to
schedule each operation of the cluster.
Um and we do not want to copy network
all away all all the time the arrays
between workers. So we basically got an
optimization step which fuses together
tasks to run on the on the same worker.
Um but we also have the opportunity to
eliminate tasks that are actually not
needed. So if this is right um aate the
mass of something but at the end we're
not even involing it. So why in the
first place? Um this optimization step
can also get rid of unnecessary
computations
and
this huge uh order of 100 layers graph
is then typically optimized into the
original use but it doesn't have any
unnecessary computations for it.
Um yeah so I think it's quite intuitive
to think about this uh from two
perspectives. So uh first from a single
worker point of view um the single
worker is just working on a chunk of
events um we are using I call it
implicit vectorization so basically sd
for array paradigms
uh multiers and so on to basically very
fast implementations that work on these
hundred thousands of events and the data
locality is from the view of a single
worker of course the data in the RAM
have you
um and and the CPU caches. From a
cluster point of view, um
basically a cluster works on multiple
chunks of events at the same time uh
each worker on one CH.
This is the paralization that is also
kind of implicit because we abstracted
with D uh across a cluster of multiple
workers. And the data locality here is
that usually data is bringing via
network into each worker and then it has
it in RAM or CPU caches for processing.
Uh yeah and now I want to talk about the
second part just to remind you um here
we have this problem of uh our files
they have roughly 1,00 columns of
different physical quantities but we're
only interested in a subset of them. So
we need to basically do some analysis of
what we actually need and only load
those into memory.
And uh one thing I want to think you
about is uh array tracing. And I hope
you do not know the concept yet. So I
would like to ask you what this function
does.
That's an idea.
Adds one. It adds one. Well, uh
this in this case it doesn't because uh
you cannot write in Python like anything
that if you add a one to it just does
something completely different, right?
Um so what we need to do is we need to
find out what happens inside of this
function and we need to understand what
happens inside of this function uh first
without loading any data and only what
happens to arrays of this function and
for this we use uh the concept of
tracing
um and how do tracers work. So this is a
lightweight implementation of a tracer.
So the tracer has this attribute which
is a recording. Uh it looks and behaves
like an array because it has shape and
detail.
And when you implement now the at
functions we basically append to the
recording the operation at and which
arrays are involved. So sorry self and
other and then we return a tracer with
the new shape and d type. This is a
simplifiedation.
So in practice you would have to promote
D type and so on. Uh and then if you
feed this tracer with shape 10 and the
type of flo
we're basically adding to the global
recording. Okay my computation needed uh
the self and other. So in this case the
array and the one um to run basically
our array does not record which data uh
sorry it does record which data touched
or accessed unlike tracing injects for
example does not record the computation
itself because we're only interested in
which data is needed to load it and not
the actual operation.
So that's why OKR implements its own
tracer back end which is called type
tracer. This is the back end I talked
before and I hope this gets clearer
where it's needed now. Um so the type
tracer records only what data has been
touched by the computation. It does not
record the computation itself. And the
tracing procedure is basically as
follows. So at first we have to delay
the actual computation. We don't want to
run it because that means we can load
everything. Um we can do this with Dask
because Dask is when you write a code it
just recalls everything in a computer
graph and only uping comput uh it
triggers the computation.
Then instead of calling it uh with
actual arrays the second step is we run
the computation the analysis once but
this time with a tracer not with actual
data. Um and then we have this recording
of all the columns that we need. So the
first step is basically we not we load
only these that are present in the in
the recording from this and we do not
load anything else. Um and then we run
the actual computation but this time
only with the loaded columns and ignore
the rest and we made this automatically
part of the D task graph optimization
does awkward. So if you do code compute
with awkward it will automatically try
to do this optimal optimization for you.
Um array tracing does have some
drawbacks um because uh tracers do not
know about their data. They only know
about the shape and d type. So you
cannot do any data dependent operations.
So you cannot say if some value is
larger than 10 in an array. Tracer
doesn't understand it because it doesn't
have any values. uh tracers can only
follow one path of an if else statement
and uh so in principle you have to trace
both branches. Um and this is usually
solved the jacks for example by using
where statements. Um we need to trace uh
the computation that would happen with
actual data. So we need to make sure
that uh we have pure functions. So if
during tracing something you flip a
switch and during the actual computation
um
it would do something different uh but
we wouldn't have traced that path. So
there would be a mismatch between what
we trace and what's actually wrong. Uh
and this is of course a bit tricky
because it relies on the users to
implement something like this. Um and of
course it can be a little bit tricky to
debug because
um if you drop in like a debugger you
sometimes you land in the tracing stuff
sometimes in the actual computation and
it's a bit confusing and uh turns out
physicists are not necessarily familiar
with this concept yet not as much as
probably AI researchers.
Uh so yeah it requires some support and
teaching about these new concepts.
So that's why we came up um with virtual
arrays basically lazy arrays arrays as a
second I optimization um I marked it
currently in R&D it's available in the
latest award release um and it's
basically a concept that we've
implemented a few years ago uh but we
now revisit it um and the idea is
basically we lazily load um the
individual arrays
of an awkward array whenever they are
needed. So when like the sum needs some
data or loads it um and what we did is
we implemented this at the lowest model
level form at the array container to
have the highest granularity of
basically laziness.
Uh and the trick here is uh that each
array in this container can be replaced
by callable that would load the data. Um
so every time operator needs it, it can
just hold the recordable um and access
the data. And this is you can imagine
this is somewhat uh like generics map
for for
um and it turns out that liter arrays
are much more robust than tracing. And
this is because well if you do something
like if this element is greater than 10
then well virtual race which is loaded
at check um where tracing cannot because
they never have access to loading data.
Um it does have some drawbacks. So we're
losing basically the ahead of time
knowledge of all the columns that we
need. So um this is giving us um not the
uh ability for the most optimal data
access. So we cannot move the request
for multiple columns in a single network
request and that just basically load
them on demand and there can be uh some
some performance hits if you will screen
every time we are network just to give
you an example. Just a time check
because you're just at the top of the
hour but you're really close to the end.
Yes, I'm very close to the end.
Um so just an example for virtual
arrays. So this is basically um this
from buffers that I showed before. So we
have this blueprint form and uh arrays
and uh this arrays container is having
these unique keys mapping to arrays. But
now you can basically substitute this by
column list uh and basically for example
putting lambdas before making even more
complex like networks to to get the
array. Um and then we get a virtual
array. And what you can do now with
virtual arrays um is you can look at
them and you can see they have question
marks here because they don't have
loaded any data yet. They do know about
their shape and D types because the form
encodes all of this. If we access for
example the Y field here, they still
don't show us anything. But if we do a
sum over the Y pairs, they load the data
and call basically the callable from the
method. So you have them available in
memory. If you look again then on the
right here we just loaded them. So we
can have a look at them. And if you look
at the original array you can see that
well for y we have the values available
but for x we do not because we have not
triggered any computation of x. So there
still is this question.
So uh yeah overview about the
distributed operate. Uh basically the
upper level is called paralization. So
on a single memory we use awkward array
and SMD
colors uh and then can also scale on the
cluster with task and thus offers. Um
for the IO optimization to reduce memory
we can either do tracing to get all the
required columns ahead of time um or we
can do them on demand with good
and that brings me also to my summary.
So I hope I stayed within the hour. Um
yes or is a generic array library for
regular arrays. Um it focuses on working
with regular arrays and not necessarily
the serialization unlike p for example.
Um it has quite some powerful features
which are for example different back end
metadata handling like xray and end
access. Um andary can scale to large
analysis needs. So we can parallelize on
a cluster with task and we have this
concept of IO optimization that lets you
load only what you need and not. So
thank you so much for your question.
Thank you so much. Um it's amazing where
so let me just come over. I have to
stand near you to be near near the
camera. Um so everyone online you're
very welcome to uh stick around. I have
a microphone so that the folks online
can hear us uh when you ask any
questions. So I want to just give
everyone a moment to say uh if you if
you do need to go if it's the top of the
hour, if you've got other things that um
are waiting for your time, thank you for
coming. Uh but you are also very welcome
to stay and we can ask some questions
and um have a little bit of a
discussion. So uh so folks of you are um
online you can put a message in the chat
or you can raise your hand and uh Lily
is going to make sure that you um get
the opportunity to to speak or if it's a
very short question we'll just read it
out. So does anyone have a question to
get us started?
Okay I see one hand raised. We've got a
hand raised. Thank you, Matthew.
Matthew. Yeah. Are you Do you want to
unmute Matthew? Yeah, Matthew. Hello.
You just voice it.
Yeah. Uh, you know, great talk as
always, Peter. Um so um uh I'm a fellow
particle physics uh person so I'm very
familiar with awkward but uh in your
talk you focused a lot on the great
technology that has been developed by
the awkward team and also the
applications to particle physics but
since uh these like jagged and ragged
structures are kind of generic can you
uh maybe give some examples of like uh
possible other areas of like domain
science where this might be interesting
or I know that you and Jim have given
some tutorials where you've kind of
given other uh examples as well.
Uh yes so uh there are basically two
application things that come to my mind
directly. So the first one is text
analysis or already actually implements
uh uh functionalities for this. So um
basically if you represented text uh
sorry sentences as arrays where for
example you have a jacket dimension
because each sentence has a different
number of words and then each word has a
different number of characters. Um you
immediately have a jacket jacket array.
Then you can do things like I want to
mask out certain words or slice the
first word of each sentence and stuff
like this. Um I think the second
application that I know of is in uh
astronomy. So you basically if you have
um like uh yeah collection of objects in
the universe like planets in a solar
system um and you have the array of
solar systems then you have multiple
different number of arrays sorry planets
per uh solar system. So this is also
something where you can even represent a
collection of product system to
different number of planets. So um as I
check the room
thanks for the question Matthew any yes
and just let me run to you with the
microphone. Thank you. I can actually
contribute another one because I
remember I was on a project where you
were interested in understanding for
cars in a car company's car fleet and
fleet as in they sell cars and all of
those cars record data. think of Tesla
or something like this um where you
record data about each drive that that
car would do um and record the battery
discharge and then we were interested in
modeling how the discharge behavior
would affect the battery aging and that
was also something that led us to I
think we had like 15,000 cars and then
each car had like different rides and
that was like kind of um something that
I remember we so that were XML data and
it was like I don't know 10,000
XML files per day and then I remember we
had to do a lot of processing on the
cluster ending up with the same kind of
raggedness. So um I imagine everything
that involves this observational style
of like real world objects that so yeah
that's just like one an additional part
here nothing special this sort of
anything real world or anything real
world kind of another question from
online
Daniel would you like to unmute
Sure thanks um thank you I love the
named axes feature that looks really
nice. I've seen other takes on this and
I I really like the take in awkward
array. Are you part of the discussions
to put named axes or dimensions in the
array API? I think there might have been
some chat about that in Seattle, but I I
didn't catch it.
And yes, so we had a discussion in
Seattle about this. Um
I think we uh did not follow up to you
about this discussion. So, uh, it's it's
the I would say open open to do. So, do
do you mind just sort of um for the
folks who went there just kind of
expanding on? Yeah, sure.
Uh so basically a few weeks ago in
Seattle
Python um community met um and talked
about various things and um one of these
were were
uh specific for the array API which is
still
trying to generalize array libraries um
in Python and uh I was uh bringing up
the idea of adding name connections
automatically to this. So yes, you can
do it with master master race. Um I I
talked with some people how did they did
it for master race and I think there was
constance of uh you probably can do this
name access. Um yeah but unfortunately
there wasn't a follow up about time
during the workshop to to actually give
it a try and implement.
Thanks. And I I'll just I'll just add on
top of that that kind of the one of the
purposes of the scientific Python
project and community is to recognize
where um either there are multiple ways
of doing something that makes it that
are sort of that makes it difficult for
users to kind of put stitch packages
together or there's a sort of added
complexity of thinking oh when I'm using
this package I have to think about it
this way when I'm using this package I
have to do it a different way. So
there's a piece of work around sort of
harmonizing across the the scientific
Python ecosystem. And then a sort of
additional part on top of that is if
someone's done something clever in one
area, you can percolate that across to
other other libraries and domains as
well. So that's just painting that
little extra bit of picture there. Do
you have a question? Yeah, let me run to
you.
Wonder whether anyone used uh like for
neural circuits because like you
basically can simulate each neuron but
the problem is that uh we probably don't
have the the exact sort of a ground
truth of each neuron. It's more like
really just simulation because I'm just
piggyback on his example use the flea
the car fleet but um because I'm
interesting working on the neuro circuit
I'm just wondering whether someone has
done something either directly using
this or some somewhat similar
I'm not aware of this um I would say
I think the rule of thumb is basically
if you have like some ragged dimension
in your data like each neural has a
different number of properties or uh
values that you want to measure. Um then
I would recommend to to use a regular
array library.
If you work with rectangular arrays
anyway, um I think it's not necessarily
um something you want to buy in unless
you want to make use of the other
features like different back ends,
metadata handling and so on. Um
I hope that you I'll also sort of
editorialize on top as well which is did
they oh maybe I can ask this as a
question but I'll I'll throw why I'm
asking it. um how young or old is
awkward array as a project? And um part
of the reason that we're really excited
to have Peter here and to have you here
is one of the challenges with um
interdicciplinary research is actually
having people from other domains know
that this is a thing that you can do. So
kind of being inspired by what's
happening in high energy particle
physics and translating that across to
neuroscience or neuropformatics is a
huge um goal and motivation of the
existence of bids. And so I want to be
very clear that just because people
haven't done it yet doesn't mean that
you should not be the first person to
take it forward. So yeah, do you want to
just answer the question of how how
young or old is awkward array? Yes. So
maybe Matthew can correct me because uh
he's a bit longer than I accept than I
am but uh from my uh knowledge Jim Kasi
initially started a project roughly 10
years ago
um and then it was first formulated in a
zero as a first release and that was
written in pure Python and numpy and
then suddenly became
uh obvious that there was a for these
jagged kernels. So there was a rewrite
for one to leave within C++. Um I think
that was roughly 5 years ago. Um and
then roughly two to three years ago
there was another uh major rewrite. So
there was a 2.0
which was basically still keeping um
these kernels but much more focusing to
uh the interoperable with the rest of
the scientific Python ecosystem. So we
can use task and checks as um yeah as
libraries too.
That's awesome. I'm gonna ask one more
question but I know there's questions
online. Sorry. Do you have an estimate
of how many users there are for awkward
array? I mean, you're welcome to just
say a few some and lots, but you have
uh I mean,
okay, a little bit less than
um number of users. That's a good
question. Um I think Jim showed a graph
about how many users there were over
time. Nice. Um I do not have the number.
Maybe he said many, many. Thank you.
Thank you. You can say you had some good
teamwork from me online online vende
thousands of users that was expecting
there was a question online chef on go
for it.
Hi Peter thanks for your talk really
great to see what you've done with the
library. Um, I was curious. So, now that
you've got a labeled or named axes, um,
it starts to look a little bit like
awkward array can replace some X-ray
functionality and you also get, um,
acceleration free out of the box. So, so
how do you see the relationship between
awkward array and XR going forward?
Yeah, that's good question. Um I think
um
okay I I might be wrong in this
statement but I think Xray is not uh
working with regular dimensions. Um it's
basically an it lets you um basically
partition rectangular arrays and then
annotate
um with metadata that is usually um
focused on climate science um like let's
say temperature um your arrays. So,
awkward array does add this
functionality for metadata handling. Um,
but a much more focuses on the record
arrays and working with record arrays
where I had the feeling that X array is
much more about representing partitioned
arrays and working with them on the
cluster. So, I rather say that last has
much more similarity with XR than offer
array. So, but I'm also not a big expert
on XR. Yeah, it's Stefan because we we
purposefully kept some time here for for
discussion. So I do want to kind of
invite like you know we don't we don't
have to just spend an hour quizzing the
so um I don't know Stefan if you have a
thought about that because when I when I
heard the question the way that Stefan
um asked it I was like are you just like
going to take over hex array does and
actually honestly like that's a it's a
slightly spicy question if that's the
answer but it is also a feature to
harmonize across the ecosystem if
there's development that happens then it
makes sense to bring things together.
So, so is Stefan, do you want to unmute
and kind of give your version of that
answer or is it is it an open question
for the community to friendly figure
out? I was I was just thinking you know
if I have like heavy rectangular array
data I need to process maybe X-ray is
still the place but it sounds like now
if I have um you know an application
where I would specifically benefit from
acceleration for example and I don't
need all of the X-ray features then
maybe I can you know get get something
out of um awkward array that I wouldn't
have been able to do before. Anyway,
just curiosity um I I think I imagine
they serve distinct purposes. I was just
curious um you know what what do you
foresee users might do. Certainly I
think I would agree with that.
So there was one more comment just a
comment that and I believe you had a
question out the comment.
Oh yeah, there was just a comment from
Daniel. Um, he says, "As a happy user,
both awkward and X-ray, I think X-ray
does reflect some fine-tuning for the
needs of the client science community,
whereas Oh, then it just scrolled.
Whereas awkward is a more fundamental
data structure, right? I think one thing
you may want to keep in mind is that uh
um
there might be cases when you use
awkward array uh with rectangular arrays
that you introduce uh starts and stops
that are actually not necessary. Usually
they're optimized away but maybe in some
cases that might happen and then may
blow a little bit of memory. It's not a
lot but in that case it's say it's
always safer if you have rectangular
data. um cruise for example.
Amazing. Thank you. And thank you Dan
for um joining. Go for it.
Yeah. So uh I'm I'm working on a project
where we're currently trying to use
awkward array for some optimization.
Uh but we haven't been able to because I
can't write from a Numba kernel to
awkward array. Yes. Um and and I'm I'm
barely started like looking into why
that I think it more lies on the number
side. But is there any like feature of
awkward array that would be preventing
it from having number kernels writing to
awkward array? Uh yes. So awkward arrays
are immutable and you cannot change
values of an awkward array. Um but
there's a solution for this with
blockchain. So we have this concept of
an array builder. um where you can
basically write um or basically
construct in for loops an or array um um
with you do not use this AK array but
instead of a different class um and you
can populate basically your jacket array
inside of a
so that I think that may suit exactly
your needs okay
what would be the easiest these stories
follow up with that question. Is that a
question for all or is that a question
for number one? You have an example on
our documentation. I can see. Amazing.
Amazing. Thank you. We are here to help
problem solve. So, um I'm very
interested in kind of the human side of
sustaining total source projects. Um,
what do you think is the most fun part
of maintaining an open source project
that's heavily used and what is the
thing that keeps you awake at night
worrying? Uh, I would say it's the same
because
uh because the fun part was about
learning. You cannot just remove the
feature because then a few hundred
people will directly knock at your door
and say uh what just happened? Yeah. Um
this is uh fun to learn. I think this is
a very fun experience. Um but at the
same time uh this also a bit restricting
because you cannot just uh overhaul the
code base and introduce something new um
that may break something. So it's always
you have to think very carefully about
uh what you change. Um so yeah, I would
say it's actually yeah that's I love
that. I love that answer. Thank you so
much. Um folks online, thank you so much
for for joining. Everyone here, you're
very welcome to stay and informally um
chat with Peter. Uh but otherwise, have
a lovely afternoon and weekend when we
get to it.