Articles on Adam2392's Bloghttps://blogs.python-gsoc.orgUpdates on different articles published on Adam2392's BlogenFri, 25 Aug 2023 20:12:59 +0000Aug 28 - Sep 3, 2023: Week 14https://blogs.python-gsoc.org/en/adam2392s-blog-copy-2/aug-28-sep-3-2023-week-14/<p><b>This week, I am finishing my final PR to fix the Coiteration algorithm: ​​<a href="https://github.com/hameerabbasi/xsparse/pull/31">https://github.com/hameerabbasi/xsparse/pull/31</a>.</b></p> <p> </p> <p><b>This will enable coiterations over levels that are nested within other levels. As such, we can define multiple Coiterate objects that co-iterate over each dimension respectively. </b></p> <p> </p> <p><b>With this, the ground-work will be laid to very easily implement the MergeLattice Coiterator, which is just an abstraction on top of this idea of calling multiple coiterators.</b></p> <p>The final PR now shows a unit-test that Co-iterates over two sets of nested levels, which each together define a CSR matrix. It is done with a conjunctive merge, so the unit-test defines how the API must be specifically defined. There were a few errors that I ran into that were hard to debug, but it turns out they were consequences of how I was leveraging the Coiterate API, which is slightly unforgiving right now.<br>  </p>adam2392@gmail.com (Adam2392)Fri, 25 Aug 2023 20:12:59 +0000https://blogs.python-gsoc.org/en/adam2392s-blog-copy-2/aug-28-sep-3-2023-week-14/Aug 21 - Aug 27, 2023: Week 13https://blogs.python-gsoc.org/en/adam2392s-blog-copy-2/aug-21-aug-27-2023-week-13/<h1> </h1> <p><b>This week, I was sick so did not have a lot of time for work. </b></p> <p> </p> <p><b>However, I did play around with the new API for the Coiterate that we needed to get working. Specifically, I realized that if we pass in a tuple of PKs and IKs into Coiterate, then we need to adapt various other parts of the API, which assumed the PK was a single element. This may present some challenges that I will aim to finish as the last sprint of my GSoC.</b></p> <p><br>  </p>adam2392@gmail.com (Adam2392)Mon, 21 Aug 2023 00:28:26 +0000https://blogs.python-gsoc.org/en/adam2392s-blog-copy-2/aug-21-aug-27-2023-week-13/Aug 14 - Aug 20, 2023: Week 12https://blogs.python-gsoc.org/en/adam2392s-blog-copy-2/aug-14-aug-20-2023-week-12/<h1> </h1> <p><b>This week, I finished up the PR to add implementation of a Tensor. I also identified a flaw with the previous Coiteration implementation. For the remainder of the GSoC I will work on fixing that flaw.</b></p> <p> </p> <p><b>Basically, the Coiterate iterator should take in a tuple of indices and a tuple of Pks if coiterating over a set of levels defined by a stack of levels.</b></p> <p><br>  </p>adam2392@gmail.com (Adam2392)Mon, 14 Aug 2023 17:29:55 +0000https://blogs.python-gsoc.org/en/adam2392s-blog-copy-2/aug-14-aug-20-2023-week-12/Aug 7 - Aug 13, 2023: Week 11https://blogs.python-gsoc.org/en/adam2392s-blog-copy-2/aug-7-aug-13-2023-week-11/<h1> </h1> <p><b>This week, I finished up the PR to add <a href="https://github.com/hameerabbasi/xsparse/pull/29">static_assert to the container-traits</a>. I also simplified the Tensor implementation PR and submitted it for review/merge.</b></p> <p> </p> <p><b>The bulk of my work now has to do with implementing a version of the Coiterate over the Tensor classes now, instead of over single levels.</b></p> <p> </p> <p><b>Now, merge lattice takes in a tuple of Tensors:</b></p> <p> </p> <ol> <li> <p><b>Constructor: we can compile-time check that the tensor shapes align? I don’t know how to do this, so probably a future PR.</b></p> </li> <li> <p><b>Algorithm:<br> <br> The algorithm must initialize a coiterator over levels for the index into the level given to the merge lattice. It will advance, dereference and compare elements until this coiterator is done. Then it will advance the coiterator above.</b><br> <br>  </p> </li> </ol> <p><b>Ex:</b></p> <p> </p> <p><b>Expression: (A_ij + B_ij) @ C_j = D_i</b></p> <p><b>Tensors: (A, B, C)</b></p> <p><b>Indices: [(0, 1), (0, 1), (1)]</b></p> <p> </p> <p><b>How would the iterator function? Initialize iter1 = Coiterate(A_i*, B_i*, true). Initialize iter2 = Coiterate (A_*j, B_*j, C_j).</b></p> <p> </p> <p><b>If iter2 != end:</b></p> <p><b>Advance iter2;</b></p> <p><b>Else:</b></p> <p><b>Advance iter1;</b></p> <p><b>Reset iter2; // what would this mean? Iter2 now must take in the new IK and PK from iter1 to know where to start</b></p> <p> </p> <p><b>My intuition stems from: <a href="https://github.com/hameerabbasi/xsparse/blob/10b91002e246a16d2e14db8495faafa3774d383e/test/source/compressed_test.cpp#L63-L77">https://github.com/hameerabbasi/xsparse/blob/10b91002e246a16d2e14db8495faafa3774d383e/test/source/compressed_test.cpp#L63-L77</a></b></p> <p> </p> <p><b>Questions:</b></p> <ol> <li> <p><b>Basically, what sort of objects do I need to handle in the iterator of MergeLattice class? Should I store a tuple of ``Coiterate`` classes for each tuple of levels from the Tensors?</b></p> </li> <li> <p><b>Am I thinking about this correctly?</b></p> </li> </ol>adam2392@gmail.com (Adam2392)Thu, 10 Aug 2023 01:37:43 +0000https://blogs.python-gsoc.org/en/adam2392s-blog-copy-2/aug-7-aug-13-2023-week-11/July 31 - Aug 6, 2023: Week 10https://blogs.python-gsoc.org/en/adam2392s-blog-copy-2/july-31-aug-6-2023-week-10/<p><b>This week, I finished the compile-time check validity of coiteration in the PR <a href="https://github.com/hameerabbasi/xsparse/pull/26">[ENH] Check validity of coiteration</a>. This has a few hacks, but it works and is able to raise a compiler-error when co-iteration with a disjunctive merge with an unordered level is involved.</b></p> <p> </p> <p><b>I am starting to understand the merge lattice class more. It is simply another abstraction on iteration that now iterates over collections of tensors instead of collections of levels like co-iteration does. </b></p> <p> </p> <p><b>As a few pre-steps towards implementing the merge lattice, I also added a PR that adds compile-time checks for valid methods to be defined for any container traits that would store indices in the sparse tensor, or the pointers to the next level (or data array): <a href="https://github.com/hameerabbasi/xsparse/pull/29">Adding required member functions of abstract container traits</a>. This tightens up the API to allow users to define their own custom containers as long as they implement a certain API.</b></p> <p> </p> <p><b>I also started working on the Tensor class implementation. The tensor class now connects a collection of levels to actual data and represents a sparse n-dimensional array. <a href="https://github.com/hameerabbasi/xsparse/pull/30">[ENH] Adding Tensor implementation</a>. This probably will take up most of my next week, since I will need to understand how to implement the iterator over the tensor. This will require initializing iterators over all the levels that define the tensor and then also dereferencing to get the right indices and data.</b></p> <p> </p> <p><b>A few questions I’ll have to figure out this week are:</b></p> <ol> <li> <p><b>How to initialize the iterators of each level?</b></p> </li> <li> <p><b>How to design the Tensor’s iterator? Should I follow a design similar to `Coiterate` since they are both at a high-level iterating over a collection of levels?</b></p> </li> <li> <p><b>What should dereference do? My thinking is return a tuple of indices (i.e. IKs) and then the data value, which is just the PK pointer into the data vector.<br> <br> I.e. tensor* -&gt; (1, 0, 5) 60.3 returns the value at index (1, 0, 5) in the 3D sparse array with non-zero value 60.3 there.</b></p> </li> <li> <p><b>Does it matter if we iterate over an unordered level?</b></p> </li> </ol>adam2392@gmail.com (Adam2392)Sat, 29 Jul 2023 01:01:46 +0000https://blogs.python-gsoc.org/en/adam2392s-blog-copy-2/july-31-aug-6-2023-week-10/July 17 - Jul 23, 2023: Week 8https://blogs.python-gsoc.org/en/adam2392s-blog-copy-2/july-17-jul-23-2023-week-8/<p><b>`Tensor` would be a tuple of levels and the last level will point into the data vector</b></p> <ul> <li> <p><b>“one Tensor” per tensor involved in the tensor operation. E.g. A+ B= C has A, B and C as a `Tensor`.</b></p> </li> <li> <p><b>TODO: add some validations and stuff, but initially we can have it as just a lightweight template class to hold a “bunch of levels”.</b></p> </li> </ul> <p> </p> <p><b>Coiterate questions:</b></p> <ol> <li> <p><b>My recursion to check the template is not working properly.</b></p> </li> </ol> <p> </p> <p><b>Tensor Questions:</b></p> <ol> <li> <p><b>What operations should a tensor have?</b></p> <ol> <li> <p><b>Should it have insert value at indices?</b></p> </li> <li> <p><b>No, because it should be defined during compile-time.</b></p> </li> </ol> </li> <li> <p><b>What checks should it do during construction?</b></p> </li> <li> <p><b>How is the template datatype used?</b></p> </li> </ol> <p> </p> <p><b>MergeLattice Questions:</b></p> <ol> <li> <p><b>What operations should a mergeLattice do?</b></p> <ol> <li> <p><b>How does a mergeLattice work in the context of say (A_ij + B_ij) @ D_i = C_i?</b></p> <ol> <li> <p><b>In this case, F := (A || B) &amp;&amp; D would be our “function passed”</b></p> <ol> <li> <p><b>When iterating over i, F(A_i, B_i, D_i) gets the booleans based on if values are present</b></p> </li> <li> <p><b>When iterating over j for a fixed i, F(A_ij, B_ij, true) gets the booleans based on if values in A_ij, B_ij are present</b></p> </li> <li> <p><b>“j” is advanced if “i”</b></p> </li> </ol> </li> <li> <p><b>Vector of indices for A,B,D, C is (0, 1), (0, 1), (0), (0, 1)</b></p> </li> <li> <p><b>Order of iteration = i, then j</b></p> </li> <li> <p><b>E.g. merger = MergeLattice(pair((A, (0, 1)), (B, (0,1)), (D, (0)))</b></p> </li> <li> <p><b>Since D_ij is not present, we would skip “j” in A and B when iterating</b></p> </li> <li> <p><b>merger.merge_iterators()</b></p> </li> </ol> </li> <li> <p><b>E.g. of something not possible with the constraint on increasing indices in vectors: D_ij + A_ji </b></p> </li> </ol> </li> </ol> <p> </p> <p><b>`MergeLattice` would take in a tuple of these input Tensors:</b></p> <ul> <li> <p><b>E.g. The input Tensors would be (A, B)</b></p> </li> <li> <p><b>Tuple of vector of integers of the same length as the tuple of input tensors.</b></p> <ul> <li> <p><b>Can check this during compile-time.</b></p> </li> </ul> </li> <li> <p><b>For each tuple element, i.e. Tensor in `input_tensors`, and vector[int] in `input_indices`, we would check the number of levels associated with that `Tensor` and the size of the vector. They should match.</b></p> <ul> <li> <p><b>We need a tuple of tuple of integers at compile time, rather than a tuple of vectors.</b></p> <ul> <li> <p><b>Or constexpr std::vector</b></p> </li> <li> <p><b>This can be compile-time check ideally if so.</b></p> </li> </ul> </li> </ul> </li> <li> <p><b>For each input indices, they should be strictly increasing (for now)</b></p> <ul> <li> <p><b>We can relax this restriction once workspaces are implemented</b></p> </li> <li> </li> </ul> </li> <li> <p><b>We also want the function F</b></p> </li> <li> <p><b>Note: the coiterator already knows the current start/end when we pass in valid Ik/Pk from the previous levels. I.e. it infers the start/end iterators for the current level.</b></p> </li> </ul> <p> </p> <p><b>template &lt;class F, typename… Levels&gt;</b></p> <p><b>class MergeLattice(std::tuple[Levels]&amp; input_levels, int output_dims, std::tuple[std::vector[int]] input_indices)</b></p> <p><b>{</b></p> <p><b>// constexpr check that no input_indices are greater than output_dims</b></p> <p><b>// constexpr check that input_indices lists each has same length as each vector of input levels</b></p> <p><b>// second check: each std::vector[int] should be the size of the input level dimension</b></p> <p><b>// Notes: see above</b></p> <p><b>}</b></p> <p> </p> <p><b>public:</b></p> <p><b>{</b></p> <p><b>inline constexpr auto get_merge_points() const noexcept</b></p> <p><b>  {</b></p> <p><b>// return a vector of mergepoints</b></p> <p><b>}</b></p> <p> </p> <p><b>inline constexpr auto merge_iterators() const noexcept</b></p> <p><b>{</b></p> <p><b>// k-way/two-way merge algorithm(?) that is, we merge an iterator into 1</b></p> <p><b>// uses a coiterator over a subset of the iterators that can be co-iterated over</b></p> <p><b>// then iterates over them and assigns them into a new level/iterator(?) e.g. dense?</b></p> <p><b>}</b></p> <p><b>}</b></p> <p> </p> <p><b>If that level exists in upper level, then we would go below and recurse and fix the upper level to true. </b></p> <p> </p> <p><b>If we are doing an outersum. For example:</b></p> <p> </p> <p><b>A_i + B_j = C_ij</b></p> <p> </p> <p><b>Function `F` would be `A || B`.</b></p> <p> </p> <p><b>For A, we would put in `true` for the function whenever A value exists and then loop over B, and when A value doesn’t exist, then put in `false` for the function and loop over B as well.</b></p> <p><b>If `F` was `A &amp;&amp; B`, then this would not loop over B. It would be like giving an empty A level fixing A values always to missing for all values of B.</b></p> <p> </p> <p><b>Iterate over each level, find out if it has a value in the upper level. If there is no upper-level, then just check if there's a value now in this level corresponding to `true` as the initial value per input.</b></p> <p> </p> <p><b>Coiterate over everything that exists for that dimension and then for other values put in the false/true that we get.</b></p> <p><br>  </p>adam2392@gmail.com (Adam2392)Sat, 22 Jul 2023 18:33:09 +0000https://blogs.python-gsoc.org/en/adam2392s-blog-copy-2/july-17-jul-23-2023-week-8/July 10 - Jul 16, 2023: Week 7https://blogs.python-gsoc.org/en/adam2392s-blog-copy-2/july-10-jul-16-2023-week-7/<p><b>This week, I continued work on implementing the compile-time check for valid co-iteration.</b></p> <p> </p> <p><b>The algorithm proceeds by calling two functions during instantiation of the Coiterate class.</b></p> <ol> <li> <p><b>Are all levels ordered, or has the locate function?</b></p> <ol> <li> <p><b>If this does not return true, then error out</b></p> </li> </ol> </li> <li> <p><b>Next, check a more complicated compile-time check:</b></p> <ol> <li> <p><b>Where we use template recursion up to a depth of 1024 to check all possible combinations of unordered levels with “true/false” input to `m_comparisonHelper`</b></p> <ol> <li> <p><b>We are template recursing over levels. E.g. for each unordered level, we can run 2 static_asserts m_comparisonHelper(..., true,...) and m_comparisonHelper(..., false, …)</b></p> </li> <li> <p><b>If the level we are checking is ordered, just static_assert false in the correct place.</b></p> </li> <li> <p><b>Note: most likely we will never really hit the 1024 depth limit in practice because the higher-level API will most likely consolidate the tensor operations with temporary workspaces in the middle.</b></p> </li> </ol> </li> <li> <p><b>E.g.<br> <br> m_levelsTuple = (A, B, C, D)<br> <br> And<br> <br> A, and D are unordered with locate() function defined and B and C are ordered.<br> <br> We want to check that the output is always false:</b></p> <ol> <li> <p><b>m_comparisonHelper(false, false, false, false)</b></p> </li> <li> <p><b>m_comparisonHelper(true, false, false, true)</b></p> </li> <li> <p><b>m_comparisonHelper(false, false, false, true)</b></p> </li> <li> <p><b>m_comparisonHelper(true, false, false, false)</b></p> </li> </ol> </li> </ol> </li> </ol> <p> </p> <p><b>I have C++ code that defines a class called Coiterate. Coiterate gets as input a tuple of levels, represented by `m_levelsTuple`. The goal is to perform a compile-time check that goes through all levels in `m_levelsTuple`. Each level must be ordered, or have the `locate` function defined.<br> <br> To determine if a level is ordered, we can check each level’s `LevelProperties::is_ordered` namespace property. For example, `A::LevelProperties::is_ordered` would determine if `A` is ordered or not. To determine if a level has the `locate` function defined, we have the following `has_locate_v` function.</b><br>  </p> <p> </p> <p><b>Merge Lattice:</b></p> <p> </p> <p><b>[Summary] Each input to the merge lattice is a stack of levels (i.e. dimensions), and we need to determine are we actually recursing into this specific input at this dimension or not, and if so, call Coiterate… If not, what do we do?</b></p> <p> </p> <p><b>If a level is broadcasted, then it would not be present. Then since that “level is not there”, we may need to construct helper classes to represent what that level should be. If the level above tells you that the current level is present/not, then you put true/false and fix it in the `m_comparisonHelper`. </b></p> <p> </p> <p><b>Input indices to output indices:</b></p> <ol> <li> <p><b>Single uint_p index which is number of output dimensions<br> E.g. 2 in this example</b></p> </li> <li> <p><b>For each input’s levels, we have a list of integers corresponding to<br> [0, 1] and [0, 1] (can use std::vector since it is constexpr as of C++20)<br> <br> The lists should be the same length as the input they correspond to. Not necessarily the size of the output dimensions (e.g. 2 in this case).</b></p> </li> </ol> <p> </p> <p><b>A_ij + B_ij = C_ij<br> <br> Another example:<br> <br> A_i + B_j = C_ij<br> <br> 1. Output dimension = 2</b></p> <p><b>2. Input dimension lists: [0] and [1]</b></p> <p><br>  </p> <p><b>TODO:</b></p> <ol> <li> <p><b>Start thinking about writing unit-tests first regarding merge lattices (keep in mind the code should be compile-time as much as possible)</b></p> </li> <li> <p><b>Sketching out the API, possibly from taco:</b></p> <ol> <li> <p><b>Comment about where we got it from and the license</b></p> </li> <li> <p><b>Change string operations to index operations</b></p> </li> <li> <p><b>Keep in mind the above input -&gt; output index operations</b></p> </li> </ol> </li> <li> <p><b>Keep reviewing the first taco paper and Kjolstad’s thesis</b></p> </li> </ol>adam2392@gmail.com (Adam2392)Wed, 12 Jul 2023 17:52:10 +0000https://blogs.python-gsoc.org/en/adam2392s-blog-copy-2/july-10-jul-16-2023-week-7/July 3 - Jul 9, 2023: Week 6https://blogs.python-gsoc.org/en/adam2392s-blog-copy-2/july-3-jul-9-2023-week-6/<p><b>I discussed the following debuggers for C++:UBSan and Valgrind. And if I can get my Linux machine working, gdb. I started using LLDB on VSCode C++ and it was wonderful.</b></p> <p> </p> <p><b>I was able to solve with the help of my mentors the compiler-time refactoring of the code. Each functionality within the coiterator now fully unwraps into a level check:</b></p> <ol> <li> <p><b>Computing the min_ik: we now ignore the unordered levels when we do this.</b></p> </li> <li> <p><b>Advance_iter: we now ignore the unordered levels when we do this.</b></p> </li> <li> <p><b>Compare: we now only compare the ordered levels and default to ‘true’ for unordered level comparisons.</b></p> </li> </ol> <p> </p> <p><b>The coiterator has the implicit assumption that only ordered levels are supported if there is a disjunctive merge. If there is a conjunctive merge, it must contain at least one ordered level and for all other unordered levels, they must have the `locate` function defined.</b></p> <p> </p> <p><b>In order to make sure this is valid during compile-time, we want to implement a compile-time check during the instantiation of the coiterator. This was the initial goal of the PR, but since we uncovered a bunch of problems, we had to table it. Now that we have refactored the code into a more generalized state, we can revisit this compile-time check.</b></p> <p> </p> <p><b>The way this is done is via fold expression most likely:</b></p> <p> </p> <p><b>If:</b></p> <p><b>    * 1. the levels are all ordered (i.e. has the `is_ordered == True` property)</b></p> <p><b>    * 2. if any of the level are do not have the is_ordered property, it must have the locate</b></p> <p><b>    * function, else return False. Then do a check that `m_comparisonHelper` defines</b></p> <p><b>    * a conjunctive merge (i.e. AND operation).</b></p> <p><b>    * Otherwise, coiteration is not allowed.</b></p> <p> </p> <p><b>The check with respect to `m_comparisonHelper` must be done for all unordered levels.</b></p> <p><br>  </p>adam2392@gmail.com (Adam2392)Thu, 06 Jul 2023 02:45:58 +0000https://blogs.python-gsoc.org/en/adam2392s-blog-copy-2/july-3-jul-9-2023-week-6/June 25 - Jul 2, 2023: Week 5https://blogs.python-gsoc.org/en/adam2392s-blog-copy-2/june-25-jul-2-2023-week-5/<p><b>I discussed the following debuggers for C++:UBSan and Valgrind. And if I can get my Linux machine working, gdb. I started using LLDB on VSCode C++ and it was wonderful.</b></p> <p> </p> <p><b>Refactored the `get_PKs_level` function into two functions now where the `get_PKs_level` helps pass the fold expression and the corresponding iterator to the `get_PK_level` function, which can then use the iterator to perform compile-time if/else functions, such as dereferencing, or locating into the iterator.</b></p> <p> </p> <p><b>Furthermore, I spent this week refactoring the C++ code specifically for calculating the “min_ik”, “advancing the iterators” and “getting the PKs”, which all follow a design that is more readable, yet still compile-time evaluated.</b></p> <p><br>  </p>adam2392@gmail.com (Adam2392)Wed, 28 Jun 2023 20:23:42 +0000https://blogs.python-gsoc.org/en/adam2392s-blog-copy-2/june-25-jul-2-2023-week-5/June 18 - Jun 25, 2023: Week 4https://blogs.python-gsoc.org/en/adam2392s-blog-copy-2/june-18-jun-25-2023-week-4/<p><b>This week, I’ve been continuing the PR <a href="https://github.com/hameerabbasi/xsparse/pull/25">https://github.com/hameerabbasi/xsparse/pull/25</a>. I am currently running into a segfault when running the unit-tests of coiterating over a (dense, hashed) levels because the hashed level seems like it is trying to access a previous element after already iterating through it. This is an issue because `min_ik` is getting set to a previous index since hashed level iterates out of order.</b></p> <p> </p> <p><b>My suspicion is that the `min_ik` design needs to be changed to account for co-iterating over unordered levels. I will discuss this with my mentors.</b></p> <p> </p> <p><b>Since I wasn’t able to discuss merge lattices with my mentors last week, here are the same notes for this week’s discussion:</b></p> <p> </p> <p><b>The MergeLattice at a high level should take in a tuple of levels that are “merge points” on the merge lattice. In addition, it should take in a index expression that dictates how the levels are merged.The index expression in taco uses a set of strings like `expr = C(i, j) = A(i,j) + B(i,j);`, whereas we would want to define an arbitrarily complex index expression… I still have to do some more reading to get an idea of how this part is implemented.</b></p> <p> </p> <p><b>Internally, the MergeLattice given the index expression will be able to determine which levels are co-iterated. Moreover, it must construct the `F` function that is passed to `Coiterate`. </b></p> <p> </p> <p><b>Overall, we would like the following higher-level function as well:</b></p> <ol> <li> <p><b>Construct a union (disjunction) over lattice points</b></p> </li> <li> <p><b>Construct an intersection (conjunction) over lattice points</b></p> </li> </ol> <p> </p> <p><b>A merge lattice is constructed per index iterator in a tensor operation. For example, say we have:<br> <br> Aij = (Bij + Cij) @ Dij</b></p> <p> </p> <p><b>To set index i for A, we have to iterate over B_i, C_i and D_i.</b></p> <p><b>To set index j for A, we have to iterate over B_*j, C_*j, and D_*j.</b></p> <p> </p> <p><b>Each index constitutes a merge lattice that we need to construct to then call Coiteration. We want to extract the operators “+” and “*” to determine addition and multiplication, where addition is converted to a disjunction and multiplication is converted to a disjunction.</b><br>  </p> <p><b>In the following example:</b></p> <p> </p> <p><b>A_i = b_i + c_i d_i </b></p> <p> </p> <p><b>Has a conjunction and disjunction. We proceed by:</b></p> <p> </p> <ol> <li> <p><b>Create leaf of the merge lattice from the tensor access rule(?</b></p> </li> <li> <p><b>Create merge lattice for c_i d_i by computing conjunctive merge (ciΛ di) a_i = this if this lattice point is reached</b></p> </li> <li> <p><b>Create merge lattice for b_i since there is no other conjunctive merge with it.</b></p> </li> <li> <p><b>Create upper-most merge latticepoint for disjunctive merge (bi) v (ciΛ di)</b></p> <ol> <li> <p><b>So the merge lattice points starts with a whole expression that is a disjunction among conjunctions</b></p> </li> <li> <p><b>Then it traverses through each lattice point, which trims down parts that are not necessary for co-iteration </b></p> </li> </ol> </li> </ol> <p> </p> <p><b>Questions:</b></p> <ol> <li> <p><b>What is the general input for a merge lattice? What is calling it? Will we have to implement, for example, an iteration graph?</b></p> </li> <li> <p><b>Do we implement the “LatticePoint”? </b></p> </li> <li> <p><b>How do we expect the “tensor operations” to be represented?</b></p> </li> </ol>adam2392@gmail.com (Adam2392)Sat, 24 Jun 2023 16:21:14 +0000https://blogs.python-gsoc.org/en/adam2392s-blog-copy-2/june-18-jun-25-2023-week-4/June 11 - June 18: Week 3https://blogs.python-gsoc.org/en/adam2392s-blog-copy-2/june-11-june-18-week-3/<p><b>This week, I’ve been digging into the details of how to best carry out co-iteration when there are levels that are not ordered. The thinking before was that co-iteration when there is a conjunctive merge was we can make the following changes:</b></p> <p> </p> <ol> <li> <p><b>Advancing iterators: Advance only the ordered level iterators, since the other iterators can do `locate(PKM1, min_ik)`. </b></p> </li> <li> <p><b>Dereferencing iterators to get PKs: Since we have locate, we can directly get the PK for a unordered level with the `locate()` function. </b></p> </li> </ol> <p> </p> <p><b>However, in 1., this is not as straightforward. Say we have A \intersect B and A has 1000 non-zero elements and B has 1 non-zero element. If A is ordered and B is not, we have to iterate over the entirety of A, when in reality, we should be able to exit early. The dereferencing part though can definitely be implemented and should be changed.</b></p> <p> </p> <p><b>Other improvements I have made in the code are:</b></p> <p> </p> <ol> <li> <p><b>Add `BaseTraits::I i` as an unused parameter in `hashed::iter_helper`<br> <br> iteration_helper iter_helper([[maybe_unused]] typename BaseTraits::I i,typename BaseTraits::PKM1 pkm1)</b><br>  </p> </li> <li> <p><b>In `Coiterate::coiteration_helper`, I changed the initialization of the `iterators` member to<br> <br> std::tuple&lt;typename levels::iteration_helper::iterator...=""&gt; it) noexcept<br> <br> From<br> <br> std::tuple&lt;typename levels::levelcapabilities::iteration_helper::iterator...=""&gt; it) noexcept<br> <br> Where we remove LevelCapabilities from the namespace. This was required since the `iteration_helper` for the hashed level is not implemented as part of the `LevelCapabilities` namespace, so this change allows `Coiterate` to be defined with some levels that include a hashed level.&lt;/typename&gt;&lt;/typename&gt;</b></p> </li> <li> <p><b>Rename `Coiterate::coiteration_helper::iterator.locate` to `deref_PKs`, since that is what it is actually doing.<br>                </b></p> </li> </ol> <p><b> template &lt;class iter=""&gt;&lt;/class&gt;</b></p> <p><b>                inline auto deref_PKs(iter i) const noexcept</b></p> <p><b>                {</b></p> <p><b>                    return (std::get&lt;0&gt;(*i) == min_ik)</b></p> <p><b>                               ? std::optional&lt;std::tuple_element_t&lt;1, decltype=""&gt;&gt;(&lt;/std::tuple_element_t&lt;1,&gt;</b></p> <p><b>                                   std::get&lt;1&gt;(*i))</b></p> <p><b>                               : std::nullopt;</b></p> <p><b>                }</b></p> <ol> <li> <p><b>Also in the actual `get_PKs` function, we now use locate if the iterator has the locate function, otherwise we apply dereferencing.</b><br> <br>  </p> </li> </ol> <p><b>                inline auto get_PKs() const noexcept</b></p> <p><b>                {</b></p> <p><b>                    /**</b></p> <p><b>                     * @brief Return tuple of PKs from each level.</b></p> <p><b>                     *</b></p> <p><b>                     * @details If the level is ordered, return the PK from the iterator using</b></p> <p><b>                     * dereferencing `*iter`. If the level is unordered, return the PK from</b></p> <p><b>                     * the iterator using `iter.locate()`.</b></p> <p><b>                     */</b></p> <p><b>                    return std::apply(</b></p> <p><b>                        [&amp;](auto&amp;... args)</b></p> <p><b>                        {</b></p> <p><b>                            return std::make_tuple((has_locate_v&lt;decltype(args)&gt;&lt;/decltype(args)&gt;</b></p> <p><b>                                                        ? args.locate(m_coiterHelper.m_pkm1, min_ik)</b></p> <p><b>                                                        : deref_PKs(args))...);</b></p> <p><b>                        },</b></p> <p><b>                        this-&gt;iterators);</b></p> <p><b>                }<br> <br> The only issue is that this produces a compiler error.</b></p> <p> </p> <p><b>Besides these improvements, I started reviewing the MergeLattice implementation inside the existing taco compiler. The code here is implemented using run-time code. There the implementation uses a builder class to construct a MergeLattice.</b></p> <p> </p> <p><b>The MergeLattice at a high level should take in a tuple of levels that are “merge points” on the merge lattice. In addition, it should take in a index expression that dictates how the levels are merged.The index expression in taco uses a set of strings like `expr = C(i, j) = A(i,j) + B(i,j);`, whereas we would want to define an arbitrarily complex index expression… I still have to do some more reading to get an idea of how this part is implemented.</b></p> <p> </p> <p><b>Internally, the MergeLattice given the index expression will be able to determine which levels are co-iterated. Moreover, it must construct the `F` function that is passed to `Coiterate`. </b></p> <p> </p> <p><b>Overall, we would like the following higher-level function as well:</b></p> <ol> <li> <p><b>Construct a union (disjunction) over lattice points</b></p> </li> <li> <p><b>Construct an intersection (conjunction) over lattice points</b></p> </li> </ol> <p> </p> <p><b>A merge lattice is constructed per index iterator in a tensor operation. For example, say we have:<br> <br> Aij = (Bij + Cij) @ Dij</b></p> <p> </p> <p><b>To set index i for A, we have to iterate over B_i, C_i and D_i.</b></p> <p><b>To set index j for A, we have to iterate over B_*j, C_*j, and D_*j.</b></p> <p> </p> <p><b>Each index constitutes a merge lattice that we need to construct to then call Coiteration. We want to extract the operators “+” and “*” to determine addition and multiplication, where addition is converted to a disjunction and multiplication is converted to a disjunction.</b><br>  </p> <p><b>In the following example:</b></p> <p> </p> <p><b>A_i = b_i + c_i d_i </b></p> <p> </p> <p><b>Has a conjunction and disjunction. We proceed by:</b></p> <p> </p> <ol> <li> <p><b>Create leaf of the merge lattice from the tensor access rule(?</b></p> </li> <li> <p><b>Create merge lattice for c_i d_i by computing conjunctive merge (ciΛ di) a_i = this if this lattice point is reached</b></p> </li> <li> <p><b>Create merge lattice for b_i since there is no other conjunctive merge with it.</b></p> </li> <li> <p><b>Create upper-most merge latticepoint for disjunctive merge (bi) v (ciΛ di)</b></p> <ol> <li> <p><b>So the merge lattice points starts with a whole expression that is a disjunction among conjunctions</b></p> </li> <li> <p><b>Then it traverses through each lattice point, which trims down parts that are not necessary for co-iteration </b></p> </li> </ol> </li> </ol> <p> </p> <p><b>Questions:</b></p> <ol> <li> <p><b>What is the general input for a merge lattice? What is calling it? Will we have to implement, for example, an iteration graph?</b></p> </li> <li> <p><b>Do we implement the “LatticePoint”? </b></p> </li> <li> <p><b>How do we expect the “tensor operations” to be represented?</b></p> </li> </ol>adam2392@gmail.com (Adam2392)Fri, 16 Jun 2023 20:14:16 +0000https://blogs.python-gsoc.org/en/adam2392s-blog-copy-2/june-11-june-18-week-3/Week 2 - Hashed LevelCapabilities and Learning more Compile-time template metaprogramminghttps://blogs.python-gsoc.org/en/adam2392s-blog-copy-2/week-2-hashed-levelcapabilities-and-learning-more-compile-time-template-metaprogramming/<p>This week, I’ve finished the PR adding level properties as a public member to the level classes. </p> <p> </p> <p>However, now I am encountering difficulties in adding the ability for hash levels to be coiterated on. Currently, the `Coiterate` class implements a coiteration_helper class, which in turn currently relies on initializing iteration helpers from each level.</p> <p> </p> <p>```</p> <p>std::tuple&lt;typename Levels::LevelCapabilities::iteration_helper...&gt; m_iterHelpers;</p> <p>…</p> <p>, m_iterHelpers(std::apply([&amp;](auto&amp;... args)</p> <p>                                          { return std::tuple(args.iter_helper(i, pkm1)...); },</p> <p>                                          coiterate.m_levelsTuple))</p> <p>```</p> <p> </p> <p>For example, in the above code, we see that `m_iterHelpers` initalizes the `iter_helper` inside each level in `m_levelsTuple`. In addition, `m_iterHelpers` is a tuple of iteration_helpers, but hash levels do not contain this level capability. I need to implement a modification in the design, so that `m_iterHelpers` is only defined on the subset of levels that are ordered.</p> <p> </p> <p>However, it turns out that this is not an issue that requires tackling. Instead, I realized upon inspection and meeting with my mentor Hameer that all levels should have an `iteration_helper` defined through the `LevelCapabilities` namespace inside their class. The `hashed` level contains an `iteration_helper`, but not through the `LevelCapabilities` class, so the next step I realized was to refactor the existing implementation of the `hashed::iterator`, so that it was contained within the `LevelCapabilities` namespace. This is a bit complicated because the hashed level implements a custom iterator and there is some advanced template metaprogramming going on, which I have to figure out. Currently I’m running into two sets of errors that are confusing to me:</p> <p> </p> <p>```</p> <p>/Users/adam2392/Documents/xsparse/include/xsparse/util/base_traits.hpp:34:9: error: static_assert failed due to requirement 'std::is_convertible_v&lt;xsparse::util::container_traits&lt;std::vector, std::set, std::unordered_map&gt;, unsigned long&gt;' "`PK` must be convertible to uintptr_t."</p> <p>        static_assert(std::is_convertible_v&lt;PK, uintptr_t&gt;,</p> <p>        ^             ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~</p> <p>```</p> <p> </p> <p>And</p> <p> </p> <p>```</p> <p>/Users/adam2392/Documents/xsparse/test/source/hashed_test.cpp:31:34: error: no member named 'iter_helper' in 'xsparse::levels::hashed&lt;std::tuple&lt;&gt;, unsigned long, unsigned long, xsparse::util::container_traits&lt;std::vector, std::set, std::unordered_map&gt;, xsparse::level_properties&lt;false, false, false, false, false&gt;&gt;'</p> <p>    for (auto const [i2, p2] : h.iter_helper(ZERO))</p> <p>                               ~ ^</p> <p>```</p> <p> </p> <p>Future Work - Although this simple PR on improving the co-iteration algorithm has turned into quite a rabbit hole, the main bulk of the GSoC is dedicated to implementing and testing a “merge lattice” data structure, which will leverage the complete co-iteration algorithm.</p> <p> </p> <p>We briefly discussed merge lattices. Merge Lattices co-iterate over subset of levels that are necessary based on properties of the function. For example:</p> <p> </p> <p>E.g. A_ij + B_ik </p> <p> </p> <p>When iterating over i, both A and B are iterated. When iterating over j, only A is iterated. When iterating over k only B is iterated.</p> <p> </p> <p>Review:</p> <ul> <li> <p>Taco paper: on only dense/hashed</p> </li> <li> <p>Taco code: but note they implement this in runtime, rather than at compile time</p> <ul> <li> <p>And we need to re-write things in compile-time</p> </li> </ul> </li> </ul>adam2392@gmail.com (Adam2392)Thu, 08 Jun 2023 20:04:25 +0000https://blogs.python-gsoc.org/en/adam2392s-blog-copy-2/week-2-hashed-levelcapabilities-and-learning-more-compile-time-template-metaprogramming/Week 1 - Coiteration and exposing level properties PRhttps://blogs.python-gsoc.org/en/adam2392s-blog-copy-2/week-1-coiteration-and-exposing-level-properties-pr/<p>This week, I’ve focused on finishing the PR to add co-iteration of non-ordered levels as long as they are part of a conjunction with ordered levels. That involves some checking of the packed parameter `levels` and the function `f`.<br> <br> For instance: F(a, b, c) = a &amp; (b | c), that is F is a function that takes in boolean values representing a, b, c and then does some boolean expression on them. During initialization of the Coiterate, we would be able to know what levels are ordered during compile-time since these are properties of the levels. So say a and b are ordered, then we would just need to test "F(a, b, c)", which we would run the function F(false, false, true), which evaluates to false.</p> <p>Say c is unordered, then check F(false, false, true) so we would check F(false, false, false), so for all combinations of unordered levels, check True/False for those.</p> <p>For storing what levels are formatted: have a constexpr function with tuple of levels input that spits out a tuple of true/false indicating ordered/unordered elements in levels.</p> <p>For dereferencing: Have to modify the algorithm to only return the PKs for the ordered ones and then locate into all unordered levels.</p> <p>Along the way, I also started adding Doxygen style C++ docstrings to the relevant LOC I’m altering. To build locally, I needed to:</p> <ol> <li> <p>Install bison and link it:<br> brew install bison<br> brew upgrade bison<br> brew link bison –force<br>  </p> </li> <li> <p>Install doxygen following the instructions here: <a href="https://www.doxygen.nl/download.html">https://www.doxygen.nl/download.html</a><br>  </p> </li> <li> <p>Build documentation site locally:<br> cmake -S documentation -B build/doc<br> cmake --build build/doc --target GenerateDocs<br> # view the docs</p> </li> </ol> <p>open build/doc/doxygen/html/index.html</p> <p>This allows me to check the validity of my docstrings locally, rather than pushing to the PR branch and letting CI take awhile to do so.</p> <p>Another PR that arose was adding the ability for instances of level formats to access their level properties and querying whether or not the level is ordered, compact, unique, branchless and full. <a href="https://github.com/hameerabbasi/xsparse/pull/22">https://github.com/hameerabbasi/xsparse/pull/22</a> This is close to being merged and is waiting on review.</p> <p><br>  </p>adam2392@gmail.com (Adam2392)Fri, 02 Jun 2023 19:44:26 +0000https://blogs.python-gsoc.org/en/adam2392s-blog-copy-2/week-1-coiteration-and-exposing-level-properties-pr/Community Bonding and First Weekhttps://blogs.python-gsoc.org/en/adam2392s-blog-copy-2/community-bonding-and-first-week/<p><b>Summary and Notes:</b></p> <p>The first week, I took some notes on the end-goal of the XSparse package. At a higher level, XSparse will eventually integrate with a Python API in a lazy fashion. Similar to dask, we can envision an API such as:</p> <pre><code># this is just some tensor operation defined in memory, but X holds the function signature, rather than the values of X X = A + B @ Y / C - D @ E</code></pre> <p>Where each element in the above equation corresponds to a tensor that is properly shaped. We see that this is a composition of tensor operations and may be stored in a variety of different formats, so we only “evaluate” the expression when a user called “compute(X)”.</p> <pre><code># this would actually compute the values of X and store in memory X = sparse.compute(X)</code></pre> <p>This would be enabled by a runtime compilation e.g. using <a href="https://cppyy.readthedocs.io/en/latest/">cppyy</a>, which is a runtime package that automatically generates Python bindings from C++. Thus, we need efficient compiler-time C++ code that will generate efficient code for operating on any combinations of levels and tensor operations. Thus, the focus of this project is to implement such abstractions.</p> <p>The core part of this project is the idea of the merge lattice, which is essentially coiterating over multiple level formats. Merge lattices is a higher-level construct that begins placing meaning to the levels that are co-iterated. For example, given a tuple of tensors that are joined by different operators (e.g. +, *, /, -), we will have a single tensor output usually. Each input dimension of each tensor will get mapped to an output dimension that we care about. </p> <p>For example, say we have the following operation written in Einstein notation: </p> <blockquote> <p>output_ij = input1_ik input2_kj</p> <p>=&gt; figure out what loops to write in tensor notation</p> <p>= take Einstein summation as input</p> <p>-&gt; generate code base off of that</p> </blockquote> <p><b>Work:</b></p> <p>I’ve continued work on my PR <a href="https://github.com/hameerabbasi/xsparse/pull/19">https://github.com/hameerabbasi/xsparse/pull/19</a>, where I want to add co-iteration ability for levels with unordered format combined with ordered formats in a conjunctive merge (AND operation; analogous to tensor multiplication). The idea is that 0’s will not contribute to the result, so we only need to iterate over the ordered formats, and locate into the unordered formats. Once the iteration over the ordered formats is complete, then we are good because even if there are non-zeros left in the unordered formats, they are irrelevant.</p> <p> </p> <p><b>Clarifying Questions to Ask Mentors:</b></p> <ol> <li> <p>If we only get unordered formats, is this supported? I would say… no?</p> </li> <li> <p>If we get an arbitrary combination of ordered and unordered formats, that is fine as long as the unordered formats all have “locate” function right?</p> </li> </ol> <p> </p> <p><b>Work to do:</b></p> <ol> <li> <p>Continue reviewing the phd thesis of Fredrik Berg Kjølstad <a href="http://tensor-compiler.org/files/kjolstad-phd-thesis-taco-compiler.pdf">http://tensor-compiler.org/files/kjolstad-phd-thesis-taco-compiler.pdf</a></p> </li> <li> <p>Sketch out ideas for how to add co-iteration with a subset of level formats being of the form “locate”</p> <ol> <li> <p>The current co-iteration codebase co-iterates over all passed in levels, which is a special case of what we want.</p> </li> <li> <p>We need to store the indices of the levels that are ordered.</p> </li> <li> <p>We then want to co-iterate over the ordered level formats and at each (index, pointer), we want to locate into the unordered formats:</p> <ol> <li> <p>If any of the unordered formats returns NULLOPT (i.e. a value of 0), then note the result will not contain anything at this index. But continue iterating</p> </li> <li> <p>Once all ordered formats reach the end, then we have reached the end of the co-iteration.</p> </li> </ol> </li> </ol> </li> </ol> <p><br>  </p>adam2392@gmail.com (Adam2392)Fri, 26 May 2023 14:42:46 +0000https://blogs.python-gsoc.org/en/adam2392s-blog-copy-2/community-bonding-and-first-week/