Skip to content
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

Merged
merged 16 commits into from
May 31, 2024

Conversation

kmlau
Copy link
Contributor

@kmlau kmlau commented May 1, 2024

The new proto DeviceParametersDiff is for a user to compose a RunJobRequest for invoking RunJob rpc on a Cirq server, in particular to populate the RunJobRequest.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.

…ay to bundle multiple DeviceParameters and their values
@kmlau kmlau requested review from wcourtney, vtomole, cduck, verult and a team as code owners May 1, 2024 23:44
@kmlau
Copy link
Contributor Author

kmlau commented May 1, 2024

cc @dstrain115

@kmlau kmlau changed the title Add a new proto DeviceParametersDiff proto which provides a compact way to bundle multiple DeviceParameters and their values Add a new proto DeviceParametersDiff which provides a compact way to bundle multiple DeviceParameters and their values May 1, 2024
Copy link

codecov bot commented May 1, 2024

Codecov Report

All modified and coverable lines are covered by tests ✅

Project coverage is 97.81%. Comparing base (6709046) to head (8e79f46).

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.
📢 Have feedback on the report? Share it here.

@senecameeks senecameeks self-requested a review May 2, 2024 21:04
// DeviceParametersDiff from a list of (DeviceParameter, ArgValue) pairs.
message DeviceParametersDiff {
message Dir {
int32 parent = 1;
Copy link
Collaborator

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.

Copy link
Contributor Author

@kmlau kmlau May 2, 2024

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
Copy link
Collaborator

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.

Copy link
Contributor Author

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.

@kmlau kmlau requested a review from dstrain115 May 2, 2024 23:42
Copy link
Collaborator

@senecameeks senecameeks left a 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?

@kmlau
Copy link
Contributor Author

kmlau commented May 3, 2024

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


import run_context

device_params_override = run_context.to_device_parameters_diff([
    (
        run_context_pb2.DeviceParameter(
            path=["q1_2", "readout_default", "readoutDemodDelay"], units="ns"
        ),
        program_pb2.ArgValue(float_value=5.0),
    ),
    (
        run_context_pb2.DeviceParameter(
            path=["q3_4", "readout_default", "readoutFidelities"]),
        program_pb2.ArgValue(
            double_values=program_pb2.RepeatedDouble(values=[0.991, 0.993])
        ),
    ),
])

my_rc = run_context_pb2.RunContext(
    device_parameters_override=device_params_override)

@kmlau kmlau requested a review from senecameeks May 3, 2024 16:06
_EMPTY_DIR_PATH_IDX = -1


def to_device_parameters_diff(
Copy link
Collaborator

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?

Copy link
Contributor Author

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.

Copy link
Collaborator

@wcourtney wcourtney May 3, 2024

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.

Copy link
Contributor Author

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?

Copy link
Collaborator

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.

@kmlau kmlau requested a review from senecameeks May 3, 2024 18:41
@CirqBot CirqBot added the size: L 250< lines changed <1000 label May 5, 2024
// See run_context.py for utility function to construct a
// DeviceParametersDiff from a list of (DeviceParameter, ArgValue) pairs.
message DeviceParametersDiff {
message Dir {
Copy link
Collaborator

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

Copy link
Contributor Author

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
Copy link
Collaborator

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.

Copy link
Contributor Author

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(
Copy link
Collaborator

@wcourtney wcourtney May 3, 2024

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
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: add punctuation.

Copy link
Contributor Author

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;
Copy link
Collaborator

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?

Copy link
Contributor Author

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.

Copy link
Collaborator

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.

Copy link
Contributor Author

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;
Copy link
Collaborator

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 {
Copy link
Collaborator

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 {
Copy link
Collaborator

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.

Copy link
Collaborator

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?

Copy link
Contributor Author

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.

Copy link
Contributor Author

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:
Copy link
Collaborator

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

Copy link
Contributor Author

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
Copy link
Collaborator

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

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done.

@kmlau
Copy link
Contributor Author

kmlau commented May 29, 2024

friendly ping @senecameeks and @dstrain115

Copy link
Collaborator

@dstrain115 dstrain115 left a 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
Copy link
Collaborator

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"

Copy link
Contributor Author

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;
Copy link
Collaborator

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(
Copy link
Collaborator

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)
Copy link
Collaborator

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?

Copy link
Contributor Author

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:
Copy link
Collaborator

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.

Copy link
Contributor Author

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

Copy link
Collaborator

@senecameeks senecameeks left a 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
Copy link
Collaborator

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
Copy link
Collaborator

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

Copy link
Contributor Author

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
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: add punctuation

Copy link
Contributor Author

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)
Copy link
Collaborator

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)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done.

@pavoljuhas pavoljuhas merged commit d077532 into quantumlib:main May 31, 2024
34 checks passed
@kmlau kmlau deleted the add-device_parameters_diff-proto branch May 31, 2024 20:44
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
size: L 250< lines changed <1000
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

7 participants