diff --git a/doc/user/project/ml/experiment_tracking/mlflow_client.md b/doc/user/project/ml/experiment_tracking/mlflow_client.md
index 477a492f09d35b20d2f8c8feb811d9638f3f5043..c46a084d0833885fbf9e4ee17e6c03f7a3c6604f 100644
--- a/doc/user/project/ml/experiment_tracking/mlflow_client.md
+++ b/doc/user/project/ml/experiment_tracking/mlflow_client.md
@@ -130,7 +130,26 @@ with mlflow.start_run():
     model.fit(X_train, y_train)
 
     # Log the model using MLflow sklearn mode flavour
-    mlflow.sklearn.log_model(model, artifact_path="model")
+    mlflow.sklearn.log_model(model, artifact_path="")
+```
+
+### Loading a run
+
+> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/509595) in GitLab 17.9.
+
+You can load a run from the GitLab model registry to, for example, make predictions.
+
+```python
+import mlflow
+import mlflow.pyfunc
+
+run_id = "<your_run_id>"
+download_path = "models"  # Local folder to download to
+
+mlflow.pyfunc.load_model(f"runs:/{run_id}/", dst_path=download_path)
+
+sample_input = [[1,0,3,4],[2,0,1,2]]
+model.predict(data=sample_input)
 ```
 
 ### Associating a run to a CI/CD job
diff --git a/lib/api/entities/ml/mlflow/run_info.rb b/lib/api/entities/ml/mlflow/run_info.rb
index ee694c16fc9038ca6ac1156234a4652e1aaccb7e..aa6fad4d1509bf1a12f82685916505c225eadf7c 100644
--- a/lib/api/entities/ml/mlflow/run_info.rb
+++ b/lib/api/entities/ml/mlflow/run_info.rb
@@ -21,21 +21,22 @@ class RunInfo < Grape::Entity
           private
 
           CANDIDATE_PREFIX = 'candidate:'
+          MLFLOW_ARTIFACTS_PREFIX = 'mlflow-artifacts'
 
           def run_id
             object.eid.to_s
           end
 
           def artifact_uri
-            uri = if object.package&.generic?
-                    generic_package_uri
-                  elsif object.model_version_id
-                    model_version_uri
-                  else
-                    ml_model_candidate_uri
-                  end
-
-            expose_url(uri)
+            if object.package&.generic?
+              expose_url(generic_package_uri)
+            elsif object.model_version_id
+              expose_url(model_version_uri)
+            elsif object.package&.version&.start_with?('candidate_')
+              "#{MLFLOW_ARTIFACTS_PREFIX}:/#{CANDIDATE_PREFIX}#{object.iid}"
+            else
+              expose_url(ml_model_candidate_uri)
+            end
           end
 
           # Example: http://127.0.0.1:3000/api/v4/projects/20/packages/ml_models/1/files/
diff --git a/lib/api/ml/mlflow/api_helpers.rb b/lib/api/ml/mlflow/api_helpers.rb
index 255388cac80b036ddb6849857165644ef5daa479..ad50bf99c9e0788c7beecd2af4a13b5216636f81 100644
--- a/lib/api/ml/mlflow/api_helpers.rb
+++ b/lib/api/ml/mlflow/api_helpers.rb
@@ -156,6 +156,11 @@ def find_model_artifact(project, version, file_path)
           ::Packages::PackageFileFinder.new(package, file_path).execute || resource_not_found!
         end
 
+        def find_run_artifact(project, version, file_path)
+          package = ::Ml::Candidate.with_project_id_and_iid(project, version).package
+          ::Packages::PackageFileFinder.new(package, file_path).execute || resource_not_found!
+        end
+
         def list_model_artifacts(project, version)
           model_version = ::Ml::ModelVersion.by_project_id_and_id(project, version)
           resource_not_found! unless model_version && model_version.package
@@ -163,9 +168,23 @@ def list_model_artifacts(project, version)
           model_version.package.installable_package_files
         end
 
+        def list_run_artifacts(project, version)
+          run = ::Ml::Candidate.with_project_id_and_iid(project, version)
+
+          resource_not_found! unless run&.package
+
+          run.package.installable_package_files
+        end
+
         def model
           @model ||= find_model(user_project, params[:name])
         end
+
+        def candidate_version?(model_version)
+          return false unless model_version
+
+          model_version&.start_with?('candidate:')
+        end
       end
     end
   end
diff --git a/lib/api/ml/mlflow_artifacts/artifacts.rb b/lib/api/ml/mlflow_artifacts/artifacts.rb
index ff16e39fc2c6650f9af22fc25f1ea0404f314759..f7184bb4a6128a3b97e94ced35090b930ecb52e1 100644
--- a/lib/api/ml/mlflow_artifacts/artifacts.rb
+++ b/lib/api/ml/mlflow_artifacts/artifacts.rb
@@ -6,6 +6,8 @@ module API
   # MLFlow integration API, replicating the Rest API https://www.mlflow.org/docs/latest/rest-api.html#rest-api
   module Ml
     module MlflowArtifacts
+      CANDIDATE_PREFIX = 'candidate:'
+
       class Artifacts < ::API::Base
         feature_category :mlops
         helpers ::API::Helpers::PackagesHelpers
@@ -30,15 +32,25 @@ class Artifacts < ::API::Base
 
           # MLflow handles directories differently than GitLab does so when MLflow checks if a path is a directory
           # we return an empty array as 404s would cause issues for MLflow
-          files = path.present? ? [] : list_model_artifacts(user_project, model_version).all
+          files = if candidate_version?(model_version)
+                    run_version = model_version.delete_prefix(CANDIDATE_PREFIX)
+                    path.present? ? [] : list_run_artifacts(user_project, run_version).all
+                  else
+                    path.present? ? [] : list_model_artifacts(user_project, model_version).all
+                  end
 
           package_files = { files: files }
           present package_files, with: Entities::Ml::MlflowArtifacts::ArtifactsList
         end
 
         get 'artifacts/:model_version/*file_path', format: false, urgency: :low do
-          present_package_file!(find_model_artifact(user_project, params[:model_version],
-            CGI.escape(params[:file_path])))
+          if candidate_version?(params[:model_version])
+            version = params[:model_version].delete_prefix(CANDIDATE_PREFIX)
+            present_package_file!(find_run_artifact(user_project, version, CGI.escape(params[:file_path])))
+          else
+            present_package_file!(find_model_artifact(user_project, params[:model_version],
+              CGI.escape(params[:file_path])))
+          end
         end
       end
     end
diff --git a/spec/lib/api/entities/ml/mlflow/run_info_spec.rb b/spec/lib/api/entities/ml/mlflow/run_info_spec.rb
index 19011b19eaf9fdd117dac70eac842de520746ba9..49a235c25d566bf2d78b61fd8fc75b215a484173 100644
--- a/spec/lib/api/entities/ml/mlflow/run_info_spec.rb
+++ b/spec/lib/api/entities/ml/mlflow/run_info_spec.rb
@@ -88,6 +88,14 @@
         expect(subject[:artifact_uri]).to eq("http://localhost/api/v4/projects/#{candidate.project_id}/packages/generic#{candidate.artifact_root}")
       end
     end
+
+    context 'when candidate has no file or generic package' do
+      let!(:candidate) { create(:ml_candidates, :with_ml_model, name: 'candidate_1') }
+
+      it 'returns a string with no package' do
+        expect(subject[:artifact_uri]).to eq("mlflow-artifacts:/candidate:#{candidate.iid}")
+      end
+    end
   end
 
   describe 'lifecycle_stage' do
diff --git a/spec/lib/api/ml/mlflow/api_helpers_spec.rb b/spec/lib/api/ml/mlflow/api_helpers_spec.rb
index f45fccfba4cf2bf0008b7118b3e6b3994b793e5c..faafc6f2dd08a0771226f0cc2df1203eefbabc25 100644
--- a/spec/lib/api/ml/mlflow/api_helpers_spec.rb
+++ b/spec/lib/api/ml/mlflow/api_helpers_spec.rb
@@ -123,4 +123,52 @@
       end
     end
   end
+
+  describe '#icandidate_version?' do
+    describe 'when version is nil' do
+      let(:version) { nil }
+
+      it 'returns false' do
+        expect(candidate_version?(version)).to be false
+      end
+    end
+
+    describe 'when version has candidate prefix' do
+      let(:version) { 'candidate:1' }
+
+      it 'returns true' do
+        expect(candidate_version?(version)).to be true
+      end
+    end
+
+    describe 'when version does not have candidate prefix' do
+      let(:version) { '1' }
+
+      it 'returns false' do
+        expect(candidate_version?(version)).to be false
+      end
+    end
+  end
+
+  describe '#find_run_artifact' do
+    let_it_be(:project) { create(:project) }
+    let_it_be(:candidate) { create(:ml_candidates, :with_ml_model, project: project) }
+    let_it_be(:candidate_package_file) { create(:package_file, :ml_model, package: candidate.package) }
+
+    it 'returns list of files' do
+      expect(find_run_artifact(project, candidate.iid, candidate_package_file.file_name)).to eq candidate_package_file
+    end
+  end
+
+  describe '#list_run_artifacts' do
+    let_it_be(:project) { create(:project) }
+    let_it_be(:candidate) { create(:ml_candidates, :with_ml_model, project: project) }
+    let_it_be(:candidate_package_file) { create(:package_file, :ml_model, package: candidate.package) }
+    let_it_be(:candidate_package_file_2) { create(:package_file, :ml_model, package: candidate.package) }
+
+    it 'returns list of files' do
+      expect(list_run_artifacts(project,
+        candidate.iid)).to match_array [candidate_package_file, candidate_package_file_2]
+    end
+  end
 end
diff --git a/spec/requests/api/ml/mlflow_artifacts/artifacts_spec.rb b/spec/requests/api/ml/mlflow_artifacts/artifacts_spec.rb
index 7de6ce2e15df7208e574f48dc95cdc669069ec31..8ac8a3414c510aa41a76592ee370ca49f7984b7e 100644
--- a/spec/requests/api/ml/mlflow_artifacts/artifacts_spec.rb
+++ b/spec/requests/api/ml/mlflow_artifacts/artifacts_spec.rb
@@ -13,6 +13,8 @@
   let_it_be(:model_version) { create(:ml_model_versions, :with_package, model: model, version: version) }
   let_it_be(:package_file) { create(:package_file, :ml_model, package: model_version.package) }
   let_it_be(:model_version_no_package) { create(:ml_model_versions, model: model, version: '0.0.2') }
+  let_it_be(:candidate) { create(:ml_candidates, :with_ml_model, project: project) }
+  let_it_be(:candidate_package_file) { create(:package_file, :ml_model, package: candidate.package) }
 
   let_it_be(:tokens) do
     {
@@ -89,6 +91,19 @@
       end
     end
 
+    context 'when the model version is a candidate version' do
+      let(:route) do
+        "/projects/#{project_id}/ml/mlflow/api/2.0/mlflow-artifacts/artifacts?path=candidate:#{candidate.iid}/MlModel"
+      end
+
+      it 'returns an empty list of artifacts', :aggregate_failures do
+        is_expected.to have_gitlab_http_status(:ok)
+        expect(json_response).to have_key('files')
+        expect(json_response['files']).to be_an_instance_of(Array)
+        expect(json_response['files']).to be_empty
+      end
+    end
+
     it_behaves_like 'MLflow|an authenticated resource'
     it_behaves_like 'MLflow|a read-only model registry resource'
   end
@@ -109,6 +124,22 @@
       end
     end
 
+    context 'when the model version is a candidate' do
+      let_it_be(:file) { candidate_package_file.file_name }
+      let(:route) do
+        "/projects/#{project_id}/ml/mlflow/api/2.0/mlflow-artifacts/artifacts/candidate:#{candidate.iid}/#{file}"
+      end
+
+      it 'returns the artifact file', :aggregate_failures do
+        is_expected.to have_gitlab_http_status(:ok)
+        expect(response.headers['Content-Disposition']).to match(
+          "attachment; filename=\"#{candidate_package_file.file_name}\""
+        )
+        expect(response.body).to eq(candidate_package_file.file.read)
+        expect(response.headers['Content-Length']).to eq(candidate_package_file.size.to_s)
+      end
+    end
+
     context 'when the file does not exist' do
       let(:file_path) { 'non_existent_file.txt' }