artiq icon indicating copy to clipboard operation
artiq copied to clipboard

Returning list/array from kernel

Open AUTProgram opened this issue 5 years ago • 11 comments

Bug Report

One-Line Summary

Trying to return lists or numpy arrays created on the coredevice as return value of kernel functions gives error.

Steps to Reproduce

This throws an error:

    def run(self):
        a = self.krun()

    @kernel
    def krun(self):
        lst = [1.0]*100
        return lst

However, this works fine:

    def run(self):
        self.krun()
        print(self.arr)

    @kernel
    def krun(self):
        lst = [1.0]*100
        self.update_arr(lst)

    def update_arr(self, arr):
        self.arr = arr

Actual (undesired) Behavior

root:While compiling /home/ion/testing/bugs_mwe/array_from_kernel.py
/home/ion/testing/bugs_mwe/array_from_kernel.py:17:16-17:19: error: cannot return an allocated value that does not live forever
        return lst
               ^^^
/home/ion/testing/bugs_mwe/array_from_kernel.py:13:5: note: the value of the expression is alive from this point...
    @kernel
    ^      
/home/ion/testing/bugs_mwe/array_from_kernel.py:17:19: note: ... to this point
        return lst
                  ^
/home/ion/testing/bugs_mwe/array_from_kernel.py:17:16-17:19: note: this expression has type list(elt=float)
        return lst
               ^^^

Your System

Linux machine ARTIQ v5.0.dev+452.gf8846a33

I guess this might be related to #224, except here it is a list that should be returned. Similar thing happens when lst is created via np.array or np.full. I am not sure whether this is a bug, a limitation or a feature, but since the documentation is lacking with regards to this, I thought I should open a issue here.

AUTProgram avatar Mar 28 '19 11:03 AUTProgram

It's a limitation due to the way the memory is managed for kernels on the core device (using the stack only).

sbourdeauducq avatar Mar 29 '19 02:03 sbourdeauducq

In theory, we could support this for the top-level kernel function by adding a special case for the return value, but I'm not sure it's worth it – attribute writeback and/or a "save result" RPC call usually avoids the issue.

dnadlinger avatar Mar 31 '19 18:03 dnadlinger

I think just about everyone who tries ARTIQ out for the first time runs into this one. I agree that it's probably not worth trying to change ARTIQ, but it would definitely be helpful to have explicit documentation of this behavior, and some code snippets indicating how one can achieve the desired functionality.

dhslichter avatar Apr 05 '19 01:04 dhslichter

I have not come across a use case that could not be solved in other ways so far, but just wanted to flag this up to see whether there are plans to change this (not sure it is worth it). It is certainly unexpected, in particular since the documentation says that lists of supported types can be used in kernel functions (but does not mention that this does not apply to return values). Writing it back as an attribute works for most cases I guess. However, if I am implementing a scan where the array is repeatedly filled with data from the core device, set as the attribute and then processed further in a lengthy rpc function with the "async" flag, there might be issues using this method where the array is modified by the kernel before the analysis for that point via the rpc function has been carried out. For this case, I have instead been passing an array into the kernel function that fills it with the desired data and then passed the filled/modified array explicitly to the analysing asynchronous rpc call. So I agree, code snippets with the recommended ways to achieve certain functionality would be very useful.

AUTProgram avatar Apr 05 '19 15:04 AUTProgram

However, if I am implementing a scan where the array is repeatedly filled with data from the core device, set as the attribute and then processed further in a lengthy rpc function with the "async" flag, there might be issues using this method where the array is modified by the kernel before the analysis for that point via the rpc function has been carried out.

You would just pass the array to the async function, at which point it would be copied.

Ack on the usefulness of a "common ARTIQ Python idioms" list.

dnadlinger avatar Apr 05 '19 17:04 dnadlinger

I have not come across a use case that could not be solved in other ways so far, but just wanted to flag this up to see whether there are plans to change this (not sure it is worth it).

It would be very hard to change this and keep ARTIQ Python's realtime guarantees.

whitequark avatar Apr 05 '19 22:04 whitequark

It would be very hard to change this and keep ARTIQ Python's realtime guarantees.

Which guarantees are you thinking about here? Execution timing is already unpredictable due to caches/RAM latency.

dnadlinger avatar Apr 06 '19 00:04 dnadlinger

@dnadlinger Cache and RAM latency is bounded, but there's no simple allocator that is useful for Python and has bounded latency.

You could use a simple cell-based allocator, like hbaker's, which offers bounded latency for allocation and garbage collection, but it doesn't let you use arrays. Any other allocator and garbage collector that has even soft-realtime guarantees is going to be incredibly complex, and probably unrealistic to integrate in ARTIQ.

Of course you could use an allocator that bins allocations by size to avoid fragmentation and uses a full-blown ownership system to deterministically free allocations and avoid the need for a GC, but at that point why not just use Rust?

whitequark avatar Apr 06 '19 01:04 whitequark

Execution timing is already unpredictable due to caches/RAM latency.

The solution to this problem is a large SRAM, but that's not cheap.

sbourdeauducq avatar Apr 06 '19 02:04 sbourdeauducq

I am having this problem. I want to return a list from a @kernel function that will be used by another @kernel function. How do I perform an attribute writeback or a save result RPC?

jonhood11 avatar Jul 29 '21 19:07 jonhood11

I am having this problem. I want to return a list from a @kernel function that will be used by another @kernel function. How do I perform an attribute writeback or a save result RPC?

My suggestion was in particular for returning from the top-level kernel. There, you can just do something like:

@kernel
def run(self):
  array = [np.int32(0)] * 64
  # Do stuff.
  self.save_array(data)

def save_array(self, data):
  # Do something with the data.
  print(data)

Attribute writeback refers to the case where you modify an attribute of the host object, like

def build(self):
  self.data = [0] * 64

def run(self):
  self.run_kernel()
  # This will now be updated if the kernel has exited normally.
  print(self.data)

@kernel
def run_kernel(self):
  self.data[1] = 42

If you want to return from a kernel function to another kernel function, that's basically not possible right now, as there is nowhere for the memory allocation to go. You might be able to make the caller function allocate the array and pass it to the callee as an argument; the latter can then just fill in the data. I suggest asking on the forums for more details.

dnadlinger avatar Jul 29 '21 19:07 dnadlinger