-
Notifications
You must be signed in to change notification settings - Fork 988
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add a new proto DeviceParametersDiff which provides a compact way to bundle multiple DeviceParameters and their values #6583
Add a new proto DeviceParametersDiff which provides a compact way to bundle multiple DeviceParameters and their values #6583
Conversation
…ay to bundle multiple DeviceParameters and their values
cc @dstrain115 |
Codecov ReportAll modified and coverable lines are covered by tests ✅
Additional details and impacted files@@ Coverage Diff @@
## main #6583 +/- ##
=======================================
Coverage 97.81% 97.81%
=======================================
Files 1061 1063 +2
Lines 91455 91501 +46
=======================================
+ Hits 89454 89500 +46
Misses 2001 2001 ☔ View full report in Codecov by Sentry. |
// DeviceParametersDiff from a list of (DeviceParameter, ArgValue) pairs. | ||
message DeviceParametersDiff { | ||
message Dir { | ||
int32 parent = 1; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This should explain that this is an index into the strs array.
Also should clarify that -1 means root.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
comments added to indicate the various int32 fields as indices into which arrays, among groups and strs.
dirs_seen: set[tuple[int, int]] = set() | ||
|
||
for device_param, value in device_params: | ||
parent = -1 # no parent for the 1st path component |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think "-1" should be a constant, as we will likely need it for decoding the diff too.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
added a constant of -1, with comment.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
How will users interact with this new proto? IIUC, the internal diff proto has an abstraction layer that clients can easily interact with for creating a Diff which can then be easily packaged into its proto form. Can a similar well defined abstraction layer e.g class DeviceParameterDiff
exist for this proto as well? It doesn't have to include all the APIs as the one linked, but a clear abstraction layer will help me see how this proto will later get integrated into a Job's run context. WDYT?
thanks for the comment. the PR includes a helper function to construct a DeviceParametersDiff proto out of a list of DeviceParameter and their values. please see the module run_context.py in this PR. Suppose you want to override some readout parameters on 2 qubits, and build a RunContext proto with such overrides, you may use the following code
|
_EMPTY_DIR_PATH_IDX = -1 | ||
|
||
|
||
def to_device_parameters_diff( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
My understanding is that users never interact with run_context
directly. Instead they call run_sweep
and pass in cirq.Sweepable
and the number of repetitions
, and we construct the run_context on their behalf.
It would be nice to have a similar cirq.DeviceParametesDiff
concept that the user can pass into run_sweep
that can later be serialized into the run_context. WDYT?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
the proto is for a user to construct a RunJobRequest, in order to invoke RunJob on a cirq server.
the DeviceParametersDiff proto is not intended for uses to make python local call to run_sweep.
let me clarify it in the pr description.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I expect that this will be something to specify outside of a run_sweep
/Job from the user's perspective - e.g. as part of a sampler - even though we pass it to the Engine API as part of the job spec. +1 to @kmlau that we don't need to add that interface yet though.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
oh, I was incorrectly assuming you were referring to our internal run_sweep API, https://github.com/qh-lab/pyle/blob/master/src/pyle/dataking/cirq_sweep/_base.py#L87 , and was not aware there is a similarly named function in cirq-google repo.
yes, I will need to make changes to the cirq run_sweep function to incorporate an optional device parameters diff concept. would it be viable to do it later, since we will need to make changes to cirq server first, before exposing the device parameeters overriding in run_sweep call?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I agree that we will need to figure out how to pass this into a sampler.run_sweep(), but let's just deal with the proto changes in this PR.
// See run_context.py for utility function to construct a | ||
// DeviceParametersDiff from a list of (DeviceParameter, ArgValue) pairs. | ||
message DeviceParametersDiff { | ||
message Dir { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Avoid abbreviation, here and below: go/pystyle#naming
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
rename from "Dir" to "ResourceGroup"
@@ -10,6 +12,10 @@ option java_multiple_files = true; | |||
message RunContext { | |||
// The parameters for operations in a program. | |||
repeated ParameterSweep parameter_sweeps = 1; | |||
|
|||
// optional override of select device parameters before program |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is it valid for both the parameter_sweeps
and device_parameter_override
to specify the same parameter(s)? The ordering of these two is ambiguous, which only makes sense if they're disjoint, but either way it should be documented.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
good catch. yes the proto definition allows such overlap. however the override will have no effect since it is applied before the circuit sweep is executed.
I have put in proto field comment to document such case.
_EMPTY_DIR_PATH_IDX = -1 | ||
|
||
|
||
def to_device_parameters_diff( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I expect that this will be something to specify outside of a run_sweep
/Job from the user's perspective - e.g. as part of a sampler - even though we pass it to the Engine API as part of the job spec. +1 to @kmlau that we don't need to add that interface yet though.
""" | ||
diff = run_context_pb2.DeviceParametersDiff() | ||
|
||
# Maps a string to its token id. A string is a component of a device parameter's path |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nit: add punctuation.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
done
int32 dir = 1; | ||
// as index into the strs array. | ||
int32 name = 2; | ||
ArgValue value = 3; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
DeviceParameter
- which is meant to represent the same parameters being overridden - can only sweep over float values. Should these two types align on the valid types?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
the possible device parameters to be override can take values other than floats.
to allow such flexibility, I would vouch for the ArgValue variant type.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Agree with @kmlau . The sweeps are restricted to floats since sweeps in cirq are restricted to floats. This isn't a sweep and just sets a value, and there are non-float device parameters.
Let's document this inconsistency above though.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
add additional docstring to explain the need for ArgValue submessage.
} | ||
message Key { | ||
// directory hosting this key, as index into dirs array. | ||
int32 dir = 1; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why does Key
reference the dir
index rather than just being a sub-message?
int32 name = 2; | ||
ArgValue value = 3; | ||
} | ||
message Del { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could deletions be incorporated into the Dir
as a list of strings indicating keys that should be deleted?
// values from this bundle before executing a circuit sweep. | ||
// See run_context.py for utility function to construct a | ||
// DeviceParametersDiff from a list of (DeviceParameter, ArgValue) pairs. | ||
message DeviceParametersDiff { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is there a reason that the parameter hierarchy isn't encoded into the message structure itself? i.e. why do we use indices to reconstruct the path from disjoint lists rather than a recursive structure like this:
message Resource {
string name = 1;
repeated Resource sub_resources = 2;
repeated map<string, ArgValue> attributes = 3;
}
If repeated names are a concern, we could still use a token dictionary for identifiers as is done in the current iteration.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
what does a repeated attributes
field mean?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this is just a typo. A map already incorporates multiple map entries.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
instead of inventing a new message as you allude to, the proposed DeviceParametersDiff is based on internal RegistryDiff data structure, which is well tested and in production uses.
I would prefer to stay with the current DeviceParametersDiff definition. wdyt?
import google.protobuf.text_format as text_format | ||
|
||
|
||
def test_to_device_parameters_diff() -> None: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Test names should summarize the behavior being tested and its expected outcome.
go/unit-testing-practices#naming
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
renamed the test to call out the behavior to be tested.
strs: "phase_i_rad" | ||
strs: "q5_6" | ||
""" | ||
assert text_format.Parse(expected_diff_pb_text, run_context_pb2.DeviceParametersDiff()) == diff |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Test behaviors, not methods.
Behaviors should be tested independently.
go/unit-testing-practices#behavior-testing-examples
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
done.
friendly ping @senecameeks and @dstrain115 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Some final comments, then LGTM
// Optional override of select device parameters before program | ||
// execution. Note it is permissible to specify the same device parameter | ||
// here and in a parameter_sweeps, as sweep.single_sweep.parameter. | ||
// However the override will have no effect since it is applied before |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This wording is confusing. Can we clarify it? Maybe something like "If the same parameter is supplied in both places, then XYZ will happen"
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
reworded the docstring here.
int32 dir = 1; | ||
// as index into the strs array. | ||
int32 name = 2; | ||
ArgValue value = 3; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Agree with @kmlau . The sweeps are restricted to floats since sweeps in cirq are restricted to floats. This isn't a sweep and just sets a value, and there are non-float device parameters.
Let's document this inconsistency above though.
_EMPTY_DIR_PATH_IDX = -1 | ||
|
||
|
||
def to_device_parameters_diff( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I agree that we will need to figure out how to pass this into a sampler.run_sweep(), but let's just deal with the proto changes in this PR.
""" | ||
diff = run_context_pb2.DeviceParametersDiff() | ||
|
||
@functools.lru_cache(maxsize=None) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is this safe to cache? What happens if we call to_device_parameters_diff twice? token(s) may not be at the same index in a subsequent call? Will the cache of the nested function handle this correctly?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
yes, the cache decoration on an inner function def token_id(...)
gives correct behavior on multiple calls to the outer function to_device_parameters_diff
.
I have added an extra unit test to make multiple calls to to_device_parameters_diff, with correct token id assignments.
# Maps a resource group path to its index in diff.groups. | ||
resource_groups_index: dict[tuple[str, ...], int] = {tuple(): _EMPTY_RESOURCE_PATH_IDX} | ||
|
||
def resource_path_id(path: tuple[str, ...]) -> int: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Probably should add a docstring.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
docstring for resource_path_id
added
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
A few nits but overall LGTM
Thanks, Kim-Ming!
// The main use case is to set those parameters with the | ||
// values from this bundle before executing a circuit sweep. | ||
// Note multiple device parameters may have common ancestor paths | ||
// and/or share the same parameter names. A DeviceParamaetersDiff |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nit: fix spelling
// Note multiple device parameters may have common ancestor paths | ||
// and/or share the same parameter names. A DeviceParamaetersDiff | ||
// stores the resource groups hierarchy extracted from the DeviceParameters' | ||
// paths and maintains a string tables; thereby storing ancestor resource |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nit: should this be maintains a table of strings
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
updated per suggested.
def to_device_parameters_diff( | ||
device_params: Sequence[tuple[run_context_pb2.DeviceParameter, program_pb2.ArgValue]] | ||
) -> run_context_pb2.DeviceParametersDiff: | ||
"""Constructs a DeviceParametersDiff from multiple DeviceParameters and values |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nit: add punctuation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
done.
|
||
def resource_path_id(path: tuple[str, ...]) -> int: | ||
"""Computes the index of a path in diff.groups.""" | ||
idx = resource_groups_index.get(path) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nit: prefer explicitly setting a default resource_groups_index.get(path, None)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
done.
The new proto
DeviceParametersDiff
is for a user to compose a RunJobRequest for invoking RunJob rpc on a Cirq server, in particular to populate theRunJobRequest.run_context
field with device parameters overrides to customize the circuit(s) execution with some control on the device's samples data.This is based on a design reviews to add "device parameters overrides" before executing circuits sweeping.
I renamed some proto type names from similar internal data structure to prevent reference to internal infrastructures.