Closed andrea-pasquale closed 1 week ago
It's on my todo list #535. But, I will prioritize doing things with the chip before it goes away.
@Jacfomg @stavros11 could you please port this feature also in QM and Zurich?
Yes, I am also planning to do it.
Perfect, thank you!
@stavros11 @Jacfomg @andrea-pasquale
I think this issue should wait until qibolab 0.2
is ready. In 0.2
we are aiming for a structure of qibolab
with clearly defined layers (e.g. see https://github.com/qiboteam/qibolab/pull/792). The layer that is instrument specific shall not know anything about how to resolve sweeps. This resolution shall be done in the higher level before requesting execution from instruments. This means that sweep resolution will happen in one centralized place and should not be implemented inside each device specific code.
I fully agree with the first part.
However, how to implement the sweep (how to transfer the parameters, and possibly further details about the sweeper implementation) will of course be instrument specific. But the most we can implement in the common layer, the better it is.
If there is enough consensus, change the assigned milestone.
Fine also from my side, the motivation to open the issue was to have a common interface in Qibocal
, given that we already making use of SweeperType
. If things change in qibolab 0.2
I am perfectly fine.
Since everyone agrees, I moved this to the 0.2.0. And I also agree with lifting as much code as possible to the common layer.
Actually, is it convenient for the devices to use the SweeperType
? I.e. can they make use of it to save device memory or message size? @stavros11 @hay-k @rodolfocarobene
The alternative would be to just use SweeperType.ABSOLUTE
(and leave the user to do the sum/product of the array on their side).
The main reason why sweepertype different from absolute exist is because of multiple qubits calibration. I can do a spectroscopy with the same sweeper on different qubits if it is a sweepertype.offset, not if it is absolute (not as easily at least).
This was already done before, but implicitly.
From the QICK point of view, there is no difference in memory usage or anything else. Actually what I implement is always an "absolute"-like sweeper
The main reason why sweepertype different from absolute exist is because of multiple qubits calibration. I can do a spectroscopy with the same sweeper on different qubits if it is a sweepertype.offset, not if it is absolute (not as easily at least).
Ok, but then this doesn't have to be a driver-supported feature, but it could be even a Qibocal feature. I.e. Qibocal can translate a single offset-specified sweeper in different absolute sweepers for Qibolab. Isn't it?
Actually, is it convenient for the devices to use the
SweeperType
? I.e. can they make use of it to save device memory or message size?
To answer this, SweeperType
may actually be convenient for QM and could be used to save both device memory and communication size.
Following @rodolfocarobene comment, the main use of SweeperType
was making parallel sweeps on different qubits. For example, using SweeperType.OFFSET
you can sweep the frequency on multiple qubits using the same range but centered around a different value for each qubit. Since 0.2 provides a proper interface for parallel sweeps (which was not available in 0.1), multiple qubit sweeps can now be done using that and the SweeperType
is not strictly needed in terms of API.
However, regarding QM in particular, parallel sweeps are compiled using the for_each_
, which according to the docs is less efficient than for_
(and from_array
) used for non-parallel (single) sweeps. Furthermore, looking at the parallel sweep example in https://github.com/qiboteam/qibolab/pull/1008#issuecomment-2313715249, it is clear that for parallel sweeps we need to upload the whole array of values we are sweeping over to the instruments, possibly requiring more memory and more communication compared to normal sweeps where we need to upload only three values per sweeper (start, finish, step), regardless of the total number of values. I have not benchmarked what is the actual difference in execution time in practice yet, but I would expect it could be relevant for large multi-dimensional sweeps.
Potential solutions for 0.2:
SweeperType
, convert all instrument sweepers to ABSOLUTE
and ignore potential performance drop in QM.for_each_
sweeps to for_
whenever this is possible. This may involve some "guessing" that won't be very elegant in terms of code. The improvements are internal so could even be postponed to 0.2.*, but would be interesting to have an idea how they'd look and if they are relevant, before we fix the interface.SweeperType
and implement them in QM and all other drivers.Following @rodolfocarobene comment, the main use of
SweeperType
was making parallel sweeps on different qubits. For example, usingSweeperType.OFFSET
you can sweep the frequency on multiple qubits using the same range but centered around a different value for each qubit. Since 0.2 provides a proper interface for parallel sweeps (which was not available in 0.1), multiple qubit sweeps can now be done using that and theSweeperType
is not strictly needed in terms of API.
I believe this was the kind of applications for which SweeperType
s were needed for.
And in practice, they were used only in Qblox, IcarusQ, and the emulator
https://github.com/search?q=repo%3Aqiboteam%2Fqibolab%20SweeperType&type=code
the first one with a complex implementation (which was coupled to sweeper parameters - and I'm not exactly sure why, though I suspect it - but I know that in the end is always converting to (start, stop, increment)
instructions at software level) and the other two with a trivial software implementation
https://github.com/qiboteam/qibolab/blob/bbac7cf23e0d879f792a16f64e2f0f5e7b147a01/src/qibolab/instruments/icarusqfpga.py#L419-L423
which makes it useless, since it could be done on the Qibocal side (if needed).
However, regarding QM in particular, parallel sweeps are compiled using the
for_each_
, which according to the docs is less efficient thanfor_
(andfrom_array
) used for non-parallel (single) sweeps. Furthermore, looking at the parallel sweep example in #1008 (comment), it is clear that for parallel sweeps we need to upload the whole array of values we are sweeping over to the instruments, possibly requiring more memory and more communication compared to normal sweeps where we need to upload only three values per sweeper (start, finish, step), regardless of the total number of values. I have not benchmarked what is the actual difference in execution time in practice yet, but I would expect it could be relevant for large multi-dimensional sweeps.
QM 0.1 is not even supporting sweeper types, and deciding the "type" based on the parameter. E.g.
https://github.com/qiboteam/qibolab/blob/9bbbbe8c30bad4e99ba2cbbcb32ecb63147a43d4/src/qibolab/instruments/qm/sweepers.py#L97-L100
(this is the frequency, and it's a SweeperType.OFFSET
, always - irrespective of the actual SweeperType
set - but there is a similar for_
loop in all the other parameters)
So, since we'd really like to release 0.2 at this point, I would keep the minimal feature right now, and keep performance optimizations for later.
Potential solutions for 0.2:
- Drop
SweeperType
, convert all instrument sweepers toABSOLUTE
and ignore potential performance drop in QM.- Follow 1, but implement improvements in the QM driver that convert
for_each_
sweeps tofor_
whenever this is possible. This may involve some "guessing" that won't be very elegant in terms of code. The improvements are internal so could even be postponed to 0.2.*, but would be interesting to have an idea how they'd look and if they are relevant, before we fix the interface.- Keep
SweeperType
and implement them in QM and all other drivers.
I would keep 2. and 3. for 0.2.2 or later. In the meanwhile, just drop it, and follow the 0.1 approach.
The idea is that we can make the SweeperType
available in a later release (if worth for performances), and add a Parameter
-dependent default. I.e. we'll put None
, but if None
we resolve as we're doing in the previous releases, even driver-dependent, if really needed (hopefully not, and we'll standardize the meaning across drivers before), but just avoiding breaking changes. It will be possible.
We also need to support (start, stop, step)
, because some instruments can only do that (e.g. Qblox, but even QICK).
In 0.1, they are inferring these values as (vals[0], vals[-1], vals[1] - vals[0])
, but we should define an interface for doing directly that, and avoiding passing the entire array if it's not possible to use it (I fear that the problem was that Sweeper
may have been designed with QM in mind, which was more capable than other instruments).
I'd keep the 0.1 behavior even in 0.2, but let's switch input:
class Sweeper(Model):
...
linspace: Optional[tuple[float, float, float]] = None
values: Optional[npt.NDArray] = None
...
@model_validator(mode='after')
def check_values(self) -> Self:
if linspace is not None and values is not None:
raise ValueError("'linspace' and 'values' are mutually exclusive)
return self
@property
def vals(self) -> npt.NDArray:
if self.values is not None:
return self.values
return np.linspace(*self.linspace)
(Pydantic syntax, but trivial to translate in dataclasses
)
Personally I would be in favor of this.
- Drop
SweeperType
, convert all instrument sweepers toABSOLUTE
and ignore potential performance drop in QM.
Since having OFFSET
and FACTOR
it is often confusing for qibocal users. Moreover, I believe that the original idea was o be able to perform single qubit calibration experiments on multiple qubits simultaneously...
Although this is for sure interesting, it shouldn't be the default.
A possible solution for qibocal could be to adjust the parameters to have ones for each qubit.
- id: resonator spectroscopy low power
operation: resonator_spectroscopy
targets: [0, 1]
parameters:
freq_width: { 0 : 5_000_000, 1: 4_000_000}
freq_step: { 0 : 100_000, 1: 200_000}
power_level: low
phase_sign: true
nshots: 1024
This is just a first attempt at a new interface. But I think that it would look much clearer compare to what we are doing now.
Of course in this particular case you will need to move from freq_width
and freq_step
to freq_min
, freq_max
and freq_step
.
Since having
OFFSET
andFACTOR
it is often confusing for qibocal users.
It's not that simple, but we can keep the substance. E.g. while you're sweeping a pulse frequency, you almost never want to really specify the absolute value (~GHz) but always the offset (~MHz). And this is what is being done, simply, the drivers are deciding by themselves (each separately) which is the meaningful choice for each parameter.
We could really convert it to absolute, but then Qibolab would only deal with ~GHz frequencies, and the conversion should be done inside Qibocal, fishing the base value from wherever it is.
For the time being, there will only be QM around, so it's easy to decide what is the base type for each parameter. While adding the second driver (Zurich or Qblox), I'd enforce the default to be the same per parameter (possibly reintroducing the sweeper type only internally, not in the interface, and passing around this information). An interface for an actual sweeper type can be added, but truly later on.
A possible solution for qibocal could be to adjust the parameters to have ones for each qubit.
I'm glad that there is room to improve even the Qibocal interface, keeping it optimal and still compatible. However, which will be the exact best Qibocal interface is a separate discussion. I would open an issue in Qibocal to discuss, as soon as we stabilize this (the attempt was useful to justify the Qibolab choice, but I'd not go any further).
Thank you for the feedback. From what I understand we all agree to drop SweeperType
, so I will soon open a PR that does this.
I am still not sure what is the expected behavior though. Originally the suggestion was to convert everything to ABSOLUTE
, however if I understand correctly @alecandido is now proposing to follow the behavior defined by the QM driver, which is:
frequency
-> OFFSET
amplitude
-> FACTOR
(note that QM has the following limitation: -2 < factor < 2)duration
-> ABSOLUTE
duration_interpolated
-> ABSOLUTE
relative_phase
-> ABSOLUTE
attenuation
-> not availablegain
-> not availablebias
-> OFFSET
lo_frequency
-> not availableAs far as I know, this is also how sweepers are currently used in qibocal, so in practice it is convenient to leave as they are (it will also be less work on the driver). I also agree that some of these options, such as OFFSET
for frequency
, are more intuitive, except maybe amplitude
which could also be more convenient as ABSOLUTE
(I can change that, if you agree). That being said, I see that it would be confusing for users that different parameters have different behavior, even if we document it.
Also, not related to sweeper types, but shall I drop attenuation
, gain
and lo_frequency
as it is unlikely that there are instruments that provide real-time sweepers of these? And at this stage, also rename bias
to offset
?
We could really convert it to absolute, but then Qibolab would only deal with ~GHz frequencies, and the conversion should be done inside Qibocal, fishing the base value from wherever it is.
I would be fine with doing the conversion in Qibocal. Especially if you consider non-runcard users this is already quite trivial to do, since we can just query the frequency in the python script. This change would be minimal compare to the changes that you already performing to support 0.2
frequency
->OFFSET
amplitude
->FACTOR
(note that QM has the following limitation: -2 < factor < 2)
duration
->ABSOLUTE
duration_interpolated
->ABSOLUTE
relative_phase
->ABSOLUTE
attenuation
-> not available
gain
-> not available
bias
->OFFSET
lo_frequency
-> not available
I would say again that we should just have absolute values. When calibrating two qubit gates having amplitude sweeps done using factor can be painful.
Also, not related to sweeper types, but shall I drop
attenuation
,gain
andlo_frequency
as it is unlikely that there are instruments that provide real-time sweepers of these?
We have some protocols in qibocal which are sweeping attenuation (for qblox) but I would hope that we can get rid of them soon-ish. I'm also happy to drop both gain
and lo_frequency
.
And at this stage, also rename
bias
tooffset
?
Yes, makes sense.
I would say again that we should just have absolute values. When calibrating two qubit gates having amplitude sweeps done using factor can be painful.
If for Qibocal is better to have all of them as absolute, then let's do so. Even for Qibolab it's the easiest (just setting some values, no operation).
We have some protocols in qibocal which are sweeping attenuation (for qblox) but I would hope that we can get rid of them soon-ish. I'm also happy to drop both
gain
andlo_frequency
.
Regarding this, there might be some value in having them as sweepers, even software ones, but on the device (if available). It would save some networking.
However, I'm not aware that this is possible. @hay-k confirmed that near-time Zurich sweepers are actually laptop-time sweepers (in this case zeraa
-time sweepers, i.e. the same machine running Qibolab and Qibocal). So, for the time being, I'd just drop them, and keep the loops in Qibocal.
Yes, makes sense.
Both are fine for me. Choose the most consistent one, according to your own taste.
Regarding this, there might be some value in having them as sweepers, even software ones, but on the device (if available). It would save some networking. However, I'm not aware that this is possible. @hay-k confirmed that near-time Zurich sweepers are actually laptop-time sweepers (in this case
zeraa
-time sweepers, i.e. the same machine running Qibolab and Qibocal). So, for the time being, I'd just drop them, and keep the loops in Qibocal.
The main issue with gain
and attenuation
is that they don't appear anywhere in the existing pulses or configs. Of course, after #995 qblox may provide it's own custom configs that have an attenuation or gain, but it's a bit weird that the Sweeper
(a general interface object) has information about custom configs.
I am also not sure to what extent we can sweep them even on device (even if not real time). I think QM provides a way to do so, but I have never tried it.
Both are fine for me. Choose the most consistent one, according to your own taste.
Same as the above, none of the existing configs has a bias
, but the DcConfig
has an offset
, so I'd go with that.
I am also not sure to what extent we can sweep them even on device (even if not real time). I think QM provides a way to do so, but I have never tried it.
The OPX and the Octave have an API for setting (the set_*
methods)
https://docs.quantum-machines.co/1.2.0/docs/API_references/qm_api/#qm.quantum_machine.QuantumMachine.set_digital_buffer
https://docs.quantum-machines.co/1.2.0/docs/API_references/qm_octave/#qm.octave.qm_octave.QmOctaveBase.set_clock
But they are not part of QUA, so I'm quite convinced they can not be made part of a qm.qua.Program
, and thus not modified on the device.
If you have a different idea, it would be quite interesting. But it's an optimization anyhow, so let's keep it simple, and leave the Python loops to Qibocal
Same as the above, none of the existing configs has a
bias
, but theDcConfig
has anoffset
, so I'd go with that.
Ah, right. So yes, I perfectly agree it should be offset
, not bias
(or they should be the same - but it's more practical to touch the sweeper at this point).
SweeperType
was dropped in #995,Sweeper
was converted to pydantic Model
, Parameter.bias
was renamed to Parameter.offset
and Parameter.attenuation
, Parameter.gain
, Parameter.lo_frequency
were dropped in #1014,
I think that it could be useful to make use of
SweeperType
introduced in https://github.com/qiboteam/qibolab/pull/459 for all drivers. In this way we could avoid to define global convention for each parameter that we sweep. I know that therfsoc
driver already supports this while in qblox this is currently being implemented in #518.@Jacfomg @stavros11 could you please port this feature also in QM and Zurich?