зеркало из https://github.com/microsoft/iqsharp.git
Merge pull request #783 from microsoft/xfield/qir
Add support for passing Target Capabilities in QIR generation and job submission
This commit is contained in:
Коммит
3d8f999d67
|
@ -56,7 +56,12 @@ public record AzureExecutionTarget
|
|||
/// Any other target capability must be subsumed by this maximum
|
||||
/// in order to be supported by this target.
|
||||
/// </summary>
|
||||
public TargetCapability DefaultCapability => GetProvider(TargetId) switch
|
||||
public TargetCapability DefaultCapability => GetMaximumCapability(TargetId);
|
||||
|
||||
/// <summary>
|
||||
/// Returns the maximum level of capability supported by the target.
|
||||
/// </summary>
|
||||
public static TargetCapability GetMaximumCapability(string? targetId) => GetProvider(targetId) switch
|
||||
{
|
||||
AzureProvider.IonQ => TargetCapabilityModule.BasicQuantumFunctionality,
|
||||
AzureProvider.Quantinuum => TargetCapabilityModule.BasicMeasurementFeedback,
|
||||
|
|
|
@ -4,14 +4,26 @@
|
|||
#nullable enable
|
||||
|
||||
using System.IO;
|
||||
using System.Reflection.PortableExecutable;
|
||||
using System.Threading;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Quantum.QsCompiler;
|
||||
using Microsoft.Quantum.Runtime;
|
||||
using Microsoft.Quantum.Runtime.Submitters;
|
||||
using Microsoft.Quantum.Simulation.Core;
|
||||
|
||||
namespace Microsoft.Quantum.IQSharp.AzureClient
|
||||
{
|
||||
public record EntryPointSubmitEventData
|
||||
{
|
||||
public string? MachineName { get; set; }
|
||||
public string? Target { get; set; }
|
||||
public string? TargetCapability { get; set; }
|
||||
public string? JobId { get; set; }
|
||||
}
|
||||
|
||||
public record EntryPointSubmitEvent : Event<EntryPointSubmitEventData>;
|
||||
|
||||
/// <inheritdoc/>
|
||||
internal class EntryPoint : IEntryPoint
|
||||
{
|
||||
|
@ -23,9 +35,12 @@ namespace Microsoft.Quantum.IQSharp.AzureClient
|
|||
private Type OutputType { get; }
|
||||
private OperationInfo OperationInfo { get; }
|
||||
private ILogger? Logger { get; }
|
||||
private IEventService? EventService { get; }
|
||||
|
||||
/// <inheritdoc/>
|
||||
public Stream? QirStream { get; }
|
||||
/// <inheritdoc/>
|
||||
public TargetCapability? TargetCapability { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Creates an object used to submit jobs to Azure Quantum.
|
||||
|
@ -38,10 +53,21 @@ namespace Microsoft.Quantum.IQSharp.AzureClient
|
|||
/// <see cref="EntryPointInfo{I,O}"/> object provided as the <c>entryPointInfo</c> argument.</param>
|
||||
/// <param name="operationInfo">Information about the Q# operation to be used as the entry point.</param>
|
||||
/// <param name="logger">Logger used to report internal diagnostics.</param>
|
||||
/// <param name="eventService">EventService used to report telemetry.</param>
|
||||
/// <param name="qirStream">
|
||||
/// Stream from which QIR bitcode for the entry point can be read.
|
||||
/// </param>
|
||||
public EntryPoint(object entryPointInfo, Type inputType, Type outputType, OperationInfo operationInfo, ILogger? logger, Stream? qirStream = null)
|
||||
/// <param name="targetCapability">
|
||||
/// The target capability which the QIR was generated for.
|
||||
/// </param>
|
||||
public EntryPoint(object entryPointInfo,
|
||||
Type inputType,
|
||||
Type outputType,
|
||||
OperationInfo operationInfo,
|
||||
ILogger? logger,
|
||||
IEventService? eventService,
|
||||
Stream? qirStream = null,
|
||||
TargetCapability? targetCapability = null)
|
||||
{
|
||||
EntryPointInfo = entryPointInfo;
|
||||
InputType = inputType;
|
||||
|
@ -49,6 +75,8 @@ namespace Microsoft.Quantum.IQSharp.AzureClient
|
|||
OperationInfo = operationInfo;
|
||||
Logger = logger;
|
||||
QirStream = qirStream;
|
||||
TargetCapability = targetCapability;
|
||||
EventService = eventService;
|
||||
}
|
||||
|
||||
private object GetEntryPointInputObject(AzureSubmissionContext submissionContext)
|
||||
|
@ -235,7 +263,19 @@ namespace Microsoft.Quantum.IQSharp.AzureClient
|
|||
&& method.GetParameters()[2].ParameterType == typeof(IQuantumMachineSubmissionContext))
|
||||
.MakeGenericMethod(new Type[] { InputType, OutputType });
|
||||
var submitParameters = new object[] { EntryPointInfo, entryPointInput, submissionContext };
|
||||
return (Task<IQuantumMachineJob>)submitMethod.Invoke(machine, submitParameters);
|
||||
|
||||
return ((Task<IQuantumMachineJob>)submitMethod.Invoke(machine, submitParameters))
|
||||
.ContinueWith<IQuantumMachineJob>(jobTask =>
|
||||
{
|
||||
var job = jobTask.Result;
|
||||
EventService?.Trigger<EntryPointSubmitEvent, EntryPointSubmitEventData>(new EntryPointSubmitEventData()
|
||||
{
|
||||
MachineName = machine.GetType().FullName,
|
||||
JobId = job.Id,
|
||||
Target = machine.Target
|
||||
});
|
||||
return job;
|
||||
});
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
|
@ -252,11 +292,27 @@ namespace Microsoft.Quantum.IQSharp.AzureClient
|
|||
|
||||
var entryPointInput = GetEntryPointInputArguments(submissionContext, allowOptionalParameters);
|
||||
Logger?.LogInformation("Submitting job {FriendlyName} with {NShots} shots.", submissionContext.FriendlyName, submissionContext.Shots);
|
||||
var options = SubmissionOptions.Default.With(submissionContext.FriendlyName, submissionContext.Shots, submissionContext.InputParams);
|
||||
var options = SubmissionOptions.Default.With(
|
||||
friendlyName: submissionContext.FriendlyName,
|
||||
shots: submissionContext.Shots,
|
||||
inputParams: submissionContext.InputParams,
|
||||
targetCapability: TargetCapability?.Name);
|
||||
|
||||
// Find and invoke the method on IQirSubmitter that is declared as:
|
||||
// Task<IQuantumMachineJob> SubmitAsync(Stream qir, string entryPoint, IReadOnlyList<Argument> arguments, SubmissionOptions submissionOptions)
|
||||
return submitter.SubmitAsync(QirStream, $"{EntryPointNamespaceName}__{submissionContext.OperationName}", entryPointInput, options);
|
||||
return submitter.SubmitAsync(QirStream, $"{EntryPointNamespaceName}__{submissionContext.OperationName}", entryPointInput, options)
|
||||
.ContinueWith<IQuantumMachineJob>(jobTask =>
|
||||
{
|
||||
var job = jobTask.Result;
|
||||
EventService?.Trigger<EntryPointSubmitEvent, EntryPointSubmitEventData>(new EntryPointSubmitEventData()
|
||||
{
|
||||
MachineName = submitter.GetType().FullName,
|
||||
JobId = job.Id,
|
||||
Target = submitter.Target,
|
||||
TargetCapability = this.TargetCapability?.Name,
|
||||
});
|
||||
return job;
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -23,6 +23,7 @@ namespace Microsoft.Quantum.IQSharp.AzureClient
|
|||
private IWorkspace Workspace { get; }
|
||||
private ISnippets Snippets { get; }
|
||||
private IServiceProvider ServiceProvider { get; }
|
||||
private IEventService EventService { get; }
|
||||
public IReferences References { get; }
|
||||
public AssemblyInfo[] WorkspaceAssemblies { get; set; } = Array.Empty<AssemblyInfo>();
|
||||
public AssemblyInfo? SnippetsAssemblyInfo { get; set; }
|
||||
|
@ -43,10 +44,11 @@ namespace Microsoft.Quantum.IQSharp.AzureClient
|
|||
References = references;
|
||||
Logger = logger;
|
||||
ServiceProvider = serviceProvider;
|
||||
EventService = eventService;
|
||||
|
||||
AssemblyLoadContext.Default.Resolving += Resolve;
|
||||
|
||||
eventService?.TriggerServiceInitialized<IEntryPointGenerator>(this);
|
||||
EventService?.TriggerServiceInitialized<IEntryPointGenerator>(this);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
@ -213,8 +215,14 @@ namespace Microsoft.Quantum.IQSharp.AzureClient
|
|||
.Invoke(new object[] { entryPointOperationInfo.RoslynType });
|
||||
|
||||
return new EntryPoint(
|
||||
entryPointInfo, entryPointInputType, entryPointOutputType, entryPointOperationInfo,
|
||||
logger: ServiceProvider.GetService<ILogger<EntryPoint>>(), EntryPointAssemblyInfo.QirBitcode
|
||||
entryPointInfo: entryPointInfo,
|
||||
inputType: entryPointInputType,
|
||||
outputType: entryPointOutputType,
|
||||
operationInfo: entryPointOperationInfo,
|
||||
logger: ServiceProvider.GetService<ILogger<EntryPoint>>(),
|
||||
eventService: EventService,
|
||||
qirStream: EntryPointAssemblyInfo.QirBitcode,
|
||||
targetCapability: capability
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
|
||||
using System.IO;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.Quantum.QsCompiler;
|
||||
using Microsoft.Quantum.Runtime;
|
||||
using Microsoft.Quantum.Runtime.Submitters;
|
||||
|
||||
|
@ -39,5 +39,10 @@ namespace Microsoft.Quantum.IQSharp.AzureClient
|
|||
/// The stream from which QIR bitcode for the entry point can be read.
|
||||
/// </summary>
|
||||
public Stream? QirStream { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The target capability which the QIR was generated for.
|
||||
/// </summary>
|
||||
public TargetCapability? TargetCapability { get; }
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,13 +3,10 @@
|
|||
|
||||
#nullable enable
|
||||
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.Quantum.QsCompiler;
|
||||
using Microsoft.Quantum.Runtime;
|
||||
using Microsoft.Quantum.Runtime.Submitters;
|
||||
using System.IO;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace System.Runtime.CompilerServices
|
||||
{
|
||||
|
@ -19,9 +16,10 @@ namespace System.Runtime.CompilerServices
|
|||
namespace Microsoft.Quantum.IQSharp.AzureClient
|
||||
{
|
||||
/// <param name="ExpectedArguments">The expected entry point arguments to the SubmitAsync method.</param>
|
||||
internal record MockQirSubmitter(IReadOnlyList<Argument> ExpectedArguments) : IQirSubmitter
|
||||
/// <param name="ExpectedTargetCapability">The expected target capability to the SubmitAsync method.</param>
|
||||
internal record MockQirSubmitter(IReadOnlyList<Argument> ExpectedArguments, TargetCapability? ExpectedTargetCapability = null) : IQirSubmitter
|
||||
{
|
||||
public string Target => throw new NotImplementedException();
|
||||
public string Target => "MockQirSubmitter";
|
||||
|
||||
private bool IsArgumentValueEqual(ArgumentValue fst, ArgumentValue snd) =>
|
||||
(fst, snd) switch
|
||||
|
@ -73,6 +71,12 @@ namespace Microsoft.Quantum.IQSharp.AzureClient
|
|||
throw new ArgumentException("The arguments passed to the SubmitAsync did not match the expected arguments to the Mock QIR submitter.");
|
||||
}
|
||||
|
||||
if (ExpectedTargetCapability != null
|
||||
&& !ExpectedTargetCapability.Name.Equals(options.TargetCapability))
|
||||
{
|
||||
throw new ArgumentException($"The options.TargetCapability passed to the SubmitAsync ({options.TargetCapability}) did not match the ExpectedTargetCapability ({ExpectedTargetCapability.Name}) to the Mock QIR submitter.");
|
||||
}
|
||||
|
||||
return Task.FromResult(job as IQuantumMachineJob);
|
||||
}
|
||||
|
||||
|
|
|
@ -3,10 +3,6 @@
|
|||
|
||||
#nullable enable
|
||||
|
||||
using System;
|
||||
using System.Reflection;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.Quantum.Runtime;
|
||||
using Microsoft.Quantum.Simulation.Core;
|
||||
|
||||
|
@ -14,9 +10,9 @@ namespace Microsoft.Quantum.IQSharp.AzureClient
|
|||
{
|
||||
internal class MockQuantumMachine : IQuantumMachine
|
||||
{
|
||||
public string ProviderId => throw new NotImplementedException();
|
||||
public string ProviderId => "MockQuantumMachine";
|
||||
|
||||
public string Target => throw new NotImplementedException();
|
||||
public string Target => "MockQuantumMachine.Target";
|
||||
|
||||
private MockAzureWorkspace? Workspace { get; }
|
||||
|
||||
|
|
|
@ -3,14 +3,18 @@
|
|||
|
||||
#nullable enable
|
||||
|
||||
using System;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.IO;
|
||||
using System.Net;
|
||||
using System.Text;
|
||||
using System.Text.RegularExpressions;
|
||||
using LlvmBindings;
|
||||
using LlvmBindings.Interop;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Quantum.IQSharp.AzureClient;
|
||||
using Microsoft.Quantum.IQSharp.Common;
|
||||
using Microsoft.Quantum.QsCompiler;
|
||||
|
||||
namespace Microsoft.Quantum.IQSharp.Kernel;
|
||||
|
||||
|
@ -37,6 +41,40 @@ public record LlvmIr(
|
|||
}
|
||||
}
|
||||
|
||||
public enum QirOutputFormat
|
||||
{
|
||||
IR,
|
||||
Bitcode,
|
||||
BitcodeBase64,
|
||||
}
|
||||
|
||||
public record QirMagicEventData()
|
||||
{
|
||||
static Regex CapabilityInsuficientRegex = new Regex("(requires runtime capabilities).*(not supported by the target)",
|
||||
RegexOptions.Compiled | RegexOptions.Multiline | RegexOptions.IgnoreCase);
|
||||
public bool OutputToFile { get; set; }
|
||||
public string? Target { get; set; }
|
||||
public string? Capability { get; set; }
|
||||
public bool InvalidCapability { get; set; }
|
||||
public bool CapabilityInsufficient { get; set; }
|
||||
public string? CapabilityName { get; set; }
|
||||
public long? QirStreamSize { get; set; }
|
||||
public QirOutputFormat? OutputFormat { get; set; }
|
||||
public string? Error { get; set; }
|
||||
|
||||
public void SetError(string message) => this.Error = message;
|
||||
|
||||
public void SetError(Exception exception)
|
||||
{
|
||||
this.Error = $"{exception.GetType().FullName}";
|
||||
var exceptionString = exception.ToString();
|
||||
this.CapabilityInsufficient = CapabilityInsuficientRegex.IsMatch(exceptionString);
|
||||
}
|
||||
}
|
||||
|
||||
public record QirMagicEvent : Event<QirMagicEventData>;
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// A magic command that can be used to generate QIR from a given
|
||||
/// operation as an entry point.
|
||||
|
@ -44,6 +82,10 @@ public record LlvmIr(
|
|||
public class QirMagic : AbstractMagic
|
||||
{
|
||||
private const string ParameterNameOperationName = "__operationName__";
|
||||
private const string ParameterNameTarget = "target";
|
||||
private const string ParameterNameTargetCapability = "target_capability";
|
||||
private const string ParameterNameOutputFile = "output_file";
|
||||
private const string ParameterNameOutputFormat = "output_format";
|
||||
|
||||
/// <summary>
|
||||
/// Constructs the magic command from DI services.
|
||||
|
@ -56,21 +98,22 @@ public class QirMagic : AbstractMagic
|
|||
ISymbolResolver symbolResolver,
|
||||
IConfigurationSource configurationSource,
|
||||
IMetadataController metadataController,
|
||||
IEventService eventService,
|
||||
ISnippets snippets
|
||||
) : base(
|
||||
"qir",
|
||||
new Microsoft.Jupyter.Core.Documentation
|
||||
{
|
||||
Summary = "Compiles a given Q# entry point to QIR, saving the resulting QIR to a given file.",
|
||||
Description = @"
|
||||
Description = $@"
|
||||
This command takes the full name of a Q# entry point, and compiles the Q# from that entry point
|
||||
into QIR. The resulting program is then executed, and the output of the program is displayed.
|
||||
|
||||
#### Required parameters
|
||||
|
||||
- Q# operation or function name. This must be the first parameter, and must be a valid Q# operation
|
||||
- `{ParameterNameOperationName}=<string>`: Q# operation or function name.
|
||||
This must be the first parameter, and must be a valid Q# operation
|
||||
or function name that has been defined either in the notebook or in a Q# file in the same folder.
|
||||
- The file path for where to save the output QIR to, specified as `output=<file path>`.
|
||||
".Dedent(),
|
||||
Examples = new []
|
||||
{
|
||||
|
@ -91,6 +134,7 @@ public class QirMagic : AbstractMagic
|
|||
this.ConfigurationSource = configurationSource;
|
||||
this.MetadataController = metadataController;
|
||||
this.Snippets = snippets;
|
||||
this.EventService = eventService;
|
||||
}
|
||||
|
||||
private IAzureClient AzureClient { get; }
|
||||
|
@ -98,6 +142,7 @@ public class QirMagic : AbstractMagic
|
|||
private ISymbolResolver SymbolResolver { get; }
|
||||
private IMetadataController MetadataController { get; }
|
||||
private ISnippets Snippets { get; }
|
||||
private IEventService EventService { get; }
|
||||
|
||||
private ILogger? Logger { get; }
|
||||
|
||||
|
@ -146,15 +191,37 @@ public class QirMagic : AbstractMagic
|
|||
}
|
||||
|
||||
/// <summary>
|
||||
/// Simulates an operation given a string with its name and a JSON
|
||||
/// encoding of its arguments.
|
||||
/// Runs the QIR magic.
|
||||
/// In addition to the public parameters documented in the %qir magic command,
|
||||
/// it also supports some internal parameters used by the `QSharpCallable._repr_qir_`
|
||||
/// method from the `qsharp` Python Package.
|
||||
/// #### Optional parameters
|
||||
/// - `target:string`: The intended execution target for the compiled entrypoint.
|
||||
/// Defaults to the active Azure Quantum target (which can be set with `%azure.target`).
|
||||
/// Otherwise, defaults to a generic target, which may not work when running on a specific target.
|
||||
/// - `target_capability:string`: The capability of the intended execution target.
|
||||
/// If `target` is specified or there is an active Azure Quantum target,
|
||||
/// defaults to the target's maximum capability.
|
||||
/// Otherwise, defaults to `FullComputation`, which may not be supported when running on a specific target.
|
||||
/// - `output_file:string`: The file path for where to save the output QIR.
|
||||
/// If empty, a uniquely-named temporary file will be created.
|
||||
/// - `output_format:QirOutputFormat`: The QIR output format.
|
||||
/// Defaults to `IR`.
|
||||
/// Possible options are:
|
||||
/// * `IR`: Human-readable Intermediate Representation in plain-text
|
||||
/// * `Bitcode`: LLVM bitcode (only when writing to a output file)
|
||||
/// * `BitcodeBase64`: LLVM bitcode encoded as Base64
|
||||
/// </summary>
|
||||
public async Task<ExecutionResult> RunAsync(string input, IChannel channel)
|
||||
{
|
||||
QirMagicEventData qirMagicArgs = new();
|
||||
try
|
||||
{
|
||||
var inputParameters = ParseInputParameters(input, firstParameterInferredName: ParameterNameOperationName);
|
||||
|
||||
var name = inputParameters.DecodeParameter<string>(ParameterNameOperationName);
|
||||
if (name == null) throw new InvalidOperationException($"No operation name provided.");
|
||||
|
||||
var symbol = SymbolResolver.Resolve(name) as IQSharpSymbol;
|
||||
if (symbol == null)
|
||||
{
|
||||
|
@ -162,54 +229,96 @@ public class QirMagic : AbstractMagic
|
|||
return ExecuteStatus.Error.ToExecutionResult();
|
||||
}
|
||||
|
||||
var outputFilePath = inputParameters.DecodeParameter<string>(ParameterNameOutputFile);
|
||||
qirMagicArgs.OutputToFile = !string.IsNullOrEmpty(outputFilePath);
|
||||
var outputFormat = inputParameters.DecodeParameter<QirOutputFormat>(ParameterNameOutputFormat,
|
||||
QirOutputFormat.IR);
|
||||
qirMagicArgs.OutputFormat = outputFormat;
|
||||
var target = inputParameters.DecodeParameter<string>(ParameterNameTarget,
|
||||
this.AzureClient.ActiveTarget?.TargetId);
|
||||
qirMagicArgs.Target = target;
|
||||
var capabilityName = inputParameters.DecodeParameter<string>(ParameterNameTargetCapability);
|
||||
qirMagicArgs.CapabilityName = capabilityName;
|
||||
|
||||
TargetCapability? capability = null;
|
||||
if (!string.IsNullOrEmpty(capabilityName))
|
||||
{
|
||||
var capabilityFromName = TargetCapabilityModule.FromName(capabilityName);
|
||||
if (FSharp.Core.OptionModule.IsNone(capabilityFromName))
|
||||
{
|
||||
qirMagicArgs.InvalidCapability = true;
|
||||
return $"The capability {capabilityName} is not a valid target capability."
|
||||
.ToExecutionResult(ExecuteStatus.Error);
|
||||
}
|
||||
capability = capabilityFromName.Value;
|
||||
}
|
||||
else if (!string.IsNullOrEmpty(target))
|
||||
{
|
||||
capability = AzureExecutionTarget.GetMaximumCapability(target);
|
||||
}
|
||||
qirMagicArgs.Capability = capability?.ToString();
|
||||
|
||||
IEntryPoint entryPoint;
|
||||
try
|
||||
{
|
||||
var capability = this.AzureClient.TargetCapability;
|
||||
var target = this.AzureClient.ActiveTarget?.TargetId;
|
||||
entryPoint = await EntryPointGenerator.Generate(name, target, capability, generateQir: true);
|
||||
}
|
||||
catch (TaskCanceledException tce)
|
||||
catch (CompilationErrorsException exception)
|
||||
{
|
||||
throw tce;
|
||||
}
|
||||
catch (CompilationErrorsException e)
|
||||
{
|
||||
var msg = $"The Q# operation {name} could not be compiled as an entry point for job execution.";
|
||||
this.Logger?.LogError(e, msg);
|
||||
channel.Stderr(msg);
|
||||
channel.Stderr(e.Message);
|
||||
|
||||
if (MetadataController.IsPythonUserAgent() || ConfigurationSource.CompilationErrorStyle == CompilationErrorStyle.Basic)
|
||||
{
|
||||
foreach (var m in e.Errors) channel.Stderr(m);
|
||||
}
|
||||
else
|
||||
{
|
||||
channel.DisplayFancyDiagnostics(e.Diagnostics, Snippets, input);
|
||||
}
|
||||
return AzureClientError.InvalidEntryPoint.ToExecutionResult();
|
||||
qirMagicArgs.SetError(exception);
|
||||
return ReturnCompilationError(input, channel, name, exception);
|
||||
}
|
||||
|
||||
if (entryPoint is null)
|
||||
{
|
||||
return "Internal error: generated entry point was null, but no compilation errors were returned."
|
||||
.ToExecutionResult(ExecuteStatus.Error);
|
||||
var message = "Internal error: generated entry point was null, but no compilation errors were returned.";
|
||||
qirMagicArgs.SetError(message);
|
||||
return message.ToExecutionResult(ExecuteStatus.Error);
|
||||
}
|
||||
|
||||
if (entryPoint.QirStream is null)
|
||||
{
|
||||
return "Internal error: generated entry point does not contain a QIR bitcode stream, but no compilation errors were returned."
|
||||
.ToExecutionResult(ExecuteStatus.Error);
|
||||
var message = "Internal error: generated entry point does not contain a QIR bitcode stream, but no compilation errors were returned.";
|
||||
return message.ToExecutionResult(ExecuteStatus.Error);
|
||||
}
|
||||
|
||||
var bitcodeFile = Path.ChangeExtension(Path.GetTempFileName(), ".bc");
|
||||
using (var outStream = File.OpenWrite(bitcodeFile))
|
||||
Stream qriStream = entryPoint.QirStream;
|
||||
qirMagicArgs.QirStreamSize = qriStream?.Length;
|
||||
|
||||
return outputFormat switch
|
||||
{
|
||||
entryPoint.QirStream.CopyTo(outStream);
|
||||
QirOutputFormat.Bitcode =>
|
||||
ReturnQirBitcode(outputFilePath, qriStream),
|
||||
QirOutputFormat.BitcodeBase64 =>
|
||||
ReturnQirBitcodeBase64(outputFilePath, qriStream),
|
||||
QirOutputFormat.IR or _ =>
|
||||
ReturnQIR_IR(channel, outputFilePath, qriStream),
|
||||
};
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
qirMagicArgs.SetError(exception);
|
||||
throw;
|
||||
}
|
||||
finally
|
||||
{
|
||||
this.EventService?.Trigger<QirMagicEvent, QirMagicEventData>(qirMagicArgs);
|
||||
}
|
||||
}
|
||||
|
||||
if (!TryParseBitcode(bitcodeFile, out var moduleRef, out var parseErr))
|
||||
private ExecutionResult ReturnQIR_IR(IChannel channel, string? outputFilePath, Stream qirStream)
|
||||
{
|
||||
string bitcodeFilePath = Path.ChangeExtension(Path.GetTempFileName(), ".bc")!;
|
||||
string irFilePath = string.IsNullOrEmpty(outputFilePath)
|
||||
? Path.ChangeExtension(bitcodeFilePath, ".ll")
|
||||
: outputFilePath;
|
||||
|
||||
using (var outStream = File.OpenWrite(bitcodeFilePath))
|
||||
{
|
||||
qirStream.CopyTo(outStream);
|
||||
}
|
||||
|
||||
if (!TryParseBitcode(bitcodeFilePath, out var moduleRef, out var parseErr))
|
||||
{
|
||||
var msg = $"Internal error: Could not parse generated QIR bitcode.\nLLVM returned error message: {parseErr}";
|
||||
channel.Stderr(msg);
|
||||
|
@ -217,10 +326,72 @@ public class QirMagic : AbstractMagic
|
|||
return ExecuteStatus.Error.ToExecutionResult();
|
||||
}
|
||||
|
||||
var llFile = Path.ChangeExtension(bitcodeFile, ".ll");
|
||||
moduleRef.TryPrintToFile(llFile, out var writeErr);
|
||||
var llvmIR = File.ReadAllText(llFile);
|
||||
if (!moduleRef.TryPrintToFile(irFilePath, out var writeErr))
|
||||
{
|
||||
return $"Error generating IR from bitcode: {writeErr}"
|
||||
.ToExecutionResult(ExecuteStatus.Error);
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(outputFilePath))
|
||||
{
|
||||
return new LlvmIr($"QIR {QirOutputFormat.IR} written to {outputFilePath}").ToExecutionResult();
|
||||
}
|
||||
|
||||
var llvmIR = File.ReadAllText(irFilePath);
|
||||
return new LlvmIr(llvmIR).ToExecutionResult();
|
||||
}
|
||||
|
||||
private static ExecutionResult ReturnQirBitcodeBase64(string? outputFilePath, Stream qirStream)
|
||||
{
|
||||
byte[] bytes;
|
||||
using (var memoryStream = new MemoryStream())
|
||||
{
|
||||
qirStream.CopyTo(memoryStream);
|
||||
bytes = memoryStream.ToArray();
|
||||
}
|
||||
string bitchedBase64 = System.Convert.ToBase64String(bytes);
|
||||
|
||||
if (!string.IsNullOrEmpty(outputFilePath))
|
||||
{
|
||||
File.WriteAllText(outputFilePath, bitchedBase64);
|
||||
return new LlvmIr($"QIR {QirOutputFormat.BitcodeBase64} written to {outputFilePath}").ToExecutionResult();
|
||||
}
|
||||
|
||||
return new LlvmIr(bitchedBase64).ToExecutionResult();
|
||||
}
|
||||
|
||||
private static ExecutionResult ReturnQirBitcode(string? outputFilePath, Stream qirStream)
|
||||
{
|
||||
if (string.IsNullOrEmpty(outputFilePath))
|
||||
{
|
||||
return $"Bitcode format can only be written to a file. You must pass the `{ParameterNameOutputFile}` parameter."
|
||||
.ToExecutionResult(ExecuteStatus.Error);
|
||||
}
|
||||
|
||||
using (var outStream = File.OpenWrite(outputFilePath))
|
||||
{
|
||||
qirStream.CopyTo(outStream);
|
||||
}
|
||||
|
||||
return new LlvmIr($"QIR {QirOutputFormat.Bitcode} written to {outputFilePath}").ToExecutionResult();
|
||||
}
|
||||
|
||||
private ExecutionResult ReturnCompilationError(string input, IChannel channel, string? name, CompilationErrorsException exception)
|
||||
{
|
||||
StringBuilder message = new($"The Q# operation {name} could not be compiled as an entry point for job execution.");
|
||||
this.Logger?.LogError(exception, message.ToString());
|
||||
message.AppendLine(exception.Message);
|
||||
|
||||
if (MetadataController.IsPythonUserAgent() || ConfigurationSource.CompilationErrorStyle == CompilationErrorStyle.Basic)
|
||||
{
|
||||
foreach (var m in exception.Errors) message.AppendLine(m);
|
||||
}
|
||||
else
|
||||
{
|
||||
channel.DisplayFancyDiagnostics(exception.Diagnostics, Snippets, input);
|
||||
}
|
||||
|
||||
channel.Stderr(message.ToString());
|
||||
return message.ToString().ToExecutionResult(ExecuteStatus.Error);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -188,8 +188,8 @@ class IQSharpClient(object):
|
|||
def trace(self, op, **kwargs) -> Any:
|
||||
return self._execute_callable_magic('trace', op, _quiet_ = True, **kwargs)
|
||||
|
||||
def compile_to_qir(self, op) -> None:
|
||||
return self._execute_callable_magic('qir', op)
|
||||
def compile_to_qir(self, op, **kwargs) -> None:
|
||||
return self._execute_callable_magic('qir', op, **kwargs)
|
||||
|
||||
def component_versions(self, **kwargs) -> Dict[str, LooseVersion]:
|
||||
"""
|
||||
|
|
|
@ -24,7 +24,7 @@ def register_magics():
|
|||
callables = qs.compile(cell)
|
||||
if isinstance(callables, qs.QSharpCallable):
|
||||
local_ns[callables._name] = callables
|
||||
else:
|
||||
elif callables:
|
||||
for qs_callable in callables:
|
||||
local_ns[qs_callable._name] = qs_callable
|
||||
|
||||
|
|
|
@ -7,20 +7,18 @@
|
|||
# Licensed under the MIT License.
|
||||
##
|
||||
|
||||
import os
|
||||
import sys
|
||||
import logging
|
||||
from types import ModuleType, new_class
|
||||
import importlib
|
||||
from importlib.abc import MetaPathFinder, Loader
|
||||
import tempfile as tf
|
||||
from typing import Iterable, Optional, Any, Dict, Tuple
|
||||
|
||||
import qsharp
|
||||
from qsharp.clients.iqsharp import IQSharpError
|
||||
|
||||
from typing import Iterable, List, Optional, Any, Dict, Tuple
|
||||
|
||||
import logging
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class QSharpModuleFinder(MetaPathFinder):
|
||||
def find_module(self, full_name : str, path : Optional[str] = None) -> Loader:
|
||||
# We expose Q# namespaces as their own root-level packages.
|
||||
|
@ -122,13 +120,58 @@ class QSharpCallable(object):
|
|||
"""
|
||||
return qsharp.client.simulate_noise(self, **kwargs)
|
||||
|
||||
def as_qir(self) -> bytes:
|
||||
def as_qir(self, **kwargs) -> str:
|
||||
"""
|
||||
Returns the QIR representation of the callable,
|
||||
assuming the callable is an entry point.
|
||||
|
||||
This method uses `%qir` magic command, which accepts the
|
||||
following (optional) kwargs parameters:
|
||||
- `target`: The intended execution target for the compiled entrypoint.
|
||||
Defaults to the active Azure Quantum target (which can be set with `%azure.target`).
|
||||
Otherwise, defaults to a generic target, which may not work when running on a specific target.
|
||||
- `target_capability`: The capability of the intended execution target.
|
||||
If `target` is specified or there is an active Azure Quantum target, defaults to the target's maximum capability.
|
||||
Otherwise, defaults to `FullComputation`, which may not be supported when running on a specific target.
|
||||
- `output_format`: the QIR output format. Defaults to `IR`.
|
||||
Possible options are:
|
||||
* `IR`: Human-readable Intermediate Representation in plain-text
|
||||
* `Bitcode`: LLVM bitcode (only when writing to a output file)
|
||||
* `BitcodeBase64`: LLVM bitcode encoded as Base64
|
||||
- `output_file`: The file path for where to save the output QIR.
|
||||
|
||||
:rtype: bytes
|
||||
"""
|
||||
data = qsharp.client.compile_to_qir(self)
|
||||
data = qsharp.client.compile_to_qir(self,
|
||||
**kwargs)
|
||||
if data and ("Text" in data):
|
||||
return data['Text']
|
||||
raise IQSharpError([f'Error in generating QIR. {data}'])
|
||||
|
||||
def _repr_qir_(self, **kwargs: Any) -> bytes:
|
||||
"""
|
||||
Returns the QIR representation of the callable,
|
||||
assuming the callable is an entry point.
|
||||
|
||||
This method uses `%qir` magic command, which accepts the
|
||||
following (optional) kwargs parameters:
|
||||
- `target`: The intended execution target for the compiled entrypoint.
|
||||
Defaults to the active Azure Quantum target (which can be set with `%azure.target`).
|
||||
Otherwise, defaults to a generic target, which may not work when running on a specific target.
|
||||
- `target_capability`: The capability of the intended execution target.
|
||||
If `target` is specified or there is an active Azure Quantum target, defaults to the target's maximum capability.
|
||||
Otherwise, defaults to `FullComputation`, which may not be supported when running on a specific target.
|
||||
|
||||
:rtype: bytes
|
||||
"""
|
||||
# We need to use the Base64 encoding to be able to transfer
|
||||
# the bitcode via the Jupyter protocol
|
||||
qir_bitcodeBase64 = self.as_qir(output_format="BitcodeBase64",
|
||||
kwargs=kwargs)
|
||||
import base64
|
||||
qir_bitcode = base64.b64decode(qir_bitcodeBase64)
|
||||
return qir_bitcode
|
||||
|
||||
|
||||
class QSharpModule(ModuleType):
|
||||
_qs_name : str
|
||||
|
|
|
@ -0,0 +1,61 @@
|
|||
##
|
||||
# Copyright (c) Microsoft Corporation.
|
||||
# Licensed under the MIT License.
|
||||
##
|
||||
|
||||
import unittest
|
||||
import qsharp
|
||||
import pytest
|
||||
|
||||
class TestQir(unittest.TestCase):
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
# A Q# callable that should work in all target capabilities
|
||||
cls.qsharp_callable_basic = qsharp.compile("""
|
||||
open Microsoft.Quantum.Intrinsic;
|
||||
operation GenerateRandomBitBasic() : Result {
|
||||
use qubit = Qubit();
|
||||
H(qubit);
|
||||
return M(qubit);
|
||||
}
|
||||
""")
|
||||
# A Q# callable with a conditional branch that is not
|
||||
# supported in all target capabilities
|
||||
cls.qsharp_callable_advanced = qsharp.compile("""
|
||||
open Microsoft.Quantum.Intrinsic;
|
||||
operation GenerateRandomBitAdvanced() : Result {
|
||||
use qubits = Qubit[2];
|
||||
H(qubits[0]);
|
||||
let r1 = M(qubits[0]);
|
||||
if r1 == One {
|
||||
H(qubits[1]);
|
||||
}
|
||||
return M(qubits[1]);
|
||||
}
|
||||
""")
|
||||
|
||||
def test_as_qir(self):
|
||||
qir_text = self.qsharp_callable_basic.as_qir()
|
||||
self.assertIn("@ENTRYPOINT__GenerateRandomBitBasic", qir_text)
|
||||
|
||||
@pytest.mark.skip(reason="Skipping the tests due to `Unable to find package 'Microsoft.Quantum.Type4.Core'` error.")
|
||||
def test_as_qir_kwargs(self):
|
||||
qir_text = self.qsharp_callable_basic \
|
||||
.as_qir(target="rigetti.simulator")
|
||||
self.assertIn("@ENTRYPOINT__GenerateRandomBitBasic", qir_text)
|
||||
|
||||
qir_text = self.qsharp_callable_basic \
|
||||
.as_qir(target="rigetti.simulator",
|
||||
target_capability="BasicExecution")
|
||||
self.assertIn("@ENTRYPOINT__GenerateRandomBitBasic", qir_text)
|
||||
|
||||
qir_text = self.qsharp_callable_advanced \
|
||||
.as_qir(target="rigetti.simulator",
|
||||
target_capability="FullComputation")
|
||||
self.assertIn("@ENTRYPOINT__GenerateRandomBitAdvanced", qir_text)
|
||||
|
||||
@pytest.mark.skip(reason="Skipping the tests due to `Unable to find package 'Microsoft.Quantum.Type4.Core'` error.")
|
||||
def test_repr_qir_(self):
|
||||
qir_bitcode = self.qsharp_callable_basic._repr_qir_(target="rigetti.simulator",
|
||||
target_capability="BasicExecution")
|
||||
self.assertGreater(len(qir_bitcode), 4)
|
|
@ -15,7 +15,11 @@ import sys
|
|||
def set_environment_variables():
|
||||
'''
|
||||
Sets environment variables for test execution and restarts the IQ# kernel.
|
||||
Also changes the working directory to the qsharp/tests folder
|
||||
so that the `Operations.qs` file will be correctly imported/loaded when
|
||||
the `qsharp` module reloads.
|
||||
'''
|
||||
os.chdir(os.path.dirname(os.path.abspath(__file__)))
|
||||
os.environ["AZURE_QUANTUM_ENV"] = "mock"
|
||||
os.environ["IQSHARP_AUTO_LOAD_PACKAGES"] = "$null"
|
||||
importlib.reload(qsharp)
|
||||
|
|
|
@ -106,11 +106,15 @@ namespace Tests.IQSharp
|
|||
public async Task QIRSubmission()
|
||||
{
|
||||
var entryPointGenerator = Init("Workspace", new string[] { SNIPPETS.HelloQ });
|
||||
var entryPoint = await entryPointGenerator.Generate("HelloQ", null, generateQir: true);
|
||||
var entryPoint = await entryPointGenerator.Generate(operationName: "HelloQ",
|
||||
executionTarget: "MyTarget",
|
||||
capability: TargetCapabilityModule.AdaptiveExecution,
|
||||
generateQir: true);
|
||||
|
||||
Assert.IsNotNull(entryPoint);
|
||||
Assert.AreEqual(TargetCapabilityModule.AdaptiveExecution, entryPoint.TargetCapability);
|
||||
var job = await entryPoint.SubmitAsync(
|
||||
new MockQirSubmitter(new List<Argument>()),
|
||||
new MockQirSubmitter(new List<Argument>(), ExpectedTargetCapability: TargetCapabilityModule.AdaptiveExecution),
|
||||
new AzureSubmissionContext());
|
||||
Assert.IsNotNull(job);
|
||||
}
|
||||
|
|
|
@ -82,8 +82,7 @@ namespace Microsoft.Quantum.IQSharp
|
|||
this.OnServiceInitialized<IWorkspace>();
|
||||
eventService.Events<WorkspaceReadyEvent, IWorkspace>().On += (workspace) =>
|
||||
{
|
||||
var evt = "WorkspaceReady"
|
||||
.AsTelemetryEvent();
|
||||
var evt = "WorkspaceReady".AsTelemetryEvent();
|
||||
TelemetryLogger.LogEvent(evt);
|
||||
};
|
||||
this.OnServiceInitialized<IReferences>();
|
||||
|
@ -123,6 +122,16 @@ namespace Microsoft.Quantum.IQSharp
|
|||
evt.SetProperty("TotalMemoryInGiB".WithTelemetryNamespace(), args.TotalMemoryInGiB?.ToString() ?? "");
|
||||
TelemetryLogger.LogEvent(evt);
|
||||
};
|
||||
eventService.Events<QirMagicEvent, QirMagicEventData>().On += (qirMagicArgs) =>
|
||||
{
|
||||
var evt = qirMagicArgs.AsTelemetryEvent();
|
||||
TelemetryLogger.LogEvent(evt);
|
||||
};
|
||||
eventService.Events<EntryPointSubmitEvent, EntryPointSubmitEventData>().On += (entryPointSubmitEventData) =>
|
||||
{
|
||||
var evt = entryPointSubmitEventData.AsTelemetryEvent();
|
||||
TelemetryLogger.LogEvent(evt);
|
||||
};
|
||||
|
||||
// As each different service starts up, we can subscribe to their
|
||||
// events as well.
|
||||
|
@ -272,6 +281,33 @@ namespace Microsoft.Quantum.IQSharp
|
|||
return evt;
|
||||
}
|
||||
|
||||
public static EventProperties AsTelemetryEvent(this QirMagicEventData qirMagicEventData)
|
||||
{
|
||||
var evt = new EventProperties() { Name = "QirMagic".WithTelemetryNamespace() };
|
||||
evt.SetProperty("CapabilityName".WithTelemetryNamespace(), qirMagicEventData.CapabilityName);
|
||||
evt.SetProperty("CapabilityInsufficient".WithTelemetryNamespace(), qirMagicEventData.CapabilityInsufficient);
|
||||
evt.SetProperty("QirStreamSize".WithTelemetryNamespace(), qirMagicEventData.QirStreamSize?.ToString());
|
||||
evt.SetProperty("Capability".WithTelemetryNamespace(), qirMagicEventData.Capability);
|
||||
evt.SetProperty("CapabilityInsufficient".WithTelemetryNamespace(), qirMagicEventData.CapabilityInsufficient);
|
||||
evt.SetProperty("Error".WithTelemetryNamespace(), qirMagicEventData.Error);
|
||||
evt.SetProperty("InvalidCapability".WithTelemetryNamespace(), qirMagicEventData.InvalidCapability);
|
||||
evt.SetProperty("OutputFormat".WithTelemetryNamespace(), qirMagicEventData.OutputFormat?.ToString());
|
||||
evt.SetProperty("Target".WithTelemetryNamespace(), qirMagicEventData.Target);
|
||||
evt.SetCommonProperties();
|
||||
return evt;
|
||||
}
|
||||
|
||||
public static EventProperties AsTelemetryEvent(this EntryPointSubmitEventData entryPointSubmitEventData)
|
||||
{
|
||||
var evt = new EventProperties() { Name = "EntryPointSubmit".WithTelemetryNamespace() };
|
||||
evt.SetProperty("MachineName".WithTelemetryNamespace(), entryPointSubmitEventData.MachineName);
|
||||
evt.SetProperty("Target".WithTelemetryNamespace(), entryPointSubmitEventData.Target);
|
||||
evt.SetProperty("TargetCapability".WithTelemetryNamespace(), entryPointSubmitEventData.TargetCapability);
|
||||
evt.SetProperty("JobId".WithTelemetryNamespace(), entryPointSubmitEventData.JobId);
|
||||
evt.SetCommonProperties();
|
||||
return evt;
|
||||
}
|
||||
|
||||
public static EventProperties AsTelemetryEvent(this ReloadedEventArgs info)
|
||||
{
|
||||
var evt = new EventProperties() { Name = "WorkspaceReload".WithTelemetryNamespace() };
|
||||
|
|
Загрузка…
Ссылка в новой задаче