Shuvendu Lahir: Hi. So thanks for coming. So it's my great pleasure to have Zvonimir Rakamaric visiting from Utah. And with the student travel who's in the audience. And Zvonimir is well known to us. He's visited us as an intern two times and he did work that is still being done both here and [indiscernible]. And he does SMACK too, which is pretty popular to compile [indiscernible]. So a lot of people are using his tools. And today he's going to talk about some new directions that has been pursued. So welcome. Zvonimir Rakamaric: Thanks a lot Shuvendu. It's certainly nice to be back. So we can keep this informal. Clearly there is a lot of people, so feel free to stop me at any point. We can discuss various things and so on. So yeah, today I'll talk about this ongoing work, which is cooperation with also Shuvendu, which is on using SMT-based verification for reasoning about approximations and programs. And before I start, this is my wonderful group of students. We kind of recreated the one of the photos that you maybe saw before. And if you want to find out more about other things that we're working on, you can go to this web address. And again, apart from the work that I'm talking about here, we do lots of other stuff related to software verification and so on. We do analysis of [indiscernible]. We are working on the SMACK tool that Shuvendu mentioned that translates LMIR into Boogie to flow. We start working on security and safety of kernel modules and protocol stacks; we still work on analysis of concurrent programs, such as Pthreads, OpenMP and GPU, and so on. Kind of doubled a little bit in Android malware detection. And also in automatic testing of Java and the NASA software. So if you're interested if any of these, I'm still here for two more days, so just let me know and we can talk about it in more details. So the work I'm going to cover in this talk is on approximate computing and it's done with a bunch of collaborators. Shaobo is here, Shuvendu as well. And then part of this work is done with [indiscernible] Wei-Fan and [indiscernible] Ganesh Gopalakrishnan. A brief roadmap about this talk. A motivator on proximate computing first with a couple of slides and then I'll introduce our approach and also why we think it's a good approach. And then I'll talk about in much more detail about how we verify safety and accuracy of approximate program, as well as [indiscernible] determination. And we'll see how much time I have in the end. I do have this separate work on synthesis of floating-point approximations. And like I said, we'll see if I get to it. And a disclaimer, this is a work in progress. So some of this may not be completely flushed out, and experiments are a little bit preliminary. So be free to ask any questions. And if you have feedback, even better, that would also be great. Okay. Maybe won't spend too much time on the introduction. We don't have too much people in the audience. So yeah, this is a pretty well-known graph that illustrates how hardware we're scaling starting from 70s to 2010. And clearly in the first few decades, this is the log scale, so everything was kind of growing exponentially, which really spurred this computing evolution that was happening around the time. And Microsoft certainly profited from this group, and other companies as well. And then around, you know, 2004, '05, certain things started slowing down, in particular clock speed, that we were, upon which improvements we were relying on before. And that's when companies such as Intel decided to switch to multi-core processers. And today, you know, this free lunch that we've been all enjoying for many years, it's pretty much over. So what broke around 2004 was in our scaling, which was saying that as transistors get smaller, their power density does not change. So you can keep increasing frequency. And then like I said, that's when hardware companies decided to switch to multi-core, which is, you know, it kind of worked. But it is a partial solution, because you have to then write parallel programs and so on, which is not always easy as, you know, just spitting out new chips, sequential execution on your chips. And then Moore's law, which talks about doubling of transistors that hardware manufactures can put on chips, it seems that it's slowing down as well. So there is some recently news that Intel is pointing their animater chip-making process. And on top of that, you can't really power up all the transistors that you have on chips these days. So there's this constant of dark silicon that talks about which transistors can be powered up and which not and so on. So we have to be careful about that as well. So this is kind of the way of the land of what is happening in hardware these days. Now if we look at applications that are really popular these days, we can see that lots of them are, I said, fuzzy. Because they thought maybe to take this fuzzy or we don't need really totally precise answers and so on. And there is many applications that fall into this category arranging from machine learning and big-data analytics to cyber-physical systems, graphics, and so on, games, sensors in mobile devices. So these are all applications that we potentially don't need completely correct results because the deal with fuzziness of the world around us anyways. And, you know, this slow down of advances in hardware combined with this fuzziness of applications that we want to run on this hardware has led to emergence of approximate computing. And the main idea about it behind it is to embrace this fuzziness by introducing controlled trade-off of accuracy to improve performance or energy efficiency. So instead of performing completely correct computations, maybe relax them a little bit, which can cause the [indiscernible] improved performance or energy efficiency. But they're still happy with the results. So that's the main kind of idea behind approximate computing in my opinion. And it's been an up-and-coming area in the last couple of years; there's been a bunch of papers in this area. And two years ago, a year and a half ago, we also started working on some of the problems in this area, which is quite interesting. So there is a bunch of ways that approximations can be introduced. At least in some of them there is more than this. Just to give you some intuition of what these approximations are. So according to mine, for example, is working on approximating memories so they're playing with tuning memory controllers to be able to lower refresh rate in DRAMs. So that way you preserve energy, but you might get more bit flips in the [indiscernible] that you read and so on. So there is a trade-off there. Then there were papers on relaxing loop computations by doing loop perforations, which means you just keep every few iterations of the loop. Recently there has been work on neural processing units where you train your neural metric to replace a part of your problem and so on. So those are kind of approximations that people have been working on. And then motivation for behind our work is that introducing these approximations has to be done in a controlled way ideally. Because there is this fine line between overly aggressively approximating code, which can lead to then really bad results in the output or being very careful in doing just very conservative approximations, and then you don't really benefit a lot from them. And to facilitate this, I think the programmers need automated techniques and tools to explore different approximate computing trade-offs so that they can play with these different approximations and see what is good enough for their purpose. So in the literature, people cannot split up this criteria based on which you judge whether approximation is good or not into a couple of different categories. So first one is, and the most basic one, is safety. So safety just says that approximations would hopefully not introduce any catastrophic failures. And I'll point to the references or things like that. The second one, and that one hasn't actually been mentioned a lot in the later work, is termination. So ideally the approximations that they're getting introduced should not cause non-termination. And finally we have quality results. And this is again typically split into two subcategories. The first one is accuracy, which talks about difference between precise and approximate outputs. For example, an approximate output is at most five away from your precise output. And the second one is reliability, which talks about probability that approximate output is precise. Some example would be my approximate output is correct 90 percent of the time. And in the other cases it might not be correct. And so far our work has focused on these three criteria. So on safety, termination, and accuracy. We still haven't really touched reliability. That's potential future work, future work area. Okay. So in our goal, like I said, is to enable this rigorous exploration of approximate computing tradeoffs. And we want to do this using automated formal techniques for reasoning about approximations. And we decided to do that after we studied a little of the literature. We know that current techniques that people use often lack in either rigor, because what often happens is that to assess approximations, other researchers just do relatively simple dynamic analysis of sampling. So there is no really any kind of rigorous proofs that the approximation is really good enough. Or some lack precision, where something like a type system can limit what you can really express and what you can reason about when it comes to approximations. And finally recently there were several techniques that improved precision and so on, but they suffer from not really that much automated, because they heavily relied on interactive theorem provers, and in particular it's Coq. And we want to apply automated differential program verification for reasoning about these approximations. And the idea is to compare original and approximate program. While at the same time we encode the relaxed specifications as differential assertions. So this property I was talking about, safety, accuracy, and so on, can be accorded as assertions that talk about precise and approximate programs. And I'm sure you've seen talks about differential program verification before from Shuvendu. It seems similar to checking for hardware. But it goes beyond the clear lines, which is why it's nice for approximate computing where what you're trying to prove is not [indiscernible]. And our hope is that we will achieve precision and automation using SMT-based checking and invariant inference. We know SMT well, as well as invariant inference. So we hope that this work will improve up on something like using Coq. So this was brief motivation. What I wanted to talk about next is how we verify safety and accuracy criteria. And we do it using differential verification, which is based on mutual summaries that relate pre- and post-states of two-procedure versions. And Shuvendu published algorithms that can check these mutual summaries modularly by constructing this product program. And by doing that you can use any off-the-shelf program verifier or inference engine, which is really nice, so we can leverage Boogie and Houdini and Z3 and apply all of that technology on to check this criteria that I listed for various approximations that people care about. I don't know if I should really talk about mutual summaries and things like that. How many people have seen these talks before? Okay. >>: [Indiscernible]. Zvonimir Rakamaric: Okay. So let's try to briefly go through it. So mutual summaries is a general form of writing differential specification. So if you have two versions of a procedure, then you can write the mutual summary which relates inputs to outputs of both of these procedures. So this mutual summary is saying if Xs are the same and these global variable G in one version is equal to G in the other version and X1 is equal to 0, then the global of the output is 1 is less than equal to the other one. So it connects these two procedures, and it specifies, it talks about a specification of these two procedures. And, you know, it includes parameters of both of them and globals on the input and output and also return value. So these mutual summaries are used in the algorithm called differential assertion checking, called DAC, which enables module reasoning and invariant inference. And the steps of DAC are to convert the differential verification problem into a single program verification problem. So you have these two program versions and you smash them together into a single program. And you have to do it in a very strict and prescribed way. And once you have this product program, then you can leverage any single program verification technique to infer invariants. And once you infer the variants, you can just verify this combined program using anything you have that can check single programs. So now I'm certainly not going into this. But you can find it in Shuvendu's paper. So this is the construction of the product program. So you have two program versions that are taken as input. In this case they're just two procedures, f1 and f2. And which invoke h1 and h2. And this is the product procedure for these two procedures. So there are a couple of things to note. It's one procedure that captures the facts of both of these. It performs these calls to h1 and h2. Because this body of this procedures are being in-lined here. And another important thing is that in the end it also calls product procedure for h1 and h2. So this is kind of the key point, because now you can stick in those mutual summaries at these call places and you can actually prove them and so on. Without this you can't really do that and then you cannot infer pre- and post-conditions. So you cannot basically perform mutual summaries. So yeah, key part of the translation is I think this, and there is kind of a lot of bookkeeping around it, which I'm not going to go into. But this is how the product construction works. And then once you have that, you know, you can run something like Houdini for a change to infer mutual summaries. Okay. And ergo, this is [indiscernible] to do. We can do invariant inference. It exploits structural similarities between two programs by just restricting the kinds of variant we can generate to simple differential predicates. And the differential predicates that it means that it talk about variable versions from the two programs. So they're in the form of X and then some operator X prime where X is from one program version and X prime from the other one. And this operator can be simple things such as a [indiscernible] and so on. So these are the predicates or predicate templates. And then we use Houdini to infer Boolean combination of these predicates. And this happens completely automatically once these predicates are provided, or predicate templates are provided. And I should also note that SymDiff automatically generates a bunch of these. So it don't even, for many examples it don't even have to go in and provide any kind of templates, but if you need something more complicated, you're able to help it out a little bit. Okay. Yeah, the key benefit of this is that you can reuse the infrastructure. So once you create the product program, this box here is what you already have. So this is, for example, Boogie and Houdini. Which is really nice, you don't have to develop really anything special for discharging these product programs. And we leverage this a lot in our work. So the implementation of our tool that discharges this approximate programs and their differential properties is based on this encoding. We did have to extend it with automatic inference of richer invariants in the sense that users can specify additional predicates, and we can infer Boolean combination over predicates and not just conjunction. So that is some of our examples. It gets a little more complicated than what SymDiff has seen before. Okay. So this is tool flow. There's an original program and an approximate program that is fed into SymDiff. Potentially we need a few manual predicate templates. From that the product will be generated, and then Boogie and Houdini are run on it, and hopefully it can be discharged using that. So like I said, we started out verifying two criteria. One is safety. And in our particular case what we checked is control flow equivalence. Which basically means that for the same input, both programs execute the same sequence of basic blocks. And this criteria is actually quite important when introducing new approximations, because you often don't want to break this guarantee. Typically it means that weird things are happening if your two versions are not control flow equivalent. And this is mentioned in other related work papers as an important thing to work on. And we also have a couple benchmarks to check accuracy. And in this case this accuracy criteria came from related work that used the same set of benchmarks. And one example would be that output computed by approximate procedure is within some prescribed distance from the original procedure output. Okay. A little about control flow equivalence. So we came up with this, I think, pretty, pretty clear way of encoding control flow equivalence using uninterpreted functions. So we have this global variable called summary, and in every block we updated with the new value, which is just F of summary and block ID, where F is some uninterpreted function. And for every block we generate you need block IDs. And by doing this, in the end we just have to compare the summary variables in the two versions. And that actually works really well, and I think it's a quite nice encoding of controlled flow equivalence. I haven't seen it before. It's pretty simple, but pretty nice. There is an old [indiscernible] with actually storing things in an array. You know every block was stored in unbounded array, which was kind of pretty dumb and not scalable. So this was a nice kind of object to that. >>: So when you look at the invariance and you're just saying things like summary on the left equals summary on the right or something like that. So you don't have to worry about the functions [indiscernible]. Zvonimir Rakamaric: Yes. >>: [Indiscernible]. Zvonimir Rakamaric: What? >>: You said approximation [indiscernible]. Zvonimir Rakamaric: Yeah, so let me show you, that will come up soon. Yeah, I didn't put it in this example, but they'll come up soon. Yeah. Good question though. Here is a good example. And next slide we have [indiscernible]. So we have a couple example where we check control flow equivalence. This is one of them. So the main thing to focus on is this part. So this is just a recursive procedure that iterates over this array, global area, and it searches for a character that is equal to X and then if it is then it replace it with Y. So it's basically replacing X with Y in this array. And it terminates when either N is reached or array is set to minus 1. So it's your basic replace character procedure. We do have to turn everything into tailored cursive procedures. So you would usually write this using a simple loop, but here we encoded it using recursion. So that's the original version. And approximation basically scrambles whatever was ready from this array. So this is the havoc that introduced approximation. So we just whatever is read in from an array we completely scramble it. So we have two versions now, one without havoc and one with havoc, and we want to prove that controlled flow equivalence, that they're controlled flow equivalent. So that's our goal. >>: That's an interesting [indiscernible], right? I mean you have the [indiscernible] there and you can consider control flow [indiscernible]. Zvonimir Rakamaric: That is a very good question. No. >>: [Indiscernible]. Zvonimir Rakamaric: But very good question. So, yeah, no, we don't [indiscernible]. We do count this. Yeah, good question. And so yeah, what is kind of interesting about this example, it's kind of not trivial to do it, it's not easy to do it using just simple tent analogies. So if you notice here, you know, this scrambled variable is flowing into this array, which then flows back into this F condition. So we tried simple tent analogies on this and it will just say everything is tented. And this happens because it can't really reason about precisely every segment. You can imagine extending some kind of additional abstract domain that would kind of keep track of actual array segments and so on, but it gets really tricky. And using SymDiff and so on, we can actually do things like this. >>: Can you mention what this analogous [indiscernible]? Zvonimir Rakamaric: So, yeah. We use this Boogie program. So I sometimes forget to relay this to real world. That's what happens when you work in program metrics. So it's trying to mimic reading from approximate memory. So every stored approximate memory you read from it and you can get anything essentially. So that's one kind of real example. That's probably our smallest one. So we verify this control flow equivalence with this example using SymDiff [indiscernible] I showed you. And verification effort is almost completely automatic. We have to just manually provide one relatively simple predicate template, which is actually talking about these array fragments. You kind of have to know whether you're ahead of I or not. So that's the predicate we needed. So that's example where we check the safety criteria. Switching to accuracy, we took a few examples from this PLDI 2012 paper that was proving this acceptability properties for approximate programs using Coq. And these are pretty small examples. I guess when you're using Coq you can't really, well okay. So here's one other examples. It's highly abstracted search engine called Switch, and this procedure is supposed to print out on the side how many search results should be printed on the screen. So it takes this maxR and N, and numR is the number of search results. So it has this loop where it's either bonded by maxR or N. And then just keeps [indiscernible] and then it returns numR. So very, very simple and abstracted abstract version of something that you could maybe see in a search engine. And then here is the approximation. So the red lines are what uses this approximation. It's a little bit more involved and interesting than what we're heading for. So we are not just completely scrambling this maxR variable, but rather we do this in a more controlled way. So what this is saying is actually that if maxR is less than 10, then don't change it. And if it's greater than 10, then it can be anything as long as it's still greater than 10. And the idea behind this approximation is that the server can kind of throttle how many things, how many searches it's presenting to the user. So if the load becomes really high, then it can, instead of presenting 100 results, it can present 20 results. And you always have to present a list then. So that's the idea behind this approximation. And it was taken from that paper. So we have to also specify what we actually want to check. And this is an example of this bit distance kind of criteria where we are saying the input are the same, then the output should be related by the same relaxedEq function. Okay. So a little bit other verification effort again. So we managed [indiscernible] this by providing manually 4 [indiscernible] simple predicates. Carbin et al had this proofing Coq that had around 330 lines of proof script. So, you know, the examples are pretty small. But I think we really are kind of improving up on this. And the surprising thing is, also, and I discuss this with Mike. I mean we are proving exactly the same properties. So it's not that we changed anything or removed anything. Exactly the same example, exactly the same property. And we can do it pretty automated free. >>: [Indiscernible] one of the tricky things that Mike had to deal with was re-convergent control. [Indiscernible] cases where you do control flow is [indiscernible], actually you come back to the same place and you want to say something about the values that exist in both programs at the same time. [indiscernible]. Zvonimir Rakamaric: So I haven't seen the examples or actually have that. Like maybe it's a different paper. The examples in this paper are all kind of following this paper, and it's not control flow. >>: If they will build the proof necessary for it but haven't found any example to exercise it. Zvonimir Rakamaric: I will think about this. It's not, so you're saying he won't allow the divergence in a certain part and then it converges back? >>: [Indiscernible] reasonable thing to say like X1 and X2. It might be the product of a totally different control. But now you're at the same place and the same variables are available. I see. Zvonimir Rakamaric: I mean we can play certainly with where you want to track it and where you don't want to track it by putting these updates to summary or not. So I'm not, maybe. We can think about it. That's a good question. So yeah, we do have a little bit more extensive relation than just these two examples. And we have a couple more control flow equivalence. Selection sort is pretty interesting, because it has some of the similar character, replace character. There is this [indiscernible] array that don't really influence control flow. And the key to be able to handle these examples is that you need to precisely capture these array fragments. So we're kind of focusing on examples where doing just simple tent analysis is just not good enough. And we handled other examples from this Carbin's paper, there's three of them. >>: [Indiscernible]. Zvonimir Rakamaric: It was always just held. [Indiscernible] >>: [Indiscernible]. Zvonimir Rakamaric: Yes. >>: [Indiscernible]. Zvonimir Rakamaric: So I remember, I probably, I actually remember selection sort. So selection sort kind of, oh, no in selection sort it actually right. It's right. It kind of has this index going from end of the array towards the beginning of the array. And control flow only depends on the, and everything after the index is sorted. >>: Yeah but you have this condition somewhere that says if x plus Y then [indiscernible]. Zvonimir Rakamaric: Yes, but this condition only talks about the elements that are before I. You go from 0 to I and then you, that's why you're applying this condition. Once you are behind I, that doesn't influence control flow anymore. So basically when you are writing your largest element at I, you can actually write anything you want. You can write completely scrambled value, so I will move and then your control gets preserved. >>: [Indiscernible]. Zvonimir Rakamaric: I can't remember how Bubble Sort. I think it was sort of like the similar behavior. It depends how you implement it. I think we just have two loops. >>: [Indiscernible]. >>: It seems like in Bubble Sort if you're just testing and swapping, right, and if that's [indiscernible], then you have a different control flow pattern and the value of variance. >>: I guess Bubble Sort you always have that loop, you have the nested loop, and then [indiscernible] depends on the value, but the control flow is the same it's always going to be exactly. Zvonimir Rakamaric: But we can't swap it actually. I mean it is control flow. It is. Because it is less than swap or not swap. So this kind of is control flow. Do you know [indiscernible] maybe? >>: So you're not always interested in fully preserving control flow. [Indiscernible] I don't care which way. So these are somehow to specify at what level of granularity. Zvonimir Rakamaric: So [indiscernible] but I think before so we can actually, given our coding we can pretty easily kind of based on some kind of user input either imitate the blocks with those other summaries or we don't annotate them. If you don't annotate them then those are just analyzed. So you can just put these control flow tracking updates to key points in the program. And everything else will still work. >>: [Indiscernible]. Zvonimir Rakamaric: Yeah, so we can do something like that. It's approximate and flows into the flow [indiscernible]. And here are some experiment results. So I managed to verify all of these. And this is the actual verification. Our initial effort was actually on rolling, which is kind of much easier, but we were not happy with that. So here we actually generate invariance and perform like full-blown verification. Okay. So let's talk a little bit about relative termination. And this is one I'm particularly excited about. And we only started working on this maybe two, three months ago. But, yeah, we're getting some direction and I think it's very interesting direction. So this termination criteria, people don't really talk about it that much in the approximate computing community. Although if we talk to somebody [indiscernible], people think that it is important. And so the way we specify it is that two programs satisfy this relative termination criterion. If a precise execution terminates for a given input, then it's approximate version should terminate as well. So it's not absolute termination. If your precise version does not terminate, then you don't care what your approximate version does. And there are some advantages to doing things this way. First of all, we avoid proving absolute termination. I believe that proving absolute termination is typically easier, because the kind of invariance you need, and I have one example, they're actually not that involved often. And we know that coming up with good ranking functions can be quite hard if you want to prove absolute termination in a single program. And I also do think that it is more meaningful in the context of these program approximations, because typically you want a reason for two program versions and you want to see how your approximation influences the original version. So I think it's probably a more meaningful thing to check when it comes to approximations and so on. >>: [Indiscernible] Zvonimir Rakamaric: No, I should have said that. I'm missing, that's a great question. So everything that I talked about so far assumes that they terminate. So this is actually, another advantage that I should adhere, another advantage I should adhere is it actually assures that whatever you prove in the previous step really holds, if you can prove absolute termination. >>: If you test that that summary is right on termination, [indiscernible]. Zvonimir Rakamaric: If you go somewhere and you don't even get to the actual assertions and so on, so you don't prove. This was very good. I should have had this sentence in the slide. So yeah, another great advantage of this. >>: Has your mother-in-law seen this slide? Zvonimir Rakamaric: My mother-in-law? No. >>: I'm sorry, the first, I mean relative safety and relative termination. >>: I'm sorry [indiscernible]. [laughter] Zvonimir Rakamaric: [Indiscernible]. >>: [Indiscernible]. Zvonimir Rakamaric: I love my mother-in-law. And she's visiting my wife right now actually. Which is also great so I get a chance to travel more. [indiscernible] >>: I can take the blame. So neutral termination [indiscernible] Zvonimir Rakamaric: [Indiscernible]. >>: [Indiscernible]. Zvonimir Rakamaric: [Indiscernible]. [laughter] Yeah, so the construction for proving this termination of reiteration is kind of somewhat similar to product program construction that I already talked about. Instead of having mutual summaries, you have relative termination condition that enables you to prove a relative termination and then you also have to check. So this is like pre-condition. So this checking is based on product program coding that was published in a CADE 2012, '11, with [indiscernible] and a bunch of people published it. And it's a pretty neat encoding for checking this relative determination. So you have to introduce these uninterpreted summary relations that capture input-output behavior functions. And then you assume that nested calls that are being made, you know, recursive calls or nested calls, you assume that they terminate. And you can do that using axioms that refer to the relative termination conditions I talked about in the previous slide and these summary relations. And then you speak of assertions that callees also terminate. And this assertion relies on this assumption. So let's just see a little bit how the construction looks like. So this is a very simple example, simple [indiscernible], increases X by 1. This is approximation. It's kind of what [indiscernible]. Instead of increasing by 1, you increase by 2. You skip every other step. So I want to prove that this approximate version relatively terminates as compared to the original version. And the first thing you have to do is extract loops [indiscernible] procedures. So this is the main kind of part and here is the recursive call. So nothing special really happening there. And we do have to, so these are the summary relations. So they're being assumed at these two places. So this one belongs to loop, that one belongs to main. And the main thing we want to prove is estimation of loop. Okay. So here is how the construction works. So this is your product procedure where you inline the body of your first function and you inline the first procedure and the body of your second procedure. >>: You mean X1 plus 2, right? Zvonimir Rakamaric: No, X2 plus 2. >>: Plus 2. Zvonimir Rakamaric: Yeah, sorry, so this is a typo. So this should be the body of the second procedure, so it should be X2 plus 2, X2 plus 2. And you have to assume your relative termination condition. And in this case it's that X1 has to be lesser or equal to X2. Write [indiscernible] so if X2 is less than, it could happen that the first program exits right away, but the second one has to iterate. And this is this axiom that I was talking about. So this axiom is saying that if relative termination condition holds and loop 1 terminated, then there should exist r2 such that loop r2 closer terminates. And this is encoded using this summary relations. >>: [Indiscernible]. Zvonimir Rakamaric: Yeah, because this is assumption. And you check here. So there is, I guess, induction happening here. And it took me a while to actually understand why this actually works. But, yeah, so this is saying your nested calls are terminating and then you want to prove that at the current level you are terminating as well. Does that make sense? >>: So [indiscernible]? Zvonimir Rakamaric: So this assertion here proves that you're terminating at this level. So this is important. Any other questions? >>: [Indiscernible] means that it possibly terminates on input X with output X and output on r1 over here? Zvonimir Rakamaric: Right. >>: So you're proving there is some execution from X1. Zvonimir Rakamaric: So yeah. >>: So does that mean also that loopR2 could be also possibly non-terminating? Zvonimir Rakamaric: If loop R1 terminates, then loop R2 that's terminate as well. >>: At least if you execute [indiscernible]. >>: So if loop R1 is possibly terminating, then loop R2 is possibly terminating. [indiscernible] Zvonimir Rakamaric: Oh, I see. Okay. >>: I just wanted to understand this. >>: [Indiscernible] R1 is deterministic. Zvonimir Rakamaric: Anyhow, [indiscernible] there is a lot of multipliers though which can get tricky. Okay. So ->>: The only one that's kicking there is the assertion. Zvonimir Rakamaric: Yeah, okay. Sure. >>: [Indiscernible]. Zvonimir Rakamaric: We can prove many of some examples but then some other ones gets a little bit harder. And I worked with [indiscernible] a long time ago and I never wanted to work with him again. Okay. So yeah, so now we have this kind of dual holdings, right. We have the big one where you are actually in walking these combined procedures and where you can incur invariance. And we have this CADE encoding we just showed that we can use to prove termination. But it's not really clear how to do invariant inference here because this product procedures are actually not being invoked anywhere. Instead we have these axioms. So to be able to do automated verification in this context then we came up with a solution which iterates between these two encodings. So we generate invariance using DAC encoding and then we transfer them into CADE encoding, and we check for termination, and then we can kind of iterate this. So look at it two-fold. Very similar except now we generate two product programs by using both encodings and then we pushing invariance from one into the other and then try to prove termination. So [indiscernible] implemented this part. And we've been playing with this a little bit. We have a preliminary relation. I think we have two or three examples. So this perforation that I showed and I replaced character. Same example, we just proved termination. And it's pretty automated. Again, we only need one or two manually provided predicate templates. Quantifiers are kind of tricky. And we do see non-termination and things like that. And we've been working on this in the next few days. So that's termination. I mean I like it in the approximate kind of computing community there hasn't really been a lot of effort on doing anything like this. So I think it's pretty new and we'll see how far we can push it. And I'll skip floating points in the interest of time. We have some basic work on synthesizing floating point routines with lower bit of floating preparations and [indiscernible] and so on. Which I think it's quite promising. You can say [indiscernible] and date bend it and things like that. And we do it in a rigorous way using our tool that we have for reasoning about array of floating computations and so on. And like I said, I can talk about it offline. And yeah, about this work. So we show that we can apply this automated differential program verification as a SymDiff for reasoning about approximations. We also showed that these mutual summaries that I talked about can quite naturally express several types of relaxed specifications that are useful for proving in this context. And we show that we can improve precision and automation. Our previous approach is because using SMT checking and invariant inference, which is almost completely automatic. And by combining all of this, we proved that it is feasible to apply all this verification techniques that we are all familiar with and that we all love, or most of us do at least, to the domain of approximate computing. So I think I ran out of time. So I'll stop here. And like I said I can talk about floating points offline if anybody's interested. Thanks a lot guys. [Applause]. Any other questions? >>: Just need some more examples. Zvonimir Rakamaric: [Indiscernible] and the first point is of more interest. Yeah, benchmarks are quite challenging. We did get some from literature and [indiscernible] has been helping us a lot to try to find more benchmarks. So hopefully in the next week or two our benchmark set will be much larger. And then we can always try to improve predicate degeneration further and so on, count the individual stuff. Anything else? >>: [Indiscernible]. Zvonimir Rakamaric: Thanks a lot for coming. [Applause]