Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 7 additions & 1 deletion .github/workflows/rocjitsu-formatting.yml
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# Formatting checks for emulation/rocjitsu/.
# Formatting checks for the emulation/ subtrees.
name: "rocjitsu-formatting"

on:
Expand All @@ -8,7 +8,12 @@ on:
# `pre-commit run --all-files` locally to catch issues outside this
# scope. We should incrementally add more paths here as projects
# adopt pre-commit.
# This job is named `pre-commit`, which is the one entry in
# `checks.required_check_runs` in tools/systems_pr_bot/policy.yml, so a
# path that is missing here leaves therock-pr-bot waiting out its timeout
# on a check run that is never created.
paths:
- "emulation/mirage/**"
- "emulation/rocjitsu/**"
- ".github/workflows/rocjitsu-*.yml"
- ".pre-commit-config.yaml"
Expand All @@ -31,6 +36,7 @@ jobs:
# which files pre-commit can check. To add a new project to this
# CI job, add its path here. That's the only change needed.
sparse-checkout: |
emulation/mirage
emulation/rocjitsu
.github

Expand Down
76 changes: 63 additions & 13 deletions emulation/mirage/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions emulation/mirage/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -158,6 +158,8 @@ tracing-subscriber = { version = "0.3", features = ["env-filter"] }
tempfile = "3"
inventory = "0.3.24"
libloading = "0.8"
goblin = "0.10.7"
rustix = { version = "1", features = ["fs"] }

mirage_core = { path = "core" }
mirage_ctl = { path = "ctl" }
Expand Down
2 changes: 2 additions & 0 deletions emulation/mirage/core/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ nix = { workspace = true }
thiserror = { workspace = true }
tracing = { workspace = true }
inventory = { workspace = true }
goblin = { workspace = true }
rustix = { workspace = true }

[dev-dependencies]
tempfile = { workspace = true }
Expand Down
23 changes: 23 additions & 0 deletions emulation/mirage/core/src/agent.rs
Original file line number Diff line number Diff line change
Expand Up @@ -251,6 +251,20 @@ pub struct VirtualMachineConfig {
pub gpu: AmdgpuConfig,
}

impl KfdDeviceInfo {
/// The gfx target this device presents, or `None` when it names no
/// GPU.
///
/// The authority for the question, because this is where the answer
/// is written down: anything that needs the ISA a session emulates
/// reaches this device and asks, rather than keeping a second copy
/// that can disagree with it.
#[must_use]
pub fn gfx_target(&self) -> Option<crate::hardware::GfxTarget> {
crate::hardware::GfxTarget::new(self.gfx_target_version)
}
}

/// Top-level agent (single-device hardware) definition.
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Default)]
#[serde(deny_unknown_fields)]
Expand All @@ -259,6 +273,15 @@ pub struct AgentDef {
pub topology: AgentTopologyDef,
}

impl AgentDef {
/// The gfx target this agent's GPU presents, or `None` when it names
/// no GPU. See [`KfdDeviceInfo::gfx_target`].
#[must_use]
pub fn gfx_target(&self) -> Option<crate::hardware::GfxTarget> {
self.vm.gpu.device.gfx_target()
}
}

/// On-disk agent store backed by `<MIRAGE_CONFIG>/agent/`.
///
/// Agents may be stored as opaque JSON blobs by external backends
Expand Down
94 changes: 94 additions & 0 deletions emulation/mirage/core/src/emulator.rs
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,50 @@ pub struct EmulatorDef {
pub topology: MaybeRef<TopologyDef>,
}

impl EmulatorDef {
/// The agent every GPU in this system instantiates, when this
/// definition is resolved.
///
/// `None` while either reference is still a name. Resolving one means
/// reading the on-disk stores, which is [`Self::resolve_refs`]'s job
/// and not something an accessor should do behind a caller's back —
/// an accessor that silently hit the filesystem would also silently
/// pick up an edit made after bring-up, which is the whole class of
/// bug a session snapshot exists to prevent.
#[must_use]
pub fn agent(&self) -> Option<&crate::agent::AgentDef> {
match &self.topology {
MaybeRef::Owned(topology) => match &topology.agent {
MaybeRef::Owned(agent) => Some(agent),
MaybeRef::Ref(_) => None,
},
MaybeRef::Ref(_) => None,
}
}

/// Follow every by-name reference in this definition, in place, so
/// that afterwards [`Self::agent`] answers.
///
/// Done once, when a session is created, so the rest of that
/// session's life reads documents nothing can edit underneath it.
///
/// # Errors
///
/// Returns an error when a topology or agent reference names a
/// document that does not exist or does not parse.
pub fn resolve_refs(&mut self) -> Result<()> {
let mut topology = match &self.topology {
MaybeRef::Owned(topology) => topology.clone(),
MaybeRef::Ref(name) => crate::store::topology_get(name)?,
};
if let MaybeRef::Ref(name) = &topology.agent {
topology.agent = MaybeRef::Owned(crate::agent::store::get(name)?);
}
self.topology = MaybeRef::Owned(topology);
Ok(())
}
}

/// Whether the host's hardware/environment can actually run an
/// emulator. This is distinct from [`EmulatorBackend::installed`]:
/// an emulator can be installed yet unsupported (e.g. HotSwap installed
Expand Down Expand Up @@ -231,6 +275,56 @@ pub trait EmulatorBackend: Sync + Send + std::fmt::Debug {
/// runtime is present and responsive.
fn health(&self, ctx: &SessionContext) -> SessionHealth;

/// Whether this backend stands up a synthetic GPU whose ISA the
/// workload's own ROCm runtime has to recognise.
///
/// `false` — the default — for a backend that retargets code onto the
/// host's real GPU. Such a backend still has an agent, and that agent
/// still names a device, but ROCr is never asked to enumerate it, so
/// the ISA says nothing about whether the session will work. A new
/// backend therefore gets silence without having to know that the
/// preflight in [`crate::rocr`] exists.
fn presents_emulated_device(&self) -> bool {
false
}

/// Reconcile `profile` with configuration only this backend can read,
/// so that the profile describes the machine the session will really
/// stand up.
///
/// Called once, on an already-resolved profile, before a session
/// freezes it. The case it exists for is a drop-in emulator config
/// file: the file describes a device of its own, and without this the
/// profile's agent would still name the device the profile was
/// written around — a device that is not the one about to exist.
/// Rather than reporting the real device separately and leaving two
/// answers in play, a backend corrects the agent here and everything
/// downstream keeps reading the one document.
///
/// A backend that cannot tell what the file describes must clear the
/// agent's `gfx_target_version` rather than leave a stale value: no
/// answer is a verdict the preflight handles, and a confident wrong
/// one is not.
///
/// `session_dir` is the session's scratch directory, already created.
/// A backend that reads a file here should snapshot it there and point
/// the profile at the snapshot, so the device it reports and the
/// configuration it later injects come from one reading of the file
/// rather than two.
///
/// # Errors
///
/// Returns an error only when the profile is unusable — not when the
/// backend simply has nothing to correct, which is the default.
fn reconcile_profile(
&self,
profile: &mut ProfileDef,
session_dir: &std::path::Path,
) -> Result<()> {
let _ = (profile, session_dir);
Ok(())
}

/// Compute the env vars / `LD_PRELOAD` / files to inject into a
/// workload run under this emulator. Returns an error when the
/// emulator is selected but its runtime library or assets are
Expand Down
Loading
Loading